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;
27
28pub mod migration;
29
30extern crate alloc;
31
32use alloc::{boxed::Box, vec, vec::Vec};
33use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
34use core::{marker::PhantomData, result::Result};
35use frame_support::{
36 dispatch::{
37 DispatchErrorWithPostInfo, GetDispatchInfo, PostDispatchInfo, WithPostDispatchInfo,
38 },
39 pallet_prelude::*,
40 traits::{
41 Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency,
42 OriginTrait, WithdrawReasons,
43 },
44 PalletId,
45};
46use frame_system::pallet_prelude::{BlockNumberFor, *};
47pub use pallet::*;
48use scale_info::TypeInfo;
49use sp_runtime::{
50 traits::{
51 AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash,
52 Saturating, Zero,
53 },
54 Either, RuntimeDebug,
55};
56use xcm::{latest::QueryResponseInfo, prelude::*};
57use xcm_builder::{
58 ExecuteController, ExecuteControllerWeightInfo, InspectMessageQueues, QueryController,
59 QueryControllerWeightInfo, SendController, SendControllerWeightInfo,
60};
61use xcm_executor::{
62 traits::{
63 AssetTransferError, CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin,
64 DropAssets, MatchesFungible, OnResponse, Properties, QueryHandler, QueryResponseStatus,
65 RecordXcm, TransactAsset, TransferType, VersionChangeNotifier, WeightBounds,
66 XcmAssetTransfers,
67 },
68 AssetsInHolding,
69};
70use xcm_runtime_apis::{
71 dry_run::{CallDryRunEffects, Error as XcmDryRunApiError, XcmDryRunEffects},
72 fees::Error as XcmPaymentApiError,
73 trusted_query::Error as TrustedQueryApiError,
74};
75
76#[cfg(any(feature = "try-runtime", test))]
77use sp_runtime::TryRuntimeError;
78
79pub trait WeightInfo {
80 fn send() -> Weight;
81 fn teleport_assets() -> Weight;
82 fn reserve_transfer_assets() -> Weight;
83 fn transfer_assets() -> Weight;
84 fn execute() -> Weight;
85 fn force_xcm_version() -> Weight;
86 fn force_default_xcm_version() -> Weight;
87 fn force_subscribe_version_notify() -> Weight;
88 fn force_unsubscribe_version_notify() -> Weight;
89 fn force_suspension() -> Weight;
90 fn migrate_supported_version() -> Weight;
91 fn migrate_version_notifiers() -> Weight;
92 fn already_notified_target() -> Weight;
93 fn notify_current_targets() -> Weight;
94 fn notify_target_migration_fail() -> Weight;
95 fn migrate_version_notify_targets() -> Weight;
96 fn migrate_and_notify_old_targets() -> Weight;
97 fn new_query() -> Weight;
98 fn take_response() -> Weight;
99 fn claim_assets() -> Weight;
100}
101
102pub struct TestWeightInfo;
104impl WeightInfo for TestWeightInfo {
105 fn send() -> Weight {
106 Weight::from_parts(100_000_000, 0)
107 }
108
109 fn teleport_assets() -> Weight {
110 Weight::from_parts(100_000_000, 0)
111 }
112
113 fn reserve_transfer_assets() -> Weight {
114 Weight::from_parts(100_000_000, 0)
115 }
116
117 fn transfer_assets() -> Weight {
118 Weight::from_parts(100_000_000, 0)
119 }
120
121 fn execute() -> Weight {
122 Weight::from_parts(100_000_000, 0)
123 }
124
125 fn force_xcm_version() -> Weight {
126 Weight::from_parts(100_000_000, 0)
127 }
128
129 fn force_default_xcm_version() -> Weight {
130 Weight::from_parts(100_000_000, 0)
131 }
132
133 fn force_subscribe_version_notify() -> Weight {
134 Weight::from_parts(100_000_000, 0)
135 }
136
137 fn force_unsubscribe_version_notify() -> Weight {
138 Weight::from_parts(100_000_000, 0)
139 }
140
141 fn force_suspension() -> Weight {
142 Weight::from_parts(100_000_000, 0)
143 }
144
145 fn migrate_supported_version() -> Weight {
146 Weight::from_parts(100_000_000, 0)
147 }
148
149 fn migrate_version_notifiers() -> Weight {
150 Weight::from_parts(100_000_000, 0)
151 }
152
153 fn already_notified_target() -> Weight {
154 Weight::from_parts(100_000_000, 0)
155 }
156
157 fn notify_current_targets() -> Weight {
158 Weight::from_parts(100_000_000, 0)
159 }
160
161 fn notify_target_migration_fail() -> Weight {
162 Weight::from_parts(100_000_000, 0)
163 }
164
165 fn migrate_version_notify_targets() -> Weight {
166 Weight::from_parts(100_000_000, 0)
167 }
168
169 fn migrate_and_notify_old_targets() -> Weight {
170 Weight::from_parts(100_000_000, 0)
171 }
172
173 fn new_query() -> Weight {
174 Weight::from_parts(100_000_000, 0)
175 }
176
177 fn take_response() -> Weight {
178 Weight::from_parts(100_000_000, 0)
179 }
180
181 fn claim_assets() -> Weight {
182 Weight::from_parts(100_000_000, 0)
183 }
184}
185
186#[frame_support::pallet]
187pub mod pallet {
188 use super::*;
189 use frame_support::{
190 dispatch::{GetDispatchInfo, PostDispatchInfo},
191 parameter_types,
192 };
193 use frame_system::Config as SysConfig;
194 use sp_core::H256;
195 use sp_runtime::traits::Dispatchable;
196 use xcm_executor::traits::{MatchesFungible, WeightBounds};
197
198 parameter_types! {
199 pub const CurrentXcmVersion: u32 = XCM_VERSION;
202 }
203
204 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
205
206 #[pallet::pallet]
207 #[pallet::storage_version(STORAGE_VERSION)]
208 #[pallet::without_storage_info]
209 pub struct Pallet<T>(_);
210
211 pub type BalanceOf<T> =
212 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
213
214 #[pallet::config]
215 pub trait Config: frame_system::Config {
217 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
219
220 type Currency: LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>;
223
224 type CurrencyMatcher: MatchesFungible<BalanceOf<Self>>;
226
227 type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
230
231 type XcmRouter: SendXcm;
233
234 type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
238
239 type XcmExecuteFilter: Contains<(Location, Xcm<<Self as Config>::RuntimeCall>)>;
241
242 type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall> + XcmAssetTransfers;
244
245 type XcmTeleportFilter: Contains<(Location, Vec<Asset>)>;
247
248 type XcmReserveTransferFilter: Contains<(Location, Vec<Asset>)>;
251
252 type Weigher: WeightBounds<<Self as Config>::RuntimeCall>;
254
255 type UniversalLocation: Get<InteriorLocation>;
257
258 type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
260
261 type RuntimeCall: Parameter
263 + GetDispatchInfo
264 + Dispatchable<
265 RuntimeOrigin = <Self as Config>::RuntimeOrigin,
266 PostInfo = PostDispatchInfo,
267 >;
268
269 const VERSION_DISCOVERY_QUEUE_SIZE: u32;
270
271 type AdvertisedXcmVersion: Get<XcmVersion>;
274
275 type AdminOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin>;
277
278 type TrustedLockers: ContainsPair<Location, Asset>;
281
282 type SovereignAccountOf: ConvertLocation<Self::AccountId>;
284
285 type MaxLockers: Get<u32>;
287
288 type MaxRemoteLockConsumers: Get<u32>;
290
291 type RemoteLockConsumerIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
293
294 type WeightInfo: WeightInfo;
296 }
297
298 impl<T: Config> ExecuteControllerWeightInfo for Pallet<T> {
299 fn execute() -> Weight {
300 T::WeightInfo::execute()
301 }
302 }
303
304 impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pallet<T> {
305 type WeightInfo = Self;
306 fn execute(
307 origin: OriginFor<T>,
308 message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
309 max_weight: Weight,
310 ) -> Result<Weight, DispatchErrorWithPostInfo> {
311 tracing::trace!(target: "xcm::pallet_xcm::execute", ?message, ?max_weight);
312 let outcome = (|| {
313 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
314 let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
315 let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
316 let value = (origin_location, message);
317 ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
318 let (origin_location, message) = value;
319 Ok(T::XcmExecutor::prepare_and_execute(
320 origin_location,
321 message,
322 &mut hash,
323 max_weight,
324 max_weight,
325 ))
326 })()
327 .map_err(|e: DispatchError| {
328 e.with_weight(<Self::WeightInfo as ExecuteControllerWeightInfo>::execute())
329 })?;
330
331 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
332 let weight_used = outcome.weight_used();
333 outcome.ensure_complete().map_err(|error| {
334 tracing::error!(target: "xcm::pallet_xcm::execute", ?error, "XCM execution failed with error");
335 Error::<T>::LocalExecutionIncomplete.with_weight(
336 weight_used.saturating_add(
337 <Self::WeightInfo as ExecuteControllerWeightInfo>::execute(),
338 ),
339 )
340 })?;
341 Ok(weight_used)
342 }
343 }
344
345 impl<T: Config> SendControllerWeightInfo for Pallet<T> {
346 fn send() -> Weight {
347 T::WeightInfo::send()
348 }
349 }
350
351 impl<T: Config> SendController<OriginFor<T>> for Pallet<T> {
352 type WeightInfo = Self;
353 fn send(
354 origin: OriginFor<T>,
355 dest: Box<VersionedLocation>,
356 message: Box<VersionedXcm<()>>,
357 ) -> Result<XcmHash, DispatchError> {
358 let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
359 let interior: Junctions =
360 origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
361 let dest = Location::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
362 let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
363
364 let message_id = Self::send_xcm(interior, dest.clone(), message.clone())
365 .map_err(|error| {
366 tracing::error!(target: "xcm::pallet_xcm::send", ?error, ?dest, ?message, "XCM send failed with error");
367 Error::<T>::from(error)
368 })?;
369 let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
370 Self::deposit_event(e);
371 Ok(message_id)
372 }
373 }
374
375 impl<T: Config> QueryControllerWeightInfo for Pallet<T> {
376 fn query() -> Weight {
377 T::WeightInfo::new_query()
378 }
379 fn take_response() -> Weight {
380 T::WeightInfo::take_response()
381 }
382 }
383
384 impl<T: Config> QueryController<OriginFor<T>, BlockNumberFor<T>> for Pallet<T> {
385 type WeightInfo = Self;
386
387 fn query(
388 origin: OriginFor<T>,
389 timeout: BlockNumberFor<T>,
390 match_querier: VersionedLocation,
391 ) -> Result<QueryId, DispatchError> {
392 let responder = <T as Config>::ExecuteXcmOrigin::ensure_origin(origin)?;
393 let query_id = <Self as QueryHandler>::new_query(
394 responder,
395 timeout,
396 Location::try_from(match_querier)
397 .map_err(|_| Into::<DispatchError>::into(Error::<T>::BadVersion))?,
398 );
399
400 Ok(query_id)
401 }
402 }
403
404 #[pallet::event]
405 #[pallet::generate_deposit(pub(super) fn deposit_event)]
406 pub enum Event<T: Config> {
407 Attempted { outcome: xcm::latest::Outcome },
409 Sent { origin: Location, destination: Location, message: Xcm<()>, message_id: XcmHash },
411 UnexpectedResponse { origin: Location, query_id: QueryId },
415 ResponseReady { query_id: QueryId, response: Response },
418 Notified { query_id: QueryId, pallet_index: u8, call_index: u8 },
421 NotifyOverweight {
425 query_id: QueryId,
426 pallet_index: u8,
427 call_index: u8,
428 actual_weight: Weight,
429 max_budgeted_weight: Weight,
430 },
431 NotifyDispatchError { query_id: QueryId, pallet_index: u8, call_index: u8 },
434 NotifyDecodeFailed { query_id: QueryId, pallet_index: u8, call_index: u8 },
438 InvalidResponder {
442 origin: Location,
443 query_id: QueryId,
444 expected_location: Option<Location>,
445 },
446 InvalidResponderVersion { origin: Location, query_id: QueryId },
454 ResponseTaken { query_id: QueryId },
456 AssetsTrapped { hash: H256, origin: Location, assets: VersionedAssets },
458 VersionChangeNotified {
462 destination: Location,
463 result: XcmVersion,
464 cost: Assets,
465 message_id: XcmHash,
466 },
467 SupportedVersionChanged { location: Location, version: XcmVersion },
470 NotifyTargetSendFail { location: Location, query_id: QueryId, error: XcmError },
473 NotifyTargetMigrationFail { location: VersionedLocation, query_id: QueryId },
476 InvalidQuerierVersion { origin: Location, query_id: QueryId },
484 InvalidQuerier {
488 origin: Location,
489 query_id: QueryId,
490 expected_querier: Location,
491 maybe_actual_querier: Option<Location>,
492 },
493 VersionNotifyStarted { destination: Location, cost: Assets, message_id: XcmHash },
496 VersionNotifyRequested { destination: Location, cost: Assets, message_id: XcmHash },
498 VersionNotifyUnrequested { destination: Location, cost: Assets, message_id: XcmHash },
501 FeesPaid { paying: Location, fees: Assets },
503 AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets },
505 VersionMigrationFinished { version: XcmVersion },
507 }
508
509 #[pallet::origin]
510 #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
511 pub enum Origin {
512 Xcm(Location),
514 Response(Location),
516 }
517 impl From<Location> for Origin {
518 fn from(location: Location) -> Origin {
519 Origin::Xcm(location)
520 }
521 }
522
523 #[pallet::error]
524 pub enum Error<T> {
525 Unreachable,
528 SendFailure,
531 Filtered,
533 UnweighableMessage,
535 DestinationNotInvertible,
537 Empty,
539 CannotReanchor,
541 TooManyAssets,
543 InvalidOrigin,
545 BadVersion,
547 BadLocation,
550 NoSubscription,
552 AlreadySubscribed,
554 CannotCheckOutTeleport,
556 LowBalance,
558 TooManyLocks,
560 AccountNotSovereign,
562 FeesNotMet,
564 LockNotFound,
566 InUse,
568 #[codec(index = 21)]
570 InvalidAssetUnknownReserve,
571 #[codec(index = 22)]
573 InvalidAssetUnsupportedReserve,
574 #[codec(index = 23)]
576 TooManyReserves,
577 #[codec(index = 24)]
579 LocalExecutionIncomplete,
580 }
581
582 impl<T: Config> From<SendError> for Error<T> {
583 fn from(e: SendError) -> Self {
584 match e {
585 SendError::Fees => Error::<T>::FeesNotMet,
586 SendError::NotApplicable => Error::<T>::Unreachable,
587 _ => Error::<T>::SendFailure,
588 }
589 }
590 }
591
592 impl<T: Config> From<AssetTransferError> for Error<T> {
593 fn from(e: AssetTransferError) -> Self {
594 match e {
595 AssetTransferError::UnknownReserve => Error::<T>::InvalidAssetUnknownReserve,
596 }
597 }
598 }
599
600 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
602 pub enum QueryStatus<BlockNumber> {
603 Pending {
605 responder: VersionedLocation,
608 maybe_match_querier: Option<VersionedLocation>,
611 maybe_notify: Option<(u8, u8)>,
612 timeout: BlockNumber,
613 },
614 VersionNotifier { origin: VersionedLocation, is_active: bool },
616 Ready { response: VersionedResponse, at: BlockNumber },
618 }
619
620 #[derive(Copy, Clone)]
621 pub(crate) struct LatestVersionedLocation<'a>(pub(crate) &'a Location);
622 impl<'a> EncodeLike<VersionedLocation> for LatestVersionedLocation<'a> {}
623 impl<'a> Encode for LatestVersionedLocation<'a> {
624 fn encode(&self) -> Vec<u8> {
625 let mut r = VersionedLocation::from(Location::default()).encode();
626 r.truncate(1);
627 self.0.using_encoded(|d| r.extend_from_slice(d));
628 r
629 }
630 }
631
632 #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
633 pub enum VersionMigrationStage {
634 MigrateSupportedVersion,
635 MigrateVersionNotifiers,
636 NotifyCurrentTargets(Option<Vec<u8>>),
637 MigrateAndNotifyOldTargets,
638 }
639
640 impl Default for VersionMigrationStage {
641 fn default() -> Self {
642 Self::MigrateSupportedVersion
643 }
644 }
645
646 #[pallet::storage]
648 pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;
649
650 #[pallet::storage]
652 #[pallet::getter(fn query)]
653 pub(super) type Queries<T: Config> =
654 StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<BlockNumberFor<T>>, OptionQuery>;
655
656 #[pallet::storage]
661 #[pallet::getter(fn asset_trap)]
662 pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;
663
664 #[pallet::storage]
667 #[pallet::whitelist_storage]
668 pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
669
670 #[pallet::storage]
672 pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
673 _,
674 Twox64Concat,
675 XcmVersion,
676 Blake2_128Concat,
677 VersionedLocation,
678 XcmVersion,
679 OptionQuery,
680 >;
681
682 #[pallet::storage]
684 pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
685 _,
686 Twox64Concat,
687 XcmVersion,
688 Blake2_128Concat,
689 VersionedLocation,
690 QueryId,
691 OptionQuery,
692 >;
693
694 #[pallet::storage]
697 pub(super) type VersionNotifyTargets<T: Config> = StorageDoubleMap<
698 _,
699 Twox64Concat,
700 XcmVersion,
701 Blake2_128Concat,
702 VersionedLocation,
703 (QueryId, Weight, XcmVersion),
704 OptionQuery,
705 >;
706
707 pub struct VersionDiscoveryQueueSize<T>(PhantomData<T>);
708 impl<T: Config> Get<u32> for VersionDiscoveryQueueSize<T> {
709 fn get() -> u32 {
710 T::VERSION_DISCOVERY_QUEUE_SIZE
711 }
712 }
713
714 #[pallet::storage]
718 #[pallet::whitelist_storage]
719 pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
720 _,
721 BoundedVec<(VersionedLocation, u32), VersionDiscoveryQueueSize<T>>,
722 ValueQuery,
723 >;
724
725 #[pallet::storage]
727 pub(super) type CurrentMigration<T: Config> =
728 StorageValue<_, VersionMigrationStage, OptionQuery>;
729
730 #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
731 #[scale_info(skip_type_params(MaxConsumers))]
732 pub struct RemoteLockedFungibleRecord<ConsumerIdentifier, MaxConsumers: Get<u32>> {
733 pub amount: u128,
735 pub owner: VersionedLocation,
737 pub locker: VersionedLocation,
739 pub consumers: BoundedVec<(ConsumerIdentifier, u128), MaxConsumers>,
743 }
744
745 impl<LockId, MaxConsumers: Get<u32>> RemoteLockedFungibleRecord<LockId, MaxConsumers> {
746 pub fn amount_held(&self) -> Option<u128> {
749 self.consumers.iter().max_by(|x, y| x.1.cmp(&y.1)).map(|max| max.1)
750 }
751 }
752
753 #[pallet::storage]
755 pub(super) type RemoteLockedFungibles<T: Config> = StorageNMap<
756 _,
757 (
758 NMapKey<Twox64Concat, XcmVersion>,
759 NMapKey<Blake2_128Concat, T::AccountId>,
760 NMapKey<Blake2_128Concat, VersionedAssetId>,
761 ),
762 RemoteLockedFungibleRecord<T::RemoteLockConsumerIdentifier, T::MaxRemoteLockConsumers>,
763 OptionQuery,
764 >;
765
766 #[pallet::storage]
768 pub(super) type LockedFungibles<T: Config> = StorageMap<
769 _,
770 Blake2_128Concat,
771 T::AccountId,
772 BoundedVec<(BalanceOf<T>, VersionedLocation), T::MaxLockers>,
773 OptionQuery,
774 >;
775
776 #[pallet::storage]
778 pub(super) type XcmExecutionSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
779
780 #[pallet::storage]
788 pub(crate) type ShouldRecordXcm<T: Config> = StorageValue<_, bool, ValueQuery>;
789
790 #[pallet::storage]
797 pub(crate) type RecordedXcm<T: Config> = StorageValue<_, Xcm<()>>;
798
799 #[pallet::genesis_config]
800 pub struct GenesisConfig<T: Config> {
801 #[serde(skip)]
802 pub _config: core::marker::PhantomData<T>,
803 pub safe_xcm_version: Option<XcmVersion>,
805 }
806
807 impl<T: Config> Default for GenesisConfig<T> {
808 fn default() -> Self {
809 Self { safe_xcm_version: Some(XCM_VERSION), _config: Default::default() }
810 }
811 }
812
813 #[pallet::genesis_build]
814 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
815 fn build(&self) {
816 SafeXcmVersion::<T>::set(self.safe_xcm_version);
817 }
818 }
819
820 #[pallet::hooks]
821 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
822 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
823 let mut weight_used = Weight::zero();
824 if let Some(migration) = CurrentMigration::<T>::get() {
825 let max_weight = T::BlockWeights::get().max_block / 10;
827 let (w, maybe_migration) = Self::check_xcm_version_change(migration, max_weight);
828 if maybe_migration.is_none() {
829 Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION });
830 }
831 CurrentMigration::<T>::set(maybe_migration);
832 weight_used.saturating_accrue(w);
833 }
834
835 let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
838 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
840 q.sort_by_key(|i| i.1);
841 while let Some((versioned_dest, _)) = q.pop() {
842 if let Ok(dest) = Location::try_from(versioned_dest) {
843 if Self::request_version_notify(dest).is_ok() {
844 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
846 break
847 }
848 }
849 }
850 if let Ok(q) = BoundedVec::try_from(q) {
853 VersionDiscoveryQueue::<T>::put(q);
854 }
855 weight_used
856 }
857
858 #[cfg(feature = "try-runtime")]
859 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
860 Self::do_try_state()
861 }
862 }
863
864 pub mod migrations {
865 use super::*;
866 use frame_support::traits::{PalletInfoAccess, StorageVersion};
867
868 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
869 enum QueryStatusV0<BlockNumber> {
870 Pending {
871 responder: VersionedLocation,
872 maybe_notify: Option<(u8, u8)>,
873 timeout: BlockNumber,
874 },
875 VersionNotifier {
876 origin: VersionedLocation,
877 is_active: bool,
878 },
879 Ready {
880 response: VersionedResponse,
881 at: BlockNumber,
882 },
883 }
884 impl<B> From<QueryStatusV0<B>> for QueryStatus<B> {
885 fn from(old: QueryStatusV0<B>) -> Self {
886 use QueryStatusV0::*;
887 match old {
888 Pending { responder, maybe_notify, timeout } => QueryStatus::Pending {
889 responder,
890 maybe_notify,
891 timeout,
892 maybe_match_querier: Some(Location::here().into()),
893 },
894 VersionNotifier { origin, is_active } =>
895 QueryStatus::VersionNotifier { origin, is_active },
896 Ready { response, at } => QueryStatus::Ready { response, at },
897 }
898 }
899 }
900
901 pub fn migrate_to_v1<T: Config, P: GetStorageVersion + PalletInfoAccess>(
902 ) -> frame_support::weights::Weight {
903 let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
904 tracing::info!(
905 target: "runtime::xcm",
906 ?on_chain_storage_version,
907 "Running migration storage v1 for xcm with storage version",
908 );
909
910 if on_chain_storage_version < 1 {
911 let mut count = 0;
912 Queries::<T>::translate::<QueryStatusV0<BlockNumberFor<T>>, _>(|_key, value| {
913 count += 1;
914 Some(value.into())
915 });
916 StorageVersion::new(1).put::<P>();
917 tracing::info!(
918 target: "runtime::xcm",
919 ?on_chain_storage_version,
920 "Running migration storage v1 for xcm with storage version was complete",
921 );
922 T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
924 } else {
925 tracing::warn!(
926 target: "runtime::xcm",
927 ?on_chain_storage_version,
928 "Attempted to apply migration to v1 but failed because storage version is",
929 );
930 T::DbWeight::get().reads(1)
931 }
932 }
933 }
934
935 #[pallet::call(weight(<T as Config>::WeightInfo))]
936 impl<T: Config> Pallet<T> {
937 #[pallet::call_index(0)]
938 pub fn send(
939 origin: OriginFor<T>,
940 dest: Box<VersionedLocation>,
941 message: Box<VersionedXcm<()>>,
942 ) -> DispatchResult {
943 <Self as SendController<_>>::send(origin, dest, message)?;
944 Ok(())
945 }
946
947 #[pallet::call_index(1)]
966 #[allow(deprecated)]
967 #[deprecated(
968 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_teleport_assets` or `transfer_assets`"
969 )]
970 pub fn teleport_assets(
971 origin: OriginFor<T>,
972 dest: Box<VersionedLocation>,
973 beneficiary: Box<VersionedLocation>,
974 assets: Box<VersionedAssets>,
975 fee_asset_item: u32,
976 ) -> DispatchResult {
977 Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
978 }
979
980 #[pallet::call_index(2)]
1011 #[allow(deprecated)]
1012 #[deprecated(
1013 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_reserve_transfer_assets` or `transfer_assets`"
1014 )]
1015 pub fn reserve_transfer_assets(
1016 origin: OriginFor<T>,
1017 dest: Box<VersionedLocation>,
1018 beneficiary: Box<VersionedLocation>,
1019 assets: Box<VersionedAssets>,
1020 fee_asset_item: u32,
1021 ) -> DispatchResult {
1022 Self::do_reserve_transfer_assets(
1023 origin,
1024 dest,
1025 beneficiary,
1026 assets,
1027 fee_asset_item,
1028 Unlimited,
1029 )
1030 }
1031
1032 #[pallet::call_index(3)]
1041 #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
1042 pub fn execute(
1043 origin: OriginFor<T>,
1044 message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
1045 max_weight: Weight,
1046 ) -> DispatchResultWithPostInfo {
1047 let weight_used =
1048 <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
1049 Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())
1050 }
1051
1052 #[pallet::call_index(4)]
1059 pub fn force_xcm_version(
1060 origin: OriginFor<T>,
1061 location: Box<Location>,
1062 version: XcmVersion,
1063 ) -> DispatchResult {
1064 T::AdminOrigin::ensure_origin(origin)?;
1065 let location = *location;
1066 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&location), version);
1067 Self::deposit_event(Event::SupportedVersionChanged { location, version });
1068 Ok(())
1069 }
1070
1071 #[pallet::call_index(5)]
1077 pub fn force_default_xcm_version(
1078 origin: OriginFor<T>,
1079 maybe_xcm_version: Option<XcmVersion>,
1080 ) -> DispatchResult {
1081 T::AdminOrigin::ensure_origin(origin)?;
1082 SafeXcmVersion::<T>::set(maybe_xcm_version);
1083 Ok(())
1084 }
1085
1086 #[pallet::call_index(6)]
1091 pub fn force_subscribe_version_notify(
1092 origin: OriginFor<T>,
1093 location: Box<VersionedLocation>,
1094 ) -> DispatchResult {
1095 T::AdminOrigin::ensure_origin(origin)?;
1096 let location: Location =
1097 (*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
1098 Self::request_version_notify(location).map_err(|e| {
1099 match e {
1100 XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
1101 _ => Error::<T>::InvalidOrigin,
1102 }
1103 .into()
1104 })
1105 }
1106
1107 #[pallet::call_index(7)]
1114 pub fn force_unsubscribe_version_notify(
1115 origin: OriginFor<T>,
1116 location: Box<VersionedLocation>,
1117 ) -> DispatchResult {
1118 T::AdminOrigin::ensure_origin(origin)?;
1119 let location: Location =
1120 (*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
1121 Self::unrequest_version_notify(location).map_err(|e| {
1122 match e {
1123 XcmError::InvalidLocation => Error::<T>::NoSubscription,
1124 _ => Error::<T>::InvalidOrigin,
1125 }
1126 .into()
1127 })
1128 }
1129
1130 #[pallet::call_index(8)]
1161 #[pallet::weight(T::WeightInfo::reserve_transfer_assets())]
1162 pub fn limited_reserve_transfer_assets(
1163 origin: OriginFor<T>,
1164 dest: Box<VersionedLocation>,
1165 beneficiary: Box<VersionedLocation>,
1166 assets: Box<VersionedAssets>,
1167 fee_asset_item: u32,
1168 weight_limit: WeightLimit,
1169 ) -> DispatchResult {
1170 Self::do_reserve_transfer_assets(
1171 origin,
1172 dest,
1173 beneficiary,
1174 assets,
1175 fee_asset_item,
1176 weight_limit,
1177 )
1178 }
1179
1180 #[pallet::call_index(9)]
1199 #[pallet::weight(T::WeightInfo::teleport_assets())]
1200 pub fn limited_teleport_assets(
1201 origin: OriginFor<T>,
1202 dest: Box<VersionedLocation>,
1203 beneficiary: Box<VersionedLocation>,
1204 assets: Box<VersionedAssets>,
1205 fee_asset_item: u32,
1206 weight_limit: WeightLimit,
1207 ) -> DispatchResult {
1208 Self::do_teleport_assets(
1209 origin,
1210 dest,
1211 beneficiary,
1212 assets,
1213 fee_asset_item,
1214 weight_limit,
1215 )
1216 }
1217
1218 #[pallet::call_index(10)]
1223 pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
1224 T::AdminOrigin::ensure_origin(origin)?;
1225 XcmExecutionSuspended::<T>::set(suspended);
1226 Ok(())
1227 }
1228
1229 #[pallet::call_index(11)]
1263 pub fn transfer_assets(
1264 origin: OriginFor<T>,
1265 dest: Box<VersionedLocation>,
1266 beneficiary: Box<VersionedLocation>,
1267 assets: Box<VersionedAssets>,
1268 fee_asset_item: u32,
1269 weight_limit: WeightLimit,
1270 ) -> DispatchResult {
1271 let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1272 let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
1273 let beneficiary: Location =
1274 (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
1275 let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1276 tracing::debug!(
1277 target: "xcm::pallet_xcm::transfer_assets",
1278 ?origin, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
1279 );
1280
1281 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1282 let assets = assets.into_inner();
1283 let fee_asset_item = fee_asset_item as usize;
1284 let (fees_transfer_type, assets_transfer_type) =
1286 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
1287
1288 Self::do_transfer_assets(
1289 origin,
1290 dest,
1291 Either::Left(beneficiary),
1292 assets,
1293 assets_transfer_type,
1294 fee_asset_item,
1295 fees_transfer_type,
1296 weight_limit,
1297 )
1298 }
1299
1300 #[pallet::call_index(12)]
1307 pub fn claim_assets(
1308 origin: OriginFor<T>,
1309 assets: Box<VersionedAssets>,
1310 beneficiary: Box<VersionedLocation>,
1311 ) -> DispatchResult {
1312 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1313 tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?origin_location, ?assets, ?beneficiary);
1314 let assets_version = assets.identify_version();
1316 let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1317 let number_of_assets = assets.len() as u32;
1318 let beneficiary: Location =
1319 (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
1320 let ticket: Location = GeneralIndex(assets_version as u128).into();
1321 let mut message = Xcm(vec![
1322 ClaimAsset { assets, ticket },
1323 DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
1324 ]);
1325 let weight =
1326 T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
1327 let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
1328 let outcome = T::XcmExecutor::prepare_and_execute(
1329 origin_location,
1330 message,
1331 &mut hash,
1332 weight,
1333 weight,
1334 );
1335 outcome.ensure_complete().map_err(|error| {
1336 tracing::error!(target: "xcm::pallet_xcm::claim_assets", ?error, "XCM execution failed with error");
1337 Error::<T>::LocalExecutionIncomplete
1338 })?;
1339 Ok(())
1340 }
1341
1342 #[pallet::call_index(13)]
1391 #[pallet::weight(T::WeightInfo::transfer_assets())]
1392 pub fn transfer_assets_using_type_and_then(
1393 origin: OriginFor<T>,
1394 dest: Box<VersionedLocation>,
1395 assets: Box<VersionedAssets>,
1396 assets_transfer_type: Box<TransferType>,
1397 remote_fees_id: Box<VersionedAssetId>,
1398 fees_transfer_type: Box<TransferType>,
1399 custom_xcm_on_dest: Box<VersionedXcm<()>>,
1400 weight_limit: WeightLimit,
1401 ) -> DispatchResult {
1402 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1403 let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
1404 let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1405 let fees_id: AssetId =
1406 (*remote_fees_id).try_into().map_err(|()| Error::<T>::BadVersion)?;
1407 let remote_xcm: Xcm<()> =
1408 (*custom_xcm_on_dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
1409 tracing::debug!(
1410 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1411 ?origin_location, ?dest, ?assets, ?assets_transfer_type, ?fees_id, ?fees_transfer_type,
1412 ?remote_xcm, ?weight_limit,
1413 );
1414
1415 let assets = assets.into_inner();
1416 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1417
1418 let fee_asset_index =
1419 assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
1420 Self::do_transfer_assets(
1421 origin_location,
1422 dest,
1423 Either::Right(remote_xcm),
1424 assets,
1425 *assets_transfer_type,
1426 fee_asset_index,
1427 *fees_transfer_type,
1428 weight_limit,
1429 )
1430 }
1431 }
1432}
1433
1434const MAX_ASSETS_FOR_TRANSFER: usize = 2;
1436
1437#[derive(Clone, PartialEq)]
1439enum FeesHandling<T: Config> {
1440 Batched { fees: Asset },
1442 Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
1444}
1445
1446impl<T: Config> core::fmt::Debug for FeesHandling<T> {
1447 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1448 match self {
1449 Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
1450 Self::Separate { local_xcm, remote_xcm } => write!(
1451 f,
1452 "FeesHandling::Separate(local: {:?}, remote: {:?})",
1453 local_xcm, remote_xcm
1454 ),
1455 }
1456 }
1457}
1458
1459impl<T: Config> QueryHandler for Pallet<T> {
1460 type BlockNumber = BlockNumberFor<T>;
1461 type Error = XcmError;
1462 type UniversalLocation = T::UniversalLocation;
1463
1464 fn new_query(
1466 responder: impl Into<Location>,
1467 timeout: BlockNumberFor<T>,
1468 match_querier: impl Into<Location>,
1469 ) -> QueryId {
1470 Self::do_new_query(responder, None, timeout, match_querier)
1471 }
1472
1473 fn report_outcome(
1476 message: &mut Xcm<()>,
1477 responder: impl Into<Location>,
1478 timeout: Self::BlockNumber,
1479 ) -> Result<QueryId, Self::Error> {
1480 let responder = responder.into();
1481 let destination = Self::UniversalLocation::get()
1482 .invert_target(&responder)
1483 .map_err(|()| XcmError::LocationNotInvertible)?;
1484 let query_id = Self::new_query(responder, timeout, Here);
1485 let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
1486 let report_error = Xcm(vec![ReportError(response_info)]);
1487 message.0.insert(0, SetAppendix(report_error));
1488 Ok(query_id)
1489 }
1490
1491 fn take_response(query_id: QueryId) -> QueryResponseStatus<Self::BlockNumber> {
1493 match Queries::<T>::get(query_id) {
1494 Some(QueryStatus::Ready { response, at }) => match response.try_into() {
1495 Ok(response) => {
1496 Queries::<T>::remove(query_id);
1497 Self::deposit_event(Event::ResponseTaken { query_id });
1498 QueryResponseStatus::Ready { response, at }
1499 },
1500 Err(_) => QueryResponseStatus::UnexpectedVersion,
1501 },
1502 Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
1503 Some(_) => QueryResponseStatus::UnexpectedVersion,
1504 None => QueryResponseStatus::NotFound,
1505 }
1506 }
1507
1508 #[cfg(feature = "runtime-benchmarks")]
1509 fn expect_response(id: QueryId, response: Response) {
1510 let response = response.into();
1511 Queries::<T>::insert(
1512 id,
1513 QueryStatus::Ready { response, at: frame_system::Pallet::<T>::block_number() },
1514 );
1515 }
1516}
1517
1518impl<T: Config> Pallet<T> {
1519 fn find_fee_and_assets_transfer_types(
1524 assets: &[Asset],
1525 fee_asset_item: usize,
1526 dest: &Location,
1527 ) -> Result<(TransferType, TransferType), Error<T>> {
1528 let mut fees_transfer_type = None;
1529 let mut assets_transfer_type = None;
1530 for (idx, asset) in assets.iter().enumerate() {
1531 if let Fungible(x) = asset.fun {
1532 ensure!(!x.is_zero(), Error::<T>::Empty);
1534 }
1535 let transfer_type =
1536 T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
1537 if idx == fee_asset_item {
1538 fees_transfer_type = Some(transfer_type);
1539 } else {
1540 if let Some(existing) = assets_transfer_type.as_ref() {
1541 ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
1544 } else {
1545 assets_transfer_type = Some(transfer_type);
1547 }
1548 }
1549 }
1550 if assets.len() == 1 {
1552 assets_transfer_type = fees_transfer_type.clone()
1553 }
1554 Ok((
1555 fees_transfer_type.ok_or(Error::<T>::Empty)?,
1556 assets_transfer_type.ok_or(Error::<T>::Empty)?,
1557 ))
1558 }
1559
1560 fn do_reserve_transfer_assets(
1561 origin: OriginFor<T>,
1562 dest: Box<VersionedLocation>,
1563 beneficiary: Box<VersionedLocation>,
1564 assets: Box<VersionedAssets>,
1565 fee_asset_item: u32,
1566 weight_limit: WeightLimit,
1567 ) -> DispatchResult {
1568 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1569 let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
1570 let beneficiary: Location =
1571 (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
1572 let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1573 tracing::debug!(
1574 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
1575 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item,
1576 );
1577
1578 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1579 let value = (origin_location, assets.into_inner());
1580 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1581 let (origin, assets) = value;
1582
1583 let fee_asset_item = fee_asset_item as usize;
1584 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
1585
1586 let (fees_transfer_type, assets_transfer_type) =
1588 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
1589 ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
1591 ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
1593
1594 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
1595 origin.clone(),
1596 dest.clone(),
1597 Either::Left(beneficiary),
1598 assets,
1599 assets_transfer_type,
1600 FeesHandling::Batched { fees },
1601 weight_limit,
1602 )?;
1603 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
1604 }
1605
1606 fn do_teleport_assets(
1607 origin: OriginFor<T>,
1608 dest: Box<VersionedLocation>,
1609 beneficiary: Box<VersionedLocation>,
1610 assets: Box<VersionedAssets>,
1611 fee_asset_item: u32,
1612 weight_limit: WeightLimit,
1613 ) -> DispatchResult {
1614 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1615 let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
1616 let beneficiary: Location =
1617 (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
1618 let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1619 tracing::debug!(
1620 target: "xcm::pallet_xcm::do_teleport_assets",
1621 ?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
1622 );
1623
1624 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1625 let value = (origin_location, assets.into_inner());
1626 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
1627 let (origin_location, assets) = value;
1628 for asset in assets.iter() {
1629 let transfer_type =
1630 T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
1631 ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
1632 }
1633 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
1634
1635 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
1636 origin_location.clone(),
1637 dest.clone(),
1638 Either::Left(beneficiary),
1639 assets,
1640 TransferType::Teleport,
1641 FeesHandling::Batched { fees },
1642 weight_limit,
1643 )?;
1644 Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
1645 }
1646
1647 fn do_transfer_assets(
1648 origin: Location,
1649 dest: Location,
1650 beneficiary: Either<Location, Xcm<()>>,
1651 mut assets: Vec<Asset>,
1652 assets_transfer_type: TransferType,
1653 fee_asset_index: usize,
1654 fees_transfer_type: TransferType,
1655 weight_limit: WeightLimit,
1656 ) -> DispatchResult {
1657 let fees = if fees_transfer_type == assets_transfer_type {
1659 let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
1660 FeesHandling::Batched { fees }
1662 } else {
1663 ensure!(
1669 !matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
1670 Error::<T>::InvalidAssetUnsupportedReserve
1671 );
1672 let weight_limit = weight_limit.clone();
1673 let fees = assets.remove(fee_asset_index);
1676 let (local_xcm, remote_xcm) = match fees_transfer_type {
1677 TransferType::LocalReserve => Self::local_reserve_fees_instructions(
1678 origin.clone(),
1679 dest.clone(),
1680 fees,
1681 weight_limit,
1682 )?,
1683 TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
1684 origin.clone(),
1685 dest.clone(),
1686 fees,
1687 weight_limit,
1688 )?,
1689 TransferType::Teleport => Self::teleport_fees_instructions(
1690 origin.clone(),
1691 dest.clone(),
1692 fees,
1693 weight_limit,
1694 )?,
1695 TransferType::RemoteReserve(_) =>
1696 return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
1697 };
1698 FeesHandling::Separate { local_xcm, remote_xcm }
1699 };
1700
1701 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
1702 origin.clone(),
1703 dest.clone(),
1704 beneficiary,
1705 assets,
1706 assets_transfer_type,
1707 fees,
1708 weight_limit,
1709 )?;
1710 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
1711 }
1712
1713 fn build_xcm_transfer_type(
1714 origin: Location,
1715 dest: Location,
1716 beneficiary: Either<Location, Xcm<()>>,
1717 assets: Vec<Asset>,
1718 transfer_type: TransferType,
1719 fees: FeesHandling<T>,
1720 weight_limit: WeightLimit,
1721 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
1722 tracing::debug!(
1723 target: "xcm::pallet_xcm::build_xcm_transfer_type",
1724 ?origin, ?dest, ?beneficiary, ?assets, ?transfer_type, ?fees, ?weight_limit,
1725 );
1726 match transfer_type {
1727 TransferType::LocalReserve => Self::local_reserve_transfer_programs(
1728 origin.clone(),
1729 dest.clone(),
1730 beneficiary,
1731 assets,
1732 fees,
1733 weight_limit,
1734 )
1735 .map(|(local, remote)| (local, Some(remote))),
1736 TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
1737 origin.clone(),
1738 dest.clone(),
1739 beneficiary,
1740 assets,
1741 fees,
1742 weight_limit,
1743 )
1744 .map(|(local, remote)| (local, Some(remote))),
1745 TransferType::RemoteReserve(reserve) => {
1746 let fees = match fees {
1747 FeesHandling::Batched { fees } => fees,
1748 _ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
1749 };
1750 Self::remote_reserve_transfer_program(
1751 origin.clone(),
1752 reserve.try_into().map_err(|()| Error::<T>::BadVersion)?,
1753 beneficiary,
1754 dest.clone(),
1755 assets,
1756 fees,
1757 weight_limit,
1758 )
1759 .map(|local| (local, None))
1760 },
1761 TransferType::Teleport => Self::teleport_assets_program(
1762 origin.clone(),
1763 dest.clone(),
1764 beneficiary,
1765 assets,
1766 fees,
1767 weight_limit,
1768 )
1769 .map(|(local, remote)| (local, Some(remote))),
1770 }
1771 }
1772
1773 fn execute_xcm_transfer(
1774 origin: Location,
1775 dest: Location,
1776 mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
1777 remote_xcm: Option<Xcm<()>>,
1778 ) -> DispatchResult {
1779 tracing::debug!(
1780 target: "xcm::pallet_xcm::execute_xcm_transfer",
1781 ?origin, ?dest, ?local_xcm, ?remote_xcm,
1782 );
1783
1784 let weight =
1785 T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
1786 let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
1787 let outcome = T::XcmExecutor::prepare_and_execute(
1788 origin.clone(),
1789 local_xcm,
1790 &mut hash,
1791 weight,
1792 weight,
1793 );
1794 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
1795 outcome.clone().ensure_complete().map_err(|error| {
1796 tracing::error!(
1797 target: "xcm::pallet_xcm::execute_xcm_transfer",
1798 ?error, "XCM execution failed with error with outcome: {:?}", outcome
1799 );
1800 Error::<T>::LocalExecutionIncomplete
1801 })?;
1802
1803 if let Some(remote_xcm) = remote_xcm {
1804 let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
1805 .map_err(|error| {
1806 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error");
1807 Error::<T>::from(error)
1808 })?;
1809 if origin != Here.into_location() {
1810 Self::charge_fees(origin.clone(), price.clone()).map_err(|error| {
1811 tracing::error!(
1812 target: "xcm::pallet_xcm::execute_xcm_transfer",
1813 ?error, ?price, ?origin, "Unable to charge fee",
1814 );
1815 Error::<T>::FeesNotMet
1816 })?;
1817 }
1818 let message_id = T::XcmRouter::deliver(ticket)
1819 .map_err(|error| {
1820 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error");
1821 Error::<T>::from(error)
1822 })?;
1823
1824 let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
1825 Self::deposit_event(e);
1826 }
1827 Ok(())
1828 }
1829
1830 fn add_fees_to_xcm(
1831 dest: Location,
1832 fees: FeesHandling<T>,
1833 weight_limit: WeightLimit,
1834 local: &mut Xcm<<T as Config>::RuntimeCall>,
1835 remote: &mut Xcm<()>,
1836 ) -> Result<(), Error<T>> {
1837 match fees {
1838 FeesHandling::Batched { fees } => {
1839 let context = T::UniversalLocation::get();
1840 let reanchored_fees =
1843 fees.reanchored(&dest, &context).map_err(|e| {
1844 tracing::error!(target: "xcm::pallet_xcm::add_fees_to_xcm", ?e, ?dest, ?context, "Failed to re-anchor fees");
1845 Error::<T>::CannotReanchor
1846 })?;
1847 remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
1849 },
1850 FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
1851 core::mem::swap(local, &mut local_fees);
1854 core::mem::swap(remote, &mut remote_fees);
1855 local.inner_mut().append(&mut local_fees.into_inner());
1857 remote.inner_mut().append(&mut remote_fees.into_inner());
1858 },
1859 }
1860 Ok(())
1861 }
1862
1863 fn local_reserve_fees_instructions(
1864 origin: Location,
1865 dest: Location,
1866 fees: Asset,
1867 weight_limit: WeightLimit,
1868 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
1869 let value = (origin, vec![fees.clone()]);
1870 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1871
1872 let context = T::UniversalLocation::get();
1873 let reanchored_fees = fees
1874 .clone()
1875 .reanchored(&dest, &context)
1876 .map_err(|_| Error::<T>::CannotReanchor)?;
1877
1878 let local_execute_xcm = Xcm(vec![
1879 TransferAsset { assets: fees.into(), beneficiary: dest },
1881 ]);
1882 let xcm_on_dest = Xcm(vec![
1883 ReserveAssetDeposited(reanchored_fees.clone().into()),
1885 BuyExecution { fees: reanchored_fees, weight_limit },
1887 ]);
1888 Ok((local_execute_xcm, xcm_on_dest))
1889 }
1890
1891 fn local_reserve_transfer_programs(
1892 origin: Location,
1893 dest: Location,
1894 beneficiary: Either<Location, Xcm<()>>,
1895 assets: Vec<Asset>,
1896 fees: FeesHandling<T>,
1897 weight_limit: WeightLimit,
1898 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
1899 let value = (origin, assets);
1900 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1901 let (_, assets) = value;
1902
1903 let max_assets =
1905 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
1906 let assets: Assets = assets.into();
1907 let context = T::UniversalLocation::get();
1908 let mut reanchored_assets = assets.clone();
1909 reanchored_assets
1910 .reanchor(&dest, &context)
1911 .map_err(|e| {
1912 tracing::error!(target: "xcm::pallet_xcm::local_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
1913 Error::<T>::CannotReanchor
1914 })?;
1915
1916 let mut local_execute_xcm = Xcm(vec![
1918 TransferAsset { assets, beneficiary: dest.clone() },
1920 ]);
1921 let mut xcm_on_dest = Xcm(vec![
1923 ReserveAssetDeposited(reanchored_assets),
1925 ClearOrigin,
1927 ]);
1928 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
1930
1931 let custom_remote_xcm = match beneficiary {
1933 Either::Right(custom_xcm) => custom_xcm,
1934 Either::Left(beneficiary) => {
1935 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
1937 },
1938 };
1939 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
1940
1941 Ok((local_execute_xcm, xcm_on_dest))
1942 }
1943
1944 fn destination_reserve_fees_instructions(
1945 origin: Location,
1946 dest: Location,
1947 fees: Asset,
1948 weight_limit: WeightLimit,
1949 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
1950 let value = (origin, vec![fees.clone()]);
1951 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1952 ensure!(
1953 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&fees, &dest),
1954 Error::<T>::InvalidAssetUnsupportedReserve
1955 );
1956
1957 let context = T::UniversalLocation::get();
1958 let reanchored_fees = fees
1959 .clone()
1960 .reanchored(&dest, &context)
1961 .map_err(|e| {
1962 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_fees_instructions", ?e, ?dest,?context, "Failed to re-anchor fees");
1963 Error::<T>::CannotReanchor
1964 })?;
1965 let fees: Assets = fees.into();
1966
1967 let local_execute_xcm = Xcm(vec![
1968 WithdrawAsset(fees.clone()),
1970 BurnAsset(fees),
1972 ]);
1973 let xcm_on_dest = Xcm(vec![
1974 WithdrawAsset(reanchored_fees.clone().into()),
1976 BuyExecution { fees: reanchored_fees, weight_limit },
1978 ]);
1979 Ok((local_execute_xcm, xcm_on_dest))
1980 }
1981
1982 fn destination_reserve_transfer_programs(
1983 origin: Location,
1984 dest: Location,
1985 beneficiary: Either<Location, Xcm<()>>,
1986 assets: Vec<Asset>,
1987 fees: FeesHandling<T>,
1988 weight_limit: WeightLimit,
1989 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
1990 let value = (origin, assets);
1991 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1992 let (_, assets) = value;
1993 for asset in assets.iter() {
1994 ensure!(
1995 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&asset, &dest),
1996 Error::<T>::InvalidAssetUnsupportedReserve
1997 );
1998 }
1999
2000 let max_assets =
2002 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2003 let assets: Assets = assets.into();
2004 let context = T::UniversalLocation::get();
2005 let mut reanchored_assets = assets.clone();
2006 reanchored_assets
2007 .reanchor(&dest, &context)
2008 .map_err(|e| {
2009 tracing::error!(target: "xcm::pallet_xcm::destination_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2010 Error::<T>::CannotReanchor
2011 })?;
2012
2013 let mut local_execute_xcm = Xcm(vec![
2015 WithdrawAsset(assets.clone()),
2017 BurnAsset(assets),
2019 ]);
2020 let mut xcm_on_dest = Xcm(vec![
2022 WithdrawAsset(reanchored_assets),
2024 ClearOrigin,
2026 ]);
2027 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2029
2030 let custom_remote_xcm = match beneficiary {
2032 Either::Right(custom_xcm) => custom_xcm,
2033 Either::Left(beneficiary) => {
2034 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2036 },
2037 };
2038 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2039
2040 Ok((local_execute_xcm, xcm_on_dest))
2041 }
2042
2043 fn remote_reserve_transfer_program(
2045 origin: Location,
2046 reserve: Location,
2047 beneficiary: Either<Location, Xcm<()>>,
2048 dest: Location,
2049 assets: Vec<Asset>,
2050 fees: Asset,
2051 weight_limit: WeightLimit,
2052 ) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
2053 let value = (origin, assets);
2054 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2055 let (_, assets) = value;
2056
2057 let max_assets = assets.len() as u32;
2058 let context = T::UniversalLocation::get();
2059 let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
2062 let reserve_fees = fees_half_1
2064 .reanchored(&reserve, &context)
2065 .map_err(|e| {
2066 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor reserve_fees");
2067 Error::<T>::CannotReanchor
2068 })?;
2069 let dest_fees = fees_half_2
2071 .reanchored(&dest, &context)
2072 .map_err(|e| {
2073 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?dest, ?context, "Failed to re-anchor dest_fees");
2074 Error::<T>::CannotReanchor
2075 })?;
2076 let dest = dest.reanchored(&reserve, &context).map_err(|e| {
2078 tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor dest");
2079 Error::<T>::CannotReanchor
2080 })?;
2081 let mut xcm_on_dest =
2083 Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
2084 let custom_xcm_on_dest = match beneficiary {
2086 Either::Right(custom_xcm) => custom_xcm,
2087 Either::Left(beneficiary) => {
2088 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2090 },
2091 };
2092 xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
2093 let xcm_on_reserve = Xcm(vec![
2095 BuyExecution { fees: reserve_fees, weight_limit },
2096 DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
2097 ]);
2098 Ok(Xcm(vec![
2099 WithdrawAsset(assets.into()),
2100 SetFeesMode { jit_withdraw: true },
2101 InitiateReserveWithdraw {
2102 assets: Wild(AllCounted(max_assets)),
2103 reserve,
2104 xcm: xcm_on_reserve,
2105 },
2106 ]))
2107 }
2108
2109 fn teleport_fees_instructions(
2110 origin: Location,
2111 dest: Location,
2112 fees: Asset,
2113 weight_limit: WeightLimit,
2114 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2115 let value = (origin, vec![fees.clone()]);
2116 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2117 ensure!(
2118 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&fees, &dest),
2119 Error::<T>::Filtered
2120 );
2121
2122 let context = T::UniversalLocation::get();
2123 let reanchored_fees = fees
2124 .clone()
2125 .reanchored(&dest, &context)
2126 .map_err(|e| {
2127 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?dest, ?context, "Failed to re-anchor fees");
2128 Error::<T>::CannotReanchor
2129 })?;
2130
2131 let dummy_context =
2133 XcmContext { origin: None, message_id: Default::default(), topic: None };
2134 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2139 &dest,
2140 &fees,
2141 &dummy_context,
2142 )
2143 .map_err(|e| {
2144 tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?fees, ?dest, "Failed can_check_out");
2145 Error::<T>::CannotCheckOutTeleport
2146 })?;
2147 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2150 &dest,
2151 &fees,
2152 &dummy_context,
2153 );
2154
2155 let fees: Assets = fees.into();
2156 let local_execute_xcm = Xcm(vec![
2157 WithdrawAsset(fees.clone()),
2159 BurnAsset(fees),
2161 ]);
2162 let xcm_on_dest = Xcm(vec![
2163 ReceiveTeleportedAsset(reanchored_fees.clone().into()),
2165 BuyExecution { fees: reanchored_fees, weight_limit },
2167 ]);
2168 Ok((local_execute_xcm, xcm_on_dest))
2169 }
2170
2171 fn teleport_assets_program(
2172 origin: Location,
2173 dest: Location,
2174 beneficiary: Either<Location, Xcm<()>>,
2175 assets: Vec<Asset>,
2176 fees: FeesHandling<T>,
2177 weight_limit: WeightLimit,
2178 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2179 let value = (origin, assets);
2180 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2181 let (_, assets) = value;
2182 for asset in assets.iter() {
2183 ensure!(
2184 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&asset, &dest),
2185 Error::<T>::Filtered
2186 );
2187 }
2188
2189 let max_assets =
2191 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2192 let context = T::UniversalLocation::get();
2193 let assets: Assets = assets.into();
2194 let mut reanchored_assets = assets.clone();
2195 reanchored_assets
2196 .reanchor(&dest, &context)
2197 .map_err(|e| {
2198 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?dest, ?context, "Failed to re-anchor asset");
2199 Error::<T>::CannotReanchor
2200 })?;
2201
2202 let dummy_context =
2204 XcmContext { origin: None, message_id: Default::default(), topic: None };
2205 for asset in assets.inner() {
2206 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2211 &dest,
2212 asset,
2213 &dummy_context,
2214 )
2215 .map_err(|e| {
2216 tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?asset, ?dest, "Failed can_check_out asset");
2217 Error::<T>::CannotCheckOutTeleport
2218 })?;
2219 }
2220 for asset in assets.inner() {
2221 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2224 &dest,
2225 asset,
2226 &dummy_context,
2227 );
2228 }
2229
2230 let mut local_execute_xcm = Xcm(vec![
2232 WithdrawAsset(assets.clone()),
2234 BurnAsset(assets),
2236 ]);
2237 let mut xcm_on_dest = Xcm(vec![
2239 ReceiveTeleportedAsset(reanchored_assets),
2241 ClearOrigin,
2243 ]);
2244 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2246
2247 let custom_remote_xcm = match beneficiary {
2249 Either::Right(custom_xcm) => custom_xcm,
2250 Either::Left(beneficiary) => {
2251 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2253 },
2254 };
2255 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2256
2257 Ok((local_execute_xcm, xcm_on_dest))
2258 }
2259
2260 pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
2262 match fees.fun {
2263 Fungible(amount) => {
2264 let fee1 = amount.saturating_div(2);
2265 let fee2 = amount.saturating_sub(fee1);
2266 ensure!(fee1 > 0, Error::<T>::FeesNotMet);
2267 ensure!(fee2 > 0, Error::<T>::FeesNotMet);
2268 Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
2269 },
2270 NonFungible(_) => Err(Error::<T>::FeesNotMet),
2271 }
2272 }
2273
2274 pub(crate) fn check_xcm_version_change(
2277 mut stage: VersionMigrationStage,
2278 weight_cutoff: Weight,
2279 ) -> (Weight, Option<VersionMigrationStage>) {
2280 let mut weight_used = Weight::zero();
2281
2282 let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
2283 let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
2284 let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
2285 let vnt_notify_weight = T::WeightInfo::notify_current_targets();
2286 let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
2287 let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
2288 let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
2289
2290 use VersionMigrationStage::*;
2291
2292 if stage == MigrateSupportedVersion {
2293 for v in 0..XCM_VERSION {
2296 for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
2297 if let Ok(new_key) = old_key.into_latest() {
2298 SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
2299 }
2300 weight_used.saturating_accrue(sv_migrate_weight);
2301 if weight_used.any_gte(weight_cutoff) {
2302 return (weight_used, Some(stage))
2303 }
2304 }
2305 }
2306 stage = MigrateVersionNotifiers;
2307 }
2308 if stage == MigrateVersionNotifiers {
2309 for v in 0..XCM_VERSION {
2310 for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
2311 if let Ok(new_key) = old_key.into_latest() {
2312 VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
2313 }
2314 weight_used.saturating_accrue(vn_migrate_weight);
2315 if weight_used.any_gte(weight_cutoff) {
2316 return (weight_used, Some(stage))
2317 }
2318 }
2319 }
2320 stage = NotifyCurrentTargets(None);
2321 }
2322
2323 let xcm_version = T::AdvertisedXcmVersion::get();
2324
2325 if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
2326 let mut iter = match maybe_last_raw_key {
2327 Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
2328 None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
2329 };
2330 while let Some((key, value)) = iter.next() {
2331 let (query_id, max_weight, target_xcm_version) = value;
2332 let new_key: Location = match key.clone().try_into() {
2333 Ok(k) if target_xcm_version != xcm_version => k,
2334 _ => {
2335 weight_used.saturating_accrue(vnt_already_notified_weight);
2338 continue
2339 },
2340 };
2341 let response = Response::Version(xcm_version);
2342 let message =
2343 Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
2344 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2345 Ok((message_id, cost)) => {
2346 let value = (query_id, max_weight, xcm_version);
2347 VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
2348 Event::VersionChangeNotified {
2349 destination: new_key,
2350 result: xcm_version,
2351 cost,
2352 message_id,
2353 }
2354 },
2355 Err(e) => {
2356 VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
2357 Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
2358 },
2359 };
2360 Self::deposit_event(event);
2361 weight_used.saturating_accrue(vnt_notify_weight);
2362 if weight_used.any_gte(weight_cutoff) {
2363 let last = Some(iter.last_raw_key().into());
2364 return (weight_used, Some(NotifyCurrentTargets(last)))
2365 }
2366 }
2367 stage = MigrateAndNotifyOldTargets;
2368 }
2369 if stage == MigrateAndNotifyOldTargets {
2370 for v in 0..XCM_VERSION {
2371 for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
2372 let (query_id, max_weight, target_xcm_version) = value;
2373 let new_key = match Location::try_from(old_key.clone()) {
2374 Ok(k) => k,
2375 Err(()) => {
2376 Self::deposit_event(Event::NotifyTargetMigrationFail {
2377 location: old_key,
2378 query_id: value.0,
2379 });
2380 weight_used.saturating_accrue(vnt_migrate_fail_weight);
2381 if weight_used.any_gte(weight_cutoff) {
2382 return (weight_used, Some(stage))
2383 }
2384 continue
2385 },
2386 };
2387
2388 let versioned_key = LatestVersionedLocation(&new_key);
2389 if target_xcm_version == xcm_version {
2390 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
2391 weight_used.saturating_accrue(vnt_migrate_weight);
2392 } else {
2393 let response = Response::Version(xcm_version);
2395 let message = Xcm(vec![QueryResponse {
2396 query_id,
2397 response,
2398 max_weight,
2399 querier: None,
2400 }]);
2401 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2402 Ok((message_id, cost)) => {
2403 VersionNotifyTargets::<T>::insert(
2404 XCM_VERSION,
2405 versioned_key,
2406 (query_id, max_weight, xcm_version),
2407 );
2408 Event::VersionChangeNotified {
2409 destination: new_key,
2410 result: xcm_version,
2411 cost,
2412 message_id,
2413 }
2414 },
2415 Err(e) => Event::NotifyTargetSendFail {
2416 location: new_key,
2417 query_id,
2418 error: e.into(),
2419 },
2420 };
2421 Self::deposit_event(event);
2422 weight_used.saturating_accrue(vnt_notify_migrate_weight);
2423 }
2424 if weight_used.any_gte(weight_cutoff) {
2425 return (weight_used, Some(stage))
2426 }
2427 }
2428 }
2429 }
2430 (weight_used, None)
2431 }
2432
2433 pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
2435 let dest = dest.into();
2436 let versioned_dest = VersionedLocation::from(dest.clone());
2437 let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
2438 ensure!(!already, XcmError::InvalidLocation);
2439 let query_id = QueryCounter::<T>::mutate(|q| {
2440 let r = *q;
2441 q.saturating_inc();
2442 r
2443 });
2444 let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
2446 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
2447 Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
2448 VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
2449 let query_status =
2450 QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
2451 Queries::<T>::insert(query_id, query_status);
2452 Ok(())
2453 }
2454
2455 pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
2457 let dest = dest.into();
2458 let versioned_dest = LatestVersionedLocation(&dest);
2459 let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
2460 .ok_or(XcmError::InvalidLocation)?;
2461 let (message_id, cost) =
2462 send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
2463 Self::deposit_event(Event::VersionNotifyUnrequested {
2464 destination: dest,
2465 cost,
2466 message_id,
2467 });
2468 Queries::<T>::remove(query_id);
2469 Ok(())
2470 }
2471
2472 pub fn send_xcm(
2476 interior: impl Into<Junctions>,
2477 dest: impl Into<Location>,
2478 mut message: Xcm<()>,
2479 ) -> Result<XcmHash, SendError> {
2480 let interior = interior.into();
2481 let dest = dest.into();
2482 let maybe_fee_payer = if interior != Junctions::Here {
2483 message.0.insert(0, DescendOrigin(interior.clone()));
2484 Some(interior.into())
2485 } else {
2486 None
2487 };
2488 tracing::debug!(target: "xcm::send_xcm", "{:?}, {:?}", dest.clone(), message.clone());
2489 let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
2490 if let Some(fee_payer) = maybe_fee_payer {
2491 Self::charge_fees(fee_payer, price).map_err(|e| {
2492 tracing::error!(
2493 target: "xcm::pallet_xcm::send_xcm",
2494 ?e,
2495 "Charging fees failed with error",
2496 );
2497 SendError::Fees
2498 })?;
2499 }
2500 T::XcmRouter::deliver(ticket)
2501 }
2502
2503 pub fn check_account() -> T::AccountId {
2504 const ID: PalletId = PalletId(*b"py/xcmch");
2505 AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
2506 }
2507
2508 pub fn dry_run_call<Runtime, Router, OriginCaller, RuntimeCall>(
2514 origin: OriginCaller,
2515 call: RuntimeCall,
2516 result_xcms_version: XcmVersion,
2517 ) -> Result<CallDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
2518 where
2519 Runtime: crate::Config,
2520 Router: InspectMessageQueues,
2521 RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
2522 <RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
2523 {
2524 crate::Pallet::<Runtime>::set_record_xcm(true);
2525 Router::clear_messages();
2527 frame_system::Pallet::<Runtime>::reset_events();
2529 let result = call.dispatch(origin.into());
2530 crate::Pallet::<Runtime>::set_record_xcm(false);
2531 let local_xcm = crate::Pallet::<Runtime>::recorded_xcm()
2532 .map(|xcm| VersionedXcm::<()>::from(xcm).into_version(result_xcms_version))
2533 .transpose()
2534 .map_err(|()| {
2535 tracing::error!(
2536 target: "xcm::DryRunApi::dry_run_call",
2537 "Local xcm version conversion failed"
2538 );
2539
2540 XcmDryRunApiError::VersionedConversionFailed
2541 })?;
2542
2543 let forwarded_xcms =
2545 Self::convert_forwarded_xcms(result_xcms_version, Router::get_messages()).inspect_err(
2546 |error| {
2547 tracing::error!(
2548 target: "xcm::DryRunApi::dry_run_call",
2549 ?error, "Forwarded xcms version conversion failed with error"
2550 );
2551 },
2552 )?;
2553 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
2554 frame_system::Pallet::<Runtime>::read_events_no_consensus()
2555 .map(|record| record.event.clone())
2556 .collect();
2557 Ok(CallDryRunEffects {
2558 local_xcm: local_xcm.map(VersionedXcm::<()>::from),
2559 forwarded_xcms,
2560 emitted_events: events,
2561 execution_result: result,
2562 })
2563 }
2564
2565 pub fn dry_run_xcm<Runtime, Router, RuntimeCall: Decode + GetDispatchInfo, XcmConfig>(
2570 origin_location: VersionedLocation,
2571 xcm: VersionedXcm<RuntimeCall>,
2572 ) -> Result<XcmDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
2573 where
2574 Runtime: frame_system::Config,
2575 Router: InspectMessageQueues,
2576 XcmConfig: xcm_executor::Config<RuntimeCall = RuntimeCall>,
2577 {
2578 let origin_location: Location = origin_location.try_into().map_err(|error| {
2579 tracing::error!(
2580 target: "xcm::DryRunApi::dry_run_xcm",
2581 ?error, "Location version conversion failed with error"
2582 );
2583 XcmDryRunApiError::VersionedConversionFailed
2584 })?;
2585 let xcm_version = xcm.identify_version();
2586 let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| {
2587 tracing::error!(
2588 target: "xcm::DryRunApi::dry_run_xcm",
2589 ?error, "Xcm version conversion failed with error"
2590 );
2591 XcmDryRunApiError::VersionedConversionFailed
2592 })?;
2593 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
2594
2595 Router::clear_messages();
2597 frame_system::Pallet::<Runtime>::reset_events();
2598
2599 let result = xcm_executor::XcmExecutor::<XcmConfig>::prepare_and_execute(
2600 origin_location,
2601 xcm,
2602 &mut hash,
2603 Weight::MAX, Weight::zero(),
2605 );
2606 let forwarded_xcms = Self::convert_forwarded_xcms(xcm_version, Router::get_messages())
2607 .inspect_err(|error| {
2608 tracing::error!(
2609 target: "xcm::DryRunApi::dry_run_xcm",
2610 ?error, "Forwarded xcms version conversion failed with error"
2611 );
2612 })?;
2613 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
2614 frame_system::Pallet::<Runtime>::read_events_no_consensus()
2615 .map(|record| record.event.clone())
2616 .collect();
2617 Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
2618 }
2619
2620 fn convert_xcms(
2621 xcm_version: XcmVersion,
2622 xcms: Vec<VersionedXcm<()>>,
2623 ) -> Result<Vec<VersionedXcm<()>>, ()> {
2624 xcms.into_iter()
2625 .map(|xcm| xcm.into_version(xcm_version))
2626 .collect::<Result<Vec<_>, ()>>()
2627 }
2628
2629 fn convert_forwarded_xcms(
2630 xcm_version: XcmVersion,
2631 forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>,
2632 ) -> Result<Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, XcmDryRunApiError> {
2633 forwarded_xcms
2634 .into_iter()
2635 .map(|(dest, forwarded_xcms)| {
2636 let dest = dest.into_version(xcm_version)?;
2637 let forwarded_xcms = Self::convert_xcms(xcm_version, forwarded_xcms)?;
2638
2639 Ok((dest, forwarded_xcms))
2640 })
2641 .collect::<Result<Vec<_>, ()>>()
2642 .map_err(|()| XcmDryRunApiError::VersionedConversionFailed)
2643 }
2644
2645 pub fn query_acceptable_payment_assets(
2650 version: xcm::Version,
2651 asset_ids: Vec<AssetId>,
2652 ) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
2653 Ok(asset_ids
2654 .into_iter()
2655 .map(|asset_id| VersionedAssetId::from(asset_id))
2656 .filter_map(|asset_id| asset_id.into_version(version).ok())
2657 .collect())
2658 }
2659
2660 pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
2661 let message = Xcm::<()>::try_from(message.clone())
2662 .map_err(|e| {
2663 tracing::error!(target: "xcm::pallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message");
2664 XcmPaymentApiError::VersionedConversionFailed
2665 })?;
2666
2667 T::Weigher::weight(&mut message.clone().into()).map_err(|()| {
2668 tracing::error!(target: "xcm::pallet_xcm::query_xcm_weight", ?message, "Error when querying XCM weight");
2669 XcmPaymentApiError::WeightNotComputable
2670 })
2671 }
2672
2673 pub fn is_trusted_reserve(
2676 asset: VersionedAsset,
2677 location: VersionedLocation,
2678 ) -> Result<bool, TrustedQueryApiError> {
2679 let location: Location = location.try_into().map_err(|e| {
2680 tracing::debug!(
2681 target: "xcm::pallet_xcm::is_trusted_reserve",
2682 "Asset version conversion failed with error: {:?}",
2683 e,
2684 );
2685 TrustedQueryApiError::VersionedLocationConversionFailed
2686 })?;
2687
2688 let a: Asset = asset.try_into().map_err(|e| {
2689 tracing::debug!(
2690 target: "xcm::pallet_xcm::is_trusted_reserve",
2691 "Location version conversion failed with error: {:?}",
2692 e,
2693 );
2694 TrustedQueryApiError::VersionedAssetConversionFailed
2695 })?;
2696
2697 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&a, &location))
2698 }
2699
2700 pub fn is_trusted_teleporter(
2702 asset: VersionedAsset,
2703 location: VersionedLocation,
2704 ) -> Result<bool, TrustedQueryApiError> {
2705 let location: Location = location.try_into().map_err(|e| {
2706 tracing::debug!(
2707 target: "xcm::pallet_xcm::is_trusted_teleporter",
2708 "Asset version conversion failed with error: {:?}",
2709 e,
2710 );
2711 TrustedQueryApiError::VersionedLocationConversionFailed
2712 })?;
2713 let a: Asset = asset.try_into().map_err(|e| {
2714 tracing::debug!(
2715 target: "xcm::pallet_xcm::is_trusted_teleporter",
2716 "Location version conversion failed with error: {:?}",
2717 e,
2718 );
2719 TrustedQueryApiError::VersionedAssetConversionFailed
2720 })?;
2721 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&a, &location))
2722 }
2723
2724 pub fn query_delivery_fees(
2725 destination: VersionedLocation,
2726 message: VersionedXcm<()>,
2727 ) -> Result<VersionedAssets, XcmPaymentApiError> {
2728 let result_version = destination.identify_version().max(message.identify_version());
2729
2730 let destination: Location = destination
2731 .clone()
2732 .try_into()
2733 .map_err(|e| {
2734 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination");
2735 XcmPaymentApiError::VersionedConversionFailed
2736 })?;
2737
2738 let message: Xcm<()> =
2739 message.clone().try_into().map_err(|e| {
2740 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message");
2741 XcmPaymentApiError::VersionedConversionFailed
2742 })?;
2743
2744 let (_, fees) = validate_send::<T::XcmRouter>(destination.clone(), message.clone()).map_err(|error| {
2745 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination");
2746 XcmPaymentApiError::Unroutable
2747 })?;
2748
2749 VersionedAssets::from(fees)
2750 .into_version(result_version)
2751 .map_err(|e| {
2752 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?result_version, "Failed to convert fees into version");
2753 XcmPaymentApiError::VersionedConversionFailed
2754 })
2755 }
2756
2757 fn do_new_query(
2759 responder: impl Into<Location>,
2760 maybe_notify: Option<(u8, u8)>,
2761 timeout: BlockNumberFor<T>,
2762 match_querier: impl Into<Location>,
2763 ) -> u64 {
2764 QueryCounter::<T>::mutate(|q| {
2765 let r = *q;
2766 q.saturating_inc();
2767 Queries::<T>::insert(
2768 r,
2769 QueryStatus::Pending {
2770 responder: responder.into().into(),
2771 maybe_match_querier: Some(match_querier.into().into()),
2772 maybe_notify,
2773 timeout,
2774 },
2775 );
2776 r
2777 })
2778 }
2779
2780 pub fn report_outcome_notify(
2803 message: &mut Xcm<()>,
2804 responder: impl Into<Location>,
2805 notify: impl Into<<T as Config>::RuntimeCall>,
2806 timeout: BlockNumberFor<T>,
2807 ) -> Result<(), XcmError> {
2808 let responder = responder.into();
2809 let destination = T::UniversalLocation::get()
2810 .invert_target(&responder)
2811 .map_err(|()| XcmError::LocationNotInvertible)?;
2812 let notify: <T as Config>::RuntimeCall = notify.into();
2813 let max_weight = notify.get_dispatch_info().call_weight;
2814 let query_id = Self::new_notify_query(responder, notify, timeout, Here);
2815 let response_info = QueryResponseInfo { destination, query_id, max_weight };
2816 let report_error = Xcm(vec![ReportError(response_info)]);
2817 message.0.insert(0, SetAppendix(report_error));
2818 Ok(())
2819 }
2820
2821 pub fn new_notify_query(
2824 responder: impl Into<Location>,
2825 notify: impl Into<<T as Config>::RuntimeCall>,
2826 timeout: BlockNumberFor<T>,
2827 match_querier: impl Into<Location>,
2828 ) -> u64 {
2829 let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
2830 "decode input is output of Call encode; Call guaranteed to have two enums; qed",
2831 );
2832 Self::do_new_query(responder, Some(notify), timeout, match_querier)
2833 }
2834
2835 fn note_unknown_version(dest: &Location) {
2838 tracing::trace!(
2839 target: "xcm::pallet_xcm::note_unknown_version",
2840 ?dest, "XCM version is unknown for destination"
2841 );
2842 let versioned_dest = VersionedLocation::from(dest.clone());
2843 VersionDiscoveryQueue::<T>::mutate(|q| {
2844 if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
2845 q[index].1.saturating_inc();
2847 } else {
2848 let _ = q.try_push((versioned_dest, 1));
2849 }
2850 });
2851 }
2852
2853 fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
2859 T::XcmExecutor::charge_fees(location.clone(), assets.clone())
2860 .map_err(|_| Error::<T>::FeesNotMet)?;
2861 Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
2862 Ok(())
2863 }
2864
2865 #[cfg(any(feature = "try-runtime", test))]
2875 pub fn do_try_state() -> Result<(), TryRuntimeError> {
2876 use migration::data::NeedsMigration;
2877
2878 let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
2882 {
2883 XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
2884 } else {
2885 XCM_VERSION.saturating_sub(1)
2886 };
2887
2888 ensure!(
2890 !Queries::<T>::iter_values()
2891 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
2892 TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
2893 );
2894
2895 ensure!(
2897 !LockedFungibles::<T>::iter_values()
2898 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
2899 TryRuntimeError::Other(
2900 "`LockedFungibles` data should be migrated to the higher xcm version!"
2901 )
2902 );
2903
2904 ensure!(
2906 !RemoteLockedFungibles::<T>::iter()
2907 .any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version) ||
2908 data.needs_migration(minimal_allowed_xcm_version)),
2909 TryRuntimeError::Other(
2910 "`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
2911 )
2912 );
2913
2914 if CurrentMigration::<T>::exists() {
2917 return Ok(())
2918 }
2919
2920 for v in 0..XCM_VERSION {
2922 ensure!(
2923 SupportedVersion::<T>::iter_prefix(v).next().is_none(),
2924 TryRuntimeError::Other(
2925 "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
2926 )
2927 );
2928 ensure!(
2929 VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
2930 TryRuntimeError::Other(
2931 "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
2932 )
2933 );
2934 ensure!(
2935 VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
2936 TryRuntimeError::Other(
2937 "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
2938 )
2939 );
2940 }
2941
2942 Ok(())
2943 }
2944}
2945
2946pub struct LockTicket<T: Config> {
2947 sovereign_account: T::AccountId,
2948 amount: BalanceOf<T>,
2949 unlocker: Location,
2950 item_index: Option<usize>,
2951}
2952
2953impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
2954 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
2955 use xcm_executor::traits::LockError::UnexpectedState;
2956 let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
2957 match self.item_index {
2958 Some(index) => {
2959 ensure!(locks.len() > index, UnexpectedState);
2960 ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
2961 locks[index].0 = locks[index].0.max(self.amount);
2962 },
2963 None => {
2964 locks
2965 .try_push((self.amount, self.unlocker.into()))
2966 .map_err(|(_balance, _location)| UnexpectedState)?;
2967 },
2968 }
2969 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
2970 T::Currency::extend_lock(
2971 *b"py/xcmlk",
2972 &self.sovereign_account,
2973 self.amount,
2974 WithdrawReasons::all(),
2975 );
2976 Ok(())
2977 }
2978}
2979
2980pub struct UnlockTicket<T: Config> {
2981 sovereign_account: T::AccountId,
2982 amount: BalanceOf<T>,
2983 unlocker: Location,
2984}
2985
2986impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
2987 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
2988 use xcm_executor::traits::LockError::UnexpectedState;
2989 let mut locks =
2990 LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
2991 let mut maybe_remove_index = None;
2992 let mut locked = BalanceOf::<T>::zero();
2993 let mut found = false;
2994 for (i, x) in locks.iter_mut().enumerate() {
2997 if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
2998 x.0 = x.0.saturating_sub(self.amount);
2999 if x.0.is_zero() {
3000 maybe_remove_index = Some(i);
3001 }
3002 found = true;
3003 }
3004 locked = locked.max(x.0);
3005 }
3006 ensure!(found, UnexpectedState);
3007 if let Some(remove_index) = maybe_remove_index {
3008 locks.swap_remove(remove_index);
3009 }
3010 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3011 let reasons = WithdrawReasons::all();
3012 T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
3013 Ok(())
3014 }
3015}
3016
3017pub struct ReduceTicket<T: Config> {
3018 key: (u32, T::AccountId, VersionedAssetId),
3019 amount: u128,
3020 locker: VersionedLocation,
3021 owner: VersionedLocation,
3022}
3023
3024impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
3025 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3026 use xcm_executor::traits::LockError::UnexpectedState;
3027 let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
3028 ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
3029 let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
3030 ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
3031 if new_amount == 0 {
3032 RemoteLockedFungibles::<T>::remove(&self.key);
3033 } else {
3034 record.amount = new_amount;
3035 RemoteLockedFungibles::<T>::insert(&self.key, &record);
3036 }
3037 Ok(())
3038 }
3039}
3040
3041impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
3042 type LockTicket = LockTicket<T>;
3043 type UnlockTicket = UnlockTicket<T>;
3044 type ReduceTicket = ReduceTicket<T>;
3045
3046 fn prepare_lock(
3047 unlocker: Location,
3048 asset: Asset,
3049 owner: Location,
3050 ) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
3051 use xcm_executor::traits::LockError::*;
3052 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3053 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3054 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3055 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3056 let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
3057 ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
3058 Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
3059 }
3060
3061 fn prepare_unlock(
3062 unlocker: Location,
3063 asset: Asset,
3064 owner: Location,
3065 ) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
3066 use xcm_executor::traits::LockError::*;
3067 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3068 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3069 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3070 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3071 let item_index =
3072 locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
3073 ensure!(locks[item_index].0 >= amount, NotLocked);
3074 Ok(UnlockTicket { sovereign_account, amount, unlocker })
3075 }
3076
3077 fn note_unlockable(
3078 locker: Location,
3079 asset: Asset,
3080 mut owner: Location,
3081 ) -> Result<(), xcm_executor::traits::LockError> {
3082 use xcm_executor::traits::LockError::*;
3083 ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
3084 let amount = match asset.fun {
3085 Fungible(a) => a,
3086 NonFungible(_) => return Err(Unimplemented),
3087 };
3088 owner.remove_network_id();
3089 let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3090 let locker = locker.into();
3091 let owner = owner.into();
3092 let id: VersionedAssetId = asset.id.into();
3093 let key = (XCM_VERSION, account, id);
3094 let mut record =
3095 RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
3096 if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
3097 ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
3099 record.consumers = old.consumers;
3100 record.amount = record.amount.max(old.amount);
3101 }
3102 RemoteLockedFungibles::<T>::insert(&key, record);
3103 Ok(())
3104 }
3105
3106 fn prepare_reduce_unlockable(
3107 locker: Location,
3108 asset: Asset,
3109 mut owner: Location,
3110 ) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
3111 use xcm_executor::traits::LockError::*;
3112 let amount = match asset.fun {
3113 Fungible(a) => a,
3114 NonFungible(_) => return Err(Unimplemented),
3115 };
3116 owner.remove_network_id();
3117 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3118 let locker = locker.into();
3119 let owner = owner.into();
3120 let id: VersionedAssetId = asset.id.into();
3121 let key = (XCM_VERSION, sovereign_account, id);
3122
3123 let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
3124 ensure!(locker == record.locker && owner == record.owner, WouldClobber);
3126 ensure!(record.amount >= amount, NotEnoughLocked);
3127 ensure!(
3128 record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
3129 InUse
3130 );
3131 Ok(ReduceTicket { key, amount, locker, owner })
3132 }
3133}
3134
3135impl<T: Config> WrapVersion for Pallet<T> {
3136 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
3137 dest: &Location,
3138 xcm: impl Into<VersionedXcm<RuntimeCall>>,
3139 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
3140 Self::get_version_for(dest)
3141 .or_else(|| {
3142 Self::note_unknown_version(dest);
3143 SafeXcmVersion::<T>::get()
3144 })
3145 .ok_or_else(|| {
3146 tracing::trace!(
3147 target: "xcm::pallet_xcm::wrap_version",
3148 ?dest, "Could not determine a version to wrap XCM for destination",
3149 );
3150 ()
3151 })
3152 .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
3153 }
3154}
3155
3156impl<T: Config> GetVersion for Pallet<T> {
3157 fn get_version_for(dest: &Location) -> Option<XcmVersion> {
3158 SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
3159 }
3160}
3161
3162impl<T: Config> VersionChangeNotifier for Pallet<T> {
3163 fn start(
3172 dest: &Location,
3173 query_id: QueryId,
3174 max_weight: Weight,
3175 _context: &XcmContext,
3176 ) -> XcmResult {
3177 let versioned_dest = LatestVersionedLocation(dest);
3178 let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
3179 ensure!(!already, XcmError::InvalidLocation);
3180
3181 let xcm_version = T::AdvertisedXcmVersion::get();
3182 let response = Response::Version(xcm_version);
3183 let instruction = QueryResponse { query_id, response, max_weight, querier: None };
3184 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3185 Self::deposit_event(Event::<T>::VersionNotifyStarted {
3186 destination: dest.clone(),
3187 cost,
3188 message_id,
3189 });
3190
3191 let value = (query_id, max_weight, xcm_version);
3192 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
3193 Ok(())
3194 }
3195
3196 fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
3199 VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
3200 Ok(())
3201 }
3202
3203 fn is_subscribed(dest: &Location) -> bool {
3205 let versioned_dest = LatestVersionedLocation(dest);
3206 VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
3207 }
3208}
3209
3210impl<T: Config> DropAssets for Pallet<T> {
3211 fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
3212 if assets.is_empty() {
3213 return Weight::zero()
3214 }
3215 let versioned = VersionedAssets::from(Assets::from(assets));
3216 let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
3217 AssetTraps::<T>::mutate(hash, |n| *n += 1);
3218 Self::deposit_event(Event::AssetsTrapped {
3219 hash,
3220 origin: origin.clone(),
3221 assets: versioned,
3222 });
3223 Weight::zero()
3225 }
3226}
3227
3228impl<T: Config> ClaimAssets for Pallet<T> {
3229 fn claim_assets(
3230 origin: &Location,
3231 ticket: &Location,
3232 assets: &Assets,
3233 _context: &XcmContext,
3234 ) -> bool {
3235 let mut versioned = VersionedAssets::from(assets.clone());
3236 match ticket.unpack() {
3237 (0, [GeneralIndex(i)]) =>
3238 versioned = match versioned.into_version(*i as u32) {
3239 Ok(v) => v,
3240 Err(()) => return false,
3241 },
3242 (0, []) => (),
3243 _ => return false,
3244 };
3245 let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
3246 match AssetTraps::<T>::get(hash) {
3247 0 => return false,
3248 1 => AssetTraps::<T>::remove(hash),
3249 n => AssetTraps::<T>::insert(hash, n - 1),
3250 }
3251 Self::deposit_event(Event::AssetsClaimed {
3252 hash,
3253 origin: origin.clone(),
3254 assets: versioned,
3255 });
3256 return true
3257 }
3258}
3259
3260impl<T: Config> OnResponse for Pallet<T> {
3261 fn expecting_response(
3262 origin: &Location,
3263 query_id: QueryId,
3264 querier: Option<&Location>,
3265 ) -> bool {
3266 match Queries::<T>::get(query_id) {
3267 Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
3268 Location::try_from(responder).map_or(false, |r| origin == &r) &&
3269 maybe_match_querier.map_or(true, |match_querier| {
3270 Location::try_from(match_querier).map_or(false, |match_querier| {
3271 querier.map_or(false, |q| q == &match_querier)
3272 })
3273 }),
3274 Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
3275 Location::try_from(r).map_or(false, |r| origin == &r),
3276 _ => false,
3277 }
3278 }
3279
3280 fn on_response(
3281 origin: &Location,
3282 query_id: QueryId,
3283 querier: Option<&Location>,
3284 response: Response,
3285 max_weight: Weight,
3286 _context: &XcmContext,
3287 ) -> Weight {
3288 let origin = origin.clone();
3289 match (response, Queries::<T>::get(query_id)) {
3290 (
3291 Response::Version(v),
3292 Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
3293 ) => {
3294 let origin: Location = match expected_origin.try_into() {
3295 Ok(o) if o == origin => o,
3296 Ok(o) => {
3297 Self::deposit_event(Event::InvalidResponder {
3298 origin: origin.clone(),
3299 query_id,
3300 expected_location: Some(o),
3301 });
3302 return Weight::zero()
3303 },
3304 _ => {
3305 Self::deposit_event(Event::InvalidResponder {
3306 origin: origin.clone(),
3307 query_id,
3308 expected_location: None,
3309 });
3310 return Weight::zero()
3312 },
3313 };
3314 if !is_active {
3316 Queries::<T>::insert(
3317 query_id,
3318 QueryStatus::VersionNotifier {
3319 origin: origin.clone().into(),
3320 is_active: true,
3321 },
3322 );
3323 }
3324 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
3326 Self::deposit_event(Event::SupportedVersionChanged {
3327 location: origin,
3328 version: v,
3329 });
3330 Weight::zero()
3331 },
3332 (
3333 response,
3334 Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
3335 ) => {
3336 if let Some(match_querier) = maybe_match_querier {
3337 let match_querier = match Location::try_from(match_querier) {
3338 Ok(mq) => mq,
3339 Err(_) => {
3340 Self::deposit_event(Event::InvalidQuerierVersion {
3341 origin: origin.clone(),
3342 query_id,
3343 });
3344 return Weight::zero()
3345 },
3346 };
3347 if querier.map_or(true, |q| q != &match_querier) {
3348 Self::deposit_event(Event::InvalidQuerier {
3349 origin: origin.clone(),
3350 query_id,
3351 expected_querier: match_querier,
3352 maybe_actual_querier: querier.cloned(),
3353 });
3354 return Weight::zero()
3355 }
3356 }
3357 let responder = match Location::try_from(responder) {
3358 Ok(r) => r,
3359 Err(_) => {
3360 Self::deposit_event(Event::InvalidResponderVersion {
3361 origin: origin.clone(),
3362 query_id,
3363 });
3364 return Weight::zero()
3365 },
3366 };
3367 if origin != responder {
3368 Self::deposit_event(Event::InvalidResponder {
3369 origin: origin.clone(),
3370 query_id,
3371 expected_location: Some(responder),
3372 });
3373 return Weight::zero()
3374 }
3375 match maybe_notify {
3376 Some((pallet_index, call_index)) => {
3377 let bare = (pallet_index, call_index, query_id, response);
3381 if let Ok(call) = bare.using_encoded(|mut bytes| {
3382 <T as Config>::RuntimeCall::decode(&mut bytes)
3383 }) {
3384 Queries::<T>::remove(query_id);
3385 let weight = call.get_dispatch_info().call_weight;
3386 if weight.any_gt(max_weight) {
3387 let e = Event::NotifyOverweight {
3388 query_id,
3389 pallet_index,
3390 call_index,
3391 actual_weight: weight,
3392 max_budgeted_weight: max_weight,
3393 };
3394 Self::deposit_event(e);
3395 return Weight::zero()
3396 }
3397 let dispatch_origin = Origin::Response(origin.clone()).into();
3398 match call.dispatch(dispatch_origin) {
3399 Ok(post_info) => {
3400 let e = Event::Notified { query_id, pallet_index, call_index };
3401 Self::deposit_event(e);
3402 post_info.actual_weight
3403 },
3404 Err(error_and_info) => {
3405 let e = Event::NotifyDispatchError {
3406 query_id,
3407 pallet_index,
3408 call_index,
3409 };
3410 Self::deposit_event(e);
3411 error_and_info.post_info.actual_weight
3414 },
3415 }
3416 .unwrap_or(weight)
3417 } else {
3418 let e =
3419 Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
3420 Self::deposit_event(e);
3421 Weight::zero()
3422 }
3423 },
3424 None => {
3425 let e = Event::ResponseReady { query_id, response: response.clone() };
3426 Self::deposit_event(e);
3427 let at = frame_system::Pallet::<T>::current_block_number();
3428 let response = response.into();
3429 Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
3430 Weight::zero()
3431 },
3432 }
3433 },
3434 _ => {
3435 let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
3436 Self::deposit_event(e);
3437 Weight::zero()
3438 },
3439 }
3440 }
3441}
3442
3443impl<T: Config> CheckSuspension for Pallet<T> {
3444 fn is_suspended<Call>(
3445 _origin: &Location,
3446 _instructions: &mut [Instruction<Call>],
3447 _max_weight: Weight,
3448 _properties: &mut Properties,
3449 ) -> bool {
3450 XcmExecutionSuspended::<T>::get()
3451 }
3452}
3453
3454impl<T: Config> RecordXcm for Pallet<T> {
3455 fn should_record() -> bool {
3456 ShouldRecordXcm::<T>::get()
3457 }
3458
3459 fn set_record_xcm(enabled: bool) {
3460 ShouldRecordXcm::<T>::put(enabled);
3461 }
3462
3463 fn recorded_xcm() -> Option<Xcm<()>> {
3464 RecordedXcm::<T>::get()
3465 }
3466
3467 fn record(xcm: Xcm<()>) {
3468 RecordedXcm::<T>::put(xcm);
3469 }
3470}
3471
3472pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
3476where
3477 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
3478{
3479 match o.into() {
3480 Ok(Origin::Xcm(location)) => Ok(location),
3481 _ => Err(BadOrigin),
3482 }
3483}
3484
3485pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
3489where
3490 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
3491{
3492 match o.into() {
3493 Ok(Origin::Response(location)) => Ok(location),
3494 _ => Err(BadOrigin),
3495 }
3496}
3497
3498pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
3503impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
3504 for IsMajorityOfBody<Prefix, Body>
3505{
3506 fn contains(l: &Location) -> bool {
3507 let maybe_suffix = l.match_and_split(&Prefix::get());
3508 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
3509 }
3510}
3511
3512pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
3516impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
3517 fn contains(l: &Location) -> bool {
3518 let maybe_suffix = l.match_and_split(&Prefix::get());
3519 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
3520 }
3521}
3522
3523pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
3526impl<
3527 O: OriginTrait + From<Origin>,
3528 F: Contains<L>,
3529 L: TryFrom<Location> + TryInto<Location> + Clone,
3530 > EnsureOrigin<O> for EnsureXcm<F, L>
3531where
3532 O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
3533{
3534 type Success = L;
3535
3536 fn try_origin(outer: O) -> Result<Self::Success, O> {
3537 outer.try_with_caller(|caller| {
3538 caller.try_into().and_then(|o| match o {
3539 Origin::Xcm(ref location)
3540 if F::contains(&location.clone().try_into().map_err(|_| o.clone().into())?) =>
3541 Ok(location.clone().try_into().map_err(|_| o.clone().into())?),
3542 Origin::Xcm(location) => Err(Origin::Xcm(location).into()),
3543 o => Err(o.into()),
3544 })
3545 })
3546 }
3547
3548 #[cfg(feature = "runtime-benchmarks")]
3549 fn try_successful_origin() -> Result<O, ()> {
3550 Ok(O::from(Origin::Xcm(Here.into())))
3551 }
3552}
3553
3554pub struct EnsureResponse<F>(PhantomData<F>);
3557impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
3558where
3559 O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
3560{
3561 type Success = Location;
3562
3563 fn try_origin(outer: O) -> Result<Self::Success, O> {
3564 outer.try_with_caller(|caller| {
3565 caller.try_into().and_then(|o| match o {
3566 Origin::Response(responder) => Ok(responder),
3567 o => Err(o.into()),
3568 })
3569 })
3570 }
3571
3572 #[cfg(feature = "runtime-benchmarks")]
3573 fn try_successful_origin() -> Result<O, ()> {
3574 Ok(O::from(Origin::Response(Here.into())))
3575 }
3576}
3577
3578pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
3581impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
3582 for XcmPassthrough<RuntimeOrigin>
3583{
3584 fn convert_origin(
3585 origin: impl Into<Location>,
3586 kind: OriginKind,
3587 ) -> Result<RuntimeOrigin, Location> {
3588 let origin = origin.into();
3589 match kind {
3590 OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
3591 _ => Err(origin),
3592 }
3593 }
3594}