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 Debug, Either, 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, Eq, Clone, Encode, Decode, DecodeWithMemTracking, Debug, TypeInfo, MaxEncodedLen,
640 )]
641 pub enum Origin {
642 Xcm(Location),
644 Response(Location),
646 }
647 impl From<Location> for Origin {
648 fn from(location: Location) -> Origin {
649 Origin::Xcm(location)
650 }
651 }
652
653 #[pallet::composite_enum]
655 pub enum HoldReason {
656 AuthorizeAlias,
658 }
659
660 #[pallet::error]
661 pub enum Error<T> {
662 Unreachable,
665 SendFailure,
668 Filtered,
670 UnweighableMessage,
672 DestinationNotInvertible,
674 Empty,
676 CannotReanchor,
678 TooManyAssets,
680 InvalidOrigin,
682 BadVersion,
684 BadLocation,
687 NoSubscription,
689 AlreadySubscribed,
691 CannotCheckOutTeleport,
693 LowBalance,
695 TooManyLocks,
697 AccountNotSovereign,
699 FeesNotMet,
701 LockNotFound,
703 InUse,
705 #[codec(index = 21)]
707 InvalidAssetUnknownReserve,
708 #[codec(index = 22)]
710 InvalidAssetUnsupportedReserve,
711 #[codec(index = 23)]
713 TooManyReserves,
714 #[deprecated(since = "20.0.0", note = "Use `LocalExecutionIncompleteWithError` instead")]
716 #[codec(index = 24)]
717 LocalExecutionIncomplete,
718 #[codec(index = 25)]
720 TooManyAuthorizedAliases,
721 #[codec(index = 26)]
723 ExpiresInPast,
724 #[codec(index = 27)]
726 AliasNotFound,
727 #[codec(index = 28)]
730 LocalExecutionIncompleteWithError { index: InstructionIndex, error: ExecutionError },
731 }
732
733 impl<T: Config> From<SendError> for Error<T> {
734 fn from(e: SendError) -> Self {
735 match e {
736 SendError::Fees => Error::<T>::FeesNotMet,
737 SendError::NotApplicable => Error::<T>::Unreachable,
738 _ => Error::<T>::SendFailure,
739 }
740 }
741 }
742
743 impl<T: Config> From<AssetTransferError> for Error<T> {
744 fn from(e: AssetTransferError) -> Self {
745 match e {
746 AssetTransferError::UnknownReserve => Error::<T>::InvalidAssetUnknownReserve,
747 }
748 }
749 }
750
751 #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)]
753 pub enum QueryStatus<BlockNumber> {
754 Pending {
756 responder: VersionedLocation,
759 maybe_match_querier: Option<VersionedLocation>,
762 maybe_notify: Option<(u8, u8)>,
763 timeout: BlockNumber,
764 },
765 VersionNotifier { origin: VersionedLocation, is_active: bool },
767 Ready { response: VersionedResponse, at: BlockNumber },
769 }
770
771 #[derive(Copy, Clone)]
772 pub(crate) struct LatestVersionedLocation<'a>(pub(crate) &'a Location);
773 impl<'a> EncodeLike<VersionedLocation> for LatestVersionedLocation<'a> {}
774 impl<'a> Encode for LatestVersionedLocation<'a> {
775 fn encode(&self) -> Vec<u8> {
776 let mut r = VersionedLocation::from(Location::default()).encode();
777 r.truncate(1);
778 self.0.using_encoded(|d| r.extend_from_slice(d));
779 r
780 }
781 }
782
783 #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
784 pub enum VersionMigrationStage {
785 MigrateSupportedVersion,
786 MigrateVersionNotifiers,
787 NotifyCurrentTargets(Option<Vec<u8>>),
788 MigrateAndNotifyOldTargets,
789 }
790
791 impl Default for VersionMigrationStage {
792 fn default() -> Self {
793 Self::MigrateSupportedVersion
794 }
795 }
796
797 #[pallet::storage]
799 pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;
800
801 #[pallet::storage]
803 pub(super) type Queries<T: Config> =
804 StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<BlockNumberFor<T>>, OptionQuery>;
805
806 #[pallet::storage]
811 pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;
812
813 #[pallet::storage]
816 #[pallet::whitelist_storage]
817 pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
818
819 #[pallet::storage]
821 pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
822 _,
823 Twox64Concat,
824 XcmVersion,
825 Blake2_128Concat,
826 VersionedLocation,
827 XcmVersion,
828 OptionQuery,
829 >;
830
831 #[pallet::storage]
833 pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
834 _,
835 Twox64Concat,
836 XcmVersion,
837 Blake2_128Concat,
838 VersionedLocation,
839 QueryId,
840 OptionQuery,
841 >;
842
843 #[pallet::storage]
846 pub(super) type VersionNotifyTargets<T: Config> = StorageDoubleMap<
847 _,
848 Twox64Concat,
849 XcmVersion,
850 Blake2_128Concat,
851 VersionedLocation,
852 (QueryId, Weight, XcmVersion),
853 OptionQuery,
854 >;
855
856 pub struct VersionDiscoveryQueueSize<T>(PhantomData<T>);
857 impl<T: Config> Get<u32> for VersionDiscoveryQueueSize<T> {
858 fn get() -> u32 {
859 T::VERSION_DISCOVERY_QUEUE_SIZE
860 }
861 }
862
863 #[pallet::storage]
867 #[pallet::whitelist_storage]
868 pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
869 _,
870 BoundedVec<(VersionedLocation, u32), VersionDiscoveryQueueSize<T>>,
871 ValueQuery,
872 >;
873
874 #[pallet::storage]
876 pub(super) type CurrentMigration<T: Config> =
877 StorageValue<_, VersionMigrationStage, OptionQuery>;
878
879 #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
880 #[scale_info(skip_type_params(MaxConsumers))]
881 pub struct RemoteLockedFungibleRecord<ConsumerIdentifier, MaxConsumers: Get<u32>> {
882 pub amount: u128,
884 pub owner: VersionedLocation,
886 pub locker: VersionedLocation,
888 pub consumers: BoundedVec<(ConsumerIdentifier, u128), MaxConsumers>,
892 }
893
894 impl<LockId, MaxConsumers: Get<u32>> RemoteLockedFungibleRecord<LockId, MaxConsumers> {
895 pub fn amount_held(&self) -> Option<u128> {
898 self.consumers.iter().max_by(|x, y| x.1.cmp(&y.1)).map(|max| max.1)
899 }
900 }
901
902 #[pallet::storage]
904 pub(super) type RemoteLockedFungibles<T: Config> = StorageNMap<
905 _,
906 (
907 NMapKey<Twox64Concat, XcmVersion>,
908 NMapKey<Blake2_128Concat, T::AccountId>,
909 NMapKey<Blake2_128Concat, VersionedAssetId>,
910 ),
911 RemoteLockedFungibleRecord<T::RemoteLockConsumerIdentifier, T::MaxRemoteLockConsumers>,
912 OptionQuery,
913 >;
914
915 #[pallet::storage]
917 pub(super) type LockedFungibles<T: Config> = StorageMap<
918 _,
919 Blake2_128Concat,
920 T::AccountId,
921 BoundedVec<(BalanceOf<T>, VersionedLocation), T::MaxLockers>,
922 OptionQuery,
923 >;
924
925 #[pallet::storage]
927 pub(super) type XcmExecutionSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
928
929 #[pallet::storage]
937 pub(crate) type ShouldRecordXcm<T: Config> = StorageValue<_, bool, ValueQuery>;
938
939 #[pallet::storage]
946 pub(crate) type RecordedXcm<T: Config> = StorageValue<_, Xcm<()>>;
947
948 #[pallet::storage]
952 pub(super) type AuthorizedAliases<T: Config> = StorageMap<
953 _,
954 Blake2_128Concat,
955 VersionedLocation,
956 AuthorizedAliasesEntry<TicketOf<T>, MaxAuthorizedAliases>,
957 OptionQuery,
958 >;
959
960 #[pallet::genesis_config]
961 pub struct GenesisConfig<T: Config> {
962 #[serde(skip)]
963 pub _config: core::marker::PhantomData<T>,
964 pub safe_xcm_version: Option<XcmVersion>,
966 pub supported_version: Vec<(Location, XcmVersion)>,
968 }
969
970 impl<T: Config> Default for GenesisConfig<T> {
971 fn default() -> Self {
972 Self {
973 _config: Default::default(),
974 safe_xcm_version: Some(XCM_VERSION),
975 supported_version: Vec::new(),
976 }
977 }
978 }
979
980 #[pallet::genesis_build]
981 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
982 fn build(&self) {
983 SafeXcmVersion::<T>::set(self.safe_xcm_version);
984 self.supported_version.iter().for_each(|(location, version)| {
986 SupportedVersion::<T>::insert(
987 XCM_VERSION,
988 LatestVersionedLocation(location),
989 version,
990 );
991 });
992 }
993 }
994
995 #[pallet::hooks]
996 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
997 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
998 let mut weight_used = Weight::zero();
999 if let Some(migration) = CurrentMigration::<T>::get() {
1000 let max_weight = T::BlockWeights::get().max_block / 10;
1002 let (w, maybe_migration) = Self::lazy_migration(migration, max_weight);
1003 if maybe_migration.is_none() {
1004 Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION });
1005 }
1006 CurrentMigration::<T>::set(maybe_migration);
1007 weight_used.saturating_accrue(w);
1008 }
1009
1010 let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
1013 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
1015 q.sort_by_key(|i| i.1);
1016 while let Some((versioned_dest, _)) = q.pop() {
1017 if let Ok(dest) = Location::try_from(versioned_dest) {
1018 if Self::request_version_notify(dest).is_ok() {
1019 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
1021 break
1022 }
1023 }
1024 }
1025 if let Ok(q) = BoundedVec::try_from(q) {
1028 VersionDiscoveryQueue::<T>::put(q);
1029 }
1030 weight_used
1031 }
1032
1033 #[cfg(feature = "try-runtime")]
1034 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1035 Self::do_try_state()
1036 }
1037 }
1038
1039 pub mod migrations {
1040 use super::*;
1041 use frame_support::traits::{PalletInfoAccess, StorageVersion};
1042
1043 #[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)]
1044 enum QueryStatusV0<BlockNumber> {
1045 Pending {
1046 responder: VersionedLocation,
1047 maybe_notify: Option<(u8, u8)>,
1048 timeout: BlockNumber,
1049 },
1050 VersionNotifier {
1051 origin: VersionedLocation,
1052 is_active: bool,
1053 },
1054 Ready {
1055 response: VersionedResponse,
1056 at: BlockNumber,
1057 },
1058 }
1059 impl<B> From<QueryStatusV0<B>> for QueryStatus<B> {
1060 fn from(old: QueryStatusV0<B>) -> Self {
1061 use QueryStatusV0::*;
1062 match old {
1063 Pending { responder, maybe_notify, timeout } => QueryStatus::Pending {
1064 responder,
1065 maybe_notify,
1066 timeout,
1067 maybe_match_querier: Some(Location::here().into()),
1068 },
1069 VersionNotifier { origin, is_active } =>
1070 QueryStatus::VersionNotifier { origin, is_active },
1071 Ready { response, at } => QueryStatus::Ready { response, at },
1072 }
1073 }
1074 }
1075
1076 pub fn migrate_to_v1<T: Config, P: GetStorageVersion + PalletInfoAccess>(
1077 ) -> frame_support::weights::Weight {
1078 let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
1079 tracing::info!(
1080 target: "runtime::xcm",
1081 ?on_chain_storage_version,
1082 "Running migration storage v1 for xcm with storage version",
1083 );
1084
1085 if on_chain_storage_version < 1 {
1086 let mut count = 0;
1087 Queries::<T>::translate::<QueryStatusV0<BlockNumberFor<T>>, _>(|_key, value| {
1088 count += 1;
1089 Some(value.into())
1090 });
1091 StorageVersion::new(1).put::<P>();
1092 tracing::info!(
1093 target: "runtime::xcm",
1094 ?on_chain_storage_version,
1095 "Running migration storage v1 for xcm with storage version was complete",
1096 );
1097 T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
1099 } else {
1100 tracing::warn!(
1101 target: "runtime::xcm",
1102 ?on_chain_storage_version,
1103 "Attempted to apply migration to v1 but failed because storage version is",
1104 );
1105 T::DbWeight::get().reads(1)
1106 }
1107 }
1108 }
1109
1110 #[pallet::call(weight(<T as Config>::WeightInfo))]
1111 impl<T: Config> Pallet<T> {
1112 #[pallet::call_index(0)]
1113 pub fn send(
1114 origin: OriginFor<T>,
1115 dest: Box<VersionedLocation>,
1116 message: Box<VersionedXcm<()>>,
1117 ) -> DispatchResult {
1118 <Self as SendController<_>>::send(origin, dest, message)?;
1119 Ok(())
1120 }
1121
1122 #[pallet::call_index(1)]
1141 #[allow(deprecated)]
1142 #[deprecated(
1143 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_teleport_assets` or `transfer_assets`"
1144 )]
1145 pub fn teleport_assets(
1146 origin: OriginFor<T>,
1147 dest: Box<VersionedLocation>,
1148 beneficiary: Box<VersionedLocation>,
1149 assets: Box<VersionedAssets>,
1150 fee_asset_item: u32,
1151 ) -> DispatchResult {
1152 Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
1153 }
1154
1155 #[pallet::call_index(2)]
1186 #[allow(deprecated)]
1187 #[deprecated(
1188 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_reserve_transfer_assets` or `transfer_assets`"
1189 )]
1190 pub fn reserve_transfer_assets(
1191 origin: OriginFor<T>,
1192 dest: Box<VersionedLocation>,
1193 beneficiary: Box<VersionedLocation>,
1194 assets: Box<VersionedAssets>,
1195 fee_asset_item: u32,
1196 ) -> DispatchResult {
1197 Self::do_reserve_transfer_assets(
1198 origin,
1199 dest,
1200 beneficiary,
1201 assets,
1202 fee_asset_item,
1203 Unlimited,
1204 )
1205 }
1206
1207 #[pallet::call_index(3)]
1216 #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
1217 pub fn execute(
1218 origin: OriginFor<T>,
1219 message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
1220 max_weight: Weight,
1221 ) -> DispatchResultWithPostInfo {
1222 let weight_used =
1223 <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
1224 Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())
1225 }
1226
1227 #[pallet::call_index(4)]
1234 pub fn force_xcm_version(
1235 origin: OriginFor<T>,
1236 location: Box<Location>,
1237 version: XcmVersion,
1238 ) -> DispatchResult {
1239 T::AdminOrigin::ensure_origin(origin)?;
1240 let location = *location;
1241 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&location), version);
1242 Self::deposit_event(Event::SupportedVersionChanged { location, version });
1243 Ok(())
1244 }
1245
1246 #[pallet::call_index(5)]
1252 pub fn force_default_xcm_version(
1253 origin: OriginFor<T>,
1254 maybe_xcm_version: Option<XcmVersion>,
1255 ) -> DispatchResult {
1256 T::AdminOrigin::ensure_origin(origin)?;
1257 SafeXcmVersion::<T>::set(maybe_xcm_version);
1258 Ok(())
1259 }
1260
1261 #[pallet::call_index(6)]
1266 pub fn force_subscribe_version_notify(
1267 origin: OriginFor<T>,
1268 location: Box<VersionedLocation>,
1269 ) -> DispatchResult {
1270 T::AdminOrigin::ensure_origin(origin)?;
1271 let location: Location = (*location).try_into().map_err(|()| {
1272 tracing::debug!(
1273 target: "xcm::pallet_xcm::force_subscribe_version_notify",
1274 "Failed to convert VersionedLocation for subscription target"
1275 );
1276 Error::<T>::BadLocation
1277 })?;
1278 Self::request_version_notify(location).map_err(|e| {
1279 tracing::debug!(
1280 target: "xcm::pallet_xcm::force_subscribe_version_notify", error=?e,
1281 "Failed to subscribe for version notifications for location"
1282 );
1283 match e {
1284 XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
1285 _ => Error::<T>::InvalidOrigin,
1286 }
1287 .into()
1288 })
1289 }
1290
1291 #[pallet::call_index(7)]
1298 pub fn force_unsubscribe_version_notify(
1299 origin: OriginFor<T>,
1300 location: Box<VersionedLocation>,
1301 ) -> DispatchResult {
1302 T::AdminOrigin::ensure_origin(origin)?;
1303 let location: Location = (*location).try_into().map_err(|()| {
1304 tracing::debug!(
1305 target: "xcm::pallet_xcm::force_unsubscribe_version_notify",
1306 "Failed to convert VersionedLocation for unsubscription target"
1307 );
1308 Error::<T>::BadLocation
1309 })?;
1310 Self::unrequest_version_notify(location).map_err(|e| {
1311 tracing::debug!(
1312 target: "xcm::pallet_xcm::force_unsubscribe_version_notify", error=?e,
1313 "Failed to unsubscribe from version notifications for location"
1314 );
1315 match e {
1316 XcmError::InvalidLocation => Error::<T>::NoSubscription,
1317 _ => Error::<T>::InvalidOrigin,
1318 }
1319 .into()
1320 })
1321 }
1322
1323 #[pallet::call_index(8)]
1354 #[pallet::weight(T::WeightInfo::reserve_transfer_assets())]
1355 pub fn limited_reserve_transfer_assets(
1356 origin: OriginFor<T>,
1357 dest: Box<VersionedLocation>,
1358 beneficiary: Box<VersionedLocation>,
1359 assets: Box<VersionedAssets>,
1360 fee_asset_item: u32,
1361 weight_limit: WeightLimit,
1362 ) -> DispatchResult {
1363 Self::do_reserve_transfer_assets(
1364 origin,
1365 dest,
1366 beneficiary,
1367 assets,
1368 fee_asset_item,
1369 weight_limit,
1370 )
1371 }
1372
1373 #[pallet::call_index(9)]
1392 #[pallet::weight(T::WeightInfo::teleport_assets())]
1393 pub fn limited_teleport_assets(
1394 origin: OriginFor<T>,
1395 dest: Box<VersionedLocation>,
1396 beneficiary: Box<VersionedLocation>,
1397 assets: Box<VersionedAssets>,
1398 fee_asset_item: u32,
1399 weight_limit: WeightLimit,
1400 ) -> DispatchResult {
1401 Self::do_teleport_assets(
1402 origin,
1403 dest,
1404 beneficiary,
1405 assets,
1406 fee_asset_item,
1407 weight_limit,
1408 )
1409 }
1410
1411 #[pallet::call_index(10)]
1416 pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
1417 T::AdminOrigin::ensure_origin(origin)?;
1418 XcmExecutionSuspended::<T>::set(suspended);
1419 Ok(())
1420 }
1421
1422 #[pallet::call_index(11)]
1456 pub fn transfer_assets(
1457 origin: OriginFor<T>,
1458 dest: Box<VersionedLocation>,
1459 beneficiary: Box<VersionedLocation>,
1460 assets: Box<VersionedAssets>,
1461 fee_asset_item: u32,
1462 weight_limit: WeightLimit,
1463 ) -> DispatchResult {
1464 let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1465 let dest = (*dest).try_into().map_err(|()| {
1466 tracing::debug!(
1467 target: "xcm::pallet_xcm::transfer_assets",
1468 "Failed to convert destination VersionedLocation",
1469 );
1470 Error::<T>::BadVersion
1471 })?;
1472 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
1473 tracing::debug!(
1474 target: "xcm::pallet_xcm::transfer_assets",
1475 "Failed to convert beneficiary VersionedLocation",
1476 );
1477 Error::<T>::BadVersion
1478 })?;
1479 let assets: Assets = (*assets).try_into().map_err(|()| {
1480 tracing::debug!(
1481 target: "xcm::pallet_xcm::transfer_assets",
1482 "Failed to convert VersionedAssets",
1483 );
1484 Error::<T>::BadVersion
1485 })?;
1486 tracing::debug!(
1487 target: "xcm::pallet_xcm::transfer_assets",
1488 ?origin, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
1489 );
1490
1491 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1492 let assets = assets.into_inner();
1493 let fee_asset_item = fee_asset_item as usize;
1494 let (fees_transfer_type, assets_transfer_type) =
1496 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
1497
1498 Self::ensure_network_asset_reserve_transfer_allowed(
1502 &assets,
1503 fee_asset_item,
1504 &assets_transfer_type,
1505 &fees_transfer_type,
1506 )?;
1507
1508 Self::do_transfer_assets(
1509 origin,
1510 dest,
1511 Either::Left(beneficiary),
1512 assets,
1513 assets_transfer_type,
1514 fee_asset_item,
1515 fees_transfer_type,
1516 weight_limit,
1517 )
1518 }
1519
1520 #[pallet::call_index(12)]
1527 pub fn claim_assets(
1528 origin: OriginFor<T>,
1529 assets: Box<VersionedAssets>,
1530 beneficiary: Box<VersionedLocation>,
1531 ) -> DispatchResult {
1532 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1533 tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?origin_location, ?assets, ?beneficiary);
1534 let assets_version = assets.identify_version();
1536 let assets: Assets = (*assets).try_into().map_err(|()| {
1537 tracing::debug!(
1538 target: "xcm::pallet_xcm::claim_assets",
1539 "Failed to convert input VersionedAssets",
1540 );
1541 Error::<T>::BadVersion
1542 })?;
1543 let number_of_assets = assets.len() as u32;
1544 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
1545 tracing::debug!(
1546 target: "xcm::pallet_xcm::claim_assets",
1547 "Failed to convert beneficiary VersionedLocation",
1548 );
1549 Error::<T>::BadVersion
1550 })?;
1551 let ticket: Location = GeneralIndex(assets_version as u128).into();
1552 let mut message = Xcm(vec![
1553 ClaimAsset { assets, ticket },
1554 DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
1555 ]);
1556 let weight = T::Weigher::weight(&mut message, Weight::MAX).map_err(|error| {
1557 tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?error, "Failed to calculate weight");
1558 Error::<T>::UnweighableMessage
1559 })?;
1560 let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
1561 let outcome = T::XcmExecutor::prepare_and_execute(
1562 origin_location,
1563 message,
1564 &mut hash,
1565 weight,
1566 weight,
1567 );
1568 outcome.ensure_complete().map_err(|error| {
1569 tracing::error!(target: "xcm::pallet_xcm::claim_assets", ?error, "XCM execution failed with error");
1570 Error::<T>::LocalExecutionIncompleteWithError { index: error.index, error: error.error.into()}
1571 })?;
1572 Ok(())
1573 }
1574
1575 #[pallet::call_index(13)]
1624 #[pallet::weight(T::WeightInfo::transfer_assets())]
1625 pub fn transfer_assets_using_type_and_then(
1626 origin: OriginFor<T>,
1627 dest: Box<VersionedLocation>,
1628 assets: Box<VersionedAssets>,
1629 assets_transfer_type: Box<TransferType>,
1630 remote_fees_id: Box<VersionedAssetId>,
1631 fees_transfer_type: Box<TransferType>,
1632 custom_xcm_on_dest: Box<VersionedXcm<()>>,
1633 weight_limit: WeightLimit,
1634 ) -> DispatchResult {
1635 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1636 let dest: Location = (*dest).try_into().map_err(|()| {
1637 tracing::debug!(
1638 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1639 "Failed to convert destination VersionedLocation",
1640 );
1641 Error::<T>::BadVersion
1642 })?;
1643 let assets: Assets = (*assets).try_into().map_err(|()| {
1644 tracing::debug!(
1645 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1646 "Failed to convert VersionedAssets",
1647 );
1648 Error::<T>::BadVersion
1649 })?;
1650 let fees_id: AssetId = (*remote_fees_id).try_into().map_err(|()| {
1651 tracing::debug!(
1652 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1653 "Failed to convert remote_fees_id VersionedAssetId",
1654 );
1655 Error::<T>::BadVersion
1656 })?;
1657 let remote_xcm: Xcm<()> = (*custom_xcm_on_dest).try_into().map_err(|()| {
1658 tracing::debug!(
1659 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1660 "Failed to convert custom_xcm_on_dest VersionedXcm",
1661 );
1662 Error::<T>::BadVersion
1663 })?;
1664 tracing::debug!(
1665 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1666 ?origin_location, ?dest, ?assets, ?assets_transfer_type, ?fees_id, ?fees_transfer_type,
1667 ?remote_xcm, ?weight_limit,
1668 );
1669
1670 let assets = assets.into_inner();
1671 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1672
1673 let fee_asset_index =
1674 assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
1675 Self::do_transfer_assets(
1676 origin_location,
1677 dest,
1678 Either::Right(remote_xcm),
1679 assets,
1680 *assets_transfer_type,
1681 fee_asset_index,
1682 *fees_transfer_type,
1683 weight_limit,
1684 )
1685 }
1686
1687 #[pallet::call_index(14)]
1699 pub fn add_authorized_alias(
1700 origin: OriginFor<T>,
1701 aliaser: Box<VersionedLocation>,
1702 expires: Option<u64>,
1703 ) -> DispatchResult {
1704 let signed_origin = ensure_signed(origin.clone())?;
1705 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1706 let new_aliaser: Location = (*aliaser).try_into().map_err(|()| {
1707 tracing::debug!(
1708 target: "xcm::pallet_xcm::add_authorized_alias",
1709 "Failed to convert aliaser VersionedLocation",
1710 );
1711 Error::<T>::BadVersion
1712 })?;
1713 ensure!(origin_location != new_aliaser, Error::<T>::BadLocation);
1714 let origin_location = match origin_location.unpack() {
1716 (0, [AccountId32 { network: _, id }]) =>
1717 Location::new(0, [AccountId32 { network: None, id: *id }]),
1718 _ => return Err(Error::<T>::InvalidOrigin.into()),
1719 };
1720 tracing::debug!(target: "xcm::pallet_xcm::add_authorized_alias", ?origin_location, ?new_aliaser, ?expires);
1721 ensure!(origin_location != new_aliaser, Error::<T>::BadLocation);
1722 if let Some(expiry) = expires {
1723 ensure!(
1724 expiry >
1725 frame_system::Pallet::<T>::current_block_number().saturated_into::<u64>(),
1726 Error::<T>::ExpiresInPast
1727 );
1728 }
1729 let versioned_origin = VersionedLocation::from(origin_location.clone());
1730 let versioned_aliaser = VersionedLocation::from(new_aliaser.clone());
1731 let entry = if let Some(entry) = AuthorizedAliases::<T>::get(&versioned_origin) {
1732 let (mut aliasers, mut ticket) = (entry.aliasers, entry.ticket);
1734 if let Some(aliaser) =
1735 aliasers.iter_mut().find(|aliaser| aliaser.location == versioned_aliaser)
1736 {
1737 aliaser.expiry = expires;
1739 } else {
1740 let aliaser =
1742 OriginAliaser { location: versioned_aliaser.clone(), expiry: expires };
1743 aliasers.try_push(aliaser).map_err(|_| {
1744 tracing::debug!(
1745 target: "xcm::pallet_xcm::add_authorized_alias",
1746 "Failed to add new aliaser to existing entry",
1747 );
1748 Error::<T>::TooManyAuthorizedAliases
1749 })?;
1750 ticket = ticket.update(&signed_origin, aliasers_footprint(aliasers.len()))?;
1752 }
1753 AuthorizedAliasesEntry { aliasers, ticket }
1754 } else {
1755 let ticket = TicketOf::<T>::new(&signed_origin, aliasers_footprint(1))?;
1757 let aliaser =
1758 OriginAliaser { location: versioned_aliaser.clone(), expiry: expires };
1759 let mut aliasers = BoundedVec::<OriginAliaser, MaxAuthorizedAliases>::new();
1760 aliasers.try_push(aliaser).map_err(|error| {
1761 tracing::debug!(
1762 target: "xcm::pallet_xcm::add_authorized_alias", ?error,
1763 "Failed to add first aliaser to new entry",
1764 );
1765 Error::<T>::TooManyAuthorizedAliases
1766 })?;
1767 AuthorizedAliasesEntry { aliasers, ticket }
1768 };
1769 AuthorizedAliases::<T>::insert(&versioned_origin, entry);
1771 Self::deposit_event(Event::AliasAuthorized {
1772 aliaser: new_aliaser,
1773 target: origin_location,
1774 expiry: expires,
1775 });
1776 Ok(())
1777 }
1778
1779 #[pallet::call_index(15)]
1782 pub fn remove_authorized_alias(
1783 origin: OriginFor<T>,
1784 aliaser: Box<VersionedLocation>,
1785 ) -> DispatchResult {
1786 let signed_origin = ensure_signed(origin.clone())?;
1787 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1788 let to_remove: Location = (*aliaser).try_into().map_err(|()| {
1789 tracing::debug!(
1790 target: "xcm::pallet_xcm::remove_authorized_alias",
1791 "Failed to convert aliaser VersionedLocation",
1792 );
1793 Error::<T>::BadVersion
1794 })?;
1795 ensure!(origin_location != to_remove, Error::<T>::BadLocation);
1796 let origin_location = match origin_location.unpack() {
1798 (0, [AccountId32 { network: _, id }]) =>
1799 Location::new(0, [AccountId32 { network: None, id: *id }]),
1800 _ => return Err(Error::<T>::InvalidOrigin.into()),
1801 };
1802 tracing::debug!(target: "xcm::pallet_xcm::remove_authorized_alias", ?origin_location, ?to_remove);
1803 ensure!(origin_location != to_remove, Error::<T>::BadLocation);
1804 let versioned_origin = VersionedLocation::from(origin_location.clone());
1806 let versioned_to_remove = VersionedLocation::from(to_remove.clone());
1807 AuthorizedAliases::<T>::get(&versioned_origin)
1808 .ok_or(Error::<T>::AliasNotFound.into())
1809 .and_then(|entry| {
1810 let (mut aliasers, mut ticket) = (entry.aliasers, entry.ticket);
1811 let old_len = aliasers.len();
1812 aliasers.retain(|alias| versioned_to_remove.ne(&alias.location));
1813 let new_len = aliasers.len();
1814 if aliasers.is_empty() {
1815 ticket.drop(&signed_origin)?;
1817 AuthorizedAliases::<T>::remove(&versioned_origin);
1818 Self::deposit_event(Event::AliasAuthorizationRemoved {
1819 aliaser: to_remove,
1820 target: origin_location,
1821 });
1822 Ok(())
1823 } else if old_len != new_len {
1824 ticket = ticket.update(&signed_origin, aliasers_footprint(new_len))?;
1826 let entry = AuthorizedAliasesEntry { aliasers, ticket };
1827 AuthorizedAliases::<T>::insert(&versioned_origin, entry);
1828 Self::deposit_event(Event::AliasAuthorizationRemoved {
1829 aliaser: to_remove,
1830 target: origin_location,
1831 });
1832 Ok(())
1833 } else {
1834 Err(Error::<T>::AliasNotFound.into())
1835 }
1836 })
1837 }
1838
1839 #[pallet::call_index(16)]
1842 #[pallet::weight(T::WeightInfo::remove_authorized_alias())]
1843 pub fn remove_all_authorized_aliases(origin: OriginFor<T>) -> DispatchResult {
1844 let signed_origin = ensure_signed(origin.clone())?;
1845 let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1846 let origin_location = match origin_location.unpack() {
1848 (0, [AccountId32 { network: _, id }]) =>
1849 Location::new(0, [AccountId32 { network: None, id: *id }]),
1850 _ => return Err(Error::<T>::InvalidOrigin.into()),
1851 };
1852 tracing::debug!(target: "xcm::pallet_xcm::remove_all_authorized_aliases", ?origin_location);
1853 let versioned_origin = VersionedLocation::from(origin_location.clone());
1855 if let Some(entry) = AuthorizedAliases::<T>::get(&versioned_origin) {
1856 entry.ticket.drop(&signed_origin)?;
1858 AuthorizedAliases::<T>::remove(&versioned_origin);
1859 Self::deposit_event(Event::AliasesAuthorizationsRemoved {
1860 target: origin_location,
1861 });
1862 Ok(())
1863 } else {
1864 tracing::debug!(target: "xcm::pallet_xcm::remove_all_authorized_aliases", "No authorized alias entry found for the origin");
1865 Err(Error::<T>::AliasNotFound.into())
1866 }
1867 }
1868 }
1869}
1870
1871const MAX_ASSETS_FOR_TRANSFER: usize = 2;
1873
1874#[derive(Clone, PartialEq)]
1876enum FeesHandling<T: Config> {
1877 Batched { fees: Asset },
1879 Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
1881}
1882
1883impl<T: Config> core::fmt::Debug for FeesHandling<T> {
1884 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1885 match self {
1886 Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
1887 Self::Separate { local_xcm, remote_xcm } => write!(
1888 f,
1889 "FeesHandling::Separate(local: {:?}, remote: {:?})",
1890 local_xcm, remote_xcm
1891 ),
1892 }
1893 }
1894}
1895
1896impl<T: Config> QueryHandler for Pallet<T> {
1897 type BlockNumber = BlockNumberFor<T>;
1898 type Error = XcmError;
1899 type UniversalLocation = T::UniversalLocation;
1900
1901 fn new_query(
1903 responder: impl Into<Location>,
1904 timeout: BlockNumberFor<T>,
1905 match_querier: impl Into<Location>,
1906 ) -> QueryId {
1907 Self::do_new_query(responder, None, timeout, match_querier)
1908 }
1909
1910 fn report_outcome(
1913 message: &mut Xcm<()>,
1914 responder: impl Into<Location>,
1915 timeout: Self::BlockNumber,
1916 ) -> Result<QueryId, Self::Error> {
1917 let responder = responder.into();
1918 let destination =
1919 Self::UniversalLocation::get().invert_target(&responder).map_err(|()| {
1920 tracing::debug!(
1921 target: "xcm::pallet_xcm::report_outcome",
1922 "Failed to invert responder Location",
1923 );
1924 XcmError::LocationNotInvertible
1925 })?;
1926 let query_id = Self::new_query(responder, timeout, Here);
1927 let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
1928 let report_error = Xcm(vec![ReportError(response_info)]);
1929 message.0.insert(0, SetAppendix(report_error));
1930 Ok(query_id)
1931 }
1932
1933 fn take_response(query_id: QueryId) -> QueryResponseStatus<Self::BlockNumber> {
1935 match Queries::<T>::get(query_id) {
1936 Some(QueryStatus::Ready { response, at }) => match response.try_into() {
1937 Ok(response) => {
1938 Queries::<T>::remove(query_id);
1939 Self::deposit_event(Event::ResponseTaken { query_id });
1940 QueryResponseStatus::Ready { response, at }
1941 },
1942 Err(_) => {
1943 tracing::debug!(
1944 target: "xcm::pallet_xcm::take_response", ?query_id,
1945 "Failed to convert VersionedResponse to Response for query",
1946 );
1947 QueryResponseStatus::UnexpectedVersion
1948 },
1949 },
1950 Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
1951 Some(_) => {
1952 tracing::debug!(
1953 target: "xcm::pallet_xcm::take_response", ?query_id,
1954 "Unexpected QueryStatus variant for query",
1955 );
1956 QueryResponseStatus::UnexpectedVersion
1957 },
1958 None => {
1959 tracing::debug!(
1960 target: "xcm::pallet_xcm::take_response", ?query_id,
1961 "Query ID not found`",
1962 );
1963 QueryResponseStatus::NotFound
1964 },
1965 }
1966 }
1967
1968 #[cfg(feature = "runtime-benchmarks")]
1969 fn expect_response(id: QueryId, response: Response) {
1970 let response = response.into();
1971 Queries::<T>::insert(
1972 id,
1973 QueryStatus::Ready { response, at: frame_system::Pallet::<T>::current_block_number() },
1974 );
1975 }
1976}
1977
1978impl<T: Config> Pallet<T> {
1979 pub fn query(query_id: &QueryId) -> Option<QueryStatus<BlockNumberFor<T>>> {
1981 Queries::<T>::get(query_id)
1982 }
1983
1984 pub fn asset_trap(trap_id: &H256) -> u32 {
1990 AssetTraps::<T>::get(trap_id)
1991 }
1992
1993 fn find_fee_and_assets_transfer_types(
1998 assets: &[Asset],
1999 fee_asset_item: usize,
2000 dest: &Location,
2001 ) -> Result<(TransferType, TransferType), Error<T>> {
2002 let mut fees_transfer_type = None;
2003 let mut assets_transfer_type = None;
2004 for (idx, asset) in assets.iter().enumerate() {
2005 if let Fungible(x) = asset.fun {
2006 ensure!(!x.is_zero(), Error::<T>::Empty);
2008 }
2009 let transfer_type =
2010 T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
2011 if idx == fee_asset_item {
2012 fees_transfer_type = Some(transfer_type);
2013 } else {
2014 if let Some(existing) = assets_transfer_type.as_ref() {
2015 ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
2018 } else {
2019 assets_transfer_type = Some(transfer_type);
2021 }
2022 }
2023 }
2024 if assets.len() == 1 {
2026 assets_transfer_type = fees_transfer_type.clone()
2027 }
2028 Ok((
2029 fees_transfer_type.ok_or(Error::<T>::Empty)?,
2030 assets_transfer_type.ok_or(Error::<T>::Empty)?,
2031 ))
2032 }
2033
2034 fn do_reserve_transfer_assets(
2035 origin: OriginFor<T>,
2036 dest: Box<VersionedLocation>,
2037 beneficiary: Box<VersionedLocation>,
2038 assets: Box<VersionedAssets>,
2039 fee_asset_item: u32,
2040 weight_limit: WeightLimit,
2041 ) -> DispatchResult {
2042 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2043 let dest = (*dest).try_into().map_err(|()| {
2044 tracing::debug!(
2045 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2046 "Failed to convert destination VersionedLocation",
2047 );
2048 Error::<T>::BadVersion
2049 })?;
2050 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2051 tracing::debug!(
2052 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2053 "Failed to convert beneficiary VersionedLocation",
2054 );
2055 Error::<T>::BadVersion
2056 })?;
2057 let assets: Assets = (*assets).try_into().map_err(|()| {
2058 tracing::debug!(
2059 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2060 "Failed to convert VersionedAssets",
2061 );
2062 Error::<T>::BadVersion
2063 })?;
2064 tracing::debug!(
2065 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
2066 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item,
2067 );
2068
2069 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2070 let value = (origin_location, assets.into_inner());
2071 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2072 let (origin, assets) = value;
2073
2074 let fee_asset_item = fee_asset_item as usize;
2075 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
2076
2077 let (fees_transfer_type, assets_transfer_type) =
2079 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
2080 ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
2082 ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
2084
2085 Self::ensure_network_asset_reserve_transfer_allowed(
2089 &assets,
2090 fee_asset_item,
2091 &assets_transfer_type,
2092 &fees_transfer_type,
2093 )?;
2094
2095 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2096 origin.clone(),
2097 dest.clone(),
2098 Either::Left(beneficiary),
2099 assets,
2100 assets_transfer_type,
2101 FeesHandling::Batched { fees },
2102 weight_limit,
2103 )?;
2104 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2105 }
2106
2107 fn do_teleport_assets(
2108 origin: OriginFor<T>,
2109 dest: Box<VersionedLocation>,
2110 beneficiary: Box<VersionedLocation>,
2111 assets: Box<VersionedAssets>,
2112 fee_asset_item: u32,
2113 weight_limit: WeightLimit,
2114 ) -> DispatchResult {
2115 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2116 let dest = (*dest).try_into().map_err(|()| {
2117 tracing::debug!(
2118 target: "xcm::pallet_xcm::do_teleport_assets",
2119 "Failed to convert destination VersionedLocation",
2120 );
2121 Error::<T>::BadVersion
2122 })?;
2123 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2124 tracing::debug!(
2125 target: "xcm::pallet_xcm::do_teleport_assets",
2126 "Failed to convert beneficiary VersionedLocation",
2127 );
2128 Error::<T>::BadVersion
2129 })?;
2130 let assets: Assets = (*assets).try_into().map_err(|()| {
2131 tracing::debug!(
2132 target: "xcm::pallet_xcm::do_teleport_assets",
2133 "Failed to convert VersionedAssets",
2134 );
2135 Error::<T>::BadVersion
2136 })?;
2137 tracing::debug!(
2138 target: "xcm::pallet_xcm::do_teleport_assets",
2139 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
2140 );
2141
2142 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2143 let value = (origin_location, assets.into_inner());
2144 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2145 let (origin_location, assets) = value;
2146 for asset in assets.iter() {
2147 let transfer_type =
2148 T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
2149 ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
2150 }
2151 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
2152
2153 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2154 origin_location.clone(),
2155 dest.clone(),
2156 Either::Left(beneficiary),
2157 assets,
2158 TransferType::Teleport,
2159 FeesHandling::Batched { fees },
2160 weight_limit,
2161 )?;
2162 Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
2163 }
2164
2165 fn do_transfer_assets(
2166 origin: Location,
2167 dest: Location,
2168 beneficiary: Either<Location, Xcm<()>>,
2169 mut assets: Vec<Asset>,
2170 assets_transfer_type: TransferType,
2171 fee_asset_index: usize,
2172 fees_transfer_type: TransferType,
2173 weight_limit: WeightLimit,
2174 ) -> DispatchResult {
2175 let fees = if fees_transfer_type == assets_transfer_type {
2177 let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
2178 FeesHandling::Batched { fees }
2180 } else {
2181 ensure!(
2187 !matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
2188 Error::<T>::InvalidAssetUnsupportedReserve
2189 );
2190 let weight_limit = weight_limit.clone();
2191 let fees = assets.remove(fee_asset_index);
2194 let (local_xcm, remote_xcm) = match fees_transfer_type {
2195 TransferType::LocalReserve => Self::local_reserve_fees_instructions(
2196 origin.clone(),
2197 dest.clone(),
2198 fees,
2199 weight_limit,
2200 )?,
2201 TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
2202 origin.clone(),
2203 dest.clone(),
2204 fees,
2205 weight_limit,
2206 )?,
2207 TransferType::Teleport => Self::teleport_fees_instructions(
2208 origin.clone(),
2209 dest.clone(),
2210 fees,
2211 weight_limit,
2212 )?,
2213 TransferType::RemoteReserve(_) =>
2214 return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2215 };
2216 FeesHandling::Separate { local_xcm, remote_xcm }
2217 };
2218
2219 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2220 origin.clone(),
2221 dest.clone(),
2222 beneficiary,
2223 assets,
2224 assets_transfer_type,
2225 fees,
2226 weight_limit,
2227 )?;
2228 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2229 }
2230
2231 fn build_xcm_transfer_type(
2232 origin: Location,
2233 dest: Location,
2234 beneficiary: Either<Location, Xcm<()>>,
2235 assets: Vec<Asset>,
2236 transfer_type: TransferType,
2237 fees: FeesHandling<T>,
2238 weight_limit: WeightLimit,
2239 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
2240 tracing::debug!(
2241 target: "xcm::pallet_xcm::build_xcm_transfer_type",
2242 ?origin, ?dest, ?beneficiary, ?assets, ?transfer_type, ?fees, ?weight_limit,
2243 );
2244 match transfer_type {
2245 TransferType::LocalReserve => Self::local_reserve_transfer_programs(
2246 origin.clone(),
2247 dest.clone(),
2248 beneficiary,
2249 assets,
2250 fees,
2251 weight_limit,
2252 )
2253 .map(|(local, remote)| (local, Some(remote))),
2254 TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
2255 origin.clone(),
2256 dest.clone(),
2257 beneficiary,
2258 assets,
2259 fees,
2260 weight_limit,
2261 )
2262 .map(|(local, remote)| (local, Some(remote))),
2263 TransferType::RemoteReserve(reserve) => {
2264 let fees = match fees {
2265 FeesHandling::Batched { fees } => fees,
2266 _ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2267 };
2268 Self::remote_reserve_transfer_program(
2269 origin.clone(),
2270 reserve.try_into().map_err(|()| {
2271 tracing::debug!(
2272 target: "xcm::pallet_xcm::build_xcm_transfer_type",
2273 "Failed to convert remote reserve location",
2274 );
2275 Error::<T>::BadVersion
2276 })?,
2277 beneficiary,
2278 dest.clone(),
2279 assets,
2280 fees,
2281 weight_limit,
2282 )
2283 .map(|local| (local, None))
2284 },
2285 TransferType::Teleport => Self::teleport_assets_program(
2286 origin.clone(),
2287 dest.clone(),
2288 beneficiary,
2289 assets,
2290 fees,
2291 weight_limit,
2292 )
2293 .map(|(local, remote)| (local, Some(remote))),
2294 }
2295 }
2296
2297 fn execute_xcm_transfer(
2298 origin: Location,
2299 dest: Location,
2300 mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
2301 remote_xcm: Option<Xcm<()>>,
2302 ) -> DispatchResult {
2303 tracing::debug!(
2304 target: "xcm::pallet_xcm::execute_xcm_transfer",
2305 ?origin, ?dest, ?local_xcm, ?remote_xcm,
2306 );
2307
2308 let weight =
2309 T::Weigher::weight(&mut local_xcm, Weight::MAX).map_err(|error| {
2310 tracing::debug!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, "Failed to calculate weight");
2311 Error::<T>::UnweighableMessage
2312 })?;
2313 let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
2314 let outcome = T::XcmExecutor::prepare_and_execute(
2315 origin.clone(),
2316 local_xcm,
2317 &mut hash,
2318 weight,
2319 weight,
2320 );
2321 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
2322 outcome.clone().ensure_complete().map_err(|error| {
2323 tracing::error!(
2324 target: "xcm::pallet_xcm::execute_xcm_transfer",
2325 ?error, "XCM execution failed with error with outcome: {:?}", outcome
2326 );
2327 Error::<T>::LocalExecutionIncompleteWithError {
2328 index: error.index,
2329 error: error.error.into(),
2330 }
2331 })?;
2332
2333 if let Some(remote_xcm) = remote_xcm {
2334 let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
2335 .map_err(|error| {
2336 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error");
2337 Error::<T>::from(error)
2338 })?;
2339 if origin != Here.into_location() {
2340 Self::charge_fees(origin.clone(), price.clone()).map_err(|error| {
2341 tracing::error!(
2342 target: "xcm::pallet_xcm::execute_xcm_transfer",
2343 ?error, ?price, ?origin, "Unable to charge fee",
2344 );
2345 Error::<T>::FeesNotMet
2346 })?;
2347 }
2348 let message_id = T::XcmRouter::deliver(ticket)
2349 .map_err(|error| {
2350 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error");
2351 Error::<T>::from(error)
2352 })?;
2353
2354 let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
2355 Self::deposit_event(e);
2356 }
2357 Ok(())
2358 }
2359
2360 fn add_fees_to_xcm(
2361 dest: Location,
2362 fees: FeesHandling<T>,
2363 weight_limit: WeightLimit,
2364 local: &mut Xcm<<T as Config>::RuntimeCall>,
2365 remote: &mut Xcm<()>,
2366 ) -> Result<(), Error<T>> {
2367 match fees {
2368 FeesHandling::Batched { fees } => {
2369 let context = T::UniversalLocation::get();
2370 let reanchored_fees =
2373 fees.reanchored(&dest, &context).map_err(|e| {
2374 tracing::error!(target: "xcm::pallet_xcm::add_fees_to_xcm", ?e, ?dest, ?context, "Failed to re-anchor fees");
2375 Error::<T>::CannotReanchor
2376 })?;
2377 remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
2379 },
2380 FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
2381 core::mem::swap(local, &mut local_fees);
2384 core::mem::swap(remote, &mut remote_fees);
2385 local.inner_mut().append(&mut local_fees.into_inner());
2387 remote.inner_mut().append(&mut remote_fees.into_inner());
2388 },
2389 }
2390 Ok(())
2391 }
2392
2393 fn local_reserve_fees_instructions(
2394 origin: Location,
2395 dest: Location,
2396 fees: Asset,
2397 weight_limit: WeightLimit,
2398 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2399 let value = (origin, vec![fees.clone()]);
2400 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2401
2402 let context = T::UniversalLocation::get();
2403 let reanchored_fees = fees.clone().reanchored(&dest, &context).map_err(|_| {
2404 tracing::debug!(
2405 target: "xcm::pallet_xcm::local_reserve_fees_instructions",
2406 "Failed to re-anchor fees",
2407 );
2408 Error::<T>::CannotReanchor
2409 })?;
2410
2411 let local_execute_xcm = Xcm(vec![
2412 TransferAsset { assets: fees.into(), beneficiary: dest },
2414 ]);
2415 let xcm_on_dest = Xcm(vec![
2416 ReserveAssetDeposited(reanchored_fees.clone().into()),
2418 BuyExecution { fees: reanchored_fees, weight_limit },
2420 ]);
2421 Ok((local_execute_xcm, xcm_on_dest))
2422 }
2423
2424 fn local_reserve_transfer_programs(
2425 origin: Location,
2426 dest: Location,
2427 beneficiary: Either<Location, Xcm<()>>,
2428 assets: Vec<Asset>,
2429 fees: FeesHandling<T>,
2430 weight_limit: WeightLimit,
2431 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2432 let value = (origin, assets);
2433 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2434 let (_, assets) = value;
2435
2436 let max_assets =
2438 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2439 let assets: Assets = assets.into();
2440 let context = T::UniversalLocation::get();
2441 let mut reanchored_assets = assets.clone();
2442 reanchored_assets
2443 .reanchor(&dest, &context)
2444 .map_err(|e| {
2445 tracing::error!(target: "xcm::pallet_xcm::local_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2446 Error::<T>::CannotReanchor
2447 })?;
2448
2449 let mut local_execute_xcm = Xcm(vec![
2451 TransferAsset { assets, beneficiary: dest.clone() },
2453 ]);
2454 let mut xcm_on_dest = Xcm(vec![
2456 ReserveAssetDeposited(reanchored_assets),
2458 ClearOrigin,
2460 ]);
2461 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2463
2464 let custom_remote_xcm = match beneficiary {
2466 Either::Right(custom_xcm) => custom_xcm,
2467 Either::Left(beneficiary) => {
2468 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2470 },
2471 };
2472 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2473
2474 Ok((local_execute_xcm, xcm_on_dest))
2475 }
2476
2477 fn destination_reserve_fees_instructions(
2478 origin: Location,
2479 dest: Location,
2480 fees: Asset,
2481 weight_limit: WeightLimit,
2482 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2483 let value = (origin, vec![fees.clone()]);
2484 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2485 ensure!(
2486 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&fees, &dest),
2487 Error::<T>::InvalidAssetUnsupportedReserve
2488 );
2489
2490 let context = T::UniversalLocation::get();
2491 let reanchored_fees = fees
2492 .clone()
2493 .reanchored(&dest, &context)
2494 .map_err(|e| {
2495 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_fees_instructions", ?e, ?dest,?context, "Failed to re-anchor fees");
2496 Error::<T>::CannotReanchor
2497 })?;
2498 let fees: Assets = fees.into();
2499
2500 let local_execute_xcm = Xcm(vec![
2501 WithdrawAsset(fees.clone()),
2503 BurnAsset(fees),
2505 ]);
2506 let xcm_on_dest = Xcm(vec![
2507 WithdrawAsset(reanchored_fees.clone().into()),
2509 BuyExecution { fees: reanchored_fees, weight_limit },
2511 ]);
2512 Ok((local_execute_xcm, xcm_on_dest))
2513 }
2514
2515 fn destination_reserve_transfer_programs(
2516 origin: Location,
2517 dest: Location,
2518 beneficiary: Either<Location, Xcm<()>>,
2519 assets: Vec<Asset>,
2520 fees: FeesHandling<T>,
2521 weight_limit: WeightLimit,
2522 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2523 let value = (origin, assets);
2524 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2525 let (_, assets) = value;
2526 for asset in assets.iter() {
2527 ensure!(
2528 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&asset, &dest),
2529 Error::<T>::InvalidAssetUnsupportedReserve
2530 );
2531 }
2532
2533 let max_assets =
2535 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2536 let assets: Assets = assets.into();
2537 let context = T::UniversalLocation::get();
2538 let mut reanchored_assets = assets.clone();
2539 reanchored_assets
2540 .reanchor(&dest, &context)
2541 .map_err(|e| {
2542 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2543 Error::<T>::CannotReanchor
2544 })?;
2545
2546 let mut local_execute_xcm = Xcm(vec![
2548 WithdrawAsset(assets.clone()),
2550 BurnAsset(assets),
2552 ]);
2553 let mut xcm_on_dest = Xcm(vec![
2555 WithdrawAsset(reanchored_assets),
2557 ClearOrigin,
2559 ]);
2560 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2562
2563 let custom_remote_xcm = match beneficiary {
2565 Either::Right(custom_xcm) => custom_xcm,
2566 Either::Left(beneficiary) => {
2567 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2569 },
2570 };
2571 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2572
2573 Ok((local_execute_xcm, xcm_on_dest))
2574 }
2575
2576 fn remote_reserve_transfer_program(
2578 origin: Location,
2579 reserve: Location,
2580 beneficiary: Either<Location, Xcm<()>>,
2581 dest: Location,
2582 assets: Vec<Asset>,
2583 fees: Asset,
2584 weight_limit: WeightLimit,
2585 ) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
2586 let value = (origin, assets);
2587 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2588 let (_, assets) = value;
2589
2590 let max_assets = assets.len() as u32;
2591 let context = T::UniversalLocation::get();
2592 let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
2595 let reserve_fees = fees_half_1
2597 .reanchored(&reserve, &context)
2598 .map_err(|e| {
2599 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor reserve_fees");
2600 Error::<T>::CannotReanchor
2601 })?;
2602 let dest_fees = fees_half_2
2604 .reanchored(&dest, &context)
2605 .map_err(|e| {
2606 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?dest, ?context, "Failed to re-anchor dest_fees");
2607 Error::<T>::CannotReanchor
2608 })?;
2609 let dest = dest.reanchored(&reserve, &context).map_err(|e| {
2611 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor dest");
2612 Error::<T>::CannotReanchor
2613 })?;
2614 let mut xcm_on_dest =
2616 Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
2617 let custom_xcm_on_dest = match beneficiary {
2619 Either::Right(custom_xcm) => custom_xcm,
2620 Either::Left(beneficiary) => {
2621 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2623 },
2624 };
2625 xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
2626 let xcm_on_reserve = Xcm(vec![
2628 BuyExecution { fees: reserve_fees, weight_limit },
2629 DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
2630 ]);
2631 Ok(Xcm(vec![
2632 WithdrawAsset(assets.into()),
2633 SetFeesMode { jit_withdraw: true },
2634 InitiateReserveWithdraw {
2635 assets: Wild(AllCounted(max_assets)),
2636 reserve,
2637 xcm: xcm_on_reserve,
2638 },
2639 ]))
2640 }
2641
2642 fn teleport_fees_instructions(
2643 origin: Location,
2644 dest: Location,
2645 fees: Asset,
2646 weight_limit: WeightLimit,
2647 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2648 let value = (origin, vec![fees.clone()]);
2649 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2650 ensure!(
2651 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&fees, &dest),
2652 Error::<T>::Filtered
2653 );
2654
2655 let context = T::UniversalLocation::get();
2656 let reanchored_fees = fees
2657 .clone()
2658 .reanchored(&dest, &context)
2659 .map_err(|e| {
2660 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?dest, ?context, "Failed to re-anchor fees");
2661 Error::<T>::CannotReanchor
2662 })?;
2663
2664 let dummy_context =
2666 XcmContext { origin: None, message_id: Default::default(), topic: None };
2667 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2672 &dest,
2673 &fees,
2674 &dummy_context,
2675 )
2676 .map_err(|e| {
2677 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?fees, ?dest, "Failed can_check_out");
2678 Error::<T>::CannotCheckOutTeleport
2679 })?;
2680 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2683 &dest,
2684 &fees,
2685 &dummy_context,
2686 );
2687
2688 let fees: Assets = fees.into();
2689 let local_execute_xcm = Xcm(vec![
2690 WithdrawAsset(fees.clone()),
2692 BurnAsset(fees),
2694 ]);
2695 let xcm_on_dest = Xcm(vec![
2696 ReceiveTeleportedAsset(reanchored_fees.clone().into()),
2698 BuyExecution { fees: reanchored_fees, weight_limit },
2700 ]);
2701 Ok((local_execute_xcm, xcm_on_dest))
2702 }
2703
2704 fn teleport_assets_program(
2705 origin: Location,
2706 dest: Location,
2707 beneficiary: Either<Location, Xcm<()>>,
2708 assets: Vec<Asset>,
2709 fees: FeesHandling<T>,
2710 weight_limit: WeightLimit,
2711 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2712 let value = (origin, assets);
2713 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2714 let (_, assets) = value;
2715 for asset in assets.iter() {
2716 ensure!(
2717 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&asset, &dest),
2718 Error::<T>::Filtered
2719 );
2720 }
2721
2722 let max_assets =
2724 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2725 let context = T::UniversalLocation::get();
2726 let assets: Assets = assets.into();
2727 let mut reanchored_assets = assets.clone();
2728 reanchored_assets
2729 .reanchor(&dest, &context)
2730 .map_err(|e| {
2731 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?dest, ?context, "Failed to re-anchor asset");
2732 Error::<T>::CannotReanchor
2733 })?;
2734
2735 let dummy_context =
2737 XcmContext { origin: None, message_id: Default::default(), topic: None };
2738 for asset in assets.inner() {
2739 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2744 &dest,
2745 asset,
2746 &dummy_context,
2747 )
2748 .map_err(|e| {
2749 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?asset, ?dest, "Failed can_check_out asset");
2750 Error::<T>::CannotCheckOutTeleport
2751 })?;
2752 }
2753 for asset in assets.inner() {
2754 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2757 &dest,
2758 asset,
2759 &dummy_context,
2760 );
2761 }
2762
2763 let mut local_execute_xcm = Xcm(vec![
2765 WithdrawAsset(assets.clone()),
2767 BurnAsset(assets),
2769 ]);
2770 let mut xcm_on_dest = Xcm(vec![
2772 ReceiveTeleportedAsset(reanchored_assets),
2774 ClearOrigin,
2776 ]);
2777 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2779
2780 let custom_remote_xcm = match beneficiary {
2782 Either::Right(custom_xcm) => custom_xcm,
2783 Either::Left(beneficiary) => {
2784 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2786 },
2787 };
2788 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2789
2790 Ok((local_execute_xcm, xcm_on_dest))
2791 }
2792
2793 pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
2795 match fees.fun {
2796 Fungible(amount) => {
2797 let fee1 = amount.saturating_div(2);
2798 let fee2 = amount.saturating_sub(fee1);
2799 ensure!(fee1 > 0, Error::<T>::FeesNotMet);
2800 ensure!(fee2 > 0, Error::<T>::FeesNotMet);
2801 Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
2802 },
2803 NonFungible(_) => Err(Error::<T>::FeesNotMet),
2804 }
2805 }
2806
2807 pub(crate) fn lazy_migration(
2810 mut stage: VersionMigrationStage,
2811 weight_cutoff: Weight,
2812 ) -> (Weight, Option<VersionMigrationStage>) {
2813 let mut weight_used = Weight::zero();
2814
2815 let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
2816 let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
2817 let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
2818 let vnt_notify_weight = T::WeightInfo::notify_current_targets();
2819 let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
2820 let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
2821 let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
2822
2823 use VersionMigrationStage::*;
2824
2825 if stage == MigrateSupportedVersion {
2826 for v in 0..XCM_VERSION {
2829 for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
2830 if let Ok(new_key) = old_key.into_latest() {
2831 SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
2832 }
2833 weight_used.saturating_accrue(sv_migrate_weight);
2834 if weight_used.any_gte(weight_cutoff) {
2835 return (weight_used, Some(stage))
2836 }
2837 }
2838 }
2839 stage = MigrateVersionNotifiers;
2840 }
2841 if stage == MigrateVersionNotifiers {
2842 for v in 0..XCM_VERSION {
2843 for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
2844 if let Ok(new_key) = old_key.into_latest() {
2845 VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
2846 }
2847 weight_used.saturating_accrue(vn_migrate_weight);
2848 if weight_used.any_gte(weight_cutoff) {
2849 return (weight_used, Some(stage))
2850 }
2851 }
2852 }
2853 stage = NotifyCurrentTargets(None);
2854 }
2855
2856 let xcm_version = T::AdvertisedXcmVersion::get();
2857
2858 if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
2859 let mut iter = match maybe_last_raw_key {
2860 Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
2861 None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
2862 };
2863 while let Some((key, value)) = iter.next() {
2864 let (query_id, max_weight, target_xcm_version) = value;
2865 let new_key: Location = match key.clone().try_into() {
2866 Ok(k) if target_xcm_version != xcm_version => k,
2867 _ => {
2868 weight_used.saturating_accrue(vnt_already_notified_weight);
2871 continue
2872 },
2873 };
2874 let response = Response::Version(xcm_version);
2875 let message =
2876 Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
2877 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2878 Ok((message_id, cost)) => {
2879 let value = (query_id, max_weight, xcm_version);
2880 VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
2881 Event::VersionChangeNotified {
2882 destination: new_key,
2883 result: xcm_version,
2884 cost,
2885 message_id,
2886 }
2887 },
2888 Err(e) => {
2889 VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
2890 Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
2891 },
2892 };
2893 Self::deposit_event(event);
2894 weight_used.saturating_accrue(vnt_notify_weight);
2895 if weight_used.any_gte(weight_cutoff) {
2896 let last = Some(iter.last_raw_key().into());
2897 return (weight_used, Some(NotifyCurrentTargets(last)))
2898 }
2899 }
2900 stage = MigrateAndNotifyOldTargets;
2901 }
2902 if stage == MigrateAndNotifyOldTargets {
2903 for v in 0..XCM_VERSION {
2904 for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
2905 let (query_id, max_weight, target_xcm_version) = value;
2906 let new_key = match Location::try_from(old_key.clone()) {
2907 Ok(k) => k,
2908 Err(()) => {
2909 Self::deposit_event(Event::NotifyTargetMigrationFail {
2910 location: old_key,
2911 query_id: value.0,
2912 });
2913 weight_used.saturating_accrue(vnt_migrate_fail_weight);
2914 if weight_used.any_gte(weight_cutoff) {
2915 return (weight_used, Some(stage))
2916 }
2917 continue
2918 },
2919 };
2920
2921 let versioned_key = LatestVersionedLocation(&new_key);
2922 if target_xcm_version == xcm_version {
2923 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
2924 weight_used.saturating_accrue(vnt_migrate_weight);
2925 } else {
2926 let response = Response::Version(xcm_version);
2928 let message = Xcm(vec![QueryResponse {
2929 query_id,
2930 response,
2931 max_weight,
2932 querier: None,
2933 }]);
2934 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2935 Ok((message_id, cost)) => {
2936 VersionNotifyTargets::<T>::insert(
2937 XCM_VERSION,
2938 versioned_key,
2939 (query_id, max_weight, xcm_version),
2940 );
2941 Event::VersionChangeNotified {
2942 destination: new_key,
2943 result: xcm_version,
2944 cost,
2945 message_id,
2946 }
2947 },
2948 Err(e) => Event::NotifyTargetSendFail {
2949 location: new_key,
2950 query_id,
2951 error: e.into(),
2952 },
2953 };
2954 Self::deposit_event(event);
2955 weight_used.saturating_accrue(vnt_notify_migrate_weight);
2956 }
2957 if weight_used.any_gte(weight_cutoff) {
2958 return (weight_used, Some(stage))
2959 }
2960 }
2961 }
2962 }
2963 (weight_used, None)
2964 }
2965
2966 pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
2968 let dest = dest.into();
2969 let versioned_dest = VersionedLocation::from(dest.clone());
2970 let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
2971 ensure!(!already, XcmError::InvalidLocation);
2972 let query_id = QueryCounter::<T>::mutate(|q| {
2973 let r = *q;
2974 q.saturating_inc();
2975 r
2976 });
2977 let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
2979 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
2980 Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
2981 VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
2982 let query_status =
2983 QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
2984 Queries::<T>::insert(query_id, query_status);
2985 Ok(())
2986 }
2987
2988 pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
2990 let dest = dest.into();
2991 let versioned_dest = LatestVersionedLocation(&dest);
2992 let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
2993 .ok_or(XcmError::InvalidLocation)?;
2994 let (message_id, cost) =
2995 send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
2996 Self::deposit_event(Event::VersionNotifyUnrequested {
2997 destination: dest,
2998 cost,
2999 message_id,
3000 });
3001 Queries::<T>::remove(query_id);
3002 Ok(())
3003 }
3004
3005 pub fn send_xcm(
3009 interior: impl Into<Junctions>,
3010 dest: impl Into<Location>,
3011 mut message: Xcm<()>,
3012 ) -> Result<XcmHash, SendError> {
3013 let interior = interior.into();
3014 let local_origin = interior.clone().into();
3015 let dest = dest.into();
3016 let is_waived =
3017 <T::XcmExecutor as FeeManager>::is_waived(Some(&local_origin), FeeReason::ChargeFees);
3018 if interior != Junctions::Here {
3019 message.0.insert(0, DescendOrigin(interior.clone()));
3020 }
3021 tracing::debug!(target: "xcm::send_xcm", "{:?}, {:?}", dest.clone(), message.clone());
3022 let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
3023 if !is_waived {
3024 Self::charge_fees(local_origin, price).map_err(|e| {
3025 tracing::error!(
3026 target: "xcm::pallet_xcm::send_xcm",
3027 ?e,
3028 "Charging fees failed with error",
3029 );
3030 SendError::Fees
3031 })?;
3032 }
3033 T::XcmRouter::deliver(ticket)
3034 }
3035
3036 pub fn check_account() -> T::AccountId {
3037 const ID: PalletId = PalletId(*b"py/xcmch");
3038 AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
3039 }
3040
3041 pub fn dry_run_call<Runtime, Router, OriginCaller, RuntimeCall>(
3047 origin: OriginCaller,
3048 call: RuntimeCall,
3049 result_xcms_version: XcmVersion,
3050 ) -> Result<CallDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3051 where
3052 Runtime: crate::Config,
3053 Router: InspectMessageQueues,
3054 RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
3055 <RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
3056 {
3057 crate::Pallet::<Runtime>::set_record_xcm(true);
3058 Router::clear_messages();
3060 frame_system::Pallet::<Runtime>::reset_events();
3062 let result = call.dispatch(origin.into());
3063 crate::Pallet::<Runtime>::set_record_xcm(false);
3064 let local_xcm = crate::Pallet::<Runtime>::recorded_xcm()
3065 .map(|xcm| VersionedXcm::<()>::from(xcm).into_version(result_xcms_version))
3066 .transpose()
3067 .map_err(|()| {
3068 tracing::error!(
3069 target: "xcm::DryRunApi::dry_run_call",
3070 "Local xcm version conversion failed"
3071 );
3072
3073 XcmDryRunApiError::VersionedConversionFailed
3074 })?;
3075
3076 let forwarded_xcms =
3078 Self::convert_forwarded_xcms(result_xcms_version, Router::get_messages()).inspect_err(
3079 |error| {
3080 tracing::error!(
3081 target: "xcm::DryRunApi::dry_run_call",
3082 ?error, "Forwarded xcms version conversion failed with error"
3083 );
3084 },
3085 )?;
3086 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
3087 frame_system::Pallet::<Runtime>::read_events_no_consensus()
3088 .map(|record| record.event.clone())
3089 .collect();
3090 Ok(CallDryRunEffects {
3091 local_xcm: local_xcm.map(VersionedXcm::<()>::from),
3092 forwarded_xcms,
3093 emitted_events: events,
3094 execution_result: result,
3095 })
3096 }
3097
3098 pub fn dry_run_xcm<Router>(
3103 origin_location: VersionedLocation,
3104 xcm: VersionedXcm<<T as Config>::RuntimeCall>,
3105 ) -> Result<XcmDryRunEffects<<T as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3106 where
3107 Router: InspectMessageQueues,
3108 {
3109 let origin_location: Location = origin_location.try_into().map_err(|error| {
3110 tracing::error!(
3111 target: "xcm::DryRunApi::dry_run_xcm",
3112 ?error, "Location version conversion failed with error"
3113 );
3114 XcmDryRunApiError::VersionedConversionFailed
3115 })?;
3116 let xcm_version = xcm.identify_version();
3117 let xcm: Xcm<<T as Config>::RuntimeCall> = xcm.try_into().map_err(|error| {
3118 tracing::error!(
3119 target: "xcm::DryRunApi::dry_run_xcm",
3120 ?error, "Xcm version conversion failed with error"
3121 );
3122 XcmDryRunApiError::VersionedConversionFailed
3123 })?;
3124 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
3125
3126 Router::clear_messages();
3128 frame_system::Pallet::<T>::reset_events();
3129
3130 let result = <T as Config>::XcmExecutor::prepare_and_execute(
3131 origin_location,
3132 xcm,
3133 &mut hash,
3134 Weight::MAX, Weight::zero(),
3136 );
3137 let forwarded_xcms = Self::convert_forwarded_xcms(xcm_version, Router::get_messages())
3138 .inspect_err(|error| {
3139 tracing::error!(
3140 target: "xcm::DryRunApi::dry_run_xcm",
3141 ?error, "Forwarded xcms version conversion failed with error"
3142 );
3143 })?;
3144 let events: Vec<<T as frame_system::Config>::RuntimeEvent> =
3145 frame_system::Pallet::<T>::read_events_no_consensus()
3146 .map(|record| record.event.clone())
3147 .collect();
3148 Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
3149 }
3150
3151 fn convert_xcms(
3152 xcm_version: XcmVersion,
3153 xcms: Vec<VersionedXcm<()>>,
3154 ) -> Result<Vec<VersionedXcm<()>>, ()> {
3155 xcms.into_iter()
3156 .map(|xcm| xcm.into_version(xcm_version))
3157 .collect::<Result<Vec<_>, ()>>()
3158 }
3159
3160 fn convert_forwarded_xcms(
3161 xcm_version: XcmVersion,
3162 forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>,
3163 ) -> Result<Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, XcmDryRunApiError> {
3164 forwarded_xcms
3165 .into_iter()
3166 .map(|(dest, forwarded_xcms)| {
3167 let dest = dest.into_version(xcm_version)?;
3168 let forwarded_xcms = Self::convert_xcms(xcm_version, forwarded_xcms)?;
3169
3170 Ok((dest, forwarded_xcms))
3171 })
3172 .collect::<Result<Vec<_>, ()>>()
3173 .map_err(|()| {
3174 tracing::debug!(
3175 target: "xcm::pallet_xcm::convert_forwarded_xcms",
3176 "Failed to convert VersionedLocation to requested version",
3177 );
3178 XcmDryRunApiError::VersionedConversionFailed
3179 })
3180 }
3181
3182 pub fn query_acceptable_payment_assets(
3187 version: xcm::Version,
3188 asset_ids: Vec<AssetId>,
3189 ) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
3190 Ok(asset_ids
3191 .into_iter()
3192 .map(|asset_id| VersionedAssetId::from(asset_id))
3193 .filter_map(|asset_id| asset_id.into_version(version).ok())
3194 .collect())
3195 }
3196
3197 pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
3198 let message = Xcm::<()>::try_from(message.clone())
3199 .map_err(|e| {
3200 tracing::debug!(target: "xcm::pallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message");
3201 XcmPaymentApiError::VersionedConversionFailed
3202 })?;
3203
3204 T::Weigher::weight(&mut message.clone().into(), Weight::MAX).map_err(|error| {
3205 tracing::debug!(target: "xcm::pallet_xcm::query_xcm_weight", ?error, ?message, "Error when querying XCM weight");
3206 XcmPaymentApiError::WeightNotComputable
3207 })
3208 }
3209
3210 pub fn query_weight_to_asset_fee<Trader: xcm_executor::traits::WeightTrader>(
3227 weight: Weight,
3228 asset: VersionedAssetId,
3229 ) -> Result<u128, XcmPaymentApiError> {
3230 let asset: AssetId = asset.clone().try_into()
3231 .map_err(|e| {
3232 tracing::debug!(target: "xcm::pallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to convert versioned asset");
3233 XcmPaymentApiError::VersionedConversionFailed
3234 })?;
3235
3236 let max_amount = u128::MAX / 2;
3237 let max_payment: Asset = (asset.clone(), max_amount).into();
3238 let context = XcmContext::with_message_id(XcmHash::default());
3239
3240 let unspent = with_transaction(|| {
3243 let mut trader = Trader::new();
3244 let result = trader.buy_weight(weight, max_payment.into(), &context)
3245 .map_err(|e| {
3246 tracing::error!(target: "xcm::pallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to buy weight");
3247
3248 DispatchError::Other("Failed to buy weight")
3250 });
3251
3252 TransactionOutcome::Rollback(result)
3253 }).map_err(|error| {
3254 tracing::debug!(target: "xcm::pallet::query_weight_to_asset_fee", ?error, "Failed to execute transaction");
3255 XcmPaymentApiError::AssetNotFound
3256 })?;
3257
3258 let Some(unspent) = unspent.fungible.get(&asset) else {
3259 tracing::error!(target: "xcm::pallet::query_weight_to_asset_fee", ?asset, "The trader didn't return the needed fungible asset");
3260 return Err(XcmPaymentApiError::AssetNotFound);
3261 };
3262
3263 let paid = max_amount - unspent;
3264 Ok(paid)
3265 }
3266
3267 pub fn query_delivery_fees<AssetExchanger: xcm_executor::traits::AssetExchange>(
3274 destination: VersionedLocation,
3275 message: VersionedXcm<()>,
3276 versioned_asset_id: VersionedAssetId,
3277 ) -> Result<VersionedAssets, XcmPaymentApiError> {
3278 let result_version = destination.identify_version().max(message.identify_version());
3279
3280 let destination: Location = destination
3281 .clone()
3282 .try_into()
3283 .map_err(|e| {
3284 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination");
3285 XcmPaymentApiError::VersionedConversionFailed
3286 })?;
3287
3288 let message: Xcm<()> =
3289 message.clone().try_into().map_err(|e| {
3290 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message");
3291 XcmPaymentApiError::VersionedConversionFailed
3292 })?;
3293
3294 let (_, fees) = validate_send::<T::XcmRouter>(destination.clone(), message.clone()).map_err(|error| {
3295 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination");
3296 XcmPaymentApiError::Unroutable
3297 })?;
3298
3299 if fees.len() != 1 {
3301 return Err(XcmPaymentApiError::Unimplemented);
3302 }
3303
3304 let fee = fees.get(0).ok_or(XcmPaymentApiError::Unimplemented)?;
3305
3306 let asset_id = versioned_asset_id.clone().try_into().map_err(|()| {
3307 tracing::trace!(
3308 target: "xcm::xcm_runtime_apis::query_delivery_fees",
3309 "Failed to convert asset id: {versioned_asset_id:?}!"
3310 );
3311 XcmPaymentApiError::VersionedConversionFailed
3312 })?;
3313
3314 let assets_to_pay = if fee.id == asset_id {
3315 fees
3317 } else {
3318 AssetExchanger::quote_exchange_price(
3320 &fees.into(),
3321 &(asset_id, Fungible(1)).into(),
3322 true, )
3324 .ok_or(XcmPaymentApiError::AssetNotFound)?
3325 };
3326
3327 VersionedAssets::from(assets_to_pay).into_version(result_version).map_err(|e| {
3328 tracing::trace!(
3329 target: "xcm::pallet_xcm::query_delivery_fees",
3330 ?e,
3331 ?result_version,
3332 "Failed to convert fees into desired version"
3333 );
3334 XcmPaymentApiError::VersionedConversionFailed
3335 })
3336 }
3337
3338 pub fn is_trusted_reserve(
3341 asset: VersionedAsset,
3342 location: VersionedLocation,
3343 ) -> Result<bool, TrustedQueryApiError> {
3344 let location: Location = location.try_into().map_err(|e| {
3345 tracing::debug!(
3346 target: "xcm::pallet_xcm::is_trusted_reserve",
3347 ?e, "Failed to convert versioned location",
3348 );
3349 TrustedQueryApiError::VersionedLocationConversionFailed
3350 })?;
3351
3352 let a: Asset = asset.try_into().map_err(|e| {
3353 tracing::debug!(
3354 target: "xcm::pallet_xcm::is_trusted_reserve",
3355 ?e, "Failed to convert versioned asset",
3356 );
3357 TrustedQueryApiError::VersionedAssetConversionFailed
3358 })?;
3359
3360 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&a, &location))
3361 }
3362
3363 pub fn is_trusted_teleporter(
3365 asset: VersionedAsset,
3366 location: VersionedLocation,
3367 ) -> Result<bool, TrustedQueryApiError> {
3368 let location: Location = location.try_into().map_err(|e| {
3369 tracing::debug!(
3370 target: "xcm::pallet_xcm::is_trusted_teleporter",
3371 ?e, "Failed to convert versioned location",
3372 );
3373 TrustedQueryApiError::VersionedLocationConversionFailed
3374 })?;
3375 let a: Asset = asset.try_into().map_err(|e| {
3376 tracing::debug!(
3377 target: "xcm::pallet_xcm::is_trusted_teleporter",
3378 ?e, "Failed to convert versioned asset",
3379 );
3380 TrustedQueryApiError::VersionedAssetConversionFailed
3381 })?;
3382 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&a, &location))
3383 }
3384
3385 pub fn authorized_aliasers(
3387 target: VersionedLocation,
3388 ) -> Result<Vec<OriginAliaser>, AuthorizedAliasersApiError> {
3389 let desired_version = target.identify_version();
3390 let target: VersionedLocation = target.into_version(XCM_VERSION).map_err(|e| {
3392 tracing::debug!(
3393 target: "xcm::pallet_xcm::authorized_aliasers",
3394 ?e, "Failed to convert versioned location",
3395 );
3396 AuthorizedAliasersApiError::LocationVersionConversionFailed
3397 })?;
3398 Ok(AuthorizedAliases::<T>::get(&target)
3399 .map(|authorized| {
3400 authorized
3401 .aliasers
3402 .into_iter()
3403 .filter_map(|aliaser| {
3404 let OriginAliaser { location, expiry } = aliaser;
3405 location
3406 .into_version(desired_version)
3407 .map(|location| OriginAliaser { location, expiry })
3408 .ok()
3409 })
3410 .collect()
3411 })
3412 .unwrap_or_default())
3413 }
3414
3415 pub fn is_authorized_alias(
3420 origin: VersionedLocation,
3421 target: VersionedLocation,
3422 ) -> Result<bool, AuthorizedAliasersApiError> {
3423 let desired_version = target.identify_version();
3424 let origin = origin.into_version(desired_version).map_err(|e| {
3425 tracing::debug!(
3426 target: "xcm::pallet_xcm::is_authorized_alias",
3427 ?e, "mismatching origin and target versions",
3428 );
3429 AuthorizedAliasersApiError::LocationVersionConversionFailed
3430 })?;
3431 Ok(Self::authorized_aliasers(target)?.into_iter().any(|aliaser| {
3432 aliaser.location == origin &&
3435 aliaser
3436 .expiry
3437 .map(|expiry| {
3438 frame_system::Pallet::<T>::current_block_number().saturated_into::<u64>() <
3439 expiry
3440 })
3441 .unwrap_or(true)
3442 }))
3443 }
3444
3445 fn do_new_query(
3447 responder: impl Into<Location>,
3448 maybe_notify: Option<(u8, u8)>,
3449 timeout: BlockNumberFor<T>,
3450 match_querier: impl Into<Location>,
3451 ) -> u64 {
3452 QueryCounter::<T>::mutate(|q| {
3453 let r = *q;
3454 q.saturating_inc();
3455 Queries::<T>::insert(
3456 r,
3457 QueryStatus::Pending {
3458 responder: responder.into().into(),
3459 maybe_match_querier: Some(match_querier.into().into()),
3460 maybe_notify,
3461 timeout,
3462 },
3463 );
3464 r
3465 })
3466 }
3467
3468 pub fn report_outcome_notify(
3491 message: &mut Xcm<()>,
3492 responder: impl Into<Location>,
3493 notify: impl Into<<T as Config>::RuntimeCall>,
3494 timeout: BlockNumberFor<T>,
3495 ) -> Result<(), XcmError> {
3496 let responder = responder.into();
3497 let destination = T::UniversalLocation::get().invert_target(&responder).map_err(|()| {
3498 tracing::debug!(
3499 target: "xcm::pallet_xcm::report_outcome_notify",
3500 "Failed to invert responder location to universal location",
3501 );
3502 XcmError::LocationNotInvertible
3503 })?;
3504 let notify: <T as Config>::RuntimeCall = notify.into();
3505 let max_weight = notify.get_dispatch_info().call_weight;
3506 let query_id = Self::new_notify_query(responder, notify, timeout, Here);
3507 let response_info = QueryResponseInfo { destination, query_id, max_weight };
3508 let report_error = Xcm(vec![ReportError(response_info)]);
3509 message.0.insert(0, SetAppendix(report_error));
3510 Ok(())
3511 }
3512
3513 pub fn new_notify_query(
3516 responder: impl Into<Location>,
3517 notify: impl Into<<T as Config>::RuntimeCall>,
3518 timeout: BlockNumberFor<T>,
3519 match_querier: impl Into<Location>,
3520 ) -> u64 {
3521 let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
3522 "decode input is output of Call encode; Call guaranteed to have two enums; qed",
3523 );
3524 Self::do_new_query(responder, Some(notify), timeout, match_querier)
3525 }
3526
3527 fn note_unknown_version(dest: &Location) {
3530 tracing::trace!(
3531 target: "xcm::pallet_xcm::note_unknown_version",
3532 ?dest, "XCM version is unknown for destination"
3533 );
3534 let versioned_dest = VersionedLocation::from(dest.clone());
3535 VersionDiscoveryQueue::<T>::mutate(|q| {
3536 if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
3537 q[index].1.saturating_inc();
3539 } else {
3540 let _ = q.try_push((versioned_dest, 1));
3541 }
3542 });
3543 }
3544
3545 fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
3551 T::XcmExecutor::charge_fees(location.clone(), assets.clone()).map_err(|error| {
3552 tracing::debug!(
3553 target: "xcm::pallet_xcm::charge_fees", ?error,
3554 "Failed to charge fees for location with assets",
3555 );
3556 Error::<T>::FeesNotMet
3557 })?;
3558 Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
3559 Ok(())
3560 }
3561
3562 #[cfg(any(feature = "try-runtime", test))]
3572 pub fn do_try_state() -> Result<(), TryRuntimeError> {
3573 use migration::data::NeedsMigration;
3574
3575 let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
3579 {
3580 XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
3581 } else {
3582 XCM_VERSION.saturating_sub(1)
3583 };
3584
3585 ensure!(
3587 !Queries::<T>::iter_values()
3588 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3589 TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
3590 );
3591
3592 ensure!(
3594 !LockedFungibles::<T>::iter_values()
3595 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3596 TryRuntimeError::Other(
3597 "`LockedFungibles` data should be migrated to the higher xcm version!"
3598 )
3599 );
3600
3601 ensure!(
3603 !RemoteLockedFungibles::<T>::iter()
3604 .any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version) ||
3605 data.needs_migration(minimal_allowed_xcm_version)),
3606 TryRuntimeError::Other(
3607 "`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
3608 )
3609 );
3610
3611 if CurrentMigration::<T>::exists() {
3614 return Ok(())
3615 }
3616
3617 for v in 0..XCM_VERSION {
3619 ensure!(
3620 SupportedVersion::<T>::iter_prefix(v).next().is_none(),
3621 TryRuntimeError::Other(
3622 "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
3623 )
3624 );
3625 ensure!(
3626 VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
3627 TryRuntimeError::Other(
3628 "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
3629 )
3630 );
3631 ensure!(
3632 VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
3633 TryRuntimeError::Other(
3634 "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
3635 )
3636 );
3637 }
3638
3639 Ok(())
3640 }
3641}
3642
3643pub struct LockTicket<T: Config> {
3644 sovereign_account: T::AccountId,
3645 amount: BalanceOf<T>,
3646 unlocker: Location,
3647 item_index: Option<usize>,
3648}
3649
3650impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
3651 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3652 use xcm_executor::traits::LockError::UnexpectedState;
3653 let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
3654 match self.item_index {
3655 Some(index) => {
3656 ensure!(locks.len() > index, UnexpectedState);
3657 ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
3658 locks[index].0 = locks[index].0.max(self.amount);
3659 },
3660 None => {
3661 locks.try_push((self.amount, self.unlocker.into())).map_err(
3662 |(balance, location)| {
3663 tracing::debug!(
3664 target: "xcm::pallet_xcm::enact", ?balance, ?location,
3665 "Failed to lock fungibles",
3666 );
3667 UnexpectedState
3668 },
3669 )?;
3670 },
3671 }
3672 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3673 T::Currency::extend_lock(
3674 *b"py/xcmlk",
3675 &self.sovereign_account,
3676 self.amount,
3677 WithdrawReasons::all(),
3678 );
3679 Ok(())
3680 }
3681}
3682
3683pub struct UnlockTicket<T: Config> {
3684 sovereign_account: T::AccountId,
3685 amount: BalanceOf<T>,
3686 unlocker: Location,
3687}
3688
3689impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
3690 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3691 use xcm_executor::traits::LockError::UnexpectedState;
3692 let mut locks =
3693 LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
3694 let mut maybe_remove_index = None;
3695 let mut locked = BalanceOf::<T>::zero();
3696 let mut found = false;
3697 for (i, x) in locks.iter_mut().enumerate() {
3700 if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
3701 x.0 = x.0.saturating_sub(self.amount);
3702 if x.0.is_zero() {
3703 maybe_remove_index = Some(i);
3704 }
3705 found = true;
3706 }
3707 locked = locked.max(x.0);
3708 }
3709 ensure!(found, UnexpectedState);
3710 if let Some(remove_index) = maybe_remove_index {
3711 locks.swap_remove(remove_index);
3712 }
3713 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3714 let reasons = WithdrawReasons::all();
3715 T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
3716 Ok(())
3717 }
3718}
3719
3720pub struct ReduceTicket<T: Config> {
3721 key: (u32, T::AccountId, VersionedAssetId),
3722 amount: u128,
3723 locker: VersionedLocation,
3724 owner: VersionedLocation,
3725}
3726
3727impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
3728 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3729 use xcm_executor::traits::LockError::UnexpectedState;
3730 let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
3731 ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
3732 let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
3733 ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
3734 if new_amount == 0 {
3735 RemoteLockedFungibles::<T>::remove(&self.key);
3736 } else {
3737 record.amount = new_amount;
3738 RemoteLockedFungibles::<T>::insert(&self.key, &record);
3739 }
3740 Ok(())
3741 }
3742}
3743
3744impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
3745 type LockTicket = LockTicket<T>;
3746 type UnlockTicket = UnlockTicket<T>;
3747 type ReduceTicket = ReduceTicket<T>;
3748
3749 fn prepare_lock(
3750 unlocker: Location,
3751 asset: Asset,
3752 owner: Location,
3753 ) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
3754 use xcm_executor::traits::LockError::*;
3755 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3756 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3757 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3758 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3759 let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
3760 ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
3761 Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
3762 }
3763
3764 fn prepare_unlock(
3765 unlocker: Location,
3766 asset: Asset,
3767 owner: Location,
3768 ) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
3769 use xcm_executor::traits::LockError::*;
3770 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3771 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3772 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3773 let item_index =
3774 locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
3775 ensure!(locks[item_index].0 >= amount, NotLocked);
3776 Ok(UnlockTicket { sovereign_account, amount, unlocker })
3777 }
3778
3779 fn note_unlockable(
3780 locker: Location,
3781 asset: Asset,
3782 mut owner: Location,
3783 ) -> Result<(), xcm_executor::traits::LockError> {
3784 use xcm_executor::traits::LockError::*;
3785 ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
3786 let amount = match asset.fun {
3787 Fungible(a) => a,
3788 NonFungible(_) => return Err(Unimplemented),
3789 };
3790 owner.remove_network_id();
3791 let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3792 let locker = locker.into();
3793 let owner = owner.into();
3794 let id: VersionedAssetId = asset.id.into();
3795 let key = (XCM_VERSION, account, id);
3796 let mut record =
3797 RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
3798 if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
3799 ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
3801 record.consumers = old.consumers;
3802 record.amount = record.amount.max(old.amount);
3803 }
3804 RemoteLockedFungibles::<T>::insert(&key, record);
3805 Ok(())
3806 }
3807
3808 fn prepare_reduce_unlockable(
3809 locker: Location,
3810 asset: Asset,
3811 mut owner: Location,
3812 ) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
3813 use xcm_executor::traits::LockError::*;
3814 let amount = match asset.fun {
3815 Fungible(a) => a,
3816 NonFungible(_) => return Err(Unimplemented),
3817 };
3818 owner.remove_network_id();
3819 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3820 let locker = locker.into();
3821 let owner = owner.into();
3822 let id: VersionedAssetId = asset.id.into();
3823 let key = (XCM_VERSION, sovereign_account, id);
3824
3825 let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
3826 ensure!(locker == record.locker && owner == record.owner, WouldClobber);
3828 ensure!(record.amount >= amount, NotEnoughLocked);
3829 ensure!(
3830 record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
3831 InUse
3832 );
3833 Ok(ReduceTicket { key, amount, locker, owner })
3834 }
3835}
3836
3837impl<T: Config> WrapVersion for Pallet<T> {
3838 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
3839 dest: &Location,
3840 xcm: impl Into<VersionedXcm<RuntimeCall>>,
3841 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
3842 Self::get_version_for(dest)
3843 .or_else(|| {
3844 Self::note_unknown_version(dest);
3845 SafeXcmVersion::<T>::get()
3846 })
3847 .ok_or_else(|| {
3848 tracing::trace!(
3849 target: "xcm::pallet_xcm::wrap_version",
3850 ?dest, "Could not determine a version to wrap XCM for destination",
3851 );
3852 ()
3853 })
3854 .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
3855 }
3856}
3857
3858impl<T: Config> GetVersion for Pallet<T> {
3859 fn get_version_for(dest: &Location) -> Option<XcmVersion> {
3860 SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
3861 }
3862}
3863
3864impl<T: Config> VersionChangeNotifier for Pallet<T> {
3865 fn start(
3874 dest: &Location,
3875 query_id: QueryId,
3876 max_weight: Weight,
3877 _context: &XcmContext,
3878 ) -> XcmResult {
3879 let versioned_dest = LatestVersionedLocation(dest);
3880 let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
3881 ensure!(!already, XcmError::InvalidLocation);
3882
3883 let xcm_version = T::AdvertisedXcmVersion::get();
3884 let response = Response::Version(xcm_version);
3885 let instruction = QueryResponse { query_id, response, max_weight, querier: None };
3886 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3887 Self::deposit_event(Event::<T>::VersionNotifyStarted {
3888 destination: dest.clone(),
3889 cost,
3890 message_id,
3891 });
3892
3893 let value = (query_id, max_weight, xcm_version);
3894 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
3895 Ok(())
3896 }
3897
3898 fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
3901 VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
3902 Ok(())
3903 }
3904
3905 fn is_subscribed(dest: &Location) -> bool {
3907 let versioned_dest = LatestVersionedLocation(dest);
3908 VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
3909 }
3910}
3911
3912impl<T: Config> DropAssets for Pallet<T> {
3913 fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
3914 if assets.is_empty() {
3915 return Weight::zero()
3916 }
3917 let versioned = VersionedAssets::from(Assets::from(assets));
3918 let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
3919 AssetTraps::<T>::mutate(hash, |n| *n += 1);
3920 Self::deposit_event(Event::AssetsTrapped {
3921 hash,
3922 origin: origin.clone(),
3923 assets: versioned,
3924 });
3925 Weight::zero()
3927 }
3928}
3929
3930impl<T: Config> ClaimAssets for Pallet<T> {
3931 fn claim_assets(
3932 origin: &Location,
3933 ticket: &Location,
3934 assets: &Assets,
3935 _context: &XcmContext,
3936 ) -> bool {
3937 let mut versioned = VersionedAssets::from(assets.clone());
3938 match ticket.unpack() {
3939 (0, [GeneralIndex(i)]) =>
3940 versioned = match versioned.into_version(*i as u32) {
3941 Ok(v) => v,
3942 Err(()) => return false,
3943 },
3944 (0, []) => (),
3945 _ => return false,
3946 };
3947 let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
3948 match AssetTraps::<T>::get(hash) {
3949 0 => return false,
3950 1 => AssetTraps::<T>::remove(hash),
3951 n => AssetTraps::<T>::insert(hash, n - 1),
3952 }
3953 Self::deposit_event(Event::AssetsClaimed {
3954 hash,
3955 origin: origin.clone(),
3956 assets: versioned,
3957 });
3958 return true
3959 }
3960}
3961
3962impl<T: Config> OnResponse for Pallet<T> {
3963 fn expecting_response(
3964 origin: &Location,
3965 query_id: QueryId,
3966 querier: Option<&Location>,
3967 ) -> bool {
3968 match Queries::<T>::get(query_id) {
3969 Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
3970 Location::try_from(responder).map_or(false, |r| origin == &r) &&
3971 maybe_match_querier.map_or(true, |match_querier| {
3972 Location::try_from(match_querier).map_or(false, |match_querier| {
3973 querier.map_or(false, |q| q == &match_querier)
3974 })
3975 }),
3976 Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
3977 Location::try_from(r).map_or(false, |r| origin == &r),
3978 _ => false,
3979 }
3980 }
3981
3982 fn on_response(
3983 origin: &Location,
3984 query_id: QueryId,
3985 querier: Option<&Location>,
3986 response: Response,
3987 max_weight: Weight,
3988 _context: &XcmContext,
3989 ) -> Weight {
3990 let origin = origin.clone();
3991 match (response, Queries::<T>::get(query_id)) {
3992 (
3993 Response::Version(v),
3994 Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
3995 ) => {
3996 let origin: Location = match expected_origin.try_into() {
3997 Ok(o) if o == origin => o,
3998 Ok(o) => {
3999 Self::deposit_event(Event::InvalidResponder {
4000 origin: origin.clone(),
4001 query_id,
4002 expected_location: Some(o),
4003 });
4004 return Weight::zero()
4005 },
4006 _ => {
4007 Self::deposit_event(Event::InvalidResponder {
4008 origin: origin.clone(),
4009 query_id,
4010 expected_location: None,
4011 });
4012 return Weight::zero()
4014 },
4015 };
4016 if !is_active {
4018 Queries::<T>::insert(
4019 query_id,
4020 QueryStatus::VersionNotifier {
4021 origin: origin.clone().into(),
4022 is_active: true,
4023 },
4024 );
4025 }
4026 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
4028 Self::deposit_event(Event::SupportedVersionChanged {
4029 location: origin,
4030 version: v,
4031 });
4032 Weight::zero()
4033 },
4034 (
4035 response,
4036 Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
4037 ) => {
4038 if let Some(match_querier) = maybe_match_querier {
4039 let match_querier = match Location::try_from(match_querier) {
4040 Ok(mq) => mq,
4041 Err(_) => {
4042 Self::deposit_event(Event::InvalidQuerierVersion {
4043 origin: origin.clone(),
4044 query_id,
4045 });
4046 return Weight::zero()
4047 },
4048 };
4049 if querier.map_or(true, |q| q != &match_querier) {
4050 Self::deposit_event(Event::InvalidQuerier {
4051 origin: origin.clone(),
4052 query_id,
4053 expected_querier: match_querier,
4054 maybe_actual_querier: querier.cloned(),
4055 });
4056 return Weight::zero()
4057 }
4058 }
4059 let responder = match Location::try_from(responder) {
4060 Ok(r) => r,
4061 Err(_) => {
4062 Self::deposit_event(Event::InvalidResponderVersion {
4063 origin: origin.clone(),
4064 query_id,
4065 });
4066 return Weight::zero()
4067 },
4068 };
4069 if origin != responder {
4070 Self::deposit_event(Event::InvalidResponder {
4071 origin: origin.clone(),
4072 query_id,
4073 expected_location: Some(responder),
4074 });
4075 return Weight::zero()
4076 }
4077 match maybe_notify {
4078 Some((pallet_index, call_index)) => {
4079 let bare = (pallet_index, call_index, query_id, response);
4083 if let Ok(call) = bare.using_encoded(|mut bytes| {
4084 <T as Config>::RuntimeCall::decode(&mut bytes)
4085 }) {
4086 Queries::<T>::remove(query_id);
4087 let weight = call.get_dispatch_info().call_weight;
4088 if weight.any_gt(max_weight) {
4089 let e = Event::NotifyOverweight {
4090 query_id,
4091 pallet_index,
4092 call_index,
4093 actual_weight: weight,
4094 max_budgeted_weight: max_weight,
4095 };
4096 Self::deposit_event(e);
4097 return Weight::zero()
4098 }
4099 let dispatch_origin = Origin::Response(origin.clone()).into();
4100 match call.dispatch(dispatch_origin) {
4101 Ok(post_info) => {
4102 let e = Event::Notified { query_id, pallet_index, call_index };
4103 Self::deposit_event(e);
4104 post_info.actual_weight
4105 },
4106 Err(error_and_info) => {
4107 let e = Event::NotifyDispatchError {
4108 query_id,
4109 pallet_index,
4110 call_index,
4111 };
4112 Self::deposit_event(e);
4113 error_and_info.post_info.actual_weight
4116 },
4117 }
4118 .unwrap_or(weight)
4119 } else {
4120 let e =
4121 Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
4122 Self::deposit_event(e);
4123 Weight::zero()
4124 }
4125 },
4126 None => {
4127 let e = Event::ResponseReady { query_id, response: response.clone() };
4128 Self::deposit_event(e);
4129 let at = frame_system::Pallet::<T>::current_block_number();
4130 let response = response.into();
4131 Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
4132 Weight::zero()
4133 },
4134 }
4135 },
4136 _ => {
4137 let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
4138 Self::deposit_event(e);
4139 Weight::zero()
4140 },
4141 }
4142 }
4143}
4144
4145impl<T: Config> CheckSuspension for Pallet<T> {
4146 fn is_suspended<Call>(
4147 _origin: &Location,
4148 _instructions: &mut [Instruction<Call>],
4149 _max_weight: Weight,
4150 _properties: &mut Properties,
4151 ) -> bool {
4152 XcmExecutionSuspended::<T>::get()
4153 }
4154}
4155
4156impl<T: Config> RecordXcm for Pallet<T> {
4157 fn should_record() -> bool {
4158 ShouldRecordXcm::<T>::get()
4159 }
4160
4161 fn set_record_xcm(enabled: bool) {
4162 ShouldRecordXcm::<T>::put(enabled);
4163 }
4164
4165 fn recorded_xcm() -> Option<Xcm<()>> {
4166 RecordedXcm::<T>::get()
4167 }
4168
4169 fn record(xcm: Xcm<()>) {
4170 RecordedXcm::<T>::put(xcm);
4171 }
4172}
4173
4174pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4178where
4179 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4180{
4181 match o.into() {
4182 Ok(Origin::Xcm(location)) => Ok(location),
4183 _ => Err(BadOrigin),
4184 }
4185}
4186
4187pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4191where
4192 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4193{
4194 match o.into() {
4195 Ok(Origin::Response(location)) => Ok(location),
4196 _ => Err(BadOrigin),
4197 }
4198}
4199
4200pub struct AuthorizedAliasers<T>(PhantomData<T>);
4206impl<L: Into<VersionedLocation> + Clone, T: Config> ContainsPair<L, L> for AuthorizedAliasers<T> {
4207 fn contains(origin: &L, target: &L) -> bool {
4208 let origin: VersionedLocation = origin.clone().into();
4209 let target: VersionedLocation = target.clone().into();
4210 tracing::trace!(target: "xcm::pallet_xcm::AuthorizedAliasers::contains", ?origin, ?target);
4211 Pallet::<T>::is_authorized_alias(origin, target).unwrap_or(false)
4214 }
4215}
4216
4217pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4222impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
4223 for IsMajorityOfBody<Prefix, Body>
4224{
4225 fn contains(l: &Location) -> bool {
4226 let maybe_suffix = l.match_and_split(&Prefix::get());
4227 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
4228 }
4229}
4230
4231pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4235impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
4236 fn contains(l: &Location) -> bool {
4237 let maybe_suffix = l.match_and_split(&Prefix::get());
4238 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
4239 }
4240}
4241
4242pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
4245impl<
4246 O: OriginTrait + From<Origin>,
4247 F: Contains<L>,
4248 L: TryFrom<Location> + TryInto<Location> + Clone,
4249 > EnsureOrigin<O> for EnsureXcm<F, L>
4250where
4251 for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4252{
4253 type Success = L;
4254
4255 fn try_origin(outer: O) -> Result<Self::Success, O> {
4256 match outer.caller().try_into() {
4257 Ok(Origin::Xcm(ref location)) =>
4258 if let Ok(location) = location.clone().try_into() {
4259 if F::contains(&location) {
4260 return Ok(location);
4261 }
4262 },
4263 _ => (),
4264 }
4265
4266 Err(outer)
4267 }
4268
4269 #[cfg(feature = "runtime-benchmarks")]
4270 fn try_successful_origin() -> Result<O, ()> {
4271 Ok(O::from(Origin::Xcm(Here.into())))
4272 }
4273}
4274
4275pub struct EnsureResponse<F>(PhantomData<F>);
4278impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
4279where
4280 for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4281{
4282 type Success = Location;
4283
4284 fn try_origin(outer: O) -> Result<Self::Success, O> {
4285 match outer.caller().try_into() {
4286 Ok(Origin::Response(responder)) => return Ok(responder.clone()),
4287 _ => (),
4288 }
4289
4290 Err(outer)
4291 }
4292
4293 #[cfg(feature = "runtime-benchmarks")]
4294 fn try_successful_origin() -> Result<O, ()> {
4295 Ok(O::from(Origin::Response(Here.into())))
4296 }
4297}
4298
4299pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
4302impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
4303 for XcmPassthrough<RuntimeOrigin>
4304{
4305 fn convert_origin(
4306 origin: impl Into<Location>,
4307 kind: OriginKind,
4308 ) -> Result<RuntimeOrigin, Location> {
4309 let origin = origin.into();
4310 match kind {
4311 OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
4312 _ => Err(origin),
4313 }
4314 }
4315}