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