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 pending_send_failure: Option<(Location, SendError, XcmHash)>,
109 _config: PhantomData<Config>,
110}
111
112#[cfg(any(test, feature = "runtime-benchmarks"))]
113impl<Config: config::Config> XcmExecutor<Config> {
114 pub fn holding(&self) -> &AssetsInHolding {
115 &self.holding
116 }
117 pub fn set_holding(&mut self, v: AssetsInHolding) {
118 self.holding = v
119 }
120 pub fn holding_limit(&self) -> &usize {
121 &self.holding_limit
122 }
123 pub fn set_holding_limit(&mut self, v: usize) {
124 self.holding_limit = v
125 }
126 pub fn origin(&self) -> &Option<Location> {
127 &self.context.origin
128 }
129 pub fn set_origin(&mut self, v: Option<Location>) {
130 self.context.origin = v
131 }
132 pub fn original_origin(&self) -> &Location {
133 &self.original_origin
134 }
135 pub fn set_original_origin(&mut self, v: Location) {
136 self.original_origin = v
137 }
138 pub fn trader(&self) -> &Config::Trader {
139 &self.trader
140 }
141 pub fn set_trader(&mut self, v: Config::Trader) {
142 self.trader = v
143 }
144 pub fn error(&self) -> &Option<(u32, XcmError)> {
145 &self.error
146 }
147 pub fn set_error(&mut self, v: Option<(u32, XcmError)>) {
148 self.error = v
149 }
150 pub fn total_surplus(&self) -> &Weight {
151 &self.total_surplus
152 }
153 pub fn set_total_surplus(&mut self, v: Weight) {
154 self.total_surplus = v
155 }
156 pub fn total_refunded(&self) -> &Weight {
157 &self.total_refunded
158 }
159 pub fn set_total_refunded(&mut self, v: Weight) {
160 self.total_refunded = v
161 }
162 pub fn error_handler(&self) -> &Xcm<Config::RuntimeCall> {
163 &self.error_handler
164 }
165 pub fn set_error_handler(&mut self, v: Xcm<Config::RuntimeCall>) {
166 self.error_handler = v
167 }
168 pub fn error_handler_weight(&self) -> &Weight {
169 &self.error_handler_weight
170 }
171 pub fn set_error_handler_weight(&mut self, v: Weight) {
172 self.error_handler_weight = v
173 }
174 pub fn appendix(&self) -> &Xcm<Config::RuntimeCall> {
175 &self.appendix
176 }
177 pub fn set_appendix(&mut self, v: Xcm<Config::RuntimeCall>) {
178 self.appendix = v
179 }
180 pub fn appendix_weight(&self) -> &Weight {
181 &self.appendix_weight
182 }
183 pub fn set_appendix_weight(&mut self, v: Weight) {
184 self.appendix_weight = v
185 }
186 pub fn transact_status(&self) -> &MaybeErrorCode {
187 &self.transact_status
188 }
189 pub fn set_transact_status(&mut self, v: MaybeErrorCode) {
190 self.transact_status = v
191 }
192 pub fn fees_mode(&self) -> &FeesMode {
193 &self.fees_mode
194 }
195 pub fn set_fees_mode(&mut self, v: FeesMode) {
196 self.fees_mode = v
197 }
198 pub fn fees(&self) -> &AssetsInHolding {
199 &self.fees
200 }
201 pub fn set_fees(&mut self, value: AssetsInHolding) {
202 self.fees = value;
203 }
204 pub fn topic(&self) -> &Option<[u8; 32]> {
205 &self.context.topic
206 }
207 pub fn set_topic(&mut self, v: Option<[u8; 32]>) {
208 self.context.topic = v;
209 }
210 pub fn asset_claimer(&self) -> Option<Location> {
211 self.asset_claimer.clone()
212 }
213 pub fn set_message_weight(&mut self, weight: Weight) {
214 self.message_weight = weight;
215 }
216 pub fn already_paid_fees(&self) -> bool {
217 self.already_paid_fees
218 }
219 pub fn asset_used_in_buy_execution(&self) -> &Option<AssetId> {
220 &self.asset_used_in_buy_execution
221 }
222}
223
224pub struct WeighedMessage<Call>(Weight, Xcm<Call>);
225impl<C> PreparedMessage for WeighedMessage<C> {
226 fn weight_of(&self) -> Weight {
227 self.0
228 }
229}
230
231#[cfg(any(test, feature = "std"))]
232impl<C> WeighedMessage<C> {
233 pub fn new(weight: Weight, message: Xcm<C>) -> Self {
234 Self(weight, message)
235 }
236}
237
238impl<Config: config::Config> ExecuteXcm<Config::RuntimeCall> for XcmExecutor<Config> {
239 type Prepared = WeighedMessage<Config::RuntimeCall>;
240 fn prepare(
241 mut message: Xcm<Config::RuntimeCall>,
242 weight_limit: Weight,
243 ) -> Result<Self::Prepared, InstructionError> {
244 match Config::Weigher::weight(&mut message, weight_limit) {
245 Ok(weight) => Ok(WeighedMessage(weight, message)),
246 Err(error) => {
247 tracing::debug!(
248 target: "xcm::prepare",
249 ?error,
250 ?message,
251 "Failed to calculate weight for XCM message; execution aborted"
252 );
253 Err(error)
254 },
255 }
256 }
257 fn execute(
258 origin: impl Into<Location>,
259 WeighedMessage(xcm_weight, mut message): WeighedMessage<Config::RuntimeCall>,
260 id: &mut XcmHash,
261 weight_credit: Weight,
262 ) -> Outcome {
263 let origin = origin.into();
264 tracing::trace!(
265 target: "xcm::execute",
266 ?origin,
267 ?message,
268 ?id,
269 ?weight_credit,
270 "Executing message",
271 );
272 let mut properties = Properties { weight_credit, message_id: None };
273
274 if Config::XcmRecorder::should_record() {
277 Config::XcmRecorder::record(message.clone().into());
278 }
279
280 if let Err(e) = Config::Barrier::should_execute(
281 &origin,
282 message.inner_mut(),
283 xcm_weight,
284 &mut properties,
285 ) {
286 tracing::trace!(
287 target: "xcm::execute",
288 ?origin,
289 ?message,
290 ?properties,
291 error = ?e,
292 "Barrier blocked execution",
293 );
294
295 return Outcome::Incomplete {
296 used: xcm_weight, error: InstructionError { index: 0, error: XcmError::Barrier }, };
299 }
300
301 *id = properties.message_id.unwrap_or(*id);
302
303 let mut vm = Self::new(origin, *id);
304 vm.message_weight = xcm_weight;
305
306 while !message.0.is_empty() {
307 let result = vm.process(message);
308 tracing::trace!(target: "xcm::execute", ?result, "Message executed");
309 message = match result {
310 Err(error) => {
311 vm.total_surplus.saturating_accrue(error.weight);
312 vm.error = Some((error.index, error.xcm_error));
313 vm.take_error_handler().or_else(|| vm.take_appendix())
314 },
315 Ok(()) => {
316 vm.drop_error_handler();
317 vm.take_appendix()
318 },
319 }
320 }
321
322 vm.post_process(xcm_weight)
323 }
324
325 fn charge_fees(origin: impl Into<Location>, fees: Assets) -> XcmResult {
326 let origin = origin.into();
327 if !Config::FeeManager::is_waived(Some(&origin), FeeReason::ChargeFees) {
328 let mut charged = AssetsInHolding::new();
329 for asset in fees.inner() {
330 let withdrawn = Config::AssetTransactor::withdraw_asset(&asset, &origin, None)?;
331 charged.subsume_assets(withdrawn);
332 }
333 Config::FeeManager::handle_fee(charged, None, FeeReason::ChargeFees);
334 }
335 Ok(())
336 }
337}
338
339impl<Config: config::Config> XcmAssetTransfers for XcmExecutor<Config> {
340 type IsReserve = Config::IsReserve;
341 type IsTeleporter = Config::IsTeleporter;
342 type AssetTransactor = Config::AssetTransactor;
343}
344
345impl<Config: config::Config> FeeManager for XcmExecutor<Config> {
346 fn is_waived(origin: Option<&Location>, r: FeeReason) -> bool {
347 Config::FeeManager::is_waived(origin, r)
348 }
349
350 fn handle_fee(fee: AssetsInHolding, context: Option<&XcmContext>, r: FeeReason) {
351 Config::FeeManager::handle_fee(fee, context, r)
352 }
353}
354
355#[derive(Debug, PartialEq)]
356pub struct ExecutorError {
357 pub index: u32,
358 pub xcm_error: XcmError,
359 pub weight: Weight,
360}
361
362#[cfg(feature = "runtime-benchmarks")]
363impl From<ExecutorError> for frame_benchmarking::BenchmarkError {
364 fn from(error: ExecutorError) -> Self {
365 tracing::error!(
366 index = ?error.index,
367 xcm_error = ?error.xcm_error,
368 weight = ?error.weight,
369 "XCM ERROR",
370 );
371 Self::Stop("xcm executor error: see error logs")
372 }
373}
374
375impl<Config: config::Config> XcmExecutor<Config> {
376 pub fn new(origin: impl Into<Location>, message_id: XcmHash) -> Self {
377 let origin = origin.into();
378 Self {
379 holding: AssetsInHolding::new(),
380 holding_limit: Config::MaxAssetsIntoHolding::get() as usize,
381 context: XcmContext { origin: Some(origin.clone()), message_id, topic: None },
382 original_origin: origin,
383 trader: Config::Trader::new(),
384 error: None,
385 total_surplus: Weight::zero(),
386 total_refunded: Weight::zero(),
387 error_handler: Xcm(vec![]),
388 error_handler_weight: Weight::zero(),
389 appendix: Xcm(vec![]),
390 appendix_weight: Weight::zero(),
391 transact_status: Default::default(),
392 fees_mode: FeesMode { jit_withdraw: false },
393 fees: AssetsInHolding::new(),
394 asset_used_in_buy_execution: None,
395 message_weight: Weight::zero(),
396 asset_claimer: None,
397 already_paid_fees: false,
398 pending_send_failure: None,
399 _config: PhantomData,
400 }
401 }
402
403 pub fn post_process(mut self, xcm_weight: Weight) -> Outcome {
407 let _ = self.refund_surplus();
410 drop(self.trader);
411
412 let mut weight_used = xcm_weight.saturating_sub(self.total_surplus);
413
414 if !self.holding.is_empty() {
415 tracing::trace!(
416 target: "xcm::post_process",
417 holding_register = ?self.holding,
418 context = ?self.context,
419 original_origin = ?self.original_origin,
420 "Trapping assets in holding register",
421 );
422 let claimer = self
423 .asset_claimer
424 .as_ref()
425 .or(self.context.origin.as_ref())
426 .unwrap_or(&self.original_origin);
427 let trap_weight = Config::AssetTrap::drop_assets(claimer, self.holding, &self.context);
428 weight_used.saturating_accrue(trap_weight);
429 };
430
431 match self.error {
432 None => Outcome::Complete { used: weight_used },
433 Some((index, error)) => {
436 tracing::trace!(
437 target: "xcm::post_process",
438 instruction = ?index,
439 ?error,
440 original_origin = ?self.original_origin,
441 "Execution failed",
442 );
443 Outcome::Incomplete {
444 used: weight_used,
445 error: InstructionError { index: index.try_into().unwrap_or(u8::MAX), error },
446 }
447 },
448 }
449 }
450
451 fn origin_ref(&self) -> Option<&Location> {
452 self.context.origin.as_ref()
453 }
454
455 fn cloned_origin(&self) -> Option<Location> {
456 self.context.origin.clone()
457 }
458
459 fn send(
468 &mut self,
469 dest: Location,
470 msg: Xcm<()>,
471 reason: FeeReason,
472 ) -> Result<XcmHash, XcmError> {
473 let mut msg = msg;
474 if !matches!(msg.last(), Some(SetTopic(_))) {
478 let topic_id = self.context.topic_or_message_id();
479 msg.0.push(SetTopic(topic_id.into()));
480 }
481 tracing::trace!(
482 target: "xcm::send",
483 ?msg,
484 destination = ?dest,
485 reason = ?reason,
486 "Sending msg",
487 );
488 let (ticket, fee) = validate_send::<Config::XcmSender>(dest.clone(), msg)?;
489 self.take_fee(fee, reason)?;
490 match Config::XcmSender::deliver(ticket) {
491 Ok(message_id) => {
492 Config::XcmEventEmitter::emit_sent_event(
493 self.original_origin.clone(),
494 dest,
495 None, message_id,
498 );
499 Ok(message_id)
500 },
501 Err(error) => {
502 tracing::debug!(target: "xcm::send", ?error, "XCM failed to deliver with error");
503 self.pending_send_failure =
507 Some((dest, error.clone(), self.context.topic_or_message_id()));
508 Err(error.into())
509 },
510 }
511 }
512
513 fn take_error_handler(&mut self) -> Xcm<Config::RuntimeCall> {
515 let mut r = Xcm::<Config::RuntimeCall>(vec![]);
516 core::mem::swap(&mut self.error_handler, &mut r);
517 self.error_handler_weight = Weight::zero();
518 r
519 }
520
521 fn drop_error_handler(&mut self) {
523 self.error_handler = Xcm::<Config::RuntimeCall>(vec![]);
524 self.total_surplus.saturating_accrue(self.error_handler_weight);
525 self.error_handler_weight = Weight::zero();
526 }
527
528 fn take_appendix(&mut self) -> Xcm<Config::RuntimeCall> {
530 let mut r = Xcm::<Config::RuntimeCall>(vec![]);
531 core::mem::swap(&mut self.appendix, &mut r);
532 self.appendix_weight = Weight::zero();
533 r
534 }
535
536 fn ensure_can_subsume_assets(&self, assets_length: usize) -> Result<(), XcmError> {
537 let worst_case_holding_len = self.holding.len() + assets_length;
542 tracing::trace!(
543 target: "xcm::ensure_can_subsume_assets",
544 ?worst_case_holding_len,
545 holding_limit = ?self.holding_limit,
546 "Ensuring subsume assets work",
547 );
548 ensure!(worst_case_holding_len <= self.holding_limit * 2, XcmError::HoldingWouldOverflow);
549 Ok(())
550 }
551
552 fn refund_surplus(&mut self) -> Result<(), XcmError> {
554 let current_surplus = self.total_surplus.saturating_sub(self.total_refunded);
555 tracing::trace!(
556 target: "xcm::refund_surplus",
557 total_surplus = ?self.total_surplus,
558 total_refunded = ?self.total_refunded,
559 ?current_surplus,
560 "Refunding surplus",
561 );
562 if current_surplus.any_gt(Weight::zero()) {
563 if let Some(refund) = self.trader.refund_weight(current_surplus, &self.context) {
564 if refund
567 .fungible
568 .first_key_value()
569 .map(|(id, _)| {
570 !self.holding.fungible.contains_key(id) &&
571 self.ensure_can_subsume_assets(1).is_err()
572 })
573 .unwrap_or(false)
574 {
575 let _ = self
579 .trader
580 .buy_weight(current_surplus, refund, &self.context)
581 .defensive_proof(
582 "refund_weight returned an asset capable of buying weight; qed",
583 );
584 tracing::error!(
585 target: "xcm::refund_surplus",
586 "error: HoldingWouldOverflow",
587 );
588 return Err(XcmError::HoldingWouldOverflow);
589 }
590 self.total_refunded.saturating_accrue(current_surplus);
591 self.holding.subsume_assets(refund);
592 }
593 }
594 if !self.fees.is_empty() {
596 let leftover_fees = self.fees.saturating_take(Wild(All));
597 tracing::trace!(
598 target: "xcm::refund_surplus",
599 ?leftover_fees,
600 );
601 self.holding.subsume_assets(leftover_fees);
602 }
603 tracing::trace!(
604 target: "xcm::refund_surplus",
605 total_refunded = ?self.total_refunded,
606 );
607 Ok(())
608 }
609
610 fn take_fee(&mut self, fees: Assets, reason: FeeReason) -> XcmResult {
613 if Config::FeeManager::is_waived(self.origin_ref(), reason.clone()) {
614 return Ok(());
615 }
616 tracing::trace!(
617 target: "xcm::fees",
618 ?fees,
619 origin_ref = ?self.origin_ref(),
620 fees_mode = ?self.fees_mode,
621 ?reason,
622 "Taking fees",
623 );
624 let Some(asset_needed_for_fees) = fees.get(0) else {
626 return Ok(()); };
628 let asset_to_pay_for_fees =
630 self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
631 tracing::trace!(target: "xcm::fees", ?asset_to_pay_for_fees);
632 let withdrawn_fee_asset: AssetsInHolding = if self.fees_mode.jit_withdraw {
634 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
635 let credit = Config::AssetTransactor::withdraw_asset(
636 &asset_to_pay_for_fees,
637 origin,
638 Some(&self.context),
639 )?;
640 tracing::trace!(target: "xcm::fees", ?asset_needed_for_fees);
641 credit
642 } else {
643 let assets_to_pay_delivery_fees: AssetsInHolding = if self.fees.is_empty() {
646 self.holding.try_take(asset_to_pay_for_fees.clone().into()).map_err(|e| {
648 tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees,
649 "Holding doesn't hold enough for fees");
650 XcmError::NotHoldingFees
651 })?
652 } else {
653 self.fees.try_take(asset_to_pay_for_fees.clone().into()).map_err(|e| {
655 tracing::error!(target: "xcm::fees", ?e, ?asset_to_pay_for_fees,
656 "Fees register doesn't hold enough for fees");
657 XcmError::NotHoldingFees
658 })?
659 };
660 tracing::trace!(target: "xcm::fees", ?assets_to_pay_delivery_fees);
661 assets_to_pay_delivery_fees
662 };
663 let paid = if asset_to_pay_for_fees.id != asset_needed_for_fees.id {
665 Config::AssetExchanger::exchange_asset(
666 self.origin_ref(),
667 withdrawn_fee_asset,
668 &asset_needed_for_fees.clone().into(),
669 false,
670 )
671 .map_err(|given_assets| {
672 tracing::error!(
673 target: "xcm::fees",
674 ?given_assets, ?asset_needed_for_fees, "Swap was deemed necessary but couldn't be done:",
675 );
676 self.fees.subsume_assets(given_assets);
677 XcmError::FeesNotMet
678 })?
679 } else {
680 withdrawn_fee_asset
684 };
685 Config::FeeManager::handle_fee(paid, Some(&self.context), reason);
686 Ok(())
687 }
688
689 fn calculate_asset_for_delivery_fees(&self, asset_needed_for_fees: Asset) -> Asset {
696 let Some(asset_wanted_for_fees) =
697 self.fees.fungible.first_key_value().map(|(id, _)| id).or_else(|| {
699 self.asset_used_in_buy_execution.as_ref()
701 })
702 .filter(|&id| asset_needed_for_fees.id.ne(id))
704 else {
705 return asset_needed_for_fees
707 };
708 Config::AssetExchanger::quote_exchange_price(
709 &(asset_wanted_for_fees.clone(), Fungible(0)).into(),
710 &asset_needed_for_fees.clone().into(),
711 false, )
713 .and_then(|necessary_assets| {
714 necessary_assets.into_inner().into_iter().next()
718 })
719 .unwrap_or_else(|| {
720 tracing::trace!(
723 target: "xcm::calculate_asset_for_delivery_fees",
724 ?asset_wanted_for_fees, "Could not convert fees",
725 );
726 asset_needed_for_fees
727 })
728 }
729
730 fn to_querier(
732 local_querier: Option<Location>,
733 destination: &Location,
734 ) -> Result<Option<Location>, XcmError> {
735 Ok(match local_querier {
736 None => None,
737 Some(q) => Some(
738 q.reanchored(&destination, &Config::UniversalLocation::get()).map_err(|e| {
739 tracing::error!(target: "xcm::xcm_executor::to_querier", ?e, ?destination, "Failed to re-anchor local_querier");
740 XcmError::ReanchorFailed
741 })?,
742 ),
743 })
744 }
745
746 fn respond(
750 &mut self,
751 local_querier: Option<Location>,
752 response: Response,
753 info: QueryResponseInfo,
754 fee_reason: FeeReason,
755 ) -> Result<XcmHash, XcmError> {
756 let querier = Self::to_querier(local_querier, &info.destination)?;
757 let QueryResponseInfo { destination, query_id, max_weight } = info;
758 let instruction = QueryResponse { query_id, response, max_weight, querier };
759 let message = Xcm(vec![instruction]);
760 self.send(destination, message, fee_reason)
761 }
762
763 fn do_reserve_deposit_assets(
764 assets: AssetsInHolding,
765 dest: &Location,
766 remote_xcm: &mut Vec<Instruction<()>>,
767 context: Option<&XcmContext>,
768 ) -> Result<Assets, XcmError> {
769 let reanchored_assets = Self::reanchored_assets(&assets, dest);
770 Self::deposit_assets_with_retry(assets, dest, context)?;
771 remote_xcm.push(ReserveAssetDeposited(reanchored_assets.clone()));
772
773 Ok(reanchored_assets)
774 }
775
776 fn do_reserve_withdraw_assets(
777 assets: AssetsInHolding,
778 failed_bin: &mut AssetsInHolding,
779 reserve: &Location,
780 remote_xcm: &mut Vec<Instruction<()>>,
781 ) -> Result<Assets, XcmError> {
782 #[cfg(not(any(test, feature = "runtime-benchmarks")))]
784 for asset in assets.assets_iter() {
785 ensure!(
786 Config::IsReserve::contains(&asset, &reserve),
787 XcmError::UntrustedReserveLocation
788 );
789 }
790 let reanchored_assets =
793 assets.reanchor_and_burn_local(reserve, &Config::UniversalLocation::get(), failed_bin);
794 remote_xcm.push(WithdrawAsset(reanchored_assets.clone()));
795
796 Ok(reanchored_assets)
797 }
798
799 fn do_teleport_assets(
800 assets: AssetsInHolding,
801 dest: &Location,
802 remote_xcm: &mut Vec<Instruction<()>>,
803 context: &XcmContext,
804 ) -> Result<Assets, XcmError> {
805 let reanchored_assets = Self::reanchored_assets(&assets, dest);
806 for asset in assets.assets_iter() {
807 #[cfg(not(any(test, feature = "runtime-benchmarks")))]
809 ensure!(
810 Config::IsTeleporter::contains(&asset, &dest),
811 XcmError::UntrustedTeleportLocation
812 );
813 Config::AssetTransactor::can_check_out(dest, &asset, context)?;
818 }
819 for asset in assets.assets_iter() {
820 Config::AssetTransactor::check_out(dest, &asset, context);
821 }
822 remote_xcm.push(ReceiveTeleportedAsset(reanchored_assets.clone()));
823
824 Ok(reanchored_assets)
825 }
826
827 fn try_reanchor<T: Reanchorable>(
828 reanchorable: T,
829 destination: &Location,
830 ) -> Result<(T, InteriorLocation), XcmError> {
831 let reanchor_context = Config::UniversalLocation::get();
832 let reanchored =
833 reanchorable.reanchored(&destination, &reanchor_context).map_err(|error| {
834 tracing::error!(target: "xcm::reanchor", ?error, ?destination, ?reanchor_context, "Failed reanchoring with error.");
835 XcmError::ReanchorFailed
836 })?;
837 Ok((reanchored, reanchor_context))
838 }
839
840 fn reanchored_assets(assets: &AssetsInHolding, dest: &Location) -> Assets {
842 assets.reanchored_assets(dest, &Config::UniversalLocation::get())
843 }
844
845 #[cfg(any(test, feature = "runtime-benchmarks"))]
846 pub fn bench_process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
847 self.process(xcm)
848 }
849
850 #[cfg(any(test, feature = "runtime-benchmarks"))]
851 pub fn bench_post_process(self, xcm_weight: Weight) -> Outcome {
852 self.post_process(xcm_weight)
853 }
854
855 fn process(&mut self, xcm: Xcm<Config::RuntimeCall>) -> Result<(), ExecutorError> {
856 tracing::trace!(
857 target: "xcm::process",
858 origin = ?self.origin_ref(),
859 total_surplus = ?self.total_surplus,
860 total_refunded = ?self.total_refunded,
861 error_handler_weight = ?self.error_handler_weight,
862 );
863 let mut result = Ok(());
864 for (i, mut instr) in xcm.0.into_iter().enumerate() {
865 match &mut result {
866 r @ Ok(()) => {
867 let inst_res = recursion_count::using_once(&mut 1, || {
870 recursion_count::with(|count| {
871 if *count > RECURSION_LIMIT {
872 return None;
873 }
874 *count = count.saturating_add(1);
875 Some(())
876 })
877 .flatten()
878 .ok_or(XcmError::ExceedsStackLimit)?;
879
880 defer! {
883 recursion_count::with(|count| {
884 *count = count.saturating_sub(1);
885 });
886 }
887
888 self.process_instruction(instr)
889 });
890 if let Some((dest, error, message_id)) = self.pending_send_failure.take() {
893 Config::XcmEventEmitter::emit_send_failure_event(
894 self.original_origin.clone(),
895 dest,
896 error,
897 message_id,
898 );
899 }
900 if let Err(error) = inst_res {
901 tracing::debug!(
902 target: "xcm::process",
903 ?error, "XCM execution failed at instruction index={i}"
904 );
905 Config::XcmEventEmitter::emit_process_failure_event(
906 self.original_origin.clone(),
907 error,
908 self.context.topic_or_message_id(),
909 );
910 *r = Err(ExecutorError {
911 index: i as u32,
912 xcm_error: error,
913 weight: Weight::zero(),
914 });
915 }
916 },
917 Err(ref mut error) => {
918 if let Ok(x) = Config::Weigher::instr_weight(&mut instr) {
919 error.weight.saturating_accrue(x)
920 }
921 },
922 }
923 }
924 result
925 }
926
927 fn transactional_process(
947 &mut self,
948 f: impl FnOnce(&mut Self) -> Result<(), XcmError>,
949 ) -> Result<(), XcmError> {
950 self.transactional_process_with_custom_rollback(f, |_| {})
951 }
952
953 fn transactional_process_with_custom_rollback(
959 &mut self,
960 f: impl FnOnce(&mut Self) -> Result<(), XcmError>,
961 on_rollback: impl FnOnce(&mut Self),
962 ) -> Result<(), XcmError> {
963 let mut backup_holding = BackupAssetsInHolding::safe_backup(&self.holding);
964 let mut backup_fees = BackupAssetsInHolding::safe_backup(&self.fees);
965 let result = Config::TransactionalProcessor::process(|| f(self));
966 if Config::TransactionalProcessor::IS_TRANSACTIONAL && result.is_err() {
967 backup_holding.restore_into(&mut self.holding);
968 backup_fees.restore_into(&mut self.fees);
969 on_rollback(self);
970 }
971 result
972 }
973
974 fn process_instruction(
976 &mut self,
977 instr: Instruction<Config::RuntimeCall>,
978 ) -> Result<(), XcmError> {
979 tracing::trace!(
980 target: "xcm::process_instruction",
981 instruction = ?instr,
982 "Processing instruction",
983 );
984
985 match instr {
986 WithdrawAsset(assets) => {
987 self.ensure_can_subsume_assets(assets.len())?;
988 Config::TransactionalProcessor::process(|| {
989 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
990 let mut total_surplus = Weight::zero();
991 let mut withdrawn = AssetsInHolding::new();
992 for asset in assets.inner() {
994 let (credit, surplus) = Config::AssetTransactor::withdraw_asset_with_surplus(
995 asset,
996 origin,
997 Some(&self.context),
998 )?;
999 withdrawn.subsume_assets(credit);
1000 total_surplus.saturating_accrue(surplus);
1002 }
1003 self.holding.subsume_assets(withdrawn);
1005 self.total_surplus.saturating_accrue(total_surplus);
1007 Ok(())
1008 })
1009 },
1010 ReserveAssetDeposited(assets) => {
1011 self.ensure_can_subsume_assets(assets.len())?;
1012 Config::TransactionalProcessor::process(|| {
1013 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1015 let mut minted_assets = AssetsInHolding::new();
1019 for asset in assets.inner() {
1020 ensure!(
1022 Config::IsReserve::contains(asset, origin),
1023 XcmError::UntrustedReserveLocation
1024 );
1025 Config::AssetTransactor::mint_asset(asset, &self.context)
1026 .map(|minted| minted_assets.subsume_assets(minted))?;
1027 }
1028 self.holding.subsume_assets(minted_assets);
1029 Ok(())
1030 })
1031 },
1032 TransferAsset { assets, beneficiary } => {
1033 Config::TransactionalProcessor::process(|| {
1034 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1036 let mut total_surplus = Weight::zero();
1037 for asset in assets.inner() {
1038 let (_, surplus) = Config::AssetTransactor::transfer_asset_with_surplus(
1039 &asset,
1040 origin,
1041 &beneficiary,
1042 &self.context,
1043 )?;
1044 total_surplus.saturating_accrue(surplus);
1046 }
1047 self.total_surplus.saturating_accrue(total_surplus);
1049 Ok(())
1050 })
1051 },
1052 TransferReserveAsset { mut assets, dest, xcm } => {
1053 self.transactional_process(|self_ref| {
1054 let origin = self_ref.origin_ref().ok_or(XcmError::BadOrigin)?;
1055 let mut total_surplus = Weight::zero();
1056 for asset in assets.inner() {
1058 let (_, surplus) = Config::AssetTransactor::transfer_asset_with_surplus(
1059 asset,
1060 origin,
1061 &dest,
1062 &self_ref.context,
1063 )?;
1064 total_surplus.saturating_accrue(surplus);
1066 }
1067 let reanchor_context = Config::UniversalLocation::get();
1068 assets
1069 .reanchor(&dest, &reanchor_context)
1070 .map_err(|()| {
1071 tracing::debug!(
1072 target: "xcm::process_instruction::transfer_reserve_asset",
1073 ?assets,
1074 ?dest,
1075 ?reanchor_context,
1076 "Failed to reanchor assets to destination in context"
1077 );
1078 XcmError::LocationFull
1079 })?;
1080 let mut message = vec![ReserveAssetDeposited(assets), ClearOrigin];
1081 message.extend(xcm.0.into_iter());
1082 self_ref.send(dest, Xcm(message), FeeReason::TransferReserveAsset)?;
1083 self_ref.total_surplus.saturating_accrue(total_surplus);
1085 Ok(())
1086 })
1087 },
1088 ReceiveTeleportedAsset(assets) => {
1089 self.ensure_can_subsume_assets(assets.len())?;
1090 Config::TransactionalProcessor::process(|| {
1091 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1092 let mut minted_assets = AssetsInHolding::new();
1093 for asset in assets.inner() {
1095 ensure!(
1098 Config::IsTeleporter::contains(asset, origin),
1099 XcmError::UntrustedTeleportLocation
1100 );
1101 Config::AssetTransactor::can_check_in(origin, asset, &self.context)?;
1106 Config::AssetTransactor::check_in(origin, asset, &self.context);
1107 Config::AssetTransactor::mint_asset(asset, &self.context)
1108 .map(|minted| minted_assets.subsume_assets(minted))?;
1109 }
1110 self.holding.subsume_assets(minted_assets);
1111 Ok(())
1112 })
1113 },
1114 Transact { origin_kind, call, .. } => {
1116 let origin = self.cloned_origin().ok_or_else(|| {
1117 tracing::trace!(
1118 target: "xcm::process_instruction::transact",
1119 "No origin provided",
1120 );
1121
1122 XcmError::BadOrigin
1123 })?;
1124
1125 let message_call = call.try_into().map_err(|_| {
1126 tracing::trace!(
1127 target: "xcm::process_instruction::transact",
1128 "Failed to decode call",
1129 );
1130
1131 XcmError::FailedToDecode
1132 })?;
1133
1134 tracing::trace!(
1135 target: "xcm::process_instruction::transact",
1136 ?message_call,
1137 "Processing call",
1138 );
1139
1140 if !Config::SafeCallFilter::contains(&message_call) {
1141 tracing::trace!(
1142 target: "xcm::process_instruction::transact",
1143 "Call filtered by `SafeCallFilter`",
1144 );
1145
1146 return Err(XcmError::NoPermission)
1147 }
1148
1149 let dispatch_origin =
1150 Config::OriginConverter::convert_origin(origin.clone(), origin_kind).map_err(
1151 |_| {
1152 tracing::trace!(
1153 target: "xcm::process_instruction::transact",
1154 ?origin,
1155 ?origin_kind,
1156 "Failed to convert origin to a local origin."
1157 );
1158
1159 XcmError::BadOrigin
1160 },
1161 )?;
1162
1163 tracing::trace!(
1164 target: "xcm::process_instruction::transact",
1165 origin = ?dispatch_origin,
1166 call = ?message_call,
1167 "Dispatching call with origin",
1168 );
1169
1170 let weight = message_call.get_dispatch_info().call_weight;
1171 let maybe_actual_weight =
1172 match Config::CallDispatcher::dispatch(message_call, dispatch_origin) {
1173 Ok(post_info) => {
1174 tracing::trace!(
1175 target: "xcm::process_instruction::transact",
1176 ?post_info,
1177 "Dispatch successful"
1178 );
1179 self.transact_status = MaybeErrorCode::Success;
1180 post_info.actual_weight
1181 },
1182 Err(error_and_info) => {
1183 tracing::trace!(
1184 target: "xcm::process_instruction::transact",
1185 ?error_and_info,
1186 "Dispatch failed"
1187 );
1188
1189 self.transact_status = error_and_info.error.encode().into();
1190 error_and_info.post_info.actual_weight
1191 },
1192 };
1193 let actual_weight = maybe_actual_weight.unwrap_or(weight);
1194 let surplus = weight.saturating_sub(actual_weight);
1195 self.total_surplus.saturating_accrue(surplus);
1202 Ok(())
1203 },
1204 QueryResponse { query_id, response, max_weight, querier } => {
1205 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1206 Config::TransactionalProcessor::process(|| {
1207 Config::ResponseHandler::on_response(
1208 origin,
1209 query_id,
1210 querier.as_ref(),
1211 response,
1212 max_weight,
1213 &self.context,
1214 );
1215 Ok(())
1216 })
1217 },
1218 DescendOrigin(who) => self.do_descend_origin(who),
1219 ClearOrigin => self.do_clear_origin(),
1220 ExecuteWithOrigin { .. } => Err(XcmError::Unimplemented),
1221 ReportError(response_info) => {
1222 self.transactional_process(|self_ref| {
1225 self_ref.respond(
1226 self_ref.cloned_origin(),
1227 Response::ExecutionResult(self_ref.error),
1228 response_info,
1229 FeeReason::Report,
1230 )?;
1231 Ok(())
1232 })
1233 },
1234 DepositAsset { assets, beneficiary } => {
1235 self.transactional_process(|self_ref| {
1236 let deposited = self_ref.holding.saturating_take(assets);
1237 let surplus = Self::deposit_assets_with_retry(
1238 deposited,
1239 &beneficiary,
1240 Some(&self_ref.context),
1241 )?;
1242 self_ref.total_surplus.saturating_accrue(surplus);
1243 Ok(())
1244 })
1245 },
1246 DepositReserveAsset { assets, dest, xcm } => {
1247 self.transactional_process(|self_ref| {
1248 let mut assets = self_ref.holding.saturating_take(assets);
1249 let maybe_delivery_fee_from_assets = if self_ref.fees.is_empty() && !self_ref.fees_mode.jit_withdraw {
1252 self_ref.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::DepositReserveAsset, &xcm)?
1254 } else {
1255 None
1256 };
1257 let mut message = Vec::with_capacity(xcm.len() + 2);
1258 tracing::trace!(target: "xcm::DepositReserveAsset", ?assets, "Assets except delivery fee");
1259 Self::do_reserve_deposit_assets(
1260 assets,
1261 &dest,
1262 &mut message,
1263 Some(&self_ref.context),
1264 )?;
1265 message.push(ClearOrigin);
1267 message.extend(xcm.0.into_iter());
1269 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1270 self_ref.holding.subsume_assets(delivery_fee);
1272 }
1273 self_ref.send(dest, Xcm(message), FeeReason::DepositReserveAsset)?;
1274 Ok(())
1275 })
1276 },
1277 InitiateReserveWithdraw { assets, reserve, xcm } => {
1278 self.transactional_process(|self_ref| {
1279 let mut assets = self_ref.holding.saturating_take(assets);
1280 let maybe_delivery_fee_from_assets = if self_ref.fees.is_empty() && !self_ref.fees_mode.jit_withdraw {
1283 self_ref.take_delivery_fee_from_assets(&mut assets, &reserve, FeeReason::InitiateReserveWithdraw, &xcm)?
1285 } else {
1286 None
1287 };
1288 let mut message = Vec::with_capacity(xcm.len() + 2);
1289 Self::do_reserve_withdraw_assets(
1290 assets,
1291 &mut self_ref.holding,
1292 &reserve,
1293 &mut message,
1294 )?;
1295 message.push(ClearOrigin);
1297 message.extend(xcm.0.into_iter());
1299 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1300 self_ref.holding.subsume_assets(delivery_fee);
1302 }
1303 self_ref.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?;
1304 Ok(())
1305 })
1306 },
1307 InitiateTeleport { assets, dest, xcm } => {
1308 self.transactional_process(|self_ref| {
1309 let mut assets = self_ref.holding.saturating_take(assets);
1310 let maybe_delivery_fee_from_assets = if self_ref.fees.is_empty() && !self_ref.fees_mode.jit_withdraw {
1313 self_ref.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::InitiateTeleport, &xcm)?
1315 } else {
1316 None
1317 };
1318 let mut message = Vec::with_capacity(xcm.len() + 2);
1319 Self::do_teleport_assets(assets, &dest, &mut message, &self_ref.context)?;
1320 message.push(ClearOrigin);
1322 message.extend(xcm.0.into_iter());
1324 if let Some(delivery_fee) = maybe_delivery_fee_from_assets {
1325 self_ref.holding.subsume_assets(delivery_fee);
1327 }
1328 self_ref.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?;
1329 Ok(())
1330 })
1331 },
1332 InitiateTransfer { destination, remote_fees, preserve_origin, assets, remote_xcm } => {
1333 self.transactional_process(|self_ref| {
1334 let mut message = Vec::with_capacity(assets.len() + remote_xcm.len() + 2);
1335
1336 let remote_fees_paid = if let Some(remote_fees) = remote_fees {
1341 let reanchored_fees = match remote_fees {
1342 AssetTransferFilter::Teleport(fees_filter) => {
1343 let teleport_fees = self_ref
1344 .holding
1345 .try_take(fees_filter)
1346 .map_err(|error| {
1347 tracing::debug!(
1348 target: "xcm::process_instruction::initiate_transfer", ?error,
1349 "Failed to take specified teleport fees from holding"
1350 );
1351 XcmError::NotHoldingFees
1352 })?;
1353 Self::do_teleport_assets(
1354 teleport_fees,
1355 &destination,
1356 &mut message,
1357 &self_ref.context,
1358 )?
1359 },
1360 AssetTransferFilter::ReserveDeposit(fees_filter) => {
1361 let reserve_deposit_fees = self_ref
1362 .holding
1363 .try_take(fees_filter)
1364 .map_err(|error| {
1365 tracing::debug!(
1366 target: "xcm::process_instruction::initiate_transfer", ?error,
1367 "Failed to take specified reserve deposit fees from holding"
1368 );
1369 XcmError::NotHoldingFees
1370 })?;
1371 Self::do_reserve_deposit_assets(
1372 reserve_deposit_fees,
1373 &destination,
1374 &mut message,
1375 Some(&self_ref.context),
1376 )?
1377 },
1378 AssetTransferFilter::ReserveWithdraw(fees_filter) => {
1379 let reserve_withdraw_fees = self_ref
1380 .holding
1381 .try_take(fees_filter)
1382 .map_err(|error| {
1383 tracing::debug!(
1384 target: "xcm::process_instruction::initiate_transfer", ?error,
1385 "Failed to take specified reserve withdraw fees from holding"
1386 );
1387 XcmError::NotHoldingFees
1388 })?;
1389 Self::do_reserve_withdraw_assets(
1390 reserve_withdraw_fees,
1391 &mut self_ref.holding,
1392 &destination,
1393 &mut message,
1394 )?
1395 },
1396 };
1397 ensure!(reanchored_fees.len() == 1, XcmError::TooManyAssets);
1398 let fees =
1399 reanchored_fees.into_inner().pop().ok_or(XcmError::NotHoldingFees)?;
1400 message.push(PayFees { asset: fees });
1403 true
1404 } else {
1405 false
1406 };
1407
1408 for asset_filter in assets {
1410 match asset_filter {
1411 AssetTransferFilter::Teleport(assets) => Self::do_teleport_assets(
1412 self_ref.holding.saturating_take(assets),
1413 &destination,
1414 &mut message,
1415 &self_ref.context,
1416 )?,
1417 AssetTransferFilter::ReserveDeposit(assets) =>
1418 Self::do_reserve_deposit_assets(
1419 self_ref.holding.saturating_take(assets),
1420 &destination,
1421 &mut message,
1422 Some(&self_ref.context),
1423 )?,
1424 AssetTransferFilter::ReserveWithdraw(assets) =>
1425 Self::do_reserve_withdraw_assets(
1426 self_ref.holding.saturating_take(assets),
1427 &mut self_ref.holding,
1428 &destination,
1429 &mut message,
1430 )?,
1431 };
1432 }
1433
1434 match self_ref
1435 .origin_ref() {
1436 Some(origin) if preserve_origin => {
1437 if *origin != Location::here() {
1439 let reanchored_origin = Self::try_reanchor(origin.clone(), &destination)?.0;
1442 message.push(AliasOrigin(reanchored_origin));
1443 }
1444 }
1446 _ => {
1447 message.push(ClearOrigin);
1449 }
1450 }
1451
1452 if !remote_fees_paid {
1456 message
1461 .push(UnpaidExecution { weight_limit: Unlimited, check_origin: None });
1462 }
1463
1464 message.extend(remote_xcm.0.into_iter());
1466 self_ref.send(destination, Xcm(message), FeeReason::InitiateTransfer)?;
1468 Ok(())
1469 })
1470 },
1471 ReportHolding { response_info, assets } => {
1472 let context = Config::UniversalLocation::get();
1473 let assets = self.holding.min(&assets)
1474 .into_inner()
1475 .into_iter()
1476 .filter_map(|a| a.reanchored(&response_info.destination, &context).ok())
1477 .collect::<Vec<Asset>>()
1478 .into();
1479 self.transactional_process(|self_ref| {
1480 self_ref.respond(
1481 self_ref.cloned_origin(),
1482 Response::Assets(assets),
1483 response_info,
1484 FeeReason::Report,
1485 )?;
1486 Ok(())
1487 })
1488 },
1489 BuyExecution { fees, weight_limit } => {
1490 let Some(weight) = Option::<Weight>::from(weight_limit) else { return Ok(()) };
1495 self.transactional_process(|self_ref| {
1496 let max_fee =
1498 self_ref.holding.try_take(fees.clone().into()).map_err(|e| {
1499 tracing::error!(target: "xcm::process_instruction::buy_execution", ?e, ?fees,
1500 "Failed to take fees from holding");
1501 XcmError::NotHoldingFees
1502 })?;
1503 let unspent = self_ref.trader.buy_weight(weight, max_fee, &self_ref.context).map_err(|(unspent, e)| {
1504 self_ref.holding.subsume_assets(unspent);
1505 e
1506 })?;
1507 self_ref.holding.subsume_assets(unspent);
1508 self_ref.asset_used_in_buy_execution = Some(fees.id);
1511 tracing::trace!(
1512 target: "xcm::executor::BuyExecution",
1513 asset_used_in_buy_execution = ?self_ref.asset_used_in_buy_execution
1514 );
1515 Ok(())
1516 })
1517 },
1518 PayFees { asset } => {
1519 if self.already_paid_fees {
1521 return Ok(());
1522 }
1523 self.already_paid_fees = true;
1525 tracing::trace!(
1527 target: "xcm::executor::PayFees",
1528 asset_for_fees = ?asset,
1529 message_weight = ?self.message_weight,
1530 );
1531 self.transactional_process_with_custom_rollback(
1533 |self_ref| {
1534 let max_fee =
1535 self_ref.holding.try_take(asset.into()).map_err(|error| {
1536 tracing::debug!(
1537 target: "xcm::process_instruction::pay_fees", ?error,
1538 "Failed to take fees from holding"
1539 );
1540 XcmError::NotHoldingFees
1541 })?;
1542 let unspent =
1543 self_ref.trader.buy_weight(self_ref.message_weight, max_fee.into(), &self_ref.context).map_err(|(unspent, e)| {
1544 self_ref.fees.subsume_assets(unspent);
1545 e
1546 })?;
1547 self_ref.fees.subsume_assets(unspent);
1549 Ok(())
1550 },
1551 |self_ref| {
1552 self_ref.already_paid_fees = false;
1553 },
1554 )
1555 },
1556 RefundSurplus => self.refund_surplus(),
1557 SetErrorHandler(mut handler) => {
1558 let handler_weight = Config::Weigher::weight(&mut handler, Weight::MAX)
1559 .map_err(|error| {
1560 tracing::debug!(
1561 target: "xcm::executor::SetErrorHandler",
1562 ?error,
1563 ?handler,
1564 "Failed to calculate weight"
1565 );
1566 XcmError::WeightNotComputable
1567 })?;
1568 self.total_surplus.saturating_accrue(self.error_handler_weight);
1569 self.error_handler = handler;
1570 self.error_handler_weight = handler_weight;
1571 Ok(())
1572 },
1573 SetAppendix(mut appendix) => {
1574 let appendix_weight = Config::Weigher::weight(&mut appendix, Weight::MAX)
1575 .map_err(|error| {
1576 tracing::debug!(
1577 target: "xcm::executor::SetErrorHandler",
1578 ?error,
1579 ?appendix,
1580 "Failed to calculate weight"
1581 );
1582 XcmError::WeightNotComputable
1583 })?;
1584 self.total_surplus.saturating_accrue(self.appendix_weight);
1585 self.appendix = appendix;
1586 self.appendix_weight = appendix_weight;
1587 Ok(())
1588 },
1589 ClearError => {
1590 self.error = None;
1591 Ok(())
1592 },
1593 SetHints { hints } => {
1594 for hint in hints.into_iter() {
1595 match hint {
1596 AssetClaimer { location } => {
1597 self.asset_claimer = Some(location)
1598 },
1599 }
1600 }
1601 Ok(())
1602 },
1603 ClaimAsset { assets, ticket } => {
1604 self.ensure_can_subsume_assets(assets.len())?;
1605 self.transactional_process(|self_ref| {
1606 let origin = self_ref.origin_ref().ok_or(XcmError::BadOrigin)?;
1607 let claimed = Config::AssetTrap::claim_assets(
1608 origin,
1609 &ticket,
1610 &assets,
1611 &self_ref.context,
1612 )
1613 .ok_or(XcmError::UnknownClaim)?;
1614 self_ref.holding.subsume_assets(claimed);
1615 Ok(())
1616 })
1617 },
1618 Trap(code) => Err(XcmError::Trap(code)),
1619 SubscribeVersion { query_id, max_response_weight } => {
1620 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1621 ensure!(&self.original_origin == origin, XcmError::BadOrigin);
1624 Config::TransactionalProcessor::process(|| {
1625 Config::SubscriptionService::start(
1626 origin,
1627 query_id,
1628 max_response_weight,
1629 &self.context,
1630 )
1631 })
1632 },
1633 UnsubscribeVersion => {
1634 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1635 ensure!(&self.original_origin == origin, XcmError::BadOrigin);
1636 Config::TransactionalProcessor::process(|| {
1637 Config::SubscriptionService::stop(origin, &self.context)
1638 })
1639 },
1640 BurnAsset(assets) => {
1641 self.holding.saturating_take(assets.into());
1642 Ok(())
1643 },
1644 ExpectAsset(assets) =>
1645 self.holding.ensure_contains(&assets).map_err(|e| {
1646 tracing::error!(target: "xcm::process_instruction::expect_asset", ?e, ?assets, "assets not contained in holding");
1647 XcmError::ExpectationFalse
1648 }),
1649 ExpectOrigin(origin) => {
1650 ensure!(self.context.origin == origin, XcmError::ExpectationFalse);
1651 Ok(())
1652 },
1653 ExpectError(error) => {
1654 ensure!(self.error == error, XcmError::ExpectationFalse);
1655 Ok(())
1656 },
1657 ExpectTransactStatus(transact_status) => {
1658 ensure!(self.transact_status == transact_status, XcmError::ExpectationFalse);
1659 Ok(())
1660 },
1661 QueryPallet { module_name, response_info } => {
1662 let pallets = Config::PalletInstancesInfo::infos()
1663 .into_iter()
1664 .filter(|x| x.module_name.as_bytes() == &module_name[..])
1665 .map(|x| {
1666 PalletInfo::new(
1667 x.index as u32,
1668 x.name.as_bytes().into(),
1669 x.module_name.as_bytes().into(),
1670 x.crate_version.major as u32,
1671 x.crate_version.minor as u32,
1672 x.crate_version.patch as u32,
1673 )
1674 })
1675 .collect::<Result<Vec<_>, XcmError>>()?;
1676 let QueryResponseInfo { destination, query_id, max_weight } = response_info;
1677 let response =
1678 Response::PalletsInfo(pallets.try_into().map_err(|error| {
1679 tracing::debug!(
1680 target: "xcm::process_instruction::query_pallet", ?error,
1681 "Failed to convert pallets to response info"
1682 );
1683 XcmError::Overflow
1684 })?);
1685 let querier = Self::to_querier(self.cloned_origin(), &destination)?;
1686 let instruction = QueryResponse { query_id, response, max_weight, querier };
1687 let message = Xcm(vec![instruction]);
1688 self.transactional_process(|self_ref| {
1689 self_ref.send(destination, message, FeeReason::QueryPallet)?;
1690 Ok(())
1691 })
1692 },
1693 ExpectPallet { index, name, module_name, crate_major, min_crate_minor } => {
1694 let pallet = Config::PalletInstancesInfo::infos()
1695 .into_iter()
1696 .find(|x| x.index == index as usize)
1697 .ok_or(XcmError::PalletNotFound)?;
1698 ensure!(pallet.name.as_bytes() == &name[..], XcmError::NameMismatch);
1699 ensure!(pallet.module_name.as_bytes() == &module_name[..], XcmError::NameMismatch);
1700 let major = pallet.crate_version.major as u32;
1701 ensure!(major == crate_major, XcmError::VersionIncompatible);
1702 let minor = pallet.crate_version.minor as u32;
1703 ensure!(minor >= min_crate_minor, XcmError::VersionIncompatible);
1704 Ok(())
1705 },
1706 ReportTransactStatus(response_info) => {
1707 self.transactional_process(|self_ref| {
1708 self_ref.respond(
1709 self_ref.cloned_origin(),
1710 Response::DispatchResult(self_ref.transact_status.clone()),
1711 response_info,
1712 FeeReason::Report,
1713 )?;
1714 Ok(())
1715 })
1716 },
1717 ClearTransactStatus => {
1718 self.transact_status = Default::default();
1719 Ok(())
1720 },
1721 UniversalOrigin(new_global) => {
1722 let universal_location = Config::UniversalLocation::get();
1723 ensure!(universal_location.first() != Some(&new_global), XcmError::InvalidLocation);
1724 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1725 let origin_xform = (origin, new_global);
1726 let ok = Config::UniversalAliases::contains(&origin_xform);
1727 ensure!(ok, XcmError::InvalidLocation);
1728 let (_, new_global) = origin_xform;
1729 let new_origin = Junctions::from([new_global]).relative_to(&universal_location);
1730 self.context.origin = Some(new_origin);
1731 Ok(())
1732 },
1733 ExportMessage { network, destination, xcm } => {
1734 let origin = self.context.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone();
1744 let universal_source = Config::UniversalLocation::get()
1745 .within_global(origin)
1746 .map_err(|()| {
1747 tracing::debug!(
1748 target: "xcm::process_instruction::export_message",
1749 "Failed to reanchor origin to universal location",
1750 );
1751 XcmError::Unanchored
1752 })?;
1753 let hash = (self.origin_ref(), &destination).using_encoded(blake2_128);
1754 let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0);
1755 let (ticket, fee) = validate_export::<Config::MessageExporter>(
1759 network,
1760 channel,
1761 universal_source,
1762 destination.clone(),
1763 xcm,
1764 )?;
1765 self.transactional_process(|self_ref| {
1766 self_ref.take_fee(fee, FeeReason::Export { network, destination })?;
1767 Config::MessageExporter::deliver(ticket)?;
1772 Ok(())
1773 })
1774 },
1775 LockAsset { asset, unlocker } => {
1776 self.transactional_process(|self_ref| {
1777 let origin = self_ref.cloned_origin().ok_or(XcmError::BadOrigin)?;
1778 let (remote_asset, context) = Self::try_reanchor(asset.clone(), &unlocker)?;
1779 let lock_ticket =
1780 Config::AssetLocker::prepare_lock(unlocker.clone(), asset, origin.clone())?;
1781 let owner = origin.reanchored(&unlocker, &context).map_err(|e| {
1782 tracing::error!(target: "xcm::xcm_executor::process_instruction", ?e, ?unlocker, ?context, "Failed to re-anchor origin");
1783 XcmError::ReanchorFailed
1784 })?;
1785 let msg = Xcm::<()>(vec![NoteUnlockable { asset: remote_asset, owner }]);
1786 let (ticket, price) = validate_send::<Config::XcmSender>(unlocker, msg)?;
1787 self_ref.take_fee(price, FeeReason::LockAsset)?;
1788 lock_ticket.enact()?;
1789 Config::XcmSender::deliver(ticket)?;
1790 Ok(())
1791 })
1792 },
1793 UnlockAsset { asset, target } => {
1794 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1795 Config::TransactionalProcessor::process(|| {
1796 Config::AssetLocker::prepare_unlock(origin, asset, target)?.enact()?;
1797 Ok(())
1798 })
1799 },
1800 NoteUnlockable { asset, owner } => {
1801 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1802 Config::TransactionalProcessor::process(|| {
1803 Config::AssetLocker::note_unlockable(origin, asset, owner)?;
1804 Ok(())
1805 })
1806 },
1807 RequestUnlock { asset, locker } => {
1808 let origin = self.cloned_origin().ok_or(XcmError::BadOrigin)?;
1809 let remote_asset = Self::try_reanchor(asset.clone(), &locker)?.0;
1810 let remote_target = Self::try_reanchor(origin.clone(), &locker)?.0;
1811 let reduce_ticket = Config::AssetLocker::prepare_reduce_unlockable(
1812 locker.clone(),
1813 asset,
1814 origin.clone(),
1815 )?;
1816 let msg =
1817 Xcm::<()>(vec![UnlockAsset { asset: remote_asset, target: remote_target }]);
1818 let (ticket, price) = validate_send::<Config::XcmSender>(locker, msg)?;
1819 self.transactional_process(|self_ref| {
1820 self_ref.take_fee(price, FeeReason::RequestUnlock)?;
1821 reduce_ticket.enact()?;
1822 Config::XcmSender::deliver(ticket)?;
1823 Ok(())
1824 })
1825 },
1826 ExchangeAsset { give, want, maximal } => {
1827 self.transactional_process(|self_ref| {
1828 let give = self_ref.holding.saturating_take(give);
1829 self_ref.ensure_can_subsume_assets(want.len())?;
1830 let received = Config::AssetExchanger::exchange_asset(
1831 self_ref.origin_ref(),
1832 give,
1833 &want,
1834 maximal,
1835 ).map_err(|unspent| {
1836 self_ref.holding.subsume_assets(unspent);
1837 XcmError::NoDeal
1838 })?;
1839 self_ref.holding.subsume_assets(received);
1840 Ok(())
1841 })
1842 },
1843 SetFeesMode { jit_withdraw } => {
1844 self.fees_mode = FeesMode { jit_withdraw };
1845 Ok(())
1846 },
1847 SetTopic(topic) => {
1848 self.context.topic = Some(topic);
1849 Ok(())
1850 },
1851 ClearTopic => {
1852 self.context.topic = None;
1853 Ok(())
1854 },
1855 AliasOrigin(target) => {
1856 let origin = self.origin_ref().ok_or(XcmError::BadOrigin)?;
1857 ensure!(Config::Aliasers::contains(origin, &target), XcmError::NoPermission);
1858 self.context.origin = Some(target);
1859 Ok(())
1860 },
1861 UnpaidExecution { check_origin, .. } => {
1862 ensure!(
1863 check_origin.is_none() || self.context.origin == check_origin,
1864 XcmError::BadOrigin
1865 );
1866 Ok(())
1867 },
1868 HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } =>
1869 Config::TransactionalProcessor::process(|| {
1870 Config::HrmpNewChannelOpenRequestHandler::handle(
1871 sender,
1872 max_message_size,
1873 max_capacity,
1874 )
1875 }),
1876 HrmpChannelAccepted { recipient } => Config::TransactionalProcessor::process(|| {
1877 Config::HrmpChannelAcceptedHandler::handle(recipient)
1878 }),
1879 HrmpChannelClosing { initiator, sender, recipient } =>
1880 Config::TransactionalProcessor::process(|| {
1881 Config::HrmpChannelClosingHandler::handle(initiator, sender, recipient)
1882 }),
1883 }
1884 }
1885
1886 fn do_descend_origin(&mut self, who: InteriorLocation) -> XcmResult {
1887 self.context
1888 .origin
1889 .as_mut()
1890 .ok_or(XcmError::BadOrigin)?
1891 .append_with(who)
1892 .map_err(|e| {
1893 tracing::error!(target: "xcm::do_descend_origin", ?e, "Failed to append junctions");
1894 XcmError::LocationFull
1895 })
1896 }
1897
1898 fn do_clear_origin(&mut self) -> XcmResult {
1899 self.context.origin = None;
1900 Ok(())
1901 }
1902
1903 fn deposit_assets_with_retry(
1920 to_deposit: AssetsInHolding,
1921 beneficiary: &Location,
1922 context: Option<&XcmContext>,
1923 ) -> Result<Weight, XcmError> {
1924 let mut total_surplus = Weight::zero();
1925 let mut failed_deposits = AssetsInHolding::new();
1926
1927 for single in to_deposit.into_per_asset_holdings() {
1929 match Config::AssetTransactor::deposit_asset_with_surplus(single, beneficiary, context)
1930 {
1931 Ok(surplus) => total_surplus.saturating_accrue(surplus),
1932 Err((unspent, _)) => {
1933 failed_deposits.subsume_assets(unspent);
1937 },
1938 }
1939 }
1940
1941 for single in failed_deposits.into_per_asset_holdings() {
1943 let surplus =
1944 Config::AssetTransactor::deposit_asset_with_surplus(single, beneficiary, context)
1945 .map_err(|(unspent, error)| {
1946 tracing::debug!(
1947 target: "xcm::deposit_assets_with_retry",
1948 ?error,
1949 ?unspent,
1950 "Retry-pass deposit failed"
1951 );
1952 error
1953 })?;
1954 total_surplus.saturating_accrue(surplus);
1955 }
1956
1957 Ok(total_surplus)
1958 }
1959
1960 fn take_delivery_fee_from_assets(
1965 &self,
1966 assets: &mut AssetsInHolding,
1967 destination: &Location,
1968 reason: FeeReason,
1969 xcm: &Xcm<()>,
1970 ) -> Result<Option<AssetsInHolding>, XcmError> {
1971 let to_weigh_reanchored = Self::reanchored_assets(&assets, destination);
1972 let remote_instruction = match reason {
1973 FeeReason::DepositReserveAsset => ReserveAssetDeposited(to_weigh_reanchored),
1974 FeeReason::InitiateReserveWithdraw => WithdrawAsset(to_weigh_reanchored),
1975 FeeReason::InitiateTeleport => ReceiveTeleportedAsset(to_weigh_reanchored),
1976 _ => {
1977 tracing::debug!(
1978 target: "xcm::take_delivery_fee_from_assets",
1979 "Unexpected delivery fee reason",
1980 );
1981 return Err(XcmError::NotHoldingFees);
1982 },
1983 };
1984 let mut message_to_weigh = Vec::with_capacity(xcm.len() + 2);
1985 message_to_weigh.push(remote_instruction);
1986 message_to_weigh.push(ClearOrigin);
1987 message_to_weigh.extend(xcm.0.clone().into_iter());
1988 let (_, fee) =
1989 validate_send::<Config::XcmSender>(destination.clone(), Xcm(message_to_weigh))?;
1990 let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| {
1991 tracing::trace!(
1992 target: "xcm::fees::take_delivery_fee_from_assets",
1993 "Asset provided to pay for fees {:?}, asset required for delivery fees: {:?}",
1994 self.asset_used_in_buy_execution, asset_needed_for_fees,
1995 );
1996 let asset_to_pay_for_fees =
1997 self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone());
1998 let delivery_fee = assets.saturating_take(asset_to_pay_for_fees.into());
2000 tracing::trace!(target: "xcm::fees::take_delivery_fee_from_assets", ?delivery_fee);
2001 delivery_fee
2002 });
2003 Ok(maybe_delivery_fee)
2004 }
2005}