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(|| {
1039 tracing::trace!(
1040 target: "xcm::process_instruction::transact",
1041 "No origin provided",
1042 );
1043
1044 XcmError::BadOrigin
1045 })?;
1046
1047 let message_call = call.take_decoded().map_err(|_| {
1049 tracing::trace!(
1050 target: "xcm::process_instruction::transact",
1051 "Failed to decode call",
1052 );
1053
1054 XcmError::FailedToDecode
1055 })?;
1056
1057 tracing::trace!(
1058 target: "xcm::process_instruction::transact",
1059 ?call,
1060 "Processing call",
1061 );
1062
1063 if !Config::SafeCallFilter::contains(&message_call) {
1064 tracing::trace!(
1065 target: "xcm::process_instruction::transact",
1066 "Call filtered by `SafeCallFilter`",
1067 );
1068
1069 return Err(XcmError::NoPermission)
1070 }
1071
1072 let dispatch_origin =
1073 Config::OriginConverter::convert_origin(origin.clone(), origin_kind).map_err(
1074 |_| {
1075 tracing::trace!(
1076 target: "xcm::process_instruction::transact",
1077 ?origin,
1078 ?origin_kind,
1079 "Failed to convert origin to a local origin."
1080 );
1081
1082 XcmError::BadOrigin
1083 },
1084 )?;
1085
1086 tracing::trace!(
1087 target: "xcm::process_instruction::transact",
1088 origin = ?dispatch_origin,
1089 "Dispatching with origin",
1090 );
1091
1092 let weight = message_call.get_dispatch_info().call_weight;
1093 let maybe_actual_weight =
1094 match Config::CallDispatcher::dispatch(message_call, dispatch_origin) {
1095 Ok(post_info) => {
1096 tracing::trace!(
1097 target: "xcm::process_instruction::transact",
1098 ?post_info,
1099 "Dispatch successful"
1100 );
1101 self.transact_status = MaybeErrorCode::Success;
1102 post_info.actual_weight
1103 },
1104 Err(error_and_info) => {
1105 tracing::trace!(
1106 target: "xcm::process_instruction::transact",
1107 ?error_and_info,
1108 "Dispatch failed"
1109 );
1110
1111 self.transact_status = error_and_info.error.encode().into();
1112 error_and_info.post_info.actual_weight
1113 },
1114 };
1115 let actual_weight = maybe_actual_weight.unwrap_or(weight);
1116 let surplus = weight.saturating_sub(actual_weight);
1117 self.total_surplus.saturating_accrue(surplus);
1124 Ok(())
1125 },
1126 QueryResponse { query_id, response, max_weight, querier } => {
1127 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1128 Config::ResponseHandler::on_response(
1129 origin,
1130 query_id,
1131 querier.as_ref(),
1132 response,
1133 max_weight,
1134 &self.context,
1135 );
1136 Ok(())
1137 },
1138 DescendOrigin(who) => self.do_descend_origin(who),
1139 ClearOrigin => self.do_clear_origin(),
1140 ExecuteWithOrigin { .. } => Err(XcmError::Unimplemented),
1141 ReportError(response_info) => {
1142 self.respond(
1145 self.cloned_origin(),
1146 Response::ExecutionResult(self.error),
1147 response_info,
1148 FeeReason::Report,
1149 )?;
1150 Ok(())
1151 },
1152 DepositAsset { assets, beneficiary } => {
1153 let old_holding = self.holding.clone();
1154 let result = Config::TransactionalProcessor::process(|| {
1155 let deposited = self.holding.saturating_take(assets);
1156 let surplus = Self::deposit_assets_with_retry(&deposited, &beneficiary, Some(&self.context))?;
1157 self.total_surplus.saturating_accrue(surplus);
1158 Ok(())
1159 });
1160 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1161 self.holding = old_holding;
1162 }
1163 result
1164 },
1165 DepositReserveAsset { assets, dest, xcm } => {
1166 let old_holding = self.holding.clone();
1167 let result = Config::TransactionalProcessor::process(|| {
1168 let mut assets = self.holding.saturating_take(assets);
1169 let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
1172 self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::DepositReserveAsset, &xcm)?
1174 } else {
1175 None
1176 };
1177 let mut message = Vec::with_capacity(xcm.len() + 2);
1178 tracing::trace!(target: "xcm::DepositReserveAsset", ?assets, "Assets except delivery fee");
1179 Self::do_reserve_deposit_assets(
1180 assets,
1181 &dest,
1182 &mut message,
1183 Some(&self.context),
1184 )?;
1185 message.push(ClearOrigin);
1187 message.extend(xcm.0.into_iter());
1189 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1190 self.holding.subsume_assets(delivery_fee);
1192 }
1193 self.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?;
1194 Ok(())
1195 });
1196 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1197 self.holding = old_holding;
1198 }
1199 result
1200 },
1201 InitiateReserveWithdraw { assets, reserve, xcm } => {
1202 let old_holding = self.holding.clone();
1203 let result = Config::TransactionalProcessor::process(|| {
1204 let mut assets = self.holding.saturating_take(assets);
1205 let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
1208 self.take_delivery_fee_from_assets(&mut assets, &reserve, FeeReason::InitiateReserveWithdraw, &xcm)?
1210 } else {
1211 None
1212 };
1213 let mut message = Vec::with_capacity(xcm.len() + 2);
1214 Self::do_reserve_withdraw_assets(
1215 assets,
1216 &mut self.holding,
1217 &reserve,
1218 &mut message,
1219 )?;
1220 message.push(ClearOrigin);
1222 message.extend(xcm.0.into_iter());
1224 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1225 self.holding.subsume_assets(delivery_fee);
1227 }
1228 self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?;
1229 Ok(())
1230 });
1231 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1232 self.holding = old_holding;
1233 }
1234 result
1235 },
1236 InitiateTeleport { assets, dest, xcm } => {
1237 let old_holding = self.holding.clone();
1238 let result = Config::TransactionalProcessor::process(|| {
1239 let mut assets = self.holding.saturating_take(assets);
1240 let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw {
1243 self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::InitiateTeleport, &xcm)?
1245 } else {
1246 None
1247 };
1248 let mut message = Vec::with_capacity(xcm.len() + 2);
1249 Self::do_teleport_assets(assets, &dest, &mut message, &self.context)?;
1250 message.push(ClearOrigin);
1252 message.extend(xcm.0.into_iter());
1254 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1255 self.holding.subsume_assets(delivery_fee);
1257 }
1258 self.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?;
1259 Ok(())
1260 });
1261 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1262 self.holding = old_holding;
1263 }
1264 result
1265 },
1266 InitiateTransfer { destination, remote_fees, preserve_origin, assets, remote_xcm } => {
1267 let old_holding = self.holding.clone();
1268 let result = Config::TransactionalProcessor::process(|| {
1269 let mut message = Vec::with_capacity(assets.len() + remote_xcm.len() + 2);
1270
1271 let remote_fees_paid = if let Some(remote_fees) = remote_fees {
1276 let reanchored_fees = match remote_fees {
1277 AssetTransferFilter::Teleport(fees_filter) => {
1278 let teleport_fees = self
1279 .holding
1280 .try_take(fees_filter)
1281 .map_err(|error| {
1282 tracing::debug!(
1283 target: "xcm::process_instruction::initiate_transfer", ?error,
1284 "Failed to take specified teleport fees from holding"
1285 );
1286 XcmError::NotHoldingFees
1287 })?;
1288 Self::do_teleport_assets(
1289 teleport_fees,
1290 &destination,
1291 &mut message,
1292 &self.context,
1293 )?
1294 },
1295 AssetTransferFilter::ReserveDeposit(fees_filter) => {
1296 let reserve_deposit_fees = self
1297 .holding
1298 .try_take(fees_filter)
1299 .map_err(|error| {
1300 tracing::debug!(
1301 target: "xcm::process_instruction::initiate_transfer", ?error,
1302 "Failed to take specified reserve deposit fees from holding"
1303 );
1304 XcmError::NotHoldingFees
1305 })?;
1306 Self::do_reserve_deposit_assets(
1307 reserve_deposit_fees,
1308 &destination,
1309 &mut message,
1310 Some(&self.context),
1311 )?
1312 },
1313 AssetTransferFilter::ReserveWithdraw(fees_filter) => {
1314 let reserve_withdraw_fees = self
1315 .holding
1316 .try_take(fees_filter)
1317 .map_err(|error| {
1318 tracing::debug!(
1319 target: "xcm::process_instruction::initiate_transfer", ?error,
1320 "Failed to take specified reserve withdraw fees from holding"
1321 );
1322 XcmError::NotHoldingFees
1323 })?;
1324 Self::do_reserve_withdraw_assets(
1325 reserve_withdraw_fees,
1326 &mut self.holding,
1327 &destination,
1328 &mut message,
1329 )?
1330 },
1331 };
1332 ensure!(reanchored_fees.len() == 1, XcmError::TooManyAssets);
1333 let fees =
1334 reanchored_fees.into_inner().pop().ok_or(XcmError::NotHoldingFees)?;
1335 message.push(PayFees { asset: fees });
1338 true
1339 } else {
1340 false
1341 };
1342
1343 for asset_filter in assets {
1345 match asset_filter {
1346 AssetTransferFilter::Teleport(assets) => Self::do_teleport_assets(
1347 self.holding.saturating_take(assets),
1348 &destination,
1349 &mut message,
1350 &self.context,
1351 )?,
1352 AssetTransferFilter::ReserveDeposit(assets) =>
1353 Self::do_reserve_deposit_assets(
1354 self.holding.saturating_take(assets),
1355 &destination,
1356 &mut message,
1357 Some(&self.context),
1358 )?,
1359 AssetTransferFilter::ReserveWithdraw(assets) =>
1360 Self::do_reserve_withdraw_assets(
1361 self.holding.saturating_take(assets),
1362 &mut self.holding,
1363 &destination,
1364 &mut message,
1365 )?,
1366 };
1367 }
1368
1369 if preserve_origin {
1370 if let Some(original_origin) = self
1372 .origin_ref()
1373 .filter(|origin| *origin != &Location::here())
1374 .cloned()
1375 {
1376 let reanchored_origin = Self::try_reanchor(original_origin, &destination)?.0;
1379 message.push(AliasOrigin(reanchored_origin));
1380 }
1381 } else {
1382 message.push(ClearOrigin);
1384 }
1385
1386 if !remote_fees_paid {
1390 message
1395 .push(UnpaidExecution { weight_limit: Unlimited, check_origin: None });
1396 }
1397
1398 message.extend(remote_xcm.0.into_iter());
1400 self.send(destination, Xcm(message), FeeReason::InitiateTransfer)?;
1402 Ok(())
1403 });
1404 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1405 self.holding = old_holding;
1406 }
1407 result
1408 },
1409 ReportHolding { response_info, assets } => {
1410 let assets =
1413 Self::reanchored(self.holding.min(&assets), &response_info.destination, None);
1414 self.respond(
1415 self.cloned_origin(),
1416 Response::Assets(assets),
1417 response_info,
1418 FeeReason::Report,
1419 )?;
1420 Ok(())
1421 },
1422 BuyExecution { fees, weight_limit } => {
1423 let Some(weight) = Option::<Weight>::from(weight_limit) else { return Ok(()) };
1428 let old_holding = self.holding.clone();
1429 self.asset_used_in_buy_execution = Some(fees.id.clone());
1432 tracing::trace!(
1433 target: "xcm::executor::BuyExecution",
1434 asset_used_in_buy_execution = ?self.asset_used_in_buy_execution
1435 );
1436 let max_fee =
1438 self.holding.try_take(fees.clone().into()).map_err(|e| {
1439 tracing::error!(target: "xcm::process_instruction::buy_execution", ?e, ?fees,
1440 "Failed to take fees from holding");
1441 XcmError::NotHoldingFees
1442 })?;
1443 let result = Config::TransactionalProcessor::process(|| {
1444 let unspent = self.trader.buy_weight(weight, max_fee, &self.context)?;
1445 self.holding.subsume_assets(unspent);
1446 Ok(())
1447 });
1448 if result.is_err() {
1449 self.holding = old_holding;
1450 }
1451 result
1452 },
1453 PayFees { asset } => {
1454 if self.already_paid_fees {
1456 return Ok(());
1457 }
1458 self.already_paid_fees = true;
1460 let old_holding = self.holding.clone();
1462 tracing::trace!(
1464 target: "xcm::executor::PayFees",
1465 asset_for_fees = ?asset,
1466 message_weight = ?self.message_weight,
1467 );
1468 let result = Config::TransactionalProcessor::process(|| {
1470 let max_fee =
1471 self.holding.try_take(asset.into()).map_err(|error| {
1472 tracing::debug!(
1473 target: "xcm::process_instruction::pay_fees", ?error,
1474 "Failed to take fees from holding"
1475 );
1476 XcmError::NotHoldingFees
1477 })?;
1478 let unspent =
1479 self.trader.buy_weight(self.message_weight, max_fee, &self.context)?;
1480 self.fees.subsume_assets(unspent);
1483 Ok(())
1484 });
1485 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1486 self.holding = old_holding;
1488 self.already_paid_fees = false;
1489 }
1490 result
1491 },
1492 RefundSurplus => self.refund_surplus(),
1493 SetErrorHandler(mut handler) => {
1494 let handler_weight = Config::Weigher::weight(&mut handler, Weight::MAX)
1495 .map_err(|error| {
1496 tracing::debug!(
1497 target: "xcm::executor::SetErrorHandler",
1498 ?error,
1499 ?handler,
1500 "Failed to calculate weight"
1501 );
1502 XcmError::WeightNotComputable
1503 })?;
1504 self.total_surplus.saturating_accrue(self.error_handler_weight);
1505 self.error_handler = handler;
1506 self.error_handler_weight = handler_weight;
1507 Ok(())
1508 },
1509 SetAppendix(mut appendix) => {
1510 let appendix_weight = Config::Weigher::weight(&mut appendix, Weight::MAX)
1511 .map_err(|error| {
1512 tracing::debug!(
1513 target: "xcm::executor::SetErrorHandler",
1514 ?error,
1515 ?appendix,
1516 "Failed to calculate weight"
1517 );
1518 XcmError::WeightNotComputable
1519 })?;
1520 self.total_surplus.saturating_accrue(self.appendix_weight);
1521 self.appendix = appendix;
1522 self.appendix_weight = appendix_weight;
1523 Ok(())
1524 },
1525 ClearError => {
1526 self.error = None;
1527 Ok(())
1528 },
1529 SetHints { hints } => {
1530 for hint in hints.into_iter() {
1531 match hint {
1532 AssetClaimer { location } => {
1533 self.asset_claimer = Some(location)
1534 },
1535 }
1536 }
1537 Ok(())
1538 },
1539 ClaimAsset { assets, ticket } => {
1540 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1541 self.ensure_can_subsume_assets(assets.len())?;
1542 let ok = Config::AssetClaims::claim_assets(origin, &ticket, &assets, &self.context);
1543 ensure!(ok, XcmError::UnknownClaim);
1544 self.holding.subsume_assets(assets.into());
1545 Ok(())
1546 },
1547 Trap(code) => Err(XcmError::Trap(code)),
1548 SubscribeVersion { query_id, max_response_weight } => {
1549 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1550 ensure!(&self.original_origin == origin, XcmError::BadOrigin);
1553 Config::SubscriptionService::start(
1554 origin,
1555 query_id,
1556 max_response_weight,
1557 &self.context,
1558 )
1559 },
1560 UnsubscribeVersion => {
1561 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1562 ensure!(&self.original_origin == origin, XcmError::BadOrigin);
1563 Config::SubscriptionService::stop(origin, &self.context)
1564 },
1565 BurnAsset(assets) => {
1566 self.holding.saturating_take(assets.into());
1567 Ok(())
1568 },
1569 ExpectAsset(assets) =>
1570 self.holding.ensure_contains(&assets).map_err(|e| {
1571 tracing::error!(target: "xcm::process_instruction::expect_asset", ?e, ?assets, "assets not contained in holding");
1572 XcmError::ExpectationFalse
1573 }),
1574 ExpectOrigin(origin) => {
1575 ensure!(self.context.origin == origin, XcmError::ExpectationFalse);
1576 Ok(())
1577 },
1578 ExpectError(error) => {
1579 ensure!(self.error == error, XcmError::ExpectationFalse);
1580 Ok(())
1581 },
1582 ExpectTransactStatus(transact_status) => {
1583 ensure!(self.transact_status == transact_status, XcmError::ExpectationFalse);
1584 Ok(())
1585 },
1586 QueryPallet { module_name, response_info } => {
1587 let pallets = Config::PalletInstancesInfo::infos()
1588 .into_iter()
1589 .filter(|x| x.module_name.as_bytes() == &module_name[..])
1590 .map(|x| {
1591 PalletInfo::new(
1592 x.index as u32,
1593 x.name.as_bytes().into(),
1594 x.module_name.as_bytes().into(),
1595 x.crate_version.major as u32,
1596 x.crate_version.minor as u32,
1597 x.crate_version.patch as u32,
1598 )
1599 })
1600 .collect::<Result<Vec<_>, XcmError>>()?;
1601 let QueryResponseInfo { destination, query_id, max_weight } = response_info;
1602 let response =
1603 Response::PalletsInfo(pallets.try_into().map_err(|error| {
1604 tracing::debug!(
1605 target: "xcm::process_instruction::query_pallet", ?error,
1606 "Failed to convert pallets to response info"
1607 );
1608 XcmError::Overflow
1609 })?);
1610 let querier = Self::to_querier(self.cloned_origin(), &destination)?;
1611 let instruction = QueryResponse { query_id, response, max_weight, querier };
1612 let message = Xcm(vec![instruction]);
1613 self.send(destination, message, FeeReason::QueryPallet)?;
1614 Ok(())
1615 },
1616 ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => {
1617 let pallet = Config::PalletInstancesInfo::infos()
1618 .into_iter()
1619 .find(|x| x.index == index as usize)
1620 .ok_or(XcmError::PalletNotFound)?;
1621 ensure!(pallet.name.as_bytes() == &name[..], XcmError::NameMismatch);
1622 ensure!(pallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch);
1623 let major = pallet.crate_version.major as u32;
1624 ensure!(major == crate_major, XcmError::VersionIncompatible);
1625 let minor = pallet.crate_version.minor as u32;
1626 ensure!(minor >= min_crate_minor, XcmError::VersionIncompatible);
1627 Ok(())
1628 },
1629 ReportTransactStatus(response_info) => {
1630 self.respond(
1631 self.cloned_origin(),
1632 Response::DispatchResult(self.transact_status.clone()),
1633 response_info,
1634 FeeReason::Report,
1635 )?;
1636 Ok(())
1637 },
1638 ClearTransactStatus => {
1639 self.transact_status = Default::default();
1640 Ok(())
1641 },
1642 UniversalOrigin(new_global) => {
1643 let universal_location = Config::UniversalLocation::get();
1644 ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation);
1645 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1646 let origin_xform = (origin, new_global);
1647 let ok = Config::UniversalAliases::contains(&origin_xform);
1648 ensure!(ok, XcmError::InvalidLocation);
1649 let (_, new_global) = origin_xform;
1650 let new_origin = Junctions::from([new_global]).relative_to(&universal_location);
1651 self.context.origin = Some(new_origin);
1652 Ok(())
1653 },
1654 ExportMessage { network, destination, xcm } => {
1655 let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone();
1665 let universal_source = Config::UniversalLocation::get()
1666 .within_global(origin)
1667 .map_err(|()| {
1668 tracing::debug!(
1669 target: "xcm::process_instruction::export_message",
1670 "Failed to reanchor origin to universal location",
1671 );
1672 XcmError::Unanchored
1673 })?;
1674 let hash = (self.origin_ref(), &destination).using_encoded(blake2_128);
1675 let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0);
1676 let (ticket, fee) = validate_export::<Config::MessageExporter>(
1680 network,
1681 channel,
1682 universal_source,
1683 destination.clone(),
1684 xcm,
1685 )?;
1686 let old_holding = self.holding.clone();
1687 let result = Config::TransactionalProcessor::process(|| {
1688 self.take_fee(fee, FeeReason::Export { network, destination })?;
1689 let _ = Config::MessageExporter::deliver(ticket).defensive_proof(
1690 "`deliver` called immediately after `validate_export`; \
1691 `take_fee` does not affect the validity of the ticket; qed",
1692 );
1693 Ok(())
1694 });
1695 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1696 self.holding = old_holding;
1697 }
1698 result
1699 },
1700 LockAsset { asset, unlocker } => {
1701 let old_holding = self.holding.clone();
1702 let result = Config::TransactionalProcessor::process(|| {
1703 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1704 let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?;
1705 let lock_ticket =
1706 Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?;
1707 let owner = origin.reanchored(&unlocker, &context).map_err(|e| {
1708 tracing::error!(target: "xcm::xcm_executor::process_instruction", ?e, ?unlocker, ?context, "Failed to re-anchor origin");
1709 XcmError::ReanchorFailed
1710 })?;
1711 let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]);
1712 let (ticket, price) = validate_send::<Config::XcmSender>(unlocker, msg)?;
1713 self.take_fee(price, FeeReason::LockAsset)?;
1714 lock_ticket.enact()?;
1715 Config::XcmSender::deliver(ticket)?;
1716 Ok(())
1717 });
1718 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1719 self.holding = old_holding;
1720 }
1721 result
1722 },
1723 UnlockAsset { asset, target } => {
1724 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1725 Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?;
1726 Ok(())
1727 },
1728 NoteUnlockable { asset, owner } => {
1729 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1730 Config::AssetLocker::note_unlockable(origin, asset, owner)?;
1731 Ok(())
1732 },
1733 RequestUnlock { asset, locker } => {
1734 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1735 let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0;
1736 let remote_target = Self::try_reanchor(origin.clone(), &locker)?.0;
1737 let reduce_ticket = Config::AssetLocker::prepare_reduce_unlockable(
1738 locker.clone(),
1739 asset,
1740 origin.clone(),
1741 )?;
1742 let msg =
1743 Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]);
1744 let (ticket, price) = validate_send::<Config::XcmSender>(locker, msg)?;
1745 let old_holding = self.holding.clone();
1746 let result = Config::TransactionalProcessor::process(|| {
1747 self.take_fee(price, FeeReason::RequestUnlock)?;
1748 reduce_ticket.enact()?;
1749 Config::XcmSender::deliver(ticket)?;
1750 Ok(())
1751 });
1752 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
1753 self.holding = old_holding;
1754 }
1755 result
1756 },
1757 ExchangeAsset { give, want, maximal } => {
1758 let old_holding = self.holding.clone();
1759 let give = self.holding.saturating_take(give);
1760 let result = Config::TransactionalProcessor::process(|| {
1761 self.ensure_can_subsume_assets(want.len())?;
1762 let exchange_result = Config::AssetExchanger::exchange_asset(
1763 self.origin_ref(),
1764 give,
1765 &want,
1766 maximal,
1767 );
1768 if let Ok(received) = exchange_result {
1769 self.holding.subsume_assets(received.into());
1770 Ok(())
1771 } else {
1772 Err(XcmError::NoDeal)
1773 }
1774 });
1775 if result.is_err() {
1776 self.holding = old_holding;
1777 }
1778 result
1779 },
1780 SetFeesMode { jit_withdraw } => {
1781 self.fees_mode = FeesMode { jit_withdraw };
1782 Ok(())
1783 },
1784 SetTopic(topic) => {
1785 self.context.topic = Some(topic);
1786 Ok(())
1787 },
1788 ClearTopic => {
1789 self.context.topic = None;
1790 Ok(())
1791 },
1792 AliasOrigin(target) => {
1793 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1794 if Config::Aliasers::contains(origin, &target) {
1795 self.context.origin = Some(target);
1796 Ok(())
1797 } else {
1798 Err(XcmError::NoPermission)
1799 }
1800 },
1801 UnpaidExecution { check_origin, .. } => {
1802 ensure!(
1803 check_origin.is_none() || self.context.origin == check_origin,
1804 XcmError::BadOrigin
1805 );
1806 Ok(())
1807 },
1808 HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
1809 Config::TransactionalProcessor::process(|| {
1810 Config::HrmpNewChannelOpenRequestHandler::handle(
1811 sender,
1812 max_message_size,
1813 max_capacity,
1814 )
1815 }),
1816 HrmpChannelAccepted { recipient } => Config::TransactionalProcessor::process(|| {
1817 Config::HrmpChannelAcceptedHandler::handle(recipient)
1818 }),
1819 HrmpChannelClosing { initiator, sender, recipient } =>
1820 Config::TransactionalProcessor::process(|| {
1821 Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient)
1822 }),
1823 }
1824 }
1825
1826 fn do_descend_origin(&mut self, who: InteriorLocation) -> XcmResult {
1827 self.context
1828 .origin
1829 .as_mut()
1830 .ok_or(XcmError::BadOrigin)?
1831 .append_with(who)
1832 .map_err(|e| {
1833 tracing::error!(target: "xcm::do_descend_origin", ?e, "Failed to append junctions");
1834 XcmError::LocationFull
1835 })
1836 }
1837
1838 fn do_clear_origin(&mut self) -> XcmResult {
1839 self.context.origin = None;
1840 Ok(())
1841 }
1842
1843 fn deposit_assets_with_retry(
1857 to_deposit: &AssetsInHolding,
1858 beneficiary: &Location,
1859 context: Option<&XcmContext>,
1860 ) -> Result<Weight, XcmError> {
1861 let mut total_surplus = Weight::zero();
1862 let mut failed_deposits = Vec::with_capacity(to_deposit.len());
1863 for asset in to_deposit.assets_iter() {
1864 match Config::AssetTransactor::deposit_asset_with_surplus(&asset, &beneficiary, context)
1865 {
1866 Ok(surplus) => {
1867 total_surplus.saturating_accrue(surplus);
1868 },
1869 Err(_) => {
1870 failed_deposits.push(asset);
1872 },
1873 }
1874 }
1875 tracing::trace!(
1876 target: "xcm::deposit_assets_with_retry",
1877 ?failed_deposits,
1878 "First‐pass failures, about to retry"
1879 );
1880 for asset in failed_deposits {
1882 match Config::AssetTransactor::deposit_asset_with_surplus(&asset, &beneficiary, context)
1883 {
1884 Ok(surplus) => {
1885 total_surplus.saturating_accrue(surplus);
1886 },
1887 Err(error) => {
1888 if !matches!(
1890 error,
1891 XcmError::FailedToTransactAsset(string)
1892 if *string == *<&'static str>::from(sp_runtime::TokenError::BelowMinimum)
1893 ) {
1894 return Err(error);
1895 }
1896 },
1897 };
1898 }
1899 Ok(total_surplus)
1900 }
1901
1902 fn take_delivery_fee_from_assets(
1907 &self,
1908 assets: &mut AssetsInHolding,
1909 destination: &Location,
1910 reason: FeeReason,
1911 xcm: &Xcm<()>,
1912 ) -> Result<Option<AssetsInHolding>, XcmError> {
1913 let to_weigh = assets.clone();
1914 let to_weigh_reanchored = Self::reanchored(to_weigh, &destination, None);
1915 let remote_instruction = match reason {
1916 FeeReason::DepositReserveAsset => ReserveAssetDeposited(to_weigh_reanchored),
1917 FeeReason::InitiateReserveWithdraw => WithdrawAsset(to_weigh_reanchored),
1918 FeeReason::InitiateTeleport => ReceiveTeleportedAsset(to_weigh_reanchored),
1919 _ => {
1920 tracing::debug!(
1921 target: "xcm::take_delivery_fee_from_assets",
1922 "Unexpected delivery fee reason",
1923 );
1924 return Err(XcmError::NotHoldingFees);
1925 },
1926 };
1927 let mut message_to_weigh = Vec::with_capacity(xcm.len() + 2);
1928 message_to_weigh.push(remote_instruction);
1929 message_to_weigh.push(ClearOrigin);
1930 message_to_weigh.extend(xcm.0.clone().into_iter());
1931 let (_, fee) =
1932 validate_send::<Config::XcmSender>(destination.clone(), Xcm(message_to_weigh))?;
1933 let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| {
1934 tracing::trace!(
1935 target: "xcm::fees::take_delivery_fee_from_assets",
1936 "Asset provided to pay for fees {:?}, asset required for delivery fees: {:?}",
1937 self.asset_used_in_buy_execution, asset_needed_for_fees,
1938 );
1939 let asset_to_pay_for_fees =
1940 self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
1941 let delivery_fee = assets.saturating_take(asset_to_pay_for_fees.into());
1943 tracing::trace!(target: "xcm::fees::take_delivery_fee_from_assets", ?delivery_fee);
1944 delivery_fee
1945 });
1946 Ok(maybe_delivery_fee)
1947 }
1948}