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 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2081 origin.clone(),
2082 dest.clone(),
2083 Either::Left(beneficiary),
2084 assets,
2085 assets_transfer_type,
2086 FeesHandling::Batched { fees },
2087 weight_limit,
2088 )?;
2089 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2090 }
2091
2092 fn do_teleport_assets(
2093 origin: OriginFor<T>,
2094 dest: Box<VersionedLocation>,
2095 beneficiary: Box<VersionedLocation>,
2096 assets: Box<VersionedAssets>,
2097 fee_asset_item: u32,
2098 weight_limit: WeightLimit,
2099 ) -> DispatchResult {
2100 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2101 let dest = (*dest).try_into().map_err(|()| {
2102 tracing::debug!(
2103 target: "xcm::pallet_xcm::do_teleport_assets",
2104 "Failed to convert destination VersionedLocation",
2105 );
2106 Error::<T>::BadVersion
2107 })?;
2108 let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2109 tracing::debug!(
2110 target: "xcm::pallet_xcm::do_teleport_assets",
2111 "Failed to convert beneficiary VersionedLocation",
2112 );
2113 Error::<T>::BadVersion
2114 })?;
2115 let assets: Assets = (*assets).try_into().map_err(|()| {
2116 tracing::debug!(
2117 target: "xcm::pallet_xcm::do_teleport_assets",
2118 "Failed to convert VersionedAssets",
2119 );
2120 Error::<T>::BadVersion
2121 })?;
2122 tracing::debug!(
2123 target: "xcm::pallet_xcm::do_teleport_assets",
2124 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
2125 );
2126
2127 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2128 let value = (origin_location, assets.into_inner());
2129 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2130 let (origin_location, assets) = value;
2131 for asset in assets.iter() {
2132 let transfer_type =
2133 T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
2134 ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
2135 }
2136 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
2137
2138 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2139 origin_location.clone(),
2140 dest.clone(),
2141 Either::Left(beneficiary),
2142 assets,
2143 TransferType::Teleport,
2144 FeesHandling::Batched { fees },
2145 weight_limit,
2146 )?;
2147 Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
2148 }
2149
2150 fn do_transfer_assets(
2151 origin: Location,
2152 dest: Location,
2153 beneficiary: Either<Location, Xcm<()>>,
2154 mut assets: Vec<Asset>,
2155 assets_transfer_type: TransferType,
2156 fee_asset_index: usize,
2157 fees_transfer_type: TransferType,
2158 weight_limit: WeightLimit,
2159 ) -> DispatchResult {
2160 let fees = if fees_transfer_type == assets_transfer_type {
2162 let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
2163 FeesHandling::Batched { fees }
2165 } else {
2166 ensure!(
2172 !matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
2173 Error::<T>::InvalidAssetUnsupportedReserve
2174 );
2175 let weight_limit = weight_limit.clone();
2176 let fees = assets.remove(fee_asset_index);
2179 let (local_xcm, remote_xcm) = match fees_transfer_type {
2180 TransferType::LocalReserve => Self::local_reserve_fees_instructions(
2181 origin.clone(),
2182 dest.clone(),
2183 fees,
2184 weight_limit,
2185 )?,
2186 TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
2187 origin.clone(),
2188 dest.clone(),
2189 fees,
2190 weight_limit,
2191 )?,
2192 TransferType::Teleport => Self::teleport_fees_instructions(
2193 origin.clone(),
2194 dest.clone(),
2195 fees,
2196 weight_limit,
2197 )?,
2198 TransferType::RemoteReserve(_) =>
2199 return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2200 };
2201 FeesHandling::Separate { local_xcm, remote_xcm }
2202 };
2203
2204 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2205 origin.clone(),
2206 dest.clone(),
2207 beneficiary,
2208 assets,
2209 assets_transfer_type,
2210 fees,
2211 weight_limit,
2212 )?;
2213 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2214 }
2215
2216 fn build_xcm_transfer_type(
2217 origin: Location,
2218 dest: Location,
2219 beneficiary: Either<Location, Xcm<()>>,
2220 assets: Vec<Asset>,
2221 transfer_type: TransferType,
2222 fees: FeesHandling<T>,
2223 weight_limit: WeightLimit,
2224 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
2225 tracing::debug!(
2226 target: "xcm::pallet_xcm::build_xcm_transfer_type",
2227 ?origin, ?dest, ?beneficiary, ?assets, ?transfer_type, ?fees, ?weight_limit,
2228 );
2229 match transfer_type {
2230 TransferType::LocalReserve => Self::local_reserve_transfer_programs(
2231 origin.clone(),
2232 dest.clone(),
2233 beneficiary,
2234 assets,
2235 fees,
2236 weight_limit,
2237 )
2238 .map(|(local, remote)| (local, Some(remote))),
2239 TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
2240 origin.clone(),
2241 dest.clone(),
2242 beneficiary,
2243 assets,
2244 fees,
2245 weight_limit,
2246 )
2247 .map(|(local, remote)| (local, Some(remote))),
2248 TransferType::RemoteReserve(reserve) => {
2249 let fees = match fees {
2250 FeesHandling::Batched { fees } => fees,
2251 _ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2252 };
2253 Self::remote_reserve_transfer_program(
2254 origin.clone(),
2255 reserve.try_into().map_err(|()| {
2256 tracing::debug!(
2257 target: "xcm::pallet_xcm::build_xcm_transfer_type",
2258 "Failed to convert remote reserve location",
2259 );
2260 Error::<T>::BadVersion
2261 })?,
2262 beneficiary,
2263 dest.clone(),
2264 assets,
2265 fees,
2266 weight_limit,
2267 )
2268 .map(|local| (local, None))
2269 },
2270 TransferType::Teleport => Self::teleport_assets_program(
2271 origin.clone(),
2272 dest.clone(),
2273 beneficiary,
2274 assets,
2275 fees,
2276 weight_limit,
2277 )
2278 .map(|(local, remote)| (local, Some(remote))),
2279 }
2280 }
2281
2282 fn execute_xcm_transfer(
2283 origin: Location,
2284 dest: Location,
2285 mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
2286 remote_xcm: Option<Xcm<()>>,
2287 ) -> DispatchResult {
2288 tracing::debug!(
2289 target: "xcm::pallet_xcm::execute_xcm_transfer",
2290 ?origin, ?dest, ?local_xcm, ?remote_xcm,
2291 );
2292
2293 let weight =
2294 T::Weigher::weight(&mut local_xcm, Weight::MAX).map_err(|error| {
2295 tracing::debug!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, "Failed to calculate weight");
2296 Error::<T>::UnweighableMessage
2297 })?;
2298 let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
2299 let outcome = T::XcmExecutor::prepare_and_execute(
2300 origin.clone(),
2301 local_xcm,
2302 &mut hash,
2303 weight,
2304 weight,
2305 );
2306 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
2307 outcome.clone().ensure_complete().map_err(|error| {
2308 tracing::error!(
2309 target: "xcm::pallet_xcm::execute_xcm_transfer",
2310 ?error, "XCM execution failed with error with outcome: {:?}", outcome
2311 );
2312 Error::<T>::LocalExecutionIncompleteWithError {
2313 index: error.index,
2314 error: error.error.into(),
2315 }
2316 })?;
2317
2318 if let Some(remote_xcm) = remote_xcm {
2319 let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
2320 .map_err(|error| {
2321 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error");
2322 Error::<T>::from(error)
2323 })?;
2324 if origin != Here.into_location() {
2325 Self::charge_fees(origin.clone(), price.clone()).map_err(|error| {
2326 tracing::error!(
2327 target: "xcm::pallet_xcm::execute_xcm_transfer",
2328 ?error, ?price, ?origin, "Unable to charge fee",
2329 );
2330 Error::<T>::FeesNotMet
2331 })?;
2332 }
2333 let message_id = T::XcmRouter::deliver(ticket)
2334 .map_err(|error| {
2335 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error");
2336 Error::<T>::from(error)
2337 })?;
2338
2339 let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
2340 Self::deposit_event(e);
2341 }
2342 Ok(())
2343 }
2344
2345 fn add_fees_to_xcm(
2346 dest: Location,
2347 fees: FeesHandling<T>,
2348 weight_limit: WeightLimit,
2349 local: &mut Xcm<<T as Config>::RuntimeCall>,
2350 remote: &mut Xcm<()>,
2351 ) -> Result<(), Error<T>> {
2352 match fees {
2353 FeesHandling::Batched { fees } => {
2354 let context = T::UniversalLocation::get();
2355 let reanchored_fees =
2358 fees.reanchored(&dest, &context).map_err(|e| {
2359 tracing::error!(target: "xcm::pallet_xcm::add_fees_to_xcm", ?e, ?dest, ?context, "Failed to re-anchor fees");
2360 Error::<T>::CannotReanchor
2361 })?;
2362 remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
2364 },
2365 FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
2366 core::mem::swap(local, &mut local_fees);
2369 core::mem::swap(remote, &mut remote_fees);
2370 local.inner_mut().append(&mut local_fees.into_inner());
2372 remote.inner_mut().append(&mut remote_fees.into_inner());
2373 },
2374 }
2375 Ok(())
2376 }
2377
2378 fn local_reserve_fees_instructions(
2379 origin: Location,
2380 dest: Location,
2381 fees: Asset,
2382 weight_limit: WeightLimit,
2383 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2384 let value = (origin, vec![fees.clone()]);
2385 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2386
2387 let context = T::UniversalLocation::get();
2388 let reanchored_fees = fees.clone().reanchored(&dest, &context).map_err(|_| {
2389 tracing::debug!(
2390 target: "xcm::pallet_xcm::local_reserve_fees_instructions",
2391 "Failed to re-anchor fees",
2392 );
2393 Error::<T>::CannotReanchor
2394 })?;
2395
2396 let local_execute_xcm = Xcm(vec![
2397 TransferAsset { assets: fees.into(), beneficiary: dest },
2399 ]);
2400 let xcm_on_dest = Xcm(vec![
2401 ReserveAssetDeposited(reanchored_fees.clone().into()),
2403 BuyExecution { fees: reanchored_fees, weight_limit },
2405 ]);
2406 Ok((local_execute_xcm, xcm_on_dest))
2407 }
2408
2409 fn local_reserve_transfer_programs(
2410 origin: Location,
2411 dest: Location,
2412 beneficiary: Either<Location, Xcm<()>>,
2413 assets: Vec<Asset>,
2414 fees: FeesHandling<T>,
2415 weight_limit: WeightLimit,
2416 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2417 let value = (origin, assets);
2418 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2419 let (_, assets) = value;
2420
2421 let max_assets =
2423 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2424 let assets: Assets = assets.into();
2425 let context = T::UniversalLocation::get();
2426 let mut reanchored_assets = assets.clone();
2427 reanchored_assets
2428 .reanchor(&dest, &context)
2429 .map_err(|e| {
2430 tracing::error!(target: "xcm::pallet_xcm::local_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2431 Error::<T>::CannotReanchor
2432 })?;
2433
2434 let mut local_execute_xcm = Xcm(vec![
2436 TransferAsset { assets, beneficiary: dest.clone() },
2438 ]);
2439 let mut xcm_on_dest = Xcm(vec![
2441 ReserveAssetDeposited(reanchored_assets),
2443 ClearOrigin,
2445 ]);
2446 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2448
2449 let custom_remote_xcm = match beneficiary {
2451 Either::Right(custom_xcm) => custom_xcm,
2452 Either::Left(beneficiary) => {
2453 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2455 },
2456 };
2457 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2458
2459 Ok((local_execute_xcm, xcm_on_dest))
2460 }
2461
2462 fn destination_reserve_fees_instructions(
2463 origin: Location,
2464 dest: Location,
2465 fees: Asset,
2466 weight_limit: WeightLimit,
2467 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2468 let value = (origin, vec![fees.clone()]);
2469 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2470 ensure!(
2471 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&fees, &dest),
2472 Error::<T>::InvalidAssetUnsupportedReserve
2473 );
2474
2475 let context = T::UniversalLocation::get();
2476 let reanchored_fees = fees
2477 .clone()
2478 .reanchored(&dest, &context)
2479 .map_err(|e| {
2480 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_fees_instructions", ?e, ?dest,?context, "Failed to re-anchor fees");
2481 Error::<T>::CannotReanchor
2482 })?;
2483 let fees: Assets = fees.into();
2484
2485 let local_execute_xcm = Xcm(vec![
2486 WithdrawAsset(fees.clone()),
2488 BurnAsset(fees),
2490 ]);
2491 let xcm_on_dest = Xcm(vec![
2492 WithdrawAsset(reanchored_fees.clone().into()),
2494 BuyExecution { fees: reanchored_fees, weight_limit },
2496 ]);
2497 Ok((local_execute_xcm, xcm_on_dest))
2498 }
2499
2500 fn destination_reserve_transfer_programs(
2501 origin: Location,
2502 dest: Location,
2503 beneficiary: Either<Location, Xcm<()>>,
2504 assets: Vec<Asset>,
2505 fees: FeesHandling<T>,
2506 weight_limit: WeightLimit,
2507 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2508 let value = (origin, assets);
2509 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2510 let (_, assets) = value;
2511 for asset in assets.iter() {
2512 ensure!(
2513 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&asset, &dest),
2514 Error::<T>::InvalidAssetUnsupportedReserve
2515 );
2516 }
2517
2518 let max_assets =
2520 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2521 let assets: Assets = assets.into();
2522 let context = T::UniversalLocation::get();
2523 let mut reanchored_assets = assets.clone();
2524 reanchored_assets
2525 .reanchor(&dest, &context)
2526 .map_err(|e| {
2527 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2528 Error::<T>::CannotReanchor
2529 })?;
2530
2531 let mut local_execute_xcm = Xcm(vec![
2533 WithdrawAsset(assets.clone()),
2535 BurnAsset(assets),
2537 ]);
2538 let mut xcm_on_dest = Xcm(vec![
2540 WithdrawAsset(reanchored_assets),
2542 ClearOrigin,
2544 ]);
2545 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2547
2548 let custom_remote_xcm = match beneficiary {
2550 Either::Right(custom_xcm) => custom_xcm,
2551 Either::Left(beneficiary) => {
2552 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2554 },
2555 };
2556 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2557
2558 Ok((local_execute_xcm, xcm_on_dest))
2559 }
2560
2561 fn remote_reserve_transfer_program(
2563 origin: Location,
2564 reserve: Location,
2565 beneficiary: Either<Location, Xcm<()>>,
2566 dest: Location,
2567 assets: Vec<Asset>,
2568 fees: Asset,
2569 weight_limit: WeightLimit,
2570 ) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
2571 let value = (origin, assets);
2572 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2573 let (_, assets) = value;
2574
2575 let max_assets = assets.len() as u32;
2576 let context = T::UniversalLocation::get();
2577 let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
2580 let reserve_fees = fees_half_1
2582 .reanchored(&reserve, &context)
2583 .map_err(|e| {
2584 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor reserve_fees");
2585 Error::<T>::CannotReanchor
2586 })?;
2587 let dest_fees = fees_half_2
2589 .reanchored(&dest, &context)
2590 .map_err(|e| {
2591 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?dest, ?context, "Failed to re-anchor dest_fees");
2592 Error::<T>::CannotReanchor
2593 })?;
2594 let dest = dest.reanchored(&reserve, &context).map_err(|e| {
2596 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor dest");
2597 Error::<T>::CannotReanchor
2598 })?;
2599 let mut xcm_on_dest =
2601 Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
2602 let custom_xcm_on_dest = match beneficiary {
2604 Either::Right(custom_xcm) => custom_xcm,
2605 Either::Left(beneficiary) => {
2606 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2608 },
2609 };
2610 xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
2611 let xcm_on_reserve = Xcm(vec![
2613 BuyExecution { fees: reserve_fees, weight_limit },
2614 DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
2615 ]);
2616 Ok(Xcm(vec![
2617 WithdrawAsset(assets.into()),
2618 SetFeesMode { jit_withdraw: true },
2619 InitiateReserveWithdraw {
2620 assets: Wild(AllCounted(max_assets)),
2621 reserve,
2622 xcm: xcm_on_reserve,
2623 },
2624 ]))
2625 }
2626
2627 fn teleport_fees_instructions(
2628 origin: Location,
2629 dest: Location,
2630 fees: Asset,
2631 weight_limit: WeightLimit,
2632 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2633 let value = (origin, vec![fees.clone()]);
2634 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2635 ensure!(
2636 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&fees, &dest),
2637 Error::<T>::Filtered
2638 );
2639
2640 let context = T::UniversalLocation::get();
2641 let reanchored_fees = fees
2642 .clone()
2643 .reanchored(&dest, &context)
2644 .map_err(|e| {
2645 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?dest, ?context, "Failed to re-anchor fees");
2646 Error::<T>::CannotReanchor
2647 })?;
2648
2649 let dummy_context =
2651 XcmContext { origin: None, message_id: Default::default(), topic: None };
2652 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2657 &dest,
2658 &fees,
2659 &dummy_context,
2660 )
2661 .map_err(|e| {
2662 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?fees, ?dest, "Failed can_check_out");
2663 Error::<T>::CannotCheckOutTeleport
2664 })?;
2665 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2668 &dest,
2669 &fees,
2670 &dummy_context,
2671 );
2672
2673 let fees: Assets = fees.into();
2674 let local_execute_xcm = Xcm(vec![
2675 WithdrawAsset(fees.clone()),
2677 BurnAsset(fees),
2679 ]);
2680 let xcm_on_dest = Xcm(vec![
2681 ReceiveTeleportedAsset(reanchored_fees.clone().into()),
2683 BuyExecution { fees: reanchored_fees, weight_limit },
2685 ]);
2686 Ok((local_execute_xcm, xcm_on_dest))
2687 }
2688
2689 fn teleport_assets_program(
2690 origin: Location,
2691 dest: Location,
2692 beneficiary: Either<Location, Xcm<()>>,
2693 assets: Vec<Asset>,
2694 fees: FeesHandling<T>,
2695 weight_limit: WeightLimit,
2696 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2697 let value = (origin, assets);
2698 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2699 let (_, assets) = value;
2700 for asset in assets.iter() {
2701 ensure!(
2702 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&asset, &dest),
2703 Error::<T>::Filtered
2704 );
2705 }
2706
2707 let max_assets =
2709 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2710 let context = T::UniversalLocation::get();
2711 let assets: Assets = assets.into();
2712 let mut reanchored_assets = assets.clone();
2713 reanchored_assets
2714 .reanchor(&dest, &context)
2715 .map_err(|e| {
2716 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?dest, ?context, "Failed to re-anchor asset");
2717 Error::<T>::CannotReanchor
2718 })?;
2719
2720 let dummy_context =
2722 XcmContext { origin: None, message_id: Default::default(), topic: None };
2723 for asset in assets.inner() {
2724 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2729 &dest,
2730 asset,
2731 &dummy_context,
2732 )
2733 .map_err(|e| {
2734 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?asset, ?dest, "Failed can_check_out asset");
2735 Error::<T>::CannotCheckOutTeleport
2736 })?;
2737 }
2738 for asset in assets.inner() {
2739 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2742 &dest,
2743 asset,
2744 &dummy_context,
2745 );
2746 }
2747
2748 let mut local_execute_xcm = Xcm(vec![
2750 WithdrawAsset(assets.clone()),
2752 BurnAsset(assets),
2754 ]);
2755 let mut xcm_on_dest = Xcm(vec![
2757 ReceiveTeleportedAsset(reanchored_assets),
2759 ClearOrigin,
2761 ]);
2762 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2764
2765 let custom_remote_xcm = match beneficiary {
2767 Either::Right(custom_xcm) => custom_xcm,
2768 Either::Left(beneficiary) => {
2769 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2771 },
2772 };
2773 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2774
2775 Ok((local_execute_xcm, xcm_on_dest))
2776 }
2777
2778 pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
2780 match fees.fun {
2781 Fungible(amount) => {
2782 let fee1 = amount.saturating_div(2);
2783 let fee2 = amount.saturating_sub(fee1);
2784 ensure!(fee1 > 0, Error::<T>::FeesNotMet);
2785 ensure!(fee2 > 0, Error::<T>::FeesNotMet);
2786 Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
2787 },
2788 NonFungible(_) => Err(Error::<T>::FeesNotMet),
2789 }
2790 }
2791
2792 pub(crate) fn lazy_migration(
2795 mut stage: VersionMigrationStage,
2796 weight_cutoff: Weight,
2797 ) -> (Weight, Option<VersionMigrationStage>) {
2798 let mut weight_used = Weight::zero();
2799
2800 let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
2801 let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
2802 let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
2803 let vnt_notify_weight = T::WeightInfo::notify_current_targets();
2804 let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
2805 let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
2806 let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
2807
2808 use VersionMigrationStage::*;
2809
2810 if stage == MigrateSupportedVersion {
2811 for v in 0..XCM_VERSION {
2814 for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
2815 if let Ok(new_key) = old_key.into_latest() {
2816 SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
2817 }
2818 weight_used.saturating_accrue(sv_migrate_weight);
2819 if weight_used.any_gte(weight_cutoff) {
2820 return (weight_used, Some(stage))
2821 }
2822 }
2823 }
2824 stage = MigrateVersionNotifiers;
2825 }
2826 if stage == MigrateVersionNotifiers {
2827 for v in 0..XCM_VERSION {
2828 for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
2829 if let Ok(new_key) = old_key.into_latest() {
2830 VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
2831 }
2832 weight_used.saturating_accrue(vn_migrate_weight);
2833 if weight_used.any_gte(weight_cutoff) {
2834 return (weight_used, Some(stage))
2835 }
2836 }
2837 }
2838 stage = NotifyCurrentTargets(None);
2839 }
2840
2841 let xcm_version = T::AdvertisedXcmVersion::get();
2842
2843 if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
2844 let mut iter = match maybe_last_raw_key {
2845 Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
2846 None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
2847 };
2848 while let Some((key, value)) = iter.next() {
2849 let (query_id, max_weight, target_xcm_version) = value;
2850 let new_key: Location = match key.clone().try_into() {
2851 Ok(k) if target_xcm_version != xcm_version => k,
2852 _ => {
2853 weight_used.saturating_accrue(vnt_already_notified_weight);
2856 continue
2857 },
2858 };
2859 let response = Response::Version(xcm_version);
2860 let message =
2861 Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
2862 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2863 Ok((message_id, cost)) => {
2864 let value = (query_id, max_weight, xcm_version);
2865 VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
2866 Event::VersionChangeNotified {
2867 destination: new_key,
2868 result: xcm_version,
2869 cost,
2870 message_id,
2871 }
2872 },
2873 Err(e) => {
2874 VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
2875 Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
2876 },
2877 };
2878 Self::deposit_event(event);
2879 weight_used.saturating_accrue(vnt_notify_weight);
2880 if weight_used.any_gte(weight_cutoff) {
2881 let last = Some(iter.last_raw_key().into());
2882 return (weight_used, Some(NotifyCurrentTargets(last)))
2883 }
2884 }
2885 stage = MigrateAndNotifyOldTargets;
2886 }
2887 if stage == MigrateAndNotifyOldTargets {
2888 for v in 0..XCM_VERSION {
2889 for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
2890 let (query_id, max_weight, target_xcm_version) = value;
2891 let new_key = match Location::try_from(old_key.clone()) {
2892 Ok(k) => k,
2893 Err(()) => {
2894 Self::deposit_event(Event::NotifyTargetMigrationFail {
2895 location: old_key,
2896 query_id: value.0,
2897 });
2898 weight_used.saturating_accrue(vnt_migrate_fail_weight);
2899 if weight_used.any_gte(weight_cutoff) {
2900 return (weight_used, Some(stage))
2901 }
2902 continue
2903 },
2904 };
2905
2906 let versioned_key = LatestVersionedLocation(&new_key);
2907 if target_xcm_version == xcm_version {
2908 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
2909 weight_used.saturating_accrue(vnt_migrate_weight);
2910 } else {
2911 let response = Response::Version(xcm_version);
2913 let message = Xcm(vec![QueryResponse {
2914 query_id,
2915 response,
2916 max_weight,
2917 querier: None,
2918 }]);
2919 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2920 Ok((message_id, cost)) => {
2921 VersionNotifyTargets::<T>::insert(
2922 XCM_VERSION,
2923 versioned_key,
2924 (query_id, max_weight, xcm_version),
2925 );
2926 Event::VersionChangeNotified {
2927 destination: new_key,
2928 result: xcm_version,
2929 cost,
2930 message_id,
2931 }
2932 },
2933 Err(e) => Event::NotifyTargetSendFail {
2934 location: new_key,
2935 query_id,
2936 error: e.into(),
2937 },
2938 };
2939 Self::deposit_event(event);
2940 weight_used.saturating_accrue(vnt_notify_migrate_weight);
2941 }
2942 if weight_used.any_gte(weight_cutoff) {
2943 return (weight_used, Some(stage))
2944 }
2945 }
2946 }
2947 }
2948 (weight_used, None)
2949 }
2950
2951 pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
2953 let dest = dest.into();
2954 let versioned_dest = VersionedLocation::from(dest.clone());
2955 let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
2956 ensure!(!already, XcmError::InvalidLocation);
2957 let query_id = QueryCounter::<T>::mutate(|q| {
2958 let r = *q;
2959 q.saturating_inc();
2960 r
2961 });
2962 let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
2964 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
2965 Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
2966 VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
2967 let query_status =
2968 QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
2969 Queries::<T>::insert(query_id, query_status);
2970 Ok(())
2971 }
2972
2973 pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
2975 let dest = dest.into();
2976 let versioned_dest = LatestVersionedLocation(&dest);
2977 let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
2978 .ok_or(XcmError::InvalidLocation)?;
2979 let (message_id, cost) =
2980 send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
2981 Self::deposit_event(Event::VersionNotifyUnrequested {
2982 destination: dest,
2983 cost,
2984 message_id,
2985 });
2986 Queries::<T>::remove(query_id);
2987 Ok(())
2988 }
2989
2990 pub fn send_xcm(
2994 interior: impl Into<Junctions>,
2995 dest: impl Into<Location>,
2996 mut message: Xcm<()>,
2997 ) -> Result<XcmHash, SendError> {
2998 let interior = interior.into();
2999 let local_origin = interior.clone().into();
3000 let dest = dest.into();
3001 let is_waived =
3002 <T::XcmExecutor as FeeManager>::is_waived(Some(&local_origin), FeeReason::ChargeFees);
3003 if interior != Junctions::Here {
3004 message.0.insert(0, DescendOrigin(interior.clone()));
3005 }
3006 tracing::debug!(target: "xcm::send_xcm", "{:?}, {:?}", dest.clone(), message.clone());
3007 let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
3008 if !is_waived {
3009 Self::charge_fees(local_origin, price).map_err(|e| {
3010 tracing::error!(
3011 target: "xcm::pallet_xcm::send_xcm",
3012 ?e,
3013 "Charging fees failed with error",
3014 );
3015 SendError::Fees
3016 })?;
3017 }
3018 T::XcmRouter::deliver(ticket)
3019 }
3020
3021 pub fn check_account() -> T::AccountId {
3022 const ID: PalletId = PalletId(*b"py/xcmch");
3023 AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
3024 }
3025
3026 pub fn dry_run_call<Runtime, Router, OriginCaller, RuntimeCall>(
3032 origin: OriginCaller,
3033 call: RuntimeCall,
3034 result_xcms_version: XcmVersion,
3035 ) -> Result<CallDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3036 where
3037 Runtime: crate::Config,
3038 Router: InspectMessageQueues,
3039 RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
3040 <RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
3041 {
3042 crate::Pallet::<Runtime>::set_record_xcm(true);
3043 Router::clear_messages();
3045 frame_system::Pallet::<Runtime>::reset_events();
3047 let result = call.dispatch(origin.into());
3048 crate::Pallet::<Runtime>::set_record_xcm(false);
3049 let local_xcm = crate::Pallet::<Runtime>::recorded_xcm()
3050 .map(|xcm| VersionedXcm::<()>::from(xcm).into_version(result_xcms_version))
3051 .transpose()
3052 .map_err(|()| {
3053 tracing::error!(
3054 target: "xcm::DryRunApi::dry_run_call",
3055 "Local xcm version conversion failed"
3056 );
3057
3058 XcmDryRunApiError::VersionedConversionFailed
3059 })?;
3060
3061 let forwarded_xcms =
3063 Self::convert_forwarded_xcms(result_xcms_version, Router::get_messages()).inspect_err(
3064 |error| {
3065 tracing::error!(
3066 target: "xcm::DryRunApi::dry_run_call",
3067 ?error, "Forwarded xcms version conversion failed with error"
3068 );
3069 },
3070 )?;
3071 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
3072 frame_system::Pallet::<Runtime>::read_events_no_consensus()
3073 .map(|record| record.event.clone())
3074 .collect();
3075 Ok(CallDryRunEffects {
3076 local_xcm: local_xcm.map(VersionedXcm::<()>::from),
3077 forwarded_xcms,
3078 emitted_events: events,
3079 execution_result: result,
3080 })
3081 }
3082
3083 pub fn dry_run_xcm<Runtime, Router, RuntimeCall: Decode + GetDispatchInfo, XcmConfig>(
3088 origin_location: VersionedLocation,
3089 xcm: VersionedXcm<RuntimeCall>,
3090 ) -> Result<XcmDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3091 where
3092 Runtime: frame_system::Config,
3093 Router: InspectMessageQueues,
3094 XcmConfig: xcm_executor::Config<RuntimeCall = RuntimeCall>,
3095 {
3096 let origin_location: Location = origin_location.try_into().map_err(|error| {
3097 tracing::error!(
3098 target: "xcm::DryRunApi::dry_run_xcm",
3099 ?error, "Location version conversion failed with error"
3100 );
3101 XcmDryRunApiError::VersionedConversionFailed
3102 })?;
3103 let xcm_version = xcm.identify_version();
3104 let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| {
3105 tracing::error!(
3106 target: "xcm::DryRunApi::dry_run_xcm",
3107 ?error, "Xcm version conversion failed with error"
3108 );
3109 XcmDryRunApiError::VersionedConversionFailed
3110 })?;
3111 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
3112
3113 Router::clear_messages();
3115 frame_system::Pallet::<Runtime>::reset_events();
3116
3117 let result = xcm_executor::XcmExecutor::<XcmConfig>::prepare_and_execute(
3118 origin_location,
3119 xcm,
3120 &mut hash,
3121 Weight::MAX, Weight::zero(),
3123 );
3124 let forwarded_xcms = Self::convert_forwarded_xcms(xcm_version, Router::get_messages())
3125 .inspect_err(|error| {
3126 tracing::error!(
3127 target: "xcm::DryRunApi::dry_run_xcm",
3128 ?error, "Forwarded xcms version conversion failed with error"
3129 );
3130 })?;
3131 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
3132 frame_system::Pallet::<Runtime>::read_events_no_consensus()
3133 .map(|record| record.event.clone())
3134 .collect();
3135 Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
3136 }
3137
3138 fn convert_xcms(
3139 xcm_version: XcmVersion,
3140 xcms: Vec<VersionedXcm<()>>,
3141 ) -> Result<Vec<VersionedXcm<()>>, ()> {
3142 xcms.into_iter()
3143 .map(|xcm| xcm.into_version(xcm_version))
3144 .collect::<Result<Vec<_>, ()>>()
3145 }
3146
3147 fn convert_forwarded_xcms(
3148 xcm_version: XcmVersion,
3149 forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>,
3150 ) -> Result<Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, XcmDryRunApiError> {
3151 forwarded_xcms
3152 .into_iter()
3153 .map(|(dest, forwarded_xcms)| {
3154 let dest = dest.into_version(xcm_version)?;
3155 let forwarded_xcms = Self::convert_xcms(xcm_version, forwarded_xcms)?;
3156
3157 Ok((dest, forwarded_xcms))
3158 })
3159 .collect::<Result<Vec<_>, ()>>()
3160 .map_err(|()| {
3161 tracing::debug!(
3162 target: "xcm::pallet_xcm::convert_forwarded_xcms",
3163 "Failed to convert VersionedLocation to requested version",
3164 );
3165 XcmDryRunApiError::VersionedConversionFailed
3166 })
3167 }
3168
3169 pub fn query_acceptable_payment_assets(
3174 version: xcm::Version,
3175 asset_ids: Vec<AssetId>,
3176 ) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
3177 Ok(asset_ids
3178 .into_iter()
3179 .map(|asset_id| VersionedAssetId::from(asset_id))
3180 .filter_map(|asset_id| asset_id.into_version(version).ok())
3181 .collect())
3182 }
3183
3184 pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
3185 let message = Xcm::<()>::try_from(message.clone())
3186 .map_err(|e| {
3187 tracing::debug!(target: "xcm::pallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message");
3188 XcmPaymentApiError::VersionedConversionFailed
3189 })?;
3190
3191 T::Weigher::weight(&mut message.clone().into(), Weight::MAX).map_err(|error| {
3192 tracing::debug!(target: "xcm::pallet_xcm::query_xcm_weight", ?error, ?message, "Error when querying XCM weight");
3193 XcmPaymentApiError::WeightNotComputable
3194 })
3195 }
3196
3197 pub fn query_weight_to_asset_fee<Trader: xcm_executor::traits::WeightTrader>(
3214 weight: Weight,
3215 asset: VersionedAssetId,
3216 ) -> Result<u128, XcmPaymentApiError> {
3217 let asset: AssetId = asset.clone().try_into()
3218 .map_err(|e| {
3219 tracing::debug!(target: "xcm::pallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to convert versioned asset");
3220 XcmPaymentApiError::VersionedConversionFailed
3221 })?;
3222
3223 let max_amount = u128::MAX / 2;
3224 let max_payment: Asset = (asset.clone(), max_amount).into();
3225 let context = XcmContext::with_message_id(XcmHash::default());
3226
3227 let unspent = with_transaction(|| {
3230 let mut trader = Trader::new();
3231 let result = trader.buy_weight(weight, max_payment.into(), &context)
3232 .map_err(|e| {
3233 tracing::error!(target: "xcm::pallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to buy weight");
3234
3235 DispatchError::Other("Failed to buy weight")
3237 });
3238
3239 TransactionOutcome::Rollback(result)
3240 }).map_err(|error| {
3241 tracing::debug!(target: "xcm::pallet::query_weight_to_asset_fee", ?error, "Failed to execute transaction");
3242 XcmPaymentApiError::AssetNotFound
3243 })?;
3244
3245 let Some(unspent) = unspent.fungible.get(&asset) else {
3246 tracing::error!(target: "xcm::pallet::query_weight_to_asset_fee", ?asset, "The trader didn't return the needed fungible asset");
3247 return Err(XcmPaymentApiError::AssetNotFound);
3248 };
3249
3250 let paid = max_amount - unspent;
3251 Ok(paid)
3252 }
3253
3254 pub fn query_delivery_fees(
3256 destination: VersionedLocation,
3257 message: VersionedXcm<()>,
3258 ) -> Result<VersionedAssets, XcmPaymentApiError> {
3259 let result_version = destination.identify_version().max(message.identify_version());
3260
3261 let destination: Location = destination
3262 .clone()
3263 .try_into()
3264 .map_err(|e| {
3265 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination");
3266 XcmPaymentApiError::VersionedConversionFailed
3267 })?;
3268
3269 let message: Xcm<()> =
3270 message.clone().try_into().map_err(|e| {
3271 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message");
3272 XcmPaymentApiError::VersionedConversionFailed
3273 })?;
3274
3275 let (_, fees) = validate_send::<T::XcmRouter>(destination.clone(), message.clone()).map_err(|error| {
3276 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination");
3277 XcmPaymentApiError::Unroutable
3278 })?;
3279
3280 VersionedAssets::from(fees)
3281 .into_version(result_version)
3282 .map_err(|e| {
3283 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?result_version, "Failed to convert fees into version");
3284 XcmPaymentApiError::VersionedConversionFailed
3285 })
3286 }
3287
3288 pub fn is_trusted_reserve(
3291 asset: VersionedAsset,
3292 location: VersionedLocation,
3293 ) -> Result<bool, TrustedQueryApiError> {
3294 let location: Location = location.try_into().map_err(|e| {
3295 tracing::debug!(
3296 target: "xcm::pallet_xcm::is_trusted_reserve",
3297 ?e, "Failed to convert versioned location",
3298 );
3299 TrustedQueryApiError::VersionedLocationConversionFailed
3300 })?;
3301
3302 let a: Asset = asset.try_into().map_err(|e| {
3303 tracing::debug!(
3304 target: "xcm::pallet_xcm::is_trusted_reserve",
3305 ?e, "Failed to convert versioned asset",
3306 );
3307 TrustedQueryApiError::VersionedAssetConversionFailed
3308 })?;
3309
3310 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&a, &location))
3311 }
3312
3313 pub fn is_trusted_teleporter(
3315 asset: VersionedAsset,
3316 location: VersionedLocation,
3317 ) -> Result<bool, TrustedQueryApiError> {
3318 let location: Location = location.try_into().map_err(|e| {
3319 tracing::debug!(
3320 target: "xcm::pallet_xcm::is_trusted_teleporter",
3321 ?e, "Failed to convert versioned location",
3322 );
3323 TrustedQueryApiError::VersionedLocationConversionFailed
3324 })?;
3325 let a: Asset = asset.try_into().map_err(|e| {
3326 tracing::debug!(
3327 target: "xcm::pallet_xcm::is_trusted_teleporter",
3328 ?e, "Failed to convert versioned asset",
3329 );
3330 TrustedQueryApiError::VersionedAssetConversionFailed
3331 })?;
3332 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&a, &location))
3333 }
3334
3335 pub fn authorized_aliasers(
3337 target: VersionedLocation,
3338 ) -> Result<Vec<OriginAliaser>, AuthorizedAliasersApiError> {
3339 let desired_version = target.identify_version();
3340 let target: VersionedLocation = target.into_version(XCM_VERSION).map_err(|e| {
3342 tracing::debug!(
3343 target: "xcm::pallet_xcm::authorized_aliasers",
3344 ?e, "Failed to convert versioned location",
3345 );
3346 AuthorizedAliasersApiError::LocationVersionConversionFailed
3347 })?;
3348 Ok(AuthorizedAliases::<T>::get(&target)
3349 .map(|authorized| {
3350 authorized
3351 .aliasers
3352 .into_iter()
3353 .filter_map(|aliaser| {
3354 let OriginAliaser { location, expiry } = aliaser;
3355 location
3356 .into_version(desired_version)
3357 .map(|location| OriginAliaser { location, expiry })
3358 .ok()
3359 })
3360 .collect()
3361 })
3362 .unwrap_or_default())
3363 }
3364
3365 pub fn is_authorized_alias(
3370 origin: VersionedLocation,
3371 target: VersionedLocation,
3372 ) -> Result<bool, AuthorizedAliasersApiError> {
3373 let desired_version = target.identify_version();
3374 let origin = origin.into_version(desired_version).map_err(|e| {
3375 tracing::debug!(
3376 target: "xcm::pallet_xcm::is_authorized_alias",
3377 ?e, "mismatching origin and target versions",
3378 );
3379 AuthorizedAliasersApiError::LocationVersionConversionFailed
3380 })?;
3381 Ok(Self::authorized_aliasers(target)?.into_iter().any(|aliaser| {
3382 aliaser.location == origin &&
3385 aliaser
3386 .expiry
3387 .map(|expiry| {
3388 frame_system::Pallet::<T>::current_block_number().saturated_into::<u64>() <
3389 expiry
3390 })
3391 .unwrap_or(true)
3392 }))
3393 }
3394
3395 fn do_new_query(
3397 responder: impl Into<Location>,
3398 maybe_notify: Option<(u8, u8)>,
3399 timeout: BlockNumberFor<T>,
3400 match_querier: impl Into<Location>,
3401 ) -> u64 {
3402 QueryCounter::<T>::mutate(|q| {
3403 let r = *q;
3404 q.saturating_inc();
3405 Queries::<T>::insert(
3406 r,
3407 QueryStatus::Pending {
3408 responder: responder.into().into(),
3409 maybe_match_querier: Some(match_querier.into().into()),
3410 maybe_notify,
3411 timeout,
3412 },
3413 );
3414 r
3415 })
3416 }
3417
3418 pub fn report_outcome_notify(
3441 message: &mut Xcm<()>,
3442 responder: impl Into<Location>,
3443 notify: impl Into<<T as Config>::RuntimeCall>,
3444 timeout: BlockNumberFor<T>,
3445 ) -> Result<(), XcmError> {
3446 let responder = responder.into();
3447 let destination = T::UniversalLocation::get().invert_target(&responder).map_err(|()| {
3448 tracing::debug!(
3449 target: "xcm::pallet_xcm::report_outcome_notify",
3450 "Failed to invert responder location to universal location",
3451 );
3452 XcmError::LocationNotInvertible
3453 })?;
3454 let notify: <T as Config>::RuntimeCall = notify.into();
3455 let max_weight = notify.get_dispatch_info().call_weight;
3456 let query_id = Self::new_notify_query(responder, notify, timeout, Here);
3457 let response_info = QueryResponseInfo { destination, query_id, max_weight };
3458 let report_error = Xcm(vec![ReportError(response_info)]);
3459 message.0.insert(0, SetAppendix(report_error));
3460 Ok(())
3461 }
3462
3463 pub fn new_notify_query(
3466 responder: impl Into<Location>,
3467 notify: impl Into<<T as Config>::RuntimeCall>,
3468 timeout: BlockNumberFor<T>,
3469 match_querier: impl Into<Location>,
3470 ) -> u64 {
3471 let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
3472 "decode input is output of Call encode; Call guaranteed to have two enums; qed",
3473 );
3474 Self::do_new_query(responder, Some(notify), timeout, match_querier)
3475 }
3476
3477 fn note_unknown_version(dest: &Location) {
3480 tracing::trace!(
3481 target: "xcm::pallet_xcm::note_unknown_version",
3482 ?dest, "XCM version is unknown for destination"
3483 );
3484 let versioned_dest = VersionedLocation::from(dest.clone());
3485 VersionDiscoveryQueue::<T>::mutate(|q| {
3486 if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
3487 q[index].1.saturating_inc();
3489 } else {
3490 let _ = q.try_push((versioned_dest, 1));
3491 }
3492 });
3493 }
3494
3495 fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
3501 T::XcmExecutor::charge_fees(location.clone(), assets.clone()).map_err(|error| {
3502 tracing::debug!(
3503 target: "xcm::pallet_xcm::charge_fees", ?error,
3504 "Failed to charge fees for location with assets",
3505 );
3506 Error::<T>::FeesNotMet
3507 })?;
3508 Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
3509 Ok(())
3510 }
3511
3512 #[cfg(any(feature = "try-runtime", test))]
3522 pub fn do_try_state() -> Result<(), TryRuntimeError> {
3523 use migration::data::NeedsMigration;
3524
3525 let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
3529 {
3530 XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
3531 } else {
3532 XCM_VERSION.saturating_sub(1)
3533 };
3534
3535 ensure!(
3537 !Queries::<T>::iter_values()
3538 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3539 TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
3540 );
3541
3542 ensure!(
3544 !LockedFungibles::<T>::iter_values()
3545 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3546 TryRuntimeError::Other(
3547 "`LockedFungibles` data should be migrated to the higher xcm version!"
3548 )
3549 );
3550
3551 ensure!(
3553 !RemoteLockedFungibles::<T>::iter()
3554 .any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version) ||
3555 data.needs_migration(minimal_allowed_xcm_version)),
3556 TryRuntimeError::Other(
3557 "`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
3558 )
3559 );
3560
3561 if CurrentMigration::<T>::exists() {
3564 return Ok(())
3565 }
3566
3567 for v in 0..XCM_VERSION {
3569 ensure!(
3570 SupportedVersion::<T>::iter_prefix(v).next().is_none(),
3571 TryRuntimeError::Other(
3572 "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
3573 )
3574 );
3575 ensure!(
3576 VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
3577 TryRuntimeError::Other(
3578 "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
3579 )
3580 );
3581 ensure!(
3582 VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
3583 TryRuntimeError::Other(
3584 "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
3585 )
3586 );
3587 }
3588
3589 Ok(())
3590 }
3591}
3592
3593pub struct LockTicket<T: Config> {
3594 sovereign_account: T::AccountId,
3595 amount: BalanceOf<T>,
3596 unlocker: Location,
3597 item_index: Option<usize>,
3598}
3599
3600impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
3601 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3602 use xcm_executor::traits::LockError::UnexpectedState;
3603 let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
3604 match self.item_index {
3605 Some(index) => {
3606 ensure!(locks.len() > index, UnexpectedState);
3607 ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
3608 locks[index].0 = locks[index].0.max(self.amount);
3609 },
3610 None => {
3611 locks.try_push((self.amount, self.unlocker.into())).map_err(
3612 |(balance, location)| {
3613 tracing::debug!(
3614 target: "xcm::pallet_xcm::enact", ?balance, ?location,
3615 "Failed to lock fungibles",
3616 );
3617 UnexpectedState
3618 },
3619 )?;
3620 },
3621 }
3622 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3623 T::Currency::extend_lock(
3624 *b"py/xcmlk",
3625 &self.sovereign_account,
3626 self.amount,
3627 WithdrawReasons::all(),
3628 );
3629 Ok(())
3630 }
3631}
3632
3633pub struct UnlockTicket<T: Config> {
3634 sovereign_account: T::AccountId,
3635 amount: BalanceOf<T>,
3636 unlocker: Location,
3637}
3638
3639impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
3640 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3641 use xcm_executor::traits::LockError::UnexpectedState;
3642 let mut locks =
3643 LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
3644 let mut maybe_remove_index = None;
3645 let mut locked = BalanceOf::<T>::zero();
3646 let mut found = false;
3647 for (i, x) in locks.iter_mut().enumerate() {
3650 if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
3651 x.0 = x.0.saturating_sub(self.amount);
3652 if x.0.is_zero() {
3653 maybe_remove_index = Some(i);
3654 }
3655 found = true;
3656 }
3657 locked = locked.max(x.0);
3658 }
3659 ensure!(found, UnexpectedState);
3660 if let Some(remove_index) = maybe_remove_index {
3661 locks.swap_remove(remove_index);
3662 }
3663 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3664 let reasons = WithdrawReasons::all();
3665 T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
3666 Ok(())
3667 }
3668}
3669
3670pub struct ReduceTicket<T: Config> {
3671 key: (u32, T::AccountId, VersionedAssetId),
3672 amount: u128,
3673 locker: VersionedLocation,
3674 owner: VersionedLocation,
3675}
3676
3677impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
3678 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3679 use xcm_executor::traits::LockError::UnexpectedState;
3680 let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
3681 ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
3682 let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
3683 ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
3684 if new_amount == 0 {
3685 RemoteLockedFungibles::<T>::remove(&self.key);
3686 } else {
3687 record.amount = new_amount;
3688 RemoteLockedFungibles::<T>::insert(&self.key, &record);
3689 }
3690 Ok(())
3691 }
3692}
3693
3694impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
3695 type LockTicket = LockTicket<T>;
3696 type UnlockTicket = UnlockTicket<T>;
3697 type ReduceTicket = ReduceTicket<T>;
3698
3699 fn prepare_lock(
3700 unlocker: Location,
3701 asset: Asset,
3702 owner: Location,
3703 ) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
3704 use xcm_executor::traits::LockError::*;
3705 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3706 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3707 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3708 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3709 let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
3710 ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
3711 Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
3712 }
3713
3714 fn prepare_unlock(
3715 unlocker: Location,
3716 asset: Asset,
3717 owner: Location,
3718 ) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
3719 use xcm_executor::traits::LockError::*;
3720 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3721 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3722 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3723 let item_index =
3724 locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
3725 ensure!(locks[item_index].0 >= amount, NotLocked);
3726 Ok(UnlockTicket { sovereign_account, amount, unlocker })
3727 }
3728
3729 fn note_unlockable(
3730 locker: Location,
3731 asset: Asset,
3732 mut owner: Location,
3733 ) -> Result<(), xcm_executor::traits::LockError> {
3734 use xcm_executor::traits::LockError::*;
3735 ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
3736 let amount = match asset.fun {
3737 Fungible(a) => a,
3738 NonFungible(_) => return Err(Unimplemented),
3739 };
3740 owner.remove_network_id();
3741 let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3742 let locker = locker.into();
3743 let owner = owner.into();
3744 let id: VersionedAssetId = asset.id.into();
3745 let key = (XCM_VERSION, account, id);
3746 let mut record =
3747 RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
3748 if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
3749 ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
3751 record.consumers = old.consumers;
3752 record.amount = record.amount.max(old.amount);
3753 }
3754 RemoteLockedFungibles::<T>::insert(&key, record);
3755 Ok(())
3756 }
3757
3758 fn prepare_reduce_unlockable(
3759 locker: Location,
3760 asset: Asset,
3761 mut owner: Location,
3762 ) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
3763 use xcm_executor::traits::LockError::*;
3764 let amount = match asset.fun {
3765 Fungible(a) => a,
3766 NonFungible(_) => return Err(Unimplemented),
3767 };
3768 owner.remove_network_id();
3769 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3770 let locker = locker.into();
3771 let owner = owner.into();
3772 let id: VersionedAssetId = asset.id.into();
3773 let key = (XCM_VERSION, sovereign_account, id);
3774
3775 let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
3776 ensure!(locker == record.locker && owner == record.owner, WouldClobber);
3778 ensure!(record.amount >= amount, NotEnoughLocked);
3779 ensure!(
3780 record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
3781 InUse
3782 );
3783 Ok(ReduceTicket { key, amount, locker, owner })
3784 }
3785}
3786
3787impl<T: Config> WrapVersion for Pallet<T> {
3788 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
3789 dest: &Location,
3790 xcm: impl Into<VersionedXcm<RuntimeCall>>,
3791 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
3792 Self::get_version_for(dest)
3793 .or_else(|| {
3794 Self::note_unknown_version(dest);
3795 SafeXcmVersion::<T>::get()
3796 })
3797 .ok_or_else(|| {
3798 tracing::trace!(
3799 target: "xcm::pallet_xcm::wrap_version",
3800 ?dest, "Could not determine a version to wrap XCM for destination",
3801 );
3802 ()
3803 })
3804 .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
3805 }
3806}
3807
3808impl<T: Config> GetVersion for Pallet<T> {
3809 fn get_version_for(dest: &Location) -> Option<XcmVersion> {
3810 SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
3811 }
3812}
3813
3814impl<T: Config> VersionChangeNotifier for Pallet<T> {
3815 fn start(
3824 dest: &Location,
3825 query_id: QueryId,
3826 max_weight: Weight,
3827 _context: &XcmContext,
3828 ) -> XcmResult {
3829 let versioned_dest = LatestVersionedLocation(dest);
3830 let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
3831 ensure!(!already, XcmError::InvalidLocation);
3832
3833 let xcm_version = T::AdvertisedXcmVersion::get();
3834 let response = Response::Version(xcm_version);
3835 let instruction = QueryResponse { query_id, response, max_weight, querier: None };
3836 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3837 Self::deposit_event(Event::<T>::VersionNotifyStarted {
3838 destination: dest.clone(),
3839 cost,
3840 message_id,
3841 });
3842
3843 let value = (query_id, max_weight, xcm_version);
3844 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
3845 Ok(())
3846 }
3847
3848 fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
3851 VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
3852 Ok(())
3853 }
3854
3855 fn is_subscribed(dest: &Location) -> bool {
3857 let versioned_dest = LatestVersionedLocation(dest);
3858 VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
3859 }
3860}
3861
3862impl<T: Config> DropAssets for Pallet<T> {
3863 fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
3864 if assets.is_empty() {
3865 return Weight::zero()
3866 }
3867 let versioned = VersionedAssets::from(Assets::from(assets));
3868 let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
3869 AssetTraps::<T>::mutate(hash, |n| *n += 1);
3870 Self::deposit_event(Event::AssetsTrapped {
3871 hash,
3872 origin: origin.clone(),
3873 assets: versioned,
3874 });
3875 Weight::zero()
3877 }
3878}
3879
3880impl<T: Config> ClaimAssets for Pallet<T> {
3881 fn claim_assets(
3882 origin: &Location,
3883 ticket: &Location,
3884 assets: &Assets,
3885 _context: &XcmContext,
3886 ) -> bool {
3887 let mut versioned = VersionedAssets::from(assets.clone());
3888 match ticket.unpack() {
3889 (0, [GeneralIndex(i)]) =>
3890 versioned = match versioned.into_version(*i as u32) {
3891 Ok(v) => v,
3892 Err(()) => return false,
3893 },
3894 (0, []) => (),
3895 _ => return false,
3896 };
3897 let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
3898 match AssetTraps::<T>::get(hash) {
3899 0 => return false,
3900 1 => AssetTraps::<T>::remove(hash),
3901 n => AssetTraps::<T>::insert(hash, n - 1),
3902 }
3903 Self::deposit_event(Event::AssetsClaimed {
3904 hash,
3905 origin: origin.clone(),
3906 assets: versioned,
3907 });
3908 return true
3909 }
3910}
3911
3912impl<T: Config> OnResponse for Pallet<T> {
3913 fn expecting_response(
3914 origin: &Location,
3915 query_id: QueryId,
3916 querier: Option<&Location>,
3917 ) -> bool {
3918 match Queries::<T>::get(query_id) {
3919 Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
3920 Location::try_from(responder).map_or(false, |r| origin == &r) &&
3921 maybe_match_querier.map_or(true, |match_querier| {
3922 Location::try_from(match_querier).map_or(false, |match_querier| {
3923 querier.map_or(false, |q| q == &match_querier)
3924 })
3925 }),
3926 Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
3927 Location::try_from(r).map_or(false, |r| origin == &r),
3928 _ => false,
3929 }
3930 }
3931
3932 fn on_response(
3933 origin: &Location,
3934 query_id: QueryId,
3935 querier: Option<&Location>,
3936 response: Response,
3937 max_weight: Weight,
3938 _context: &XcmContext,
3939 ) -> Weight {
3940 let origin = origin.clone();
3941 match (response, Queries::<T>::get(query_id)) {
3942 (
3943 Response::Version(v),
3944 Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
3945 ) => {
3946 let origin: Location = match expected_origin.try_into() {
3947 Ok(o) if o == origin => o,
3948 Ok(o) => {
3949 Self::deposit_event(Event::InvalidResponder {
3950 origin: origin.clone(),
3951 query_id,
3952 expected_location: Some(o),
3953 });
3954 return Weight::zero()
3955 },
3956 _ => {
3957 Self::deposit_event(Event::InvalidResponder {
3958 origin: origin.clone(),
3959 query_id,
3960 expected_location: None,
3961 });
3962 return Weight::zero()
3964 },
3965 };
3966 if !is_active {
3968 Queries::<T>::insert(
3969 query_id,
3970 QueryStatus::VersionNotifier {
3971 origin: origin.clone().into(),
3972 is_active: true,
3973 },
3974 );
3975 }
3976 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
3978 Self::deposit_event(Event::SupportedVersionChanged {
3979 location: origin,
3980 version: v,
3981 });
3982 Weight::zero()
3983 },
3984 (
3985 response,
3986 Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
3987 ) => {
3988 if let Some(match_querier) = maybe_match_querier {
3989 let match_querier = match Location::try_from(match_querier) {
3990 Ok(mq) => mq,
3991 Err(_) => {
3992 Self::deposit_event(Event::InvalidQuerierVersion {
3993 origin: origin.clone(),
3994 query_id,
3995 });
3996 return Weight::zero()
3997 },
3998 };
3999 if querier.map_or(true, |q| q != &match_querier) {
4000 Self::deposit_event(Event::InvalidQuerier {
4001 origin: origin.clone(),
4002 query_id,
4003 expected_querier: match_querier,
4004 maybe_actual_querier: querier.cloned(),
4005 });
4006 return Weight::zero()
4007 }
4008 }
4009 let responder = match Location::try_from(responder) {
4010 Ok(r) => r,
4011 Err(_) => {
4012 Self::deposit_event(Event::InvalidResponderVersion {
4013 origin: origin.clone(),
4014 query_id,
4015 });
4016 return Weight::zero()
4017 },
4018 };
4019 if origin != responder {
4020 Self::deposit_event(Event::InvalidResponder {
4021 origin: origin.clone(),
4022 query_id,
4023 expected_location: Some(responder),
4024 });
4025 return Weight::zero()
4026 }
4027 match maybe_notify {
4028 Some((pallet_index, call_index)) => {
4029 let bare = (pallet_index, call_index, query_id, response);
4033 if let Ok(call) = bare.using_encoded(|mut bytes| {
4034 <T as Config>::RuntimeCall::decode(&mut bytes)
4035 }) {
4036 Queries::<T>::remove(query_id);
4037 let weight = call.get_dispatch_info().call_weight;
4038 if weight.any_gt(max_weight) {
4039 let e = Event::NotifyOverweight {
4040 query_id,
4041 pallet_index,
4042 call_index,
4043 actual_weight: weight,
4044 max_budgeted_weight: max_weight,
4045 };
4046 Self::deposit_event(e);
4047 return Weight::zero()
4048 }
4049 let dispatch_origin = Origin::Response(origin.clone()).into();
4050 match call.dispatch(dispatch_origin) {
4051 Ok(post_info) => {
4052 let e = Event::Notified { query_id, pallet_index, call_index };
4053 Self::deposit_event(e);
4054 post_info.actual_weight
4055 },
4056 Err(error_and_info) => {
4057 let e = Event::NotifyDispatchError {
4058 query_id,
4059 pallet_index,
4060 call_index,
4061 };
4062 Self::deposit_event(e);
4063 error_and_info.post_info.actual_weight
4066 },
4067 }
4068 .unwrap_or(weight)
4069 } else {
4070 let e =
4071 Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
4072 Self::deposit_event(e);
4073 Weight::zero()
4074 }
4075 },
4076 None => {
4077 let e = Event::ResponseReady { query_id, response: response.clone() };
4078 Self::deposit_event(e);
4079 let at = frame_system::Pallet::<T>::current_block_number();
4080 let response = response.into();
4081 Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
4082 Weight::zero()
4083 },
4084 }
4085 },
4086 _ => {
4087 let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
4088 Self::deposit_event(e);
4089 Weight::zero()
4090 },
4091 }
4092 }
4093}
4094
4095impl<T: Config> CheckSuspension for Pallet<T> {
4096 fn is_suspended<Call>(
4097 _origin: &Location,
4098 _instructions: &mut [Instruction<Call>],
4099 _max_weight: Weight,
4100 _properties: &mut Properties,
4101 ) -> bool {
4102 XcmExecutionSuspended::<T>::get()
4103 }
4104}
4105
4106impl<T: Config> RecordXcm for Pallet<T> {
4107 fn should_record() -> bool {
4108 ShouldRecordXcm::<T>::get()
4109 }
4110
4111 fn set_record_xcm(enabled: bool) {
4112 ShouldRecordXcm::<T>::put(enabled);
4113 }
4114
4115 fn recorded_xcm() -> Option<Xcm<()>> {
4116 RecordedXcm::<T>::get()
4117 }
4118
4119 fn record(xcm: Xcm<()>) {
4120 RecordedXcm::<T>::put(xcm);
4121 }
4122}
4123
4124pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4128where
4129 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4130{
4131 match o.into() {
4132 Ok(Origin::Xcm(location)) => Ok(location),
4133 _ => Err(BadOrigin),
4134 }
4135}
4136
4137pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4141where
4142 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4143{
4144 match o.into() {
4145 Ok(Origin::Response(location)) => Ok(location),
4146 _ => Err(BadOrigin),
4147 }
4148}
4149
4150pub struct AuthorizedAliasers<T>(PhantomData<T>);
4156impl<L: Into<VersionedLocation> + Clone, T: Config> ContainsPair<L, L> for AuthorizedAliasers<T> {
4157 fn contains(origin: &L, target: &L) -> bool {
4158 let origin: VersionedLocation = origin.clone().into();
4159 let target: VersionedLocation = target.clone().into();
4160 tracing::trace!(target: "xcm::pallet_xcm::AuthorizedAliasers::contains", ?origin, ?target);
4161 Pallet::<T>::is_authorized_alias(origin, target).unwrap_or(false)
4164 }
4165}
4166
4167pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4172impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
4173 for IsMajorityOfBody<Prefix, Body>
4174{
4175 fn contains(l: &Location) -> bool {
4176 let maybe_suffix = l.match_and_split(&Prefix::get());
4177 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
4178 }
4179}
4180
4181pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4185impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
4186 fn contains(l: &Location) -> bool {
4187 let maybe_suffix = l.match_and_split(&Prefix::get());
4188 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
4189 }
4190}
4191
4192pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
4195impl<
4196 O: OriginTrait + From<Origin>,
4197 F: Contains<L>,
4198 L: TryFrom<Location> + TryInto<Location> + Clone,
4199 > EnsureOrigin<O> for EnsureXcm<F, L>
4200where
4201 for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4202{
4203 type Success = L;
4204
4205 fn try_origin(outer: O) -> Result<Self::Success, O> {
4206 match outer.caller().try_into() {
4207 Ok(Origin::Xcm(ref location)) =>
4208 if let Ok(location) = location.clone().try_into() {
4209 if F::contains(&location) {
4210 return Ok(location);
4211 }
4212 },
4213 _ => (),
4214 }
4215
4216 Err(outer)
4217 }
4218
4219 #[cfg(feature = "runtime-benchmarks")]
4220 fn try_successful_origin() -> Result<O, ()> {
4221 Ok(O::from(Origin::Xcm(Here.into())))
4222 }
4223}
4224
4225pub struct EnsureResponse<F>(PhantomData<F>);
4228impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
4229where
4230 for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4231{
4232 type Success = Location;
4233
4234 fn try_origin(outer: O) -> Result<Self::Success, O> {
4235 match outer.caller().try_into() {
4236 Ok(Origin::Response(responder)) => return Ok(responder.clone()),
4237 _ => (),
4238 }
4239
4240 Err(outer)
4241 }
4242
4243 #[cfg(feature = "runtime-benchmarks")]
4244 fn try_successful_origin() -> Result<O, ()> {
4245 Ok(O::from(Origin::Response(Here.into())))
4246 }
4247}
4248
4249pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
4252impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
4253 for XcmPassthrough<RuntimeOrigin>
4254{
4255 fn convert_origin(
4256 origin: impl Into<Location>,
4257 kind: OriginKind,
4258 ) -> Result<RuntimeOrigin, Location> {
4259 let origin = origin.into();
4260 match kind {
4261 OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
4262 _ => Err(origin),
4263 }
4264 }
4265}