1#![cfg_attr(not(feature = "std"), no_std)]
20
21#[cfg(feature = "runtime-benchmarks")]
22pub mod benchmarking;
23#[cfg(test)]
24mod mock;
25#[cfg(test)]
26mod tests;
27
28pub mod migration;
29#[cfg(any(test, feature = "test-utils"))]
30pub mod xcm_helpers;
31
32extern crate alloc;
33
34use alloc::{boxed::Box, vec, vec::Vec};
35use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
36use core::{marker::PhantomData, result::Result};
37use frame_support::{
38 dispatch::{
39 DispatchErrorWithPostInfo, GetDispatchInfo, PostDispatchInfo, WithPostDispatchInfo,
40 },
41 pallet_prelude::*,
42 traits::{
43 Consideration, Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Footprint, Get,
44 LockableCurrency, OriginTrait, WithdrawReasons,
45 },
46 PalletId,
47};
48use frame_system::pallet_prelude::{BlockNumberFor, *};
49pub use pallet::*;
50use scale_info::TypeInfo;
51use sp_core::H256;
52use sp_runtime::{
53 traits::{
54 AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash,
55 Saturating, Zero,
56 },
57 Either, RuntimeDebug, SaturatedConversion,
58};
59use storage::{with_transaction, TransactionOutcome};
60use xcm::{latest::QueryResponseInfo, prelude::*};
61use xcm_builder::{
62 ExecuteController, ExecuteControllerWeightInfo, InspectMessageQueues, QueryController,
63 QueryControllerWeightInfo, SendController, SendControllerWeightInfo,
64};
65use xcm_executor::{
66 traits::{
67 AssetTransferError, CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin,
68 DropAssets, EventEmitter, FeeManager, FeeReason, MatchesFungible, OnResponse, Properties,
69 QueryHandler, QueryResponseStatus, RecordXcm, TransactAsset, TransferType,
70 VersionChangeNotifier, WeightBounds, XcmAssetTransfers,
71 },
72 AssetsInHolding,
73};
74use xcm_runtime_apis::{
75 authorized_aliases::{Error as AuthorizedAliasersApiError, OriginAliaser},
76 dry_run::{CallDryRunEffects, Error as XcmDryRunApiError, XcmDryRunEffects},
77 fees::Error as XcmPaymentApiError,
78 trusted_query::Error as TrustedQueryApiError,
79};
80
81mod errors;
82pub use errors::ExecutionError;
83
84#[cfg(any(feature = "try-runtime", test))]
85use sp_runtime::TryRuntimeError;
86
87pub trait WeightInfo {
88 fn send() -> Weight;
89 fn teleport_assets() -> Weight;
90 fn reserve_transfer_assets() -> Weight;
91 fn transfer_assets() -> Weight;
92 fn execute() -> Weight;
93 fn force_xcm_version() -> Weight;
94 fn force_default_xcm_version() -> Weight;
95 fn force_subscribe_version_notify() -> Weight;
96 fn force_unsubscribe_version_notify() -> Weight;
97 fn force_suspension() -> Weight;
98 fn migrate_supported_version() -> Weight;
99 fn migrate_version_notifiers() -> Weight;
100 fn already_notified_target() -> Weight;
101 fn notify_current_targets() -> Weight;
102 fn notify_target_migration_fail() -> Weight;
103 fn migrate_version_notify_targets() -> Weight;
104 fn migrate_and_notify_old_targets() -> Weight;
105 fn new_query() -> Weight;
106 fn take_response() -> Weight;
107 fn claim_assets() -> Weight;
108 fn add_authorized_alias() -> Weight;
109 fn remove_authorized_alias() -> Weight;
110}
111
112pub struct TestWeightInfo;
114impl WeightInfo for TestWeightInfo {
115 fn send() -> Weight {
116 Weight::from_parts(100_000_000, 0)
117 }
118
119 fn teleport_assets() -> Weight {
120 Weight::from_parts(100_000_000, 0)
121 }
122
123 fn reserve_transfer_assets() -> Weight {
124 Weight::from_parts(100_000_000, 0)
125 }
126
127 fn transfer_assets() -> Weight {
128 Weight::from_parts(100_000_000, 0)
129 }
130
131 fn execute() -> Weight {
132 Weight::from_parts(100_000_000, 0)
133 }
134
135 fn force_xcm_version() -> Weight {
136 Weight::from_parts(100_000_000, 0)
137 }
138
139 fn force_default_xcm_version() -> Weight {
140 Weight::from_parts(100_000_000, 0)
141 }
142
143 fn force_subscribe_version_notify() -> Weight {
144 Weight::from_parts(100_000_000, 0)
145 }
146
147 fn force_unsubscribe_version_notify() -> Weight {
148 Weight::from_parts(100_000_000, 0)
149 }
150
151 fn force_suspension() -> Weight {
152 Weight::from_parts(100_000_000, 0)
153 }
154
155 fn migrate_supported_version() -> Weight {
156 Weight::from_parts(100_000_000, 0)
157 }
158
159 fn migrate_version_notifiers() -> Weight {
160 Weight::from_parts(100_000_000, 0)
161 }
162
163 fn already_notified_target() -> Weight {
164 Weight::from_parts(100_000_000, 0)
165 }
166
167 fn notify_current_targets() -> Weight {
168 Weight::from_parts(100_000_000, 0)
169 }
170
171 fn notify_target_migration_fail() -> Weight {
172 Weight::from_parts(100_000_000, 0)
173 }
174
175 fn migrate_version_notify_targets() -> Weight {
176 Weight::from_parts(100_000_000, 0)
177 }
178
179 fn migrate_and_notify_old_targets() -> Weight {
180 Weight::from_parts(100_000_000, 0)
181 }
182
183 fn new_query() -> Weight {
184 Weight::from_parts(100_000_000, 0)
185 }
186
187 fn take_response() -> Weight {
188 Weight::from_parts(100_000_000, 0)
189 }
190
191 fn claim_assets() -> Weight {
192 Weight::from_parts(100_000_000, 0)
193 }
194
195 fn add_authorized_alias() -> Weight {
196 Weight::from_parts(100_000, 0)
197 }
198
199 fn remove_authorized_alias() -> Weight {
200 Weight::from_parts(100_000, 0)
201 }
202}
203
204#[derive(Clone, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
205pub struct AuthorizedAliasesEntry<Ticket, MAX: Get<u32>> {
206 pub aliasers: BoundedVec<OriginAliaser, MAX>,
207 pub ticket: Ticket,
208}
209
210pub fn aliasers_footprint(aliasers_count: usize) -> Footprint {
211 Footprint::from_parts(aliasers_count, OriginAliaser::max_encoded_len())
212}
213
214#[frame_support::pallet]
215pub mod pallet {
216 use super::*;
217 use frame_support::{
218 dispatch::{GetDispatchInfo, PostDispatchInfo},
219 parameter_types,
220 };
221 use frame_system::Config as SysConfig;
222 use sp_runtime::traits::Dispatchable;
223 use xcm_executor::traits::{MatchesFungible, WeightBounds};
224
225 parameter_types! {
226 pub const CurrentXcmVersion: u32 = XCM_VERSION;
229
230 #[derive(Debug, TypeInfo)]
231 pub const MaxAuthorizedAliases: u32 = 10;
233 }
234
235 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
236
237 #[pallet::pallet]
238 #[pallet::storage_version(STORAGE_VERSION)]
239 #[pallet::without_storage_info]
240 pub struct Pallet<T>(_);
241
242 pub type BalanceOf<T> =
243 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
244 pub type TicketOf<T> = <T as Config>::AuthorizedAliasConsideration;
245
246 #[pallet::config]
247 pub trait Config: frame_system::Config {
249 #[allow(deprecated)]
251 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
252
253 type Currency: LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>;
256
257 type CurrencyMatcher: MatchesFungible<BalanceOf<Self>>;
259
260 type AuthorizedAliasConsideration: Consideration<Self::AccountId, Footprint>;
262
263 type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
266
267 type XcmRouter: SendXcm;
269
270 type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
274
275 type XcmExecuteFilter: Contains<(Location, Xcm<<Self as Config>::RuntimeCall>)>;
277
278 type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall> + XcmAssetTransfers + FeeManager;
280
281 type XcmTeleportFilter: Contains<(Location, Vec<Asset>)>;
283
284 type XcmReserveTransferFilter: Contains<(Location, Vec<Asset>)>;
287
288 type Weigher: WeightBounds<<Self as Config>::RuntimeCall>;
290
291 #[pallet::constant]
293 type UniversalLocation: Get<InteriorLocation>;
294
295 type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
297
298 type RuntimeCall: Parameter
300 + GetDispatchInfo
301 + Dispatchable<
302 RuntimeOrigin = <Self as Config>::RuntimeOrigin,
303 PostInfo = PostDispatchInfo,
304 >;
305
306 const VERSION_DISCOVERY_QUEUE_SIZE: u32;
307
308 #[pallet::constant]
311 type AdvertisedXcmVersion: Get<XcmVersion>;
312
313 type AdminOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin>;
315
316 type TrustedLockers: ContainsPair<Location, Asset>;
319
320 type SovereignAccountOf: ConvertLocation<Self::AccountId>;
322
323 #[pallet::constant]
325 type MaxLockers: Get<u32>;
326
327 #[pallet::constant]
329 type MaxRemoteLockConsumers: Get<u32>;
330
331 type RemoteLockConsumerIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
333
334 type WeightInfo: WeightInfo;
336 }
337
338 impl<T: Config> ExecuteControllerWeightInfo for Pallet<T> {
339 fn execute() -> Weight {
340 T::WeightInfo::execute()
341 }
342 }
343
344 impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pallet<T> {
345 type WeightInfo = Self;
346 fn execute(
347 origin: OriginFor<T>,
348 message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
349 max_weight: Weight,
350 ) -> Result<Weight, DispatchErrorWithPostInfo> {
351 tracing::trace!(target: "xcm::pallet_xcm::execute", ?message, ?max_weight);
352 let outcome = (|| {
353 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
354 let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
355 let message = (*message).try_into().map_err(|()| {
356 tracing::debug!(
357 target: "xcm::pallet_xcm::execute", id=?hash,
358 "Failed to convert VersionedXcm to Xcm",
359 );
360 Error::<T>::BadVersion
361 })?;
362 let value = (origin_location, message);
363 ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
364 let (origin_location, message) = value;
365 Ok(T::XcmExecutor::prepare_and_execute(
366 origin_location,
367 message,
368 &mut hash,
369 max_weight,
370 max_weight,
371 ))
372 })()
373 .map_err(|e: DispatchError| {
374 tracing::debug!(
375 target: "xcm::pallet_xcm::execute", error=?e,
376 "Failed XCM pre-execution validation or filter",
377 );
378 e.with_weight(<Self::WeightInfo as ExecuteControllerWeightInfo>::execute())
379 })?;
380
381 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
382 let weight_used = outcome.weight_used();
383 outcome.ensure_complete().map_err(|error| {
384 tracing::error!(target: "xcm::pallet_xcm::execute", ?error, "XCM execution failed with error");
385 Error::<T>::LocalExecutionIncompleteWithError {
386 index: error.index,
387 error: error.error.into(),
388 }
389 .with_weight(
390 weight_used.saturating_add(
391 <Self::WeightInfo as ExecuteControllerWeightInfo>::execute(),
392 ),
393 )
394 })?;
395 Ok(weight_used)
396 }
397 }
398
399 impl<T: Config> SendControllerWeightInfo for Pallet<T> {
400 fn send() -> Weight {
401 T::WeightInfo::send()
402 }
403 }
404
405 impl<T: Config> SendController<OriginFor<T>> for Pallet<T> {
406 type WeightInfo = Self;
407 fn send(
408 origin: OriginFor<T>,
409 dest: Box<VersionedLocation>,
410 message: Box<VersionedXcm<()>>,
411 ) -> Result<XcmHash, DispatchError> {
412 let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
413 let interior: Junctions = origin_location.clone().try_into().map_err(|_| {
414 tracing::debug!(
415 target: "xcm::pallet_xcm::send",
416 "Failed to convert origin_location to interior Junctions",
417 );
418 Error::<T>::InvalidOrigin
419 })?;
420 let dest = Location::try_from(*dest).map_err(|()| {
421 tracing::debug!(
422 target: "xcm::pallet_xcm::send",
423 "Failed to convert destination VersionedLocation to Location",
424 );
425 Error::<T>::BadVersion
426 })?;
427 let message: Xcm<()> = (*message).try_into().map_err(|()| {
428 tracing::debug!(
429 target: "xcm::pallet_xcm::send",
430 "Failed to convert VersionedXcm message to Xcm",
431 );
432 Error::<T>::BadVersion
433 })?;
434
435 let message_id = Self::send_xcm(interior, dest.clone(), message.clone())
436 .map_err(|error| {
437 tracing::error!(target: "xcm::pallet_xcm::send", ?error, ?dest, ?message, "XCM send failed with error");
438 Error::<T>::from(error)
439 })?;
440 let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
441 Self::deposit_event(e);
442 Ok(message_id)
443 }
444 }
445
446 impl<T: Config> QueryControllerWeightInfo for Pallet<T> {
447 fn query() -> Weight {
448 T::WeightInfo::new_query()
449 }
450 fn take_response() -> Weight {
451 T::WeightInfo::take_response()
452 }
453 }
454
455 impl<T: Config> QueryController<OriginFor<T>, BlockNumberFor<T>> for Pallet<T> {
456 type WeightInfo = Self;
457
458 fn query(
459 origin: OriginFor<T>,
460 timeout: BlockNumberFor<T>,
461 match_querier: VersionedLocation,
462 ) -> Result<QueryId, DispatchError> {
463 let responder = <T as Config>::ExecuteXcmOrigin::ensure_origin(origin)?;
464 let query_id = <Self as QueryHandler>::new_query(
465 responder,
466 timeout,
467 Location::try_from(match_querier).map_err(|_| {
468 tracing::debug!(
469 target: "xcm::pallet_xcm::query",
470 "Failed to convert VersionedLocation for match_querier",
471 );
472 Into::<DispatchError>::into(Error::<T>::BadVersion)
473 })?,
474 );
475
476 Ok(query_id)
477 }
478 }
479
480 impl<T: Config> EventEmitter for Pallet<T> {
481 fn emit_sent_event(
482 origin: Location,
483 destination: Location,
484 message: Option<Xcm<()>>,
485 message_id: XcmHash,
486 ) {
487 Self::deposit_event(Event::Sent {
488 origin,
489 destination,
490 message: message.unwrap_or_default(),
491 message_id,
492 });
493 }
494
495 fn emit_send_failure_event(
496 origin: Location,
497 destination: Location,
498 error: SendError,
499 message_id: XcmHash,
500 ) {
501 Self::deposit_event(Event::SendFailed { origin, destination, error, message_id });
502 }
503
504 fn emit_process_failure_event(origin: Location, error: XcmError, message_id: XcmHash) {
505 Self::deposit_event(Event::ProcessXcmError { origin, error, message_id });
506 }
507 }
508
509 #[pallet::event]
510 #[pallet::generate_deposit(pub(super) fn deposit_event)]
511 pub enum Event<T: Config> {
512 Attempted { outcome: xcm::latest::Outcome },
514 Sent { origin: Location, destination: Location, message: Xcm<()>, message_id: XcmHash },
516 SendFailed {
518 origin: Location,
519 destination: Location,
520 error: SendError,
521 message_id: XcmHash,
522 },
523 ProcessXcmError { origin: Location, error: XcmError, message_id: XcmHash },
525 UnexpectedResponse { origin: Location, query_id: QueryId },
529 ResponseReady { query_id: QueryId, response: Response },
532 Notified { query_id: QueryId, pallet_index: u8, call_index: u8 },
535 NotifyOverweight {
539 query_id: QueryId,
540 pallet_index: u8,
541 call_index: u8,
542 actual_weight: Weight,
543 max_budgeted_weight: Weight,
544 },
545 NotifyDispatchError { query_id: QueryId, pallet_index: u8, call_index: u8 },
548 NotifyDecodeFailed { query_id: QueryId, pallet_index: u8, call_index: u8 },
552 InvalidResponder {
556 origin: Location,
557 query_id: QueryId,
558 expected_location: Option<Location>,
559 },
560 InvalidResponderVersion { origin: Location, query_id: QueryId },
568 ResponseTaken { query_id: QueryId },
570 AssetsTrapped { hash: H256, origin: Location, assets: VersionedAssets },
572 VersionChangeNotified {
576 destination: Location,
577 result: XcmVersion,
578 cost: Assets,
579 message_id: XcmHash,
580 },
581 SupportedVersionChanged { location: Location, version: XcmVersion },
584 NotifyTargetSendFail { location: Location, query_id: QueryId, error: XcmError },
587 NotifyTargetMigrationFail { location: VersionedLocation, query_id: QueryId },
590 InvalidQuerierVersion { origin: Location, query_id: QueryId },
598 InvalidQuerier {
602 origin: Location,
603 query_id: QueryId,
604 expected_querier: Location,
605 maybe_actual_querier: Option<Location>,
606 },
607 VersionNotifyStarted { destination: Location, cost: Assets, message_id: XcmHash },
610 VersionNotifyRequested { destination: Location, cost: Assets, message_id: XcmHash },
612 VersionNotifyUnrequested { destination: Location, cost: Assets, message_id: XcmHash },
615 FeesPaid { paying: Location, fees: Assets },
617 AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets },
619 VersionMigrationFinished { version: XcmVersion },
621 AliasAuthorized { aliaser: Location, target: Location, expiry: Option<u64> },
624 AliasAuthorizationRemoved { aliaser: Location, target: Location },
626 AliasesAuthorizationsRemoved { target: Location },
628 }
629
630 #[pallet::origin]
631 #[derive(
632 PartialEq,
633 Eq,
634 Clone,
635 Encode,
636 Decode,
637 DecodeWithMemTracking,
638 RuntimeDebug,
639 TypeInfo,
640 MaxEncodedLen,
641 )]
642 pub enum Origin {
643 Xcm(Location),
645 Response(Location),
647 }
648 impl From<Location> for Origin {
649 fn from(location: Location) -> Origin {
650 Origin::Xcm(location)
651 }
652 }
653
654 #[pallet::composite_enum]
656 pub enum HoldReason {
657 AuthorizeAlias,
659 }
660
661 #[pallet::error]
662 pub enum Error<T> {
663 Unreachable,
666 SendFailure,
669 Filtered,
671 UnweighableMessage,
673 DestinationNotInvertible,
675 Empty,
677 CannotReanchor,
679 TooManyAssets,
681 InvalidOrigin,
683 BadVersion,
685 BadLocation,
688 NoSubscription,
690 AlreadySubscribed,
692 CannotCheckOutTeleport,
694 LowBalance,
696 TooManyLocks,
698 AccountNotSovereign,
700 FeesNotMet,
702 LockNotFound,
704 InUse,
706 #[codec(index = 21)]
708 InvalidAssetUnknownReserve,
709 #[codec(index = 22)]
711 InvalidAssetUnsupportedReserve,
712 #[codec(index = 23)]
714 TooManyReserves,
715 #[deprecated(since = "20.0.0", note = "Use `LocalExecutionIncompleteWithError` instead")]
717 #[codec(index = 24)]
718 LocalExecutionIncomplete,
719 #[codec(index = 25)]
721 TooManyAuthorizedAliases,
722 #[codec(index = 26)]
724 ExpiresInPast,
725 #[codec(index = 27)]
727 AliasNotFound,
728 #[codec(index = 28)]
731 LocalExecutionIncompleteWithError { index: InstructionIndex, error: ExecutionError },
732 }
733
734 impl<T: Config> From<SendError> for Error<T> {
735 fn from(e: SendError) -> Self {
736 match e {
737 SendError::Fees => Error::<T>::FeesNotMet,
738 SendError::NotApplicable => Error::<T>::Unreachable,
739 _ => Error::<T>::SendFailure,
740 }
741 }
742 }
743
744 impl<T: Config> From<AssetTransferError> for Error<T> {
745 fn from(e: AssetTransferError) -> Self {
746 match e {
747 AssetTransferError::UnknownReserve => Error::<T>::InvalidAssetUnknownReserve,
748 }
749 }
750 }
751
752 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
754 pub enum QueryStatus<BlockNumber> {
755 Pending {
757 responder: VersionedLocation,
760 maybe_match_querier: Option<VersionedLocation>,
763 maybe_notify: Option<(u8, u8)>,
764 timeout: BlockNumber,
765 },
766 VersionNotifier { origin: VersionedLocation, is_active: bool },
768 Ready { response: VersionedResponse, at: BlockNumber },
770 }
771
772 #[derive(Copy, Clone)]
773 pub(crate) struct LatestVersionedLocation<'a>(pub(crate) &'a Location);
774 impl<'a> EncodeLike<VersionedLocation> for LatestVersionedLocation<'a> {}
775 impl<'a> Encode for LatestVersionedLocation<'a> {
776 fn encode(&self) -> Vec<u8> {
777 let mut r = VersionedLocation::from(Location::default()).encode();
778 r.truncate(1);
779 self.0.using_encoded(|d| r.extend_from_slice(d));
780 r
781 }
782 }
783
784 #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
785 pub enum VersionMigrationStage {
786 MigrateSupportedVersion,
787 MigrateVersionNotifiers,
788 NotifyCurrentTargets(Option<Vec<u8>>),
789 MigrateAndNotifyOldTargets,
790 }
791
792 impl Default for VersionMigrationStage {
793 fn default() -> Self {
794 Self::MigrateSupportedVersion
795 }
796 }
797
798 #[pallet::storage]
800 pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;
801
802 #[pallet::storage]
804 pub(super) type Queries<T: Config> =
805 StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<BlockNumberFor<T>>, OptionQuery>;
806
807 #[pallet::storage]
812 pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;
813
814 #[pallet::storage]
817 #[pallet::whitelist_storage]
818 pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
819
820 #[pallet::storage]
822 pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
823 _,
824 Twox64Concat,
825 XcmVersion,
826 Blake2_128Concat,
827 VersionedLocation,
828 XcmVersion,
829 OptionQuery,
830 >;
831
832 #[pallet::storage]
834 pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
835 _,
836 Twox64Concat,
837 XcmVersion,
838 Blake2_128Concat,
839 VersionedLocation,
840 QueryId,
841 OptionQuery,
842 >;
843
844 #[pallet::storage]
847 pub(super) type VersionNotifyTargets<T: Config> = StorageDoubleMap<
848 _,
849 Twox64Concat,
850 XcmVersion,
851 Blake2_128Concat,
852 VersionedLocation,
853 (QueryId, Weight, XcmVersion),
854 OptionQuery,
855 >;
856
857 pub struct VersionDiscoveryQueueSize<T>(PhantomData<T>);
858 impl<T: Config> Get<u32> for VersionDiscoveryQueueSize<T> {
859 fn get() -> u32 {
860 T::VERSION_DISCOVERY_QUEUE_SIZE
861 }
862 }
863
864 #[pallet::storage]
868 #[pallet::whitelist_storage]
869 pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
870 _,
871 BoundedVec<(VersionedLocation, u32), VersionDiscoveryQueueSize<T>>,
872 ValueQuery,
873 >;
874
875 #[pallet::storage]
877 pub(super) type CurrentMigration<T: Config> =
878 StorageValue<_, VersionMigrationStage, OptionQuery>;
879
880 #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
881 #[scale_info(skip_type_params(MaxConsumers))]
882 pub struct RemoteLockedFungibleRecord<ConsumerIdentifier, MaxConsumers: Get<u32>> {
883 pub amount: u128,
885 pub owner: VersionedLocation,
887 pub locker: VersionedLocation,
889 pub consumers: BoundedVec<(ConsumerIdentifier, u128), MaxConsumers>,
893 }
894
895 impl<LockId, MaxConsumers: Get<u32>> RemoteLockedFungibleRecord<LockId, MaxConsumers> {
896 pub fn amount_held(&self) -> Option<u128> {
899 self.consumers.iter().max_by(|x, y| x.1.cmp(&y.1)).map(|max| max.1)
900 }
901 }
902
903 #[pallet::storage]
905 pub(super) type RemoteLockedFungibles<T: Config> = StorageNMap<
906 _,
907 (
908 NMapKey<Twox64Concat, XcmVersion>,
909 NMapKey<Blake2_128Concat, T::AccountId>,
910 NMapKey<Blake2_128Concat, VersionedAssetId>,
911 ),
912 RemoteLockedFungibleRecord<T::RemoteLockConsumerIdentifier, T::MaxRemoteLockConsumers>,
913 OptionQuery,
914 >;
915
916 #[pallet::storage]
918 pub(super) type LockedFungibles<T: Config> = StorageMap<
919 _,
920 Blake2_128Concat,
921 T::AccountId,
922 BoundedVec<(BalanceOf<T>, VersionedLocation), T::MaxLockers>,
923 OptionQuery,
924 >;
925
926 #[pallet::storage]
928 pub(super) type XcmExecutionSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
929
930 #[pallet::storage]
938 pub(crate) type ShouldRecordXcm<T: Config> = StorageValue<_, bool, ValueQuery>;
939
940 #[pallet::storage]
947 pub(crate) type RecordedXcm<T: Config> = StorageValue<_, Xcm<()>>;
948
949 #[pallet::storage]
953 pub(super) type AuthorizedAliases<T: Config> = StorageMap<
954 _,
955 Blake2_128Concat,
956 VersionedLocation,
957 AuthorizedAliasesEntry<TicketOf<T>, MaxAuthorizedAliases>,
958 OptionQuery,
959 >;
960
961 #[pallet::genesis_config]
962 pub struct GenesisConfig<T: Config> {
963 #[serde(skip)]
964 pub _config: core::marker::PhantomData<T>,
965 pub safe_xcm_version: Option<XcmVersion>,
967 }
968
969 impl<T: Config> Default for GenesisConfig<T> {
970 fn default() -> Self {
971 Self { safe_xcm_version: Some(XCM_VERSION), _config: Default::default() }
972 }
973 }
974
975 #[pallet::genesis_build]
976 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
977 fn build(&self) {
978 SafeXcmVersion::<T>::set(self.safe_xcm_version);
979 }
980 }
981
982 #[pallet::hooks]
983 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
984 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
985 let mut weight_used = Weight::zero();
986 if let Some(migration) = CurrentMigration::<T>::get() {
987 let max_weight = T::BlockWeights::get().max_block / 10;
989 let (w, maybe_migration) = Self::lazy_migration(migration, max_weight);
990 if maybe_migration.is_none() {
991 Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION });
992 }
993 CurrentMigration::<T>::set(maybe_migration);
994 weight_used.saturating_accrue(w);
995 }
996
997 let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
1000 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
1002 q.sort_by_key(|i| i.1);
1003 while let Some((versioned_dest, _)) = q.pop() {
1004 if let Ok(dest) = Location::try_from(versioned_dest) {
1005 if Self::request_version_notify(dest).is_ok() {
1006 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
1008 break
1009 }
1010 }
1011 }
1012 if let Ok(q) = BoundedVec::try_from(q) {
1015 VersionDiscoveryQueue::<T>::put(q);
1016 }
1017 weight_used
1018 }
1019
1020 #[cfg(feature = "try-runtime")]
1021 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1022 Self::do_try_state()
1023 }
1024 }
1025
1026 pub mod migrations {
1027 use super::*;
1028 use frame_support::traits::{PalletInfoAccess, StorageVersion};
1029
1030 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
1031 enum QueryStatusV0<BlockNumber> {
1032 Pending {
1033 responder: VersionedLocation,
1034 maybe_notify: Option<(u8, u8)>,
1035 timeout: BlockNumber,
1036 },
1037 VersionNotifier {
1038 origin: VersionedLocation,
1039 is_active: bool,
1040 },
1041 Ready {
1042 response: VersionedResponse,
1043 at: BlockNumber,
1044 },
1045 }
1046 impl<B> From<QueryStatusV0<B>> for QueryStatus<B> {
1047 fn from(old: QueryStatusV0<B>) -> Self {
1048 use QueryStatusV0::*;
1049 match old {
1050 Pending { responder, maybe_notify, timeout } => QueryStatus::Pending {
1051 responder,
1052 maybe_notify,
1053 timeout,
1054 maybe_match_querier: Some(Location::here().into()),
1055 },
1056 VersionNotifier { origin, is_active } =>
1057 QueryStatus::VersionNotifier { origin, is_active },
1058 Ready { response, at } => QueryStatus::Ready { response, at },
1059 }
1060 }
1061 }
1062
1063 pub fn migrate_to_v1<T: Config, P: GetStorageVersion + PalletInfoAccess>(
1064 ) -> frame_support::weights::Weight {
1065 let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
1066 tracing::info!(
1067 target: "runtime::xcm",
1068 ?on_chain_storage_version,
1069 "Running migration storage v1 for xcm with storage version",
1070 );
1071
1072 if on_chain_storage_version < 1 {
1073 let mut count = 0;
1074 Queries::<T>::translate::<QueryStatusV0<BlockNumberFor<T>>, _>(|_key, value| {
1075 count += 1;
1076 Some(value.into())
1077 });
1078 StorageVersion::new(1).put::<P>();
1079 tracing::info!(
1080 target: "runtime::xcm",
1081 ?on_chain_storage_version,
1082 "Running migration storage v1 for xcm with storage version was complete",
1083 );
1084 T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
1086 } else {
1087 tracing::warn!(
1088 target: "runtime::xcm",
1089 ?on_chain_storage_version,
1090 "Attempted to apply migration to v1 but failed because storage version is",
1091 );
1092 T::DbWeight::get().reads(1)
1093 }
1094 }
1095 }
1096
1097 #[pallet::call(weight(<T as Config>::WeightInfo))]
1098 impl<T: Config> Pallet<T> {
1099 #[pallet::call_index(0)]
1100 pub fn send(
1101 origin: OriginFor<T>,
1102 dest: Box<VersionedLocation>,
1103 message: Box<VersionedXcm<()>>,
1104 ) -> DispatchResult {
1105 <Self as SendController<_>>::send(origin, dest, message)?;
1106 Ok(())
1107 }
1108
1109 #[pallet::call_index(1)]
1128 #[allow(deprecated)]
1129 #[deprecated(
1130 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_teleport_assets` or `transfer_assets`"
1131 )]
1132 pub fn teleport_assets(
1133 origin: OriginFor<T>,
1134 dest: Box<VersionedLocation>,
1135 beneficiary: Box<VersionedLocation>,
1136 assets: Box<VersionedAssets>,
1137 fee_asset_item: u32,
1138 ) -> DispatchResult {
1139 Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
1140 }
1141
1142 #[pallet::call_index(2)]
1173 #[allow(deprecated)]
1174 #[deprecated(
1175 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_reserve_transfer_assets` or `transfer_assets`"
1176 )]
1177 pub fn reserve_transfer_assets(
1178 origin: OriginFor<T>,
1179 dest: Box<VersionedLocation>,
1180 beneficiary: Box<VersionedLocation>,
1181 assets: Box<VersionedAssets>,
1182 fee_asset_item: u32,
1183 ) -> DispatchResult {
1184 Self::do_reserve_transfer_assets(
1185 origin,
1186 dest,
1187 beneficiary,
1188 assets,
1189 fee_asset_item,
1190 Unlimited,
1191 )
1192 }
1193
1194 #[pallet::call_index(3)]
1203 #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
1204 pub fn execute(
1205 origin: OriginFor<T>,
1206 message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
1207 max_weight: Weight,
1208 ) -> DispatchResultWithPostInfo {
1209 let weight_used =
1210 <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
1211 Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())
1212 }
1213
1214 #[pallet::call_index(4)]
1221 pub fn force_xcm_version(
1222 origin: OriginFor<T>,
1223 location: Box<Location>,
1224 version: XcmVersion,
1225 ) -> DispatchResult {
1226 T::AdminOrigin::ensure_origin(origin)?;
1227 let location = *location;
1228 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&location), version);
1229 Self::deposit_event(Event::SupportedVersionChanged { location, version });
1230 Ok(())
1231 }
1232
1233 #[pallet::call_index(5)]
1239 pub fn force_default_xcm_version(
1240 origin: OriginFor<T>,
1241 maybe_xcm_version: Option<XcmVersion>,
1242 ) -> DispatchResult {
1243 T::AdminOrigin::ensure_origin(origin)?;
1244 SafeXcmVersion::<T>::set(maybe_xcm_version);
1245 Ok(())
1246 }
1247
1248 #[pallet::call_index(6)]
1253 pub fn force_subscribe_version_notify(
1254 origin: OriginFor<T>,
1255 location: Box<VersionedLocation>,
1256 ) -> DispatchResult {
1257 T::AdminOrigin::ensure_origin(origin)?;
1258 let location: Location = (*location).try_into().map_err(|()| {
1259 tracing::debug!(
1260 target: "xcm::pallet_xcm::force_subscribe_version_notify",
1261 "Failed to convert VersionedLocation for subscription target"
1262 );
1263 Error::<T>::BadLocation
1264 })?;
1265 Self::request_version_notify(location).map_err(|e| {
1266 tracing::debug!(
1267 target: "xcm::pallet_xcm::force_subscribe_version_notify", error=?e,
1268 "Failed to subscribe for version notifications for location"
1269 );
1270 match e {
1271 XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
1272 _ => Error::<T>::InvalidOrigin,
1273 }
1274 .into()
1275 })
1276 }
1277
1278 #[pallet::call_index(7)]
1285 pub fn force_unsubscribe_version_notify(
1286 origin: OriginFor<T>,
1287 location: Box<VersionedLocation>,
1288 ) -> DispatchResult {
1289 T::AdminOrigin::ensure_origin(origin)?;
1290 let location: Location = (*location).try_into().map_err(|()| {
1291 tracing::debug!(
1292 target: "xcm::pallet_xcm::force_unsubscribe_version_notify",
1293 "Failed to convert VersionedLocation for unsubscription target"
1294 );
1295 Error::<T>::BadLocation
1296 })?;
1297 Self::unrequest_version_notify(location).map_err(|e| {
1298 tracing::debug!(
1299 target: "xcm::pallet_xcm::force_unsubscribe_version_notify", error=?e,
1300 "Failed to unsubscribe from version notifications for location"
1301 );
1302 match e {
1303 XcmError::InvalidLocation => Error::<T>::NoSubscription,
1304 _ => Error::<T>::InvalidOrigin,
1305 }
1306 .into()
1307 })
1308 }
1309
1310 #[pallet::call_index(8)]
1341 #[pallet::weight(T::WeightInfo::reserve_transfer_assets())]
1342 pub fn limited_reserve_transfer_assets(
1343 origin: OriginFor<T>,
1344 dest: Box<VersionedLocation>,
1345 beneficiary: Box<VersionedLocation>,
1346 assets: Box<VersionedAssets>,
1347 fee_asset_item: u32,
1348 weight_limit: WeightLimit,
1349 ) -> DispatchResult {
1350 Self::do_reserve_transfer_assets(
1351 origin,
1352 dest,
1353 beneficiary,
1354 assets,
1355 fee_asset_item,
1356 weight_limit,
1357 )
1358 }
1359
1360 #[pallet::call_index(9)]
1379 #[pallet::weight(T::WeightInfo::teleport_assets())]
1380 pub fn limited_teleport_assets(
1381 origin: OriginFor<T>,
1382 dest: Box<VersionedLocation>,
1383 beneficiary: Box<VersionedLocation>,
1384 assets: Box<VersionedAssets>,
1385 fee_asset_item: u32,
1386 weight_limit: WeightLimit,
1387 ) -> DispatchResult {
1388 Self::do_teleport_assets(
1389 origin,
1390 dest,
1391 beneficiary,
1392 assets,
1393 fee_asset_item,
1394 weight_limit,
1395 )
1396 }
1397
1398 #[pallet::call_index(10)]
1403 pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
1404 T::AdminOrigin::ensure_origin(origin)?;
1405 XcmExecutionSuspended::<T>::set(suspended);
1406 Ok(())
1407 }
1408
1409 #[pallet::call_index(11)]
1443 pub fn transfer_assets(
1444 origin: OriginFor<T>,
1445 dest: Box<VersionedLocation>,
1446 beneficiary: Box<VersionedLocation>,
1447 assets: Box<VersionedAssets>,
1448 fee_asset_item: u32,
1449 weight_limit: WeightLimit,
1450 ) -> DispatchResult {
1451 let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1452 let dest = (*dest).try_into().map_err(|()| {
1453 tracing::debug!(
1454 target: "xcm::pallet_xcm::transfer_assets",
1455 "Failed to convert destination VersionedLocation",
1456 );
1457 Error::<T>::BadVersion
1458 })?;
1459 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
1460 tracing::debug!(
1461 target: "xcm::pallet_xcm::transfer_assets",
1462 "Failed to convert beneficiary VersionedLocation",
1463 );
1464 Error::<T>::BadVersion
1465 })?;
1466 let assets: Assets = (*assets).try_into().map_err(|()| {
1467 tracing::debug!(
1468 target: "xcm::pallet_xcm::transfer_assets",
1469 "Failed to convert VersionedAssets",
1470 );
1471 Error::<T>::BadVersion
1472 })?;
1473 tracing::debug!(
1474 target: "xcm::pallet_xcm::transfer_assets",
1475 ?origin, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
1476 );
1477
1478 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1479 let assets = assets.into_inner();
1480 let fee_asset_item = fee_asset_item as usize;
1481 let (fees_transfer_type, assets_transfer_type) =
1483 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
1484
1485 Self::do_transfer_assets(
1486 origin,
1487 dest,
1488 Either::Left(beneficiary),
1489 assets,
1490 assets_transfer_type,
1491 fee_asset_item,
1492 fees_transfer_type,
1493 weight_limit,
1494 )
1495 }
1496
1497 #[pallet::call_index(12)]
1504 pub fn claim_assets(
1505 origin: OriginFor<T>,
1506 assets: Box<VersionedAssets>,
1507 beneficiary: Box<VersionedLocation>,
1508 ) -> DispatchResult {
1509 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1510 tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?origin_location, ?assets, ?beneficiary);
1511 let assets_version = assets.identify_version();
1513 let assets: Assets = (*assets).try_into().map_err(|()| {
1514 tracing::debug!(
1515 target: "xcm::pallet_xcm::claim_assets",
1516 "Failed to convert input VersionedAssets",
1517 );
1518 Error::<T>::BadVersion
1519 })?;
1520 let number_of_assets = assets.len() as u32;
1521 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
1522 tracing::debug!(
1523 target: "xcm::pallet_xcm::claim_assets",
1524 "Failed to convert beneficiary VersionedLocation",
1525 );
1526 Error::<T>::BadVersion
1527 })?;
1528 let ticket: Location = GeneralIndex(assets_version as u128).into();
1529 let mut message = Xcm(vec![
1530 ClaimAsset { assets, ticket },
1531 DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
1532 ]);
1533 let weight = T::Weigher::weight(&mut message, Weight::MAX).map_err(|error| {
1534 tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?error, "Failed to calculate weight");
1535 Error::<T>::UnweighableMessage
1536 })?;
1537 let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
1538 let outcome = T::XcmExecutor::prepare_and_execute(
1539 origin_location,
1540 message,
1541 &mut hash,
1542 weight,
1543 weight,
1544 );
1545 outcome.ensure_complete().map_err(|error| {
1546 tracing::error!(target: "xcm::pallet_xcm::claim_assets", ?error, "XCM execution failed with error");
1547 Error::<T>::LocalExecutionIncompleteWithError { index: error.index, error: error.error.into()}
1548 })?;
1549 Ok(())
1550 }
1551
1552 #[pallet::call_index(13)]
1601 #[pallet::weight(T::WeightInfo::transfer_assets())]
1602 pub fn transfer_assets_using_type_and_then(
1603 origin: OriginFor<T>,
1604 dest: Box<VersionedLocation>,
1605 assets: Box<VersionedAssets>,
1606 assets_transfer_type: Box<TransferType>,
1607 remote_fees_id: Box<VersionedAssetId>,
1608 fees_transfer_type: Box<TransferType>,
1609 custom_xcm_on_dest: Box<VersionedXcm<()>>,
1610 weight_limit: WeightLimit,
1611 ) -> DispatchResult {
1612 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1613 let dest: Location = (*dest).try_into().map_err(|()| {
1614 tracing::debug!(
1615 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1616 "Failed to convert destination VersionedLocation",
1617 );
1618 Error::<T>::BadVersion
1619 })?;
1620 let assets: Assets = (*assets).try_into().map_err(|()| {
1621 tracing::debug!(
1622 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1623 "Failed to convert VersionedAssets",
1624 );
1625 Error::<T>::BadVersion
1626 })?;
1627 let fees_id: AssetId = (*remote_fees_id).try_into().map_err(|()| {
1628 tracing::debug!(
1629 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1630 "Failed to convert remote_fees_id VersionedAssetId",
1631 );
1632 Error::<T>::BadVersion
1633 })?;
1634 let remote_xcm: Xcm<()> = (*custom_xcm_on_dest).try_into().map_err(|()| {
1635 tracing::debug!(
1636 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1637 "Failed to convert custom_xcm_on_dest VersionedXcm",
1638 );
1639 Error::<T>::BadVersion
1640 })?;
1641 tracing::debug!(
1642 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1643 ?origin_location, ?dest, ?assets, ?assets_transfer_type, ?fees_id, ?fees_transfer_type,
1644 ?remote_xcm, ?weight_limit,
1645 );
1646
1647 let assets = assets.into_inner();
1648 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1649
1650 let fee_asset_index =
1651 assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
1652 Self::do_transfer_assets(
1653 origin_location,
1654 dest,
1655 Either::Right(remote_xcm),
1656 assets,
1657 *assets_transfer_type,
1658 fee_asset_index,
1659 *fees_transfer_type,
1660 weight_limit,
1661 )
1662 }
1663
1664 #[pallet::call_index(14)]
1676 pub fn add_authorized_alias(
1677 origin: OriginFor<T>,
1678 aliaser: Box<VersionedLocation>,
1679 expires: Option<u64>,
1680 ) -> DispatchResult {
1681 let signed_origin = ensure_signed(origin.clone())?;
1682 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1683 let new_aliaser: Location = (*aliaser).try_into().map_err(|()| {
1684 tracing::debug!(
1685 target: "xcm::pallet_xcm::add_authorized_alias",
1686 "Failed to convert aliaser VersionedLocation",
1687 );
1688 Error::<T>::BadVersion
1689 })?;
1690 ensure!(origin_location != new_aliaser, Error::<T>::BadLocation);
1691 let origin_location = match origin_location.unpack() {
1693 (0, [AccountId32 { network: _, id }]) =>
1694 Location::new(0, [AccountId32 { network: None, id: *id }]),
1695 _ => return Err(Error::<T>::InvalidOrigin.into()),
1696 };
1697 tracing::debug!(target: "xcm::pallet_xcm::add_authorized_alias", ?origin_location, ?new_aliaser, ?expires);
1698 ensure!(origin_location != new_aliaser, Error::<T>::BadLocation);
1699 if let Some(expiry) = expires {
1700 ensure!(
1701 expiry >
1702 frame_system::Pallet::<T>::current_block_number().saturated_into::<u64>(),
1703 Error::<T>::ExpiresInPast
1704 );
1705 }
1706 let versioned_origin = VersionedLocation::from(origin_location.clone());
1707 let versioned_aliaser = VersionedLocation::from(new_aliaser.clone());
1708 let entry = if let Some(entry) = AuthorizedAliases::<T>::get(&versioned_origin) {
1709 let (mut aliasers, mut ticket) = (entry.aliasers, entry.ticket);
1711 if let Some(aliaser) =
1712 aliasers.iter_mut().find(|aliaser| aliaser.location == versioned_aliaser)
1713 {
1714 aliaser.expiry = expires;
1716 } else {
1717 let aliaser =
1719 OriginAliaser { location: versioned_aliaser.clone(), expiry: expires };
1720 aliasers.try_push(aliaser).map_err(|_| {
1721 tracing::debug!(
1722 target: "xcm::pallet_xcm::add_authorized_alias",
1723 "Failed to add new aliaser to existing entry",
1724 );
1725 Error::<T>::TooManyAuthorizedAliases
1726 })?;
1727 ticket = ticket.update(&signed_origin, aliasers_footprint(aliasers.len()))?;
1729 }
1730 AuthorizedAliasesEntry { aliasers, ticket }
1731 } else {
1732 let ticket = TicketOf::<T>::new(&signed_origin, aliasers_footprint(1))?;
1734 let aliaser =
1735 OriginAliaser { location: versioned_aliaser.clone(), expiry: expires };
1736 let mut aliasers = BoundedVec::<OriginAliaser, MaxAuthorizedAliases>::new();
1737 aliasers.try_push(aliaser).map_err(|error| {
1738 tracing::debug!(
1739 target: "xcm::pallet_xcm::add_authorized_alias", ?error,
1740 "Failed to add first aliaser to new entry",
1741 );
1742 Error::<T>::TooManyAuthorizedAliases
1743 })?;
1744 AuthorizedAliasesEntry { aliasers, ticket }
1745 };
1746 AuthorizedAliases::<T>::insert(&versioned_origin, entry);
1748 Self::deposit_event(Event::AliasAuthorized {
1749 aliaser: new_aliaser,
1750 target: origin_location,
1751 expiry: expires,
1752 });
1753 Ok(())
1754 }
1755
1756 #[pallet::call_index(15)]
1759 pub fn remove_authorized_alias(
1760 origin: OriginFor<T>,
1761 aliaser: Box<VersionedLocation>,
1762 ) -> DispatchResult {
1763 let signed_origin = ensure_signed(origin.clone())?;
1764 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1765 let to_remove: Location = (*aliaser).try_into().map_err(|()| {
1766 tracing::debug!(
1767 target: "xcm::pallet_xcm::remove_authorized_alias",
1768 "Failed to convert aliaser VersionedLocation",
1769 );
1770 Error::<T>::BadVersion
1771 })?;
1772 ensure!(origin_location != to_remove, Error::<T>::BadLocation);
1773 let origin_location = match origin_location.unpack() {
1775 (0, [AccountId32 { network: _, id }]) =>
1776 Location::new(0, [AccountId32 { network: None, id: *id }]),
1777 _ => return Err(Error::<T>::InvalidOrigin.into()),
1778 };
1779 tracing::debug!(target: "xcm::pallet_xcm::remove_authorized_alias", ?origin_location, ?to_remove);
1780 ensure!(origin_location != to_remove, Error::<T>::BadLocation);
1781 let versioned_origin = VersionedLocation::from(origin_location.clone());
1783 let versioned_to_remove = VersionedLocation::from(to_remove.clone());
1784 AuthorizedAliases::<T>::get(&versioned_origin)
1785 .ok_or(Error::<T>::AliasNotFound.into())
1786 .and_then(|entry| {
1787 let (mut aliasers, mut ticket) = (entry.aliasers, entry.ticket);
1788 let old_len = aliasers.len();
1789 aliasers.retain(|alias| versioned_to_remove.ne(&alias.location));
1790 let new_len = aliasers.len();
1791 if aliasers.is_empty() {
1792 ticket.drop(&signed_origin)?;
1794 AuthorizedAliases::<T>::remove(&versioned_origin);
1795 Self::deposit_event(Event::AliasAuthorizationRemoved {
1796 aliaser: to_remove,
1797 target: origin_location,
1798 });
1799 Ok(())
1800 } else if old_len != new_len {
1801 ticket = ticket.update(&signed_origin, aliasers_footprint(new_len))?;
1803 let entry = AuthorizedAliasesEntry { aliasers, ticket };
1804 AuthorizedAliases::<T>::insert(&versioned_origin, entry);
1805 Self::deposit_event(Event::AliasAuthorizationRemoved {
1806 aliaser: to_remove,
1807 target: origin_location,
1808 });
1809 Ok(())
1810 } else {
1811 Err(Error::<T>::AliasNotFound.into())
1812 }
1813 })
1814 }
1815
1816 #[pallet::call_index(16)]
1819 #[pallet::weight(T::WeightInfo::remove_authorized_alias())]
1820 pub fn remove_all_authorized_aliases(origin: OriginFor<T>) -> DispatchResult {
1821 let signed_origin = ensure_signed(origin.clone())?;
1822 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1823 let origin_location = match origin_location.unpack() {
1825 (0, [AccountId32 { network: _, id }]) =>
1826 Location::new(0, [AccountId32 { network: None, id: *id }]),
1827 _ => return Err(Error::<T>::InvalidOrigin.into()),
1828 };
1829 tracing::debug!(target: "xcm::pallet_xcm::remove_all_authorized_aliases", ?origin_location);
1830 let versioned_origin = VersionedLocation::from(origin_location.clone());
1832 if let Some(entry) = AuthorizedAliases::<T>::get(&versioned_origin) {
1833 entry.ticket.drop(&signed_origin)?;
1835 AuthorizedAliases::<T>::remove(&versioned_origin);
1836 Self::deposit_event(Event::AliasesAuthorizationsRemoved {
1837 target: origin_location,
1838 });
1839 Ok(())
1840 } else {
1841 tracing::debug!(target: "xcm::pallet_xcm::remove_all_authorized_aliases", "No authorized alias entry found for the origin");
1842 Err(Error::<T>::AliasNotFound.into())
1843 }
1844 }
1845 }
1846}
1847
1848const MAX_ASSETS_FOR_TRANSFER: usize = 2;
1850
1851#[derive(Clone, PartialEq)]
1853enum FeesHandling<T: Config> {
1854 Batched { fees: Asset },
1856 Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
1858}
1859
1860impl<T: Config> core::fmt::Debug for FeesHandling<T> {
1861 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1862 match self {
1863 Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
1864 Self::Separate { local_xcm, remote_xcm } => write!(
1865 f,
1866 "FeesHandling::Separate(local: {:?}, remote: {:?})",
1867 local_xcm, remote_xcm
1868 ),
1869 }
1870 }
1871}
1872
1873impl<T: Config> QueryHandler for Pallet<T> {
1874 type BlockNumber = BlockNumberFor<T>;
1875 type Error = XcmError;
1876 type UniversalLocation = T::UniversalLocation;
1877
1878 fn new_query(
1880 responder: impl Into<Location>,
1881 timeout: BlockNumberFor<T>,
1882 match_querier: impl Into<Location>,
1883 ) -> QueryId {
1884 Self::do_new_query(responder, None, timeout, match_querier)
1885 }
1886
1887 fn report_outcome(
1890 message: &mut Xcm<()>,
1891 responder: impl Into<Location>,
1892 timeout: Self::BlockNumber,
1893 ) -> Result<QueryId, Self::Error> {
1894 let responder = responder.into();
1895 let destination =
1896 Self::UniversalLocation::get().invert_target(&responder).map_err(|()| {
1897 tracing::debug!(
1898 target: "xcm::pallet_xcm::report_outcome",
1899 "Failed to invert responder Location",
1900 );
1901 XcmError::LocationNotInvertible
1902 })?;
1903 let query_id = Self::new_query(responder, timeout, Here);
1904 let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
1905 let report_error = Xcm(vec![ReportError(response_info)]);
1906 message.0.insert(0, SetAppendix(report_error));
1907 Ok(query_id)
1908 }
1909
1910 fn take_response(query_id: QueryId) -> QueryResponseStatus<Self::BlockNumber> {
1912 match Queries::<T>::get(query_id) {
1913 Some(QueryStatus::Ready { response, at }) => match response.try_into() {
1914 Ok(response) => {
1915 Queries::<T>::remove(query_id);
1916 Self::deposit_event(Event::ResponseTaken { query_id });
1917 QueryResponseStatus::Ready { response, at }
1918 },
1919 Err(_) => {
1920 tracing::debug!(
1921 target: "xcm::pallet_xcm::take_response", ?query_id,
1922 "Failed to convert VersionedResponse to Response for query",
1923 );
1924 QueryResponseStatus::UnexpectedVersion
1925 },
1926 },
1927 Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
1928 Some(_) => {
1929 tracing::debug!(
1930 target: "xcm::pallet_xcm::take_response", ?query_id,
1931 "Unexpected QueryStatus variant for query",
1932 );
1933 QueryResponseStatus::UnexpectedVersion
1934 },
1935 None => {
1936 tracing::debug!(
1937 target: "xcm::pallet_xcm::take_response", ?query_id,
1938 "Query ID not found`",
1939 );
1940 QueryResponseStatus::NotFound
1941 },
1942 }
1943 }
1944
1945 #[cfg(feature = "runtime-benchmarks")]
1946 fn expect_response(id: QueryId, response: Response) {
1947 let response = response.into();
1948 Queries::<T>::insert(
1949 id,
1950 QueryStatus::Ready { response, at: frame_system::Pallet::<T>::current_block_number() },
1951 );
1952 }
1953}
1954
1955impl<T: Config> Pallet<T> {
1956 pub fn query(query_id: &QueryId) -> Option<QueryStatus<BlockNumberFor<T>>> {
1958 Queries::<T>::get(query_id)
1959 }
1960
1961 pub fn asset_trap(trap_id: &H256) -> u32 {
1967 AssetTraps::<T>::get(trap_id)
1968 }
1969
1970 fn find_fee_and_assets_transfer_types(
1975 assets: &[Asset],
1976 fee_asset_item: usize,
1977 dest: &Location,
1978 ) -> Result<(TransferType, TransferType), Error<T>> {
1979 let mut fees_transfer_type = None;
1980 let mut assets_transfer_type = None;
1981 for (idx, asset) in assets.iter().enumerate() {
1982 if let Fungible(x) = asset.fun {
1983 ensure!(!x.is_zero(), Error::<T>::Empty);
1985 }
1986 let transfer_type =
1987 T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
1988 if idx == fee_asset_item {
1989 fees_transfer_type = Some(transfer_type);
1990 } else {
1991 if let Some(existing) = assets_transfer_type.as_ref() {
1992 ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
1995 } else {
1996 assets_transfer_type = Some(transfer_type);
1998 }
1999 }
2000 }
2001 if assets.len() == 1 {
2003 assets_transfer_type = fees_transfer_type.clone()
2004 }
2005 Ok((
2006 fees_transfer_type.ok_or(Error::<T>::Empty)?,
2007 assets_transfer_type.ok_or(Error::<T>::Empty)?,
2008 ))
2009 }
2010
2011 fn do_reserve_transfer_assets(
2012 origin: OriginFor<T>,
2013 dest: Box<VersionedLocation>,
2014 beneficiary: Box<VersionedLocation>,
2015 assets: Box<VersionedAssets>,
2016 fee_asset_item: u32,
2017 weight_limit: WeightLimit,
2018 ) -> DispatchResult {
2019 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2020 let dest = (*dest).try_into().map_err(|()| {
2021 tracing::debug!(
2022 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2023 "Failed to convert destination VersionedLocation",
2024 );
2025 Error::<T>::BadVersion
2026 })?;
2027 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2028 tracing::debug!(
2029 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2030 "Failed to convert beneficiary VersionedLocation",
2031 );
2032 Error::<T>::BadVersion
2033 })?;
2034 let assets: Assets = (*assets).try_into().map_err(|()| {
2035 tracing::debug!(
2036 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2037 "Failed to convert VersionedAssets",
2038 );
2039 Error::<T>::BadVersion
2040 })?;
2041 tracing::debug!(
2042 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2043 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item,
2044 );
2045
2046 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2047 let value = (origin_location, assets.into_inner());
2048 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2049 let (origin, assets) = value;
2050
2051 let fee_asset_item = fee_asset_item as usize;
2052 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
2053
2054 let (fees_transfer_type, assets_transfer_type) =
2056 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
2057 ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
2059 ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
2061
2062 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2063 origin.clone(),
2064 dest.clone(),
2065 Either::Left(beneficiary),
2066 assets,
2067 assets_transfer_type,
2068 FeesHandling::Batched { fees },
2069 weight_limit,
2070 )?;
2071 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2072 }
2073
2074 fn do_teleport_assets(
2075 origin: OriginFor<T>,
2076 dest: Box<VersionedLocation>,
2077 beneficiary: Box<VersionedLocation>,
2078 assets: Box<VersionedAssets>,
2079 fee_asset_item: u32,
2080 weight_limit: WeightLimit,
2081 ) -> DispatchResult {
2082 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2083 let dest = (*dest).try_into().map_err(|()| {
2084 tracing::debug!(
2085 target: "xcm::pallet_xcm::do_teleport_assets",
2086 "Failed to convert destination VersionedLocation",
2087 );
2088 Error::<T>::BadVersion
2089 })?;
2090 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2091 tracing::debug!(
2092 target: "xcm::pallet_xcm::do_teleport_assets",
2093 "Failed to convert beneficiary VersionedLocation",
2094 );
2095 Error::<T>::BadVersion
2096 })?;
2097 let assets: Assets = (*assets).try_into().map_err(|()| {
2098 tracing::debug!(
2099 target: "xcm::pallet_xcm::do_teleport_assets",
2100 "Failed to convert VersionedAssets",
2101 );
2102 Error::<T>::BadVersion
2103 })?;
2104 tracing::debug!(
2105 target: "xcm::pallet_xcm::do_teleport_assets",
2106 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
2107 );
2108
2109 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2110 let value = (origin_location, assets.into_inner());
2111 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2112 let (origin_location, assets) = value;
2113 for asset in assets.iter() {
2114 let transfer_type =
2115 T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
2116 ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
2117 }
2118 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
2119
2120 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2121 origin_location.clone(),
2122 dest.clone(),
2123 Either::Left(beneficiary),
2124 assets,
2125 TransferType::Teleport,
2126 FeesHandling::Batched { fees },
2127 weight_limit,
2128 )?;
2129 Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
2130 }
2131
2132 fn do_transfer_assets(
2133 origin: Location,
2134 dest: Location,
2135 beneficiary: Either<Location, Xcm<()>>,
2136 mut assets: Vec<Asset>,
2137 assets_transfer_type: TransferType,
2138 fee_asset_index: usize,
2139 fees_transfer_type: TransferType,
2140 weight_limit: WeightLimit,
2141 ) -> DispatchResult {
2142 let fees = if fees_transfer_type == assets_transfer_type {
2144 let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
2145 FeesHandling::Batched { fees }
2147 } else {
2148 ensure!(
2154 !matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
2155 Error::<T>::InvalidAssetUnsupportedReserve
2156 );
2157 let weight_limit = weight_limit.clone();
2158 let fees = assets.remove(fee_asset_index);
2161 let (local_xcm, remote_xcm) = match fees_transfer_type {
2162 TransferType::LocalReserve => Self::local_reserve_fees_instructions(
2163 origin.clone(),
2164 dest.clone(),
2165 fees,
2166 weight_limit,
2167 )?,
2168 TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
2169 origin.clone(),
2170 dest.clone(),
2171 fees,
2172 weight_limit,
2173 )?,
2174 TransferType::Teleport => Self::teleport_fees_instructions(
2175 origin.clone(),
2176 dest.clone(),
2177 fees,
2178 weight_limit,
2179 )?,
2180 TransferType::RemoteReserve(_) =>
2181 return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2182 };
2183 FeesHandling::Separate { local_xcm, remote_xcm }
2184 };
2185
2186 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2187 origin.clone(),
2188 dest.clone(),
2189 beneficiary,
2190 assets,
2191 assets_transfer_type,
2192 fees,
2193 weight_limit,
2194 )?;
2195 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2196 }
2197
2198 fn build_xcm_transfer_type(
2199 origin: Location,
2200 dest: Location,
2201 beneficiary: Either<Location, Xcm<()>>,
2202 assets: Vec<Asset>,
2203 transfer_type: TransferType,
2204 fees: FeesHandling<T>,
2205 weight_limit: WeightLimit,
2206 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
2207 tracing::debug!(
2208 target: "xcm::pallet_xcm::build_xcm_transfer_type",
2209 ?origin, ?dest, ?beneficiary, ?assets, ?transfer_type, ?fees, ?weight_limit,
2210 );
2211 match transfer_type {
2212 TransferType::LocalReserve => Self::local_reserve_transfer_programs(
2213 origin.clone(),
2214 dest.clone(),
2215 beneficiary,
2216 assets,
2217 fees,
2218 weight_limit,
2219 )
2220 .map(|(local, remote)| (local, Some(remote))),
2221 TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
2222 origin.clone(),
2223 dest.clone(),
2224 beneficiary,
2225 assets,
2226 fees,
2227 weight_limit,
2228 )
2229 .map(|(local, remote)| (local, Some(remote))),
2230 TransferType::RemoteReserve(reserve) => {
2231 let fees = match fees {
2232 FeesHandling::Batched { fees } => fees,
2233 _ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2234 };
2235 Self::remote_reserve_transfer_program(
2236 origin.clone(),
2237 reserve.try_into().map_err(|()| {
2238 tracing::debug!(
2239 target: "xcm::pallet_xcm::build_xcm_transfer_type",
2240 "Failed to convert remote reserve location",
2241 );
2242 Error::<T>::BadVersion
2243 })?,
2244 beneficiary,
2245 dest.clone(),
2246 assets,
2247 fees,
2248 weight_limit,
2249 )
2250 .map(|local| (local, None))
2251 },
2252 TransferType::Teleport => Self::teleport_assets_program(
2253 origin.clone(),
2254 dest.clone(),
2255 beneficiary,
2256 assets,
2257 fees,
2258 weight_limit,
2259 )
2260 .map(|(local, remote)| (local, Some(remote))),
2261 }
2262 }
2263
2264 fn execute_xcm_transfer(
2265 origin: Location,
2266 dest: Location,
2267 mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
2268 remote_xcm: Option<Xcm<()>>,
2269 ) -> DispatchResult {
2270 tracing::debug!(
2271 target: "xcm::pallet_xcm::execute_xcm_transfer",
2272 ?origin, ?dest, ?local_xcm, ?remote_xcm,
2273 );
2274
2275 let weight =
2276 T::Weigher::weight(&mut local_xcm, Weight::MAX).map_err(|error| {
2277 tracing::debug!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, "Failed to calculate weight");
2278 Error::<T>::UnweighableMessage
2279 })?;
2280 let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
2281 let outcome = T::XcmExecutor::prepare_and_execute(
2282 origin.clone(),
2283 local_xcm,
2284 &mut hash,
2285 weight,
2286 weight,
2287 );
2288 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
2289 outcome.clone().ensure_complete().map_err(|error| {
2290 tracing::error!(
2291 target: "xcm::pallet_xcm::execute_xcm_transfer",
2292 ?error, "XCM execution failed with error with outcome: {:?}", outcome
2293 );
2294 Error::<T>::LocalExecutionIncompleteWithError {
2295 index: error.index,
2296 error: error.error.into(),
2297 }
2298 })?;
2299
2300 if let Some(remote_xcm) = remote_xcm {
2301 let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
2302 .map_err(|error| {
2303 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error");
2304 Error::<T>::from(error)
2305 })?;
2306 if origin != Here.into_location() {
2307 Self::charge_fees(origin.clone(), price.clone()).map_err(|error| {
2308 tracing::error!(
2309 target: "xcm::pallet_xcm::execute_xcm_transfer",
2310 ?error, ?price, ?origin, "Unable to charge fee",
2311 );
2312 Error::<T>::FeesNotMet
2313 })?;
2314 }
2315 let message_id = T::XcmRouter::deliver(ticket)
2316 .map_err(|error| {
2317 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error");
2318 Error::<T>::from(error)
2319 })?;
2320
2321 let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
2322 Self::deposit_event(e);
2323 }
2324 Ok(())
2325 }
2326
2327 fn add_fees_to_xcm(
2328 dest: Location,
2329 fees: FeesHandling<T>,
2330 weight_limit: WeightLimit,
2331 local: &mut Xcm<<T as Config>::RuntimeCall>,
2332 remote: &mut Xcm<()>,
2333 ) -> Result<(), Error<T>> {
2334 match fees {
2335 FeesHandling::Batched { fees } => {
2336 let context = T::UniversalLocation::get();
2337 let reanchored_fees =
2340 fees.reanchored(&dest, &context).map_err(|e| {
2341 tracing::error!(target: "xcm::pallet_xcm::add_fees_to_xcm", ?e, ?dest, ?context, "Failed to re-anchor fees");
2342 Error::<T>::CannotReanchor
2343 })?;
2344 remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
2346 },
2347 FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
2348 core::mem::swap(local, &mut local_fees);
2351 core::mem::swap(remote, &mut remote_fees);
2352 local.inner_mut().append(&mut local_fees.into_inner());
2354 remote.inner_mut().append(&mut remote_fees.into_inner());
2355 },
2356 }
2357 Ok(())
2358 }
2359
2360 fn local_reserve_fees_instructions(
2361 origin: Location,
2362 dest: Location,
2363 fees: Asset,
2364 weight_limit: WeightLimit,
2365 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2366 let value = (origin, vec![fees.clone()]);
2367 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2368
2369 let context = T::UniversalLocation::get();
2370 let reanchored_fees = fees.clone().reanchored(&dest, &context).map_err(|_| {
2371 tracing::debug!(
2372 target: "xcm::pallet_xcm::local_reserve_fees_instructions",
2373 "Failed to re-anchor fees",
2374 );
2375 Error::<T>::CannotReanchor
2376 })?;
2377
2378 let local_execute_xcm = Xcm(vec![
2379 TransferAsset { assets: fees.into(), beneficiary: dest },
2381 ]);
2382 let xcm_on_dest = Xcm(vec![
2383 ReserveAssetDeposited(reanchored_fees.clone().into()),
2385 BuyExecution { fees: reanchored_fees, weight_limit },
2387 ]);
2388 Ok((local_execute_xcm, xcm_on_dest))
2389 }
2390
2391 fn local_reserve_transfer_programs(
2392 origin: Location,
2393 dest: Location,
2394 beneficiary: Either<Location, Xcm<()>>,
2395 assets: Vec<Asset>,
2396 fees: FeesHandling<T>,
2397 weight_limit: WeightLimit,
2398 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2399 let value = (origin, assets);
2400 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2401 let (_, assets) = value;
2402
2403 let max_assets =
2405 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2406 let assets: Assets = assets.into();
2407 let context = T::UniversalLocation::get();
2408 let mut reanchored_assets = assets.clone();
2409 reanchored_assets
2410 .reanchor(&dest, &context)
2411 .map_err(|e| {
2412 tracing::error!(target: "xcm::pallet_xcm::local_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2413 Error::<T>::CannotReanchor
2414 })?;
2415
2416 let mut local_execute_xcm = Xcm(vec![
2418 TransferAsset { assets, beneficiary: dest.clone() },
2420 ]);
2421 let mut xcm_on_dest = Xcm(vec![
2423 ReserveAssetDeposited(reanchored_assets),
2425 ClearOrigin,
2427 ]);
2428 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2430
2431 let custom_remote_xcm = match beneficiary {
2433 Either::Right(custom_xcm) => custom_xcm,
2434 Either::Left(beneficiary) => {
2435 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2437 },
2438 };
2439 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2440
2441 Ok((local_execute_xcm, xcm_on_dest))
2442 }
2443
2444 fn destination_reserve_fees_instructions(
2445 origin: Location,
2446 dest: Location,
2447 fees: Asset,
2448 weight_limit: WeightLimit,
2449 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2450 let value = (origin, vec![fees.clone()]);
2451 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2452 ensure!(
2453 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&fees, &dest),
2454 Error::<T>::InvalidAssetUnsupportedReserve
2455 );
2456
2457 let context = T::UniversalLocation::get();
2458 let reanchored_fees = fees
2459 .clone()
2460 .reanchored(&dest, &context)
2461 .map_err(|e| {
2462 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_fees_instructions", ?e, ?dest,?context, "Failed to re-anchor fees");
2463 Error::<T>::CannotReanchor
2464 })?;
2465 let fees: Assets = fees.into();
2466
2467 let local_execute_xcm = Xcm(vec![
2468 WithdrawAsset(fees.clone()),
2470 BurnAsset(fees),
2472 ]);
2473 let xcm_on_dest = Xcm(vec![
2474 WithdrawAsset(reanchored_fees.clone().into()),
2476 BuyExecution { fees: reanchored_fees, weight_limit },
2478 ]);
2479 Ok((local_execute_xcm, xcm_on_dest))
2480 }
2481
2482 fn destination_reserve_transfer_programs(
2483 origin: Location,
2484 dest: Location,
2485 beneficiary: Either<Location, Xcm<()>>,
2486 assets: Vec<Asset>,
2487 fees: FeesHandling<T>,
2488 weight_limit: WeightLimit,
2489 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2490 let value = (origin, assets);
2491 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2492 let (_, assets) = value;
2493 for asset in assets.iter() {
2494 ensure!(
2495 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&asset, &dest),
2496 Error::<T>::InvalidAssetUnsupportedReserve
2497 );
2498 }
2499
2500 let max_assets =
2502 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2503 let assets: Assets = assets.into();
2504 let context = T::UniversalLocation::get();
2505 let mut reanchored_assets = assets.clone();
2506 reanchored_assets
2507 .reanchor(&dest, &context)
2508 .map_err(|e| {
2509 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2510 Error::<T>::CannotReanchor
2511 })?;
2512
2513 let mut local_execute_xcm = Xcm(vec![
2515 WithdrawAsset(assets.clone()),
2517 BurnAsset(assets),
2519 ]);
2520 let mut xcm_on_dest = Xcm(vec![
2522 WithdrawAsset(reanchored_assets),
2524 ClearOrigin,
2526 ]);
2527 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2529
2530 let custom_remote_xcm = match beneficiary {
2532 Either::Right(custom_xcm) => custom_xcm,
2533 Either::Left(beneficiary) => {
2534 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2536 },
2537 };
2538 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2539
2540 Ok((local_execute_xcm, xcm_on_dest))
2541 }
2542
2543 fn remote_reserve_transfer_program(
2545 origin: Location,
2546 reserve: Location,
2547 beneficiary: Either<Location, Xcm<()>>,
2548 dest: Location,
2549 assets: Vec<Asset>,
2550 fees: Asset,
2551 weight_limit: WeightLimit,
2552 ) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
2553 let value = (origin, assets);
2554 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2555 let (_, assets) = value;
2556
2557 let max_assets = assets.len() as u32;
2558 let context = T::UniversalLocation::get();
2559 let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
2562 let reserve_fees = fees_half_1
2564 .reanchored(&reserve, &context)
2565 .map_err(|e| {
2566 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor reserve_fees");
2567 Error::<T>::CannotReanchor
2568 })?;
2569 let dest_fees = fees_half_2
2571 .reanchored(&dest, &context)
2572 .map_err(|e| {
2573 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?dest, ?context, "Failed to re-anchor dest_fees");
2574 Error::<T>::CannotReanchor
2575 })?;
2576 let dest = dest.reanchored(&reserve, &context).map_err(|e| {
2578 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor dest");
2579 Error::<T>::CannotReanchor
2580 })?;
2581 let mut xcm_on_dest =
2583 Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
2584 let custom_xcm_on_dest = match beneficiary {
2586 Either::Right(custom_xcm) => custom_xcm,
2587 Either::Left(beneficiary) => {
2588 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2590 },
2591 };
2592 xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
2593 let xcm_on_reserve = Xcm(vec![
2595 BuyExecution { fees: reserve_fees, weight_limit },
2596 DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
2597 ]);
2598 Ok(Xcm(vec![
2599 WithdrawAsset(assets.into()),
2600 SetFeesMode { jit_withdraw: true },
2601 InitiateReserveWithdraw {
2602 assets: Wild(AllCounted(max_assets)),
2603 reserve,
2604 xcm: xcm_on_reserve,
2605 },
2606 ]))
2607 }
2608
2609 fn teleport_fees_instructions(
2610 origin: Location,
2611 dest: Location,
2612 fees: Asset,
2613 weight_limit: WeightLimit,
2614 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2615 let value = (origin, vec![fees.clone()]);
2616 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2617 ensure!(
2618 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&fees, &dest),
2619 Error::<T>::Filtered
2620 );
2621
2622 let context = T::UniversalLocation::get();
2623 let reanchored_fees = fees
2624 .clone()
2625 .reanchored(&dest, &context)
2626 .map_err(|e| {
2627 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?dest, ?context, "Failed to re-anchor fees");
2628 Error::<T>::CannotReanchor
2629 })?;
2630
2631 let dummy_context =
2633 XcmContext { origin: None, message_id: Default::default(), topic: None };
2634 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2639 &dest,
2640 &fees,
2641 &dummy_context,
2642 )
2643 .map_err(|e| {
2644 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?fees, ?dest, "Failed can_check_out");
2645 Error::<T>::CannotCheckOutTeleport
2646 })?;
2647 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2650 &dest,
2651 &fees,
2652 &dummy_context,
2653 );
2654
2655 let fees: Assets = fees.into();
2656 let local_execute_xcm = Xcm(vec![
2657 WithdrawAsset(fees.clone()),
2659 BurnAsset(fees),
2661 ]);
2662 let xcm_on_dest = Xcm(vec![
2663 ReceiveTeleportedAsset(reanchored_fees.clone().into()),
2665 BuyExecution { fees: reanchored_fees, weight_limit },
2667 ]);
2668 Ok((local_execute_xcm, xcm_on_dest))
2669 }
2670
2671 fn teleport_assets_program(
2672 origin: Location,
2673 dest: Location,
2674 beneficiary: Either<Location, Xcm<()>>,
2675 assets: Vec<Asset>,
2676 fees: FeesHandling<T>,
2677 weight_limit: WeightLimit,
2678 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2679 let value = (origin, assets);
2680 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2681 let (_, assets) = value;
2682 for asset in assets.iter() {
2683 ensure!(
2684 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&asset, &dest),
2685 Error::<T>::Filtered
2686 );
2687 }
2688
2689 let max_assets =
2691 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2692 let context = T::UniversalLocation::get();
2693 let assets: Assets = assets.into();
2694 let mut reanchored_assets = assets.clone();
2695 reanchored_assets
2696 .reanchor(&dest, &context)
2697 .map_err(|e| {
2698 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?dest, ?context, "Failed to re-anchor asset");
2699 Error::<T>::CannotReanchor
2700 })?;
2701
2702 let dummy_context =
2704 XcmContext { origin: None, message_id: Default::default(), topic: None };
2705 for asset in assets.inner() {
2706 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2711 &dest,
2712 asset,
2713 &dummy_context,
2714 )
2715 .map_err(|e| {
2716 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?asset, ?dest, "Failed can_check_out asset");
2717 Error::<T>::CannotCheckOutTeleport
2718 })?;
2719 }
2720 for asset in assets.inner() {
2721 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2724 &dest,
2725 asset,
2726 &dummy_context,
2727 );
2728 }
2729
2730 let mut local_execute_xcm = Xcm(vec![
2732 WithdrawAsset(assets.clone()),
2734 BurnAsset(assets),
2736 ]);
2737 let mut xcm_on_dest = Xcm(vec![
2739 ReceiveTeleportedAsset(reanchored_assets),
2741 ClearOrigin,
2743 ]);
2744 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2746
2747 let custom_remote_xcm = match beneficiary {
2749 Either::Right(custom_xcm) => custom_xcm,
2750 Either::Left(beneficiary) => {
2751 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2753 },
2754 };
2755 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2756
2757 Ok((local_execute_xcm, xcm_on_dest))
2758 }
2759
2760 pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
2762 match fees.fun {
2763 Fungible(amount) => {
2764 let fee1 = amount.saturating_div(2);
2765 let fee2 = amount.saturating_sub(fee1);
2766 ensure!(fee1 > 0, Error::<T>::FeesNotMet);
2767 ensure!(fee2 > 0, Error::<T>::FeesNotMet);
2768 Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
2769 },
2770 NonFungible(_) => Err(Error::<T>::FeesNotMet),
2771 }
2772 }
2773
2774 pub(crate) fn lazy_migration(
2777 mut stage: VersionMigrationStage,
2778 weight_cutoff: Weight,
2779 ) -> (Weight, Option<VersionMigrationStage>) {
2780 let mut weight_used = Weight::zero();
2781
2782 let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
2783 let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
2784 let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
2785 let vnt_notify_weight = T::WeightInfo::notify_current_targets();
2786 let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
2787 let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
2788 let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
2789
2790 use VersionMigrationStage::*;
2791
2792 if stage == MigrateSupportedVersion {
2793 for v in 0..XCM_VERSION {
2796 for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
2797 if let Ok(new_key) = old_key.into_latest() {
2798 SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
2799 }
2800 weight_used.saturating_accrue(sv_migrate_weight);
2801 if weight_used.any_gte(weight_cutoff) {
2802 return (weight_used, Some(stage))
2803 }
2804 }
2805 }
2806 stage = MigrateVersionNotifiers;
2807 }
2808 if stage == MigrateVersionNotifiers {
2809 for v in 0..XCM_VERSION {
2810 for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
2811 if let Ok(new_key) = old_key.into_latest() {
2812 VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
2813 }
2814 weight_used.saturating_accrue(vn_migrate_weight);
2815 if weight_used.any_gte(weight_cutoff) {
2816 return (weight_used, Some(stage))
2817 }
2818 }
2819 }
2820 stage = NotifyCurrentTargets(None);
2821 }
2822
2823 let xcm_version = T::AdvertisedXcmVersion::get();
2824
2825 if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
2826 let mut iter = match maybe_last_raw_key {
2827 Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
2828 None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
2829 };
2830 while let Some((key, value)) = iter.next() {
2831 let (query_id, max_weight, target_xcm_version) = value;
2832 let new_key: Location = match key.clone().try_into() {
2833 Ok(k) if target_xcm_version != xcm_version => k,
2834 _ => {
2835 weight_used.saturating_accrue(vnt_already_notified_weight);
2838 continue
2839 },
2840 };
2841 let response = Response::Version(xcm_version);
2842 let message =
2843 Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
2844 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2845 Ok((message_id, cost)) => {
2846 let value = (query_id, max_weight, xcm_version);
2847 VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
2848 Event::VersionChangeNotified {
2849 destination: new_key,
2850 result: xcm_version,
2851 cost,
2852 message_id,
2853 }
2854 },
2855 Err(e) => {
2856 VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
2857 Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
2858 },
2859 };
2860 Self::deposit_event(event);
2861 weight_used.saturating_accrue(vnt_notify_weight);
2862 if weight_used.any_gte(weight_cutoff) {
2863 let last = Some(iter.last_raw_key().into());
2864 return (weight_used, Some(NotifyCurrentTargets(last)))
2865 }
2866 }
2867 stage = MigrateAndNotifyOldTargets;
2868 }
2869 if stage == MigrateAndNotifyOldTargets {
2870 for v in 0..XCM_VERSION {
2871 for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
2872 let (query_id, max_weight, target_xcm_version) = value;
2873 let new_key = match Location::try_from(old_key.clone()) {
2874 Ok(k) => k,
2875 Err(()) => {
2876 Self::deposit_event(Event::NotifyTargetMigrationFail {
2877 location: old_key,
2878 query_id: value.0,
2879 });
2880 weight_used.saturating_accrue(vnt_migrate_fail_weight);
2881 if weight_used.any_gte(weight_cutoff) {
2882 return (weight_used, Some(stage))
2883 }
2884 continue
2885 },
2886 };
2887
2888 let versioned_key = LatestVersionedLocation(&new_key);
2889 if target_xcm_version == xcm_version {
2890 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
2891 weight_used.saturating_accrue(vnt_migrate_weight);
2892 } else {
2893 let response = Response::Version(xcm_version);
2895 let message = Xcm(vec![QueryResponse {
2896 query_id,
2897 response,
2898 max_weight,
2899 querier: None,
2900 }]);
2901 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2902 Ok((message_id, cost)) => {
2903 VersionNotifyTargets::<T>::insert(
2904 XCM_VERSION,
2905 versioned_key,
2906 (query_id, max_weight, xcm_version),
2907 );
2908 Event::VersionChangeNotified {
2909 destination: new_key,
2910 result: xcm_version,
2911 cost,
2912 message_id,
2913 }
2914 },
2915 Err(e) => Event::NotifyTargetSendFail {
2916 location: new_key,
2917 query_id,
2918 error: e.into(),
2919 },
2920 };
2921 Self::deposit_event(event);
2922 weight_used.saturating_accrue(vnt_notify_migrate_weight);
2923 }
2924 if weight_used.any_gte(weight_cutoff) {
2925 return (weight_used, Some(stage))
2926 }
2927 }
2928 }
2929 }
2930 (weight_used, None)
2931 }
2932
2933 pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
2935 let dest = dest.into();
2936 let versioned_dest = VersionedLocation::from(dest.clone());
2937 let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
2938 ensure!(!already, XcmError::InvalidLocation);
2939 let query_id = QueryCounter::<T>::mutate(|q| {
2940 let r = *q;
2941 q.saturating_inc();
2942 r
2943 });
2944 let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
2946 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
2947 Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
2948 VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
2949 let query_status =
2950 QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
2951 Queries::<T>::insert(query_id, query_status);
2952 Ok(())
2953 }
2954
2955 pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
2957 let dest = dest.into();
2958 let versioned_dest = LatestVersionedLocation(&dest);
2959 let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
2960 .ok_or(XcmError::InvalidLocation)?;
2961 let (message_id, cost) =
2962 send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
2963 Self::deposit_event(Event::VersionNotifyUnrequested {
2964 destination: dest,
2965 cost,
2966 message_id,
2967 });
2968 Queries::<T>::remove(query_id);
2969 Ok(())
2970 }
2971
2972 pub fn send_xcm(
2976 interior: impl Into<Junctions>,
2977 dest: impl Into<Location>,
2978 mut message: Xcm<()>,
2979 ) -> Result<XcmHash, SendError> {
2980 let interior = interior.into();
2981 let local_origin = interior.clone().into();
2982 let dest = dest.into();
2983 let is_waived =
2984 <T::XcmExecutor as FeeManager>::is_waived(Some(&local_origin), FeeReason::ChargeFees);
2985 if interior != Junctions::Here {
2986 message.0.insert(0, DescendOrigin(interior.clone()));
2987 }
2988 tracing::debug!(target: "xcm::send_xcm", "{:?}, {:?}", dest.clone(), message.clone());
2989 let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
2990 if !is_waived {
2991 Self::charge_fees(local_origin, price).map_err(|e| {
2992 tracing::error!(
2993 target: "xcm::pallet_xcm::send_xcm",
2994 ?e,
2995 "Charging fees failed with error",
2996 );
2997 SendError::Fees
2998 })?;
2999 }
3000 T::XcmRouter::deliver(ticket)
3001 }
3002
3003 pub fn check_account() -> T::AccountId {
3004 const ID: PalletId = PalletId(*b"py/xcmch");
3005 AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
3006 }
3007
3008 pub fn dry_run_call<Runtime, Router, OriginCaller, RuntimeCall>(
3014 origin: OriginCaller,
3015 call: RuntimeCall,
3016 result_xcms_version: XcmVersion,
3017 ) -> Result<CallDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3018 where
3019 Runtime: crate::Config,
3020 Router: InspectMessageQueues,
3021 RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
3022 <RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
3023 {
3024 crate::Pallet::<Runtime>::set_record_xcm(true);
3025 Router::clear_messages();
3027 frame_system::Pallet::<Runtime>::reset_events();
3029 let result = call.dispatch(origin.into());
3030 crate::Pallet::<Runtime>::set_record_xcm(false);
3031 let local_xcm = crate::Pallet::<Runtime>::recorded_xcm()
3032 .map(|xcm| VersionedXcm::<()>::from(xcm).into_version(result_xcms_version))
3033 .transpose()
3034 .map_err(|()| {
3035 tracing::error!(
3036 target: "xcm::DryRunApi::dry_run_call",
3037 "Local xcm version conversion failed"
3038 );
3039
3040 XcmDryRunApiError::VersionedConversionFailed
3041 })?;
3042
3043 let forwarded_xcms =
3045 Self::convert_forwarded_xcms(result_xcms_version, Router::get_messages()).inspect_err(
3046 |error| {
3047 tracing::error!(
3048 target: "xcm::DryRunApi::dry_run_call",
3049 ?error, "Forwarded xcms version conversion failed with error"
3050 );
3051 },
3052 )?;
3053 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
3054 frame_system::Pallet::<Runtime>::read_events_no_consensus()
3055 .map(|record| record.event.clone())
3056 .collect();
3057 Ok(CallDryRunEffects {
3058 local_xcm: local_xcm.map(VersionedXcm::<()>::from),
3059 forwarded_xcms,
3060 emitted_events: events,
3061 execution_result: result,
3062 })
3063 }
3064
3065 pub fn dry_run_xcm<Runtime, Router, RuntimeCall: Decode + GetDispatchInfo, XcmConfig>(
3070 origin_location: VersionedLocation,
3071 xcm: VersionedXcm<RuntimeCall>,
3072 ) -> Result<XcmDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3073 where
3074 Runtime: frame_system::Config,
3075 Router: InspectMessageQueues,
3076 XcmConfig: xcm_executor::Config<RuntimeCall = RuntimeCall>,
3077 {
3078 let origin_location: Location = origin_location.try_into().map_err(|error| {
3079 tracing::error!(
3080 target: "xcm::DryRunApi::dry_run_xcm",
3081 ?error, "Location version conversion failed with error"
3082 );
3083 XcmDryRunApiError::VersionedConversionFailed
3084 })?;
3085 let xcm_version = xcm.identify_version();
3086 let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| {
3087 tracing::error!(
3088 target: "xcm::DryRunApi::dry_run_xcm",
3089 ?error, "Xcm version conversion failed with error"
3090 );
3091 XcmDryRunApiError::VersionedConversionFailed
3092 })?;
3093 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
3094
3095 Router::clear_messages();
3097 frame_system::Pallet::<Runtime>::reset_events();
3098
3099 let result = xcm_executor::XcmExecutor::<XcmConfig>::prepare_and_execute(
3100 origin_location,
3101 xcm,
3102 &mut hash,
3103 Weight::MAX, Weight::zero(),
3105 );
3106 let forwarded_xcms = Self::convert_forwarded_xcms(xcm_version, Router::get_messages())
3107 .inspect_err(|error| {
3108 tracing::error!(
3109 target: "xcm::DryRunApi::dry_run_xcm",
3110 ?error, "Forwarded xcms version conversion failed with error"
3111 );
3112 })?;
3113 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
3114 frame_system::Pallet::<Runtime>::read_events_no_consensus()
3115 .map(|record| record.event.clone())
3116 .collect();
3117 Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
3118 }
3119
3120 fn convert_xcms(
3121 xcm_version: XcmVersion,
3122 xcms: Vec<VersionedXcm<()>>,
3123 ) -> Result<Vec<VersionedXcm<()>>, ()> {
3124 xcms.into_iter()
3125 .map(|xcm| xcm.into_version(xcm_version))
3126 .collect::<Result<Vec<_>, ()>>()
3127 }
3128
3129 fn convert_forwarded_xcms(
3130 xcm_version: XcmVersion,
3131 forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>,
3132 ) -> Result<Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, XcmDryRunApiError> {
3133 forwarded_xcms
3134 .into_iter()
3135 .map(|(dest, forwarded_xcms)| {
3136 let dest = dest.into_version(xcm_version)?;
3137 let forwarded_xcms = Self::convert_xcms(xcm_version, forwarded_xcms)?;
3138
3139 Ok((dest, forwarded_xcms))
3140 })
3141 .collect::<Result<Vec<_>, ()>>()
3142 .map_err(|()| {
3143 tracing::debug!(
3144 target: "xcm::pallet_xcm::convert_forwarded_xcms",
3145 "Failed to convert VersionedLocation to requested version",
3146 );
3147 XcmDryRunApiError::VersionedConversionFailed
3148 })
3149 }
3150
3151 pub fn query_acceptable_payment_assets(
3156 version: xcm::Version,
3157 asset_ids: Vec<AssetId>,
3158 ) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
3159 Ok(asset_ids
3160 .into_iter()
3161 .map(|asset_id| VersionedAssetId::from(asset_id))
3162 .filter_map(|asset_id| asset_id.into_version(version).ok())
3163 .collect())
3164 }
3165
3166 pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
3167 let message = Xcm::<()>::try_from(message.clone())
3168 .map_err(|e| {
3169 tracing::debug!(target: "xcm::pallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message");
3170 XcmPaymentApiError::VersionedConversionFailed
3171 })?;
3172
3173 T::Weigher::weight(&mut message.clone().into(), Weight::MAX).map_err(|error| {
3174 tracing::debug!(target: "xcm::pallet_xcm::query_xcm_weight", ?error, ?message, "Error when querying XCM weight");
3175 XcmPaymentApiError::WeightNotComputable
3176 })
3177 }
3178
3179 pub fn query_weight_to_asset_fee<Trader: xcm_executor::traits::WeightTrader>(
3196 weight: Weight,
3197 asset: VersionedAssetId,
3198 ) -> Result<u128, XcmPaymentApiError> {
3199 let asset: AssetId = asset.clone().try_into()
3200 .map_err(|e| {
3201 tracing::debug!(target: "xcm::pallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to convert versioned asset");
3202 XcmPaymentApiError::VersionedConversionFailed
3203 })?;
3204
3205 let max_amount = u128::MAX / 2;
3206 let max_payment: Asset = (asset.clone(), max_amount).into();
3207 let context = XcmContext::with_message_id(XcmHash::default());
3208
3209 let unspent = with_transaction(|| {
3212 let mut trader = Trader::new();
3213 let result = trader.buy_weight(weight, max_payment.into(), &context)
3214 .map_err(|e| {
3215 tracing::error!(target: "xcm::pallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to buy weight");
3216
3217 DispatchError::Other("Failed to buy weight")
3219 });
3220
3221 TransactionOutcome::Rollback(result)
3222 }).map_err(|error| {
3223 tracing::debug!(target: "xcm::pallet::query_weight_to_asset_fee", ?error, "Failed to execute transaction");
3224 XcmPaymentApiError::AssetNotFound
3225 })?;
3226
3227 let Some(unspent) = unspent.fungible.get(&asset) else {
3228 tracing::error!(target: "xcm::pallet::query_weight_to_asset_fee", ?asset, "The trader didn't return the needed fungible asset");
3229 return Err(XcmPaymentApiError::AssetNotFound);
3230 };
3231
3232 let paid = max_amount - unspent;
3233 Ok(paid)
3234 }
3235
3236 pub fn query_delivery_fees(
3238 destination: VersionedLocation,
3239 message: VersionedXcm<()>,
3240 ) -> Result<VersionedAssets, XcmPaymentApiError> {
3241 let result_version = destination.identify_version().max(message.identify_version());
3242
3243 let destination: Location = destination
3244 .clone()
3245 .try_into()
3246 .map_err(|e| {
3247 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination");
3248 XcmPaymentApiError::VersionedConversionFailed
3249 })?;
3250
3251 let message: Xcm<()> =
3252 message.clone().try_into().map_err(|e| {
3253 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message");
3254 XcmPaymentApiError::VersionedConversionFailed
3255 })?;
3256
3257 let (_, fees) = validate_send::<T::XcmRouter>(destination.clone(), message.clone()).map_err(|error| {
3258 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination");
3259 XcmPaymentApiError::Unroutable
3260 })?;
3261
3262 VersionedAssets::from(fees)
3263 .into_version(result_version)
3264 .map_err(|e| {
3265 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?result_version, "Failed to convert fees into version");
3266 XcmPaymentApiError::VersionedConversionFailed
3267 })
3268 }
3269
3270 pub fn is_trusted_reserve(
3273 asset: VersionedAsset,
3274 location: VersionedLocation,
3275 ) -> Result<bool, TrustedQueryApiError> {
3276 let location: Location = location.try_into().map_err(|e| {
3277 tracing::debug!(
3278 target: "xcm::pallet_xcm::is_trusted_reserve",
3279 ?e, "Failed to convert versioned location",
3280 );
3281 TrustedQueryApiError::VersionedLocationConversionFailed
3282 })?;
3283
3284 let a: Asset = asset.try_into().map_err(|e| {
3285 tracing::debug!(
3286 target: "xcm::pallet_xcm::is_trusted_reserve",
3287 ?e, "Failed to convert versioned asset",
3288 );
3289 TrustedQueryApiError::VersionedAssetConversionFailed
3290 })?;
3291
3292 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&a, &location))
3293 }
3294
3295 pub fn is_trusted_teleporter(
3297 asset: VersionedAsset,
3298 location: VersionedLocation,
3299 ) -> Result<bool, TrustedQueryApiError> {
3300 let location: Location = location.try_into().map_err(|e| {
3301 tracing::debug!(
3302 target: "xcm::pallet_xcm::is_trusted_teleporter",
3303 ?e, "Failed to convert versioned location",
3304 );
3305 TrustedQueryApiError::VersionedLocationConversionFailed
3306 })?;
3307 let a: Asset = asset.try_into().map_err(|e| {
3308 tracing::debug!(
3309 target: "xcm::pallet_xcm::is_trusted_teleporter",
3310 ?e, "Failed to convert versioned asset",
3311 );
3312 TrustedQueryApiError::VersionedAssetConversionFailed
3313 })?;
3314 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&a, &location))
3315 }
3316
3317 pub fn authorized_aliasers(
3319 target: VersionedLocation,
3320 ) -> Result<Vec<OriginAliaser>, AuthorizedAliasersApiError> {
3321 let desired_version = target.identify_version();
3322 let target: VersionedLocation = target.into_version(XCM_VERSION).map_err(|e| {
3324 tracing::debug!(
3325 target: "xcm::pallet_xcm::authorized_aliasers",
3326 ?e, "Failed to convert versioned location",
3327 );
3328 AuthorizedAliasersApiError::LocationVersionConversionFailed
3329 })?;
3330 Ok(AuthorizedAliases::<T>::get(&target)
3331 .map(|authorized| {
3332 authorized
3333 .aliasers
3334 .into_iter()
3335 .filter_map(|aliaser| {
3336 let OriginAliaser { location, expiry } = aliaser;
3337 location
3338 .into_version(desired_version)
3339 .map(|location| OriginAliaser { location, expiry })
3340 .ok()
3341 })
3342 .collect()
3343 })
3344 .unwrap_or_default())
3345 }
3346
3347 pub fn is_authorized_alias(
3352 origin: VersionedLocation,
3353 target: VersionedLocation,
3354 ) -> Result<bool, AuthorizedAliasersApiError> {
3355 let desired_version = target.identify_version();
3356 let origin = origin.into_version(desired_version).map_err(|e| {
3357 tracing::debug!(
3358 target: "xcm::pallet_xcm::is_authorized_alias",
3359 ?e, "mismatching origin and target versions",
3360 );
3361 AuthorizedAliasersApiError::LocationVersionConversionFailed
3362 })?;
3363 Ok(Self::authorized_aliasers(target)?.into_iter().any(|aliaser| {
3364 aliaser.location == origin &&
3367 aliaser
3368 .expiry
3369 .map(|expiry| {
3370 frame_system::Pallet::<T>::current_block_number().saturated_into::<u64>() <
3371 expiry
3372 })
3373 .unwrap_or(true)
3374 }))
3375 }
3376
3377 fn do_new_query(
3379 responder: impl Into<Location>,
3380 maybe_notify: Option<(u8, u8)>,
3381 timeout: BlockNumberFor<T>,
3382 match_querier: impl Into<Location>,
3383 ) -> u64 {
3384 QueryCounter::<T>::mutate(|q| {
3385 let r = *q;
3386 q.saturating_inc();
3387 Queries::<T>::insert(
3388 r,
3389 QueryStatus::Pending {
3390 responder: responder.into().into(),
3391 maybe_match_querier: Some(match_querier.into().into()),
3392 maybe_notify,
3393 timeout,
3394 },
3395 );
3396 r
3397 })
3398 }
3399
3400 pub fn report_outcome_notify(
3423 message: &mut Xcm<()>,
3424 responder: impl Into<Location>,
3425 notify: impl Into<<T as Config>::RuntimeCall>,
3426 timeout: BlockNumberFor<T>,
3427 ) -> Result<(), XcmError> {
3428 let responder = responder.into();
3429 let destination = T::UniversalLocation::get().invert_target(&responder).map_err(|()| {
3430 tracing::debug!(
3431 target: "xcm::pallet_xcm::report_outcome_notify",
3432 "Failed to invert responder location to universal location",
3433 );
3434 XcmError::LocationNotInvertible
3435 })?;
3436 let notify: <T as Config>::RuntimeCall = notify.into();
3437 let max_weight = notify.get_dispatch_info().call_weight;
3438 let query_id = Self::new_notify_query(responder, notify, timeout, Here);
3439 let response_info = QueryResponseInfo { destination, query_id, max_weight };
3440 let report_error = Xcm(vec![ReportError(response_info)]);
3441 message.0.insert(0, SetAppendix(report_error));
3442 Ok(())
3443 }
3444
3445 pub fn new_notify_query(
3448 responder: impl Into<Location>,
3449 notify: impl Into<<T as Config>::RuntimeCall>,
3450 timeout: BlockNumberFor<T>,
3451 match_querier: impl Into<Location>,
3452 ) -> u64 {
3453 let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
3454 "decode input is output of Call encode; Call guaranteed to have two enums; qed",
3455 );
3456 Self::do_new_query(responder, Some(notify), timeout, match_querier)
3457 }
3458
3459 fn note_unknown_version(dest: &Location) {
3462 tracing::trace!(
3463 target: "xcm::pallet_xcm::note_unknown_version",
3464 ?dest, "XCM version is unknown for destination"
3465 );
3466 let versioned_dest = VersionedLocation::from(dest.clone());
3467 VersionDiscoveryQueue::<T>::mutate(|q| {
3468 if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
3469 q[index].1.saturating_inc();
3471 } else {
3472 let _ = q.try_push((versioned_dest, 1));
3473 }
3474 });
3475 }
3476
3477 fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
3483 T::XcmExecutor::charge_fees(location.clone(), assets.clone()).map_err(|error| {
3484 tracing::debug!(
3485 target: "xcm::pallet_xcm::charge_fees", ?error,
3486 "Failed to charge fees for location with assets",
3487 );
3488 Error::<T>::FeesNotMet
3489 })?;
3490 Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
3491 Ok(())
3492 }
3493
3494 #[cfg(any(feature = "try-runtime", test))]
3504 pub fn do_try_state() -> Result<(), TryRuntimeError> {
3505 use migration::data::NeedsMigration;
3506
3507 let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
3511 {
3512 XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
3513 } else {
3514 XCM_VERSION.saturating_sub(1)
3515 };
3516
3517 ensure!(
3519 !Queries::<T>::iter_values()
3520 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3521 TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
3522 );
3523
3524 ensure!(
3526 !LockedFungibles::<T>::iter_values()
3527 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3528 TryRuntimeError::Other(
3529 "`LockedFungibles` data should be migrated to the higher xcm version!"
3530 )
3531 );
3532
3533 ensure!(
3535 !RemoteLockedFungibles::<T>::iter()
3536 .any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version) ||
3537 data.needs_migration(minimal_allowed_xcm_version)),
3538 TryRuntimeError::Other(
3539 "`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
3540 )
3541 );
3542
3543 if CurrentMigration::<T>::exists() {
3546 return Ok(())
3547 }
3548
3549 for v in 0..XCM_VERSION {
3551 ensure!(
3552 SupportedVersion::<T>::iter_prefix(v).next().is_none(),
3553 TryRuntimeError::Other(
3554 "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
3555 )
3556 );
3557 ensure!(
3558 VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
3559 TryRuntimeError::Other(
3560 "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
3561 )
3562 );
3563 ensure!(
3564 VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
3565 TryRuntimeError::Other(
3566 "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
3567 )
3568 );
3569 }
3570
3571 Ok(())
3572 }
3573}
3574
3575pub struct LockTicket<T: Config> {
3576 sovereign_account: T::AccountId,
3577 amount: BalanceOf<T>,
3578 unlocker: Location,
3579 item_index: Option<usize>,
3580}
3581
3582impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
3583 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3584 use xcm_executor::traits::LockError::UnexpectedState;
3585 let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
3586 match self.item_index {
3587 Some(index) => {
3588 ensure!(locks.len() > index, UnexpectedState);
3589 ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
3590 locks[index].0 = locks[index].0.max(self.amount);
3591 },
3592 None => {
3593 locks.try_push((self.amount, self.unlocker.into())).map_err(
3594 |(balance, location)| {
3595 tracing::debug!(
3596 target: "xcm::pallet_xcm::enact", ?balance, ?location,
3597 "Failed to lock fungibles",
3598 );
3599 UnexpectedState
3600 },
3601 )?;
3602 },
3603 }
3604 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3605 T::Currency::extend_lock(
3606 *b"py/xcmlk",
3607 &self.sovereign_account,
3608 self.amount,
3609 WithdrawReasons::all(),
3610 );
3611 Ok(())
3612 }
3613}
3614
3615pub struct UnlockTicket<T: Config> {
3616 sovereign_account: T::AccountId,
3617 amount: BalanceOf<T>,
3618 unlocker: Location,
3619}
3620
3621impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
3622 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3623 use xcm_executor::traits::LockError::UnexpectedState;
3624 let mut locks =
3625 LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
3626 let mut maybe_remove_index = None;
3627 let mut locked = BalanceOf::<T>::zero();
3628 let mut found = false;
3629 for (i, x) in locks.iter_mut().enumerate() {
3632 if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
3633 x.0 = x.0.saturating_sub(self.amount);
3634 if x.0.is_zero() {
3635 maybe_remove_index = Some(i);
3636 }
3637 found = true;
3638 }
3639 locked = locked.max(x.0);
3640 }
3641 ensure!(found, UnexpectedState);
3642 if let Some(remove_index) = maybe_remove_index {
3643 locks.swap_remove(remove_index);
3644 }
3645 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3646 let reasons = WithdrawReasons::all();
3647 T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
3648 Ok(())
3649 }
3650}
3651
3652pub struct ReduceTicket<T: Config> {
3653 key: (u32, T::AccountId, VersionedAssetId),
3654 amount: u128,
3655 locker: VersionedLocation,
3656 owner: VersionedLocation,
3657}
3658
3659impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
3660 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3661 use xcm_executor::traits::LockError::UnexpectedState;
3662 let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
3663 ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
3664 let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
3665 ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
3666 if new_amount == 0 {
3667 RemoteLockedFungibles::<T>::remove(&self.key);
3668 } else {
3669 record.amount = new_amount;
3670 RemoteLockedFungibles::<T>::insert(&self.key, &record);
3671 }
3672 Ok(())
3673 }
3674}
3675
3676impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
3677 type LockTicket = LockTicket<T>;
3678 type UnlockTicket = UnlockTicket<T>;
3679 type ReduceTicket = ReduceTicket<T>;
3680
3681 fn prepare_lock(
3682 unlocker: Location,
3683 asset: Asset,
3684 owner: Location,
3685 ) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
3686 use xcm_executor::traits::LockError::*;
3687 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3688 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3689 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3690 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3691 let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
3692 ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
3693 Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
3694 }
3695
3696 fn prepare_unlock(
3697 unlocker: Location,
3698 asset: Asset,
3699 owner: Location,
3700 ) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
3701 use xcm_executor::traits::LockError::*;
3702 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3703 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3704 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3705 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3706 let item_index =
3707 locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
3708 ensure!(locks[item_index].0 >= amount, NotLocked);
3709 Ok(UnlockTicket { sovereign_account, amount, unlocker })
3710 }
3711
3712 fn note_unlockable(
3713 locker: Location,
3714 asset: Asset,
3715 mut owner: Location,
3716 ) -> Result<(), xcm_executor::traits::LockError> {
3717 use xcm_executor::traits::LockError::*;
3718 ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
3719 let amount = match asset.fun {
3720 Fungible(a) => a,
3721 NonFungible(_) => return Err(Unimplemented),
3722 };
3723 owner.remove_network_id();
3724 let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3725 let locker = locker.into();
3726 let owner = owner.into();
3727 let id: VersionedAssetId = asset.id.into();
3728 let key = (XCM_VERSION, account, id);
3729 let mut record =
3730 RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
3731 if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
3732 ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
3734 record.consumers = old.consumers;
3735 record.amount = record.amount.max(old.amount);
3736 }
3737 RemoteLockedFungibles::<T>::insert(&key, record);
3738 Ok(())
3739 }
3740
3741 fn prepare_reduce_unlockable(
3742 locker: Location,
3743 asset: Asset,
3744 mut owner: Location,
3745 ) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
3746 use xcm_executor::traits::LockError::*;
3747 let amount = match asset.fun {
3748 Fungible(a) => a,
3749 NonFungible(_) => return Err(Unimplemented),
3750 };
3751 owner.remove_network_id();
3752 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3753 let locker = locker.into();
3754 let owner = owner.into();
3755 let id: VersionedAssetId = asset.id.into();
3756 let key = (XCM_VERSION, sovereign_account, id);
3757
3758 let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
3759 ensure!(locker == record.locker && owner == record.owner, WouldClobber);
3761 ensure!(record.amount >= amount, NotEnoughLocked);
3762 ensure!(
3763 record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
3764 InUse
3765 );
3766 Ok(ReduceTicket { key, amount, locker, owner })
3767 }
3768}
3769
3770impl<T: Config> WrapVersion for Pallet<T> {
3771 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
3772 dest: &Location,
3773 xcm: impl Into<VersionedXcm<RuntimeCall>>,
3774 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
3775 Self::get_version_for(dest)
3776 .or_else(|| {
3777 Self::note_unknown_version(dest);
3778 SafeXcmVersion::<T>::get()
3779 })
3780 .ok_or_else(|| {
3781 tracing::trace!(
3782 target: "xcm::pallet_xcm::wrap_version",
3783 ?dest, "Could not determine a version to wrap XCM for destination",
3784 );
3785 ()
3786 })
3787 .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
3788 }
3789}
3790
3791impl<T: Config> GetVersion for Pallet<T> {
3792 fn get_version_for(dest: &Location) -> Option<XcmVersion> {
3793 SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
3794 }
3795}
3796
3797impl<T: Config> VersionChangeNotifier for Pallet<T> {
3798 fn start(
3807 dest: &Location,
3808 query_id: QueryId,
3809 max_weight: Weight,
3810 _context: &XcmContext,
3811 ) -> XcmResult {
3812 let versioned_dest = LatestVersionedLocation(dest);
3813 let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
3814 ensure!(!already, XcmError::InvalidLocation);
3815
3816 let xcm_version = T::AdvertisedXcmVersion::get();
3817 let response = Response::Version(xcm_version);
3818 let instruction = QueryResponse { query_id, response, max_weight, querier: None };
3819 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3820 Self::deposit_event(Event::<T>::VersionNotifyStarted {
3821 destination: dest.clone(),
3822 cost,
3823 message_id,
3824 });
3825
3826 let value = (query_id, max_weight, xcm_version);
3827 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
3828 Ok(())
3829 }
3830
3831 fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
3834 VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
3835 Ok(())
3836 }
3837
3838 fn is_subscribed(dest: &Location) -> bool {
3840 let versioned_dest = LatestVersionedLocation(dest);
3841 VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
3842 }
3843}
3844
3845impl<T: Config> DropAssets for Pallet<T> {
3846 fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
3847 if assets.is_empty() {
3848 return Weight::zero()
3849 }
3850 let versioned = VersionedAssets::from(Assets::from(assets));
3851 let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
3852 AssetTraps::<T>::mutate(hash, |n| *n += 1);
3853 Self::deposit_event(Event::AssetsTrapped {
3854 hash,
3855 origin: origin.clone(),
3856 assets: versioned,
3857 });
3858 Weight::zero()
3860 }
3861}
3862
3863impl<T: Config> ClaimAssets for Pallet<T> {
3864 fn claim_assets(
3865 origin: &Location,
3866 ticket: &Location,
3867 assets: &Assets,
3868 _context: &XcmContext,
3869 ) -> bool {
3870 let mut versioned = VersionedAssets::from(assets.clone());
3871 match ticket.unpack() {
3872 (0, [GeneralIndex(i)]) =>
3873 versioned = match versioned.into_version(*i as u32) {
3874 Ok(v) => v,
3875 Err(()) => return false,
3876 },
3877 (0, []) => (),
3878 _ => return false,
3879 };
3880 let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
3881 match AssetTraps::<T>::get(hash) {
3882 0 => return false,
3883 1 => AssetTraps::<T>::remove(hash),
3884 n => AssetTraps::<T>::insert(hash, n - 1),
3885 }
3886 Self::deposit_event(Event::AssetsClaimed {
3887 hash,
3888 origin: origin.clone(),
3889 assets: versioned,
3890 });
3891 return true
3892 }
3893}
3894
3895impl<T: Config> OnResponse for Pallet<T> {
3896 fn expecting_response(
3897 origin: &Location,
3898 query_id: QueryId,
3899 querier: Option<&Location>,
3900 ) -> bool {
3901 match Queries::<T>::get(query_id) {
3902 Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
3903 Location::try_from(responder).map_or(false, |r| origin == &r) &&
3904 maybe_match_querier.map_or(true, |match_querier| {
3905 Location::try_from(match_querier).map_or(false, |match_querier| {
3906 querier.map_or(false, |q| q == &match_querier)
3907 })
3908 }),
3909 Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
3910 Location::try_from(r).map_or(false, |r| origin == &r),
3911 _ => false,
3912 }
3913 }
3914
3915 fn on_response(
3916 origin: &Location,
3917 query_id: QueryId,
3918 querier: Option<&Location>,
3919 response: Response,
3920 max_weight: Weight,
3921 _context: &XcmContext,
3922 ) -> Weight {
3923 let origin = origin.clone();
3924 match (response, Queries::<T>::get(query_id)) {
3925 (
3926 Response::Version(v),
3927 Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
3928 ) => {
3929 let origin: Location = match expected_origin.try_into() {
3930 Ok(o) if o == origin => o,
3931 Ok(o) => {
3932 Self::deposit_event(Event::InvalidResponder {
3933 origin: origin.clone(),
3934 query_id,
3935 expected_location: Some(o),
3936 });
3937 return Weight::zero()
3938 },
3939 _ => {
3940 Self::deposit_event(Event::InvalidResponder {
3941 origin: origin.clone(),
3942 query_id,
3943 expected_location: None,
3944 });
3945 return Weight::zero()
3947 },
3948 };
3949 if !is_active {
3951 Queries::<T>::insert(
3952 query_id,
3953 QueryStatus::VersionNotifier {
3954 origin: origin.clone().into(),
3955 is_active: true,
3956 },
3957 );
3958 }
3959 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
3961 Self::deposit_event(Event::SupportedVersionChanged {
3962 location: origin,
3963 version: v,
3964 });
3965 Weight::zero()
3966 },
3967 (
3968 response,
3969 Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
3970 ) => {
3971 if let Some(match_querier) = maybe_match_querier {
3972 let match_querier = match Location::try_from(match_querier) {
3973 Ok(mq) => mq,
3974 Err(_) => {
3975 Self::deposit_event(Event::InvalidQuerierVersion {
3976 origin: origin.clone(),
3977 query_id,
3978 });
3979 return Weight::zero()
3980 },
3981 };
3982 if querier.map_or(true, |q| q != &match_querier) {
3983 Self::deposit_event(Event::InvalidQuerier {
3984 origin: origin.clone(),
3985 query_id,
3986 expected_querier: match_querier,
3987 maybe_actual_querier: querier.cloned(),
3988 });
3989 return Weight::zero()
3990 }
3991 }
3992 let responder = match Location::try_from(responder) {
3993 Ok(r) => r,
3994 Err(_) => {
3995 Self::deposit_event(Event::InvalidResponderVersion {
3996 origin: origin.clone(),
3997 query_id,
3998 });
3999 return Weight::zero()
4000 },
4001 };
4002 if origin != responder {
4003 Self::deposit_event(Event::InvalidResponder {
4004 origin: origin.clone(),
4005 query_id,
4006 expected_location: Some(responder),
4007 });
4008 return Weight::zero()
4009 }
4010 match maybe_notify {
4011 Some((pallet_index, call_index)) => {
4012 let bare = (pallet_index, call_index, query_id, response);
4016 if let Ok(call) = bare.using_encoded(|mut bytes| {
4017 <T as Config>::RuntimeCall::decode(&mut bytes)
4018 }) {
4019 Queries::<T>::remove(query_id);
4020 let weight = call.get_dispatch_info().call_weight;
4021 if weight.any_gt(max_weight) {
4022 let e = Event::NotifyOverweight {
4023 query_id,
4024 pallet_index,
4025 call_index,
4026 actual_weight: weight,
4027 max_budgeted_weight: max_weight,
4028 };
4029 Self::deposit_event(e);
4030 return Weight::zero()
4031 }
4032 let dispatch_origin = Origin::Response(origin.clone()).into();
4033 match call.dispatch(dispatch_origin) {
4034 Ok(post_info) => {
4035 let e = Event::Notified { query_id, pallet_index, call_index };
4036 Self::deposit_event(e);
4037 post_info.actual_weight
4038 },
4039 Err(error_and_info) => {
4040 let e = Event::NotifyDispatchError {
4041 query_id,
4042 pallet_index,
4043 call_index,
4044 };
4045 Self::deposit_event(e);
4046 error_and_info.post_info.actual_weight
4049 },
4050 }
4051 .unwrap_or(weight)
4052 } else {
4053 let e =
4054 Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
4055 Self::deposit_event(e);
4056 Weight::zero()
4057 }
4058 },
4059 None => {
4060 let e = Event::ResponseReady { query_id, response: response.clone() };
4061 Self::deposit_event(e);
4062 let at = frame_system::Pallet::<T>::current_block_number();
4063 let response = response.into();
4064 Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
4065 Weight::zero()
4066 },
4067 }
4068 },
4069 _ => {
4070 let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
4071 Self::deposit_event(e);
4072 Weight::zero()
4073 },
4074 }
4075 }
4076}
4077
4078impl<T: Config> CheckSuspension for Pallet<T> {
4079 fn is_suspended<Call>(
4080 _origin: &Location,
4081 _instructions: &mut [Instruction<Call>],
4082 _max_weight: Weight,
4083 _properties: &mut Properties,
4084 ) -> bool {
4085 XcmExecutionSuspended::<T>::get()
4086 }
4087}
4088
4089impl<T: Config> RecordXcm for Pallet<T> {
4090 fn should_record() -> bool {
4091 ShouldRecordXcm::<T>::get()
4092 }
4093
4094 fn set_record_xcm(enabled: bool) {
4095 ShouldRecordXcm::<T>::put(enabled);
4096 }
4097
4098 fn recorded_xcm() -> Option<Xcm<()>> {
4099 RecordedXcm::<T>::get()
4100 }
4101
4102 fn record(xcm: Xcm<()>) {
4103 RecordedXcm::<T>::put(xcm);
4104 }
4105}
4106
4107pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4111where
4112 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4113{
4114 match o.into() {
4115 Ok(Origin::Xcm(location)) => Ok(location),
4116 _ => Err(BadOrigin),
4117 }
4118}
4119
4120pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4124where
4125 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4126{
4127 match o.into() {
4128 Ok(Origin::Response(location)) => Ok(location),
4129 _ => Err(BadOrigin),
4130 }
4131}
4132
4133pub struct AuthorizedAliasers<T>(PhantomData<T>);
4139impl<L: Into<VersionedLocation> + Clone, T: Config> ContainsPair<L, L> for AuthorizedAliasers<T> {
4140 fn contains(origin: &L, target: &L) -> bool {
4141 let origin: VersionedLocation = origin.clone().into();
4142 let target: VersionedLocation = target.clone().into();
4143 tracing::trace!(target: "xcm::pallet_xcm::AuthorizedAliasers::contains", ?origin, ?target);
4144 Pallet::<T>::is_authorized_alias(origin, target).unwrap_or(false)
4147 }
4148}
4149
4150pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4155impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
4156 for IsMajorityOfBody<Prefix, Body>
4157{
4158 fn contains(l: &Location) -> bool {
4159 let maybe_suffix = l.match_and_split(&Prefix::get());
4160 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
4161 }
4162}
4163
4164pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4168impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
4169 fn contains(l: &Location) -> bool {
4170 let maybe_suffix = l.match_and_split(&Prefix::get());
4171 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
4172 }
4173}
4174
4175pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
4178impl<
4179 O: OriginTrait + From<Origin>,
4180 F: Contains<L>,
4181 L: TryFrom<Location> + TryInto<Location> + Clone,
4182 > EnsureOrigin<O> for EnsureXcm<F, L>
4183where
4184 for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4185{
4186 type Success = L;
4187
4188 fn try_origin(outer: O) -> Result<Self::Success, O> {
4189 match outer.caller().try_into() {
4190 Ok(Origin::Xcm(ref location)) =>
4191 if let Ok(location) = location.clone().try_into() {
4192 if F::contains(&location) {
4193 return Ok(location);
4194 }
4195 },
4196 _ => (),
4197 }
4198
4199 Err(outer)
4200 }
4201
4202 #[cfg(feature = "runtime-benchmarks")]
4203 fn try_successful_origin() -> Result<O, ()> {
4204 Ok(O::from(Origin::Xcm(Here.into())))
4205 }
4206}
4207
4208pub struct EnsureResponse<F>(PhantomData<F>);
4211impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
4212where
4213 for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4214{
4215 type Success = Location;
4216
4217 fn try_origin(outer: O) -> Result<Self::Success, O> {
4218 match outer.caller().try_into() {
4219 Ok(Origin::Response(responder)) => return Ok(responder.clone()),
4220 _ => (),
4221 }
4222
4223 Err(outer)
4224 }
4225
4226 #[cfg(feature = "runtime-benchmarks")]
4227 fn try_successful_origin() -> Result<O, ()> {
4228 Ok(O::from(Origin::Response(Here.into())))
4229 }
4230}
4231
4232pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
4235impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
4236 for XcmPassthrough<RuntimeOrigin>
4237{
4238 fn convert_origin(
4239 origin: impl Into<Location>,
4240 kind: OriginKind,
4241 ) -> Result<RuntimeOrigin, Location> {
4242 let origin = origin.into();
4243 match kind {
4244 OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
4245 _ => Err(origin),
4246 }
4247 }
4248}