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};
74
75#[cfg(any(feature = "try-runtime", test))]
76use sp_runtime::TryRuntimeError;
77
78pub trait WeightInfo {
79 fn send() -> Weight;
80 fn teleport_assets() -> Weight;
81 fn reserve_transfer_assets() -> Weight;
82 fn transfer_assets() -> Weight;
83 fn execute() -> Weight;
84 fn force_xcm_version() -> Weight;
85 fn force_default_xcm_version() -> Weight;
86 fn force_subscribe_version_notify() -> Weight;
87 fn force_unsubscribe_version_notify() -> Weight;
88 fn force_suspension() -> Weight;
89 fn migrate_supported_version() -> Weight;
90 fn migrate_version_notifiers() -> Weight;
91 fn already_notified_target() -> Weight;
92 fn notify_current_targets() -> Weight;
93 fn notify_target_migration_fail() -> Weight;
94 fn migrate_version_notify_targets() -> Weight;
95 fn migrate_and_notify_old_targets() -> Weight;
96 fn new_query() -> Weight;
97 fn take_response() -> Weight;
98 fn claim_assets() -> Weight;
99}
100
101pub struct TestWeightInfo;
103impl WeightInfo for TestWeightInfo {
104 fn send() -> Weight {
105 Weight::from_parts(100_000_000, 0)
106 }
107
108 fn teleport_assets() -> Weight {
109 Weight::from_parts(100_000_000, 0)
110 }
111
112 fn reserve_transfer_assets() -> Weight {
113 Weight::from_parts(100_000_000, 0)
114 }
115
116 fn transfer_assets() -> Weight {
117 Weight::from_parts(100_000_000, 0)
118 }
119
120 fn execute() -> Weight {
121 Weight::from_parts(100_000_000, 0)
122 }
123
124 fn force_xcm_version() -> Weight {
125 Weight::from_parts(100_000_000, 0)
126 }
127
128 fn force_default_xcm_version() -> Weight {
129 Weight::from_parts(100_000_000, 0)
130 }
131
132 fn force_subscribe_version_notify() -> Weight {
133 Weight::from_parts(100_000_000, 0)
134 }
135
136 fn force_unsubscribe_version_notify() -> Weight {
137 Weight::from_parts(100_000_000, 0)
138 }
139
140 fn force_suspension() -> Weight {
141 Weight::from_parts(100_000_000, 0)
142 }
143
144 fn migrate_supported_version() -> Weight {
145 Weight::from_parts(100_000_000, 0)
146 }
147
148 fn migrate_version_notifiers() -> Weight {
149 Weight::from_parts(100_000_000, 0)
150 }
151
152 fn already_notified_target() -> Weight {
153 Weight::from_parts(100_000_000, 0)
154 }
155
156 fn notify_current_targets() -> Weight {
157 Weight::from_parts(100_000_000, 0)
158 }
159
160 fn notify_target_migration_fail() -> Weight {
161 Weight::from_parts(100_000_000, 0)
162 }
163
164 fn migrate_version_notify_targets() -> Weight {
165 Weight::from_parts(100_000_000, 0)
166 }
167
168 fn migrate_and_notify_old_targets() -> Weight {
169 Weight::from_parts(100_000_000, 0)
170 }
171
172 fn new_query() -> Weight {
173 Weight::from_parts(100_000_000, 0)
174 }
175
176 fn take_response() -> Weight {
177 Weight::from_parts(100_000_000, 0)
178 }
179
180 fn claim_assets() -> Weight {
181 Weight::from_parts(100_000_000, 0)
182 }
183}
184
185#[frame_support::pallet]
186pub mod pallet {
187 use super::*;
188 use frame_support::{
189 dispatch::{GetDispatchInfo, PostDispatchInfo},
190 parameter_types,
191 };
192 use frame_system::Config as SysConfig;
193 use sp_core::H256;
194 use sp_runtime::traits::Dispatchable;
195 use xcm_executor::traits::{MatchesFungible, WeightBounds};
196
197 parameter_types! {
198 pub const CurrentXcmVersion: u32 = XCM_VERSION;
201 }
202
203 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
204
205 #[pallet::pallet]
206 #[pallet::storage_version(STORAGE_VERSION)]
207 #[pallet::without_storage_info]
208 pub struct Pallet<T>(_);
209
210 pub type BalanceOf<T> =
211 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
212
213 #[pallet::config]
214 pub trait Config: frame_system::Config {
216 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
218
219 type Currency: LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>;
222
223 type CurrencyMatcher: MatchesFungible<BalanceOf<Self>>;
225
226 type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
229
230 type XcmRouter: SendXcm;
232
233 type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
237
238 type XcmExecuteFilter: Contains<(Location, Xcm<<Self as Config>::RuntimeCall>)>;
240
241 type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall> + XcmAssetTransfers;
243
244 type XcmTeleportFilter: Contains<(Location, Vec<Asset>)>;
246
247 type XcmReserveTransferFilter: Contains<(Location, Vec<Asset>)>;
250
251 type Weigher: WeightBounds<<Self as Config>::RuntimeCall>;
253
254 type UniversalLocation: Get<InteriorLocation>;
256
257 type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
259
260 type RuntimeCall: Parameter
262 + GetDispatchInfo
263 + Dispatchable<
264 RuntimeOrigin = <Self as Config>::RuntimeOrigin,
265 PostInfo = PostDispatchInfo,
266 >;
267
268 const VERSION_DISCOVERY_QUEUE_SIZE: u32;
269
270 type AdvertisedXcmVersion: Get<XcmVersion>;
273
274 type AdminOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin>;
276
277 type TrustedLockers: ContainsPair<Location, Asset>;
280
281 type SovereignAccountOf: ConvertLocation<Self::AccountId>;
283
284 type MaxLockers: Get<u32>;
286
287 type MaxRemoteLockConsumers: Get<u32>;
289
290 type RemoteLockConsumerIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
292
293 type WeightInfo: WeightInfo;
295 }
296
297 impl<T: Config> ExecuteControllerWeightInfo for Pallet<T> {
298 fn execute() -> Weight {
299 T::WeightInfo::execute()
300 }
301 }
302
303 impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pallet<T> {
304 type WeightInfo = Self;
305 fn execute(
306 origin: OriginFor<T>,
307 message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
308 max_weight: Weight,
309 ) -> Result<Weight, DispatchErrorWithPostInfo> {
310 log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight);
311 let outcome = (|| {
312 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
313 let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
314 let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
315 let value = (origin_location, message);
316 ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
317 let (origin_location, message) = value;
318 Ok(T::XcmExecutor::prepare_and_execute(
319 origin_location,
320 message,
321 &mut hash,
322 max_weight,
323 max_weight,
324 ))
325 })()
326 .map_err(|e: DispatchError| {
327 e.with_weight(<Self::WeightInfo as ExecuteControllerWeightInfo>::execute())
328 })?;
329
330 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
331 let weight_used = outcome.weight_used();
332 outcome.ensure_complete().map_err(|error| {
333 log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error);
334 Error::<T>::LocalExecutionIncomplete.with_weight(
335 weight_used.saturating_add(
336 <Self::WeightInfo as ExecuteControllerWeightInfo>::execute(),
337 ),
338 )
339 })?;
340 Ok(weight_used)
341 }
342 }
343
344 impl<T: Config> SendControllerWeightInfo for Pallet<T> {
345 fn send() -> Weight {
346 T::WeightInfo::send()
347 }
348 }
349
350 impl<T: Config> SendController<OriginFor<T>> for Pallet<T> {
351 type WeightInfo = Self;
352 fn send(
353 origin: OriginFor<T>,
354 dest: Box<VersionedLocation>,
355 message: Box<VersionedXcm<()>>,
356 ) -> Result<XcmHash, DispatchError> {
357 let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
358 let interior: Junctions =
359 origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
360 let dest = Location::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
361 let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
362
363 let message_id = Self::send_xcm(interior, dest.clone(), message.clone())
364 .map_err(|error| {
365 tracing::error!(target: "xcm::pallet_xcm::send", ?error, ?dest, ?message, "XCM send failed with error");
366 Error::<T>::from(error)
367 })?;
368 let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
369 Self::deposit_event(e);
370 Ok(message_id)
371 }
372 }
373
374 impl<T: Config> QueryControllerWeightInfo for Pallet<T> {
375 fn query() -> Weight {
376 T::WeightInfo::new_query()
377 }
378 fn take_response() -> Weight {
379 T::WeightInfo::take_response()
380 }
381 }
382
383 impl<T: Config> QueryController<OriginFor<T>, BlockNumberFor<T>> for Pallet<T> {
384 type WeightInfo = Self;
385
386 fn query(
387 origin: OriginFor<T>,
388 timeout: BlockNumberFor<T>,
389 match_querier: VersionedLocation,
390 ) -> Result<QueryId, DispatchError> {
391 let responder = <T as Config>::ExecuteXcmOrigin::ensure_origin(origin)?;
392 let query_id = <Self as QueryHandler>::new_query(
393 responder,
394 timeout,
395 Location::try_from(match_querier)
396 .map_err(|_| Into::<DispatchError>::into(Error::<T>::BadVersion))?,
397 );
398
399 Ok(query_id)
400 }
401 }
402
403 #[pallet::event]
404 #[pallet::generate_deposit(pub(super) fn deposit_event)]
405 pub enum Event<T: Config> {
406 Attempted { outcome: xcm::latest::Outcome },
408 Sent { origin: Location, destination: Location, message: Xcm<()>, message_id: XcmHash },
410 UnexpectedResponse { origin: Location, query_id: QueryId },
414 ResponseReady { query_id: QueryId, response: Response },
417 Notified { query_id: QueryId, pallet_index: u8, call_index: u8 },
420 NotifyOverweight {
424 query_id: QueryId,
425 pallet_index: u8,
426 call_index: u8,
427 actual_weight: Weight,
428 max_budgeted_weight: Weight,
429 },
430 NotifyDispatchError { query_id: QueryId, pallet_index: u8, call_index: u8 },
433 NotifyDecodeFailed { query_id: QueryId, pallet_index: u8, call_index: u8 },
437 InvalidResponder {
441 origin: Location,
442 query_id: QueryId,
443 expected_location: Option<Location>,
444 },
445 InvalidResponderVersion { origin: Location, query_id: QueryId },
453 ResponseTaken { query_id: QueryId },
455 AssetsTrapped { hash: H256, origin: Location, assets: VersionedAssets },
457 VersionChangeNotified {
461 destination: Location,
462 result: XcmVersion,
463 cost: Assets,
464 message_id: XcmHash,
465 },
466 SupportedVersionChanged { location: Location, version: XcmVersion },
469 NotifyTargetSendFail { location: Location, query_id: QueryId, error: XcmError },
472 NotifyTargetMigrationFail { location: VersionedLocation, query_id: QueryId },
475 InvalidQuerierVersion { origin: Location, query_id: QueryId },
483 InvalidQuerier {
487 origin: Location,
488 query_id: QueryId,
489 expected_querier: Location,
490 maybe_actual_querier: Option<Location>,
491 },
492 VersionNotifyStarted { destination: Location, cost: Assets, message_id: XcmHash },
495 VersionNotifyRequested { destination: Location, cost: Assets, message_id: XcmHash },
497 VersionNotifyUnrequested { destination: Location, cost: Assets, message_id: XcmHash },
500 FeesPaid { paying: Location, fees: Assets },
502 AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets },
504 VersionMigrationFinished { version: XcmVersion },
506 }
507
508 #[pallet::origin]
509 #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
510 pub enum Origin {
511 Xcm(Location),
513 Response(Location),
515 }
516 impl From<Location> for Origin {
517 fn from(location: Location) -> Origin {
518 Origin::Xcm(location)
519 }
520 }
521
522 #[pallet::error]
523 pub enum Error<T> {
524 Unreachable,
527 SendFailure,
530 Filtered,
532 UnweighableMessage,
534 DestinationNotInvertible,
536 Empty,
538 CannotReanchor,
540 TooManyAssets,
542 InvalidOrigin,
544 BadVersion,
546 BadLocation,
549 NoSubscription,
551 AlreadySubscribed,
553 CannotCheckOutTeleport,
555 LowBalance,
557 TooManyLocks,
559 AccountNotSovereign,
561 FeesNotMet,
563 LockNotFound,
565 InUse,
567 #[codec(index = 21)]
569 InvalidAssetUnknownReserve,
570 #[codec(index = 22)]
572 InvalidAssetUnsupportedReserve,
573 #[codec(index = 23)]
575 TooManyReserves,
576 #[codec(index = 24)]
578 LocalExecutionIncomplete,
579 }
580
581 impl<T: Config> From<SendError> for Error<T> {
582 fn from(e: SendError) -> Self {
583 match e {
584 SendError::Fees => Error::<T>::FeesNotMet,
585 SendError::NotApplicable => Error::<T>::Unreachable,
586 _ => Error::<T>::SendFailure,
587 }
588 }
589 }
590
591 impl<T: Config> From<AssetTransferError> for Error<T> {
592 fn from(e: AssetTransferError) -> Self {
593 match e {
594 AssetTransferError::UnknownReserve => Error::<T>::InvalidAssetUnknownReserve,
595 }
596 }
597 }
598
599 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
601 pub enum QueryStatus<BlockNumber> {
602 Pending {
604 responder: VersionedLocation,
607 maybe_match_querier: Option<VersionedLocation>,
610 maybe_notify: Option<(u8, u8)>,
611 timeout: BlockNumber,
612 },
613 VersionNotifier { origin: VersionedLocation, is_active: bool },
615 Ready { response: VersionedResponse, at: BlockNumber },
617 }
618
619 #[derive(Copy, Clone)]
620 pub(crate) struct LatestVersionedLocation<'a>(pub(crate) &'a Location);
621 impl<'a> EncodeLike<VersionedLocation> for LatestVersionedLocation<'a> {}
622 impl<'a> Encode for LatestVersionedLocation<'a> {
623 fn encode(&self) -> Vec<u8> {
624 let mut r = VersionedLocation::from(Location::default()).encode();
625 r.truncate(1);
626 self.0.using_encoded(|d| r.extend_from_slice(d));
627 r
628 }
629 }
630
631 #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
632 pub enum VersionMigrationStage {
633 MigrateSupportedVersion,
634 MigrateVersionNotifiers,
635 NotifyCurrentTargets(Option<Vec<u8>>),
636 MigrateAndNotifyOldTargets,
637 }
638
639 impl Default for VersionMigrationStage {
640 fn default() -> Self {
641 Self::MigrateSupportedVersion
642 }
643 }
644
645 #[pallet::storage]
647 pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;
648
649 #[pallet::storage]
651 #[pallet::getter(fn query)]
652 pub(super) type Queries<T: Config> =
653 StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<BlockNumberFor<T>>, OptionQuery>;
654
655 #[pallet::storage]
660 #[pallet::getter(fn asset_trap)]
661 pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;
662
663 #[pallet::storage]
666 #[pallet::whitelist_storage]
667 pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
668
669 #[pallet::storage]
671 pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
672 _,
673 Twox64Concat,
674 XcmVersion,
675 Blake2_128Concat,
676 VersionedLocation,
677 XcmVersion,
678 OptionQuery,
679 >;
680
681 #[pallet::storage]
683 pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
684 _,
685 Twox64Concat,
686 XcmVersion,
687 Blake2_128Concat,
688 VersionedLocation,
689 QueryId,
690 OptionQuery,
691 >;
692
693 #[pallet::storage]
696 pub(super) type VersionNotifyTargets<T: Config> = StorageDoubleMap<
697 _,
698 Twox64Concat,
699 XcmVersion,
700 Blake2_128Concat,
701 VersionedLocation,
702 (QueryId, Weight, XcmVersion),
703 OptionQuery,
704 >;
705
706 pub struct VersionDiscoveryQueueSize<T>(PhantomData<T>);
707 impl<T: Config> Get<u32> for VersionDiscoveryQueueSize<T> {
708 fn get() -> u32 {
709 T::VERSION_DISCOVERY_QUEUE_SIZE
710 }
711 }
712
713 #[pallet::storage]
717 #[pallet::whitelist_storage]
718 pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
719 _,
720 BoundedVec<(VersionedLocation, u32), VersionDiscoveryQueueSize<T>>,
721 ValueQuery,
722 >;
723
724 #[pallet::storage]
726 pub(super) type CurrentMigration<T: Config> =
727 StorageValue<_, VersionMigrationStage, OptionQuery>;
728
729 #[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
730 #[scale_info(skip_type_params(MaxConsumers))]
731 pub struct RemoteLockedFungibleRecord<ConsumerIdentifier, MaxConsumers: Get<u32>> {
732 pub amount: u128,
734 pub owner: VersionedLocation,
736 pub locker: VersionedLocation,
738 pub consumers: BoundedVec<(ConsumerIdentifier, u128), MaxConsumers>,
742 }
743
744 impl<LockId, MaxConsumers: Get<u32>> RemoteLockedFungibleRecord<LockId, MaxConsumers> {
745 pub fn amount_held(&self) -> Option<u128> {
748 self.consumers.iter().max_by(|x, y| x.1.cmp(&y.1)).map(|max| max.1)
749 }
750 }
751
752 #[pallet::storage]
754 pub(super) type RemoteLockedFungibles<T: Config> = StorageNMap<
755 _,
756 (
757 NMapKey<Twox64Concat, XcmVersion>,
758 NMapKey<Blake2_128Concat, T::AccountId>,
759 NMapKey<Blake2_128Concat, VersionedAssetId>,
760 ),
761 RemoteLockedFungibleRecord<T::RemoteLockConsumerIdentifier, T::MaxRemoteLockConsumers>,
762 OptionQuery,
763 >;
764
765 #[pallet::storage]
767 pub(super) type LockedFungibles<T: Config> = StorageMap<
768 _,
769 Blake2_128Concat,
770 T::AccountId,
771 BoundedVec<(BalanceOf<T>, VersionedLocation), T::MaxLockers>,
772 OptionQuery,
773 >;
774
775 #[pallet::storage]
777 pub(super) type XcmExecutionSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
778
779 #[pallet::storage]
787 pub(crate) type ShouldRecordXcm<T: Config> = StorageValue<_, bool, ValueQuery>;
788
789 #[pallet::storage]
796 pub(crate) type RecordedXcm<T: Config> = StorageValue<_, Xcm<()>>;
797
798 #[pallet::genesis_config]
799 pub struct GenesisConfig<T: Config> {
800 #[serde(skip)]
801 pub _config: core::marker::PhantomData<T>,
802 pub safe_xcm_version: Option<XcmVersion>,
804 }
805
806 impl<T: Config> Default for GenesisConfig<T> {
807 fn default() -> Self {
808 Self { safe_xcm_version: Some(XCM_VERSION), _config: Default::default() }
809 }
810 }
811
812 #[pallet::genesis_build]
813 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
814 fn build(&self) {
815 SafeXcmVersion::<T>::set(self.safe_xcm_version);
816 }
817 }
818
819 #[pallet::hooks]
820 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
821 fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
822 let mut weight_used = Weight::zero();
823 if let Some(migration) = CurrentMigration::<T>::get() {
824 let max_weight = T::BlockWeights::get().max_block / 10;
826 let (w, maybe_migration) = Self::check_xcm_version_change(migration, max_weight);
827 if maybe_migration.is_none() {
828 Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION });
829 }
830 CurrentMigration::<T>::set(maybe_migration);
831 weight_used.saturating_accrue(w);
832 }
833
834 let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
837 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
839 q.sort_by_key(|i| i.1);
840 while let Some((versioned_dest, _)) = q.pop() {
841 if let Ok(dest) = Location::try_from(versioned_dest) {
842 if Self::request_version_notify(dest).is_ok() {
843 weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
845 break
846 }
847 }
848 }
849 if let Ok(q) = BoundedVec::try_from(q) {
852 VersionDiscoveryQueue::<T>::put(q);
853 }
854 weight_used
855 }
856
857 #[cfg(feature = "try-runtime")]
858 fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
859 Self::do_try_state()
860 }
861 }
862
863 pub mod migrations {
864 use super::*;
865 use frame_support::traits::{PalletInfoAccess, StorageVersion};
866
867 #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
868 enum QueryStatusV0<BlockNumber> {
869 Pending {
870 responder: VersionedLocation,
871 maybe_notify: Option<(u8, u8)>,
872 timeout: BlockNumber,
873 },
874 VersionNotifier {
875 origin: VersionedLocation,
876 is_active: bool,
877 },
878 Ready {
879 response: VersionedResponse,
880 at: BlockNumber,
881 },
882 }
883 impl<B> From<QueryStatusV0<B>> for QueryStatus<B> {
884 fn from(old: QueryStatusV0<B>) -> Self {
885 use QueryStatusV0::*;
886 match old {
887 Pending { responder, maybe_notify, timeout } => QueryStatus::Pending {
888 responder,
889 maybe_notify,
890 timeout,
891 maybe_match_querier: Some(Location::here().into()),
892 },
893 VersionNotifier { origin, is_active } =>
894 QueryStatus::VersionNotifier { origin, is_active },
895 Ready { response, at } => QueryStatus::Ready { response, at },
896 }
897 }
898 }
899
900 pub fn migrate_to_v1<T: Config, P: GetStorageVersion + PalletInfoAccess>(
901 ) -> frame_support::weights::Weight {
902 let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
903 log::info!(
904 target: "runtime::xcm",
905 "Running migration storage v1 for xcm with storage version {:?}",
906 on_chain_storage_version,
907 );
908
909 if on_chain_storage_version < 1 {
910 let mut count = 0;
911 Queries::<T>::translate::<QueryStatusV0<BlockNumberFor<T>>, _>(|_key, value| {
912 count += 1;
913 Some(value.into())
914 });
915 StorageVersion::new(1).put::<P>();
916 log::info!(
917 target: "runtime::xcm",
918 "Running migration storage v1 for xcm with storage version {:?} was complete",
919 on_chain_storage_version,
920 );
921 T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
923 } else {
924 log::warn!(
925 target: "runtime::xcm",
926 "Attempted to apply migration to v1 but failed because storage version is {:?}",
927 on_chain_storage_version,
928 );
929 T::DbWeight::get().reads(1)
930 }
931 }
932 }
933
934 #[pallet::call(weight(<T as Config>::WeightInfo))]
935 impl<T: Config> Pallet<T> {
936 #[pallet::call_index(0)]
937 pub fn send(
938 origin: OriginFor<T>,
939 dest: Box<VersionedLocation>,
940 message: Box<VersionedXcm<()>>,
941 ) -> DispatchResult {
942 <Self as SendController<_>>::send(origin, dest, message)?;
943 Ok(())
944 }
945
946 #[pallet::call_index(1)]
965 #[allow(deprecated)]
966 #[deprecated(
967 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_teleport_assets` or `transfer_assets`"
968 )]
969 pub fn teleport_assets(
970 origin: OriginFor<T>,
971 dest: Box<VersionedLocation>,
972 beneficiary: Box<VersionedLocation>,
973 assets: Box<VersionedAssets>,
974 fee_asset_item: u32,
975 ) -> DispatchResult {
976 Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
977 }
978
979 #[pallet::call_index(2)]
1010 #[allow(deprecated)]
1011 #[deprecated(
1012 note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_reserve_transfer_assets` or `transfer_assets`"
1013 )]
1014 pub fn reserve_transfer_assets(
1015 origin: OriginFor<T>,
1016 dest: Box<VersionedLocation>,
1017 beneficiary: Box<VersionedLocation>,
1018 assets: Box<VersionedAssets>,
1019 fee_asset_item: u32,
1020 ) -> DispatchResult {
1021 Self::do_reserve_transfer_assets(
1022 origin,
1023 dest,
1024 beneficiary,
1025 assets,
1026 fee_asset_item,
1027 Unlimited,
1028 )
1029 }
1030
1031 #[pallet::call_index(3)]
1040 #[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
1041 pub fn execute(
1042 origin: OriginFor<T>,
1043 message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
1044 max_weight: Weight,
1045 ) -> DispatchResultWithPostInfo {
1046 let weight_used =
1047 <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
1048 Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())
1049 }
1050
1051 #[pallet::call_index(4)]
1058 pub fn force_xcm_version(
1059 origin: OriginFor<T>,
1060 location: Box<Location>,
1061 version: XcmVersion,
1062 ) -> DispatchResult {
1063 T::AdminOrigin::ensure_origin(origin)?;
1064 let location = *location;
1065 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&location), version);
1066 Self::deposit_event(Event::SupportedVersionChanged { location, version });
1067 Ok(())
1068 }
1069
1070 #[pallet::call_index(5)]
1076 pub fn force_default_xcm_version(
1077 origin: OriginFor<T>,
1078 maybe_xcm_version: Option<XcmVersion>,
1079 ) -> DispatchResult {
1080 T::AdminOrigin::ensure_origin(origin)?;
1081 SafeXcmVersion::<T>::set(maybe_xcm_version);
1082 Ok(())
1083 }
1084
1085 #[pallet::call_index(6)]
1090 pub fn force_subscribe_version_notify(
1091 origin: OriginFor<T>,
1092 location: Box<VersionedLocation>,
1093 ) -> DispatchResult {
1094 T::AdminOrigin::ensure_origin(origin)?;
1095 let location: Location =
1096 (*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
1097 Self::request_version_notify(location).map_err(|e| {
1098 match e {
1099 XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
1100 _ => Error::<T>::InvalidOrigin,
1101 }
1102 .into()
1103 })
1104 }
1105
1106 #[pallet::call_index(7)]
1113 pub fn force_unsubscribe_version_notify(
1114 origin: OriginFor<T>,
1115 location: Box<VersionedLocation>,
1116 ) -> DispatchResult {
1117 T::AdminOrigin::ensure_origin(origin)?;
1118 let location: Location =
1119 (*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
1120 Self::unrequest_version_notify(location).map_err(|e| {
1121 match e {
1122 XcmError::InvalidLocation => Error::<T>::NoSubscription,
1123 _ => Error::<T>::InvalidOrigin,
1124 }
1125 .into()
1126 })
1127 }
1128
1129 #[pallet::call_index(8)]
1160 #[pallet::weight(T::WeightInfo::reserve_transfer_assets())]
1161 pub fn limited_reserve_transfer_assets(
1162 origin: OriginFor<T>,
1163 dest: Box<VersionedLocation>,
1164 beneficiary: Box<VersionedLocation>,
1165 assets: Box<VersionedAssets>,
1166 fee_asset_item: u32,
1167 weight_limit: WeightLimit,
1168 ) -> DispatchResult {
1169 Self::do_reserve_transfer_assets(
1170 origin,
1171 dest,
1172 beneficiary,
1173 assets,
1174 fee_asset_item,
1175 weight_limit,
1176 )
1177 }
1178
1179 #[pallet::call_index(9)]
1198 #[pallet::weight(T::WeightInfo::teleport_assets())]
1199 pub fn limited_teleport_assets(
1200 origin: OriginFor<T>,
1201 dest: Box<VersionedLocation>,
1202 beneficiary: Box<VersionedLocation>,
1203 assets: Box<VersionedAssets>,
1204 fee_asset_item: u32,
1205 weight_limit: WeightLimit,
1206 ) -> DispatchResult {
1207 Self::do_teleport_assets(
1208 origin,
1209 dest,
1210 beneficiary,
1211 assets,
1212 fee_asset_item,
1213 weight_limit,
1214 )
1215 }
1216
1217 #[pallet::call_index(10)]
1222 pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
1223 T::AdminOrigin::ensure_origin(origin)?;
1224 XcmExecutionSuspended::<T>::set(suspended);
1225 Ok(())
1226 }
1227
1228 #[pallet::call_index(11)]
1262 pub fn transfer_assets(
1263 origin: OriginFor<T>,
1264 dest: Box<VersionedLocation>,
1265 beneficiary: Box<VersionedLocation>,
1266 assets: Box<VersionedAssets>,
1267 fee_asset_item: u32,
1268 weight_limit: WeightLimit,
1269 ) -> DispatchResult {
1270 let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1271 let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
1272 let beneficiary: Location =
1273 (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
1274 let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1275 log::debug!(
1276 target: "xcm::pallet_xcm::transfer_assets",
1277 "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}",
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 log::debug!(target: "xcm::pallet_xcm::claim_assets", "origin: {:?}, assets: {:?}, beneficiary: {:?}", 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 log::error!(target: "xcm::pallet_xcm::claim_assets", "XCM execution failed with error: {:?}", 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 log::debug!(
1410 target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
1411 "origin {origin_location:?}, dest {dest:?}, assets {assets:?} through {assets_transfer_type:?}, \
1412 remote_fees_id {fees_id:?} through {fees_transfer_type:?}, \
1413 custom_xcm_on_dest {remote_xcm:?}, weight-limit {weight_limit:?}",
1414 );
1415
1416 let assets = assets.into_inner();
1417 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1418
1419 let fee_asset_index =
1420 assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
1421 Self::do_transfer_assets(
1422 origin_location,
1423 dest,
1424 Either::Right(remote_xcm),
1425 assets,
1426 *assets_transfer_type,
1427 fee_asset_index,
1428 *fees_transfer_type,
1429 weight_limit,
1430 )
1431 }
1432 }
1433}
1434
1435const MAX_ASSETS_FOR_TRANSFER: usize = 2;
1437
1438#[derive(Clone, PartialEq)]
1440enum FeesHandling<T: Config> {
1441 Batched { fees: Asset },
1443 Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
1445}
1446
1447impl<T: Config> core::fmt::Debug for FeesHandling<T> {
1448 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1449 match self {
1450 Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
1451 Self::Separate { local_xcm, remote_xcm } => write!(
1452 f,
1453 "FeesHandling::Separate(local: {:?}, remote: {:?})",
1454 local_xcm, remote_xcm
1455 ),
1456 }
1457 }
1458}
1459
1460impl<T: Config> QueryHandler for Pallet<T> {
1461 type BlockNumber = BlockNumberFor<T>;
1462 type Error = XcmError;
1463 type UniversalLocation = T::UniversalLocation;
1464
1465 fn new_query(
1467 responder: impl Into<Location>,
1468 timeout: BlockNumberFor<T>,
1469 match_querier: impl Into<Location>,
1470 ) -> QueryId {
1471 Self::do_new_query(responder, None, timeout, match_querier)
1472 }
1473
1474 fn report_outcome(
1477 message: &mut Xcm<()>,
1478 responder: impl Into<Location>,
1479 timeout: Self::BlockNumber,
1480 ) -> Result<QueryId, Self::Error> {
1481 let responder = responder.into();
1482 let destination = Self::UniversalLocation::get()
1483 .invert_target(&responder)
1484 .map_err(|()| XcmError::LocationNotInvertible)?;
1485 let query_id = Self::new_query(responder, timeout, Here);
1486 let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
1487 let report_error = Xcm(vec![ReportError(response_info)]);
1488 message.0.insert(0, SetAppendix(report_error));
1489 Ok(query_id)
1490 }
1491
1492 fn take_response(query_id: QueryId) -> QueryResponseStatus<Self::BlockNumber> {
1494 match Queries::<T>::get(query_id) {
1495 Some(QueryStatus::Ready { response, at }) => match response.try_into() {
1496 Ok(response) => {
1497 Queries::<T>::remove(query_id);
1498 Self::deposit_event(Event::ResponseTaken { query_id });
1499 QueryResponseStatus::Ready { response, at }
1500 },
1501 Err(_) => QueryResponseStatus::UnexpectedVersion,
1502 },
1503 Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
1504 Some(_) => QueryResponseStatus::UnexpectedVersion,
1505 None => QueryResponseStatus::NotFound,
1506 }
1507 }
1508
1509 #[cfg(feature = "runtime-benchmarks")]
1510 fn expect_response(id: QueryId, response: Response) {
1511 let response = response.into();
1512 Queries::<T>::insert(
1513 id,
1514 QueryStatus::Ready { response, at: frame_system::Pallet::<T>::block_number() },
1515 );
1516 }
1517}
1518
1519impl<T: Config> Pallet<T> {
1520 fn find_fee_and_assets_transfer_types(
1525 assets: &[Asset],
1526 fee_asset_item: usize,
1527 dest: &Location,
1528 ) -> Result<(TransferType, TransferType), Error<T>> {
1529 let mut fees_transfer_type = None;
1530 let mut assets_transfer_type = None;
1531 for (idx, asset) in assets.iter().enumerate() {
1532 if let Fungible(x) = asset.fun {
1533 ensure!(!x.is_zero(), Error::<T>::Empty);
1535 }
1536 let transfer_type =
1537 T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
1538 if idx == fee_asset_item {
1539 fees_transfer_type = Some(transfer_type);
1540 } else {
1541 if let Some(existing) = assets_transfer_type.as_ref() {
1542 ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
1545 } else {
1546 assets_transfer_type = Some(transfer_type);
1548 }
1549 }
1550 }
1551 if assets.len() == 1 {
1553 assets_transfer_type = fees_transfer_type.clone()
1554 }
1555 Ok((
1556 fees_transfer_type.ok_or(Error::<T>::Empty)?,
1557 assets_transfer_type.ok_or(Error::<T>::Empty)?,
1558 ))
1559 }
1560
1561 fn do_reserve_transfer_assets(
1562 origin: OriginFor<T>,
1563 dest: Box<VersionedLocation>,
1564 beneficiary: Box<VersionedLocation>,
1565 assets: Box<VersionedAssets>,
1566 fee_asset_item: u32,
1567 weight_limit: WeightLimit,
1568 ) -> DispatchResult {
1569 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1570 let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
1571 let beneficiary: Location =
1572 (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
1573 let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1574 log::debug!(
1575 target: "xcm::pallet_xcm::do_reserve_transfer_assets",
1576 "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}",
1577 origin_location, dest, beneficiary, assets, fee_asset_item,
1578 );
1579
1580 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1581 let value = (origin_location, assets.into_inner());
1582 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1583 let (origin, assets) = value;
1584
1585 let fee_asset_item = fee_asset_item as usize;
1586 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
1587
1588 let (fees_transfer_type, assets_transfer_type) =
1590 Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
1591 ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
1593 ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
1595
1596 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
1597 origin.clone(),
1598 dest.clone(),
1599 Either::Left(beneficiary),
1600 assets,
1601 assets_transfer_type,
1602 FeesHandling::Batched { fees },
1603 weight_limit,
1604 )?;
1605 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
1606 }
1607
1608 fn do_teleport_assets(
1609 origin: OriginFor<T>,
1610 dest: Box<VersionedLocation>,
1611 beneficiary: Box<VersionedLocation>,
1612 assets: Box<VersionedAssets>,
1613 fee_asset_item: u32,
1614 weight_limit: WeightLimit,
1615 ) -> DispatchResult {
1616 let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1617 let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
1618 let beneficiary: Location =
1619 (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
1620 let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
1621 log::debug!(
1622 target: "xcm::pallet_xcm::do_teleport_assets",
1623 "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}",
1624 origin_location, dest, beneficiary, assets, fee_asset_item, weight_limit,
1625 );
1626
1627 ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1628 let value = (origin_location, assets.into_inner());
1629 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
1630 let (origin_location, assets) = value;
1631 for asset in assets.iter() {
1632 let transfer_type =
1633 T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
1634 ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
1635 }
1636 let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
1637
1638 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
1639 origin_location.clone(),
1640 dest.clone(),
1641 Either::Left(beneficiary),
1642 assets,
1643 TransferType::Teleport,
1644 FeesHandling::Batched { fees },
1645 weight_limit,
1646 )?;
1647 Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
1648 }
1649
1650 fn do_transfer_assets(
1651 origin: Location,
1652 dest: Location,
1653 beneficiary: Either<Location, Xcm<()>>,
1654 mut assets: Vec<Asset>,
1655 assets_transfer_type: TransferType,
1656 fee_asset_index: usize,
1657 fees_transfer_type: TransferType,
1658 weight_limit: WeightLimit,
1659 ) -> DispatchResult {
1660 let fees = if fees_transfer_type == assets_transfer_type {
1662 let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
1663 FeesHandling::Batched { fees }
1665 } else {
1666 ensure!(
1672 !matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
1673 Error::<T>::InvalidAssetUnsupportedReserve
1674 );
1675 let weight_limit = weight_limit.clone();
1676 let fees = assets.remove(fee_asset_index);
1679 let (local_xcm, remote_xcm) = match fees_transfer_type {
1680 TransferType::LocalReserve => Self::local_reserve_fees_instructions(
1681 origin.clone(),
1682 dest.clone(),
1683 fees,
1684 weight_limit,
1685 )?,
1686 TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
1687 origin.clone(),
1688 dest.clone(),
1689 fees,
1690 weight_limit,
1691 )?,
1692 TransferType::Teleport => Self::teleport_fees_instructions(
1693 origin.clone(),
1694 dest.clone(),
1695 fees,
1696 weight_limit,
1697 )?,
1698 TransferType::RemoteReserve(_) =>
1699 return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
1700 };
1701 FeesHandling::Separate { local_xcm, remote_xcm }
1702 };
1703
1704 let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
1705 origin.clone(),
1706 dest.clone(),
1707 beneficiary,
1708 assets,
1709 assets_transfer_type,
1710 fees,
1711 weight_limit,
1712 )?;
1713 Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
1714 }
1715
1716 fn build_xcm_transfer_type(
1717 origin: Location,
1718 dest: Location,
1719 beneficiary: Either<Location, Xcm<()>>,
1720 assets: Vec<Asset>,
1721 transfer_type: TransferType,
1722 fees: FeesHandling<T>,
1723 weight_limit: WeightLimit,
1724 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
1725 log::debug!(
1726 target: "xcm::pallet_xcm::build_xcm_transfer_type",
1727 "origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
1728 fees_handling {:?}, weight_limit: {:?}",
1729 origin, dest, beneficiary, assets, transfer_type, fees, weight_limit,
1730 );
1731 match transfer_type {
1732 TransferType::LocalReserve => Self::local_reserve_transfer_programs(
1733 origin.clone(),
1734 dest.clone(),
1735 beneficiary,
1736 assets,
1737 fees,
1738 weight_limit,
1739 )
1740 .map(|(local, remote)| (local, Some(remote))),
1741 TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
1742 origin.clone(),
1743 dest.clone(),
1744 beneficiary,
1745 assets,
1746 fees,
1747 weight_limit,
1748 )
1749 .map(|(local, remote)| (local, Some(remote))),
1750 TransferType::RemoteReserve(reserve) => {
1751 let fees = match fees {
1752 FeesHandling::Batched { fees } => fees,
1753 _ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
1754 };
1755 Self::remote_reserve_transfer_program(
1756 origin.clone(),
1757 reserve.try_into().map_err(|()| Error::<T>::BadVersion)?,
1758 beneficiary,
1759 dest.clone(),
1760 assets,
1761 fees,
1762 weight_limit,
1763 )
1764 .map(|local| (local, None))
1765 },
1766 TransferType::Teleport => Self::teleport_assets_program(
1767 origin.clone(),
1768 dest.clone(),
1769 beneficiary,
1770 assets,
1771 fees,
1772 weight_limit,
1773 )
1774 .map(|(local, remote)| (local, Some(remote))),
1775 }
1776 }
1777
1778 fn execute_xcm_transfer(
1779 origin: Location,
1780 dest: Location,
1781 mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
1782 remote_xcm: Option<Xcm<()>>,
1783 ) -> DispatchResult {
1784 log::debug!(
1785 target: "xcm::pallet_xcm::execute_xcm_transfer",
1786 "origin {:?}, dest {:?}, local_xcm {:?}, remote_xcm {:?}",
1787 origin, dest, local_xcm, remote_xcm,
1788 );
1789
1790 let weight =
1791 T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
1792 let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
1793 let outcome = T::XcmExecutor::prepare_and_execute(
1794 origin.clone(),
1795 local_xcm,
1796 &mut hash,
1797 weight,
1798 weight,
1799 );
1800 Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
1801 outcome.ensure_complete().map_err(|error| {
1802 log::error!(
1803 target: "xcm::pallet_xcm::execute_xcm_transfer",
1804 "XCM execution failed with error {:?}", error
1805 );
1806 Error::<T>::LocalExecutionIncomplete
1807 })?;
1808
1809 if let Some(remote_xcm) = remote_xcm {
1810 let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
1811 .map_err(|error| {
1812 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error");
1813 Error::<T>::from(error)
1814 })?;
1815 if origin != Here.into_location() {
1816 Self::charge_fees(origin.clone(), price).map_err(|error| {
1817 log::error!(
1818 target: "xcm::pallet_xcm::execute_xcm_transfer",
1819 "Unable to charge fee with error {:?}", error
1820 );
1821 Error::<T>::FeesNotMet
1822 })?;
1823 }
1824 let message_id = T::XcmRouter::deliver(ticket)
1825 .map_err(|error| {
1826 tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error");
1827 Error::<T>::from(error)
1828 })?;
1829
1830 let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
1831 Self::deposit_event(e);
1832 }
1833 Ok(())
1834 }
1835
1836 fn add_fees_to_xcm(
1837 dest: Location,
1838 fees: FeesHandling<T>,
1839 weight_limit: WeightLimit,
1840 local: &mut Xcm<<T as Config>::RuntimeCall>,
1841 remote: &mut Xcm<()>,
1842 ) -> Result<(), Error<T>> {
1843 match fees {
1844 FeesHandling::Batched { fees } => {
1845 let context = T::UniversalLocation::get();
1846 let reanchored_fees =
1849 fees.reanchored(&dest, &context).map_err(|_| Error::<T>::CannotReanchor)?;
1850 remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
1852 },
1853 FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
1854 core::mem::swap(local, &mut local_fees);
1857 core::mem::swap(remote, &mut remote_fees);
1858 local.inner_mut().append(&mut local_fees.into_inner());
1860 remote.inner_mut().append(&mut remote_fees.into_inner());
1861 },
1862 }
1863 Ok(())
1864 }
1865
1866 fn local_reserve_fees_instructions(
1867 origin: Location,
1868 dest: Location,
1869 fees: Asset,
1870 weight_limit: WeightLimit,
1871 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
1872 let value = (origin, vec![fees.clone()]);
1873 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1874
1875 let context = T::UniversalLocation::get();
1876 let reanchored_fees = fees
1877 .clone()
1878 .reanchored(&dest, &context)
1879 .map_err(|_| Error::<T>::CannotReanchor)?;
1880
1881 let local_execute_xcm = Xcm(vec![
1882 TransferAsset { assets: fees.into(), beneficiary: dest },
1884 ]);
1885 let xcm_on_dest = Xcm(vec![
1886 ReserveAssetDeposited(reanchored_fees.clone().into()),
1888 BuyExecution { fees: reanchored_fees, weight_limit },
1890 ]);
1891 Ok((local_execute_xcm, xcm_on_dest))
1892 }
1893
1894 fn local_reserve_transfer_programs(
1895 origin: Location,
1896 dest: Location,
1897 beneficiary: Either<Location, Xcm<()>>,
1898 assets: Vec<Asset>,
1899 fees: FeesHandling<T>,
1900 weight_limit: WeightLimit,
1901 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
1902 let value = (origin, assets);
1903 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1904 let (_, assets) = value;
1905
1906 let max_assets =
1908 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
1909 let assets: Assets = assets.into();
1910 let context = T::UniversalLocation::get();
1911 let mut reanchored_assets = assets.clone();
1912 reanchored_assets
1913 .reanchor(&dest, &context)
1914 .map_err(|_| Error::<T>::CannotReanchor)?;
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(|_| Error::<T>::CannotReanchor)?;
1962 let fees: Assets = fees.into();
1963
1964 let local_execute_xcm = Xcm(vec![
1965 WithdrawAsset(fees.clone()),
1967 BurnAsset(fees),
1969 ]);
1970 let xcm_on_dest = Xcm(vec![
1971 WithdrawAsset(reanchored_fees.clone().into()),
1973 BuyExecution { fees: reanchored_fees, weight_limit },
1975 ]);
1976 Ok((local_execute_xcm, xcm_on_dest))
1977 }
1978
1979 fn destination_reserve_transfer_programs(
1980 origin: Location,
1981 dest: Location,
1982 beneficiary: Either<Location, Xcm<()>>,
1983 assets: Vec<Asset>,
1984 fees: FeesHandling<T>,
1985 weight_limit: WeightLimit,
1986 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
1987 let value = (origin, assets);
1988 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
1989 let (_, assets) = value;
1990 for asset in assets.iter() {
1991 ensure!(
1992 <T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&asset, &dest),
1993 Error::<T>::InvalidAssetUnsupportedReserve
1994 );
1995 }
1996
1997 let max_assets =
1999 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2000 let assets: Assets = assets.into();
2001 let context = T::UniversalLocation::get();
2002 let mut reanchored_assets = assets.clone();
2003 reanchored_assets
2004 .reanchor(&dest, &context)
2005 .map_err(|_| Error::<T>::CannotReanchor)?;
2006
2007 let mut local_execute_xcm = Xcm(vec![
2009 WithdrawAsset(assets.clone()),
2011 BurnAsset(assets),
2013 ]);
2014 let mut xcm_on_dest = Xcm(vec![
2016 WithdrawAsset(reanchored_assets),
2018 ClearOrigin,
2020 ]);
2021 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2023
2024 let custom_remote_xcm = match beneficiary {
2026 Either::Right(custom_xcm) => custom_xcm,
2027 Either::Left(beneficiary) => {
2028 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2030 },
2031 };
2032 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2033
2034 Ok((local_execute_xcm, xcm_on_dest))
2035 }
2036
2037 fn remote_reserve_transfer_program(
2039 origin: Location,
2040 reserve: Location,
2041 beneficiary: Either<Location, Xcm<()>>,
2042 dest: Location,
2043 assets: Vec<Asset>,
2044 fees: Asset,
2045 weight_limit: WeightLimit,
2046 ) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
2047 let value = (origin, assets);
2048 ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2049 let (_, assets) = value;
2050
2051 let max_assets = assets.len() as u32;
2052 let context = T::UniversalLocation::get();
2053 let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
2056 let reserve_fees = fees_half_1
2058 .reanchored(&reserve, &context)
2059 .map_err(|_| Error::<T>::CannotReanchor)?;
2060 let dest_fees = fees_half_2
2062 .reanchored(&dest, &context)
2063 .map_err(|_| Error::<T>::CannotReanchor)?;
2064 let dest = dest.reanchored(&reserve, &context).map_err(|_| Error::<T>::CannotReanchor)?;
2066 let mut xcm_on_dest =
2068 Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
2069 let custom_xcm_on_dest = match beneficiary {
2071 Either::Right(custom_xcm) => custom_xcm,
2072 Either::Left(beneficiary) => {
2073 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2075 },
2076 };
2077 xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
2078 let xcm_on_reserve = Xcm(vec![
2080 BuyExecution { fees: reserve_fees, weight_limit },
2081 DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
2082 ]);
2083 Ok(Xcm(vec![
2084 WithdrawAsset(assets.into()),
2085 SetFeesMode { jit_withdraw: true },
2086 InitiateReserveWithdraw {
2087 assets: Wild(AllCounted(max_assets)),
2088 reserve,
2089 xcm: xcm_on_reserve,
2090 },
2091 ]))
2092 }
2093
2094 fn teleport_fees_instructions(
2095 origin: Location,
2096 dest: Location,
2097 fees: Asset,
2098 weight_limit: WeightLimit,
2099 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2100 let value = (origin, vec![fees.clone()]);
2101 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2102 ensure!(
2103 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&fees, &dest),
2104 Error::<T>::Filtered
2105 );
2106
2107 let context = T::UniversalLocation::get();
2108 let reanchored_fees = fees
2109 .clone()
2110 .reanchored(&dest, &context)
2111 .map_err(|_| Error::<T>::CannotReanchor)?;
2112
2113 let dummy_context =
2115 XcmContext { origin: None, message_id: Default::default(), topic: None };
2116 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2121 &dest,
2122 &fees,
2123 &dummy_context,
2124 )
2125 .map_err(|_| Error::<T>::CannotCheckOutTeleport)?;
2126 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2129 &dest,
2130 &fees,
2131 &dummy_context,
2132 );
2133
2134 let fees: Assets = fees.into();
2135 let local_execute_xcm = Xcm(vec![
2136 WithdrawAsset(fees.clone()),
2138 BurnAsset(fees),
2140 ]);
2141 let xcm_on_dest = Xcm(vec![
2142 ReceiveTeleportedAsset(reanchored_fees.clone().into()),
2144 BuyExecution { fees: reanchored_fees, weight_limit },
2146 ]);
2147 Ok((local_execute_xcm, xcm_on_dest))
2148 }
2149
2150 fn teleport_assets_program(
2151 origin: Location,
2152 dest: Location,
2153 beneficiary: Either<Location, Xcm<()>>,
2154 assets: Vec<Asset>,
2155 fees: FeesHandling<T>,
2156 weight_limit: WeightLimit,
2157 ) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2158 let value = (origin, assets);
2159 ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2160 let (_, assets) = value;
2161 for asset in assets.iter() {
2162 ensure!(
2163 <T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&asset, &dest),
2164 Error::<T>::Filtered
2165 );
2166 }
2167
2168 let max_assets =
2170 assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2171 let context = T::UniversalLocation::get();
2172 let assets: Assets = assets.into();
2173 let mut reanchored_assets = assets.clone();
2174 reanchored_assets
2175 .reanchor(&dest, &context)
2176 .map_err(|_| Error::<T>::CannotReanchor)?;
2177
2178 let dummy_context =
2180 XcmContext { origin: None, message_id: Default::default(), topic: None };
2181 for asset in assets.inner() {
2182 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2187 &dest,
2188 asset,
2189 &dummy_context,
2190 )
2191 .map_err(|_| Error::<T>::CannotCheckOutTeleport)?;
2192 }
2193 for asset in assets.inner() {
2194 <T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2197 &dest,
2198 asset,
2199 &dummy_context,
2200 );
2201 }
2202
2203 let mut local_execute_xcm = Xcm(vec![
2205 WithdrawAsset(assets.clone()),
2207 BurnAsset(assets),
2209 ]);
2210 let mut xcm_on_dest = Xcm(vec![
2212 ReceiveTeleportedAsset(reanchored_assets),
2214 ClearOrigin,
2216 ]);
2217 Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2219
2220 let custom_remote_xcm = match beneficiary {
2222 Either::Right(custom_xcm) => custom_xcm,
2223 Either::Left(beneficiary) => {
2224 Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2226 },
2227 };
2228 xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2229
2230 Ok((local_execute_xcm, xcm_on_dest))
2231 }
2232
2233 pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
2235 match fees.fun {
2236 Fungible(amount) => {
2237 let fee1 = amount.saturating_div(2);
2238 let fee2 = amount.saturating_sub(fee1);
2239 ensure!(fee1 > 0, Error::<T>::FeesNotMet);
2240 ensure!(fee2 > 0, Error::<T>::FeesNotMet);
2241 Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
2242 },
2243 NonFungible(_) => Err(Error::<T>::FeesNotMet),
2244 }
2245 }
2246
2247 pub(crate) fn check_xcm_version_change(
2250 mut stage: VersionMigrationStage,
2251 weight_cutoff: Weight,
2252 ) -> (Weight, Option<VersionMigrationStage>) {
2253 let mut weight_used = Weight::zero();
2254
2255 let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
2256 let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
2257 let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
2258 let vnt_notify_weight = T::WeightInfo::notify_current_targets();
2259 let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
2260 let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
2261 let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
2262
2263 use VersionMigrationStage::*;
2264
2265 if stage == MigrateSupportedVersion {
2266 for v in 0..XCM_VERSION {
2269 for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
2270 if let Ok(new_key) = old_key.into_latest() {
2271 SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
2272 }
2273 weight_used.saturating_accrue(sv_migrate_weight);
2274 if weight_used.any_gte(weight_cutoff) {
2275 return (weight_used, Some(stage))
2276 }
2277 }
2278 }
2279 stage = MigrateVersionNotifiers;
2280 }
2281 if stage == MigrateVersionNotifiers {
2282 for v in 0..XCM_VERSION {
2283 for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
2284 if let Ok(new_key) = old_key.into_latest() {
2285 VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
2286 }
2287 weight_used.saturating_accrue(vn_migrate_weight);
2288 if weight_used.any_gte(weight_cutoff) {
2289 return (weight_used, Some(stage))
2290 }
2291 }
2292 }
2293 stage = NotifyCurrentTargets(None);
2294 }
2295
2296 let xcm_version = T::AdvertisedXcmVersion::get();
2297
2298 if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
2299 let mut iter = match maybe_last_raw_key {
2300 Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
2301 None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
2302 };
2303 while let Some((key, value)) = iter.next() {
2304 let (query_id, max_weight, target_xcm_version) = value;
2305 let new_key: Location = match key.clone().try_into() {
2306 Ok(k) if target_xcm_version != xcm_version => k,
2307 _ => {
2308 weight_used.saturating_accrue(vnt_already_notified_weight);
2311 continue
2312 },
2313 };
2314 let response = Response::Version(xcm_version);
2315 let message =
2316 Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
2317 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2318 Ok((message_id, cost)) => {
2319 let value = (query_id, max_weight, xcm_version);
2320 VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
2321 Event::VersionChangeNotified {
2322 destination: new_key,
2323 result: xcm_version,
2324 cost,
2325 message_id,
2326 }
2327 },
2328 Err(e) => {
2329 VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
2330 Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
2331 },
2332 };
2333 Self::deposit_event(event);
2334 weight_used.saturating_accrue(vnt_notify_weight);
2335 if weight_used.any_gte(weight_cutoff) {
2336 let last = Some(iter.last_raw_key().into());
2337 return (weight_used, Some(NotifyCurrentTargets(last)))
2338 }
2339 }
2340 stage = MigrateAndNotifyOldTargets;
2341 }
2342 if stage == MigrateAndNotifyOldTargets {
2343 for v in 0..XCM_VERSION {
2344 for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
2345 let (query_id, max_weight, target_xcm_version) = value;
2346 let new_key = match Location::try_from(old_key.clone()) {
2347 Ok(k) => k,
2348 Err(()) => {
2349 Self::deposit_event(Event::NotifyTargetMigrationFail {
2350 location: old_key,
2351 query_id: value.0,
2352 });
2353 weight_used.saturating_accrue(vnt_migrate_fail_weight);
2354 if weight_used.any_gte(weight_cutoff) {
2355 return (weight_used, Some(stage))
2356 }
2357 continue
2358 },
2359 };
2360
2361 let versioned_key = LatestVersionedLocation(&new_key);
2362 if target_xcm_version == xcm_version {
2363 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
2364 weight_used.saturating_accrue(vnt_migrate_weight);
2365 } else {
2366 let response = Response::Version(xcm_version);
2368 let message = Xcm(vec![QueryResponse {
2369 query_id,
2370 response,
2371 max_weight,
2372 querier: None,
2373 }]);
2374 let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2375 Ok((message_id, cost)) => {
2376 VersionNotifyTargets::<T>::insert(
2377 XCM_VERSION,
2378 versioned_key,
2379 (query_id, max_weight, xcm_version),
2380 );
2381 Event::VersionChangeNotified {
2382 destination: new_key,
2383 result: xcm_version,
2384 cost,
2385 message_id,
2386 }
2387 },
2388 Err(e) => Event::NotifyTargetSendFail {
2389 location: new_key,
2390 query_id,
2391 error: e.into(),
2392 },
2393 };
2394 Self::deposit_event(event);
2395 weight_used.saturating_accrue(vnt_notify_migrate_weight);
2396 }
2397 if weight_used.any_gte(weight_cutoff) {
2398 return (weight_used, Some(stage))
2399 }
2400 }
2401 }
2402 }
2403 (weight_used, None)
2404 }
2405
2406 pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
2408 let dest = dest.into();
2409 let versioned_dest = VersionedLocation::from(dest.clone());
2410 let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
2411 ensure!(!already, XcmError::InvalidLocation);
2412 let query_id = QueryCounter::<T>::mutate(|q| {
2413 let r = *q;
2414 q.saturating_inc();
2415 r
2416 });
2417 let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
2419 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
2420 Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
2421 VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
2422 let query_status =
2423 QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
2424 Queries::<T>::insert(query_id, query_status);
2425 Ok(())
2426 }
2427
2428 pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
2430 let dest = dest.into();
2431 let versioned_dest = LatestVersionedLocation(&dest);
2432 let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
2433 .ok_or(XcmError::InvalidLocation)?;
2434 let (message_id, cost) =
2435 send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
2436 Self::deposit_event(Event::VersionNotifyUnrequested {
2437 destination: dest,
2438 cost,
2439 message_id,
2440 });
2441 Queries::<T>::remove(query_id);
2442 Ok(())
2443 }
2444
2445 pub fn send_xcm(
2449 interior: impl Into<Junctions>,
2450 dest: impl Into<Location>,
2451 mut message: Xcm<()>,
2452 ) -> Result<XcmHash, SendError> {
2453 let interior = interior.into();
2454 let dest = dest.into();
2455 let maybe_fee_payer = if interior != Junctions::Here {
2456 message.0.insert(0, DescendOrigin(interior.clone()));
2457 Some(interior.into())
2458 } else {
2459 None
2460 };
2461 log::debug!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
2462 let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
2463 if let Some(fee_payer) = maybe_fee_payer {
2464 Self::charge_fees(fee_payer, price).map_err(|_| SendError::Fees)?;
2465 }
2466 T::XcmRouter::deliver(ticket)
2467 }
2468
2469 pub fn check_account() -> T::AccountId {
2470 const ID: PalletId = PalletId(*b"py/xcmch");
2471 AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
2472 }
2473
2474 pub fn dry_run_call<Runtime, Router, OriginCaller, RuntimeCall>(
2480 origin: OriginCaller,
2481 call: RuntimeCall,
2482 result_xcms_version: XcmVersion,
2483 ) -> Result<CallDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
2484 where
2485 Runtime: crate::Config,
2486 Router: InspectMessageQueues,
2487 RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
2488 <RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
2489 {
2490 crate::Pallet::<Runtime>::set_record_xcm(true);
2491 Router::clear_messages();
2493 frame_system::Pallet::<Runtime>::reset_events();
2495 let result = call.dispatch(origin.into());
2496 crate::Pallet::<Runtime>::set_record_xcm(false);
2497 let local_xcm = crate::Pallet::<Runtime>::recorded_xcm()
2498 .map(|xcm| VersionedXcm::<()>::from(xcm).into_version(result_xcms_version))
2499 .transpose()
2500 .map_err(|()| {
2501 tracing::error!(
2502 target: "xcm::DryRunApi::dry_run_call",
2503 "Local xcm version conversion failed"
2504 );
2505
2506 XcmDryRunApiError::VersionedConversionFailed
2507 })?;
2508
2509 let forwarded_xcms =
2511 Self::convert_forwarded_xcms(result_xcms_version, Router::get_messages()).inspect_err(
2512 |error| {
2513 tracing::error!(
2514 target: "xcm::DryRunApi::dry_run_call",
2515 ?error, "Forwarded xcms version conversion failed with error"
2516 );
2517 },
2518 )?;
2519 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
2520 frame_system::Pallet::<Runtime>::read_events_no_consensus()
2521 .map(|record| record.event.clone())
2522 .collect();
2523 Ok(CallDryRunEffects {
2524 local_xcm: local_xcm.map(VersionedXcm::<()>::from),
2525 forwarded_xcms,
2526 emitted_events: events,
2527 execution_result: result,
2528 })
2529 }
2530
2531 pub fn dry_run_xcm<Runtime, Router, RuntimeCall, XcmConfig>(
2536 origin_location: VersionedLocation,
2537 xcm: VersionedXcm<RuntimeCall>,
2538 ) -> Result<XcmDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
2539 where
2540 Runtime: frame_system::Config,
2541 Router: InspectMessageQueues,
2542 XcmConfig: xcm_executor::Config<RuntimeCall = RuntimeCall>,
2543 {
2544 let origin_location: Location = origin_location.try_into().map_err(|error| {
2545 log::error!(
2546 target: "xcm::DryRunApi::dry_run_xcm",
2547 "Location version conversion failed with error: {:?}",
2548 error,
2549 );
2550 XcmDryRunApiError::VersionedConversionFailed
2551 })?;
2552 let xcm_version = xcm.identify_version();
2553 let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| {
2554 log::error!(
2555 target: "xcm::DryRunApi::dry_run_xcm",
2556 "Xcm version conversion failed with error {:?}",
2557 error,
2558 );
2559 XcmDryRunApiError::VersionedConversionFailed
2560 })?;
2561 let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
2562
2563 Router::clear_messages();
2565 frame_system::Pallet::<Runtime>::reset_events();
2566
2567 let result = xcm_executor::XcmExecutor::<XcmConfig>::prepare_and_execute(
2568 origin_location,
2569 xcm,
2570 &mut hash,
2571 Weight::MAX, Weight::zero(),
2573 );
2574 let forwarded_xcms = Self::convert_forwarded_xcms(xcm_version, Router::get_messages())
2575 .inspect_err(|error| {
2576 tracing::error!(
2577 target: "xcm::DryRunApi::dry_run_xcm",
2578 ?error, "Forwarded xcms version conversion failed with error"
2579 );
2580 })?;
2581 let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
2582 frame_system::Pallet::<Runtime>::read_events_no_consensus()
2583 .map(|record| record.event.clone())
2584 .collect();
2585 Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
2586 }
2587
2588 fn convert_xcms(
2589 xcm_version: XcmVersion,
2590 xcms: Vec<VersionedXcm<()>>,
2591 ) -> Result<Vec<VersionedXcm<()>>, ()> {
2592 xcms.into_iter()
2593 .map(|xcm| xcm.into_version(xcm_version))
2594 .collect::<Result<Vec<_>, ()>>()
2595 }
2596
2597 fn convert_forwarded_xcms(
2598 xcm_version: XcmVersion,
2599 forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>,
2600 ) -> Result<Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, XcmDryRunApiError> {
2601 forwarded_xcms
2602 .into_iter()
2603 .map(|(dest, forwarded_xcms)| {
2604 let dest = dest.into_version(xcm_version)?;
2605 let forwarded_xcms = Self::convert_xcms(xcm_version, forwarded_xcms)?;
2606
2607 Ok((dest, forwarded_xcms))
2608 })
2609 .collect::<Result<Vec<_>, ()>>()
2610 .map_err(|()| XcmDryRunApiError::VersionedConversionFailed)
2611 }
2612
2613 pub fn query_acceptable_payment_assets(
2618 version: xcm::Version,
2619 asset_ids: Vec<AssetId>,
2620 ) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
2621 Ok(asset_ids
2622 .into_iter()
2623 .map(|asset_id| VersionedAssetId::from(asset_id))
2624 .filter_map(|asset_id| asset_id.into_version(version).ok())
2625 .collect())
2626 }
2627
2628 pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
2629 let message = Xcm::<()>::try_from(message)
2630 .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?;
2631
2632 T::Weigher::weight(&mut message.into()).map_err(|()| {
2633 log::error!(target: "xcm::pallet_xcm::query_xcm_weight", "Error when querying XCM weight");
2634 XcmPaymentApiError::WeightNotComputable
2635 })
2636 }
2637
2638 pub fn query_delivery_fees(
2639 destination: VersionedLocation,
2640 message: VersionedXcm<()>,
2641 ) -> Result<VersionedAssets, XcmPaymentApiError> {
2642 let result_version = destination.identify_version().max(message.identify_version());
2643
2644 let destination = destination
2645 .try_into()
2646 .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?;
2647
2648 let message =
2649 message.try_into().map_err(|_| XcmPaymentApiError::VersionedConversionFailed)?;
2650
2651 let (_, fees) = validate_send::<T::XcmRouter>(destination, message).map_err(|error| {
2652 log::error!(target: "xcm::pallet_xcm::query_delivery_fees", "Error when querying delivery fees: {:?}", error);
2653 XcmPaymentApiError::Unroutable
2654 })?;
2655
2656 VersionedAssets::from(fees)
2657 .into_version(result_version)
2658 .map_err(|_| XcmPaymentApiError::VersionedConversionFailed)
2659 }
2660
2661 fn do_new_query(
2663 responder: impl Into<Location>,
2664 maybe_notify: Option<(u8, u8)>,
2665 timeout: BlockNumberFor<T>,
2666 match_querier: impl Into<Location>,
2667 ) -> u64 {
2668 QueryCounter::<T>::mutate(|q| {
2669 let r = *q;
2670 q.saturating_inc();
2671 Queries::<T>::insert(
2672 r,
2673 QueryStatus::Pending {
2674 responder: responder.into().into(),
2675 maybe_match_querier: Some(match_querier.into().into()),
2676 maybe_notify,
2677 timeout,
2678 },
2679 );
2680 r
2681 })
2682 }
2683
2684 pub fn report_outcome_notify(
2707 message: &mut Xcm<()>,
2708 responder: impl Into<Location>,
2709 notify: impl Into<<T as Config>::RuntimeCall>,
2710 timeout: BlockNumberFor<T>,
2711 ) -> Result<(), XcmError> {
2712 let responder = responder.into();
2713 let destination = T::UniversalLocation::get()
2714 .invert_target(&responder)
2715 .map_err(|()| XcmError::LocationNotInvertible)?;
2716 let notify: <T as Config>::RuntimeCall = notify.into();
2717 let max_weight = notify.get_dispatch_info().weight;
2718 let query_id = Self::new_notify_query(responder, notify, timeout, Here);
2719 let response_info = QueryResponseInfo { destination, query_id, max_weight };
2720 let report_error = Xcm(vec![ReportError(response_info)]);
2721 message.0.insert(0, SetAppendix(report_error));
2722 Ok(())
2723 }
2724
2725 pub fn new_notify_query(
2728 responder: impl Into<Location>,
2729 notify: impl Into<<T as Config>::RuntimeCall>,
2730 timeout: BlockNumberFor<T>,
2731 match_querier: impl Into<Location>,
2732 ) -> u64 {
2733 let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
2734 "decode input is output of Call encode; Call guaranteed to have two enums; qed",
2735 );
2736 Self::do_new_query(responder, Some(notify), timeout, match_querier)
2737 }
2738
2739 fn note_unknown_version(dest: &Location) {
2742 log::trace!(
2743 target: "xcm::pallet_xcm::note_unknown_version",
2744 "XCM version is unknown for destination: {:?}",
2745 dest,
2746 );
2747 let versioned_dest = VersionedLocation::from(dest.clone());
2748 VersionDiscoveryQueue::<T>::mutate(|q| {
2749 if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
2750 q[index].1.saturating_inc();
2752 } else {
2753 let _ = q.try_push((versioned_dest, 1));
2754 }
2755 });
2756 }
2757
2758 fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
2764 T::XcmExecutor::charge_fees(location.clone(), assets.clone())
2765 .map_err(|_| Error::<T>::FeesNotMet)?;
2766 Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
2767 Ok(())
2768 }
2769
2770 #[cfg(any(feature = "try-runtime", test))]
2780 pub fn do_try_state() -> Result<(), TryRuntimeError> {
2781 use migration::data::NeedsMigration;
2782
2783 let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
2787 {
2788 XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
2789 } else {
2790 XCM_VERSION.saturating_sub(1)
2791 };
2792
2793 ensure!(
2795 !Queries::<T>::iter_values()
2796 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
2797 TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
2798 );
2799
2800 ensure!(
2802 !LockedFungibles::<T>::iter_values()
2803 .any(|data| data.needs_migration(minimal_allowed_xcm_version)),
2804 TryRuntimeError::Other(
2805 "`LockedFungibles` data should be migrated to the higher xcm version!"
2806 )
2807 );
2808
2809 ensure!(
2811 !RemoteLockedFungibles::<T>::iter()
2812 .any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version) ||
2813 data.needs_migration(minimal_allowed_xcm_version)),
2814 TryRuntimeError::Other(
2815 "`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
2816 )
2817 );
2818
2819 if CurrentMigration::<T>::exists() {
2822 return Ok(())
2823 }
2824
2825 for v in 0..XCM_VERSION {
2827 ensure!(
2828 SupportedVersion::<T>::iter_prefix(v).next().is_none(),
2829 TryRuntimeError::Other(
2830 "`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
2831 )
2832 );
2833 ensure!(
2834 VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
2835 TryRuntimeError::Other(
2836 "`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
2837 )
2838 );
2839 ensure!(
2840 VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
2841 TryRuntimeError::Other(
2842 "`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
2843 )
2844 );
2845 }
2846
2847 Ok(())
2848 }
2849}
2850
2851pub struct LockTicket<T: Config> {
2852 sovereign_account: T::AccountId,
2853 amount: BalanceOf<T>,
2854 unlocker: Location,
2855 item_index: Option<usize>,
2856}
2857
2858impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
2859 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
2860 use xcm_executor::traits::LockError::UnexpectedState;
2861 let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
2862 match self.item_index {
2863 Some(index) => {
2864 ensure!(locks.len() > index, UnexpectedState);
2865 ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
2866 locks[index].0 = locks[index].0.max(self.amount);
2867 },
2868 None => {
2869 locks
2870 .try_push((self.amount, self.unlocker.into()))
2871 .map_err(|(_balance, _location)| UnexpectedState)?;
2872 },
2873 }
2874 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
2875 T::Currency::extend_lock(
2876 *b"py/xcmlk",
2877 &self.sovereign_account,
2878 self.amount,
2879 WithdrawReasons::all(),
2880 );
2881 Ok(())
2882 }
2883}
2884
2885pub struct UnlockTicket<T: Config> {
2886 sovereign_account: T::AccountId,
2887 amount: BalanceOf<T>,
2888 unlocker: Location,
2889}
2890
2891impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
2892 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
2893 use xcm_executor::traits::LockError::UnexpectedState;
2894 let mut locks =
2895 LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
2896 let mut maybe_remove_index = None;
2897 let mut locked = BalanceOf::<T>::zero();
2898 let mut found = false;
2899 for (i, x) in locks.iter_mut().enumerate() {
2902 if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
2903 x.0 = x.0.saturating_sub(self.amount);
2904 if x.0.is_zero() {
2905 maybe_remove_index = Some(i);
2906 }
2907 found = true;
2908 }
2909 locked = locked.max(x.0);
2910 }
2911 ensure!(found, UnexpectedState);
2912 if let Some(remove_index) = maybe_remove_index {
2913 locks.swap_remove(remove_index);
2914 }
2915 LockedFungibles::<T>::insert(&self.sovereign_account, locks);
2916 let reasons = WithdrawReasons::all();
2917 T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
2918 Ok(())
2919 }
2920}
2921
2922pub struct ReduceTicket<T: Config> {
2923 key: (u32, T::AccountId, VersionedAssetId),
2924 amount: u128,
2925 locker: VersionedLocation,
2926 owner: VersionedLocation,
2927}
2928
2929impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
2930 fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
2931 use xcm_executor::traits::LockError::UnexpectedState;
2932 let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
2933 ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
2934 let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
2935 ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
2936 if new_amount == 0 {
2937 RemoteLockedFungibles::<T>::remove(&self.key);
2938 } else {
2939 record.amount = new_amount;
2940 RemoteLockedFungibles::<T>::insert(&self.key, &record);
2941 }
2942 Ok(())
2943 }
2944}
2945
2946impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
2947 type LockTicket = LockTicket<T>;
2948 type UnlockTicket = UnlockTicket<T>;
2949 type ReduceTicket = ReduceTicket<T>;
2950
2951 fn prepare_lock(
2952 unlocker: Location,
2953 asset: Asset,
2954 owner: Location,
2955 ) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
2956 use xcm_executor::traits::LockError::*;
2957 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
2958 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
2959 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
2960 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
2961 let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
2962 ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
2963 Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
2964 }
2965
2966 fn prepare_unlock(
2967 unlocker: Location,
2968 asset: Asset,
2969 owner: Location,
2970 ) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
2971 use xcm_executor::traits::LockError::*;
2972 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
2973 let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
2974 ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
2975 let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
2976 let item_index =
2977 locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
2978 ensure!(locks[item_index].0 >= amount, NotLocked);
2979 Ok(UnlockTicket { sovereign_account, amount, unlocker })
2980 }
2981
2982 fn note_unlockable(
2983 locker: Location,
2984 asset: Asset,
2985 mut owner: Location,
2986 ) -> Result<(), xcm_executor::traits::LockError> {
2987 use xcm_executor::traits::LockError::*;
2988 ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
2989 let amount = match asset.fun {
2990 Fungible(a) => a,
2991 NonFungible(_) => return Err(Unimplemented),
2992 };
2993 owner.remove_network_id();
2994 let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
2995 let locker = locker.into();
2996 let owner = owner.into();
2997 let id: VersionedAssetId = asset.id.into();
2998 let key = (XCM_VERSION, account, id);
2999 let mut record =
3000 RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
3001 if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
3002 ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
3004 record.consumers = old.consumers;
3005 record.amount = record.amount.max(old.amount);
3006 }
3007 RemoteLockedFungibles::<T>::insert(&key, record);
3008 Ok(())
3009 }
3010
3011 fn prepare_reduce_unlockable(
3012 locker: Location,
3013 asset: Asset,
3014 mut owner: Location,
3015 ) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
3016 use xcm_executor::traits::LockError::*;
3017 let amount = match asset.fun {
3018 Fungible(a) => a,
3019 NonFungible(_) => return Err(Unimplemented),
3020 };
3021 owner.remove_network_id();
3022 let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3023 let locker = locker.into();
3024 let owner = owner.into();
3025 let id: VersionedAssetId = asset.id.into();
3026 let key = (XCM_VERSION, sovereign_account, id);
3027
3028 let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
3029 ensure!(locker == record.locker && owner == record.owner, WouldClobber);
3031 ensure!(record.amount >= amount, NotEnoughLocked);
3032 ensure!(
3033 record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
3034 InUse
3035 );
3036 Ok(ReduceTicket { key, amount, locker, owner })
3037 }
3038}
3039
3040impl<T: Config> WrapVersion for Pallet<T> {
3041 fn wrap_version<RuntimeCall>(
3042 dest: &Location,
3043 xcm: impl Into<VersionedXcm<RuntimeCall>>,
3044 ) -> Result<VersionedXcm<RuntimeCall>, ()> {
3045 Self::get_version_for(dest)
3046 .or_else(|| {
3047 Self::note_unknown_version(dest);
3048 SafeXcmVersion::<T>::get()
3049 })
3050 .ok_or_else(|| {
3051 log::trace!(
3052 target: "xcm::pallet_xcm::wrap_version",
3053 "Could not determine a version to wrap XCM for destination: {:?}",
3054 dest,
3055 );
3056 ()
3057 })
3058 .and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
3059 }
3060}
3061
3062impl<T: Config> GetVersion for Pallet<T> {
3063 fn get_version_for(dest: &Location) -> Option<XcmVersion> {
3064 SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
3065 }
3066}
3067
3068impl<T: Config> VersionChangeNotifier for Pallet<T> {
3069 fn start(
3078 dest: &Location,
3079 query_id: QueryId,
3080 max_weight: Weight,
3081 _context: &XcmContext,
3082 ) -> XcmResult {
3083 let versioned_dest = LatestVersionedLocation(dest);
3084 let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
3085 ensure!(!already, XcmError::InvalidLocation);
3086
3087 let xcm_version = T::AdvertisedXcmVersion::get();
3088 let response = Response::Version(xcm_version);
3089 let instruction = QueryResponse { query_id, response, max_weight, querier: None };
3090 let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3091 Self::deposit_event(Event::<T>::VersionNotifyStarted {
3092 destination: dest.clone(),
3093 cost,
3094 message_id,
3095 });
3096
3097 let value = (query_id, max_weight, xcm_version);
3098 VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
3099 Ok(())
3100 }
3101
3102 fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
3105 VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
3106 Ok(())
3107 }
3108
3109 fn is_subscribed(dest: &Location) -> bool {
3111 let versioned_dest = LatestVersionedLocation(dest);
3112 VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
3113 }
3114}
3115
3116impl<T: Config> DropAssets for Pallet<T> {
3117 fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
3118 if assets.is_empty() {
3119 return Weight::zero()
3120 }
3121 let versioned = VersionedAssets::from(Assets::from(assets));
3122 let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
3123 AssetTraps::<T>::mutate(hash, |n| *n += 1);
3124 Self::deposit_event(Event::AssetsTrapped {
3125 hash,
3126 origin: origin.clone(),
3127 assets: versioned,
3128 });
3129 Weight::zero()
3131 }
3132}
3133
3134impl<T: Config> ClaimAssets for Pallet<T> {
3135 fn claim_assets(
3136 origin: &Location,
3137 ticket: &Location,
3138 assets: &Assets,
3139 _context: &XcmContext,
3140 ) -> bool {
3141 let mut versioned = VersionedAssets::from(assets.clone());
3142 match ticket.unpack() {
3143 (0, [GeneralIndex(i)]) =>
3144 versioned = match versioned.into_version(*i as u32) {
3145 Ok(v) => v,
3146 Err(()) => return false,
3147 },
3148 (0, []) => (),
3149 _ => return false,
3150 };
3151 let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
3152 match AssetTraps::<T>::get(hash) {
3153 0 => return false,
3154 1 => AssetTraps::<T>::remove(hash),
3155 n => AssetTraps::<T>::insert(hash, n - 1),
3156 }
3157 Self::deposit_event(Event::AssetsClaimed {
3158 hash,
3159 origin: origin.clone(),
3160 assets: versioned,
3161 });
3162 return true
3163 }
3164}
3165
3166impl<T: Config> OnResponse for Pallet<T> {
3167 fn expecting_response(
3168 origin: &Location,
3169 query_id: QueryId,
3170 querier: Option<&Location>,
3171 ) -> bool {
3172 match Queries::<T>::get(query_id) {
3173 Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
3174 Location::try_from(responder).map_or(false, |r| origin == &r) &&
3175 maybe_match_querier.map_or(true, |match_querier| {
3176 Location::try_from(match_querier).map_or(false, |match_querier| {
3177 querier.map_or(false, |q| q == &match_querier)
3178 })
3179 }),
3180 Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
3181 Location::try_from(r).map_or(false, |r| origin == &r),
3182 _ => false,
3183 }
3184 }
3185
3186 fn on_response(
3187 origin: &Location,
3188 query_id: QueryId,
3189 querier: Option<&Location>,
3190 response: Response,
3191 max_weight: Weight,
3192 _context: &XcmContext,
3193 ) -> Weight {
3194 let origin = origin.clone();
3195 match (response, Queries::<T>::get(query_id)) {
3196 (
3197 Response::Version(v),
3198 Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
3199 ) => {
3200 let origin: Location = match expected_origin.try_into() {
3201 Ok(o) if o == origin => o,
3202 Ok(o) => {
3203 Self::deposit_event(Event::InvalidResponder {
3204 origin: origin.clone(),
3205 query_id,
3206 expected_location: Some(o),
3207 });
3208 return Weight::zero()
3209 },
3210 _ => {
3211 Self::deposit_event(Event::InvalidResponder {
3212 origin: origin.clone(),
3213 query_id,
3214 expected_location: None,
3215 });
3216 return Weight::zero()
3218 },
3219 };
3220 if !is_active {
3222 Queries::<T>::insert(
3223 query_id,
3224 QueryStatus::VersionNotifier {
3225 origin: origin.clone().into(),
3226 is_active: true,
3227 },
3228 );
3229 }
3230 SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
3232 Self::deposit_event(Event::SupportedVersionChanged {
3233 location: origin,
3234 version: v,
3235 });
3236 Weight::zero()
3237 },
3238 (
3239 response,
3240 Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
3241 ) => {
3242 if let Some(match_querier) = maybe_match_querier {
3243 let match_querier = match Location::try_from(match_querier) {
3244 Ok(mq) => mq,
3245 Err(_) => {
3246 Self::deposit_event(Event::InvalidQuerierVersion {
3247 origin: origin.clone(),
3248 query_id,
3249 });
3250 return Weight::zero()
3251 },
3252 };
3253 if querier.map_or(true, |q| q != &match_querier) {
3254 Self::deposit_event(Event::InvalidQuerier {
3255 origin: origin.clone(),
3256 query_id,
3257 expected_querier: match_querier,
3258 maybe_actual_querier: querier.cloned(),
3259 });
3260 return Weight::zero()
3261 }
3262 }
3263 let responder = match Location::try_from(responder) {
3264 Ok(r) => r,
3265 Err(_) => {
3266 Self::deposit_event(Event::InvalidResponderVersion {
3267 origin: origin.clone(),
3268 query_id,
3269 });
3270 return Weight::zero()
3271 },
3272 };
3273 if origin != responder {
3274 Self::deposit_event(Event::InvalidResponder {
3275 origin: origin.clone(),
3276 query_id,
3277 expected_location: Some(responder),
3278 });
3279 return Weight::zero()
3280 }
3281 match maybe_notify {
3282 Some((pallet_index, call_index)) => {
3283 let bare = (pallet_index, call_index, query_id, response);
3287 if let Ok(call) = bare.using_encoded(|mut bytes| {
3288 <T as Config>::RuntimeCall::decode(&mut bytes)
3289 }) {
3290 Queries::<T>::remove(query_id);
3291 let weight = call.get_dispatch_info().weight;
3292 if weight.any_gt(max_weight) {
3293 let e = Event::NotifyOverweight {
3294 query_id,
3295 pallet_index,
3296 call_index,
3297 actual_weight: weight,
3298 max_budgeted_weight: max_weight,
3299 };
3300 Self::deposit_event(e);
3301 return Weight::zero()
3302 }
3303 let dispatch_origin = Origin::Response(origin.clone()).into();
3304 match call.dispatch(dispatch_origin) {
3305 Ok(post_info) => {
3306 let e = Event::Notified { query_id, pallet_index, call_index };
3307 Self::deposit_event(e);
3308 post_info.actual_weight
3309 },
3310 Err(error_and_info) => {
3311 let e = Event::NotifyDispatchError {
3312 query_id,
3313 pallet_index,
3314 call_index,
3315 };
3316 Self::deposit_event(e);
3317 error_and_info.post_info.actual_weight
3320 },
3321 }
3322 .unwrap_or(weight)
3323 } else {
3324 let e =
3325 Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
3326 Self::deposit_event(e);
3327 Weight::zero()
3328 }
3329 },
3330 None => {
3331 let e = Event::ResponseReady { query_id, response: response.clone() };
3332 Self::deposit_event(e);
3333 let at = frame_system::Pallet::<T>::current_block_number();
3334 let response = response.into();
3335 Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
3336 Weight::zero()
3337 },
3338 }
3339 },
3340 _ => {
3341 let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
3342 Self::deposit_event(e);
3343 Weight::zero()
3344 },
3345 }
3346 }
3347}
3348
3349impl<T: Config> CheckSuspension for Pallet<T> {
3350 fn is_suspended<Call>(
3351 _origin: &Location,
3352 _instructions: &mut [Instruction<Call>],
3353 _max_weight: Weight,
3354 _properties: &mut Properties,
3355 ) -> bool {
3356 XcmExecutionSuspended::<T>::get()
3357 }
3358}
3359
3360impl<T: Config> RecordXcm for Pallet<T> {
3361 fn should_record() -> bool {
3362 ShouldRecordXcm::<T>::get()
3363 }
3364
3365 fn set_record_xcm(enabled: bool) {
3366 ShouldRecordXcm::<T>::put(enabled);
3367 }
3368
3369 fn recorded_xcm() -> Option<Xcm<()>> {
3370 RecordedXcm::<T>::get()
3371 }
3372
3373 fn record(xcm: Xcm<()>) {
3374 RecordedXcm::<T>::put(xcm);
3375 }
3376}
3377
3378pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
3382where
3383 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
3384{
3385 match o.into() {
3386 Ok(Origin::Xcm(location)) => Ok(location),
3387 _ => Err(BadOrigin),
3388 }
3389}
3390
3391pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
3395where
3396 OuterOrigin: Into<Result<Origin, OuterOrigin>>,
3397{
3398 match o.into() {
3399 Ok(Origin::Response(location)) => Ok(location),
3400 _ => Err(BadOrigin),
3401 }
3402}
3403
3404pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
3409impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
3410 for IsMajorityOfBody<Prefix, Body>
3411{
3412 fn contains(l: &Location) -> bool {
3413 let maybe_suffix = l.match_and_split(&Prefix::get());
3414 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
3415 }
3416}
3417
3418pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
3422impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
3423 fn contains(l: &Location) -> bool {
3424 let maybe_suffix = l.match_and_split(&Prefix::get());
3425 matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
3426 }
3427}
3428
3429pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
3432impl<
3433 O: OriginTrait + From<Origin>,
3434 F: Contains<L>,
3435 L: TryFrom<Location> + TryInto<Location> + Clone,
3436 > EnsureOrigin<O> for EnsureXcm<F, L>
3437where
3438 O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
3439{
3440 type Success = L;
3441
3442 fn try_origin(outer: O) -> Result<Self::Success, O> {
3443 outer.try_with_caller(|caller| {
3444 caller.try_into().and_then(|o| match o {
3445 Origin::Xcm(ref location)
3446 if F::contains(&location.clone().try_into().map_err(|_| o.clone().into())?) =>
3447 Ok(location.clone().try_into().map_err(|_| o.clone().into())?),
3448 Origin::Xcm(location) => Err(Origin::Xcm(location).into()),
3449 o => Err(o.into()),
3450 })
3451 })
3452 }
3453
3454 #[cfg(feature = "runtime-benchmarks")]
3455 fn try_successful_origin() -> Result<O, ()> {
3456 Ok(O::from(Origin::Xcm(Here.into())))
3457 }
3458}
3459
3460pub struct EnsureResponse<F>(PhantomData<F>);
3463impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
3464where
3465 O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
3466{
3467 type Success = Location;
3468
3469 fn try_origin(outer: O) -> Result<Self::Success, O> {
3470 outer.try_with_caller(|caller| {
3471 caller.try_into().and_then(|o| match o {
3472 Origin::Response(responder) => Ok(responder),
3473 o => Err(o.into()),
3474 })
3475 })
3476 }
3477
3478 #[cfg(feature = "runtime-benchmarks")]
3479 fn try_successful_origin() -> Result<O, ()> {
3480 Ok(O::from(Origin::Response(Here.into())))
3481 }
3482}
3483
3484pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
3487impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
3488 for XcmPassthrough<RuntimeOrigin>
3489{
3490 fn convert_origin(
3491 origin: impl Into<Location>,
3492 kind: OriginKind,
3493 ) -> Result<RuntimeOrigin, Location> {
3494 let origin = origin.into();
3495 match kind {
3496 OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
3497 _ => Err(origin),
3498 }
3499 }
3500}