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