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