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<CallDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
2517 where
2518 Runtime: crate::Config,
2519 Router: InspectMessageQueues,
2520 RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
2521 <RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
2522 {
2523 crate::Pallet::<Runtime>::set_record_xcm(true);
2524 Router::clear_messages();
2526 frame_system::Pallet::<Runtime>::reset_events();
2528 let result = call.dispatch(origin.into());
2529 crate::Pallet::<Runtime>::set_record_xcm(false);
2530 let local_xcm = crate::Pallet::<Runtime>::recorded_xcm();
2531 let forwarded_xcms = Router::get_messages();
2533 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
2534 frame_system::Pallet::<Runtime>::read_events_no_consensus()
2535 .map(|record| record.event.clone())
2536 .collect();
2537 Ok(CallDryRunEffects {
2538 local_xcm: local_xcm.map(VersionedXcm::<()>::from),
2539 forwarded_xcms,
2540 emitted_events: events,
2541 execution_result: result,
2542 })
2543 }
2544
2545 pub fn dry_run_xcm<Runtime, Router, RuntimeCall: Decode + GetDispatchInfo, XcmConfig>(
2550 origin_location: VersionedLocation,
2551 xcm: VersionedXcm<RuntimeCall>,
2552 ) -> Result<XcmDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
2553 where
2554 Runtime: frame_system::Config,
2555 Router: InspectMessageQueues,
2556 XcmConfig: xcm_executor::Config<RuntimeCall = RuntimeCall>,
2557 {
2558 let origin_location: Location = origin_location.try_into().map_err(|error| {
2559 tracing::error!(
2560 target: "xcm::DryRunApi::dry_run_xcm",
2561 ?error, "Location version conversion failed with error"
2562 );
2563 XcmDryRunApiError::VersionedConversionFailed
2564 })?;
2565 let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| {
2566 tracing::error!(
2567 target: "xcm::DryRunApi::dry_run_xcm",
2568 ?error, "Xcm version conversion failed with error"
2569 );
2570 XcmDryRunApiError::VersionedConversionFailed
2571 })?;
2572 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
2573 frame_system::Pallet::<Runtime>::reset_events(); let result = xcm_executor::XcmExecutor::<XcmConfig>::prepare_and_execute(
2575 origin_location,
2576 xcm,
2577 &mut hash,
2578 Weight::MAX, Weight::zero(),
2580 );
2581 let forwarded_xcms = Router::get_messages();
2582 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
2583 frame_system::Pallet::<Runtime>::read_events_no_consensus()
2584 .map(|record| record.event.clone())
2585 .collect();
2586 Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
2587 }
2588
2589 pub fn query_acceptable_payment_assets(
2594 version: xcm::Version,
2595 asset_ids: Vec<AssetId>,
2596 ) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
2597 Ok(asset_ids
2598 .into_iter()
2599 .map(|asset_id| VersionedAssetId::from(asset_id))
2600 .filter_map(|asset_id| asset_id.into_version(version).ok())
2601 .collect())
2602 }
2603
2604 pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
2605 let message = Xcm::<()>::try_from(message.clone())
2606 .map_err(|e| {
2607 tracing::error!(target: "xcm::pallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message");
2608 XcmPaymentApiError::VersionedConversionFailed
2609 })?;
2610
2611 T::Weigher::weight(&mut message.clone().into()).map_err(|()| {
2612 tracing::error!(target: "xcm::pallet_xcm::query_xcm_weight", ?message, "Error when querying XCM weight");
2613 XcmPaymentApiError::WeightNotComputable
2614 })
2615 }
2616
2617 pub fn is_trusted_reserve(
2620 asset: VersionedAsset,
2621 location: VersionedLocation,
2622 ) -> Result<bool, TrustedQueryApiError> {
2623 let location: Location = location.try_into().map_err(|e| {
2624 tracing::debug!(
2625 target: "xcm::pallet_xcm::is_trusted_reserve",
2626 "Asset version conversion failed with error: {:?}",
2627 e,
2628 );
2629 TrustedQueryApiError::VersionedLocationConversionFailed
2630 })?;
2631
2632 let a: Asset = asset.try_into().map_err(|e| {
2633 tracing::debug!(
2634 target: "xcm::pallet_xcm::is_trusted_reserve",
2635 "Location version conversion failed with error: {:?}",
2636 e,
2637 );
2638 TrustedQueryApiError::VersionedAssetConversionFailed
2639 })?;
2640
2641 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&a, &location))
2642 }
2643
2644 pub fn is_trusted_teleporter(
2646 asset: VersionedAsset,
2647 location: VersionedLocation,
2648 ) -> Result<bool, TrustedQueryApiError> {
2649 let location: Location = location.try_into().map_err(|e| {
2650 tracing::debug!(
2651 target: "xcm::pallet_xcm::is_trusted_teleporter",
2652 "Asset version conversion failed with error: {:?}",
2653 e,
2654 );
2655 TrustedQueryApiError::VersionedLocationConversionFailed
2656 })?;
2657 let a: Asset = asset.try_into().map_err(|e| {
2658 tracing::debug!(
2659 target: "xcm::pallet_xcm::is_trusted_teleporter",
2660 "Location version conversion failed with error: {:?}",
2661 e,
2662 );
2663 TrustedQueryApiError::VersionedAssetConversionFailed
2664 })?;
2665 Ok(<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&a, &location))
2666 }
2667
2668 pub fn query_delivery_fees(
2669 destination: VersionedLocation,
2670 message: VersionedXcm<()>,
2671 ) -> Result<VersionedAssets, XcmPaymentApiError> {
2672 let result_version = destination.identify_version().max(message.identify_version());
2673
2674 let destination: Location = destination
2675 .clone()
2676 .try_into()
2677 .map_err(|e| {
2678 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination");
2679 XcmPaymentApiError::VersionedConversionFailed
2680 })?;
2681
2682 let message: Xcm<()> =
2683 message.clone().try_into().map_err(|e| {
2684 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message");
2685 XcmPaymentApiError::VersionedConversionFailed
2686 })?;
2687
2688 let (_, fees) = validate_send::<T::XcmRouter>(destination.clone(), message.clone()).map_err(|error| {
2689 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination");
2690 XcmPaymentApiError::Unroutable
2691 })?;
2692
2693 VersionedAssets::from(fees)
2694 .into_version(result_version)
2695 .map_err(|e| {
2696 tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?result_version, "Failed to convert fees into version");
2697 XcmPaymentApiError::VersionedConversionFailed
2698 })
2699 }
2700
2701 fn do_new_query(
2703 responder: impl Into<Location>,
2704 maybe_notify: Option<(u8, u8)>,
2705 timeout: BlockNumberFor<T>,
2706 match_querier: impl Into<Location>,
2707 ) -> u64 {
2708 QueryCounter::<T>::mutate(|q| {
2709 let r = *q;
2710 q.saturating_inc();
2711 Queries::<T>::insert(
2712 r,
2713 QueryStatus::Pending {
2714 responder: responder.into().into(),
2715 maybe_match_querier: Some(match_querier.into().into()),
2716 maybe_notify,
2717 timeout,
2718 },
2719 );
2720 r
2721 })
2722 }
2723
2724 pub fn report_outcome_notify(
2747 message: &mut Xcm<()>,
2748 responder: impl Into<Location>,
2749 notify: impl Into<<T as Config>::RuntimeCall>,
2750 timeout: BlockNumberFor<T>,
2751 ) -> Result<(), XcmError> {
2752 let responder = responder.into();
2753 let destination = T::UniversalLocation::get()
2754 .invert_target(&responder)
2755 .map_err(|()| XcmError::LocationNotInvertible)?;
2756 let notify: <T as Config>::RuntimeCall = notify.into();
2757 let max_weight = notify.get_dispatch_info().call_weight;
2758 let query_id = Self::new_notify_query(responder, notify, timeout, Here);
2759 let response_info = QueryResponseInfo { destination, query_id, max_weight };
2760 let report_error = Xcm(vec![ReportError(response_info)]);
2761 message.0.insert(0, SetAppendix(report_error));
2762 Ok(())
2763 }
2764
2765 pub fn new_notify_query(
2768 responder: impl Into<Location>,
2769 notify: impl Into<<T as Config>::RuntimeCall>,
2770 timeout: BlockNumberFor<T>,
2771 match_querier: impl Into<Location>,
2772 ) -> u64 {
2773 let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
2774 "decode input is output of Call encode; Call guaranteed to have two enums; qed",
2775 );
2776 Self::do_new_query(responder, Some(notify), timeout, match_querier)
2777 }
2778
2779 fn note_unknown_version(dest: &Location) {
2782 tracing::trace!(
2783 target: "xcm::pallet_xcm::note_unknown_version",
2784 ?dest, "XCM version is unknown for destination"
2785 );
2786 let versioned_dest = VersionedLocation::from(dest.clone());
2787 VersionDiscoveryQueue::<T>::mutate(|q| {
2788 if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
2789 q[index].1.saturating_inc();
2791 } else {
2792 let _ = q.try_push((versioned_dest, 1));
2793 }
2794 });
2795 }
2796
2797 fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
2803 T::XcmExecutor::charge_fees(location.clone(), assets.clone())
2804 .map_err(|_| Error::<T>::FeesNotMet)?;
2805 Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
2806 Ok(())
2807 }
2808
2809 #[cfg(any(feature = "try-runtime", test))]
2819 pub fn do_try_state() -> Result<(), TryRuntimeError> {
2820 use migration::data::NeedsMigration;
2821
2822 let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
2826 {
2827 XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
2828 } else {
2829 XCM_VERSION.saturating_sub(1)
2830 };
2831
2832 ensure!(
2834 !Queries::<T>::iter_values()
2835 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
2836 TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
2837 );
2838
2839 ensure!(
2841 !LockedFungibles::<T>::iter_values()
2842 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
2843 TryRuntimeError::Other(
2844 "`LockedFungibles` data should be migrated to the higher xcm version!"
2845 )
2846 );
2847
2848 ensure!(
2850 !RemoteLockedFungibles::<T>::iter()
2851 .any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version) ||
2852 data.needs_migration(minimal_allowed_xcm_version)),
2853 TryRuntimeError::Other(
2854 "`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
2855 )
2856 );
2857
2858 if CurrentMigration::<T>::exists() {
2861 return Ok(())
2862 }
2863
2864 for v in 0..XCM_VERSION {
2866 ensure!(
2867 SupportedVersion::<T>::iter_prefix(v).next().is_none(),
2868 TryRuntimeError::Other(
2869 "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
2870 )
2871 );
2872 ensure!(
2873 VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
2874 TryRuntimeError::Other(
2875 "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
2876 )
2877 );
2878 ensure!(
2879 VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
2880 TryRuntimeError::Other(
2881 "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
2882 )
2883 );
2884 }
2885
2886 Ok(())
2887 }
2888}
2889
2890pub struct LockTicket<T: Config> {
2891 sovereign_account: T::AccountId,
2892 amount: BalanceOf<T>,
2893 unlocker: Location,
2894 item_index: Option<usize>,
2895}
2896
2897impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
2898 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
2899 use xcm_executor::traits::LockError::UnexpectedState;
2900 let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
2901 match self.item_index {
2902 Some(index) => {
2903 ensure!(locks.len() > index, UnexpectedState);
2904 ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
2905 locks[index].0 = locks[index].0.max(self.amount);
2906 },
2907 None => {
2908 locks
2909 .try_push((self.amount, self.unlocker.into()))
2910 .map_err(|(_balance, _location)| UnexpectedState)?;
2911 },
2912 }
2913 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
2914 T::Currency::extend_lock(
2915 *b"py/xcmlk",
2916 &self.sovereign_account,
2917 self.amount,
2918 WithdrawReasons::all(),
2919 );
2920 Ok(())
2921 }
2922}
2923
2924pub struct UnlockTicket<T: Config> {
2925 sovereign_account: T::AccountId,
2926 amount: BalanceOf<T>,
2927 unlocker: Location,
2928}
2929
2930impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
2931 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
2932 use xcm_executor::traits::LockError::UnexpectedState;
2933 let mut locks =
2934 LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
2935 let mut maybe_remove_index = None;
2936 let mut locked = BalanceOf::<T>::zero();
2937 let mut found = false;
2938 for (i, x) in locks.iter_mut().enumerate() {
2941 if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
2942 x.0 = x.0.saturating_sub(self.amount);
2943 if x.0.is_zero() {
2944 maybe_remove_index = Some(i);
2945 }
2946 found = true;
2947 }
2948 locked = locked.max(x.0);
2949 }
2950 ensure!(found, UnexpectedState);
2951 if let Some(remove_index) = maybe_remove_index {
2952 locks.swap_remove(remove_index);
2953 }
2954 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
2955 let reasons = WithdrawReasons::all();
2956 T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
2957 Ok(())
2958 }
2959}
2960
2961pub struct ReduceTicket<T: Config> {
2962 key: (u32, T::AccountId, VersionedAssetId),
2963 amount: u128,
2964 locker: VersionedLocation,
2965 owner: VersionedLocation,
2966}
2967
2968impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
2969 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
2970 use xcm_executor::traits::LockError::UnexpectedState;
2971 let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
2972 ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
2973 let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
2974 ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
2975 if new_amount == 0 {
2976 RemoteLockedFungibles::<T>::remove(&self.key);
2977 } else {
2978 record.amount = new_amount;
2979 RemoteLockedFungibles::<T>::insert(&self.key, &record);
2980 }
2981 Ok(())
2982 }
2983}
2984
2985impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
2986 type LockTicket = LockTicket<T>;
2987 type UnlockTicket = UnlockTicket<T>;
2988 type ReduceTicket = ReduceTicket<T>;
2989
2990 fn prepare_lock(
2991 unlocker: Location,
2992 asset: Asset,
2993 owner: Location,
2994 ) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
2995 use xcm_executor::traits::LockError::*;
2996 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
2997 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
2998 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
2999 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3000 let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
3001 ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
3002 Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
3003 }
3004
3005 fn prepare_unlock(
3006 unlocker: Location,
3007 asset: Asset,
3008 owner: Location,
3009 ) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
3010 use xcm_executor::traits::LockError::*;
3011 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3012 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3013 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3014 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3015 let item_index =
3016 locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
3017 ensure!(locks[item_index].0 >= amount, NotLocked);
3018 Ok(UnlockTicket { sovereign_account, amount, unlocker })
3019 }
3020
3021 fn note_unlockable(
3022 locker: Location,
3023 asset: Asset,
3024 mut owner: Location,
3025 ) -> Result<(), xcm_executor::traits::LockError> {
3026 use xcm_executor::traits::LockError::*;
3027 ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
3028 let amount = match asset.fun {
3029 Fungible(a) => a,
3030 NonFungible(_) => return Err(Unimplemented),
3031 };
3032 owner.remove_network_id();
3033 let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3034 let locker = locker.into();
3035 let owner = owner.into();
3036 let id: VersionedAssetId = asset.id.into();
3037 let key = (XCM_VERSION, account, id);
3038 let mut record =
3039 RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
3040 if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
3041 ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
3043 record.consumers = old.consumers;
3044 record.amount = record.amount.max(old.amount);
3045 }
3046 RemoteLockedFungibles::<T>::insert(&key, record);
3047 Ok(())
3048 }
3049
3050 fn prepare_reduce_unlockable(
3051 locker: Location,
3052 asset: Asset,
3053 mut owner: Location,
3054 ) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
3055 use xcm_executor::traits::LockError::*;
3056 let amount = match asset.fun {
3057 Fungible(a) => a,
3058 NonFungible(_) => return Err(Unimplemented),
3059 };
3060 owner.remove_network_id();
3061 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3062 let locker = locker.into();
3063 let owner = owner.into();
3064 let id: VersionedAssetId = asset.id.into();
3065 let key = (XCM_VERSION, sovereign_account, id);
3066
3067 let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
3068 ensure!(locker == record.locker && owner == record.owner, WouldClobber);
3070 ensure!(record.amount >= amount, NotEnoughLocked);
3071 ensure!(
3072 record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
3073 InUse
3074 );
3075 Ok(ReduceTicket { key, amount, locker, owner })
3076 }
3077}
3078
3079impl<T: Config> WrapVersion for Pallet<T> {
3080 fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
3081 dest: &Location,
3082 xcm: impl Into<VersionedXcm<RuntimeCall>>,
3083 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
3084 Self::get_version_for(dest)
3085 .or_else(|| {
3086 Self::note_unknown_version(dest);
3087 SafeXcmVersion::<T>::get()
3088 })
3089 .ok_or_else(|| {
3090 tracing::trace!(
3091 target: "xcm::pallet_xcm::wrap_version",
3092 ?dest, "Could not determine a version to wrap XCM for destination",
3093 );
3094 ()
3095 })
3096 .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
3097 }
3098}
3099
3100impl<T: Config> GetVersion for Pallet<T> {
3101 fn get_version_for(dest: &Location) -> Option<XcmVersion> {
3102 SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
3103 }
3104}
3105
3106impl<T: Config> VersionChangeNotifier for Pallet<T> {
3107 fn start(
3116 dest: &Location,
3117 query_id: QueryId,
3118 max_weight: Weight,
3119 _context: &XcmContext,
3120 ) -> XcmResult {
3121 let versioned_dest = LatestVersionedLocation(dest);
3122 let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
3123 ensure!(!already, XcmError::InvalidLocation);
3124
3125 let xcm_version = T::AdvertisedXcmVersion::get();
3126 let response = Response::Version(xcm_version);
3127 let instruction = QueryResponse { query_id, response, max_weight, querier: None };
3128 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3129 Self::deposit_event(Event::<T>::VersionNotifyStarted {
3130 destination: dest.clone(),
3131 cost,
3132 message_id,
3133 });
3134
3135 let value = (query_id, max_weight, xcm_version);
3136 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
3137 Ok(())
3138 }
3139
3140 fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
3143 VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
3144 Ok(())
3145 }
3146
3147 fn is_subscribed(dest: &Location) -> bool {
3149 let versioned_dest = LatestVersionedLocation(dest);
3150 VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
3151 }
3152}
3153
3154impl<T: Config> DropAssets for Pallet<T> {
3155 fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
3156 if assets.is_empty() {
3157 return Weight::zero()
3158 }
3159 let versioned = VersionedAssets::from(Assets::from(assets));
3160 let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
3161 AssetTraps::<T>::mutate(hash, |n| *n += 1);
3162 Self::deposit_event(Event::AssetsTrapped {
3163 hash,
3164 origin: origin.clone(),
3165 assets: versioned,
3166 });
3167 Weight::zero()
3169 }
3170}
3171
3172impl<T: Config> ClaimAssets for Pallet<T> {
3173 fn claim_assets(
3174 origin: &Location,
3175 ticket: &Location,
3176 assets: &Assets,
3177 _context: &XcmContext,
3178 ) -> bool {
3179 let mut versioned = VersionedAssets::from(assets.clone());
3180 match ticket.unpack() {
3181 (0, [GeneralIndex(i)]) =>
3182 versioned = match versioned.into_version(*i as u32) {
3183 Ok(v) => v,
3184 Err(()) => return false,
3185 },
3186 (0, []) => (),
3187 _ => return false,
3188 };
3189 let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
3190 match AssetTraps::<T>::get(hash) {
3191 0 => return false,
3192 1 => AssetTraps::<T>::remove(hash),
3193 n => AssetTraps::<T>::insert(hash, n - 1),
3194 }
3195 Self::deposit_event(Event::AssetsClaimed {
3196 hash,
3197 origin: origin.clone(),
3198 assets: versioned,
3199 });
3200 return true
3201 }
3202}
3203
3204impl<T: Config> OnResponse for Pallet<T> {
3205 fn expecting_response(
3206 origin: &Location,
3207 query_id: QueryId,
3208 querier: Option<&Location>,
3209 ) -> bool {
3210 match Queries::<T>::get(query_id) {
3211 Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
3212 Location::try_from(responder).map_or(false, |r| origin == &r) &&
3213 maybe_match_querier.map_or(true, |match_querier| {
3214 Location::try_from(match_querier).map_or(false, |match_querier| {
3215 querier.map_or(false, |q| q == &match_querier)
3216 })
3217 }),
3218 Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
3219 Location::try_from(r).map_or(false, |r| origin == &r),
3220 _ => false,
3221 }
3222 }
3223
3224 fn on_response(
3225 origin: &Location,
3226 query_id: QueryId,
3227 querier: Option<&Location>,
3228 response: Response,
3229 max_weight: Weight,
3230 _context: &XcmContext,
3231 ) -> Weight {
3232 let origin = origin.clone();
3233 match (response, Queries::<T>::get(query_id)) {
3234 (
3235 Response::Version(v),
3236 Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
3237 ) => {
3238 let origin: Location = match expected_origin.try_into() {
3239 Ok(o) if o == origin => o,
3240 Ok(o) => {
3241 Self::deposit_event(Event::InvalidResponder {
3242 origin: origin.clone(),
3243 query_id,
3244 expected_location: Some(o),
3245 });
3246 return Weight::zero()
3247 },
3248 _ => {
3249 Self::deposit_event(Event::InvalidResponder {
3250 origin: origin.clone(),
3251 query_id,
3252 expected_location: None,
3253 });
3254 return Weight::zero()
3256 },
3257 };
3258 if !is_active {
3260 Queries::<T>::insert(
3261 query_id,
3262 QueryStatus::VersionNotifier {
3263 origin: origin.clone().into(),
3264 is_active: true,
3265 },
3266 );
3267 }
3268 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
3270 Self::deposit_event(Event::SupportedVersionChanged {
3271 location: origin,
3272 version: v,
3273 });
3274 Weight::zero()
3275 },
3276 (
3277 response,
3278 Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
3279 ) => {
3280 if let Some(match_querier) = maybe_match_querier {
3281 let match_querier = match Location::try_from(match_querier) {
3282 Ok(mq) => mq,
3283 Err(_) => {
3284 Self::deposit_event(Event::InvalidQuerierVersion {
3285 origin: origin.clone(),
3286 query_id,
3287 });
3288 return Weight::zero()
3289 },
3290 };
3291 if querier.map_or(true, |q| q != &match_querier) {
3292 Self::deposit_event(Event::InvalidQuerier {
3293 origin: origin.clone(),
3294 query_id,
3295 expected_querier: match_querier,
3296 maybe_actual_querier: querier.cloned(),
3297 });
3298 return Weight::zero()
3299 }
3300 }
3301 let responder = match Location::try_from(responder) {
3302 Ok(r) => r,
3303 Err(_) => {
3304 Self::deposit_event(Event::InvalidResponderVersion {
3305 origin: origin.clone(),
3306 query_id,
3307 });
3308 return Weight::zero()
3309 },
3310 };
3311 if origin != responder {
3312 Self::deposit_event(Event::InvalidResponder {
3313 origin: origin.clone(),
3314 query_id,
3315 expected_location: Some(responder),
3316 });
3317 return Weight::zero()
3318 }
3319 match maybe_notify {
3320 Some((pallet_index, call_index)) => {
3321 let bare = (pallet_index, call_index, query_id, response);
3325 if let Ok(call) = bare.using_encoded(|mut bytes| {
3326 <T as Config>::RuntimeCall::decode(&mut bytes)
3327 }) {
3328 Queries::<T>::remove(query_id);
3329 let weight = call.get_dispatch_info().call_weight;
3330 if weight.any_gt(max_weight) {
3331 let e = Event::NotifyOverweight {
3332 query_id,
3333 pallet_index,
3334 call_index,
3335 actual_weight: weight,
3336 max_budgeted_weight: max_weight,
3337 };
3338 Self::deposit_event(e);
3339 return Weight::zero()
3340 }
3341 let dispatch_origin = Origin::Response(origin.clone()).into();
3342 match call.dispatch(dispatch_origin) {
3343 Ok(post_info) => {
3344 let e = Event::Notified { query_id, pallet_index, call_index };
3345 Self::deposit_event(e);
3346 post_info.actual_weight
3347 },
3348 Err(error_and_info) => {
3349 let e = Event::NotifyDispatchError {
3350 query_id,
3351 pallet_index,
3352 call_index,
3353 };
3354 Self::deposit_event(e);
3355 error_and_info.post_info.actual_weight
3358 },
3359 }
3360 .unwrap_or(weight)
3361 } else {
3362 let e =
3363 Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
3364 Self::deposit_event(e);
3365 Weight::zero()
3366 }
3367 },
3368 None => {
3369 let e = Event::ResponseReady { query_id, response: response.clone() };
3370 Self::deposit_event(e);
3371 let at = frame_system::Pallet::<T>::current_block_number();
3372 let response = response.into();
3373 Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
3374 Weight::zero()
3375 },
3376 }
3377 },
3378 _ => {
3379 let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
3380 Self::deposit_event(e);
3381 Weight::zero()
3382 },
3383 }
3384 }
3385}
3386
3387impl<T: Config> CheckSuspension for Pallet<T> {
3388 fn is_suspended<Call>(
3389 _origin: &Location,
3390 _instructions: &mut [Instruction<Call>],
3391 _max_weight: Weight,
3392 _properties: &mut Properties,
3393 ) -> bool {
3394 XcmExecutionSuspended::<T>::get()
3395 }
3396}
3397
3398impl<T: Config> RecordXcm for Pallet<T> {
3399 fn should_record() -> bool {
3400 ShouldRecordXcm::<T>::get()
3401 }
3402
3403 fn set_record_xcm(enabled: bool) {
3404 ShouldRecordXcm::<T>::put(enabled);
3405 }
3406
3407 fn recorded_xcm() -> Option<Xcm<()>> {
3408 RecordedXcm::<T>::get()
3409 }
3410
3411 fn record(xcm: Xcm<()>) {
3412 RecordedXcm::<T>::put(xcm);
3413 }
3414}
3415
3416pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
3420where
3421 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
3422{
3423 match o.into() {
3424 Ok(Origin::Xcm(location)) => Ok(location),
3425 _ => Err(BadOrigin),
3426 }
3427}
3428
3429pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
3433where
3434 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
3435{
3436 match o.into() {
3437 Ok(Origin::Response(location)) => Ok(location),
3438 _ => Err(BadOrigin),
3439 }
3440}
3441
3442pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
3447impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
3448 for IsMajorityOfBody<Prefix, Body>
3449{
3450 fn contains(l: &Location) -> bool {
3451 let maybe_suffix = l.match_and_split(&Prefix::get());
3452 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
3453 }
3454}
3455
3456pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
3460impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
3461 fn contains(l: &Location) -> bool {
3462 let maybe_suffix = l.match_and_split(&Prefix::get());
3463 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
3464 }
3465}
3466
3467pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
3470impl<
3471 O: OriginTrait + From<Origin>,
3472 F: Contains<L>,
3473 L: TryFrom<Location> + TryInto<Location> + Clone,
3474 > EnsureOrigin<O> for EnsureXcm<F, L>
3475where
3476 O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
3477{
3478 type Success = L;
3479
3480 fn try_origin(outer: O) -> Result<Self::Success, O> {
3481 outer.try_with_caller(|caller| {
3482 caller.try_into().and_then(|o| match o {
3483 Origin::Xcm(ref location)
3484 if F::contains(&location.clone().try_into().map_err(|_| o.clone().into())?) =>
3485 Ok(location.clone().try_into().map_err(|_| o.clone().into())?),
3486 Origin::Xcm(location) => Err(Origin::Xcm(location).into()),
3487 o => Err(o.into()),
3488 })
3489 })
3490 }
3491
3492 #[cfg(feature = "runtime-benchmarks")]
3493 fn try_successful_origin() -> Result<O, ()> {
3494 Ok(O::from(Origin::Xcm(Here.into())))
3495 }
3496}
3497
3498pub struct EnsureResponse<F>(PhantomData<F>);
3501impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
3502where
3503 O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
3504{
3505 type Success = Location;
3506
3507 fn try_origin(outer: O) -> Result<Self::Success, O> {
3508 outer.try_with_caller(|caller| {
3509 caller.try_into().and_then(|o| match o {
3510 Origin::Response(responder) => Ok(responder),
3511 o => Err(o.into()),
3512 })
3513 })
3514 }
3515
3516 #[cfg(feature = "runtime-benchmarks")]
3517 fn try_successful_origin() -> Result<O, ()> {
3518 Ok(O::from(Origin::Response(Here.into())))
3519 }
3520}
3521
3522pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
3525impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
3526 for XcmPassthrough<RuntimeOrigin>
3527{
3528 fn convert_origin(
3529 origin: impl Into<Location>,
3530 kind: OriginKind,
3531 ) -> Result<RuntimeOrigin, Location> {
3532 let origin = origin.into();
3533 match kind {
3534 OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
3535 _ => Err(origin),
3536 }
3537 }
3538}