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