1#![cfg_attr(not(feature = "std"), no_std)]
18
19extern crate alloc;
20
21use alloc::{vec, vec::Vec};
22use codec::{Decode, Encode};
23use core::{fmt::Debug, marker::PhantomData};
24use frame_support::{
25 dispatch::GetDispatchInfo,
26 ensure,
27 traits::{Contains, ContainsPair, Defensive, Get, PalletsInfoAccess},
28};
29use sp_core::defer;
30use sp_io::hashing::blake2_128;
31use sp_weights::Weight;
32use xcm::latest::{prelude::*, AssetTransferFilter};
33
34pub mod traits;
35use traits::{
36 validate_export, AssetExchange, AssetLock, CallDispatcher, ClaimAssets, ConvertOrigin,
37 DropAssets, Enact, EventEmitter, ExportXcm, FeeManager, FeeReason, HandleHrmpChannelAccepted,
38 HandleHrmpChannelClosing, HandleHrmpNewChannelOpenRequest, OnResponse, ProcessTransaction,
39 Properties, ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader,
40 XcmAssetTransfers,
41};
42
43pub use traits::RecordXcm;
44
45mod assets;
46pub use assets::AssetsInHolding;
47mod config;
48pub use config::Config;
49
50#[cfg(test)]
51mod tests;
52
53#[derive(Copy, Clone, Debug, PartialEq, Eq)]
55pub struct FeesMode {
56 pub jit_withdraw: bool,
61}
62
63pub const RECURSION_LIMIT: u8 = 10;
70
71environmental::environmental!(recursion_count: u8);
72
73pub struct XcmExecutor<Config: config::Config> {
75 holding: AssetsInHolding,
76 holding_limit: usize,
77 context: XcmContext,
78 original_origin: Location,
79 trader: Config::Trader,
80 error: Option<(u32, XcmError)>,
83 total_surplus: Weight,
88 total_refunded: Weight,
89 error_handler: Xcm<Config::RuntimeCall>,
90 error_handler_weight: Weight,
91 appendix: Xcm<Config::RuntimeCall>,
92 appendix_weight: Weight,
93 transact_status: MaybeErrorCode,
94 fees_mode: FeesMode,
95 fees: AssetsInHolding,
96 asset_used_in_buy_execution: Option<AssetId>,
99 message_weight: Weight,
101 asset_claimer: Option<Location>,
102 already_paid_fees: bool,
103 _config: PhantomData<Config>,
104}
105
106#[cfg(any(test, feature = "runtime-benchmarks"))]
107impl<Config: config::Config> XcmExecutor<Config> {
108 pub fn holding(&self) -> &AssetsInHolding {
109 &self.holding
110 }
111 pub fn set_holding(&mut self, v: AssetsInHolding) {
112 self.holding = v
113 }
114 pub fn holding_limit(&self) -> &usize {
115 &self.holding_limit
116 }
117 pub fn set_holding_limit(&mut self, v: usize) {
118 self.holding_limit = v
119 }
120 pub fn origin(&self) -> &Option<Location> {
121 &self.context.origin
122 }
123 pub fn set_origin(&mut self, v: Option<Location>) {
124 self.context.origin = v
125 }
126 pub fn original_origin(&self) -> &Location {
127 &self.original_origin
128 }
129 pub fn set_original_origin(&mut self, v: Location) {
130 self.original_origin = v
131 }
132 pub fn trader(&self) -> &Config::Trader {
133 &self.trader
134 }
135 pub fn set_trader(&mut self, v: Config::Trader) {
136 self.trader = v
137 }
138 pub fn error(&self) -> &Option<(u32, XcmError)> {
139 &self.error
140 }
141 pub fn set_error(&mut self, v: Option<(u32, XcmError)>) {
142 self.error = v
143 }
144 pub fn total_surplus(&self) -> &Weight {
145 &self.total_surplus
146 }
147 pub fn set_total_surplus(&mut self, v: Weight) {
148 self.total_surplus = v
149 }
150 pub fn total_refunded(&self) -> &Weight {
151 &self.total_refunded
152 }
153 pub fn set_total_refunded(&mut self, v: Weight) {
154 self.total_refunded = v
155 }
156 pub fn error_handler(&self) -> &Xcm<Config::RuntimeCall> {
157 &self.error_handler
158 }
159 pub fn set_error_handler(&mut self, v: Xcm<Config::RuntimeCall>) {
160 self.error_handler = v
161 }
162 pub fn error_handler_weight(&self) -> &Weight {
163 &self.error_handler_weight
164 }
165 pub fn set_error_handler_weight(&mut self, v: Weight) {
166 self.error_handler_weight = v
167 }
168 pub fn appendix(&self) -> &Xcm<Config::RuntimeCall> {
169 &self.appendix
170 }
171 pub fn set_appendix(&mut self, v: Xcm<Config::RuntimeCall>) {
172 self.appendix = v
173 }
174 pub fn appendix_weight(&self) -> &Weight {
175 &self.appendix_weight
176 }
177 pub fn set_appendix_weight(&mut self, v: Weight) {
178 self.appendix_weight = v
179 }
180 pub fn transact_status(&self) -> &MaybeErrorCode {
181 &self.transact_status
182 }
183 pub fn set_transact_status(&mut self, v: MaybeErrorCode) {
184 self.transact_status = v
185 }
186 pub fn fees_mode(&self) -> &FeesMode {
187 &self.fees_mode
188 }
189 pub fn set_fees_mode(&mut self, v: FeesMode) {
190 self.fees_mode = v
191 }
192 pub fn fees(&self) -> &AssetsInHolding {
193 &self.fees
194 }
195 pub fn set_fees(&mut self, value: AssetsInHolding) {
196 self.fees = value;
197 }
198 pub fn topic(&self) -> &Option<[u8; 32]> {
199 &self.context.topic
200 }
201 pub fn set_topic(&mut self, v: Option<[u8; 32]>) {
202 self.context.topic = v;
203 }
204 pub fn asset_claimer(&self) -> Option<Location> {
205 self.asset_claimer.clone()
206 }
207 pub fn set_message_weight(&mut self, weight: Weight) {
208 self.message_weight = weight;
209 }
210 pub fn already_paid_fees(&self) -> bool {
211 self.already_paid_fees
212 }
213}
214
215pub struct WeighedMessage<Call>(Weight, Xcm<Call>);
216impl<C> PreparedMessage for WeighedMessage<C> {
217 fn weight_of(&self) -> Weight {
218 self.0
219 }
220}
221
222#[cfg(any(test, feature = "std"))]
223impl<C> WeighedMessage<C> {
224 pub fn new(weight: Weight, message: Xcm<C>) -> Self {
225 Self(weight, message)
226 }
227}
228
229impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Config> {
230 type Prepared = WeighedMessage<Config::RuntimeCall>;
231 fn prepare(
232 mut message: Xcm<Config::RuntimeCall>,
233 weight_limit: Weight,
234 ) -> Result<Self::Prepared, InstructionError> {
235 match Config::Weigher::weight(&mut message, weight_limit) {
236 Ok(weight) => Ok(WeighedMessage(weight, message)),
237 Err(error) => {
238 tracing::debug!(
239 target: "xcm::prepare",
240 ?error,
241 ?message,
242 "Failed to calculate weight for XCM message; execution aborted"
243 );
244 Err(error)
245 },
246 }
247 }
248 fn execute(
249 origin: impl Into<Location>,
250 WeighedMessage(xcm_weight, mut message): WeighedMessage<Config::RuntimeCall>,
251 id: &mut XcmHash,
252 weight_credit: Weight,
253 ) -> Outcome {
254 let origin = origin.into();
255 tracing::trace!(
256 target: "xcm::execute",
257 ?origin,
258 ?message,
259 ?id,
260 ?weight_credit,
261 "Executing message",
262 );
263 let mut properties = Properties { weight_credit, message_id: None };
264
265 if Config::XcmRecorder::should_record() {
268 Config::XcmRecorder::record(message.clone().into());
269 }
270
271 if let Err(e) = Config::Barrier::should_execute(
272 &origin,
273 message.inner_mut(),
274 xcm_weight,
275 &mut properties,
276 ) {
277 tracing::trace!(
278 target: "xcm::execute",
279 ?origin,
280 ?message,
281 ?properties,
282 error = ?e,
283 "Barrier blocked execution",
284 );
285
286 return Outcome::Incomplete {
287 used: xcm_weight, error: InstructionError { index: 0, error: XcmError::Barrier }, };
290 }
291
292 *id = properties.message_id.unwrap_or(*id);
293
294 let mut vm = Self::new(origin, *id);
295 vm.message_weight = xcm_weight;
296
297 while !message.0.is_empty() {
298 let result = vm.process(message);
299 tracing::trace!(target: "xcm::execute", ?result, "Message executed");
300 message = if let Err(error) = result {
301 vm.total_surplus.saturating_accrue(error.weight);
302 vm.error = Some((error.index, error.xcm_error));
303 vm.take_error_handler().or_else(|| vm.take_appendix())
304 } else {
305 vm.drop_error_handler();
306 vm.take_appendix()
307 }
308 }
309
310 vm.post_process(xcm_weight)
311 }
312
313 fn charge_fees(origin: impl Into<Location>, fees: Assets) -> XcmResult {
314 let origin = origin.into();
315 if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) {
316 for asset in fees.inner() {
317 Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?;
318 }
319 Config::FeeManager::handle_fee(fees.into(), None, FeeReason::ChargeFees);
320 }
321 Ok(())
322 }
323}
324
325impl<Config: config::Config> XcmAssetTransfers for XcmExecutor<Config> {
326 type IsReserve = Config::IsReserve;
327 type IsTeleporter = Config::IsTeleporter;
328 type AssetTransactor = Config::AssetTransactor;
329}
330
331impl<Config: config::Config> FeeManager for XcmExecutor<Config> {
332 fn is_waived(origin: Option<&Location>, r: FeeReason) -> bool {
333 Config::FeeManager::is_waived(origin, r)
334 }
335
336 fn handle_fee(fee: Assets, context: Option<&XcmContext>, r: FeeReason) {
337 Config::FeeManager::handle_fee(fee, context, r)
338 }
339}
340
341#[derive(Debug, PartialEq)]
342pub struct ExecutorError {
343 pub index: u32,
344 pub xcm_error: XcmError,
345 pub weight: Weight,
346}
347
348#[cfg(feature = "runtime-benchmarks")]
349impl From<ExecutorError> for frame_benchmarking::BenchmarkError {
350 fn from(error: ExecutorError) -> Self {
351 tracing::error!(
352 index = ?error.index,
353 xcm_error = ?error.xcm_error,
354 weight = ?error.weight,
355 "XCM ERROR",
356 );
357 Self::Stop("xcm executor error: see error logs")
358 }
359}
360
361impl<Config: config::Config> XcmExecutor<Config> {
362 pub fn new(origin: impl Into<Location>, message_id: XcmHash) -> Self {
363 let origin = origin.into();
364 Self {
365 holding: AssetsInHolding::new(),
366 holding_limit: Config::MaxAssetsIntoHolding::get() as usize,
367 context: XcmContext { origin: Some(origin.clone()), message_id, topic: None },
368 original_origin: origin,
369 trader: Config::Trader::new(),
370 error: None,
371 total_surplus: Weight::zero(),
372 total_refunded: Weight::zero(),
373 error_handler: Xcm(vec![]),
374 error_handler_weight: Weight::zero(),
375 appendix: Xcm(vec![]),
376 appendix_weight: Weight::zero(),
377 transact_status: Default::default(),
378 fees_mode: FeesMode { jit_withdraw: false },
379 fees: AssetsInHolding::new(),
380 asset_used_in_buy_execution: None,
381 message_weight: Weight::zero(),
382 asset_claimer: None,
383 already_paid_fees: false,
384 _config: PhantomData,
385 }
386 }
387
388 pub fn post_process(mut self, xcm_weight: Weight) -> Outcome {
392 let _ = self.refund_surplus();
395 drop(self.trader);
396
397 let mut weight_used = xcm_weight.saturating_sub(self.total_surplus);
398
399 if !self.holding.is_empty() {
400 tracing::trace!(
401 target: "xcm::post_process",
402 holding_register = ?self.holding,
403 context = ?self.context,
404 original_origin = ?self.original_origin,
405 "Trapping assets in holding register",
406 );
407 let claimer = if let Some(asset_claimer) = self.asset_claimer.as_ref() {
408 asset_claimer
409 } else {
410 self.context.origin.as_ref().unwrap_or(&self.original_origin)
411 };
412 let trap_weight = Config::AssetTrap::drop_assets(claimer, self.holding, &self.context);
413 weight_used.saturating_accrue(trap_weight);
414 };
415
416 match self.error {
417 None => Outcome::Complete { used: weight_used },
418 Some((index, error)) => {
421 tracing::trace!(
422 target: "xcm::post_process",
423 instruction = ?index,
424 ?error,
425 original_origin = ?self.original_origin,
426 "Execution failed",
427 );
428 Outcome::Incomplete {
429 used: weight_used,
430 error: InstructionError { index: index.try_into().unwrap_or(u8::MAX), error },
431 }
432 },
433 }
434 }
435
436 fn origin_ref(&self) -> Option<&Location> {
437 self.context.origin.as_ref()
438 }
439
440 fn cloned_origin(&self) -> Option<Location> {
441 self.context.origin.clone()
442 }
443
444 fn send(
446 &mut self,
447 dest: Location,
448 msg: Xcm<()>,
449 reason: FeeReason,
450 ) -> Result<XcmHash, XcmError> {
451 let mut msg = msg;
452 if !matches!(msg.last(), Some(SetTopic(_))) {
456 let topic_id = self.context.topic_or_message_id();
457 msg.0.push(SetTopic(topic_id.into()));
458 }
459 tracing::trace!(
460 target: "xcm::send",
461 ?msg,
462 destination = ?dest,
463 reason = ?reason,
464 "Sending msg",
465 );
466 let (ticket, fee) = validate_send::<Config::XcmSender>(dest.clone(), msg)?;
467 self.take_fee(fee, reason)?;
468 match Config::XcmSender::deliver(ticket) {
469 Ok(message_id) => {
470 Config::XcmEventEmitter::emit_sent_event(
471 self.original_origin.clone(),
472 dest,
473 None, message_id,
476 );
477 Ok(message_id)
478 },
479 Err(error) => {
480 tracing::debug!(target: "xcm::send", ?error, "XCM failed to deliver with error");
481 Config::XcmEventEmitter::emit_send_failure_event(
482 self.original_origin.clone(),
483 dest,
484 error.clone(),
485 self.context.topic_or_message_id(),
486 );
487 Err(error.into())
488 },
489 }
490 }
491
492 fn take_error_handler(&mut self) -> Xcm<Config::RuntimeCall> {
494 let mut r = Xcm::<Config::RuntimeCall>(vec![]);
495 core::mem::swap(&mut self.error_handler, &mut r);
496 self.error_handler_weight = Weight::zero();
497 r
498 }
499
500 fn drop_error_handler(&mut self) {
502 self.error_handler = Xcm::<Config::RuntimeCall>(vec![]);
503 self.total_surplus.saturating_accrue(self.error_handler_weight);
504 self.error_handler_weight = Weight::zero();
505 }
506
507 fn take_appendix(&mut self) -> Xcm<Config::RuntimeCall> {
509 let mut r = Xcm::<Config::RuntimeCall>(vec![]);
510 core::mem::swap(&mut self.appendix, &mut r);
511 self.appendix_weight = Weight::zero();
512 r
513 }
514
515 fn ensure_can_subsume_assets(&self, assets_length: usize) -> Result<(), XcmError> {
516 let worst_case_holding_len = self.holding.len() + assets_length;
521 tracing::trace!(
522 target: "xcm::ensure_can_subsume_assets",
523 ?worst_case_holding_len,
524 holding_limit = ?self.holding_limit,
525 "Ensuring subsume assets work",
526 );
527 ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow);
528 Ok(())
529 }
530
531 fn refund_surplus(&mut self) -> Result<(), XcmError> {
533 let current_surplus = self.total_surplus.saturating_sub(self.total_refunded);
534 tracing::trace!(
535 target: "xcm::refund_surplus",
536 total_surplus = ?self.total_surplus,
537 total_refunded = ?self.total_refunded,
538 ?current_surplus,
539 "Refunding surplus",
540 );
541 if current_surplus.any_gt(Weight::zero()) {
542 if let Some(w) = self.trader.refund_weight(current_surplus, &self.context) {
543 if !self.holding.contains_asset(&(w.id.clone(), 1).into()) &&
544 self.ensure_can_subsume_assets(1).is_err()
545 {
546 let _ = self
547 .trader
548 .buy_weight(current_surplus, w.into(), &self.context)
549 .defensive_proof(
550 "refund_weight returned an asset capable of buying weight; qed",
551 );
552 tracing::error!(
553 target: "xcm::refund_surplus",
554 "error: HoldingWouldOverflow",
555 );
556 return Err(XcmError::HoldingWouldOverflow);
557 }
558 self.total_refunded.saturating_accrue(current_surplus);
559 self.holding.subsume_assets(w.into());
560 }
561 }
562 if !self.fees.is_empty() {
564 let leftover_fees = self.fees.saturating_take(Wild(All));
565 tracing::trace!(
566 target: "xcm::refund_surplus",
567 ?leftover_fees,
568 );
569 self.holding.subsume_assets(leftover_fees);
570 }
571 tracing::trace!(
572 target: "xcm::refund_surplus",
573 total_refunded = ?self.total_refunded,
574 );
575 Ok(())
576 }
577
578 fn take_fee(&mut self, fees: Assets, reason: FeeReason) -> XcmResult {
579 if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) {
580 return Ok(());
581 }
582 tracing::trace!(
583 target: "xcm::fees",
584 ?fees,
585 origin_ref = ?self.origin_ref(),
586 fees_mode = ?self.fees_mode,
587 ?reason,
588 "Taking fees",
589 );
590 let asset_needed_for_fees = match fees.get(0) {
592 Some(fee) => fee,
593 None => return Ok(()), };
595 let asset_to_pay_for_fees =
597 self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
598 tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees);
599 let withdrawn_fee_asset: AssetsInHolding = if self.fees_mode.jit_withdraw {
601 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
602 Config::AssetTransactor::withdraw_asset(
603 &asset_to_pay_for_fees,
604 origin,
605 Some(&self.context),
606 )?;
607 tracing::trace!(target: "xcm::fees", ?asset_needed_for_fees);
608 asset_to_pay_for_fees.clone().into()
609 } else {
610 let assets_to_pay_delivery_fees: AssetsInHolding = if self.fees.is_empty() {
613 self.holding
615 .try_take(asset_to_pay_for_fees.clone().into())
616 .map_err(|e| {
617 tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees,
618 "Holding doesn't hold enough for fees");
619 XcmError::NotHoldingFees
620 })?
621 .into()
622 } else {
623 self.fees
625 .try_take(asset_to_pay_for_fees.clone().into())
626 .map_err(|e| {
627 tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees,
628 "Fees register doesn't hold enough for fees");
629 XcmError::NotHoldingFees
630 })?
631 .into()
632 };
633 tracing::trace!(target: "xcm::fees", ?assets_to_pay_delivery_fees);
634 let mut iter = assets_to_pay_delivery_fees.fungible_assets_iter();
635 let asset = iter.next().ok_or(XcmError::NotHoldingFees)?;
636 asset.into()
637 };
638 let paid = if asset_to_pay_for_fees.id != asset_needed_for_fees.id {
640 let swapped_asset: Assets = Config::AssetExchanger::exchange_asset(
641 self.origin_ref(),
642 withdrawn_fee_asset.clone().into(),
643 &asset_needed_for_fees.clone().into(),
644 false,
645 )
646 .map_err(|given_assets| {
647 tracing::error!(
648 target: "xcm::fees",
649 ?given_assets, "Swap was deemed necessary but couldn't be done for withdrawn_fee_asset: {:?} and asset_needed_for_fees: {:?}", withdrawn_fee_asset.clone(), asset_needed_for_fees,
650 );
651 XcmError::FeesNotMet
652 })?
653 .into();
654 swapped_asset
655 } else {
656 withdrawn_fee_asset.into()
660 };
661 Config::FeeManager::handle_fee(paid, Some(&self.context), reason);
662 Ok(())
663 }
664
665 fn calculate_asset_for_delivery_fees(&self, asset_needed_for_fees: Asset) -> Asset {
672 let Some(asset_wanted_for_fees) =
673 self.fees.fungible.first_key_value().map(|(id, _)| id).or_else(|| {
675 self.asset_used_in_buy_execution.as_ref()
677 })
678 .filter(|&id| asset_needed_for_fees.id.ne(id))
680 else {
681 return asset_needed_for_fees
683 };
684 Config::AssetExchanger::quote_exchange_price(
685 &(asset_wanted_for_fees.clone(), Fungible(0)).into(),
686 &asset_needed_for_fees.clone().into(),
687 false, )
689 .and_then(|necessary_assets| {
690 necessary_assets.into_inner().into_iter().next()
694 })
695 .unwrap_or_else(|| {
696 tracing::trace!(
699 target: "xcm::calculate_asset_for_delivery_fees",
700 ?asset_wanted_for_fees, "Could not convert fees",
701 );
702 asset_needed_for_fees
703 })
704 }
705
706 fn to_querier(
708 local_querier: Option<Location>,
709 destination: &Location,
710 ) -> Result<Option<Location>, XcmError> {
711 Ok(match local_querier {
712 None => None,
713 Some(q) => Some(
714 q.reanchored(&destination, &Config::UniversalLocation::get()).map_err(|e| {
715 tracing::error!(target: "xcm::xcm_executor::to_querier", ?e, ?destination, "Failed to re-anchor local_querier");
716 XcmError::ReanchorFailed
717 })?,
718 ),
719 })
720 }
721
722 fn respond(
726 &mut self,
727 local_querier: Option<Location>,
728 response: Response,
729 info: QueryResponseInfo,
730 fee_reason: FeeReason,
731 ) -> Result<XcmHash, XcmError> {
732 let querier = Self::to_querier(local_querier, &info.destination)?;
733 let QueryResponseInfo { destination, query_id, max_weight } = info;
734 let instruction = QueryResponse { query_id, response, max_weight, querier };
735 let message = Xcm(vec![instruction]);
736 self.send(destination, message, fee_reason)
737 }
738
739 fn do_reserve_deposit_assets(
740 assets: AssetsInHolding,
741 dest: &Location,
742 remote_xcm: &mut Vec<Instruction<()>>,
743 context: Option<&XcmContext>,
744 ) -> Result<Assets, XcmError> {
745 Self::deposit_assets_with_retry(&assets, dest, context)?;
746 let reanchored_assets = Self::reanchored(assets, dest, None);
750 remote_xcm.push(ReserveAssetDeposited(reanchored_assets.clone()));
751
752 Ok(reanchored_assets)
753 }
754
755 fn do_reserve_withdraw_assets(
756 assets: AssetsInHolding,
757 failed_bin: &mut AssetsInHolding,
758 reserve: &Location,
759 remote_xcm: &mut Vec<Instruction<()>>,
760 ) -> Result<Assets, XcmError> {
761 #[cfg(not(any(test, feature = "runtime-benchmarks")))]
763 for asset in assets.assets_iter() {
764 ensure!(
765 Config::IsReserve::contains(&asset, &reserve),
766 XcmError::UntrustedReserveLocation
767 );
768 }
769 let reanchored_assets = Self::reanchored(assets, reserve, Some(failed_bin));
772 remote_xcm.push(WithdrawAsset(reanchored_assets.clone()));
773
774 Ok(reanchored_assets)
775 }
776
777 fn do_teleport_assets(
778 assets: AssetsInHolding,
779 dest: &Location,
780 remote_xcm: &mut Vec<Instruction<()>>,
781 context: &XcmContext,
782 ) -> Result<Assets, XcmError> {
783 for asset in assets.assets_iter() {
784 #[cfg(not(any(test, feature = "runtime-benchmarks")))]
786 ensure!(
787 Config::IsTeleporter::contains(&asset, &dest),
788 XcmError::UntrustedTeleportLocation
789 );
790 Config::AssetTransactor::can_check_out(dest, &asset, context)?;
795 }
796 for asset in assets.assets_iter() {
797 Config::AssetTransactor::check_out(dest, &asset, context);
798 }
799 let reanchored_assets = Self::reanchored(assets, dest, None);
802 remote_xcm.push(ReceiveTeleportedAsset(reanchored_assets.clone()));
803
804 Ok(reanchored_assets)
805 }
806
807 fn try_reanchor<T: Reanchorable>(
808 reanchorable: T,
809 destination: &Location,
810 ) -> Result<(T, InteriorLocation), XcmError> {
811 let reanchor_context = Config::UniversalLocation::get();
812 let reanchored =
813 reanchorable.reanchored(&destination, &reanchor_context).map_err(|error| {
814 tracing::error!(target: "xcm::reanchor", ?error, ?destination, ?reanchor_context, "Failed reanchoring with error.");
815 XcmError::ReanchorFailed
816 })?;
817 Ok((reanchored, reanchor_context))
818 }
819
820 fn reanchored(
822 mut assets: AssetsInHolding,
823 dest: &Location,
824 maybe_failed_bin: Option<&mut AssetsInHolding>,
825 ) -> Assets {
826 let reanchor_context = Config::UniversalLocation::get();
827 assets.reanchor(dest, &reanchor_context, maybe_failed_bin);
828 assets.into_assets_iter().collect::<Vec<_>>().into()
829 }
830
831 #[cfg(any(test, feature = "runtime-benchmarks"))]
832 pub fn bench_process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
833 self.process(xcm)
834 }
835
836 #[cfg(any(test, feature = "runtime-benchmarks"))]
837 pub fn bench_post_process(self, xcm_weight: Weight) -> Outcome {
838 self.post_process(xcm_weight)
839 }
840
841 fn process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
842 tracing::trace!(
843 target: "xcm::process",
844 origin = ?self.origin_ref(),
845 total_surplus = ?self.total_surplus,
846 total_refunded = ?self.total_refunded,
847 error_handler_weight = ?self.error_handler_weight,
848 );
849 let mut result = Ok(());
850 for (i, mut instr) in xcm.0.into_iter().enumerate() {
851 match &mut result {
852 r @ Ok(()) => {
853 let inst_res = recursion_count::using_once(&mut 1, || {
856 recursion_count::with(|count| {
857 if *count > RECURSION_LIMIT {
858 return None;
859 }
860 *count = count.saturating_add(1);
861 Some(())
862 })
863 .flatten()
864 .ok_or(XcmError::ExceedsStackLimit)?;
865
866 defer! {
869 recursion_count::with(|count| {
870 *count = count.saturating_sub(1);
871 });
872 }
873
874 self.process_instruction(instr)
875 });
876 if let Err(error) = inst_res {
877 tracing::debug!(
878 target: "xcm::process",
879 ?error, "XCM execution failed at instruction index={i}"
880 );
881 Config::XcmEventEmitter::emit_process_failure_event(
882 self.original_origin.clone(),
883 error,
884 self.context.topic_or_message_id(),
885 );
886 *r = Err(ExecutorError {
887 index: i as u32,
888 xcm_error: error,
889 weight: Weight::zero(),
890 });
891 }
892 },
893 Err(ref mut error) =>
894 if let Ok(x) = Config::Weigher::instr_weight(&mut instr) {
895 error.weight.saturating_accrue(x)
896 },
897 }
898 }
899 result
900 }
901
902 fn process_instruction(
904 &mut self,
905 instr: Instruction<Config::RuntimeCall>,
906 ) -> Result<(), XcmError> {
907 tracing::trace!(
908 target: "xcm::process_instruction",
909 instruction = ?instr,
910 "Processing instruction",
911 );
912
913 match instr {
914 WithdrawAsset(assets) => {
915 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
916 self.ensure_can_subsume_assets(assets.len())?;
917 let mut total_surplus = Weight::zero();
918 Config::TransactionalProcessor::process(|| {
919 for asset in assets.inner() {
921 let (_, surplus) = Config::AssetTransactor::withdraw_asset_with_surplus(
922 asset,
923 origin,
924 Some(&self.context),
925 )?;
926 total_surplus.saturating_accrue(surplus);
928 }
929 Ok(())
930 })
931 .and_then(|_| {
932 self.holding.subsume_assets(assets.into());
934 self.total_surplus.saturating_accrue(total_surplus);
936 Ok(())
937 })
938 },
939 ReserveAssetDeposited(assets) => {
940 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
942 self.ensure_can_subsume_assets(assets.len())?;
943 for asset in assets.inner() {
944 ensure!(
946 Config::IsReserve::contains(asset, origin),
947 XcmError::UntrustedReserveLocation
948 );
949 }
950 self.holding.subsume_assets(assets.into());
951 Ok(())
952 },
953 TransferAsset { assets, beneficiary } => {
954 Config::TransactionalProcessor::process(|| {
955 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
957 let mut total_surplus = Weight::zero();
958 for asset in assets.inner() {
959 let (_, surplus) = Config::AssetTransactor::transfer_asset_with_surplus(
960 &asset,
961 origin,
962 &beneficiary,
963 &self.context,
964 )?;
965 total_surplus.saturating_accrue(surplus);
967 }
968 self.total_surplus.saturating_accrue(total_surplus);
970 Ok(())
971 })
972 },
973 TransferReserveAsset { mut assets, dest, xcm } => {
974 Config::TransactionalProcessor::process(|| {
975 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
976 let mut total_surplus = Weight::zero();
977 for asset in assets.inner() {
979 let (_, surplus) = Config::AssetTransactor::transfer_asset_with_surplus(
980 asset,
981 origin,
982 &dest,
983 &self.context,
984 )?;
985 total_surplus.saturating_accrue(surplus);
987 }
988 let reanchor_context = Config::UniversalLocation::get();
989 assets
990 .reanchor(&dest, &reanchor_context)
991 .map_err(|()| {
992 tracing::debug!(
993 target: "xcm::process_instruction::transfer_reserve_asset",
994 ?assets,
995 ?dest,
996 ?reanchor_context,
997 "Failed to reanchor assets to destination in context"
998 );
999 XcmError::LocationFull
1000 })?;
1001 let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin];
1002 message.extend(xcm.0.into_iter());
1003 self.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?;
1004 self.total_surplus.saturating_accrue(total_surplus);
1006 Ok(())
1007 })
1008 },
1009 ReceiveTeleportedAsset(assets) => {
1010 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1011 self.ensure_can_subsume_assets(assets.len())?;
1012 Config::TransactionalProcessor::process(|| {
1013 for asset in assets.inner() {
1015 ensure!(
1018 Config::IsTeleporter::contains(asset, origin),
1019 XcmError::UntrustedTeleportLocation
1020 );
1021 Config::AssetTransactor::can_check_in(origin, asset, &self.context)?;
1026 Config::AssetTransactor::check_in(origin, asset, &self.context);
1027 }
1028 Ok(())
1029 })
1030 .and_then(|_| {
1031 self.holding.subsume_assets(assets.into());
1032 Ok(())
1033 })
1034 },
1035 Transact { origin_kind, mut call, .. } => {
1037 let origin = self.cloned_origin().ok_or_else(|| {
1038 tracing::trace!(
1039 target: "xcm::process_instruction::transact",
1040 "No origin provided",
1041 );
1042
1043 XcmError::BadOrigin
1044 })?;
1045
1046 let message_call = call.take_decoded().map_err(|_| {
1047 tracing::trace!(
1048 target: "xcm::process_instruction::transact",
1049 "Failed to decode call",
1050 );
1051
1052 XcmError::FailedToDecode
1053 })?;
1054
1055 tracing::trace!(
1056 target: "xcm::process_instruction::transact",
1057 ?call,
1058 "Processing call",
1059 );
1060
1061 if !Config::SafeCallFilter::contains(&message_call) {
1062 tracing::trace!(
1063 target: "xcm::process_instruction::transact",
1064 "Call filtered by `SafeCallFilter`",
1065 );
1066
1067 return Err(XcmError::NoPermission)
1068 }
1069
1070 let dispatch_origin =
1071 Config::OriginConverter::convert_origin(origin.clone(), origin_kind).map_err(
1072 |_| {
1073 tracing::trace!(
1074 target: "xcm::process_instruction::transact",
1075 ?origin,
1076 ?origin_kind,
1077 "Failed to convert origin to a local origin."
1078 );
1079
1080 XcmError::BadOrigin
1081 },
1082 )?;
1083
1084 tracing::trace!(
1085 target: "xcm::process_instruction::transact",
1086 origin = ?dispatch_origin,
1087 call = ?message_call,
1088 "Dispatching call with origin",
1089 );
1090
1091 let weight = message_call.get_dispatch_info().call_weight;
1092 let maybe_actual_weight =
1093 match Config::CallDispatcher::dispatch(message_call, dispatch_origin) {
1094 Ok(post_info) => {
1095 tracing::trace!(
1096 target: "xcm::process_instruction::transact",
1097 ?post_info,
1098 "Dispatch successful"
1099 );
1100 self.transact_status = MaybeErrorCode::Success;
1101 post_info.actual_weight
1102 },
1103 Err(error_and_info) => {
1104 tracing::trace!(
1105 target: "xcm::process_instruction::transact",
1106 ?error_and_info,
1107 "Dispatch failed"
1108 );
1109
1110 self.transact_status = error_and_info.error.encode().into();
1111 error_and_info.post_info.actual_weight
1112 },
1113 };
1114 let actual_weight = maybe_actual_weight.unwrap_or(weight);
1115 let surplus = weight.saturating_sub(actual_weight);
1116 self.total_surplus.saturating_accrue(surplus);
1123 Ok(())
1124 },
1125 QueryResponse { query_id, response, max_weight, querier } => {
1126 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1127 Config::ResponseHandler::on_response(
1128 origin,
1129 query_id,
1130 querier.as_ref(),
1131 response,
1132 max_weight,
1133 &self.context,
1134 );
1135 Ok(())
1136 },
1137 DescendOrigin(who) => self.do_descend_origin(who),
1138 ClearOrigin => self.do_clear_origin(),
1139 ExecuteWithOrigin { .. } => Err(XcmError::Unimplemented),
1140 ReportError(response_info) => {
1141 self.respond(
1144 self.cloned_origin(),
1145 Response::ExecutionResult(self.error),
1146 response_info,
1147 FeeReason::Report,
1148 )?;
1149 Ok(())
1150 },
1151 DepositAsset { assets, beneficiary } => {
1152 let old_holding = self.holding.clone();
1153 let result = Config::TransactionalProcessor::process(|| {
1154 let deposited = self.holding.saturating_take(assets);
1155 let surplus = Self::deposit_assets_with_retry(&deposited, &beneficiary, Some(&self.context))?;
1156 self.total_surplus.saturating_accrue(surplus);
1157 Ok(())
1158 });
1159 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1160 self.holding = old_holding;
1161 }
1162 result
1163 },
1164 DepositReserveAsset { assets, dest, xcm } => {
1165 let old_holding = self.holding.clone();
1166 let result = Config::TransactionalProcessor::process(|| {
1167 let mut assets = self.holding.saturating_take(assets);
1168 let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
1171 self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::DepositReserveAsset, &xcm)?
1173 } else {
1174 None
1175 };
1176 let mut message = Vec::with_capacity(xcm.len() + 2);
1177 tracing::trace!(target: "xcm::DepositReserveAsset", ?assets, "Assets except delivery fee");
1178 Self::do_reserve_deposit_assets(
1179 assets,
1180 &dest,
1181 &mut message,
1182 Some(&self.context),
1183 )?;
1184 message.push(ClearOrigin);
1186 message.extend(xcm.0.into_iter());
1188 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1189 self.holding.subsume_assets(delivery_fee);
1191 }
1192 self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?;
1193 Ok(())
1194 });
1195 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1196 self.holding = old_holding;
1197 }
1198 result
1199 },
1200 InitiateReserveWithdraw { assets, reserve, xcm } => {
1201 let old_holding = self.holding.clone();
1202 let result = Config::TransactionalProcessor::process(|| {
1203 let mut assets = self.holding.saturating_take(assets);
1204 let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
1207 self.take_delivery_fee_from_assets(&mut assets, &reserve, FeeReason::InitiateReserveWithdraw, &xcm)?
1209 } else {
1210 None
1211 };
1212 let mut message = Vec::with_capacity(xcm.len() + 2);
1213 Self::do_reserve_withdraw_assets(
1214 assets,
1215 &mut self.holding,
1216 &reserve,
1217 &mut message,
1218 )?;
1219 message.push(ClearOrigin);
1221 message.extend(xcm.0.into_iter());
1223 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1224 self.holding.subsume_assets(delivery_fee);
1226 }
1227 self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?;
1228 Ok(())
1229 });
1230 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1231 self.holding = old_holding;
1232 }
1233 result
1234 },
1235 InitiateTeleport { assets, dest, xcm } => {
1236 let old_holding = self.holding.clone();
1237 let result = Config::TransactionalProcessor::process(|| {
1238 let mut assets = self.holding.saturating_take(assets);
1239 let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
1242 self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::InitiateTeleport, &xcm)?
1244 } else {
1245 None
1246 };
1247 let mut message = Vec::with_capacity(xcm.len() + 2);
1248 Self::do_teleport_assets(assets, &dest, &mut message, &self.context)?;
1249 message.push(ClearOrigin);
1251 message.extend(xcm.0.into_iter());
1253 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1254 self.holding.subsume_assets(delivery_fee);
1256 }
1257 self.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?;
1258 Ok(())
1259 });
1260 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1261 self.holding = old_holding;
1262 }
1263 result
1264 },
1265 InitiateTransfer { destination, remote_fees, preserve_origin, assets, remote_xcm } => {
1266 let old_holding = self.holding.clone();
1267 let result = Config::TransactionalProcessor::process(|| {
1268 let mut message = Vec::with_capacity(assets.len() + remote_xcm.len() + 2);
1269
1270 let remote_fees_paid = if let Some(remote_fees) = remote_fees {
1275 let reanchored_fees = match remote_fees {
1276 AssetTransferFilter::Teleport(fees_filter) => {
1277 let teleport_fees = self
1278 .holding
1279 .try_take(fees_filter)
1280 .map_err(|error| {
1281 tracing::debug!(
1282 target: "xcm::process_instruction::initiate_transfer", ?error,
1283 "Failed to take specified teleport fees from holding"
1284 );
1285 XcmError::NotHoldingFees
1286 })?;
1287 Self::do_teleport_assets(
1288 teleport_fees,
1289 &destination,
1290 &mut message,
1291 &self.context,
1292 )?
1293 },
1294 AssetTransferFilter::ReserveDeposit(fees_filter) => {
1295 let reserve_deposit_fees = self
1296 .holding
1297 .try_take(fees_filter)
1298 .map_err(|error| {
1299 tracing::debug!(
1300 target: "xcm::process_instruction::initiate_transfer", ?error,
1301 "Failed to take specified reserve deposit fees from holding"
1302 );
1303 XcmError::NotHoldingFees
1304 })?;
1305 Self::do_reserve_deposit_assets(
1306 reserve_deposit_fees,
1307 &destination,
1308 &mut message,
1309 Some(&self.context),
1310 )?
1311 },
1312 AssetTransferFilter::ReserveWithdraw(fees_filter) => {
1313 let reserve_withdraw_fees = self
1314 .holding
1315 .try_take(fees_filter)
1316 .map_err(|error| {
1317 tracing::debug!(
1318 target: "xcm::process_instruction::initiate_transfer", ?error,
1319 "Failed to take specified reserve withdraw fees from holding"
1320 );
1321 XcmError::NotHoldingFees
1322 })?;
1323 Self::do_reserve_withdraw_assets(
1324 reserve_withdraw_fees,
1325 &mut self.holding,
1326 &destination,
1327 &mut message,
1328 )?
1329 },
1330 };
1331 ensure!(reanchored_fees.len() == 1, XcmError::TooManyAssets);
1332 let fees =
1333 reanchored_fees.into_inner().pop().ok_or(XcmError::NotHoldingFees)?;
1334 message.push(PayFees { asset: fees });
1337 true
1338 } else {
1339 false
1340 };
1341
1342 for asset_filter in assets {
1344 match asset_filter {
1345 AssetTransferFilter::Teleport(assets) => Self::do_teleport_assets(
1346 self.holding.saturating_take(assets),
1347 &destination,
1348 &mut message,
1349 &self.context,
1350 )?,
1351 AssetTransferFilter::ReserveDeposit(assets) =>
1352 Self::do_reserve_deposit_assets(
1353 self.holding.saturating_take(assets),
1354 &destination,
1355 &mut message,
1356 Some(&self.context),
1357 )?,
1358 AssetTransferFilter::ReserveWithdraw(assets) =>
1359 Self::do_reserve_withdraw_assets(
1360 self.holding.saturating_take(assets),
1361 &mut self.holding,
1362 &destination,
1363 &mut message,
1364 )?,
1365 };
1366 }
1367
1368 if preserve_origin {
1369 if let Some(original_origin) = self
1371 .origin_ref()
1372 .filter(|origin| *origin != &Location::here())
1373 .cloned()
1374 {
1375 let reanchored_origin = Self::try_reanchor(original_origin, &destination)?.0;
1378 message.push(AliasOrigin(reanchored_origin));
1379 }
1380 } else {
1381 message.push(ClearOrigin);
1383 }
1384
1385 if !remote_fees_paid {
1389 message
1394 .push(UnpaidExecution { weight_limit: Unlimited, check_origin: None });
1395 }
1396
1397 message.extend(remote_xcm.0.into_iter());
1399 self.send(destination, Xcm(message), FeeReason::InitiateTransfer)?;
1401 Ok(())
1402 });
1403 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1404 self.holding = old_holding;
1405 }
1406 result
1407 },
1408 ReportHolding { response_info, assets } => {
1409 let assets =
1412 Self::reanchored(self.holding.min(&assets), &response_info.destination, None);
1413 self.respond(
1414 self.cloned_origin(),
1415 Response::Assets(assets),
1416 response_info,
1417 FeeReason::Report,
1418 )?;
1419 Ok(())
1420 },
1421 BuyExecution { fees, weight_limit } => {
1422 let Some(weight) = Option::<Weight>::from(weight_limit) else { return Ok(()) };
1427 let old_holding = self.holding.clone();
1428 self.asset_used_in_buy_execution = Some(fees.id.clone());
1431 tracing::trace!(
1432 target: "xcm::executor::BuyExecution",
1433 asset_used_in_buy_execution = ?self.asset_used_in_buy_execution
1434 );
1435 let max_fee =
1437 self.holding.try_take(fees.clone().into()).map_err(|e| {
1438 tracing::error!(target: "xcm::process_instruction::buy_execution", ?e, ?fees,
1439 "Failed to take fees from holding");
1440 XcmError::NotHoldingFees
1441 })?;
1442 let result = Config::TransactionalProcessor::process(|| {
1443 let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?;
1444 self.holding.subsume_assets(unspent);
1445 Ok(())
1446 });
1447 if result.is_err() {
1448 self.holding = old_holding;
1449 }
1450 result
1451 },
1452 PayFees { asset } => {
1453 if self.already_paid_fees {
1455 return Ok(());
1456 }
1457 self.already_paid_fees = true;
1459 let old_holding = self.holding.clone();
1461 tracing::trace!(
1463 target: "xcm::executor::PayFees",
1464 asset_for_fees = ?asset,
1465 message_weight = ?self.message_weight,
1466 );
1467 let result = Config::TransactionalProcessor::process(|| {
1469 let max_fee =
1470 self.holding.try_take(asset.into()).map_err(|error| {
1471 tracing::debug!(
1472 target: "xcm::process_instruction::pay_fees", ?error,
1473 "Failed to take fees from holding"
1474 );
1475 XcmError::NotHoldingFees
1476 })?;
1477 let unspent =
1478 self.trader.buy_weight(self.message_weight, max_fee, &self.context)?;
1479 self.fees.subsume_assets(unspent);
1482 Ok(())
1483 });
1484 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1485 self.holding = old_holding;
1487 self.already_paid_fees = false;
1488 }
1489 result
1490 },
1491 RefundSurplus => self.refund_surplus(),
1492 SetErrorHandler(mut handler) => {
1493 let handler_weight = Config::Weigher::weight(&mut handler, Weight::MAX)
1494 .map_err(|error| {
1495 tracing::debug!(
1496 target: "xcm::executor::SetErrorHandler",
1497 ?error,
1498 ?handler,
1499 "Failed to calculate weight"
1500 );
1501 XcmError::WeightNotComputable
1502 })?;
1503 self.total_surplus.saturating_accrue(self.error_handler_weight);
1504 self.error_handler = handler;
1505 self.error_handler_weight = handler_weight;
1506 Ok(())
1507 },
1508 SetAppendix(mut appendix) => {
1509 let appendix_weight = Config::Weigher::weight(&mut appendix, Weight::MAX)
1510 .map_err(|error| {
1511 tracing::debug!(
1512 target: "xcm::executor::SetErrorHandler",
1513 ?error,
1514 ?appendix,
1515 "Failed to calculate weight"
1516 );
1517 XcmError::WeightNotComputable
1518 })?;
1519 self.total_surplus.saturating_accrue(self.appendix_weight);
1520 self.appendix = appendix;
1521 self.appendix_weight = appendix_weight;
1522 Ok(())
1523 },
1524 ClearError => {
1525 self.error = None;
1526 Ok(())
1527 },
1528 SetHints { hints } => {
1529 for hint in hints.into_iter() {
1530 match hint {
1531 AssetClaimer { location } => {
1532 self.asset_claimer = Some(location)
1533 },
1534 }
1535 }
1536 Ok(())
1537 },
1538 ClaimAsset { assets, ticket } => {
1539 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1540 self.ensure_can_subsume_assets(assets.len())?;
1541 let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context);
1542 ensure!(ok, XcmError::UnknownClaim);
1543 self.holding.subsume_assets(assets.into());
1544 Ok(())
1545 },
1546 Trap(code) => Err(XcmError::Trap(code)),
1547 SubscribeVersion { query_id, max_response_weight } => {
1548 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1549 ensure!(&self.original_origin == origin, XcmError::BadOrigin);
1552 Config::SubscriptionService::start(
1553 origin,
1554 query_id,
1555 max_response_weight,
1556 &self.context,
1557 )
1558 },
1559 UnsubscribeVersion => {
1560 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1561 ensure!(&self.original_origin == origin, XcmError::BadOrigin);
1562 Config::SubscriptionService::stop(origin, &self.context)
1563 },
1564 BurnAsset(assets) => {
1565 self.holding.saturating_take(assets.into());
1566 Ok(())
1567 },
1568 ExpectAsset(assets) =>
1569 self.holding.ensure_contains(&assets).map_err(|e| {
1570 tracing::error!(target: "xcm::process_instruction::expect_asset", ?e, ?assets, "assets not contained in holding");
1571 XcmError::ExpectationFalse
1572 }),
1573 ExpectOrigin(origin) => {
1574 ensure!(self.context.origin == origin, XcmError::ExpectationFalse);
1575 Ok(())
1576 },
1577 ExpectError(error) => {
1578 ensure!(self.error == error, XcmError::ExpectationFalse);
1579 Ok(())
1580 },
1581 ExpectTransactStatus(transact_status) => {
1582 ensure!(self.transact_status == transact_status, XcmError::ExpectationFalse);
1583 Ok(())
1584 },
1585 QueryPallet { module_name, response_info } => {
1586 let pallets = Config::PalletInstancesInfo::infos()
1587 .into_iter()
1588 .filter(|x| x.module_name.as_bytes() == &module_name[..])
1589 .map(|x| {
1590 PalletInfo::new(
1591 x.index as u32,
1592 x.name.as_bytes().into(),
1593 x.module_name.as_bytes().into(),
1594 x.crate_version.major as u32,
1595 x.crate_version.minor as u32,
1596 x.crate_version.patch as u32,
1597 )
1598 })
1599 .collect::<Result<Vec<_>, XcmError>>()?;
1600 let QueryResponseInfo { destination, query_id, max_weight } = response_info;
1601 let response =
1602 Response::PalletsInfo(pallets.try_into().map_err(|error| {
1603 tracing::debug!(
1604 target: "xcm::process_instruction::query_pallet", ?error,
1605 "Failed to convert pallets to response info"
1606 );
1607 XcmError::Overflow
1608 })?);
1609 let querier = Self::to_querier(self.cloned_origin(), &destination)?;
1610 let instruction = QueryResponse { query_id, response, max_weight, querier };
1611 let message = Xcm(vec![instruction]);
1612 self.send(destination, message, FeeReason::QueryPallet)?;
1613 Ok(())
1614 },
1615 ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => {
1616 let pallet = Config::PalletInstancesInfo::infos()
1617 .into_iter()
1618 .find(|x| x.index == index as usize)
1619 .ok_or(XcmError::PalletNotFound)?;
1620 ensure!(pallet.name.as_bytes() == &name[..], XcmError::NameMismatch);
1621 ensure!(pallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch);
1622 let major = pallet.crate_version.major as u32;
1623 ensure!(major == crate_major, XcmError::VersionIncompatible);
1624 let minor = pallet.crate_version.minor as u32;
1625 ensure!(minor >= min_crate_minor, XcmError::VersionIncompatible);
1626 Ok(())
1627 },
1628 ReportTransactStatus(response_info) => {
1629 self.respond(
1630 self.cloned_origin(),
1631 Response::DispatchResult(self.transact_status.clone()),
1632 response_info,
1633 FeeReason::Report,
1634 )?;
1635 Ok(())
1636 },
1637 ClearTransactStatus => {
1638 self.transact_status = Default::default();
1639 Ok(())
1640 },
1641 UniversalOrigin(new_global) => {
1642 let universal_location = Config::UniversalLocation::get();
1643 ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation);
1644 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1645 let origin_xform = (origin, new_global);
1646 let ok = Config::UniversalAliases::contains(&origin_xform);
1647 ensure!(ok, XcmError::InvalidLocation);
1648 let (_, new_global) = origin_xform;
1649 let new_origin = Junctions::from([new_global]).relative_to(&universal_location);
1650 self.context.origin = Some(new_origin);
1651 Ok(())
1652 },
1653 ExportMessage { network, destination, xcm } => {
1654 let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone();
1664 let universal_source = Config::UniversalLocation::get()
1665 .within_global(origin)
1666 .map_err(|()| {
1667 tracing::debug!(
1668 target: "xcm::process_instruction::export_message",
1669 "Failed to reanchor origin to universal location",
1670 );
1671 XcmError::Unanchored
1672 })?;
1673 let hash = (self.origin_ref(), &destination).using_encoded(blake2_128);
1674 let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0);
1675 let (ticket, fee) = validate_export::<Config::MessageExporter>(
1679 network,
1680 channel,
1681 universal_source,
1682 destination.clone(),
1683 xcm,
1684 )?;
1685 let old_holding = self.holding.clone();
1686 let result = Config::TransactionalProcessor::process(|| {
1687 self.take_fee(fee, FeeReason::Export { network, destination })?;
1688 let _ = Config::MessageExporter::deliver(ticket).defensive_proof(
1689 "`deliver` called immediately after `validate_export`; \
1690 `take_fee` does not affect the validity of the ticket; qed",
1691 );
1692 Ok(())
1693 });
1694 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1695 self.holding = old_holding;
1696 }
1697 result
1698 },
1699 LockAsset { asset, unlocker } => {
1700 let old_holding = self.holding.clone();
1701 let result = Config::TransactionalProcessor::process(|| {
1702 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1703 let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?;
1704 let lock_ticket =
1705 Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?;
1706 let owner = origin.reanchored(&unlocker, &context).map_err(|e| {
1707 tracing::error!(target: "xcm::xcm_executor::process_instruction", ?e, ?unlocker, ?context, "Failed to re-anchor origin");
1708 XcmError::ReanchorFailed
1709 })?;
1710 let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]);
1711 let (ticket, price) = validate_send::<Config::XcmSender>(unlocker, msg)?;
1712 self.take_fee(price, FeeReason::LockAsset)?;
1713 lock_ticket.enact()?;
1714 Config::XcmSender::deliver(ticket)?;
1715 Ok(())
1716 });
1717 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1718 self.holding = old_holding;
1719 }
1720 result
1721 },
1722 UnlockAsset { asset, target } => {
1723 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1724 Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?;
1725 Ok(())
1726 },
1727 NoteUnlockable { asset, owner } => {
1728 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1729 Config::AssetLocker::note_unlockable(origin, asset, owner)?;
1730 Ok(())
1731 },
1732 RequestUnlock { asset, locker } => {
1733 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1734 let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0;
1735 let remote_target = Self::try_reanchor(origin.clone(), &locker)?.0;
1736 let reduce_ticket = Config::AssetLocker::prepare_reduce_unlockable(
1737 locker.clone(),
1738 asset,
1739 origin.clone(),
1740 )?;
1741 let msg =
1742 Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]);
1743 let (ticket, price) = validate_send::<Config::XcmSender>(locker, msg)?;
1744 let old_holding = self.holding.clone();
1745 let result = Config::TransactionalProcessor::process(|| {
1746 self.take_fee(price, FeeReason::RequestUnlock)?;
1747 reduce_ticket.enact()?;
1748 Config::XcmSender::deliver(ticket)?;
1749 Ok(())
1750 });
1751 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1752 self.holding = old_holding;
1753 }
1754 result
1755 },
1756 ExchangeAsset { give, want, maximal } => {
1757 let old_holding = self.holding.clone();
1758 let give = self.holding.saturating_take(give);
1759 let result = Config::TransactionalProcessor::process(|| {
1760 self.ensure_can_subsume_assets(want.len())?;
1761 let exchange_result = Config::AssetExchanger::exchange_asset(
1762 self.origin_ref(),
1763 give,
1764 &want,
1765 maximal,
1766 );
1767 if let Ok(received) = exchange_result {
1768 self.holding.subsume_assets(received.into());
1769 Ok(())
1770 } else {
1771 Err(XcmError::NoDeal)
1772 }
1773 });
1774 if result.is_err() {
1775 self.holding = old_holding;
1776 }
1777 result
1778 },
1779 SetFeesMode { jit_withdraw } => {
1780 self.fees_mode = FeesMode { jit_withdraw };
1781 Ok(())
1782 },
1783 SetTopic(topic) => {
1784 self.context.topic = Some(topic);
1785 Ok(())
1786 },
1787 ClearTopic => {
1788 self.context.topic = None;
1789 Ok(())
1790 },
1791 AliasOrigin(target) => {
1792 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1793 if Config::Aliasers::contains(origin, &target) {
1794 self.context.origin = Some(target);
1795 Ok(())
1796 } else {
1797 Err(XcmError::NoPermission)
1798 }
1799 },
1800 UnpaidExecution { check_origin, .. } => {
1801 ensure!(
1802 check_origin.is_none() || self.context.origin == check_origin,
1803 XcmError::BadOrigin
1804 );
1805 Ok(())
1806 },
1807 HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
1808 Config::TransactionalProcessor::process(|| {
1809 Config::HrmpNewChannelOpenRequestHandler::handle(
1810 sender,
1811 max_message_size,
1812 max_capacity,
1813 )
1814 }),
1815 HrmpChannelAccepted { recipient } => Config::TransactionalProcessor::process(|| {
1816 Config::HrmpChannelAcceptedHandler::handle(recipient)
1817 }),
1818 HrmpChannelClosing { initiator, sender, recipient } =>
1819 Config::TransactionalProcessor::process(|| {
1820 Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient)
1821 }),
1822 }
1823 }
1824
1825 fn do_descend_origin(&mut self, who: InteriorLocation) -> XcmResult {
1826 self.context
1827 .origin
1828 .as_mut()
1829 .ok_or(XcmError::BadOrigin)?
1830 .append_with(who)
1831 .map_err(|e| {
1832 tracing::error!(target: "xcm::do_descend_origin", ?e, "Failed to append junctions");
1833 XcmError::LocationFull
1834 })
1835 }
1836
1837 fn do_clear_origin(&mut self) -> XcmResult {
1838 self.context.origin = None;
1839 Ok(())
1840 }
1841
1842 fn deposit_assets_with_retry(
1856 to_deposit: &AssetsInHolding,
1857 beneficiary: &Location,
1858 context: Option<&XcmContext>,
1859 ) -> Result<Weight, XcmError> {
1860 let mut total_surplus = Weight::zero();
1861 let mut failed_deposits = Vec::with_capacity(to_deposit.len());
1862 for asset in to_deposit.assets_iter() {
1863 match Config::AssetTransactor::deposit_asset_with_surplus(&asset, &beneficiary, context)
1864 {
1865 Ok(surplus) => {
1866 total_surplus.saturating_accrue(surplus);
1867 },
1868 Err(_) => {
1869 failed_deposits.push(asset);
1871 },
1872 }
1873 }
1874 tracing::trace!(
1875 target: "xcm::deposit_assets_with_retry",
1876 ?failed_deposits,
1877 "First‐pass failures, about to retry"
1878 );
1879 for asset in failed_deposits {
1881 match Config::AssetTransactor::deposit_asset_with_surplus(&asset, &beneficiary, context)
1882 {
1883 Ok(surplus) => {
1884 total_surplus.saturating_accrue(surplus);
1885 },
1886 Err(error) => {
1887 if !matches!(
1889 error,
1890 XcmError::FailedToTransactAsset(string)
1891 if *string == *<&'static str>::from(sp_runtime::TokenError::BelowMinimum)
1892 ) {
1893 return Err(error);
1894 }
1895 },
1896 };
1897 }
1898 Ok(total_surplus)
1899 }
1900
1901 fn take_delivery_fee_from_assets(
1906 &self,
1907 assets: &mut AssetsInHolding,
1908 destination: &Location,
1909 reason: FeeReason,
1910 xcm: &Xcm<()>,
1911 ) -> Result<Option<AssetsInHolding>, XcmError> {
1912 let to_weigh = assets.clone();
1913 let to_weigh_reanchored = Self::reanchored(to_weigh, &destination, None);
1914 let remote_instruction = match reason {
1915 FeeReason::DepositReserveAsset => ReserveAssetDeposited(to_weigh_reanchored),
1916 FeeReason::InitiateReserveWithdraw => WithdrawAsset(to_weigh_reanchored),
1917 FeeReason::InitiateTeleport => ReceiveTeleportedAsset(to_weigh_reanchored),
1918 _ => {
1919 tracing::debug!(
1920 target: "xcm::take_delivery_fee_from_assets",
1921 "Unexpected delivery fee reason",
1922 );
1923 return Err(XcmError::NotHoldingFees);
1924 },
1925 };
1926 let mut message_to_weigh = Vec::with_capacity(xcm.len() + 2);
1927 message_to_weigh.push(remote_instruction);
1928 message_to_weigh.push(ClearOrigin);
1929 message_to_weigh.extend(xcm.0.clone().into_iter());
1930 let (_, fee) =
1931 validate_send::<Config::XcmSender>(destination.clone(), Xcm(message_to_weigh))?;
1932 let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| {
1933 tracing::trace!(
1934 target: "xcm::fees::take_delivery_fee_from_assets",
1935 "Asset provided to pay for fees {:?}, asset required for delivery fees: {:?}",
1936 self.asset_used_in_buy_execution, asset_needed_for_fees,
1937 );
1938 let asset_to_pay_for_fees =
1939 self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
1940 let delivery_fee = assets.saturating_take(asset_to_pay_for_fees.into());
1942 tracing::trace!(target: "xcm::fees::take_delivery_fee_from_assets", ?delivery_fee);
1943 delivery_fee
1944 });
1945 Ok(maybe_delivery_fee)
1946 }
1947}