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