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