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 pub supported_version: Vec<(Location, XcmVersion)>,
976 }
977
978 impl<T: Config> Default for GenesisConfig<T> {
979 fn default() -> Self {
980 Self {
981 _config: Default::default(),
982 safe_xcm_version: Some(XCM_VERSION),
983 supported_version: Vec::new(),
984 }
985 }
986 }
987
988 #[pallet::genesis_build]
989 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
990 fn build(&self) {
991 SafeXcmVersion::<T>::set(self.safe_xcm_version);
992 self.supported_version.iter().for_each(|(location, version)| {
994 SupportedVersion::<T>::insert(
995 XCM_VERSION,
996 LatestVersionedLocation(location),
997 version,
998 );
999 });
1000 }
1001 }
1002
1003 #[pallet::hooks]
1004 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
1005 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
1006 let mut weight_used = Weight::zero();
1007 if let Some(migration) = CurrentMigration::<T>::get() {
1008 let max_weight = T::BlockWeights::get().max_block / 10;
1010 let (w, maybe_migration) = Self::lazy_migration(migration, max_weight);
1011 if maybe_migration.is_none() {
1012 Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION });
1013 }
1014 CurrentMigration::<T>::set(maybe_migration);
1015 weight_used.saturating_accrue(w);
1016 }
1017
1018 let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
1021 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
1023 q.sort_by_key(|i| i.1);
1024 while let Some((versioned_dest, _)) = q.pop() {
1025 if let Ok(dest) = Location::try_from(versioned_dest) {
1026 if Self::request_version_notify(dest).is_ok() {
1027 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
1029 break
1030 }
1031 }
1032 }
1033 if let Ok(q) = BoundedVec::try_from(q) {
1036 VersionDiscoveryQueue::<T>::put(q);
1037 }
1038 weight_used
1039 }
1040
1041 #[cfg(feature = "try-runtime")]
1042 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1043 Self::do_try_state()
1044 }
1045 }
1046
1047 pub mod migrations {
1048 use super::*;
1049 use frame_support::traits::{PalletInfoAccess, StorageVersion};
1050
1051 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
1052 enum QueryStatusV0<BlockNumber> {
1053 Pending {
1054 responder: VersionedLocation,
1055 maybe_notify: Option<(u8, u8)>,
1056 timeout: BlockNumber,
1057 },
1058 VersionNotifier {
1059 origin: VersionedLocation,
1060 is_active: bool,
1061 },
1062 Ready {
1063 response: VersionedResponse,
1064 at: BlockNumber,
1065 },
1066 }
1067 impl<B> From<QueryStatusV0<B>> for QueryStatus<B> {
1068 fn from(old: QueryStatusV0<B>) -> Self {
1069 use QueryStatusV0::*;
1070 match old {
1071 Pending { responder, maybe_notify, timeout } => QueryStatus::Pending {
1072 responder,
1073 maybe_notify,
1074 timeout,
1075 maybe_match_querier: Some(Location::here().into()),
1076 },
1077 VersionNotifier { origin, is_active } =>
1078 QueryStatus::VersionNotifier { origin, is_active },
1079 Ready { response, at } => QueryStatus::Ready { response, at },
1080 }
1081 }
1082 }
1083
1084 pub fn migrate_to_v1<T: Config, P: GetStorageVersion + PalletInfoAccess>(
1085 ) -> frame_support::weights::Weight {
1086 let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
1087 tracing::info!(
1088 target: "runtime::xcm",
1089 ?on_chain_storage_version,
1090 "Running migration storage v1 for xcm with storage version",
1091 );
1092
1093 if on_chain_storage_version < 1 {
1094 let mut count = 0;
1095 Queries::<T>::translate::<QueryStatusV0<BlockNumberFor<T>>, _>(|_key, value| {
1096 count += 1;
1097 Some(value.into())
1098 });
1099 StorageVersion::new(1).put::<P>();
1100 tracing::info!(
1101 target: "runtime::xcm",
1102 ?on_chain_storage_version,
1103 "Running migration storage v1 for xcm with storage version was complete",
1104 );
1105 T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
1107 } else {
1108 tracing::warn!(
1109 target: "runtime::xcm",
1110 ?on_chain_storage_version,
1111 "Attempted to apply migration to v1 but failed because storage version is",
1112 );
1113 T::DbWeight::get().reads(1)
1114 }
1115 }
1116 }
1117
1118 #[pallet::call(weight(<T as Config>::WeightInfo))]
1119 impl<T: Config> Pallet<T> {
1120 #[pallet::call_index(0)]
1121 pub fn send(
1122 origin: OriginFor<T>,
1123 dest: Box<VersionedLocation>,
1124 message: Box<VersionedXcm<()>>,
1125 ) -> DispatchResult {
1126 <Self as SendController<_>>::send(origin, dest, message)?;
1127 Ok(())
1128 }
1129
1130 #[pallet::call_index(1)]
1149 #[allow(deprecated)]
1150 #[deprecated(
1151 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_teleport_assets` or `transfer_assets`"
1152 )]
1153 pub fn teleport_assets(
1154 origin: OriginFor<T>,
1155 dest: Box<VersionedLocation>,
1156 beneficiary: Box<VersionedLocation>,
1157 assets: Box<VersionedAssets>,
1158 fee_asset_item: u32,
1159 ) -> DispatchResult {
1160 Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
1161 }
1162
1163 #[pallet::call_index(2)]
1194 #[allow(deprecated)]
1195 #[deprecated(
1196 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_reserve_transfer_assets` or `transfer_assets`"
1197 )]
1198 pub fn reserve_transfer_assets(
1199 origin: OriginFor<T>,
1200 dest: Box<VersionedLocation>,
1201 beneficiary: Box<VersionedLocation>,
1202 assets: Box<VersionedAssets>,
1203 fee_asset_item: u32,
1204 ) -> DispatchResult {
1205 Self::do_reserve_transfer_assets(
1206 origin,
1207 dest,
1208 beneficiary,
1209 assets,
1210 fee_asset_item,
1211 Unlimited,
1212 )
1213 }
1214
1215 #[pallet::call_index(3)]
1224 #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
1225 pub fn execute(
1226 origin: OriginFor<T>,
1227 message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
1228 max_weight: Weight,
1229 ) -> DispatchResultWithPostInfo {
1230 let weight_used =
1231 <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
1232 Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())
1233 }
1234
1235 #[pallet::call_index(4)]
1242 pub fn force_xcm_version(
1243 origin: OriginFor<T>,
1244 location: Box<Location>,
1245 version: XcmVersion,
1246 ) -> DispatchResult {
1247 T::AdminOrigin::ensure_origin(origin)?;
1248 let location = *location;
1249 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&location), version);
1250 Self::deposit_event(Event::SupportedVersionChanged { location, version });
1251 Ok(())
1252 }
1253
1254 #[pallet::call_index(5)]
1260 pub fn force_default_xcm_version(
1261 origin: OriginFor<T>,
1262 maybe_xcm_version: Option<XcmVersion>,
1263 ) -> DispatchResult {
1264 T::AdminOrigin::ensure_origin(origin)?;
1265 SafeXcmVersion::<T>::set(maybe_xcm_version);
1266 Ok(())
1267 }
1268
1269 #[pallet::call_index(6)]
1274 pub fn force_subscribe_version_notify(
1275 origin: OriginFor<T>,
1276 location: Box<VersionedLocation>,
1277 ) -> DispatchResult {
1278 T::AdminOrigin::ensure_origin(origin)?;
1279 let location: Location = (*location).try_into().map_err(|()| {
1280 tracing::debug!(
1281 target: "xcm::pallet_xcm::force_subscribe_version_notify",
1282 "Failed to convert VersionedLocation for subscription target"
1283 );
1284 Error::<T>::BadLocation
1285 })?;
1286 Self::request_version_notify(location).map_err(|e| {
1287 tracing::debug!(
1288 target: "xcm::pallet_xcm::force_subscribe_version_notify", error=?e,
1289 "Failed to subscribe for version notifications for location"
1290 );
1291 match e {
1292 XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
1293 _ => Error::<T>::InvalidOrigin,
1294 }
1295 .into()
1296 })
1297 }
1298
1299 #[pallet::call_index(7)]
1306 pub fn force_unsubscribe_version_notify(
1307 origin: OriginFor<T>,
1308 location: Box<VersionedLocation>,
1309 ) -> DispatchResult {
1310 T::AdminOrigin::ensure_origin(origin)?;
1311 let location: Location = (*location).try_into().map_err(|()| {
1312 tracing::debug!(
1313 target: "xcm::pallet_xcm::force_unsubscribe_version_notify",
1314 "Failed to convert VersionedLocation for unsubscription target"
1315 );
1316 Error::<T>::BadLocation
1317 })?;
1318 Self::unrequest_version_notify(location).map_err(|e| {
1319 tracing::debug!(
1320 target: "xcm::pallet_xcm::force_unsubscribe_version_notify", error=?e,
1321 "Failed to unsubscribe from version notifications for location"
1322 );
1323 match e {
1324 XcmError::InvalidLocation => Error::<T>::NoSubscription,
1325 _ => Error::<T>::InvalidOrigin,
1326 }
1327 .into()
1328 })
1329 }
1330
1331 #[pallet::call_index(8)]
1362 #[pallet::weight(T::WeightInfo::reserve_transfer_assets())]
1363 pub fn limited_reserve_transfer_assets(
1364 origin: OriginFor<T>,
1365 dest: Box<VersionedLocation>,
1366 beneficiary: Box<VersionedLocation>,
1367 assets: Box<VersionedAssets>,
1368 fee_asset_item: u32,
1369 weight_limit: WeightLimit,
1370 ) -> DispatchResult {
1371 Self::do_reserve_transfer_assets(
1372 origin,
1373 dest,
1374 beneficiary,
1375 assets,
1376 fee_asset_item,
1377 weight_limit,
1378 )
1379 }
1380
1381 #[pallet::call_index(9)]
1400 #[pallet::weight(T::WeightInfo::teleport_assets())]
1401 pub fn limited_teleport_assets(
1402 origin: OriginFor<T>,
1403 dest: Box<VersionedLocation>,
1404 beneficiary: Box<VersionedLocation>,
1405 assets: Box<VersionedAssets>,
1406 fee_asset_item: u32,
1407 weight_limit: WeightLimit,
1408 ) -> DispatchResult {
1409 Self::do_teleport_assets(
1410 origin,
1411 dest,
1412 beneficiary,
1413 assets,
1414 fee_asset_item,
1415 weight_limit,
1416 )
1417 }
1418
1419 #[pallet::call_index(10)]
1424 pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
1425 T::AdminOrigin::ensure_origin(origin)?;
1426 XcmExecutionSuspended::<T>::set(suspended);
1427 Ok(())
1428 }
1429
1430 #[pallet::call_index(11)]
1464 pub fn transfer_assets(
1465 origin: OriginFor<T>,
1466 dest: Box<VersionedLocation>,
1467 beneficiary: Box<VersionedLocation>,
1468 assets: Box<VersionedAssets>,
1469 fee_asset_item: u32,
1470 weight_limit: WeightLimit,
1471 ) -> DispatchResult {
1472 let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1473 let dest = (*dest).try_into().map_err(|()| {
1474 tracing::debug!(
1475 target: "xcm::pallet_xcm::transfer_assets",
1476 "Failed to convert destination VersionedLocation",
1477 );
1478 Error::<T>::BadVersion
1479 })?;
1480 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
1481 tracing::debug!(
1482 target: "xcm::pallet_xcm::transfer_assets",
1483 "Failed to convert beneficiary VersionedLocation",
1484 );
1485 Error::<T>::BadVersion
1486 })?;
1487 let assets: Assets = (*assets).try_into().map_err(|()| {
1488 tracing::debug!(
1489 target: "xcm::pallet_xcm::transfer_assets",
1490 "Failed to convert VersionedAssets",
1491 );
1492 Error::<T>::BadVersion
1493 })?;
1494 tracing::debug!(
1495 target: "xcm::pallet_xcm::transfer_assets",
1496 ?origin, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
1497 );
1498
1499 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1500 let assets = assets.into_inner();
1501 let fee_asset_item = fee_asset_item as usize;
1502 let (fees_transfer_type, assets_transfer_type) =
1504 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
1505
1506 Self::ensure_network_asset_reserve_transfer_allowed(
1510 &assets,
1511 fee_asset_item,
1512 &assets_transfer_type,
1513 &fees_transfer_type,
1514 )?;
1515
1516 Self::do_transfer_assets(
1517 origin,
1518 dest,
1519 Either::Left(beneficiary),
1520 assets,
1521 assets_transfer_type,
1522 fee_asset_item,
1523 fees_transfer_type,
1524 weight_limit,
1525 )
1526 }
1527
1528 #[pallet::call_index(12)]
1535 pub fn claim_assets(
1536 origin: OriginFor<T>,
1537 assets: Box<VersionedAssets>,
1538 beneficiary: Box<VersionedLocation>,
1539 ) -> DispatchResult {
1540 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1541 tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?origin_location, ?assets, ?beneficiary);
1542 let assets_version = assets.identify_version();
1544 let assets: Assets = (*assets).try_into().map_err(|()| {
1545 tracing::debug!(
1546 target: "xcm::pallet_xcm::claim_assets",
1547 "Failed to convert input VersionedAssets",
1548 );
1549 Error::<T>::BadVersion
1550 })?;
1551 let number_of_assets = assets.len() as u32;
1552 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
1553 tracing::debug!(
1554 target: "xcm::pallet_xcm::claim_assets",
1555 "Failed to convert beneficiary VersionedLocation",
1556 );
1557 Error::<T>::BadVersion
1558 })?;
1559 let ticket: Location = GeneralIndex(assets_version as u128).into();
1560 let mut message = Xcm(vec![
1561 ClaimAsset { assets, ticket },
1562 DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
1563 ]);
1564 let weight = T::Weigher::weight(&mut message, Weight::MAX).map_err(|error| {
1565 tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?error, "Failed to calculate weight");
1566 Error::<T>::UnweighableMessage
1567 })?;
1568 let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
1569 let outcome = T::XcmExecutor::prepare_and_execute(
1570 origin_location,
1571 message,
1572 &mut hash,
1573 weight,
1574 weight,
1575 );
1576 outcome.ensure_complete().map_err(|error| {
1577 tracing::error!(target: "xcm::pallet_xcm::claim_assets", ?error, "XCM execution failed with error");
1578 Error::<T>::LocalExecutionIncompleteWithError { index: error.index, error: error.error.into()}
1579 })?;
1580 Ok(())
1581 }
1582
1583 #[pallet::call_index(13)]
1632 #[pallet::weight(T::WeightInfo::transfer_assets())]
1633 pub fn transfer_assets_using_type_and_then(
1634 origin: OriginFor<T>,
1635 dest: Box<VersionedLocation>,
1636 assets: Box<VersionedAssets>,
1637 assets_transfer_type: Box<TransferType>,
1638 remote_fees_id: Box<VersionedAssetId>,
1639 fees_transfer_type: Box<TransferType>,
1640 custom_xcm_on_dest: Box<VersionedXcm<()>>,
1641 weight_limit: WeightLimit,
1642 ) -> DispatchResult {
1643 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1644 let dest: Location = (*dest).try_into().map_err(|()| {
1645 tracing::debug!(
1646 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1647 "Failed to convert destination VersionedLocation",
1648 );
1649 Error::<T>::BadVersion
1650 })?;
1651 let assets: Assets = (*assets).try_into().map_err(|()| {
1652 tracing::debug!(
1653 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1654 "Failed to convert VersionedAssets",
1655 );
1656 Error::<T>::BadVersion
1657 })?;
1658 let fees_id: AssetId = (*remote_fees_id).try_into().map_err(|()| {
1659 tracing::debug!(
1660 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1661 "Failed to convert remote_fees_id VersionedAssetId",
1662 );
1663 Error::<T>::BadVersion
1664 })?;
1665 let remote_xcm: Xcm<()> = (*custom_xcm_on_dest).try_into().map_err(|()| {
1666 tracing::debug!(
1667 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1668 "Failed to convert custom_xcm_on_dest VersionedXcm",
1669 );
1670 Error::<T>::BadVersion
1671 })?;
1672 tracing::debug!(
1673 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1674 ?origin_location, ?dest, ?assets, ?assets_transfer_type, ?fees_id, ?fees_transfer_type,
1675 ?remote_xcm, ?weight_limit,
1676 );
1677
1678 let assets = assets.into_inner();
1679 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1680
1681 let fee_asset_index =
1682 assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
1683 Self::do_transfer_assets(
1684 origin_location,
1685 dest,
1686 Either::Right(remote_xcm),
1687 assets,
1688 *assets_transfer_type,
1689 fee_asset_index,
1690 *fees_transfer_type,
1691 weight_limit,
1692 )
1693 }
1694
1695 #[pallet::call_index(14)]
1707 pub fn add_authorized_alias(
1708 origin: OriginFor<T>,
1709 aliaser: Box<VersionedLocation>,
1710 expires: Option<u64>,
1711 ) -> DispatchResult {
1712 let signed_origin = ensure_signed(origin.clone())?;
1713 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1714 let new_aliaser: Location = (*aliaser).try_into().map_err(|()| {
1715 tracing::debug!(
1716 target: "xcm::pallet_xcm::add_authorized_alias",
1717 "Failed to convert aliaser VersionedLocation",
1718 );
1719 Error::<T>::BadVersion
1720 })?;
1721 ensure!(origin_location != new_aliaser, Error::<T>::BadLocation);
1722 let origin_location = match origin_location.unpack() {
1724 (0, [AccountId32 { network: _, id }]) =>
1725 Location::new(0, [AccountId32 { network: None, id: *id }]),
1726 _ => return Err(Error::<T>::InvalidOrigin.into()),
1727 };
1728 tracing::debug!(target: "xcm::pallet_xcm::add_authorized_alias", ?origin_location, ?new_aliaser, ?expires);
1729 ensure!(origin_location != new_aliaser, Error::<T>::BadLocation);
1730 if let Some(expiry) = expires {
1731 ensure!(
1732 expiry >
1733 frame_system::Pallet::<T>::current_block_number().saturated_into::<u64>(),
1734 Error::<T>::ExpiresInPast
1735 );
1736 }
1737 let versioned_origin = VersionedLocation::from(origin_location.clone());
1738 let versioned_aliaser = VersionedLocation::from(new_aliaser.clone());
1739 let entry = if let Some(entry) = AuthorizedAliases::<T>::get(&versioned_origin) {
1740 let (mut aliasers, mut ticket) = (entry.aliasers, entry.ticket);
1742 if let Some(aliaser) =
1743 aliasers.iter_mut().find(|aliaser| aliaser.location == versioned_aliaser)
1744 {
1745 aliaser.expiry = expires;
1747 } else {
1748 let aliaser =
1750 OriginAliaser { location: versioned_aliaser.clone(), expiry: expires };
1751 aliasers.try_push(aliaser).map_err(|_| {
1752 tracing::debug!(
1753 target: "xcm::pallet_xcm::add_authorized_alias",
1754 "Failed to add new aliaser to existing entry",
1755 );
1756 Error::<T>::TooManyAuthorizedAliases
1757 })?;
1758 ticket = ticket.update(&signed_origin, aliasers_footprint(aliasers.len()))?;
1760 }
1761 AuthorizedAliasesEntry { aliasers, ticket }
1762 } else {
1763 let ticket = TicketOf::<T>::new(&signed_origin, aliasers_footprint(1))?;
1765 let aliaser =
1766 OriginAliaser { location: versioned_aliaser.clone(), expiry: expires };
1767 let mut aliasers = BoundedVec::<OriginAliaser, MaxAuthorizedAliases>::new();
1768 aliasers.try_push(aliaser).map_err(|error| {
1769 tracing::debug!(
1770 target: "xcm::pallet_xcm::add_authorized_alias", ?error,
1771 "Failed to add first aliaser to new entry",
1772 );
1773 Error::<T>::TooManyAuthorizedAliases
1774 })?;
1775 AuthorizedAliasesEntry { aliasers, ticket }
1776 };
1777 AuthorizedAliases::<T>::insert(&versioned_origin, entry);
1779 Self::deposit_event(Event::AliasAuthorized {
1780 aliaser: new_aliaser,
1781 target: origin_location,
1782 expiry: expires,
1783 });
1784 Ok(())
1785 }
1786
1787 #[pallet::call_index(15)]
1790 pub fn remove_authorized_alias(
1791 origin: OriginFor<T>,
1792 aliaser: Box<VersionedLocation>,
1793 ) -> DispatchResult {
1794 let signed_origin = ensure_signed(origin.clone())?;
1795 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1796 let to_remove: Location = (*aliaser).try_into().map_err(|()| {
1797 tracing::debug!(
1798 target: "xcm::pallet_xcm::remove_authorized_alias",
1799 "Failed to convert aliaser VersionedLocation",
1800 );
1801 Error::<T>::BadVersion
1802 })?;
1803 ensure!(origin_location != to_remove, Error::<T>::BadLocation);
1804 let origin_location = match origin_location.unpack() {
1806 (0, [AccountId32 { network: _, id }]) =>
1807 Location::new(0, [AccountId32 { network: None, id: *id }]),
1808 _ => return Err(Error::<T>::InvalidOrigin.into()),
1809 };
1810 tracing::debug!(target: "xcm::pallet_xcm::remove_authorized_alias", ?origin_location, ?to_remove);
1811 ensure!(origin_location != to_remove, Error::<T>::BadLocation);
1812 let versioned_origin = VersionedLocation::from(origin_location.clone());
1814 let versioned_to_remove = VersionedLocation::from(to_remove.clone());
1815 AuthorizedAliases::<T>::get(&versioned_origin)
1816 .ok_or(Error::<T>::AliasNotFound.into())
1817 .and_then(|entry| {
1818 let (mut aliasers, mut ticket) = (entry.aliasers, entry.ticket);
1819 let old_len = aliasers.len();
1820 aliasers.retain(|alias| versioned_to_remove.ne(&alias.location));
1821 let new_len = aliasers.len();
1822 if aliasers.is_empty() {
1823 ticket.drop(&signed_origin)?;
1825 AuthorizedAliases::<T>::remove(&versioned_origin);
1826 Self::deposit_event(Event::AliasAuthorizationRemoved {
1827 aliaser: to_remove,
1828 target: origin_location,
1829 });
1830 Ok(())
1831 } else if old_len != new_len {
1832 ticket = ticket.update(&signed_origin, aliasers_footprint(new_len))?;
1834 let entry = AuthorizedAliasesEntry { aliasers, ticket };
1835 AuthorizedAliases::<T>::insert(&versioned_origin, entry);
1836 Self::deposit_event(Event::AliasAuthorizationRemoved {
1837 aliaser: to_remove,
1838 target: origin_location,
1839 });
1840 Ok(())
1841 } else {
1842 Err(Error::<T>::AliasNotFound.into())
1843 }
1844 })
1845 }
1846
1847 #[pallet::call_index(16)]
1850 #[pallet::weight(T::WeightInfo::remove_authorized_alias())]
1851 pub fn remove_all_authorized_aliases(origin: OriginFor<T>) -> DispatchResult {
1852 let signed_origin = ensure_signed(origin.clone())?;
1853 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1854 let origin_location = match origin_location.unpack() {
1856 (0, [AccountId32 { network: _, id }]) =>
1857 Location::new(0, [AccountId32 { network: None, id: *id }]),
1858 _ => return Err(Error::<T>::InvalidOrigin.into()),
1859 };
1860 tracing::debug!(target: "xcm::pallet_xcm::remove_all_authorized_aliases", ?origin_location);
1861 let versioned_origin = VersionedLocation::from(origin_location.clone());
1863 if let Some(entry) = AuthorizedAliases::<T>::get(&versioned_origin) {
1864 entry.ticket.drop(&signed_origin)?;
1866 AuthorizedAliases::<T>::remove(&versioned_origin);
1867 Self::deposit_event(Event::AliasesAuthorizationsRemoved {
1868 target: origin_location,
1869 });
1870 Ok(())
1871 } else {
1872 tracing::debug!(target: "xcm::pallet_xcm::remove_all_authorized_aliases", "No authorized alias entry found for the origin");
1873 Err(Error::<T>::AliasNotFound.into())
1874 }
1875 }
1876 }
1877}
1878
1879const MAX_ASSETS_FOR_TRANSFER: usize = 2;
1881
1882#[derive(Clone, PartialEq)]
1884enum FeesHandling<T: Config> {
1885 Batched { fees: Asset },
1887 Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
1889}
1890
1891impl<T: Config> core::fmt::Debug for FeesHandling<T> {
1892 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1893 match self {
1894 Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
1895 Self::Separate { local_xcm, remote_xcm } => write!(
1896 f,
1897 "FeesHandling::Separate(local: {:?}, remote: {:?})",
1898 local_xcm, remote_xcm
1899 ),
1900 }
1901 }
1902}
1903
1904impl<T: Config> QueryHandler for Pallet<T> {
1905 type BlockNumber = BlockNumberFor<T>;
1906 type Error = XcmError;
1907 type UniversalLocation = T::UniversalLocation;
1908
1909 fn new_query(
1911 responder: impl Into<Location>,
1912 timeout: BlockNumberFor<T>,
1913 match_querier: impl Into<Location>,
1914 ) -> QueryId {
1915 Self::do_new_query(responder, None, timeout, match_querier)
1916 }
1917
1918 fn report_outcome(
1921 message: &mut Xcm<()>,
1922 responder: impl Into<Location>,
1923 timeout: Self::BlockNumber,
1924 ) -> Result<QueryId, Self::Error> {
1925 let responder = responder.into();
1926 let destination =
1927 Self::UniversalLocation::get().invert_target(&responder).map_err(|()| {
1928 tracing::debug!(
1929 target: "xcm::pallet_xcm::report_outcome",
1930 "Failed to invert responder Location",
1931 );
1932 XcmError::LocationNotInvertible
1933 })?;
1934 let query_id = Self::new_query(responder, timeout, Here);
1935 let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
1936 let report_error = Xcm(vec![ReportError(response_info)]);
1937 message.0.insert(0, SetAppendix(report_error));
1938 Ok(query_id)
1939 }
1940
1941 fn take_response(query_id: QueryId) -> QueryResponseStatus<Self::BlockNumber> {
1943 match Queries::<T>::get(query_id) {
1944 Some(QueryStatus::Ready { response, at }) => match response.try_into() {
1945 Ok(response) => {
1946 Queries::<T>::remove(query_id);
1947 Self::deposit_event(Event::ResponseTaken { query_id });
1948 QueryResponseStatus::Ready { response, at }
1949 },
1950 Err(_) => {
1951 tracing::debug!(
1952 target: "xcm::pallet_xcm::take_response", ?query_id,
1953 "Failed to convert VersionedResponse to Response for query",
1954 );
1955 QueryResponseStatus::UnexpectedVersion
1956 },
1957 },
1958 Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
1959 Some(_) => {
1960 tracing::debug!(
1961 target: "xcm::pallet_xcm::take_response", ?query_id,
1962 "Unexpected QueryStatus variant for query",
1963 );
1964 QueryResponseStatus::UnexpectedVersion
1965 },
1966 None => {
1967 tracing::debug!(
1968 target: "xcm::pallet_xcm::take_response", ?query_id,
1969 "Query ID not found`",
1970 );
1971 QueryResponseStatus::NotFound
1972 },
1973 }
1974 }
1975
1976 #[cfg(feature = "runtime-benchmarks")]
1977 fn expect_response(id: QueryId, response: Response) {
1978 let response = response.into();
1979 Queries::<T>::insert(
1980 id,
1981 QueryStatus::Ready { response, at: frame_system::Pallet::<T>::current_block_number() },
1982 );
1983 }
1984}
1985
1986impl<T: Config> Pallet<T> {
1987 pub fn query(query_id: &QueryId) -> Option<QueryStatus<BlockNumberFor<T>>> {
1989 Queries::<T>::get(query_id)
1990 }
1991
1992 pub fn asset_trap(trap_id: &H256) -> u32 {
1998 AssetTraps::<T>::get(trap_id)
1999 }
2000
2001 fn find_fee_and_assets_transfer_types(
2006 assets: &[Asset],
2007 fee_asset_item: usize,
2008 dest: &Location,
2009 ) -> Result<(TransferType, TransferType), Error<T>> {
2010 let mut fees_transfer_type = None;
2011 let mut assets_transfer_type = None;
2012 for (idx, asset) in assets.iter().enumerate() {
2013 if let Fungible(x) = asset.fun {
2014 ensure!(!x.is_zero(), Error::<T>::Empty);
2016 }
2017 let transfer_type =
2018 T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
2019 if idx == fee_asset_item {
2020 fees_transfer_type = Some(transfer_type);
2021 } else {
2022 if let Some(existing) = assets_transfer_type.as_ref() {
2023 ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
2026 } else {
2027 assets_transfer_type = Some(transfer_type);
2029 }
2030 }
2031 }
2032 if assets.len() == 1 {
2034 assets_transfer_type = fees_transfer_type.clone()
2035 }
2036 Ok((
2037 fees_transfer_type.ok_or(Error::<T>::Empty)?,
2038 assets_transfer_type.ok_or(Error::<T>::Empty)?,
2039 ))
2040 }
2041
2042 fn do_reserve_transfer_assets(
2043 origin: OriginFor<T>,
2044 dest: Box<VersionedLocation>,
2045 beneficiary: Box<VersionedLocation>,
2046 assets: Box<VersionedAssets>,
2047 fee_asset_item: u32,
2048 weight_limit: WeightLimit,
2049 ) -> DispatchResult {
2050 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2051 let dest = (*dest).try_into().map_err(|()| {
2052 tracing::debug!(
2053 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2054 "Failed to convert destination VersionedLocation",
2055 );
2056 Error::<T>::BadVersion
2057 })?;
2058 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2059 tracing::debug!(
2060 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2061 "Failed to convert beneficiary VersionedLocation",
2062 );
2063 Error::<T>::BadVersion
2064 })?;
2065 let assets: Assets = (*assets).try_into().map_err(|()| {
2066 tracing::debug!(
2067 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2068 "Failed to convert VersionedAssets",
2069 );
2070 Error::<T>::BadVersion
2071 })?;
2072 tracing::debug!(
2073 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2074 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item,
2075 );
2076
2077 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2078 let value = (origin_location, assets.into_inner());
2079 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2080 let (origin, assets) = value;
2081
2082 let fee_asset_item = fee_asset_item as usize;
2083 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
2084
2085 let (fees_transfer_type, assets_transfer_type) =
2087 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
2088 ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
2090 ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
2092
2093 Self::ensure_network_asset_reserve_transfer_allowed(
2097 &assets,
2098 fee_asset_item,
2099 &assets_transfer_type,
2100 &fees_transfer_type,
2101 )?;
2102
2103 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2104 origin.clone(),
2105 dest.clone(),
2106 Either::Left(beneficiary),
2107 assets,
2108 assets_transfer_type,
2109 FeesHandling::Batched { fees },
2110 weight_limit,
2111 )?;
2112 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2113 }
2114
2115 fn do_teleport_assets(
2116 origin: OriginFor<T>,
2117 dest: Box<VersionedLocation>,
2118 beneficiary: Box<VersionedLocation>,
2119 assets: Box<VersionedAssets>,
2120 fee_asset_item: u32,
2121 weight_limit: WeightLimit,
2122 ) -> DispatchResult {
2123 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2124 let dest = (*dest).try_into().map_err(|()| {
2125 tracing::debug!(
2126 target: "xcm::pallet_xcm::do_teleport_assets",
2127 "Failed to convert destination VersionedLocation",
2128 );
2129 Error::<T>::BadVersion
2130 })?;
2131 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2132 tracing::debug!(
2133 target: "xcm::pallet_xcm::do_teleport_assets",
2134 "Failed to convert beneficiary VersionedLocation",
2135 );
2136 Error::<T>::BadVersion
2137 })?;
2138 let assets: Assets = (*assets).try_into().map_err(|()| {
2139 tracing::debug!(
2140 target: "xcm::pallet_xcm::do_teleport_assets",
2141 "Failed to convert VersionedAssets",
2142 );
2143 Error::<T>::BadVersion
2144 })?;
2145 tracing::debug!(
2146 target: "xcm::pallet_xcm::do_teleport_assets",
2147 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
2148 );
2149
2150 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2151 let value = (origin_location, assets.into_inner());
2152 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2153 let (origin_location, assets) = value;
2154 for asset in assets.iter() {
2155 let transfer_type =
2156 T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
2157 ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
2158 }
2159 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
2160
2161 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2162 origin_location.clone(),
2163 dest.clone(),
2164 Either::Left(beneficiary),
2165 assets,
2166 TransferType::Teleport,
2167 FeesHandling::Batched { fees },
2168 weight_limit,
2169 )?;
2170 Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
2171 }
2172
2173 fn do_transfer_assets(
2174 origin: Location,
2175 dest: Location,
2176 beneficiary: Either<Location, Xcm<()>>,
2177 mut assets: Vec<Asset>,
2178 assets_transfer_type: TransferType,
2179 fee_asset_index: usize,
2180 fees_transfer_type: TransferType,
2181 weight_limit: WeightLimit,
2182 ) -> DispatchResult {
2183 let fees = if fees_transfer_type == assets_transfer_type {
2185 let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
2186 FeesHandling::Batched { fees }
2188 } else {
2189 ensure!(
2195 !matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
2196 Error::<T>::InvalidAssetUnsupportedReserve
2197 );
2198 let weight_limit = weight_limit.clone();
2199 let fees = assets.remove(fee_asset_index);
2202 let (local_xcm, remote_xcm) = match fees_transfer_type {
2203 TransferType::LocalReserve => Self::local_reserve_fees_instructions(
2204 origin.clone(),
2205 dest.clone(),
2206 fees,
2207 weight_limit,
2208 )?,
2209 TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
2210 origin.clone(),
2211 dest.clone(),
2212 fees,
2213 weight_limit,
2214 )?,
2215 TransferType::Teleport => Self::teleport_fees_instructions(
2216 origin.clone(),
2217 dest.clone(),
2218 fees,
2219 weight_limit,
2220 )?,
2221 TransferType::RemoteReserve(_) =>
2222 return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2223 };
2224 FeesHandling::Separate { local_xcm, remote_xcm }
2225 };
2226
2227 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2228 origin.clone(),
2229 dest.clone(),
2230 beneficiary,
2231 assets,
2232 assets_transfer_type,
2233 fees,
2234 weight_limit,
2235 )?;
2236 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2237 }
2238
2239 fn build_xcm_transfer_type(
2240 origin: Location,
2241 dest: Location,
2242 beneficiary: Either<Location, Xcm<()>>,
2243 assets: Vec<Asset>,
2244 transfer_type: TransferType,
2245 fees: FeesHandling<T>,
2246 weight_limit: WeightLimit,
2247 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
2248 tracing::debug!(
2249 target: "xcm::pallet_xcm::build_xcm_transfer_type",
2250 ?origin, ?dest, ?beneficiary, ?assets, ?transfer_type, ?fees, ?weight_limit,
2251 );
2252 match transfer_type {
2253 TransferType::LocalReserve => Self::local_reserve_transfer_programs(
2254 origin.clone(),
2255 dest.clone(),
2256 beneficiary,
2257 assets,
2258 fees,
2259 weight_limit,
2260 )
2261 .map(|(local, remote)| (local, Some(remote))),
2262 TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
2263 origin.clone(),
2264 dest.clone(),
2265 beneficiary,
2266 assets,
2267 fees,
2268 weight_limit,
2269 )
2270 .map(|(local, remote)| (local, Some(remote))),
2271 TransferType::RemoteReserve(reserve) => {
2272 let fees = match fees {
2273 FeesHandling::Batched { fees } => fees,
2274 _ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2275 };
2276 Self::remote_reserve_transfer_program(
2277 origin.clone(),
2278 reserve.try_into().map_err(|()| {
2279 tracing::debug!(
2280 target: "xcm::pallet_xcm::build_xcm_transfer_type",
2281 "Failed to convert remote reserve location",
2282 );
2283 Error::<T>::BadVersion
2284 })?,
2285 beneficiary,
2286 dest.clone(),
2287 assets,
2288 fees,
2289 weight_limit,
2290 )
2291 .map(|local| (local, None))
2292 },
2293 TransferType::Teleport => Self::teleport_assets_program(
2294 origin.clone(),
2295 dest.clone(),
2296 beneficiary,
2297 assets,
2298 fees,
2299 weight_limit,
2300 )
2301 .map(|(local, remote)| (local, Some(remote))),
2302 }
2303 }
2304
2305 fn execute_xcm_transfer(
2306 origin: Location,
2307 dest: Location,
2308 mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
2309 remote_xcm: Option<Xcm<()>>,
2310 ) -> DispatchResult {
2311 tracing::debug!(
2312 target: "xcm::pallet_xcm::execute_xcm_transfer",
2313 ?origin, ?dest, ?local_xcm, ?remote_xcm,
2314 );
2315
2316 let weight =
2317 T::Weigher::weight(&mut local_xcm, Weight::MAX).map_err(|error| {
2318 tracing::debug!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, "Failed to calculate weight");
2319 Error::<T>::UnweighableMessage
2320 })?;
2321 let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
2322 let outcome = T::XcmExecutor::prepare_and_execute(
2323 origin.clone(),
2324 local_xcm,
2325 &mut hash,
2326 weight,
2327 weight,
2328 );
2329 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
2330 outcome.clone().ensure_complete().map_err(|error| {
2331 tracing::error!(
2332 target: "xcm::pallet_xcm::execute_xcm_transfer",
2333 ?error, "XCM execution failed with error with outcome: {:?}", outcome
2334 );
2335 Error::<T>::LocalExecutionIncompleteWithError {
2336 index: error.index,
2337 error: error.error.into(),
2338 }
2339 })?;
2340
2341 if let Some(remote_xcm) = remote_xcm {
2342 let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
2343 .map_err(|error| {
2344 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error");
2345 Error::<T>::from(error)
2346 })?;
2347 if origin != Here.into_location() {
2348 Self::charge_fees(origin.clone(), price.clone()).map_err(|error| {
2349 tracing::error!(
2350 target: "xcm::pallet_xcm::execute_xcm_transfer",
2351 ?error, ?price, ?origin, "Unable to charge fee",
2352 );
2353 Error::<T>::FeesNotMet
2354 })?;
2355 }
2356 let message_id = T::XcmRouter::deliver(ticket)
2357 .map_err(|error| {
2358 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error");
2359 Error::<T>::from(error)
2360 })?;
2361
2362 let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
2363 Self::deposit_event(e);
2364 }
2365 Ok(())
2366 }
2367
2368 fn add_fees_to_xcm(
2369 dest: Location,
2370 fees: FeesHandling<T>,
2371 weight_limit: WeightLimit,
2372 local: &mut Xcm<<T as Config>::RuntimeCall>,
2373 remote: &mut Xcm<()>,
2374 ) -> Result<(), Error<T>> {
2375 match fees {
2376 FeesHandling::Batched { fees } => {
2377 let context = T::UniversalLocation::get();
2378 let reanchored_fees =
2381 fees.reanchored(&dest, &context).map_err(|e| {
2382 tracing::error!(target: "xcm::pallet_xcm::add_fees_to_xcm", ?e, ?dest, ?context, "Failed to re-anchor fees");
2383 Error::<T>::CannotReanchor
2384 })?;
2385 remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
2387 },
2388 FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
2389 core::mem::swap(local, &mut local_fees);
2392 core::mem::swap(remote, &mut remote_fees);
2393 local.inner_mut().append(&mut local_fees.into_inner());
2395 remote.inner_mut().append(&mut remote_fees.into_inner());
2396 },
2397 }
2398 Ok(())
2399 }
2400
2401 fn local_reserve_fees_instructions(
2402 origin: Location,
2403 dest: Location,
2404 fees: Asset,
2405 weight_limit: WeightLimit,
2406 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2407 let value = (origin, vec![fees.clone()]);
2408 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2409
2410 let context = T::UniversalLocation::get();
2411 let reanchored_fees = fees.clone().reanchored(&dest, &context).map_err(|_| {
2412 tracing::debug!(
2413 target: "xcm::pallet_xcm::local_reserve_fees_instructions",
2414 "Failed to re-anchor fees",
2415 );
2416 Error::<T>::CannotReanchor
2417 })?;
2418
2419 let local_execute_xcm = Xcm(vec![
2420 TransferAsset { assets: fees.into(), beneficiary: dest },
2422 ]);
2423 let xcm_on_dest = Xcm(vec![
2424 ReserveAssetDeposited(reanchored_fees.clone().into()),
2426 BuyExecution { fees: reanchored_fees, weight_limit },
2428 ]);
2429 Ok((local_execute_xcm, xcm_on_dest))
2430 }
2431
2432 fn local_reserve_transfer_programs(
2433 origin: Location,
2434 dest: Location,
2435 beneficiary: Either<Location, Xcm<()>>,
2436 assets: Vec<Asset>,
2437 fees: FeesHandling<T>,
2438 weight_limit: WeightLimit,
2439 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2440 let value = (origin, assets);
2441 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2442 let (_, assets) = value;
2443
2444 let max_assets =
2446 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2447 let assets: Assets = assets.into();
2448 let context = T::UniversalLocation::get();
2449 let mut reanchored_assets = assets.clone();
2450 reanchored_assets
2451 .reanchor(&dest, &context)
2452 .map_err(|e| {
2453 tracing::error!(target: "xcm::pallet_xcm::local_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2454 Error::<T>::CannotReanchor
2455 })?;
2456
2457 let mut local_execute_xcm = Xcm(vec![
2459 TransferAsset { assets, beneficiary: dest.clone() },
2461 ]);
2462 let mut xcm_on_dest = Xcm(vec![
2464 ReserveAssetDeposited(reanchored_assets),
2466 ClearOrigin,
2468 ]);
2469 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2471
2472 let custom_remote_xcm = match beneficiary {
2474 Either::Right(custom_xcm) => custom_xcm,
2475 Either::Left(beneficiary) => {
2476 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2478 },
2479 };
2480 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2481
2482 Ok((local_execute_xcm, xcm_on_dest))
2483 }
2484
2485 fn destination_reserve_fees_instructions(
2486 origin: Location,
2487 dest: Location,
2488 fees: Asset,
2489 weight_limit: WeightLimit,
2490 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2491 let value = (origin, vec![fees.clone()]);
2492 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2493 ensure!(
2494 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&fees, &dest),
2495 Error::<T>::InvalidAssetUnsupportedReserve
2496 );
2497
2498 let context = T::UniversalLocation::get();
2499 let reanchored_fees = fees
2500 .clone()
2501 .reanchored(&dest, &context)
2502 .map_err(|e| {
2503 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_fees_instructions", ?e, ?dest,?context, "Failed to re-anchor fees");
2504 Error::<T>::CannotReanchor
2505 })?;
2506 let fees: Assets = fees.into();
2507
2508 let local_execute_xcm = Xcm(vec![
2509 WithdrawAsset(fees.clone()),
2511 BurnAsset(fees),
2513 ]);
2514 let xcm_on_dest = Xcm(vec![
2515 WithdrawAsset(reanchored_fees.clone().into()),
2517 BuyExecution { fees: reanchored_fees, weight_limit },
2519 ]);
2520 Ok((local_execute_xcm, xcm_on_dest))
2521 }
2522
2523 fn destination_reserve_transfer_programs(
2524 origin: Location,
2525 dest: Location,
2526 beneficiary: Either<Location, Xcm<()>>,
2527 assets: Vec<Asset>,
2528 fees: FeesHandling<T>,
2529 weight_limit: WeightLimit,
2530 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2531 let value = (origin, assets);
2532 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2533 let (_, assets) = value;
2534 for asset in assets.iter() {
2535 ensure!(
2536 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&asset, &dest),
2537 Error::<T>::InvalidAssetUnsupportedReserve
2538 );
2539 }
2540
2541 let max_assets =
2543 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2544 let assets: Assets = assets.into();
2545 let context = T::UniversalLocation::get();
2546 let mut reanchored_assets = assets.clone();
2547 reanchored_assets
2548 .reanchor(&dest, &context)
2549 .map_err(|e| {
2550 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2551 Error::<T>::CannotReanchor
2552 })?;
2553
2554 let mut local_execute_xcm = Xcm(vec![
2556 WithdrawAsset(assets.clone()),
2558 BurnAsset(assets),
2560 ]);
2561 let mut xcm_on_dest = Xcm(vec![
2563 WithdrawAsset(reanchored_assets),
2565 ClearOrigin,
2567 ]);
2568 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2570
2571 let custom_remote_xcm = match beneficiary {
2573 Either::Right(custom_xcm) => custom_xcm,
2574 Either::Left(beneficiary) => {
2575 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2577 },
2578 };
2579 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2580
2581 Ok((local_execute_xcm, xcm_on_dest))
2582 }
2583
2584 fn remote_reserve_transfer_program(
2586 origin: Location,
2587 reserve: Location,
2588 beneficiary: Either<Location, Xcm<()>>,
2589 dest: Location,
2590 assets: Vec<Asset>,
2591 fees: Asset,
2592 weight_limit: WeightLimit,
2593 ) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
2594 let value = (origin, assets);
2595 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2596 let (_, assets) = value;
2597
2598 let max_assets = assets.len() as u32;
2599 let context = T::UniversalLocation::get();
2600 let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
2603 let reserve_fees = fees_half_1
2605 .reanchored(&reserve, &context)
2606 .map_err(|e| {
2607 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor reserve_fees");
2608 Error::<T>::CannotReanchor
2609 })?;
2610 let dest_fees = fees_half_2
2612 .reanchored(&dest, &context)
2613 .map_err(|e| {
2614 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?dest, ?context, "Failed to re-anchor dest_fees");
2615 Error::<T>::CannotReanchor
2616 })?;
2617 let dest = dest.reanchored(&reserve, &context).map_err(|e| {
2619 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor dest");
2620 Error::<T>::CannotReanchor
2621 })?;
2622 let mut xcm_on_dest =
2624 Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
2625 let custom_xcm_on_dest = match beneficiary {
2627 Either::Right(custom_xcm) => custom_xcm,
2628 Either::Left(beneficiary) => {
2629 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2631 },
2632 };
2633 xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
2634 let xcm_on_reserve = Xcm(vec![
2636 BuyExecution { fees: reserve_fees, weight_limit },
2637 DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
2638 ]);
2639 Ok(Xcm(vec![
2640 WithdrawAsset(assets.into()),
2641 SetFeesMode { jit_withdraw: true },
2642 InitiateReserveWithdraw {
2643 assets: Wild(AllCounted(max_assets)),
2644 reserve,
2645 xcm: xcm_on_reserve,
2646 },
2647 ]))
2648 }
2649
2650 fn teleport_fees_instructions(
2651 origin: Location,
2652 dest: Location,
2653 fees: Asset,
2654 weight_limit: WeightLimit,
2655 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2656 let value = (origin, vec![fees.clone()]);
2657 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2658 ensure!(
2659 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&fees, &dest),
2660 Error::<T>::Filtered
2661 );
2662
2663 let context = T::UniversalLocation::get();
2664 let reanchored_fees = fees
2665 .clone()
2666 .reanchored(&dest, &context)
2667 .map_err(|e| {
2668 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?dest, ?context, "Failed to re-anchor fees");
2669 Error::<T>::CannotReanchor
2670 })?;
2671
2672 let dummy_context =
2674 XcmContext { origin: None, message_id: Default::default(), topic: None };
2675 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2680 &dest,
2681 &fees,
2682 &dummy_context,
2683 )
2684 .map_err(|e| {
2685 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?fees, ?dest, "Failed can_check_out");
2686 Error::<T>::CannotCheckOutTeleport
2687 })?;
2688 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2691 &dest,
2692 &fees,
2693 &dummy_context,
2694 );
2695
2696 let fees: Assets = fees.into();
2697 let local_execute_xcm = Xcm(vec![
2698 WithdrawAsset(fees.clone()),
2700 BurnAsset(fees),
2702 ]);
2703 let xcm_on_dest = Xcm(vec![
2704 ReceiveTeleportedAsset(reanchored_fees.clone().into()),
2706 BuyExecution { fees: reanchored_fees, weight_limit },
2708 ]);
2709 Ok((local_execute_xcm, xcm_on_dest))
2710 }
2711
2712 fn teleport_assets_program(
2713 origin: Location,
2714 dest: Location,
2715 beneficiary: Either<Location, Xcm<()>>,
2716 assets: Vec<Asset>,
2717 fees: FeesHandling<T>,
2718 weight_limit: WeightLimit,
2719 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2720 let value = (origin, assets);
2721 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2722 let (_, assets) = value;
2723 for asset in assets.iter() {
2724 ensure!(
2725 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&asset, &dest),
2726 Error::<T>::Filtered
2727 );
2728 }
2729
2730 let max_assets =
2732 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2733 let context = T::UniversalLocation::get();
2734 let assets: Assets = assets.into();
2735 let mut reanchored_assets = assets.clone();
2736 reanchored_assets
2737 .reanchor(&dest, &context)
2738 .map_err(|e| {
2739 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?dest, ?context, "Failed to re-anchor asset");
2740 Error::<T>::CannotReanchor
2741 })?;
2742
2743 let dummy_context =
2745 XcmContext { origin: None, message_id: Default::default(), topic: None };
2746 for asset in assets.inner() {
2747 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2752 &dest,
2753 asset,
2754 &dummy_context,
2755 )
2756 .map_err(|e| {
2757 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?asset, ?dest, "Failed can_check_out asset");
2758 Error::<T>::CannotCheckOutTeleport
2759 })?;
2760 }
2761 for asset in assets.inner() {
2762 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2765 &dest,
2766 asset,
2767 &dummy_context,
2768 );
2769 }
2770
2771 let mut local_execute_xcm = Xcm(vec![
2773 WithdrawAsset(assets.clone()),
2775 BurnAsset(assets),
2777 ]);
2778 let mut xcm_on_dest = Xcm(vec![
2780 ReceiveTeleportedAsset(reanchored_assets),
2782 ClearOrigin,
2784 ]);
2785 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2787
2788 let custom_remote_xcm = match beneficiary {
2790 Either::Right(custom_xcm) => custom_xcm,
2791 Either::Left(beneficiary) => {
2792 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2794 },
2795 };
2796 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2797
2798 Ok((local_execute_xcm, xcm_on_dest))
2799 }
2800
2801 pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
2803 match fees.fun {
2804 Fungible(amount) => {
2805 let fee1 = amount.saturating_div(2);
2806 let fee2 = amount.saturating_sub(fee1);
2807 ensure!(fee1 > 0, Error::<T>::FeesNotMet);
2808 ensure!(fee2 > 0, Error::<T>::FeesNotMet);
2809 Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
2810 },
2811 NonFungible(_) => Err(Error::<T>::FeesNotMet),
2812 }
2813 }
2814
2815 pub(crate) fn lazy_migration(
2818 mut stage: VersionMigrationStage,
2819 weight_cutoff: Weight,
2820 ) -> (Weight, Option<VersionMigrationStage>) {
2821 let mut weight_used = Weight::zero();
2822
2823 let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
2824 let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
2825 let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
2826 let vnt_notify_weight = T::WeightInfo::notify_current_targets();
2827 let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
2828 let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
2829 let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
2830
2831 use VersionMigrationStage::*;
2832
2833 if stage == MigrateSupportedVersion {
2834 for v in 0..XCM_VERSION {
2837 for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
2838 if let Ok(new_key) = old_key.into_latest() {
2839 SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
2840 }
2841 weight_used.saturating_accrue(sv_migrate_weight);
2842 if weight_used.any_gte(weight_cutoff) {
2843 return (weight_used, Some(stage))
2844 }
2845 }
2846 }
2847 stage = MigrateVersionNotifiers;
2848 }
2849 if stage == MigrateVersionNotifiers {
2850 for v in 0..XCM_VERSION {
2851 for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
2852 if let Ok(new_key) = old_key.into_latest() {
2853 VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
2854 }
2855 weight_used.saturating_accrue(vn_migrate_weight);
2856 if weight_used.any_gte(weight_cutoff) {
2857 return (weight_used, Some(stage))
2858 }
2859 }
2860 }
2861 stage = NotifyCurrentTargets(None);
2862 }
2863
2864 let xcm_version = T::AdvertisedXcmVersion::get();
2865
2866 if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
2867 let mut iter = match maybe_last_raw_key {
2868 Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
2869 None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
2870 };
2871 while let Some((key, value)) = iter.next() {
2872 let (query_id, max_weight, target_xcm_version) = value;
2873 let new_key: Location = match key.clone().try_into() {
2874 Ok(k) if target_xcm_version != xcm_version => k,
2875 _ => {
2876 weight_used.saturating_accrue(vnt_already_notified_weight);
2879 continue
2880 },
2881 };
2882 let response = Response::Version(xcm_version);
2883 let message =
2884 Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
2885 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2886 Ok((message_id, cost)) => {
2887 let value = (query_id, max_weight, xcm_version);
2888 VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
2889 Event::VersionChangeNotified {
2890 destination: new_key,
2891 result: xcm_version,
2892 cost,
2893 message_id,
2894 }
2895 },
2896 Err(e) => {
2897 VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
2898 Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
2899 },
2900 };
2901 Self::deposit_event(event);
2902 weight_used.saturating_accrue(vnt_notify_weight);
2903 if weight_used.any_gte(weight_cutoff) {
2904 let last = Some(iter.last_raw_key().into());
2905 return (weight_used, Some(NotifyCurrentTargets(last)))
2906 }
2907 }
2908 stage = MigrateAndNotifyOldTargets;
2909 }
2910 if stage == MigrateAndNotifyOldTargets {
2911 for v in 0..XCM_VERSION {
2912 for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
2913 let (query_id, max_weight, target_xcm_version) = value;
2914 let new_key = match Location::try_from(old_key.clone()) {
2915 Ok(k) => k,
2916 Err(()) => {
2917 Self::deposit_event(Event::NotifyTargetMigrationFail {
2918 location: old_key,
2919 query_id: value.0,
2920 });
2921 weight_used.saturating_accrue(vnt_migrate_fail_weight);
2922 if weight_used.any_gte(weight_cutoff) {
2923 return (weight_used, Some(stage))
2924 }
2925 continue
2926 },
2927 };
2928
2929 let versioned_key = LatestVersionedLocation(&new_key);
2930 if target_xcm_version == xcm_version {
2931 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
2932 weight_used.saturating_accrue(vnt_migrate_weight);
2933 } else {
2934 let response = Response::Version(xcm_version);
2936 let message = Xcm(vec![QueryResponse {
2937 query_id,
2938 response,
2939 max_weight,
2940 querier: None,
2941 }]);
2942 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2943 Ok((message_id, cost)) => {
2944 VersionNotifyTargets::<T>::insert(
2945 XCM_VERSION,
2946 versioned_key,
2947 (query_id, max_weight, xcm_version),
2948 );
2949 Event::VersionChangeNotified {
2950 destination: new_key,
2951 result: xcm_version,
2952 cost,
2953 message_id,
2954 }
2955 },
2956 Err(e) => Event::NotifyTargetSendFail {
2957 location: new_key,
2958 query_id,
2959 error: e.into(),
2960 },
2961 };
2962 Self::deposit_event(event);
2963 weight_used.saturating_accrue(vnt_notify_migrate_weight);
2964 }
2965 if weight_used.any_gte(weight_cutoff) {
2966 return (weight_used, Some(stage))
2967 }
2968 }
2969 }
2970 }
2971 (weight_used, None)
2972 }
2973
2974 pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
2976 let dest = dest.into();
2977 let versioned_dest = VersionedLocation::from(dest.clone());
2978 let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
2979 ensure!(!already, XcmError::InvalidLocation);
2980 let query_id = QueryCounter::<T>::mutate(|q| {
2981 let r = *q;
2982 q.saturating_inc();
2983 r
2984 });
2985 let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
2987 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
2988 Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
2989 VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
2990 let query_status =
2991 QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
2992 Queries::<T>::insert(query_id, query_status);
2993 Ok(())
2994 }
2995
2996 pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
2998 let dest = dest.into();
2999 let versioned_dest = LatestVersionedLocation(&dest);
3000 let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
3001 .ok_or(XcmError::InvalidLocation)?;
3002 let (message_id, cost) =
3003 send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
3004 Self::deposit_event(Event::VersionNotifyUnrequested {
3005 destination: dest,
3006 cost,
3007 message_id,
3008 });
3009 Queries::<T>::remove(query_id);
3010 Ok(())
3011 }
3012
3013 pub fn send_xcm(
3017 interior: impl Into<Junctions>,
3018 dest: impl Into<Location>,
3019 mut message: Xcm<()>,
3020 ) -> Result<XcmHash, SendError> {
3021 let interior = interior.into();
3022 let local_origin = interior.clone().into();
3023 let dest = dest.into();
3024 let is_waived =
3025 <T::XcmExecutor as FeeManager>::is_waived(Some(&local_origin), FeeReason::ChargeFees);
3026 if interior != Junctions::Here {
3027 message.0.insert(0, DescendOrigin(interior.clone()));
3028 }
3029 tracing::debug!(target: "xcm::send_xcm", "{:?}, {:?}", dest.clone(), message.clone());
3030 let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
3031 if !is_waived {
3032 Self::charge_fees(local_origin, price).map_err(|e| {
3033 tracing::error!(
3034 target: "xcm::pallet_xcm::send_xcm",
3035 ?e,
3036 "Charging fees failed with error",
3037 );
3038 SendError::Fees
3039 })?;
3040 }
3041 T::XcmRouter::deliver(ticket)
3042 }
3043
3044 pub fn check_account() -> T::AccountId {
3045 const ID: PalletId = PalletId(*b"py/xcmch");
3046 AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
3047 }
3048
3049 pub fn dry_run_call<Runtime, Router, OriginCaller, RuntimeCall>(
3055 origin: OriginCaller,
3056 call: RuntimeCall,
3057 result_xcms_version: XcmVersion,
3058 ) -> Result<CallDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3059 where
3060 Runtime: crate::Config,
3061 Router: InspectMessageQueues,
3062 RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
3063 <RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
3064 {
3065 crate::Pallet::<Runtime>::set_record_xcm(true);
3066 Router::clear_messages();
3068 frame_system::Pallet::<Runtime>::reset_events();
3070 let result = call.dispatch(origin.into());
3071 crate::Pallet::<Runtime>::set_record_xcm(false);
3072 let local_xcm = crate::Pallet::<Runtime>::recorded_xcm()
3073 .map(|xcm| VersionedXcm::<()>::from(xcm).into_version(result_xcms_version))
3074 .transpose()
3075 .map_err(|()| {
3076 tracing::error!(
3077 target: "xcm::DryRunApi::dry_run_call",
3078 "Local xcm version conversion failed"
3079 );
3080
3081 XcmDryRunApiError::VersionedConversionFailed
3082 })?;
3083
3084 let forwarded_xcms =
3086 Self::convert_forwarded_xcms(result_xcms_version, Router::get_messages()).inspect_err(
3087 |error| {
3088 tracing::error!(
3089 target: "xcm::DryRunApi::dry_run_call",
3090 ?error, "Forwarded xcms version conversion failed with error"
3091 );
3092 },
3093 )?;
3094 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
3095 frame_system::Pallet::<Runtime>::read_events_no_consensus()
3096 .map(|record| record.event.clone())
3097 .collect();
3098 Ok(CallDryRunEffects {
3099 local_xcm: local_xcm.map(VersionedXcm::<()>::from),
3100 forwarded_xcms,
3101 emitted_events: events,
3102 execution_result: result,
3103 })
3104 }
3105
3106 pub fn dry_run_xcm<Router>(
3111 origin_location: VersionedLocation,
3112 xcm: VersionedXcm<<T as Config>::RuntimeCall>,
3113 ) -> Result<XcmDryRunEffects<<T as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3114 where
3115 Router: InspectMessageQueues,
3116 {
3117 let origin_location: Location = origin_location.try_into().map_err(|error| {
3118 tracing::error!(
3119 target: "xcm::DryRunApi::dry_run_xcm",
3120 ?error, "Location version conversion failed with error"
3121 );
3122 XcmDryRunApiError::VersionedConversionFailed
3123 })?;
3124 let xcm_version = xcm.identify_version();
3125 let xcm: Xcm<<T as Config>::RuntimeCall> = xcm.try_into().map_err(|error| {
3126 tracing::error!(
3127 target: "xcm::DryRunApi::dry_run_xcm",
3128 ?error, "Xcm version conversion failed with error"
3129 );
3130 XcmDryRunApiError::VersionedConversionFailed
3131 })?;
3132 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
3133
3134 Router::clear_messages();
3136 frame_system::Pallet::<T>::reset_events();
3137
3138 let result = <T as Config>::XcmExecutor::prepare_and_execute(
3139 origin_location,
3140 xcm,
3141 &mut hash,
3142 Weight::MAX, Weight::zero(),
3144 );
3145 let forwarded_xcms = Self::convert_forwarded_xcms(xcm_version, Router::get_messages())
3146 .inspect_err(|error| {
3147 tracing::error!(
3148 target: "xcm::DryRunApi::dry_run_xcm",
3149 ?error, "Forwarded xcms version conversion failed with error"
3150 );
3151 })?;
3152 let events: Vec<<T as frame_system::Config>::RuntimeEvent> =
3153 frame_system::Pallet::<T>::read_events_no_consensus()
3154 .map(|record| record.event.clone())
3155 .collect();
3156 Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
3157 }
3158
3159 fn convert_xcms(
3160 xcm_version: XcmVersion,
3161 xcms: Vec<VersionedXcm<()>>,
3162 ) -> Result<Vec<VersionedXcm<()>>, ()> {
3163 xcms.into_iter()
3164 .map(|xcm| xcm.into_version(xcm_version))
3165 .collect::<Result<Vec<_>, ()>>()
3166 }
3167
3168 fn convert_forwarded_xcms(
3169 xcm_version: XcmVersion,
3170 forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>,
3171 ) -> Result<Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, XcmDryRunApiError> {
3172 forwarded_xcms
3173 .into_iter()
3174 .map(|(dest, forwarded_xcms)| {
3175 let dest = dest.into_version(xcm_version)?;
3176 let forwarded_xcms = Self::convert_xcms(xcm_version, forwarded_xcms)?;
3177
3178 Ok((dest, forwarded_xcms))
3179 })
3180 .collect::<Result<Vec<_>, ()>>()
3181 .map_err(|()| {
3182 tracing::debug!(
3183 target: "xcm::pallet_xcm::convert_forwarded_xcms",
3184 "Failed to convert VersionedLocation to requested version",
3185 );
3186 XcmDryRunApiError::VersionedConversionFailed
3187 })
3188 }
3189
3190 pub fn query_acceptable_payment_assets(
3195 version: xcm::Version,
3196 asset_ids: Vec<AssetId>,
3197 ) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
3198 Ok(asset_ids
3199 .into_iter()
3200 .map(|asset_id| VersionedAssetId::from(asset_id))
3201 .filter_map(|asset_id| asset_id.into_version(version).ok())
3202 .collect())
3203 }
3204
3205 pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
3206 let message = Xcm::<()>::try_from(message.clone())
3207 .map_err(|e| {
3208 tracing::debug!(target: "xcm::pallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message");
3209 XcmPaymentApiError::VersionedConversionFailed
3210 })?;
3211
3212 T::Weigher::weight(&mut message.clone().into(), Weight::MAX).map_err(|error| {
3213 tracing::debug!(target: "xcm::pallet_xcm::query_xcm_weight", ?error, ?message, "Error when querying XCM weight");
3214 XcmPaymentApiError::WeightNotComputable
3215 })
3216 }
3217
3218 pub fn query_weight_to_asset_fee<Trader: xcm_executor::traits::WeightTrader>(
3235 weight: Weight,
3236 asset: VersionedAssetId,
3237 ) -> Result<u128, XcmPaymentApiError> {
3238 let asset: AssetId = asset.clone().try_into()
3239 .map_err(|e| {
3240 tracing::debug!(target: "xcm::pallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to convert versioned asset");
3241 XcmPaymentApiError::VersionedConversionFailed
3242 })?;
3243
3244 let max_amount = u128::MAX / 2;
3245 let max_payment: Asset = (asset.clone(), max_amount).into();
3246 let context = XcmContext::with_message_id(XcmHash::default());
3247
3248 let unspent = with_transaction(|| {
3251 let mut trader = Trader::new();
3252 let result = trader.buy_weight(weight, max_payment.into(), &context)
3253 .map_err(|e| {
3254 tracing::error!(target: "xcm::pallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to buy weight");
3255
3256 DispatchError::Other("Failed to buy weight")
3258 });
3259
3260 TransactionOutcome::Rollback(result)
3261 }).map_err(|error| {
3262 tracing::debug!(target: "xcm::pallet::query_weight_to_asset_fee", ?error, "Failed to execute transaction");
3263 XcmPaymentApiError::AssetNotFound
3264 })?;
3265
3266 let Some(unspent) = unspent.fungible.get(&asset) else {
3267 tracing::error!(target: "xcm::pallet::query_weight_to_asset_fee", ?asset, "The trader didn't return the needed fungible asset");
3268 return Err(XcmPaymentApiError::AssetNotFound);
3269 };
3270
3271 let paid = max_amount - unspent;
3272 Ok(paid)
3273 }
3274
3275 pub fn query_delivery_fees<AssetExchanger: xcm_executor::traits::AssetExchange>(
3282 destination: VersionedLocation,
3283 message: VersionedXcm<()>,
3284 versioned_asset_id: VersionedAssetId,
3285 ) -> Result<VersionedAssets, XcmPaymentApiError> {
3286 let result_version = destination.identify_version().max(message.identify_version());
3287
3288 let destination: Location = destination
3289 .clone()
3290 .try_into()
3291 .map_err(|e| {
3292 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination");
3293 XcmPaymentApiError::VersionedConversionFailed
3294 })?;
3295
3296 let message: Xcm<()> =
3297 message.clone().try_into().map_err(|e| {
3298 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message");
3299 XcmPaymentApiError::VersionedConversionFailed
3300 })?;
3301
3302 let (_, fees) = validate_send::<T::XcmRouter>(destination.clone(), message.clone()).map_err(|error| {
3303 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination");
3304 XcmPaymentApiError::Unroutable
3305 })?;
3306
3307 if fees.len() != 1 {
3309 return Err(XcmPaymentApiError::Unimplemented);
3310 }
3311
3312 let fee = fees.get(0).ok_or(XcmPaymentApiError::Unimplemented)?;
3313
3314 let asset_id = versioned_asset_id.clone().try_into().map_err(|()| {
3315 tracing::trace!(
3316 target: "xcm::xcm_runtime_apis::query_delivery_fees",
3317 "Failed to convert asset id: {versioned_asset_id:?}!"
3318 );
3319 XcmPaymentApiError::VersionedConversionFailed
3320 })?;
3321
3322 let assets_to_pay = if fee.id == asset_id {
3323 fees
3325 } else {
3326 AssetExchanger::quote_exchange_price(
3328 &fees.into(),
3329 &(asset_id, Fungible(1)).into(),
3330 true, )
3332 .ok_or(XcmPaymentApiError::AssetNotFound)?
3333 };
3334
3335 VersionedAssets::from(assets_to_pay).into_version(result_version).map_err(|e| {
3336 tracing::trace!(
3337 target: "xcm::pallet_xcm::query_delivery_fees",
3338 ?e,
3339 ?result_version,
3340 "Failed to convert fees into desired version"
3341 );
3342 XcmPaymentApiError::VersionedConversionFailed
3343 })
3344 }
3345
3346 pub fn is_trusted_reserve(
3349 asset: VersionedAsset,
3350 location: VersionedLocation,
3351 ) -> Result<bool, TrustedQueryApiError> {
3352 let location: Location = location.try_into().map_err(|e| {
3353 tracing::debug!(
3354 target: "xcm::pallet_xcm::is_trusted_reserve",
3355 ?e, "Failed to convert versioned location",
3356 );
3357 TrustedQueryApiError::VersionedLocationConversionFailed
3358 })?;
3359
3360 let a: Asset = asset.try_into().map_err(|e| {
3361 tracing::debug!(
3362 target: "xcm::pallet_xcm::is_trusted_reserve",
3363 ?e, "Failed to convert versioned asset",
3364 );
3365 TrustedQueryApiError::VersionedAssetConversionFailed
3366 })?;
3367
3368 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&a, &location))
3369 }
3370
3371 pub fn is_trusted_teleporter(
3373 asset: VersionedAsset,
3374 location: VersionedLocation,
3375 ) -> Result<bool, TrustedQueryApiError> {
3376 let location: Location = location.try_into().map_err(|e| {
3377 tracing::debug!(
3378 target: "xcm::pallet_xcm::is_trusted_teleporter",
3379 ?e, "Failed to convert versioned location",
3380 );
3381 TrustedQueryApiError::VersionedLocationConversionFailed
3382 })?;
3383 let a: Asset = asset.try_into().map_err(|e| {
3384 tracing::debug!(
3385 target: "xcm::pallet_xcm::is_trusted_teleporter",
3386 ?e, "Failed to convert versioned asset",
3387 );
3388 TrustedQueryApiError::VersionedAssetConversionFailed
3389 })?;
3390 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&a, &location))
3391 }
3392
3393 pub fn authorized_aliasers(
3395 target: VersionedLocation,
3396 ) -> Result<Vec<OriginAliaser>, AuthorizedAliasersApiError> {
3397 let desired_version = target.identify_version();
3398 let target: VersionedLocation = target.into_version(XCM_VERSION).map_err(|e| {
3400 tracing::debug!(
3401 target: "xcm::pallet_xcm::authorized_aliasers",
3402 ?e, "Failed to convert versioned location",
3403 );
3404 AuthorizedAliasersApiError::LocationVersionConversionFailed
3405 })?;
3406 Ok(AuthorizedAliases::<T>::get(&target)
3407 .map(|authorized| {
3408 authorized
3409 .aliasers
3410 .into_iter()
3411 .filter_map(|aliaser| {
3412 let OriginAliaser { location, expiry } = aliaser;
3413 location
3414 .into_version(desired_version)
3415 .map(|location| OriginAliaser { location, expiry })
3416 .ok()
3417 })
3418 .collect()
3419 })
3420 .unwrap_or_default())
3421 }
3422
3423 pub fn is_authorized_alias(
3428 origin: VersionedLocation,
3429 target: VersionedLocation,
3430 ) -> Result<bool, AuthorizedAliasersApiError> {
3431 let desired_version = target.identify_version();
3432 let origin = origin.into_version(desired_version).map_err(|e| {
3433 tracing::debug!(
3434 target: "xcm::pallet_xcm::is_authorized_alias",
3435 ?e, "mismatching origin and target versions",
3436 );
3437 AuthorizedAliasersApiError::LocationVersionConversionFailed
3438 })?;
3439 Ok(Self::authorized_aliasers(target)?.into_iter().any(|aliaser| {
3440 aliaser.location == origin &&
3443 aliaser
3444 .expiry
3445 .map(|expiry| {
3446 frame_system::Pallet::<T>::current_block_number().saturated_into::<u64>() <
3447 expiry
3448 })
3449 .unwrap_or(true)
3450 }))
3451 }
3452
3453 fn do_new_query(
3455 responder: impl Into<Location>,
3456 maybe_notify: Option<(u8, u8)>,
3457 timeout: BlockNumberFor<T>,
3458 match_querier: impl Into<Location>,
3459 ) -> u64 {
3460 QueryCounter::<T>::mutate(|q| {
3461 let r = *q;
3462 q.saturating_inc();
3463 Queries::<T>::insert(
3464 r,
3465 QueryStatus::Pending {
3466 responder: responder.into().into(),
3467 maybe_match_querier: Some(match_querier.into().into()),
3468 maybe_notify,
3469 timeout,
3470 },
3471 );
3472 r
3473 })
3474 }
3475
3476 pub fn report_outcome_notify(
3499 message: &mut Xcm<()>,
3500 responder: impl Into<Location>,
3501 notify: impl Into<<T as Config>::RuntimeCall>,
3502 timeout: BlockNumberFor<T>,
3503 ) -> Result<(), XcmError> {
3504 let responder = responder.into();
3505 let destination = T::UniversalLocation::get().invert_target(&responder).map_err(|()| {
3506 tracing::debug!(
3507 target: "xcm::pallet_xcm::report_outcome_notify",
3508 "Failed to invert responder location to universal location",
3509 );
3510 XcmError::LocationNotInvertible
3511 })?;
3512 let notify: <T as Config>::RuntimeCall = notify.into();
3513 let max_weight = notify.get_dispatch_info().call_weight;
3514 let query_id = Self::new_notify_query(responder, notify, timeout, Here);
3515 let response_info = QueryResponseInfo { destination, query_id, max_weight };
3516 let report_error = Xcm(vec![ReportError(response_info)]);
3517 message.0.insert(0, SetAppendix(report_error));
3518 Ok(())
3519 }
3520
3521 pub fn new_notify_query(
3524 responder: impl Into<Location>,
3525 notify: impl Into<<T as Config>::RuntimeCall>,
3526 timeout: BlockNumberFor<T>,
3527 match_querier: impl Into<Location>,
3528 ) -> u64 {
3529 let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
3530 "decode input is output of Call encode; Call guaranteed to have two enums; qed",
3531 );
3532 Self::do_new_query(responder, Some(notify), timeout, match_querier)
3533 }
3534
3535 fn note_unknown_version(dest: &Location) {
3538 tracing::trace!(
3539 target: "xcm::pallet_xcm::note_unknown_version",
3540 ?dest, "XCM version is unknown for destination"
3541 );
3542 let versioned_dest = VersionedLocation::from(dest.clone());
3543 VersionDiscoveryQueue::<T>::mutate(|q| {
3544 if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
3545 q[index].1.saturating_inc();
3547 } else {
3548 let _ = q.try_push((versioned_dest, 1));
3549 }
3550 });
3551 }
3552
3553 fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
3559 T::XcmExecutor::charge_fees(location.clone(), assets.clone()).map_err(|error| {
3560 tracing::debug!(
3561 target: "xcm::pallet_xcm::charge_fees", ?error,
3562 "Failed to charge fees for location with assets",
3563 );
3564 Error::<T>::FeesNotMet
3565 })?;
3566 Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
3567 Ok(())
3568 }
3569
3570 #[cfg(any(feature = "try-runtime", test))]
3580 pub fn do_try_state() -> Result<(), TryRuntimeError> {
3581 use migration::data::NeedsMigration;
3582
3583 let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
3587 {
3588 XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
3589 } else {
3590 XCM_VERSION.saturating_sub(1)
3591 };
3592
3593 ensure!(
3595 !Queries::<T>::iter_values()
3596 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3597 TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
3598 );
3599
3600 ensure!(
3602 !LockedFungibles::<T>::iter_values()
3603 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3604 TryRuntimeError::Other(
3605 "`LockedFungibles` data should be migrated to the higher xcm version!"
3606 )
3607 );
3608
3609 ensure!(
3611 !RemoteLockedFungibles::<T>::iter()
3612 .any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version) ||
3613 data.needs_migration(minimal_allowed_xcm_version)),
3614 TryRuntimeError::Other(
3615 "`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
3616 )
3617 );
3618
3619 if CurrentMigration::<T>::exists() {
3622 return Ok(())
3623 }
3624
3625 for v in 0..XCM_VERSION {
3627 ensure!(
3628 SupportedVersion::<T>::iter_prefix(v).next().is_none(),
3629 TryRuntimeError::Other(
3630 "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
3631 )
3632 );
3633 ensure!(
3634 VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
3635 TryRuntimeError::Other(
3636 "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
3637 )
3638 );
3639 ensure!(
3640 VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
3641 TryRuntimeError::Other(
3642 "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
3643 )
3644 );
3645 }
3646
3647 Ok(())
3648 }
3649}
3650
3651pub struct LockTicket<T: Config> {
3652 sovereign_account: T::AccountId,
3653 amount: BalanceOf<T>,
3654 unlocker: Location,
3655 item_index: Option<usize>,
3656}
3657
3658impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
3659 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3660 use xcm_executor::traits::LockError::UnexpectedState;
3661 let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
3662 match self.item_index {
3663 Some(index) => {
3664 ensure!(locks.len() > index, UnexpectedState);
3665 ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
3666 locks[index].0 = locks[index].0.max(self.amount);
3667 },
3668 None => {
3669 locks.try_push((self.amount, self.unlocker.into())).map_err(
3670 |(balance, location)| {
3671 tracing::debug!(
3672 target: "xcm::pallet_xcm::enact", ?balance, ?location,
3673 "Failed to lock fungibles",
3674 );
3675 UnexpectedState
3676 },
3677 )?;
3678 },
3679 }
3680 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3681 T::Currency::extend_lock(
3682 *b"py/xcmlk",
3683 &self.sovereign_account,
3684 self.amount,
3685 WithdrawReasons::all(),
3686 );
3687 Ok(())
3688 }
3689}
3690
3691pub struct UnlockTicket<T: Config> {
3692 sovereign_account: T::AccountId,
3693 amount: BalanceOf<T>,
3694 unlocker: Location,
3695}
3696
3697impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
3698 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3699 use xcm_executor::traits::LockError::UnexpectedState;
3700 let mut locks =
3701 LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
3702 let mut maybe_remove_index = None;
3703 let mut locked = BalanceOf::<T>::zero();
3704 let mut found = false;
3705 for (i, x) in locks.iter_mut().enumerate() {
3708 if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
3709 x.0 = x.0.saturating_sub(self.amount);
3710 if x.0.is_zero() {
3711 maybe_remove_index = Some(i);
3712 }
3713 found = true;
3714 }
3715 locked = locked.max(x.0);
3716 }
3717 ensure!(found, UnexpectedState);
3718 if let Some(remove_index) = maybe_remove_index {
3719 locks.swap_remove(remove_index);
3720 }
3721 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3722 let reasons = WithdrawReasons::all();
3723 T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
3724 Ok(())
3725 }
3726}
3727
3728pub struct ReduceTicket<T: Config> {
3729 key: (u32, T::AccountId, VersionedAssetId),
3730 amount: u128,
3731 locker: VersionedLocation,
3732 owner: VersionedLocation,
3733}
3734
3735impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
3736 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3737 use xcm_executor::traits::LockError::UnexpectedState;
3738 let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
3739 ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
3740 let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
3741 ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
3742 if new_amount == 0 {
3743 RemoteLockedFungibles::<T>::remove(&self.key);
3744 } else {
3745 record.amount = new_amount;
3746 RemoteLockedFungibles::<T>::insert(&self.key, &record);
3747 }
3748 Ok(())
3749 }
3750}
3751
3752impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
3753 type LockTicket = LockTicket<T>;
3754 type UnlockTicket = UnlockTicket<T>;
3755 type ReduceTicket = ReduceTicket<T>;
3756
3757 fn prepare_lock(
3758 unlocker: Location,
3759 asset: Asset,
3760 owner: Location,
3761 ) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
3762 use xcm_executor::traits::LockError::*;
3763 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3764 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3765 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3766 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3767 let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
3768 ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
3769 Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
3770 }
3771
3772 fn prepare_unlock(
3773 unlocker: Location,
3774 asset: Asset,
3775 owner: Location,
3776 ) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
3777 use xcm_executor::traits::LockError::*;
3778 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3779 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3780 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3781 let item_index =
3782 locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
3783 ensure!(locks[item_index].0 >= amount, NotLocked);
3784 Ok(UnlockTicket { sovereign_account, amount, unlocker })
3785 }
3786
3787 fn note_unlockable(
3788 locker: Location,
3789 asset: Asset,
3790 mut owner: Location,
3791 ) -> Result<(), xcm_executor::traits::LockError> {
3792 use xcm_executor::traits::LockError::*;
3793 ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
3794 let amount = match asset.fun {
3795 Fungible(a) => a,
3796 NonFungible(_) => return Err(Unimplemented),
3797 };
3798 owner.remove_network_id();
3799 let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3800 let locker = locker.into();
3801 let owner = owner.into();
3802 let id: VersionedAssetId = asset.id.into();
3803 let key = (XCM_VERSION, account, id);
3804 let mut record =
3805 RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
3806 if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
3807 ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
3809 record.consumers = old.consumers;
3810 record.amount = record.amount.max(old.amount);
3811 }
3812 RemoteLockedFungibles::<T>::insert(&key, record);
3813 Ok(())
3814 }
3815
3816 fn prepare_reduce_unlockable(
3817 locker: Location,
3818 asset: Asset,
3819 mut owner: Location,
3820 ) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
3821 use xcm_executor::traits::LockError::*;
3822 let amount = match asset.fun {
3823 Fungible(a) => a,
3824 NonFungible(_) => return Err(Unimplemented),
3825 };
3826 owner.remove_network_id();
3827 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3828 let locker = locker.into();
3829 let owner = owner.into();
3830 let id: VersionedAssetId = asset.id.into();
3831 let key = (XCM_VERSION, sovereign_account, id);
3832
3833 let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
3834 ensure!(locker == record.locker && owner == record.owner, WouldClobber);
3836 ensure!(record.amount >= amount, NotEnoughLocked);
3837 ensure!(
3838 record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
3839 InUse
3840 );
3841 Ok(ReduceTicket { key, amount, locker, owner })
3842 }
3843}
3844
3845impl<T: Config> WrapVersion for Pallet<T> {
3846 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
3847 dest: &Location,
3848 xcm: impl Into<VersionedXcm<RuntimeCall>>,
3849 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
3850 Self::get_version_for(dest)
3851 .or_else(|| {
3852 Self::note_unknown_version(dest);
3853 SafeXcmVersion::<T>::get()
3854 })
3855 .ok_or_else(|| {
3856 tracing::trace!(
3857 target: "xcm::pallet_xcm::wrap_version",
3858 ?dest, "Could not determine a version to wrap XCM for destination",
3859 );
3860 ()
3861 })
3862 .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
3863 }
3864}
3865
3866impl<T: Config> GetVersion for Pallet<T> {
3867 fn get_version_for(dest: &Location) -> Option<XcmVersion> {
3868 SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
3869 }
3870}
3871
3872impl<T: Config> VersionChangeNotifier for Pallet<T> {
3873 fn start(
3882 dest: &Location,
3883 query_id: QueryId,
3884 max_weight: Weight,
3885 _context: &XcmContext,
3886 ) -> XcmResult {
3887 let versioned_dest = LatestVersionedLocation(dest);
3888 let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
3889 ensure!(!already, XcmError::InvalidLocation);
3890
3891 let xcm_version = T::AdvertisedXcmVersion::get();
3892 let response = Response::Version(xcm_version);
3893 let instruction = QueryResponse { query_id, response, max_weight, querier: None };
3894 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3895 Self::deposit_event(Event::<T>::VersionNotifyStarted {
3896 destination: dest.clone(),
3897 cost,
3898 message_id,
3899 });
3900
3901 let value = (query_id, max_weight, xcm_version);
3902 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
3903 Ok(())
3904 }
3905
3906 fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
3909 VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
3910 Ok(())
3911 }
3912
3913 fn is_subscribed(dest: &Location) -> bool {
3915 let versioned_dest = LatestVersionedLocation(dest);
3916 VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
3917 }
3918}
3919
3920impl<T: Config> DropAssets for Pallet<T> {
3921 fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
3922 if assets.is_empty() {
3923 return Weight::zero()
3924 }
3925 let versioned = VersionedAssets::from(Assets::from(assets));
3926 let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
3927 AssetTraps::<T>::mutate(hash, |n| *n += 1);
3928 Self::deposit_event(Event::AssetsTrapped {
3929 hash,
3930 origin: origin.clone(),
3931 assets: versioned,
3932 });
3933 Weight::zero()
3935 }
3936}
3937
3938impl<T: Config> ClaimAssets for Pallet<T> {
3939 fn claim_assets(
3940 origin: &Location,
3941 ticket: &Location,
3942 assets: &Assets,
3943 _context: &XcmContext,
3944 ) -> bool {
3945 let mut versioned = VersionedAssets::from(assets.clone());
3946 match ticket.unpack() {
3947 (0, [GeneralIndex(i)]) =>
3948 versioned = match versioned.into_version(*i as u32) {
3949 Ok(v) => v,
3950 Err(()) => return false,
3951 },
3952 (0, []) => (),
3953 _ => return false,
3954 };
3955 let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
3956 match AssetTraps::<T>::get(hash) {
3957 0 => return false,
3958 1 => AssetTraps::<T>::remove(hash),
3959 n => AssetTraps::<T>::insert(hash, n - 1),
3960 }
3961 Self::deposit_event(Event::AssetsClaimed {
3962 hash,
3963 origin: origin.clone(),
3964 assets: versioned,
3965 });
3966 return true
3967 }
3968}
3969
3970impl<T: Config> OnResponse for Pallet<T> {
3971 fn expecting_response(
3972 origin: &Location,
3973 query_id: QueryId,
3974 querier: Option<&Location>,
3975 ) -> bool {
3976 match Queries::<T>::get(query_id) {
3977 Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
3978 Location::try_from(responder).map_or(false, |r| origin == &r) &&
3979 maybe_match_querier.map_or(true, |match_querier| {
3980 Location::try_from(match_querier).map_or(false, |match_querier| {
3981 querier.map_or(false, |q| q == &match_querier)
3982 })
3983 }),
3984 Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
3985 Location::try_from(r).map_or(false, |r| origin == &r),
3986 _ => false,
3987 }
3988 }
3989
3990 fn on_response(
3991 origin: &Location,
3992 query_id: QueryId,
3993 querier: Option<&Location>,
3994 response: Response,
3995 max_weight: Weight,
3996 _context: &XcmContext,
3997 ) -> Weight {
3998 let origin = origin.clone();
3999 match (response, Queries::<T>::get(query_id)) {
4000 (
4001 Response::Version(v),
4002 Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
4003 ) => {
4004 let origin: Location = match expected_origin.try_into() {
4005 Ok(o) if o == origin => o,
4006 Ok(o) => {
4007 Self::deposit_event(Event::InvalidResponder {
4008 origin: origin.clone(),
4009 query_id,
4010 expected_location: Some(o),
4011 });
4012 return Weight::zero()
4013 },
4014 _ => {
4015 Self::deposit_event(Event::InvalidResponder {
4016 origin: origin.clone(),
4017 query_id,
4018 expected_location: None,
4019 });
4020 return Weight::zero()
4022 },
4023 };
4024 if !is_active {
4026 Queries::<T>::insert(
4027 query_id,
4028 QueryStatus::VersionNotifier {
4029 origin: origin.clone().into(),
4030 is_active: true,
4031 },
4032 );
4033 }
4034 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
4036 Self::deposit_event(Event::SupportedVersionChanged {
4037 location: origin,
4038 version: v,
4039 });
4040 Weight::zero()
4041 },
4042 (
4043 response,
4044 Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
4045 ) => {
4046 if let Some(match_querier) = maybe_match_querier {
4047 let match_querier = match Location::try_from(match_querier) {
4048 Ok(mq) => mq,
4049 Err(_) => {
4050 Self::deposit_event(Event::InvalidQuerierVersion {
4051 origin: origin.clone(),
4052 query_id,
4053 });
4054 return Weight::zero()
4055 },
4056 };
4057 if querier.map_or(true, |q| q != &match_querier) {
4058 Self::deposit_event(Event::InvalidQuerier {
4059 origin: origin.clone(),
4060 query_id,
4061 expected_querier: match_querier,
4062 maybe_actual_querier: querier.cloned(),
4063 });
4064 return Weight::zero()
4065 }
4066 }
4067 let responder = match Location::try_from(responder) {
4068 Ok(r) => r,
4069 Err(_) => {
4070 Self::deposit_event(Event::InvalidResponderVersion {
4071 origin: origin.clone(),
4072 query_id,
4073 });
4074 return Weight::zero()
4075 },
4076 };
4077 if origin != responder {
4078 Self::deposit_event(Event::InvalidResponder {
4079 origin: origin.clone(),
4080 query_id,
4081 expected_location: Some(responder),
4082 });
4083 return Weight::zero()
4084 }
4085 match maybe_notify {
4086 Some((pallet_index, call_index)) => {
4087 let bare = (pallet_index, call_index, query_id, response);
4091 if let Ok(call) = bare.using_encoded(|mut bytes| {
4092 <T as Config>::RuntimeCall::decode(&mut bytes)
4093 }) {
4094 Queries::<T>::remove(query_id);
4095 let weight = call.get_dispatch_info().call_weight;
4096 if weight.any_gt(max_weight) {
4097 let e = Event::NotifyOverweight {
4098 query_id,
4099 pallet_index,
4100 call_index,
4101 actual_weight: weight,
4102 max_budgeted_weight: max_weight,
4103 };
4104 Self::deposit_event(e);
4105 return Weight::zero()
4106 }
4107 let dispatch_origin = Origin::Response(origin.clone()).into();
4108 match call.dispatch(dispatch_origin) {
4109 Ok(post_info) => {
4110 let e = Event::Notified { query_id, pallet_index, call_index };
4111 Self::deposit_event(e);
4112 post_info.actual_weight
4113 },
4114 Err(error_and_info) => {
4115 let e = Event::NotifyDispatchError {
4116 query_id,
4117 pallet_index,
4118 call_index,
4119 };
4120 Self::deposit_event(e);
4121 error_and_info.post_info.actual_weight
4124 },
4125 }
4126 .unwrap_or(weight)
4127 } else {
4128 let e =
4129 Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
4130 Self::deposit_event(e);
4131 Weight::zero()
4132 }
4133 },
4134 None => {
4135 let e = Event::ResponseReady { query_id, response: response.clone() };
4136 Self::deposit_event(e);
4137 let at = frame_system::Pallet::<T>::current_block_number();
4138 let response = response.into();
4139 Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
4140 Weight::zero()
4141 },
4142 }
4143 },
4144 _ => {
4145 let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
4146 Self::deposit_event(e);
4147 Weight::zero()
4148 },
4149 }
4150 }
4151}
4152
4153impl<T: Config> CheckSuspension for Pallet<T> {
4154 fn is_suspended<Call>(
4155 _origin: &Location,
4156 _instructions: &mut [Instruction<Call>],
4157 _max_weight: Weight,
4158 _properties: &mut Properties,
4159 ) -> bool {
4160 XcmExecutionSuspended::<T>::get()
4161 }
4162}
4163
4164impl<T: Config> RecordXcm for Pallet<T> {
4165 fn should_record() -> bool {
4166 ShouldRecordXcm::<T>::get()
4167 }
4168
4169 fn set_record_xcm(enabled: bool) {
4170 ShouldRecordXcm::<T>::put(enabled);
4171 }
4172
4173 fn recorded_xcm() -> Option<Xcm<()>> {
4174 RecordedXcm::<T>::get()
4175 }
4176
4177 fn record(xcm: Xcm<()>) {
4178 RecordedXcm::<T>::put(xcm);
4179 }
4180}
4181
4182pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4186where
4187 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4188{
4189 match o.into() {
4190 Ok(Origin::Xcm(location)) => Ok(location),
4191 _ => Err(BadOrigin),
4192 }
4193}
4194
4195pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4199where
4200 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4201{
4202 match o.into() {
4203 Ok(Origin::Response(location)) => Ok(location),
4204 _ => Err(BadOrigin),
4205 }
4206}
4207
4208pub struct AuthorizedAliasers<T>(PhantomData<T>);
4214impl<L: Into<VersionedLocation> + Clone, T: Config> ContainsPair<L, L> for AuthorizedAliasers<T> {
4215 fn contains(origin: &L, target: &L) -> bool {
4216 let origin: VersionedLocation = origin.clone().into();
4217 let target: VersionedLocation = target.clone().into();
4218 tracing::trace!(target: "xcm::pallet_xcm::AuthorizedAliasers::contains", ?origin, ?target);
4219 Pallet::<T>::is_authorized_alias(origin, target).unwrap_or(false)
4222 }
4223}
4224
4225pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4230impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
4231 for IsMajorityOfBody<Prefix, Body>
4232{
4233 fn contains(l: &Location) -> bool {
4234 let maybe_suffix = l.match_and_split(&Prefix::get());
4235 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
4236 }
4237}
4238
4239pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4243impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
4244 fn contains(l: &Location) -> bool {
4245 let maybe_suffix = l.match_and_split(&Prefix::get());
4246 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
4247 }
4248}
4249
4250pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
4253impl<
4254 O: OriginTrait + From<Origin>,
4255 F: Contains<L>,
4256 L: TryFrom<Location> + TryInto<Location> + Clone,
4257 > EnsureOrigin<O> for EnsureXcm<F, L>
4258where
4259 for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4260{
4261 type Success = L;
4262
4263 fn try_origin(outer: O) -> Result<Self::Success, O> {
4264 match outer.caller().try_into() {
4265 Ok(Origin::Xcm(ref location)) =>
4266 if let Ok(location) = location.clone().try_into() {
4267 if F::contains(&location) {
4268 return Ok(location);
4269 }
4270 },
4271 _ => (),
4272 }
4273
4274 Err(outer)
4275 }
4276
4277 #[cfg(feature = "runtime-benchmarks")]
4278 fn try_successful_origin() -> Result<O, ()> {
4279 Ok(O::from(Origin::Xcm(Here.into())))
4280 }
4281}
4282
4283pub struct EnsureResponse<F>(PhantomData<F>);
4286impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
4287where
4288 for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4289{
4290 type Success = Location;
4291
4292 fn try_origin(outer: O) -> Result<Self::Success, O> {
4293 match outer.caller().try_into() {
4294 Ok(Origin::Response(responder)) => return Ok(responder.clone()),
4295 _ => (),
4296 }
4297
4298 Err(outer)
4299 }
4300
4301 #[cfg(feature = "runtime-benchmarks")]
4302 fn try_successful_origin() -> Result<O, ()> {
4303 Ok(O::from(Origin::Response(Here.into())))
4304 }
4305}
4306
4307pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
4310impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
4311 for XcmPassthrough<RuntimeOrigin>
4312{
4313 fn convert_origin(
4314 origin: impl Into<Location>,
4315 kind: OriginKind,
4316 ) -> Result<RuntimeOrigin, Location> {
4317 let origin = origin.into();
4318 match kind {
4319 OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
4320 _ => Err(origin),
4321 }
4322 }
4323}