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