pezpallet_xcm/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
2// This file is part of Pezkuwi.
3
4// Pezkuwi is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Pezkuwi is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Pezkuwi.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Pezpallet to handle XCM messages.
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21#[cfg(feature = "runtime-benchmarks")]
22pub mod benchmarking;
23#[cfg(test)]
24mod mock;
25#[cfg(test)]
26mod tests;
27mod transfer_assets_validation;
28
29pub mod migration;
30#[cfg(any(test, feature = "test-utils"))]
31pub mod xcm_helpers;
32
33extern crate alloc;
34
35use alloc::{boxed::Box, vec, vec::Vec};
36use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
37use core::{marker::PhantomData, result::Result};
38use pezframe_support::{
39	dispatch::{
40		DispatchErrorWithPostInfo, GetDispatchInfo, PostDispatchInfo, WithPostDispatchInfo,
41	},
42	pezpallet_prelude::*,
43	traits::{
44		Consideration, Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Footprint, Get,
45		LockableCurrency, OriginTrait, WithdrawReasons,
46	},
47	PalletId,
48};
49use pezframe_system::pezpallet_prelude::{BlockNumberFor, *};
50pub use pezpallet::*;
51use pezsp_core::H256;
52use pezsp_runtime::{
53	traits::{
54		AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash,
55		Saturating, Zero,
56	},
57	Either, RuntimeDebug, SaturatedConversion,
58};
59use scale_info::TypeInfo;
60use storage::{with_transaction, TransactionOutcome};
61use xcm::{latest::QueryResponseInfo, prelude::*};
62use xcm_builder::{
63	ExecuteController, ExecuteControllerWeightInfo, InspectMessageQueues, QueryController,
64	QueryControllerWeightInfo, SendController, SendControllerWeightInfo,
65};
66use xcm_executor::{
67	traits::{
68		AssetTransferError, CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin,
69		DropAssets, EventEmitter, FeeManager, FeeReason, MatchesFungible, OnResponse, Properties,
70		QueryHandler, QueryResponseStatus, RecordXcm, TransactAsset, TransferType,
71		VersionChangeNotifier, WeightBounds, XcmAssetTransfers,
72	},
73	AssetsInHolding,
74};
75use xcm_runtime_pezapis::{
76	authorized_aliases::{Error as AuthorizedAliasersApiError, OriginAliaser},
77	dry_run::{CallDryRunEffects, Error as XcmDryRunApiError, XcmDryRunEffects},
78	fees::Error as XcmPaymentApiError,
79	trusted_query::Error as TrustedQueryApiError,
80};
81
82mod errors;
83pub use errors::ExecutionError;
84
85#[cfg(any(feature = "try-runtime", test))]
86use pezsp_runtime::TryRuntimeError;
87
88pub trait WeightInfo {
89	fn send() -> Weight;
90	fn teleport_assets() -> Weight;
91	fn reserve_transfer_assets() -> Weight;
92	fn transfer_assets() -> Weight;
93	fn execute() -> Weight;
94	fn force_xcm_version() -> Weight;
95	fn force_default_xcm_version() -> Weight;
96	fn force_subscribe_version_notify() -> Weight;
97	fn force_unsubscribe_version_notify() -> Weight;
98	fn force_suspension() -> Weight;
99	fn migrate_supported_version() -> Weight;
100	fn migrate_version_notifiers() -> Weight;
101	fn already_notified_target() -> Weight;
102	fn notify_current_targets() -> Weight;
103	fn notify_target_migration_fail() -> Weight;
104	fn migrate_version_notify_targets() -> Weight;
105	fn migrate_and_notify_old_targets() -> Weight;
106	fn new_query() -> Weight;
107	fn take_response() -> Weight;
108	fn claim_assets() -> Weight;
109	fn add_authorized_alias() -> Weight;
110	fn remove_authorized_alias() -> Weight;
111
112	fn weigh_message() -> Weight;
113}
114
115/// fallback implementation
116pub struct TestWeightInfo;
117impl WeightInfo for TestWeightInfo {
118	fn send() -> Weight {
119		Weight::from_parts(100_000_000, 0)
120	}
121
122	fn teleport_assets() -> Weight {
123		Weight::from_parts(100_000_000, 0)
124	}
125
126	fn reserve_transfer_assets() -> Weight {
127		Weight::from_parts(100_000_000, 0)
128	}
129
130	fn transfer_assets() -> Weight {
131		Weight::from_parts(100_000_000, 0)
132	}
133
134	fn execute() -> Weight {
135		Weight::from_parts(100_000_000, 0)
136	}
137
138	fn force_xcm_version() -> Weight {
139		Weight::from_parts(100_000_000, 0)
140	}
141
142	fn force_default_xcm_version() -> Weight {
143		Weight::from_parts(100_000_000, 0)
144	}
145
146	fn force_subscribe_version_notify() -> Weight {
147		Weight::from_parts(100_000_000, 0)
148	}
149
150	fn force_unsubscribe_version_notify() -> Weight {
151		Weight::from_parts(100_000_000, 0)
152	}
153
154	fn force_suspension() -> Weight {
155		Weight::from_parts(100_000_000, 0)
156	}
157
158	fn migrate_supported_version() -> Weight {
159		Weight::from_parts(100_000_000, 0)
160	}
161
162	fn migrate_version_notifiers() -> Weight {
163		Weight::from_parts(100_000_000, 0)
164	}
165
166	fn already_notified_target() -> Weight {
167		Weight::from_parts(100_000_000, 0)
168	}
169
170	fn notify_current_targets() -> Weight {
171		Weight::from_parts(100_000_000, 0)
172	}
173
174	fn notify_target_migration_fail() -> Weight {
175		Weight::from_parts(100_000_000, 0)
176	}
177
178	fn migrate_version_notify_targets() -> Weight {
179		Weight::from_parts(100_000_000, 0)
180	}
181
182	fn migrate_and_notify_old_targets() -> Weight {
183		Weight::from_parts(100_000_000, 0)
184	}
185
186	fn new_query() -> Weight {
187		Weight::from_parts(100_000_000, 0)
188	}
189
190	fn take_response() -> Weight {
191		Weight::from_parts(100_000_000, 0)
192	}
193
194	fn claim_assets() -> Weight {
195		Weight::from_parts(100_000_000, 0)
196	}
197
198	fn add_authorized_alias() -> Weight {
199		Weight::from_parts(100_000, 0)
200	}
201
202	fn remove_authorized_alias() -> Weight {
203		Weight::from_parts(100_000, 0)
204	}
205
206	fn weigh_message() -> Weight {
207		Weight::from_parts(100_000, 0)
208	}
209}
210
211#[derive(Clone, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
212pub struct AuthorizedAliasesEntry<Ticket, MAX: Get<u32>> {
213	pub aliasers: BoundedVec<OriginAliaser, MAX>,
214	pub ticket: Ticket,
215}
216
217pub fn aliasers_footprint(aliasers_count: usize) -> Footprint {
218	Footprint::from_parts(aliasers_count, OriginAliaser::max_encoded_len())
219}
220
221#[pezframe_support::pezpallet]
222pub mod pezpallet {
223	use super::*;
224	use pezframe_support::{
225		dispatch::{GetDispatchInfo, PostDispatchInfo},
226		parameter_types,
227	};
228	use pezframe_system::Config as SysConfig;
229	use pezsp_runtime::traits::Dispatchable;
230	use xcm_executor::traits::{MatchesFungible, WeightBounds};
231
232	parameter_types! {
233		/// An implementation of `Get<u32>` which just returns the latest XCM version which we can
234		/// support.
235		pub const CurrentXcmVersion: u32 = XCM_VERSION;
236
237		#[derive(Debug, TypeInfo)]
238		/// The maximum number of distinct locations allowed as authorized aliases for a local origin.
239		pub const MaxAuthorizedAliases: u32 = 10;
240	}
241
242	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
243
244	#[pezpallet::pezpallet]
245	#[pezpallet::storage_version(STORAGE_VERSION)]
246	#[pezpallet::without_storage_info]
247	pub struct Pezpallet<T>(_);
248
249	pub type BalanceOf<T> =
250		<<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
251	pub type TicketOf<T> = <T as Config>::AuthorizedAliasConsideration;
252
253	#[pezpallet::config]
254	/// The module configuration trait.
255	pub trait Config: pezframe_system::Config {
256		/// The overarching event type.
257		#[allow(deprecated)]
258		type RuntimeEvent: From<Event<Self>>
259			+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
260
261		/// A lockable currency.
262		// TODO: We should really use a trait which can handle multiple currencies.
263		type Currency: LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>;
264
265		/// The `Asset` matcher for `Currency`.
266		type CurrencyMatcher: MatchesFungible<BalanceOf<Self>>;
267
268		/// A means of providing some cost while Authorized Aliasers data is stored on-chain.
269		type AuthorizedAliasConsideration: Consideration<Self::AccountId, Footprint>;
270
271		/// Required origin for sending XCM messages. If successful, it resolves to `Location`
272		/// which exists as an interior location within this chain's XCM context.
273		type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
274
275		/// The type used to actually dispatch an XCM to its destination.
276		type XcmRouter: SendXcm;
277
278		/// Required origin for executing XCM messages, including the teleport functionality. If
279		/// successful, then it resolves to `Location` which exists as an interior location
280		/// within this chain's XCM context.
281		type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
282
283		/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
284		type XcmExecuteFilter: Contains<(Location, Xcm<<Self as Config>::RuntimeCall>)>;
285
286		/// Something to execute an XCM message.
287		type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall> + XcmAssetTransfers + FeeManager;
288
289		/// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass.
290		type XcmTeleportFilter: Contains<(Location, Vec<Asset>)>;
291
292		/// Our XCM filter which messages to be reserve-transferred using the dedicated extrinsic
293		/// must pass.
294		type XcmReserveTransferFilter: Contains<(Location, Vec<Asset>)>;
295
296		/// Means of measuring the weight consumed by an XCM message locally.
297		type Weigher: WeightBounds<<Self as Config>::RuntimeCall>;
298
299		/// This chain's Universal Location.
300		#[pezpallet::constant]
301		type UniversalLocation: Get<InteriorLocation>;
302
303		/// The runtime `Origin` type.
304		type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
305
306		/// The runtime `Call` type.
307		type RuntimeCall: Parameter
308			+ GetDispatchInfo
309			+ Dispatchable<
310				RuntimeOrigin = <Self as Config>::RuntimeOrigin,
311				PostInfo = PostDispatchInfo,
312			>;
313
314		const VERSION_DISCOVERY_QUEUE_SIZE: u32;
315
316		/// The latest supported version that we advertise. Generally just set it to
317		/// `pezpallet_xcm::CurrentXcmVersion`.
318		#[pezpallet::constant]
319		type AdvertisedXcmVersion: Get<XcmVersion>;
320
321		/// The origin that is allowed to call privileged operations on the XCM pezpallet
322		type AdminOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin>;
323
324		/// The assets which we consider a given origin is trusted if they claim to have placed a
325		/// lock.
326		type TrustedLockers: ContainsPair<Location, Asset>;
327
328		/// How to get an `AccountId` value from a `Location`, useful for handling asset locks.
329		type SovereignAccountOf: ConvertLocation<Self::AccountId>;
330
331		/// The maximum number of local XCM locks that a single account may have.
332		#[pezpallet::constant]
333		type MaxLockers: Get<u32>;
334
335		/// The maximum number of consumers a single remote lock may have.
336		#[pezpallet::constant]
337		type MaxRemoteLockConsumers: Get<u32>;
338
339		/// The ID type for local consumers of remote locks.
340		type RemoteLockConsumerIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
341
342		/// Weight information for extrinsics in this pezpallet.
343		type WeightInfo: WeightInfo;
344	}
345
346	impl<T: Config> ExecuteControllerWeightInfo for Pezpallet<T> {
347		fn execute() -> Weight {
348			T::WeightInfo::execute()
349		}
350	}
351
352	impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pezpallet<T> {
353		type WeightInfo = Self;
354		fn execute(
355			origin: OriginFor<T>,
356			message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
357			max_weight: Weight,
358		) -> Result<Weight, DispatchErrorWithPostInfo> {
359			tracing::trace!(target: "xcm::pezpallet_xcm::execute", ?message, ?max_weight);
360			let outcome = (|| {
361				let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
362				let mut hash = message.using_encoded(pezsp_io::hashing::blake2_256);
363				let message = (*message).try_into().map_err(|()| {
364					tracing::debug!(
365						target: "xcm::pezpallet_xcm::execute", id=?hash,
366						"Failed to convert VersionedXcm to Xcm",
367					);
368					Error::<T>::BadVersion
369				})?;
370				let value = (origin_location, message);
371				ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
372				let (origin_location, message) = value;
373				Ok(T::XcmExecutor::prepare_and_execute(
374					origin_location,
375					message,
376					&mut hash,
377					max_weight,
378					max_weight,
379				))
380			})()
381			.map_err(|e: DispatchError| {
382				tracing::debug!(
383					target: "xcm::pezpallet_xcm::execute", error=?e,
384					"Failed XCM pre-execution validation or filter",
385				);
386				e.with_weight(<Self::WeightInfo as ExecuteControllerWeightInfo>::execute())
387			})?;
388
389			Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
390			let weight_used = outcome.weight_used();
391			outcome.ensure_complete().map_err(|error| {
392				tracing::error!(target: "xcm::pezpallet_xcm::execute", ?error, "XCM execution failed with error");
393				Error::<T>::LocalExecutionIncompleteWithError {
394					index: error.index,
395					error: error.error.into(),
396				}
397				.with_weight(
398					weight_used.saturating_add(
399						<Self::WeightInfo as ExecuteControllerWeightInfo>::execute(),
400					),
401				)
402			})?;
403			Ok(weight_used)
404		}
405	}
406
407	impl<T: Config> SendControllerWeightInfo for Pezpallet<T> {
408		fn send() -> Weight {
409			T::WeightInfo::send()
410		}
411	}
412
413	impl<T: Config> SendController<OriginFor<T>> for Pezpallet<T> {
414		type WeightInfo = Self;
415		fn send(
416			origin: OriginFor<T>,
417			dest: Box<VersionedLocation>,
418			message: Box<VersionedXcm<()>>,
419		) -> Result<XcmHash, DispatchError> {
420			let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
421			let interior: Junctions = origin_location.clone().try_into().map_err(|_| {
422				tracing::debug!(
423					target: "xcm::pezpallet_xcm::send",
424					"Failed to convert origin_location to interior Junctions",
425				);
426				Error::<T>::InvalidOrigin
427			})?;
428			let dest = Location::try_from(*dest).map_err(|()| {
429				tracing::debug!(
430					target: "xcm::pezpallet_xcm::send",
431					"Failed to convert destination VersionedLocation to Location",
432				);
433				Error::<T>::BadVersion
434			})?;
435			let message: Xcm<()> = (*message).try_into().map_err(|()| {
436				tracing::debug!(
437					target: "xcm::pezpallet_xcm::send",
438					"Failed to convert VersionedXcm message to Xcm",
439				);
440				Error::<T>::BadVersion
441			})?;
442
443			let message_id = Self::send_xcm(interior, dest.clone(), message.clone())
444				.map_err(|error| {
445					tracing::error!(target: "xcm::pezpallet_xcm::send", ?error, ?dest, ?message, "XCM send failed with error");
446					Error::<T>::from(error)
447				})?;
448			let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
449			Self::deposit_event(e);
450			Ok(message_id)
451		}
452	}
453
454	impl<T: Config> QueryControllerWeightInfo for Pezpallet<T> {
455		fn query() -> Weight {
456			T::WeightInfo::new_query()
457		}
458		fn take_response() -> Weight {
459			T::WeightInfo::take_response()
460		}
461	}
462
463	impl<T: Config> QueryController<OriginFor<T>, BlockNumberFor<T>> for Pezpallet<T> {
464		type WeightInfo = Self;
465
466		fn query(
467			origin: OriginFor<T>,
468			timeout: BlockNumberFor<T>,
469			match_querier: VersionedLocation,
470		) -> Result<QueryId, DispatchError> {
471			let responder = <T as Config>::ExecuteXcmOrigin::ensure_origin(origin)?;
472			let query_id = <Self as QueryHandler>::new_query(
473				responder,
474				timeout,
475				Location::try_from(match_querier).map_err(|_| {
476					tracing::debug!(
477						target: "xcm::pezpallet_xcm::query",
478						"Failed to convert VersionedLocation for match_querier",
479					);
480					Into::<DispatchError>::into(Error::<T>::BadVersion)
481				})?,
482			);
483
484			Ok(query_id)
485		}
486	}
487
488	impl<T: Config> EventEmitter for Pezpallet<T> {
489		fn emit_sent_event(
490			origin: Location,
491			destination: Location,
492			message: Option<Xcm<()>>,
493			message_id: XcmHash,
494		) {
495			Self::deposit_event(Event::Sent {
496				origin,
497				destination,
498				message: message.unwrap_or_default(),
499				message_id,
500			});
501		}
502
503		fn emit_send_failure_event(
504			origin: Location,
505			destination: Location,
506			error: SendError,
507			message_id: XcmHash,
508		) {
509			Self::deposit_event(Event::SendFailed { origin, destination, error, message_id });
510		}
511
512		fn emit_process_failure_event(origin: Location, error: XcmError, message_id: XcmHash) {
513			Self::deposit_event(Event::ProcessXcmError { origin, error, message_id });
514		}
515	}
516
517	#[pezpallet::event]
518	#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
519	pub enum Event<T: Config> {
520		/// Execution of an XCM message was attempted.
521		Attempted { outcome: xcm::latest::Outcome },
522		/// An XCM message was sent.
523		Sent { origin: Location, destination: Location, message: Xcm<()>, message_id: XcmHash },
524		/// An XCM message failed to send.
525		SendFailed {
526			origin: Location,
527			destination: Location,
528			error: SendError,
529			message_id: XcmHash,
530		},
531		/// An XCM message failed to process.
532		ProcessXcmError { origin: Location, error: XcmError, message_id: XcmHash },
533		/// Query response received which does not match a registered query. This may be because a
534		/// matching query was never registered, it may be because it is a duplicate response, or
535		/// because the query timed out.
536		UnexpectedResponse { origin: Location, query_id: QueryId },
537		/// Query response has been received and is ready for taking with `take_response`. There is
538		/// no registered notification call.
539		ResponseReady { query_id: QueryId, response: Response },
540		/// Query response has been received and query is removed. The registered notification has
541		/// been dispatched and executed successfully.
542		Notified { query_id: QueryId, pezpallet_index: u8, call_index: u8 },
543		/// Query response has been received and query is removed. The registered notification
544		/// could not be dispatched because the dispatch weight is greater than the maximum weight
545		/// originally budgeted by this runtime for the query result.
546		NotifyOverweight {
547			query_id: QueryId,
548			pezpallet_index: u8,
549			call_index: u8,
550			actual_weight: Weight,
551			max_budgeted_weight: Weight,
552		},
553		/// Query response has been received and query is removed. There was a general error with
554		/// dispatching the notification call.
555		NotifyDispatchError { query_id: QueryId, pezpallet_index: u8, call_index: u8 },
556		/// Query response has been received and query is removed. The dispatch was unable to be
557		/// decoded into a `Call`; this might be due to dispatch function having a signature which
558		/// is not `(origin, QueryId, Response)`.
559		NotifyDecodeFailed { query_id: QueryId, pezpallet_index: u8, call_index: u8 },
560		/// Expected query response has been received but the origin location of the response does
561		/// not match that expected. The query remains registered for a later, valid, response to
562		/// be received and acted upon.
563		InvalidResponder {
564			origin: Location,
565			query_id: QueryId,
566			expected_location: Option<Location>,
567		},
568		/// Expected query response has been received but the expected origin location placed in
569		/// storage by this runtime previously cannot be decoded. The query remains registered.
570		///
571		/// This is unexpected (since a location placed in storage in a previously executing
572		/// runtime should be readable prior to query timeout) and dangerous since the possibly
573		/// valid response will be dropped. Manual governance intervention is probably going to be
574		/// needed.
575		InvalidResponderVersion { origin: Location, query_id: QueryId },
576		/// Received query response has been read and removed.
577		ResponseTaken { query_id: QueryId },
578		/// Some assets have been placed in an asset trap.
579		AssetsTrapped { hash: H256, origin: Location, assets: VersionedAssets },
580		/// An XCM version change notification message has been attempted to be sent.
581		///
582		/// The cost of sending it (borne by the chain) is included.
583		VersionChangeNotified {
584			destination: Location,
585			result: XcmVersion,
586			cost: Assets,
587			message_id: XcmHash,
588		},
589		/// The supported version of a location has been changed. This might be through an
590		/// automatic notification or a manual intervention.
591		SupportedVersionChanged { location: Location, version: XcmVersion },
592		/// A given location which had a version change subscription was dropped owing to an error
593		/// sending the notification to it.
594		NotifyTargetSendFail { location: Location, query_id: QueryId, error: XcmError },
595		/// A given location which had a version change subscription was dropped owing to an error
596		/// migrating the location to our new XCM format.
597		NotifyTargetMigrationFail { location: VersionedLocation, query_id: QueryId },
598		/// Expected query response has been received but the expected querier location placed in
599		/// storage by this runtime previously cannot be decoded. The query remains registered.
600		///
601		/// This is unexpected (since a location placed in storage in a previously executing
602		/// runtime should be readable prior to query timeout) and dangerous since the possibly
603		/// valid response will be dropped. Manual governance intervention is probably going to be
604		/// needed.
605		InvalidQuerierVersion { origin: Location, query_id: QueryId },
606		/// Expected query response has been received but the querier location of the response does
607		/// not match the expected. The query remains registered for a later, valid, response to
608		/// be received and acted upon.
609		InvalidQuerier {
610			origin: Location,
611			query_id: QueryId,
612			expected_querier: Location,
613			maybe_actual_querier: Option<Location>,
614		},
615		/// A remote has requested XCM version change notification from us and we have honored it.
616		/// A version information message is sent to them and its cost is included.
617		VersionNotifyStarted { destination: Location, cost: Assets, message_id: XcmHash },
618		/// We have requested that a remote chain send us XCM version change notifications.
619		VersionNotifyRequested { destination: Location, cost: Assets, message_id: XcmHash },
620		/// We have requested that a remote chain stops sending us XCM version change
621		/// notifications.
622		VersionNotifyUnrequested { destination: Location, cost: Assets, message_id: XcmHash },
623		/// Fees were paid from a location for an operation (often for using `SendXcm`).
624		FeesPaid { paying: Location, fees: Assets },
625		/// Some assets have been claimed from an asset trap
626		AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets },
627		/// A XCM version migration finished.
628		VersionMigrationFinished { version: XcmVersion },
629		/// An `aliaser` location was authorized by `target` to alias it, authorization valid until
630		/// `expiry` block number.
631		AliasAuthorized { aliaser: Location, target: Location, expiry: Option<u64> },
632		/// `target` removed alias authorization for `aliaser`.
633		AliasAuthorizationRemoved { aliaser: Location, target: Location },
634		/// `target` removed all alias authorizations.
635		AliasesAuthorizationsRemoved { target: Location },
636	}
637
638	#[pezpallet::origin]
639	#[derive(
640		PartialEq,
641		Eq,
642		Clone,
643		Encode,
644		Decode,
645		DecodeWithMemTracking,
646		RuntimeDebug,
647		TypeInfo,
648		MaxEncodedLen,
649	)]
650	pub enum Origin {
651		/// It comes from somewhere in the XCM space wanting to transact.
652		Xcm(Location),
653		/// It comes as an expected response from an XCM location.
654		Response(Location),
655	}
656	impl From<Location> for Origin {
657		fn from(location: Location) -> Origin {
658			Origin::Xcm(location)
659		}
660	}
661
662	/// A reason for this pezpallet placing a hold on funds.
663	#[pezpallet::composite_enum]
664	pub enum HoldReason {
665		/// The funds are held as storage deposit for an authorized alias.
666		AuthorizeAlias,
667	}
668
669	#[pezpallet::error]
670	pub enum Error<T> {
671		/// The desired destination was unreachable, generally because there is a no way of routing
672		/// to it.
673		Unreachable,
674		/// There was some other issue (i.e. not to do with routing) in sending the message.
675		/// Perhaps a lack of space for buffering the message.
676		SendFailure,
677		/// The message execution fails the filter.
678		Filtered,
679		/// The message's weight could not be determined.
680		UnweighableMessage,
681		/// The destination `Location` provided cannot be inverted.
682		DestinationNotInvertible,
683		/// The assets to be sent are empty.
684		Empty,
685		/// Could not re-anchor the assets to declare the fees for the destination chain.
686		CannotReanchor,
687		/// Too many assets have been attempted for transfer.
688		TooManyAssets,
689		/// Origin is invalid for sending.
690		InvalidOrigin,
691		/// The version of the `Versioned` value used is not able to be interpreted.
692		BadVersion,
693		/// The given location could not be used (e.g. because it cannot be expressed in the
694		/// desired version of XCM).
695		BadLocation,
696		/// The referenced subscription could not be found.
697		NoSubscription,
698		/// The location is invalid since it already has a subscription from us.
699		AlreadySubscribed,
700		/// Could not check-out the assets for teleportation to the destination chain.
701		CannotCheckOutTeleport,
702		/// The owner does not own (all) of the asset that they wish to do the operation on.
703		LowBalance,
704		/// The asset owner has too many locks on the asset.
705		TooManyLocks,
706		/// The given account is not an identifiable sovereign account for any location.
707		AccountNotSovereign,
708		/// The operation required fees to be paid which the initiator could not meet.
709		FeesNotMet,
710		/// A remote lock with the corresponding data could not be found.
711		LockNotFound,
712		/// The unlock operation cannot succeed because there are still consumers of the lock.
713		InUse,
714		/// Invalid asset, reserve chain could not be determined for it.
715		#[codec(index = 21)]
716		InvalidAssetUnknownReserve,
717		/// Invalid asset, do not support remote asset reserves with different fees reserves.
718		#[codec(index = 22)]
719		InvalidAssetUnsupportedReserve,
720		/// Too many assets with different reserve locations have been attempted for transfer.
721		#[codec(index = 23)]
722		TooManyReserves,
723		/// Local XCM execution incomplete.
724		#[deprecated(since = "20.0.0", note = "Use `LocalExecutionIncompleteWithError` instead")]
725		#[codec(index = 24)]
726		LocalExecutionIncomplete,
727		/// Too many locations authorized to alias origin.
728		#[codec(index = 25)]
729		TooManyAuthorizedAliases,
730		/// Expiry block number is in the past.
731		#[codec(index = 26)]
732		ExpiresInPast,
733		/// The alias to remove authorization for was not found.
734		#[codec(index = 27)]
735		AliasNotFound,
736		/// Local XCM execution incomplete with the actual XCM error and the index of the
737		/// instruction that caused the error.
738		#[codec(index = 28)]
739		LocalExecutionIncompleteWithError { index: InstructionIndex, error: ExecutionError },
740	}
741
742	impl<T: Config> From<SendError> for Error<T> {
743		fn from(e: SendError) -> Self {
744			match e {
745				SendError::Fees => Error::<T>::FeesNotMet,
746				SendError::NotApplicable => Error::<T>::Unreachable,
747				_ => Error::<T>::SendFailure,
748			}
749		}
750	}
751
752	impl<T: Config> From<AssetTransferError> for Error<T> {
753		fn from(e: AssetTransferError) -> Self {
754			match e {
755				AssetTransferError::UnknownReserve => Error::<T>::InvalidAssetUnknownReserve,
756			}
757		}
758	}
759
760	/// The status of a query.
761	#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
762	pub enum QueryStatus<BlockNumber> {
763		/// The query was sent but no response has yet been received.
764		Pending {
765			/// The `QueryResponse` XCM must have this origin to be considered a reply for this
766			/// query.
767			responder: VersionedLocation,
768			/// The `QueryResponse` XCM must have this value as the `querier` field to be
769			/// considered a reply for this query. If `None` then the querier is ignored.
770			maybe_match_querier: Option<VersionedLocation>,
771			maybe_notify: Option<(u8, u8)>,
772			timeout: BlockNumber,
773		},
774		/// The query is for an ongoing version notification subscription.
775		VersionNotifier { origin: VersionedLocation, is_active: bool },
776		/// A response has been received.
777		Ready { response: VersionedResponse, at: BlockNumber },
778	}
779
780	#[derive(Copy, Clone)]
781	pub(crate) struct LatestVersionedLocation<'a>(pub(crate) &'a Location);
782	impl<'a> EncodeLike<VersionedLocation> for LatestVersionedLocation<'a> {}
783	impl<'a> Encode for LatestVersionedLocation<'a> {
784		fn encode(&self) -> Vec<u8> {
785			let mut r = VersionedLocation::from(Location::default()).encode();
786			r.truncate(1);
787			self.0.using_encoded(|d| r.extend_from_slice(d));
788			r
789		}
790	}
791
792	#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
793	pub enum VersionMigrationStage {
794		MigrateSupportedVersion,
795		MigrateVersionNotifiers,
796		NotifyCurrentTargets(Option<Vec<u8>>),
797		MigrateAndNotifyOldTargets,
798	}
799
800	impl Default for VersionMigrationStage {
801		fn default() -> Self {
802			Self::MigrateSupportedVersion
803		}
804	}
805
806	/// The latest available query index.
807	#[pezpallet::storage]
808	pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;
809
810	/// The ongoing queries.
811	#[pezpallet::storage]
812	pub(super) type Queries<T: Config> =
813		StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<BlockNumberFor<T>>, OptionQuery>;
814
815	/// The existing asset traps.
816	///
817	/// Key is the blake2 256 hash of (origin, versioned `Assets`) pair. Value is the number of
818	/// times this pair has been trapped (usually just 1 if it exists at all).
819	#[pezpallet::storage]
820	pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;
821
822	/// Default version to encode XCM when latest version of destination is unknown. If `None`,
823	/// then the destinations whose XCM version is unknown are considered unreachable.
824	#[pezpallet::storage]
825	#[pezpallet::whitelist_storage]
826	pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
827
828	/// The Latest versions that we know various locations support.
829	#[pezpallet::storage]
830	pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
831		_,
832		Twox64Concat,
833		XcmVersion,
834		Blake2_128Concat,
835		VersionedLocation,
836		XcmVersion,
837		OptionQuery,
838	>;
839
840	/// All locations that we have requested version notifications from.
841	#[pezpallet::storage]
842	pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
843		_,
844		Twox64Concat,
845		XcmVersion,
846		Blake2_128Concat,
847		VersionedLocation,
848		QueryId,
849		OptionQuery,
850	>;
851
852	/// The target locations that are subscribed to our version changes, as well as the most recent
853	/// of our versions we informed them of.
854	#[pezpallet::storage]
855	pub(super) type VersionNotifyTargets<T: Config> = StorageDoubleMap<
856		_,
857		Twox64Concat,
858		XcmVersion,
859		Blake2_128Concat,
860		VersionedLocation,
861		(QueryId, Weight, XcmVersion),
862		OptionQuery,
863	>;
864
865	pub struct VersionDiscoveryQueueSize<T>(PhantomData<T>);
866	impl<T: Config> Get<u32> for VersionDiscoveryQueueSize<T> {
867		fn get() -> u32 {
868			T::VERSION_DISCOVERY_QUEUE_SIZE
869		}
870	}
871
872	/// Destinations whose latest XCM version we would like to know. Duplicates not allowed, and
873	/// the `u32` counter is the number of times that a send to the destination has been attempted,
874	/// which is used as a prioritization.
875	#[pezpallet::storage]
876	#[pezpallet::whitelist_storage]
877	pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
878		_,
879		BoundedVec<(VersionedLocation, u32), VersionDiscoveryQueueSize<T>>,
880		ValueQuery,
881	>;
882
883	/// The current migration's stage, if any.
884	#[pezpallet::storage]
885	pub(super) type CurrentMigration<T: Config> =
886		StorageValue<_, VersionMigrationStage, OptionQuery>;
887
888	#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
889	#[scale_info(skip_type_params(MaxConsumers))]
890	pub struct RemoteLockedFungibleRecord<ConsumerIdentifier, MaxConsumers: Get<u32>> {
891		/// Total amount of the asset held by the remote lock.
892		pub amount: u128,
893		/// The owner of the locked asset.
894		pub owner: VersionedLocation,
895		/// The location which holds the original lock.
896		pub locker: VersionedLocation,
897		/// Local consumers of the remote lock with a consumer identifier and the amount
898		/// of fungible asset every consumer holds.
899		/// Every consumer can hold up to total amount of the remote lock.
900		pub consumers: BoundedVec<(ConsumerIdentifier, u128), MaxConsumers>,
901	}
902
903	impl<LockId, MaxConsumers: Get<u32>> RemoteLockedFungibleRecord<LockId, MaxConsumers> {
904		/// Amount of the remote lock in use by consumers.
905		/// Returns `None` if the remote lock has no consumers.
906		pub fn amount_held(&self) -> Option<u128> {
907			self.consumers.iter().max_by(|x, y| x.1.cmp(&y.1)).map(|max| max.1)
908		}
909	}
910
911	/// Fungible assets which we know are locked on a remote chain.
912	#[pezpallet::storage]
913	pub(super) type RemoteLockedFungibles<T: Config> = StorageNMap<
914		_,
915		(
916			NMapKey<Twox64Concat, XcmVersion>,
917			NMapKey<Blake2_128Concat, T::AccountId>,
918			NMapKey<Blake2_128Concat, VersionedAssetId>,
919		),
920		RemoteLockedFungibleRecord<T::RemoteLockConsumerIdentifier, T::MaxRemoteLockConsumers>,
921		OptionQuery,
922	>;
923
924	/// Fungible assets which we know are locked on this chain.
925	#[pezpallet::storage]
926	pub(super) type LockedFungibles<T: Config> = StorageMap<
927		_,
928		Blake2_128Concat,
929		T::AccountId,
930		BoundedVec<(BalanceOf<T>, VersionedLocation), T::MaxLockers>,
931		OptionQuery,
932	>;
933
934	/// Global suspension state of the XCM executor.
935	#[pezpallet::storage]
936	pub(super) type XcmExecutionSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
937
938	/// Whether or not incoming XCMs (both executed locally and received) should be recorded.
939	/// Only one XCM program will be recorded at a time.
940	/// This is meant to be used in runtime APIs, and it's advised it stays false
941	/// for all other use cases, so as to not degrade regular performance.
942	///
943	/// Only relevant if this pezpallet is being used as the [`xcm_executor::traits::RecordXcm`]
944	/// implementation in the XCM executor configuration.
945	#[pezpallet::storage]
946	pub(crate) type ShouldRecordXcm<T: Config> = StorageValue<_, bool, ValueQuery>;
947
948	/// If [`ShouldRecordXcm`] is set to true, then the last XCM program executed locally
949	/// will be stored here.
950	/// Runtime APIs can fetch the XCM that was executed by accessing this value.
951	///
952	/// Only relevant if this pezpallet is being used as the [`xcm_executor::traits::RecordXcm`]
953	/// implementation in the XCM executor configuration.
954	#[pezpallet::storage]
955	pub(crate) type RecordedXcm<T: Config> = StorageValue<_, Xcm<()>>;
956
957	/// Map of authorized aliasers of local origins. Each local location can authorize a list of
958	/// other locations to alias into it. Each aliaser is only valid until its inner `expiry`
959	/// block number.
960	#[pezpallet::storage]
961	pub(super) type AuthorizedAliases<T: Config> = StorageMap<
962		_,
963		Blake2_128Concat,
964		VersionedLocation,
965		AuthorizedAliasesEntry<TicketOf<T>, MaxAuthorizedAliases>,
966		OptionQuery,
967	>;
968
969	#[pezpallet::genesis_config]
970	pub struct GenesisConfig<T: Config> {
971		#[serde(skip)]
972		pub _config: core::marker::PhantomData<T>,
973		/// The default version to encode outgoing XCM messages with.
974		pub safe_xcm_version: Option<XcmVersion>,
975		/// The default versioned locations to support at genesis.
976		pub supported_version: Vec<(Location, XcmVersion)>,
977	}
978
979	impl<T: Config> Default for GenesisConfig<T> {
980		fn default() -> Self {
981			Self {
982				_config: Default::default(),
983				safe_xcm_version: Some(XCM_VERSION),
984				supported_version: Vec::new(),
985			}
986		}
987	}
988
989	#[pezpallet::genesis_build]
990	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
991		fn build(&self) {
992			SafeXcmVersion::<T>::set(self.safe_xcm_version);
993			// Set versioned locations to support at genesis.
994			self.supported_version.iter().for_each(|(location, version)| {
995				SupportedVersion::<T>::insert(
996					XCM_VERSION,
997					LatestVersionedLocation(location),
998					version,
999				);
1000			});
1001		}
1002	}
1003
1004	#[pezpallet::hooks]
1005	impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
1006		fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
1007			let mut weight_used = Weight::zero();
1008			if let Some(migration) = CurrentMigration::<T>::get() {
1009				// Consume 10% of block at most
1010				let max_weight = T::BlockWeights::get().max_block / 10;
1011				let (w, maybe_migration) = Self::lazy_migration(migration, max_weight);
1012				if maybe_migration.is_none() {
1013					Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION });
1014				}
1015				CurrentMigration::<T>::set(maybe_migration);
1016				weight_used.saturating_accrue(w);
1017			}
1018
1019			// Here we aim to get one successful version negotiation request sent per block, ordered
1020			// by the destinations being most sent to.
1021			let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
1022			// TODO: correct weights.
1023			weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
1024			q.sort_by_key(|i| i.1);
1025			while let Some((versioned_dest, _)) = q.pop() {
1026				if let Ok(dest) = Location::try_from(versioned_dest) {
1027					if Self::request_version_notify(dest).is_ok() {
1028						// TODO: correct weights.
1029						weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
1030						break;
1031					}
1032				}
1033			}
1034			// Should never fail since we only removed items. But better safe than panicking as it's
1035			// way better to drop the queue than panic on initialize.
1036			if let Ok(q) = BoundedVec::try_from(q) {
1037				VersionDiscoveryQueue::<T>::put(q);
1038			}
1039			weight_used
1040		}
1041
1042		#[cfg(feature = "try-runtime")]
1043		fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
1044			Self::do_try_state()
1045		}
1046	}
1047
1048	pub mod migrations {
1049		use super::*;
1050		use pezframe_support::traits::{PalletInfoAccess, StorageVersion};
1051
1052		#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
1053		enum QueryStatusV0<BlockNumber> {
1054			Pending {
1055				responder: VersionedLocation,
1056				maybe_notify: Option<(u8, u8)>,
1057				timeout: BlockNumber,
1058			},
1059			VersionNotifier {
1060				origin: VersionedLocation,
1061				is_active: bool,
1062			},
1063			Ready {
1064				response: VersionedResponse,
1065				at: BlockNumber,
1066			},
1067		}
1068		impl<B> From<QueryStatusV0<B>> for QueryStatus<B> {
1069			fn from(old: QueryStatusV0<B>) -> Self {
1070				use QueryStatusV0::*;
1071				match old {
1072					Pending { responder, maybe_notify, timeout } => QueryStatus::Pending {
1073						responder,
1074						maybe_notify,
1075						timeout,
1076						maybe_match_querier: Some(Location::here().into()),
1077					},
1078					VersionNotifier { origin, is_active } => {
1079						QueryStatus::VersionNotifier { origin, is_active }
1080					},
1081					Ready { response, at } => QueryStatus::Ready { response, at },
1082				}
1083			}
1084		}
1085
1086		pub fn migrate_to_v1<T: Config, P: GetStorageVersion + PalletInfoAccess>(
1087		) -> pezframe_support::weights::Weight {
1088			let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
1089			tracing::info!(
1090				target: "runtime::xcm",
1091				?on_chain_storage_version,
1092				"Running migration storage v1 for xcm with storage version",
1093			);
1094
1095			if on_chain_storage_version < 1 {
1096				let mut count = 0;
1097				Queries::<T>::translate::<QueryStatusV0<BlockNumberFor<T>>, _>(|_key, value| {
1098					count += 1;
1099					Some(value.into())
1100				});
1101				StorageVersion::new(1).put::<P>();
1102				tracing::info!(
1103					target: "runtime::xcm",
1104					?on_chain_storage_version,
1105					"Running migration storage v1 for xcm with storage version was complete",
1106				);
1107				// calculate and return migration weights
1108				T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
1109			} else {
1110				tracing::warn!(
1111					target: "runtime::xcm",
1112					?on_chain_storage_version,
1113					"Attempted to apply migration to v1 but failed because storage version is",
1114				);
1115				T::DbWeight::get().reads(1)
1116			}
1117		}
1118	}
1119
1120	#[pezpallet::call(weight(<T as Config>::WeightInfo))]
1121	impl<T: Config> Pezpallet<T> {
1122		#[pezpallet::call_index(0)]
1123		pub fn send(
1124			origin: OriginFor<T>,
1125			dest: Box<VersionedLocation>,
1126			message: Box<VersionedXcm<()>>,
1127		) -> DispatchResult {
1128			<Self as SendController<_>>::send(origin, dest, message)?;
1129			Ok(())
1130		}
1131
1132		/// Teleport some assets from the local chain to some destination chain.
1133		///
1134		/// **This function is deprecated: Use `limited_teleport_assets` instead.**
1135		///
1136		/// Fee payment on the destination side is made from the asset in the `assets` vector of
1137		/// id `fee_asset_id`. The weight limit for fees is not provided and thus is unlimited,
1138		/// with all fees taken as needed from the asset.
1139		///
1140		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
1141		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
1142		///   Teyrchain(..)]` to send from teyrchain to teyrchain, or `[Teyrchain(..)]` to send from
1143		///   relay to teyrchain.
1144		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
1145		///   generally be an `AccountId32` value.
1146		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
1147		///   fee on the `dest` chain.
1148		/// - `fee_asset_id`: Id of the asset from `assets` which should be used to pay fees.
1149		#[pezpallet::call_index(1)]
1150		#[allow(deprecated)]
1151		#[deprecated(
1152			note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_teleport_assets` or `transfer_assets`"
1153		)]
1154		pub fn teleport_assets(
1155			origin: OriginFor<T>,
1156			dest: Box<VersionedLocation>,
1157			beneficiary: Box<VersionedLocation>,
1158			assets: Box<VersionedAssets>,
1159			fee_asset_id: Box<VersionedAssetId>,
1160		) -> DispatchResult {
1161			Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_id, Unlimited)
1162		}
1163
1164		/// Transfer some assets from the local chain to the destination chain through their local,
1165		/// destination or remote reserve.
1166		///
1167		/// `assets` must have same reserve location and may not be teleportable to `dest`.
1168		///  - `assets` have local reserve: transfer assets to sovereign account of destination
1169		///    chain and forward a notification XCM to `dest` to mint and deposit reserve-based
1170		///    assets to `beneficiary`.
1171		///  - `assets` have destination reserve: burn local assets and forward a notification to
1172		///    `dest` chain to withdraw the reserve assets from this chain's sovereign account and
1173		///    deposit them to `beneficiary`.
1174		///  - `assets` have remote reserve: burn local assets, forward XCM to reserve chain to move
1175		///    reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest`
1176		///    to mint and deposit reserve-based assets to `beneficiary`.
1177		///
1178		/// **This function is deprecated: Use `limited_reserve_transfer_assets` instead.**
1179		///
1180		/// Fee payment on the destination side is made from the asset in the `assets` vector of
1181		/// id `fee_asset_id`. The weight limit for fees is not provided and thus is unlimited,
1182		/// with all fees taken as needed from the asset.
1183		///
1184		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
1185		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
1186		///   Teyrchain(..)]` to send from teyrchain to teyrchain, or `[Teyrchain(..)]` to send from
1187		///   relay to teyrchain.
1188		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
1189		///   generally be an `AccountId32` value.
1190		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
1191		///   fee on the `dest` (and possibly reserve) chains.
1192		/// - `fee_asset_id`: Id of the asset from `assets` which should be used to pay fees.
1193		#[pezpallet::call_index(2)]
1194		#[allow(deprecated)]
1195		#[deprecated(
1196			note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_reserve_transfer_assets` or `transfer_assets`"
1197		)]
1198		pub fn reserve_transfer_assets(
1199			origin: OriginFor<T>,
1200			dest: Box<VersionedLocation>,
1201			beneficiary: Box<VersionedLocation>,
1202			assets: Box<VersionedAssets>,
1203			fee_asset_id: Box<VersionedAssetId>,
1204		) -> DispatchResult {
1205			Self::do_reserve_transfer_assets(
1206				origin,
1207				dest,
1208				beneficiary,
1209				assets,
1210				fee_asset_id,
1211				Unlimited,
1212			)
1213		}
1214
1215		/// Execute an XCM message from a local, signed, origin.
1216		///
1217		/// An event is deposited indicating whether `msg` could be executed completely or only
1218		/// partially.
1219		///
1220		/// No more than `max_weight` will be used in its attempted execution. If this is less than
1221		/// the maximum amount of weight that the message could take to be executed, then no
1222		/// execution attempt will be made.
1223		#[pezpallet::call_index(3)]
1224		#[pezpallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
1225		pub fn execute(
1226			origin: OriginFor<T>,
1227			message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
1228			max_weight: Weight,
1229		) -> DispatchResultWithPostInfo {
1230			let weight_used =
1231				<Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
1232			Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())
1233		}
1234
1235		/// Extoll that a particular destination can be communicated with through a particular
1236		/// version of XCM.
1237		///
1238		/// - `origin`: Must be an origin specified by AdminOrigin.
1239		/// - `location`: The destination that is being described.
1240		/// - `xcm_version`: The latest version of XCM that `location` supports.
1241		#[pezpallet::call_index(4)]
1242		pub fn force_xcm_version(
1243			origin: OriginFor<T>,
1244			location: Box<Location>,
1245			version: XcmVersion,
1246		) -> DispatchResult {
1247			T::AdminOrigin::ensure_origin(origin)?;
1248			let location = *location;
1249			SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&location), version);
1250			Self::deposit_event(Event::SupportedVersionChanged { location, version });
1251			Ok(())
1252		}
1253
1254		/// Set a safe XCM version (the version that XCM should be encoded with if the most recent
1255		/// version a destination can accept is unknown).
1256		///
1257		/// - `origin`: Must be an origin specified by AdminOrigin.
1258		/// - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable.
1259		#[pezpallet::call_index(5)]
1260		pub fn force_default_xcm_version(
1261			origin: OriginFor<T>,
1262			maybe_xcm_version: Option<XcmVersion>,
1263		) -> DispatchResult {
1264			T::AdminOrigin::ensure_origin(origin)?;
1265			SafeXcmVersion::<T>::set(maybe_xcm_version);
1266			Ok(())
1267		}
1268
1269		/// Ask a location to notify us regarding their XCM version and any changes to it.
1270		///
1271		/// - `origin`: Must be an origin specified by AdminOrigin.
1272		/// - `location`: The location to which we should subscribe for XCM version notifications.
1273		#[pezpallet::call_index(6)]
1274		pub fn force_subscribe_version_notify(
1275			origin: OriginFor<T>,
1276			location: Box<VersionedLocation>,
1277		) -> DispatchResult {
1278			T::AdminOrigin::ensure_origin(origin)?;
1279			let location: Location = (*location).try_into().map_err(|()| {
1280				tracing::debug!(
1281					target: "xcm::pezpallet_xcm::force_subscribe_version_notify",
1282					"Failed to convert VersionedLocation for subscription target"
1283				);
1284				Error::<T>::BadLocation
1285			})?;
1286			Self::request_version_notify(location).map_err(|e| {
1287				tracing::debug!(
1288					target: "xcm::pezpallet_xcm::force_subscribe_version_notify", error=?e,
1289					"Failed to subscribe for version notifications for location"
1290				);
1291				match e {
1292					XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
1293					_ => Error::<T>::InvalidOrigin,
1294				}
1295				.into()
1296			})
1297		}
1298
1299		/// Require that a particular destination should no longer notify us regarding any XCM
1300		/// version changes.
1301		///
1302		/// - `origin`: Must be an origin specified by AdminOrigin.
1303		/// - `location`: The location to which we are currently subscribed for XCM version
1304		///   notifications which we no longer desire.
1305		#[pezpallet::call_index(7)]
1306		pub fn force_unsubscribe_version_notify(
1307			origin: OriginFor<T>,
1308			location: Box<VersionedLocation>,
1309		) -> DispatchResult {
1310			T::AdminOrigin::ensure_origin(origin)?;
1311			let location: Location = (*location).try_into().map_err(|()| {
1312				tracing::debug!(
1313					target: "xcm::pezpallet_xcm::force_unsubscribe_version_notify",
1314					"Failed to convert VersionedLocation for unsubscription target"
1315				);
1316				Error::<T>::BadLocation
1317			})?;
1318			Self::unrequest_version_notify(location).map_err(|e| {
1319				tracing::debug!(
1320					target: "xcm::pezpallet_xcm::force_unsubscribe_version_notify", error=?e,
1321					"Failed to unsubscribe from version notifications for location"
1322				);
1323				match e {
1324					XcmError::InvalidLocation => Error::<T>::NoSubscription,
1325					_ => Error::<T>::InvalidOrigin,
1326				}
1327				.into()
1328			})
1329		}
1330
1331		/// Transfer some assets from the local chain to the destination chain through their local,
1332		/// destination or remote reserve.
1333		///
1334		/// `assets` must have same reserve location and may not be teleportable to `dest`.
1335		///  - `assets` have local reserve: transfer assets to sovereign account of destination
1336		///    chain and forward a notification XCM to `dest` to mint and deposit reserve-based
1337		///    assets to `beneficiary`.
1338		///  - `assets` have destination reserve: burn local assets and forward a notification to
1339		///    `dest` chain to withdraw the reserve assets from this chain's sovereign account and
1340		///    deposit them to `beneficiary`.
1341		///  - `assets` have remote reserve: burn local assets, forward XCM to reserve chain to move
1342		///    reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest`
1343		///    to mint and deposit reserve-based assets to `beneficiary`.
1344		///
1345		/// Fee payment on the destination side is made from the asset in the `assets` vector of
1346		/// id `fee_asset_id`, up to enough to pay for `weight_limit` of weight. If more weight
1347		/// is needed than `weight_limit`, then the operation will fail and the sent assets may be
1348		/// at risk.
1349		///
1350		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
1351		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
1352		///   Teyrchain(..)]` to send from teyrchain to teyrchain, or `[Teyrchain(..)]` to send from
1353		///   relay to teyrchain.
1354		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
1355		///   generally be an `AccountId32` value.
1356		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
1357		///   fee on the `dest` (and possibly reserve) chains.
1358		/// - `fee_asset_id`: Id of the asset from `assets` which should be used to pay fees.
1359		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
1360		#[pezpallet::call_index(8)]
1361		#[pezpallet::weight(T::WeightInfo::reserve_transfer_assets())]
1362		pub fn limited_reserve_transfer_assets(
1363			origin: OriginFor<T>,
1364			dest: Box<VersionedLocation>,
1365			beneficiary: Box<VersionedLocation>,
1366			assets: Box<VersionedAssets>,
1367			fee_asset_id: Box<VersionedAssetId>,
1368			weight_limit: WeightLimit,
1369		) -> DispatchResult {
1370			Self::do_reserve_transfer_assets(
1371				origin,
1372				dest,
1373				beneficiary,
1374				assets,
1375				fee_asset_id,
1376				weight_limit,
1377			)
1378		}
1379
1380		/// Teleport some assets from the local chain to some destination chain.
1381		///
1382		/// Fee payment on the destination side is made from the asset in the `assets` vector of
1383		/// id `fee_asset_id`, up to enough to pay for `weight_limit` of weight. If more weight
1384		/// is needed than `weight_limit`, then the operation will fail and the sent assets may be
1385		/// at risk.
1386		///
1387		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
1388		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
1389		///   Teyrchain(..)]` to send from teyrchain to teyrchain, or `[Teyrchain(..)]` to send from
1390		///   relay to teyrchain.
1391		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
1392		///   generally be an `AccountId32` value.
1393		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
1394		///   fee on the `dest` chain.
1395		/// - `fee_asset_id`: Id of the asset from `assets` which should be used to pay fees.
1396		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
1397		#[pezpallet::call_index(9)]
1398		#[pezpallet::weight(T::WeightInfo::teleport_assets())]
1399		pub fn limited_teleport_assets(
1400			origin: OriginFor<T>,
1401			dest: Box<VersionedLocation>,
1402			beneficiary: Box<VersionedLocation>,
1403			assets: Box<VersionedAssets>,
1404			fee_asset_id: Box<VersionedAssetId>,
1405			weight_limit: WeightLimit,
1406		) -> DispatchResult {
1407			Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_id, weight_limit)
1408		}
1409
1410		/// Set or unset the global suspension state of the XCM executor.
1411		///
1412		/// - `origin`: Must be an origin specified by AdminOrigin.
1413		/// - `suspended`: `true` to suspend, `false` to resume.
1414		#[pezpallet::call_index(10)]
1415		pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
1416			T::AdminOrigin::ensure_origin(origin)?;
1417			XcmExecutionSuspended::<T>::set(suspended);
1418			Ok(())
1419		}
1420
1421		/// Transfer some assets from the local chain to the destination chain through their local,
1422		/// destination or remote reserve, or through teleports.
1423		///
1424		/// Fee payment on the destination side is made from the asset in the `assets` vector of
1425		/// id `fee_asset_id` (hence referred to as `fees`), up to enough to pay for
1426		/// `weight_limit` of weight. If more weight is needed than `weight_limit`, then the
1427		/// operation will fail and the sent assets may be at risk.
1428		///
1429		/// `assets` (excluding `fees`) must have same reserve location or otherwise be teleportable
1430		/// to `dest`, no limitations imposed on `fees`.
1431		///  - for local reserve: transfer assets to sovereign account of destination chain and
1432		///    forward a notification XCM to `dest` to mint and deposit reserve-based assets to
1433		///    `beneficiary`.
1434		///  - for destination reserve: burn local assets and forward a notification to `dest` chain
1435		///    to withdraw the reserve assets from this chain's sovereign account and deposit them
1436		///    to `beneficiary`.
1437		///  - for remote reserve: burn local assets, forward XCM to reserve chain to move reserves
1438		///    from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` to mint
1439		///    and deposit reserve-based assets to `beneficiary`.
1440		///  - for teleports: burn local assets and forward XCM to `dest` chain to mint/teleport
1441		///    assets and deposit them to `beneficiary`.
1442		///
1443		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
1444		/// - `dest`: Destination context for the assets. Will typically be `X2(Parent,
1445		///   Teyrchain(..))` to send from teyrchain to teyrchain, or `X1(Teyrchain(..))` to send
1446		///   from relay to teyrchain.
1447		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
1448		///   generally be an `AccountId32` value.
1449		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
1450		///   fee on the `dest` (and possibly reserve) chains.
1451		/// - `fee_asset_id`: Id of the asset from `assets` which should be used to pay fees.
1452		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
1453		#[pezpallet::call_index(11)]
1454		pub fn transfer_assets(
1455			origin: OriginFor<T>,
1456			dest: Box<VersionedLocation>,
1457			beneficiary: Box<VersionedLocation>,
1458			assets: Box<VersionedAssets>,
1459			fee_asset_id: Box<VersionedAssetId>,
1460			weight_limit: WeightLimit,
1461		) -> DispatchResult {
1462			let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1463			let dest = (*dest).try_into().map_err(|()| {
1464				tracing::debug!(
1465					target: "xcm::pezpallet_xcm::transfer_assets",
1466					"Failed to convert destination VersionedLocation",
1467				);
1468				Error::<T>::BadVersion
1469			})?;
1470			let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
1471				tracing::debug!(
1472					target: "xcm::pezpallet_xcm::transfer_assets",
1473					"Failed to convert beneficiary VersionedLocation",
1474				);
1475				Error::<T>::BadVersion
1476			})?;
1477			let assets: Assets = (*assets).try_into().map_err(|()| {
1478				tracing::debug!(
1479					target: "xcm::pezpallet_xcm::transfer_assets",
1480					"Failed to convert VersionedAssets",
1481				);
1482				Error::<T>::BadVersion
1483			})?;
1484			let fee_asset_id: AssetId = (*fee_asset_id).try_into().map_err(|()| {
1485				tracing::debug!(
1486					target: "xcm::pezpallet_xcm::transfer_assets",
1487					"Failed to convert VersionedAssetId",
1488				);
1489				Error::<T>::BadVersion
1490			})?;
1491			tracing::debug!(
1492				target: "xcm::pezpallet_xcm::transfer_assets",
1493				?origin, ?dest, ?beneficiary, ?assets, ?fee_asset_id, ?weight_limit,
1494			);
1495
1496			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1497			let assets = assets.into_inner();
1498			// Find transfer types for fee and non-fee assets.
1499			let (fees_transfer_type, assets_transfer_type) =
1500				Self::find_fee_and_assets_transfer_types(&assets, &fee_asset_id, &dest)?;
1501
1502			// We check for network native asset reserve transfers in preparation for the Asset Hub
1503			// Migration. This check will be removed after the migration and the determined
1504			// reserve location adjusted accordingly. For more information, see https://github.com/pezkuwichain/pezkuwi-sdk/issues/301.
1505			Self::ensure_network_asset_reserve_transfer_allowed(
1506				&assets,
1507				&fee_asset_id,
1508				&assets_transfer_type,
1509				&fees_transfer_type,
1510			)?;
1511
1512			Self::do_transfer_assets(
1513				origin,
1514				dest,
1515				Either::Left(beneficiary),
1516				assets,
1517				assets_transfer_type,
1518				fee_asset_id,
1519				fees_transfer_type,
1520				weight_limit,
1521			)
1522		}
1523
1524		/// Claims assets trapped on this pezpallet because of leftover assets during XCM execution.
1525		///
1526		/// - `origin`: Anyone can call this extrinsic.
1527		/// - `assets`: The exact assets that were trapped. Use the version to specify what version
1528		/// was the latest when they were trapped.
1529		/// - `beneficiary`: The location/account where the claimed assets will be deposited.
1530		#[pezpallet::call_index(12)]
1531		pub fn claim_assets(
1532			origin: OriginFor<T>,
1533			assets: Box<VersionedAssets>,
1534			beneficiary: Box<VersionedLocation>,
1535		) -> DispatchResult {
1536			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1537			tracing::debug!(target: "xcm::pezpallet_xcm::claim_assets", ?origin_location, ?assets, ?beneficiary);
1538			// Extract version from `assets`.
1539			let assets_version = assets.identify_version();
1540			let assets: Assets = (*assets).try_into().map_err(|()| {
1541				tracing::debug!(
1542					target: "xcm::pezpallet_xcm::claim_assets",
1543					"Failed to convert input VersionedAssets",
1544				);
1545				Error::<T>::BadVersion
1546			})?;
1547			let number_of_assets = assets.len() as u32;
1548			let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
1549				tracing::debug!(
1550					target: "xcm::pezpallet_xcm::claim_assets",
1551					"Failed to convert beneficiary VersionedLocation",
1552				);
1553				Error::<T>::BadVersion
1554			})?;
1555			let ticket: Location = GeneralIndex(assets_version as u128).into();
1556			let mut message = Xcm(vec![
1557				ClaimAsset { assets, ticket },
1558				DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
1559			]);
1560			let weight = T::Weigher::weight(&mut message, Weight::MAX).map_err(|error| {
1561				tracing::debug!(target: "xcm::pezpallet_xcm::claim_assets", ?error, "Failed to calculate weight");
1562				Error::<T>::UnweighableMessage
1563			})?;
1564			let mut hash = message.using_encoded(pezsp_io::hashing::blake2_256);
1565			let outcome = T::XcmExecutor::prepare_and_execute(
1566				origin_location,
1567				message,
1568				&mut hash,
1569				weight,
1570				weight,
1571			);
1572			outcome.ensure_complete().map_err(|error| {
1573				tracing::error!(target: "xcm::pezpallet_xcm::claim_assets", ?error, "XCM execution failed with error");
1574				Error::<T>::LocalExecutionIncompleteWithError { index: error.index, error: error.error.into()}
1575			})?;
1576			Ok(())
1577		}
1578
1579		/// Transfer assets from the local chain to the destination chain using explicit transfer
1580		/// types for assets and fees.
1581		///
1582		/// `assets` must have same reserve location or may be teleportable to `dest`. Caller must
1583		/// provide the `assets_transfer_type` to be used for `assets`:
1584		///  - `TransferType::LocalReserve`: transfer assets to sovereign account of destination
1585		///    chain and forward a notification XCM to `dest` to mint and deposit reserve-based
1586		///    assets to `beneficiary`.
1587		///  - `TransferType::DestinationReserve`: burn local assets and forward a notification to
1588		///    `dest` chain to withdraw the reserve assets from this chain's sovereign account and
1589		///    deposit them to `beneficiary`.
1590		///  - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve`
1591		///    chain to move reserves from this chain's SA to `dest` chain's SA, and forward another
1592		///    XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically
1593		///    the remote `reserve` is Asset Hub.
1594		///  - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to
1595		///    mint/teleport assets and deposit them to `beneficiary`.
1596		///
1597		/// On the destination chain, as well as any intermediary hops, `BuyExecution` is used to
1598		/// buy execution using transferred `assets` identified by `remote_fees_id`.
1599		/// Make sure enough of the specified `remote_fees_id` asset is included in the given list
1600		/// of `assets`. `remote_fees_id` should be enough to pay for `weight_limit`. If more weight
1601		/// is needed than `weight_limit`, then the operation will fail and the sent assets may be
1602		/// at risk.
1603		///
1604		/// `remote_fees_id` may use different transfer type than rest of `assets` and can be
1605		/// specified through `fees_transfer_type`.
1606		///
1607		/// The caller needs to specify what should happen to the transferred assets once they reach
1608		/// the `dest` chain. This is done through the `custom_xcm_on_dest` parameter, which
1609		/// contains the instructions to execute on `dest` as a final step.
1610		///   This is usually as simple as:
1611		///   `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`,
1612		///   but could be something more exotic like sending the `assets` even further.
1613		///
1614		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
1615		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
1616		///   Teyrchain(..)]` to send from teyrchain to teyrchain, or `[Teyrchain(..)]` to send from
1617		///   relay to teyrchain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from
1618		///   teyrchain across a bridge to another ecosystem destination.
1619		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
1620		///   fee on the `dest` (and possibly reserve) chains.
1621		/// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`.
1622		/// - `remote_fees_id`: One of the included `assets` to be used to pay fees.
1623		/// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets.
1624		/// - `custom_xcm_on_dest`: The XCM to be executed on `dest` chain as the last step of the
1625		///   transfer, which also determines what happens to the assets on the destination chain.
1626		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
1627		#[pezpallet::call_index(13)]
1628		#[pezpallet::weight(T::WeightInfo::transfer_assets())]
1629		pub fn transfer_assets_using_type_and_then(
1630			origin: OriginFor<T>,
1631			dest: Box<VersionedLocation>,
1632			assets: Box<VersionedAssets>,
1633			assets_transfer_type: Box<TransferType>,
1634			remote_fees_id: Box<VersionedAssetId>,
1635			fees_transfer_type: Box<TransferType>,
1636			custom_xcm_on_dest: Box<VersionedXcm<()>>,
1637			weight_limit: WeightLimit,
1638		) -> DispatchResult {
1639			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1640			let dest: Location = (*dest).try_into().map_err(|()| {
1641				tracing::debug!(
1642					target: "xcm::pezpallet_xcm::transfer_assets_using_type_and_then",
1643					"Failed to convert destination VersionedLocation",
1644				);
1645				Error::<T>::BadVersion
1646			})?;
1647			let assets: Assets = (*assets).try_into().map_err(|()| {
1648				tracing::debug!(
1649					target: "xcm::pezpallet_xcm::transfer_assets_using_type_and_then",
1650					"Failed to convert VersionedAssets",
1651				);
1652				Error::<T>::BadVersion
1653			})?;
1654			let fees_id: AssetId = (*remote_fees_id).try_into().map_err(|()| {
1655				tracing::debug!(
1656					target: "xcm::pezpallet_xcm::transfer_assets_using_type_and_then",
1657					"Failed to convert remote_fees_id VersionedAssetId",
1658				);
1659				Error::<T>::BadVersion
1660			})?;
1661			let remote_xcm: Xcm<()> = (*custom_xcm_on_dest).try_into().map_err(|()| {
1662				tracing::debug!(
1663					target: "xcm::pezpallet_xcm::transfer_assets_using_type_and_then",
1664					"Failed to convert custom_xcm_on_dest VersionedXcm",
1665				);
1666				Error::<T>::BadVersion
1667			})?;
1668			tracing::debug!(
1669				target: "xcm::pezpallet_xcm::transfer_assets_using_type_and_then",
1670				?origin_location, ?dest, ?assets, ?assets_transfer_type, ?fees_id, ?fees_transfer_type,
1671				?remote_xcm, ?weight_limit,
1672			);
1673
1674			let assets = assets.into_inner();
1675			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
1676
1677			Self::do_transfer_assets(
1678				origin_location,
1679				dest,
1680				Either::Right(remote_xcm),
1681				assets,
1682				*assets_transfer_type,
1683				fees_id,
1684				*fees_transfer_type,
1685				weight_limit,
1686			)
1687		}
1688
1689		/// Authorize another `aliaser` location to alias into the local `origin` making this call.
1690		/// The `aliaser` is only authorized until the provided `expiry` block number.
1691		/// The call can also be used for a previously authorized alias in order to update its
1692		/// `expiry` block number.
1693		///
1694		/// Usually useful to allow your local account to be aliased into from a remote location
1695		/// also under your control (like your account on another chain).
1696		///
1697		/// WARNING: make sure the caller `origin` (you) trusts the `aliaser` location to act in
1698		/// their/your name. Once authorized using this call, the `aliaser` can freely impersonate
1699		/// `origin` in XCM programs executed on the local chain.
1700		#[pezpallet::call_index(14)]
1701		pub fn add_authorized_alias(
1702			origin: OriginFor<T>,
1703			aliaser: Box<VersionedLocation>,
1704			expires: Option<u64>,
1705		) -> DispatchResult {
1706			let signed_origin = ensure_signed(origin.clone())?;
1707			let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1708			let new_aliaser: Location = (*aliaser).try_into().map_err(|()| {
1709				tracing::debug!(
1710					target: "xcm::pezpallet_xcm::add_authorized_alias",
1711					"Failed to convert aliaser VersionedLocation",
1712				);
1713				Error::<T>::BadVersion
1714			})?;
1715			ensure!(origin_location != new_aliaser, Error::<T>::BadLocation);
1716			// remove `network` from inner `AccountId32` for easier matching
1717			let origin_location = match origin_location.unpack() {
1718				(0, [AccountId32 { network: _, id }]) => {
1719					Location::new(0, [AccountId32 { network: None, id: *id }])
1720				},
1721				_ => return Err(Error::<T>::InvalidOrigin.into()),
1722			};
1723			tracing::debug!(target: "xcm::pezpallet_xcm::add_authorized_alias", ?origin_location, ?new_aliaser, ?expires);
1724			ensure!(origin_location != new_aliaser, Error::<T>::BadLocation);
1725			if let Some(expiry) = expires {
1726				ensure!(
1727					expiry
1728						> pezframe_system::Pezpallet::<T>::current_block_number()
1729							.saturated_into::<u64>(),
1730					Error::<T>::ExpiresInPast
1731				);
1732			}
1733			let versioned_origin = VersionedLocation::from(origin_location.clone());
1734			let versioned_aliaser = VersionedLocation::from(new_aliaser.clone());
1735			let entry = if let Some(entry) = AuthorizedAliases::<T>::get(&versioned_origin) {
1736				// entry already exists, update it
1737				let (mut aliasers, mut ticket) = (entry.aliasers, entry.ticket);
1738				if let Some(aliaser) =
1739					aliasers.iter_mut().find(|aliaser| aliaser.location == versioned_aliaser)
1740				{
1741					// if the aliaser already exists, just update its expiry block
1742					aliaser.expiry = expires;
1743				} else {
1744					// if it doesn't, we try to add it
1745					let aliaser =
1746						OriginAliaser { location: versioned_aliaser.clone(), expiry: expires };
1747					aliasers.try_push(aliaser).map_err(|_| {
1748						tracing::debug!(
1749							target: "xcm::pezpallet_xcm::add_authorized_alias",
1750							"Failed to add new aliaser to existing entry",
1751						);
1752						Error::<T>::TooManyAuthorizedAliases
1753					})?;
1754					// we try to update the ticket (the storage deposit)
1755					ticket = ticket.update(&signed_origin, aliasers_footprint(aliasers.len()))?;
1756				}
1757				AuthorizedAliasesEntry { aliasers, ticket }
1758			} else {
1759				// add new entry with its first alias
1760				let ticket = TicketOf::<T>::new(&signed_origin, aliasers_footprint(1))?;
1761				let aliaser =
1762					OriginAliaser { location: versioned_aliaser.clone(), expiry: expires };
1763				let mut aliasers = BoundedVec::<OriginAliaser, MaxAuthorizedAliases>::new();
1764				aliasers.try_push(aliaser).map_err(|error| {
1765					tracing::debug!(
1766						target: "xcm::pezpallet_xcm::add_authorized_alias", ?error,
1767						"Failed to add first aliaser to new entry",
1768					);
1769					Error::<T>::TooManyAuthorizedAliases
1770				})?;
1771				AuthorizedAliasesEntry { aliasers, ticket }
1772			};
1773			// write to storage
1774			AuthorizedAliases::<T>::insert(&versioned_origin, entry);
1775			Self::deposit_event(Event::AliasAuthorized {
1776				aliaser: new_aliaser,
1777				target: origin_location,
1778				expiry: expires,
1779			});
1780			Ok(())
1781		}
1782
1783		/// Remove a previously authorized `aliaser` from the list of locations that can alias into
1784		/// the local `origin` making this call.
1785		#[pezpallet::call_index(15)]
1786		pub fn remove_authorized_alias(
1787			origin: OriginFor<T>,
1788			aliaser: Box<VersionedLocation>,
1789		) -> DispatchResult {
1790			let signed_origin = ensure_signed(origin.clone())?;
1791			let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1792			let to_remove: Location = (*aliaser).try_into().map_err(|()| {
1793				tracing::debug!(
1794					target: "xcm::pezpallet_xcm::remove_authorized_alias",
1795					"Failed to convert aliaser VersionedLocation",
1796				);
1797				Error::<T>::BadVersion
1798			})?;
1799			ensure!(origin_location != to_remove, Error::<T>::BadLocation);
1800			// remove `network` from inner `AccountId32` for easier matching
1801			let origin_location = match origin_location.unpack() {
1802				(0, [AccountId32 { network: _, id }]) => {
1803					Location::new(0, [AccountId32 { network: None, id: *id }])
1804				},
1805				_ => return Err(Error::<T>::InvalidOrigin.into()),
1806			};
1807			tracing::debug!(target: "xcm::pezpallet_xcm::remove_authorized_alias", ?origin_location, ?to_remove);
1808			ensure!(origin_location != to_remove, Error::<T>::BadLocation);
1809			// convert to latest versioned
1810			let versioned_origin = VersionedLocation::from(origin_location.clone());
1811			let versioned_to_remove = VersionedLocation::from(to_remove.clone());
1812			AuthorizedAliases::<T>::get(&versioned_origin)
1813				.ok_or(Error::<T>::AliasNotFound.into())
1814				.and_then(|entry| {
1815					let (mut aliasers, mut ticket) = (entry.aliasers, entry.ticket);
1816					let old_len = aliasers.len();
1817					aliasers.retain(|alias| versioned_to_remove.ne(&alias.location));
1818					let new_len = aliasers.len();
1819					if aliasers.is_empty() {
1820						// remove entry altogether and return all storage deposit
1821						ticket.drop(&signed_origin)?;
1822						AuthorizedAliases::<T>::remove(&versioned_origin);
1823						Self::deposit_event(Event::AliasAuthorizationRemoved {
1824							aliaser: to_remove,
1825							target: origin_location,
1826						});
1827						Ok(())
1828					} else if old_len != new_len {
1829						// update aliasers and storage deposit
1830						ticket = ticket.update(&signed_origin, aliasers_footprint(new_len))?;
1831						let entry = AuthorizedAliasesEntry { aliasers, ticket };
1832						AuthorizedAliases::<T>::insert(&versioned_origin, entry);
1833						Self::deposit_event(Event::AliasAuthorizationRemoved {
1834							aliaser: to_remove,
1835							target: origin_location,
1836						});
1837						Ok(())
1838					} else {
1839						Err(Error::<T>::AliasNotFound.into())
1840					}
1841				})
1842		}
1843
1844		/// Remove all previously authorized `aliaser`s that can alias into the local `origin`
1845		/// making this call.
1846		#[pezpallet::call_index(16)]
1847		#[pezpallet::weight(T::WeightInfo::remove_authorized_alias())]
1848		pub fn remove_all_authorized_aliases(origin: OriginFor<T>) -> DispatchResult {
1849			let signed_origin = ensure_signed(origin.clone())?;
1850			let origin_location: Location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
1851			// remove `network` from inner `AccountId32` for easier matching
1852			let origin_location = match origin_location.unpack() {
1853				(0, [AccountId32 { network: _, id }]) => {
1854					Location::new(0, [AccountId32 { network: None, id: *id }])
1855				},
1856				_ => return Err(Error::<T>::InvalidOrigin.into()),
1857			};
1858			tracing::debug!(target: "xcm::pezpallet_xcm::remove_all_authorized_aliases", ?origin_location);
1859			// convert to latest versioned
1860			let versioned_origin = VersionedLocation::from(origin_location.clone());
1861			if let Some(entry) = AuthorizedAliases::<T>::get(&versioned_origin) {
1862				// remove entry altogether and return all storage deposit
1863				entry.ticket.drop(&signed_origin)?;
1864				AuthorizedAliases::<T>::remove(&versioned_origin);
1865				Self::deposit_event(Event::AliasesAuthorizationsRemoved {
1866					target: origin_location,
1867				});
1868				Ok(())
1869			} else {
1870				tracing::debug!(target: "xcm::pezpallet_xcm::remove_all_authorized_aliases", "No authorized alias entry found for the origin");
1871				Err(Error::<T>::AliasNotFound.into())
1872			}
1873		}
1874	}
1875}
1876
1877/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
1878const MAX_ASSETS_FOR_TRANSFER: usize = 2;
1879
1880/// Specify how assets used for fees are handled during asset transfers.
1881#[derive(Clone, PartialEq)]
1882enum FeesHandling<T: Config> {
1883	/// `fees` asset can be batch-transferred with rest of assets using same XCM instructions.
1884	Batched { fees: Asset },
1885	/// fees cannot be batched, they are handled separately using XCM programs here.
1886	Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
1887}
1888
1889impl<T: Config> core::fmt::Debug for FeesHandling<T> {
1890	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1891		match self {
1892			Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
1893			Self::Separate { local_xcm, remote_xcm } => write!(
1894				f,
1895				"FeesHandling::Separate(local: {:?}, remote: {:?})",
1896				local_xcm, remote_xcm
1897			),
1898		}
1899	}
1900}
1901
1902impl<T: Config> QueryHandler for Pezpallet<T> {
1903	type BlockNumber = BlockNumberFor<T>;
1904	type Error = XcmError;
1905	type UniversalLocation = T::UniversalLocation;
1906
1907	/// Attempt to create a new query ID and register it as a query that is yet to respond.
1908	fn new_query(
1909		responder: impl Into<Location>,
1910		timeout: BlockNumberFor<T>,
1911		match_querier: impl Into<Location>,
1912	) -> QueryId {
1913		Self::do_new_query(responder, None, timeout, match_querier)
1914	}
1915
1916	/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
1917	/// value.
1918	fn report_outcome(
1919		message: &mut Xcm<()>,
1920		responder: impl Into<Location>,
1921		timeout: Self::BlockNumber,
1922	) -> Result<QueryId, Self::Error> {
1923		let responder = responder.into();
1924		let destination =
1925			Self::UniversalLocation::get().invert_target(&responder).map_err(|()| {
1926				tracing::debug!(
1927					target: "xcm::pezpallet_xcm::report_outcome",
1928					"Failed to invert responder Location",
1929				);
1930				XcmError::LocationNotInvertible
1931			})?;
1932		let query_id = Self::new_query(responder, timeout, Here);
1933		let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
1934		let report_error = Xcm(vec![ReportError(response_info)]);
1935		message.0.insert(0, SetAppendix(report_error));
1936		Ok(query_id)
1937	}
1938
1939	/// Removes response when ready and emits [Event::ResponseTaken] event.
1940	fn take_response(query_id: QueryId) -> QueryResponseStatus<Self::BlockNumber> {
1941		match Queries::<T>::get(query_id) {
1942			Some(QueryStatus::Ready { response, at }) => match response.try_into() {
1943				Ok(response) => {
1944					Queries::<T>::remove(query_id);
1945					Self::deposit_event(Event::ResponseTaken { query_id });
1946					QueryResponseStatus::Ready { response, at }
1947				},
1948				Err(_) => {
1949					tracing::debug!(
1950						target: "xcm::pezpallet_xcm::take_response", ?query_id,
1951						"Failed to convert VersionedResponse to Response for query",
1952					);
1953					QueryResponseStatus::UnexpectedVersion
1954				},
1955			},
1956			Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
1957			Some(_) => {
1958				tracing::debug!(
1959					target: "xcm::pezpallet_xcm::take_response", ?query_id,
1960					"Unexpected QueryStatus variant for query",
1961				);
1962				QueryResponseStatus::UnexpectedVersion
1963			},
1964			None => {
1965				tracing::debug!(
1966					target: "xcm::pezpallet_xcm::take_response", ?query_id,
1967					"Query ID not found`",
1968				);
1969				QueryResponseStatus::NotFound
1970			},
1971		}
1972	}
1973
1974	#[cfg(feature = "runtime-benchmarks")]
1975	fn expect_response(id: QueryId, response: Response) {
1976		let response = response.into();
1977		Queries::<T>::insert(
1978			id,
1979			QueryStatus::Ready {
1980				response,
1981				at: pezframe_system::Pezpallet::<T>::current_block_number(),
1982			},
1983		);
1984	}
1985}
1986
1987impl<T: Config> Pezpallet<T> {
1988	/// The ongoing queries.
1989	pub fn query(query_id: &QueryId) -> Option<QueryStatus<BlockNumberFor<T>>> {
1990		Queries::<T>::get(query_id)
1991	}
1992
1993	/// The existing asset traps.
1994	///
1995	/// Key is the blake2 256 hash of (origin, versioned `Assets`) pair.
1996	/// Value is the number of times this pair has been trapped
1997	/// (usually just 1 if it exists at all).
1998	pub fn asset_trap(trap_id: &H256) -> u32 {
1999		AssetTraps::<T>::get(trap_id)
2000	}
2001
2002	/// Find `TransferType`s for `assets` and fee identified through `fee_asset_id`, when
2003	/// transferring to `dest`.
2004	///
2005	/// Validate `assets` to all have same `TransferType`.
2006	fn find_fee_and_assets_transfer_types(
2007		assets: &[Asset],
2008		fee_asset_id: &AssetId,
2009		dest: &Location,
2010	) -> Result<(TransferType, TransferType), Error<T>> {
2011		let mut fees_transfer_type = None;
2012		let mut assets_transfer_type = None;
2013		for asset in assets.iter() {
2014			if let Fungible(x) = asset.fun {
2015				// If fungible asset, ensure non-zero amount.
2016				ensure!(!x.is_zero(), Error::<T>::Empty);
2017			}
2018			let transfer_type =
2019				T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
2020			if asset.id == *fee_asset_id {
2021				fees_transfer_type = Some(transfer_type);
2022			} else {
2023				if let Some(existing) = assets_transfer_type.as_ref() {
2024					// Ensure transfer for multiple assets uses same transfer type (only fee may
2025					// have different transfer type/path)
2026					ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
2027				} else {
2028					// asset reserve identified
2029					assets_transfer_type = Some(transfer_type);
2030				}
2031			}
2032		}
2033		// single asset also marked as fee item
2034		if assets.len() == 1 {
2035			assets_transfer_type = fees_transfer_type.clone()
2036		}
2037		Ok((
2038			fees_transfer_type.ok_or(Error::<T>::Empty)?,
2039			assets_transfer_type.ok_or(Error::<T>::Empty)?,
2040		))
2041	}
2042
2043	fn do_reserve_transfer_assets(
2044		origin: OriginFor<T>,
2045		dest: Box<VersionedLocation>,
2046		beneficiary: Box<VersionedLocation>,
2047		assets: Box<VersionedAssets>,
2048		fee_asset_id: Box<VersionedAssetId>,
2049		weight_limit: WeightLimit,
2050	) -> DispatchResult {
2051		let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2052		let dest = (*dest).try_into().map_err(|()| {
2053			tracing::debug!(
2054				target: "xcm::pezpallet_xcm::do_reserve_transfer_assets",
2055				"Failed to convert destination VersionedLocation",
2056			);
2057			Error::<T>::BadVersion
2058		})?;
2059		let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2060			tracing::debug!(
2061				target: "xcm::pezpallet_xcm::do_reserve_transfer_assets",
2062				"Failed to convert beneficiary VersionedLocation",
2063			);
2064			Error::<T>::BadVersion
2065		})?;
2066		let assets: Assets = (*assets).try_into().map_err(|()| {
2067			tracing::debug!(
2068				target: "xcm::pezpallet_xcm::do_reserve_transfer_assets",
2069				"Failed to convert VersionedAssets",
2070			);
2071			Error::<T>::BadVersion
2072		})?;
2073		let fee_asset_id: AssetId = (*fee_asset_id).try_into().map_err(|()| {
2074			tracing::debug!(
2075				target: "xcm::pezpallet_xcm::do_reserve_transfer_assets",
2076				"Failed to convert VersionedAssetId",
2077			);
2078			Error::<T>::BadVersion
2079		})?;
2080		tracing::debug!(
2081			target: "xcm::pezpallet_xcm::do_reserve_transfer_assets",
2082			?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_id,
2083		);
2084
2085		ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2086		let value = (origin_location, assets.into_inner());
2087		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2088		let (origin, assets) = value;
2089
2090		let fees = assets.iter().find(|a| a.id == fee_asset_id).ok_or(Error::<T>::Empty)?.clone();
2091
2092		// Find transfer types for fee and non-fee assets.
2093		let (fees_transfer_type, assets_transfer_type) =
2094			Self::find_fee_and_assets_transfer_types(&assets, &fee_asset_id, &dest)?;
2095		// Ensure assets (and fees according to check below) are not teleportable to `dest`.
2096		ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
2097		// Ensure all assets (including fees) have same reserve location.
2098		ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
2099
2100		// We check for network native asset reserve transfers in preparation for the Asset Hub
2101		// Migration. This check will be removed after the migration and the determined
2102		// reserve location adjusted accordingly. For more information, see https://github.com/pezkuwichain/pezkuwi-sdk/issues/301.
2103		Self::ensure_network_asset_reserve_transfer_allowed(
2104			&assets,
2105			&fee_asset_id,
2106			&assets_transfer_type,
2107			&fees_transfer_type,
2108		)?;
2109
2110		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2111			origin.clone(),
2112			dest.clone(),
2113			Either::Left(beneficiary),
2114			assets,
2115			assets_transfer_type,
2116			FeesHandling::Batched { fees },
2117			weight_limit,
2118		)?;
2119		Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2120	}
2121
2122	fn do_teleport_assets(
2123		origin: OriginFor<T>,
2124		dest: Box<VersionedLocation>,
2125		beneficiary: Box<VersionedLocation>,
2126		assets: Box<VersionedAssets>,
2127		fee_asset_id: Box<VersionedAssetId>,
2128		weight_limit: WeightLimit,
2129	) -> DispatchResult {
2130		let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
2131		let dest = (*dest).try_into().map_err(|()| {
2132			tracing::debug!(
2133				target: "xcm::pezpallet_xcm::do_teleport_assets",
2134				"Failed to convert destination VersionedLocation",
2135			);
2136			Error::<T>::BadVersion
2137		})?;
2138		let beneficiary: Location = (*beneficiary).try_into().map_err(|()| {
2139			tracing::debug!(
2140				target: "xcm::pezpallet_xcm::do_teleport_assets",
2141				"Failed to convert beneficiary VersionedLocation",
2142			);
2143			Error::<T>::BadVersion
2144		})?;
2145		let assets: Assets = (*assets).try_into().map_err(|()| {
2146			tracing::debug!(
2147				target: "xcm::pezpallet_xcm::do_teleport_assets",
2148				"Failed to convert VersionedAssets",
2149			);
2150			Error::<T>::BadVersion
2151		})?;
2152		let fee_asset_id: AssetId = (*fee_asset_id).try_into().map_err(|()| {
2153			tracing::debug!(
2154				target: "xcm::pezpallet_xcm::do_teleport_assets",
2155				"Failed to convert VersionedAssetId",
2156			);
2157			Error::<T>::BadVersion
2158		})?;
2159
2160		tracing::debug!(
2161			target: "xcm::pezpallet_xcm::do_teleport_assets",
2162			?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_id, ?weight_limit,
2163		);
2164
2165		ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
2166		let value = (origin_location, assets.into_inner());
2167		ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2168		let (origin_location, assets) = value;
2169		let mut maybe_fee_asset = None;
2170		for asset in assets.iter() {
2171			let transfer_type =
2172				T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
2173			ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
2174			if asset.id == fee_asset_id {
2175				maybe_fee_asset = Some(asset)
2176			}
2177		}
2178		let fees = maybe_fee_asset.ok_or(Error::<T>::Empty)?.clone();
2179
2180		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2181			origin_location.clone(),
2182			dest.clone(),
2183			Either::Left(beneficiary),
2184			assets,
2185			TransferType::Teleport,
2186			FeesHandling::Batched { fees },
2187			weight_limit,
2188		)?;
2189		Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
2190	}
2191
2192	fn do_transfer_assets(
2193		origin: Location,
2194		dest: Location,
2195		beneficiary: Either<Location, Xcm<()>>,
2196		mut assets: Vec<Asset>,
2197		assets_transfer_type: TransferType,
2198		fee_asset_id: AssetId,
2199		fees_transfer_type: TransferType,
2200		weight_limit: WeightLimit,
2201	) -> DispatchResult {
2202		// local and remote XCM programs to potentially handle fees separately
2203		let fees = if fees_transfer_type == assets_transfer_type {
2204			let fees =
2205				assets.iter().find(|a| a.id == fee_asset_id).ok_or(Error::<T>::Empty)?.clone();
2206			// no need for custom fees instructions, fees are batched with assets
2207			FeesHandling::Batched { fees }
2208		} else {
2209			// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered
2210			// by branch above). The reason for this is that we'd need to send XCMs to separate
2211			// chains with no guarantee of delivery order on final destination; therefore we
2212			// cannot guarantee to have fees in place on final destination chain to pay for
2213			// assets transfer.
2214			ensure!(
2215				!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
2216				Error::<T>::InvalidAssetUnsupportedReserve
2217			);
2218			let weight_limit = weight_limit.clone();
2219			// remove `fees` from `assets` and build separate fees transfer instructions to be
2220			// added to assets transfers XCM programs
2221			let fee_asset_index =
2222				assets.iter().position(|a| a.id == fee_asset_id).ok_or(Error::<T>::FeesNotMet)?;
2223			let fees = assets.remove(fee_asset_index);
2224			let (local_xcm, remote_xcm) = match fees_transfer_type {
2225				TransferType::LocalReserve => Self::local_reserve_fees_instructions(
2226					origin.clone(),
2227					dest.clone(),
2228					fees,
2229					weight_limit,
2230				)?,
2231				TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
2232					origin.clone(),
2233					dest.clone(),
2234					fees,
2235					weight_limit,
2236				)?,
2237				TransferType::Teleport => Self::teleport_fees_instructions(
2238					origin.clone(),
2239					dest.clone(),
2240					fees,
2241					weight_limit,
2242				)?,
2243				TransferType::RemoteReserve(_) => {
2244					return Err(Error::<T>::InvalidAssetUnsupportedReserve.into())
2245				},
2246			};
2247			FeesHandling::Separate { local_xcm, remote_xcm }
2248		};
2249
2250		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
2251			origin.clone(),
2252			dest.clone(),
2253			beneficiary,
2254			assets,
2255			assets_transfer_type,
2256			fees,
2257			weight_limit,
2258		)?;
2259		Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
2260	}
2261
2262	fn build_xcm_transfer_type(
2263		origin: Location,
2264		dest: Location,
2265		beneficiary: Either<Location, Xcm<()>>,
2266		assets: Vec<Asset>,
2267		transfer_type: TransferType,
2268		fees: FeesHandling<T>,
2269		weight_limit: WeightLimit,
2270	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
2271		tracing::debug!(
2272			target: "xcm::pezpallet_xcm::build_xcm_transfer_type",
2273			?origin, ?dest, ?beneficiary, ?assets, ?transfer_type, ?fees, ?weight_limit,
2274		);
2275		match transfer_type {
2276			TransferType::LocalReserve => Self::local_reserve_transfer_programs(
2277				origin.clone(),
2278				dest.clone(),
2279				beneficiary,
2280				assets,
2281				fees,
2282				weight_limit,
2283			)
2284			.map(|(local, remote)| (local, Some(remote))),
2285			TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
2286				origin.clone(),
2287				dest.clone(),
2288				beneficiary,
2289				assets,
2290				fees,
2291				weight_limit,
2292			)
2293			.map(|(local, remote)| (local, Some(remote))),
2294			TransferType::RemoteReserve(reserve) => {
2295				let fees = match fees {
2296					FeesHandling::Batched { fees } => fees,
2297					_ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
2298				};
2299				Self::remote_reserve_transfer_program(
2300					origin.clone(),
2301					reserve.try_into().map_err(|()| {
2302						tracing::debug!(
2303							target: "xcm::pezpallet_xcm::build_xcm_transfer_type",
2304							"Failed to convert remote reserve location",
2305						);
2306						Error::<T>::BadVersion
2307					})?,
2308					beneficiary,
2309					dest.clone(),
2310					assets,
2311					fees,
2312					weight_limit,
2313				)
2314				.map(|local| (local, None))
2315			},
2316			TransferType::Teleport => Self::teleport_assets_program(
2317				origin.clone(),
2318				dest.clone(),
2319				beneficiary,
2320				assets,
2321				fees,
2322				weight_limit,
2323			)
2324			.map(|(local, remote)| (local, Some(remote))),
2325		}
2326	}
2327
2328	fn execute_xcm_transfer(
2329		origin: Location,
2330		dest: Location,
2331		mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
2332		remote_xcm: Option<Xcm<()>>,
2333	) -> DispatchResult {
2334		tracing::debug!(
2335			target: "xcm::pezpallet_xcm::execute_xcm_transfer",
2336			?origin, ?dest, ?local_xcm, ?remote_xcm,
2337		);
2338
2339		let weight =
2340			T::Weigher::weight(&mut local_xcm, Weight::MAX).map_err(|error| {
2341				tracing::debug!(target: "xcm::pezpallet_xcm::execute_xcm_transfer", ?error, "Failed to calculate weight");
2342				Error::<T>::UnweighableMessage
2343			})?;
2344		let mut hash = local_xcm.using_encoded(pezsp_io::hashing::blake2_256);
2345		let outcome = T::XcmExecutor::prepare_and_execute(
2346			origin.clone(),
2347			local_xcm,
2348			&mut hash,
2349			weight,
2350			weight,
2351		);
2352		Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
2353		outcome.clone().ensure_complete().map_err(|error| {
2354			tracing::error!(
2355				target: "xcm::pezpallet_xcm::execute_xcm_transfer",
2356				?error, "XCM execution failed with error with outcome: {:?}", outcome
2357			);
2358			Error::<T>::LocalExecutionIncompleteWithError {
2359				index: error.index,
2360				error: error.error.into(),
2361			}
2362		})?;
2363
2364		if let Some(remote_xcm) = remote_xcm {
2365			let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
2366				.map_err(|error| {
2367					tracing::error!(target: "xcm::pezpallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error");
2368					Error::<T>::from(error)
2369				})?;
2370			if origin != Here.into_location() {
2371				Self::charge_fees(origin.clone(), price.clone()).map_err(|error| {
2372					tracing::error!(
2373						target: "xcm::pezpallet_xcm::execute_xcm_transfer",
2374						?error, ?price, ?origin, "Unable to charge fee",
2375					);
2376					Error::<T>::FeesNotMet
2377				})?;
2378			}
2379			let message_id = T::XcmRouter::deliver(ticket)
2380				.map_err(|error| {
2381					tracing::error!(target: "xcm::pezpallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error");
2382					Error::<T>::from(error)
2383				})?;
2384
2385			let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
2386			Self::deposit_event(e);
2387		}
2388		Ok(())
2389	}
2390
2391	fn add_fees_to_xcm(
2392		dest: Location,
2393		fees: FeesHandling<T>,
2394		weight_limit: WeightLimit,
2395		local: &mut Xcm<<T as Config>::RuntimeCall>,
2396		remote: &mut Xcm<()>,
2397	) -> Result<(), Error<T>> {
2398		match fees {
2399			FeesHandling::Batched { fees } => {
2400				let context = T::UniversalLocation::get();
2401				// no custom fees instructions, they are batched together with `assets` transfer;
2402				// BuyExecution happens after receiving all `assets`
2403				let reanchored_fees =
2404					fees.reanchored(&dest, &context).map_err(|e| {
2405						tracing::error!(target: "xcm::pezpallet_xcm::add_fees_to_xcm", ?e, ?dest, ?context, "Failed to re-anchor fees");
2406						Error::<T>::CannotReanchor
2407					})?;
2408				// buy execution using `fees` batched together with above `reanchored_assets`
2409				remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
2410			},
2411			FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
2412				// fees are handled by separate XCM instructions, prepend fees instructions (for
2413				// remote XCM they have to be prepended instead of appended to pass barriers).
2414				core::mem::swap(local, &mut local_fees);
2415				core::mem::swap(remote, &mut remote_fees);
2416				// these are now swapped so fees actually go first
2417				local.inner_mut().append(&mut local_fees.into_inner());
2418				remote.inner_mut().append(&mut remote_fees.into_inner());
2419			},
2420		}
2421		Ok(())
2422	}
2423
2424	fn local_reserve_fees_instructions(
2425		origin: Location,
2426		dest: Location,
2427		fees: Asset,
2428		weight_limit: WeightLimit,
2429	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2430		let value = (origin, vec![fees.clone()]);
2431		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2432
2433		let context = T::UniversalLocation::get();
2434		let reanchored_fees = fees.clone().reanchored(&dest, &context).map_err(|_| {
2435			tracing::debug!(
2436				target: "xcm::pezpallet_xcm::local_reserve_fees_instructions",
2437				"Failed to re-anchor fees",
2438			);
2439			Error::<T>::CannotReanchor
2440		})?;
2441
2442		let local_execute_xcm = Xcm(vec![
2443			// move `fees` to `dest`s local sovereign account
2444			TransferAsset { assets: fees.into(), beneficiary: dest },
2445		]);
2446		let xcm_on_dest = Xcm(vec![
2447			// let (dest) chain know `fees` are in its SA on reserve
2448			ReserveAssetDeposited(reanchored_fees.clone().into()),
2449			// buy exec using `fees` in holding deposited in above instruction
2450			BuyExecution { fees: reanchored_fees, weight_limit },
2451		]);
2452		Ok((local_execute_xcm, xcm_on_dest))
2453	}
2454
2455	fn local_reserve_transfer_programs(
2456		origin: Location,
2457		dest: Location,
2458		beneficiary: Either<Location, Xcm<()>>,
2459		assets: Vec<Asset>,
2460		fees: FeesHandling<T>,
2461		weight_limit: WeightLimit,
2462	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2463		let value = (origin, assets);
2464		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2465		let (_, assets) = value;
2466
2467		// max assets is `assets` (+ potentially separately handled fee)
2468		let max_assets =
2469			assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2470		let assets: Assets = assets.into();
2471		let context = T::UniversalLocation::get();
2472		let mut reanchored_assets = assets.clone();
2473		reanchored_assets
2474			.reanchor(&dest, &context)
2475			.map_err(|e| {
2476				tracing::error!(target: "xcm::pezpallet_xcm::local_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2477				Error::<T>::CannotReanchor
2478			})?;
2479
2480		// XCM instructions to be executed on local chain
2481		let mut local_execute_xcm = Xcm(vec![
2482			// locally move `assets` to `dest`s local sovereign account
2483			TransferAsset { assets, beneficiary: dest.clone() },
2484		]);
2485		// XCM instructions to be executed on destination chain
2486		let mut xcm_on_dest = Xcm(vec![
2487			// let (dest) chain know assets are in its SA on reserve
2488			ReserveAssetDeposited(reanchored_assets),
2489			// following instructions are not exec'ed on behalf of origin chain anymore
2490			ClearOrigin,
2491		]);
2492		// handle fees
2493		Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2494
2495		// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
2496		let custom_remote_xcm = match beneficiary {
2497			Either::Right(custom_xcm) => custom_xcm,
2498			Either::Left(beneficiary) => {
2499				// deposit all remaining assets in holding to `beneficiary` location
2500				Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2501			},
2502		};
2503		xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2504
2505		Ok((local_execute_xcm, xcm_on_dest))
2506	}
2507
2508	fn destination_reserve_fees_instructions(
2509		origin: Location,
2510		dest: Location,
2511		fees: Asset,
2512		weight_limit: WeightLimit,
2513	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2514		let value = (origin, vec![fees.clone()]);
2515		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2516		ensure!(
2517			<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&fees, &dest),
2518			Error::<T>::InvalidAssetUnsupportedReserve
2519		);
2520
2521		let context = T::UniversalLocation::get();
2522		let reanchored_fees = fees
2523			.clone()
2524			.reanchored(&dest, &context)
2525			.map_err(|e| {
2526				tracing::error!(target: "xcm::pezpallet_xcm::destination_reserve_fees_instructions", ?e, ?dest,?context, "Failed to re-anchor fees");
2527				Error::<T>::CannotReanchor
2528			})?;
2529		let fees: Assets = fees.into();
2530
2531		let local_execute_xcm = Xcm(vec![
2532			// withdraw reserve-based fees (derivatives)
2533			WithdrawAsset(fees.clone()),
2534			// burn derivatives
2535			BurnAsset(fees),
2536		]);
2537		let xcm_on_dest = Xcm(vec![
2538			// withdraw `fees` from origin chain's sovereign account
2539			WithdrawAsset(reanchored_fees.clone().into()),
2540			// buy exec using `fees` in holding withdrawn in above instruction
2541			BuyExecution { fees: reanchored_fees, weight_limit },
2542		]);
2543		Ok((local_execute_xcm, xcm_on_dest))
2544	}
2545
2546	fn destination_reserve_transfer_programs(
2547		origin: Location,
2548		dest: Location,
2549		beneficiary: Either<Location, Xcm<()>>,
2550		assets: Vec<Asset>,
2551		fees: FeesHandling<T>,
2552		weight_limit: WeightLimit,
2553	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2554		let value = (origin, assets);
2555		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2556		let (_, assets) = value;
2557		for asset in assets.iter() {
2558			ensure!(
2559				<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&asset, &dest),
2560				Error::<T>::InvalidAssetUnsupportedReserve
2561			);
2562		}
2563
2564		// max assets is `assets` (+ potentially separately handled fee)
2565		let max_assets =
2566			assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2567		let assets: Assets = assets.into();
2568		let context = T::UniversalLocation::get();
2569		let mut reanchored_assets = assets.clone();
2570		reanchored_assets
2571			.reanchor(&dest, &context)
2572			.map_err(|e| {
2573				tracing::error!(target: "xcm::pezpallet_xcm::destination_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
2574				Error::<T>::CannotReanchor
2575			})?;
2576
2577		// XCM instructions to be executed on local chain
2578		let mut local_execute_xcm = Xcm(vec![
2579			// withdraw reserve-based assets
2580			WithdrawAsset(assets.clone()),
2581			// burn reserve-based assets
2582			BurnAsset(assets),
2583		]);
2584		// XCM instructions to be executed on destination chain
2585		let mut xcm_on_dest = Xcm(vec![
2586			// withdraw `assets` from origin chain's sovereign account
2587			WithdrawAsset(reanchored_assets),
2588			// following instructions are not exec'ed on behalf of origin chain anymore
2589			ClearOrigin,
2590		]);
2591		// handle fees
2592		Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2593
2594		// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
2595		let custom_remote_xcm = match beneficiary {
2596			Either::Right(custom_xcm) => custom_xcm,
2597			Either::Left(beneficiary) => {
2598				// deposit all remaining assets in holding to `beneficiary` location
2599				Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2600			},
2601		};
2602		xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2603
2604		Ok((local_execute_xcm, xcm_on_dest))
2605	}
2606
2607	// function assumes fees and assets have the same remote reserve
2608	fn remote_reserve_transfer_program(
2609		origin: Location,
2610		reserve: Location,
2611		beneficiary: Either<Location, Xcm<()>>,
2612		dest: Location,
2613		assets: Vec<Asset>,
2614		fees: Asset,
2615		weight_limit: WeightLimit,
2616	) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
2617		let value = (origin, assets);
2618		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
2619		let (_, assets) = value;
2620
2621		let max_assets = assets.len() as u32;
2622		let context = T::UniversalLocation::get();
2623		// we spend up to half of fees for execution on reserve and other half for execution on
2624		// destination
2625		let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
2626		// identifies fee item as seen by `reserve` - to be used at reserve chain
2627		let reserve_fees = fees_half_1
2628			.reanchored(&reserve, &context)
2629			.map_err(|e| {
2630				tracing::error!(target: "xcm::pezpallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor reserve_fees");
2631				Error::<T>::CannotReanchor
2632			})?;
2633		// identifies fee item as seen by `dest` - to be used at destination chain
2634		let dest_fees = fees_half_2
2635			.reanchored(&dest, &context)
2636			.map_err(|e| {
2637				tracing::error!(target: "xcm::pezpallet_xcm::remote_reserve_transfer_program", ?e, ?dest, ?context, "Failed to re-anchor dest_fees");
2638				Error::<T>::CannotReanchor
2639			})?;
2640		// identifies `dest` as seen by `reserve`
2641		let dest = dest.reanchored(&reserve, &context).map_err(|e| {
2642			tracing::error!(target: "xcm::pezpallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor dest");
2643			Error::<T>::CannotReanchor
2644		})?;
2645		// xcm to be executed at dest
2646		let mut xcm_on_dest =
2647			Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
2648		// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
2649		let custom_xcm_on_dest = match beneficiary {
2650			Either::Right(custom_xcm) => custom_xcm,
2651			Either::Left(beneficiary) => {
2652				// deposit all remaining assets in holding to `beneficiary` location
2653				Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2654			},
2655		};
2656		xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
2657		// xcm to be executed on reserve
2658		let xcm_on_reserve = Xcm(vec![
2659			BuyExecution { fees: reserve_fees, weight_limit },
2660			DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
2661		]);
2662		Ok(Xcm(vec![
2663			WithdrawAsset(assets.into()),
2664			SetFeesMode { jit_withdraw: true },
2665			InitiateReserveWithdraw {
2666				assets: Wild(AllCounted(max_assets)),
2667				reserve,
2668				xcm: xcm_on_reserve,
2669			},
2670		]))
2671	}
2672
2673	fn teleport_fees_instructions(
2674		origin: Location,
2675		dest: Location,
2676		fees: Asset,
2677		weight_limit: WeightLimit,
2678	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2679		let value = (origin, vec![fees.clone()]);
2680		ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2681		ensure!(
2682			<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&fees, &dest),
2683			Error::<T>::Filtered
2684		);
2685
2686		let context = T::UniversalLocation::get();
2687		let reanchored_fees = fees
2688			.clone()
2689			.reanchored(&dest, &context)
2690			.map_err(|e| {
2691				tracing::error!(target: "xcm::pezpallet_xcm::teleport_fees_instructions", ?e, ?dest, ?context, "Failed to re-anchor fees");
2692				Error::<T>::CannotReanchor
2693			})?;
2694
2695		// XcmContext irrelevant in teleports checks
2696		let dummy_context =
2697			XcmContext { origin: None, message_id: Default::default(), topic: None };
2698		// We should check that the asset can actually be teleported out (for this to
2699		// be in error, there would need to be an accounting violation by ourselves,
2700		// so it's unlikely, but we don't want to allow that kind of bug to leak into
2701		// a trusted chain.
2702		<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2703			&dest,
2704			&fees,
2705			&dummy_context,
2706		)
2707		.map_err(|e| {
2708			tracing::error!(target: "xcm::pezpallet_xcm::teleport_fees_instructions", ?e, ?fees, ?dest, "Failed can_check_out");
2709			Error::<T>::CannotCheckOutTeleport
2710		})?;
2711		// safe to do this here, we're in a transactional call that will be reverted on any
2712		// errors down the line
2713		<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2714			&dest,
2715			&fees,
2716			&dummy_context,
2717		);
2718
2719		let fees: Assets = fees.into();
2720		let local_execute_xcm = Xcm(vec![
2721			// withdraw fees
2722			WithdrawAsset(fees.clone()),
2723			// burn fees
2724			BurnAsset(fees),
2725		]);
2726		let xcm_on_dest = Xcm(vec![
2727			// (dest) chain receive teleported assets burned on origin chain
2728			ReceiveTeleportedAsset(reanchored_fees.clone().into()),
2729			// buy exec using `fees` in holding received in above instruction
2730			BuyExecution { fees: reanchored_fees, weight_limit },
2731		]);
2732		Ok((local_execute_xcm, xcm_on_dest))
2733	}
2734
2735	fn teleport_assets_program(
2736		origin: Location,
2737		dest: Location,
2738		beneficiary: Either<Location, Xcm<()>>,
2739		assets: Vec<Asset>,
2740		fees: FeesHandling<T>,
2741		weight_limit: WeightLimit,
2742	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
2743		let value = (origin, assets);
2744		ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
2745		let (_, assets) = value;
2746		for asset in assets.iter() {
2747			ensure!(
2748				<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&asset, &dest),
2749				Error::<T>::Filtered
2750			);
2751		}
2752
2753		// max assets is `assets` (+ potentially separately handled fee)
2754		let max_assets =
2755			assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
2756		let context = T::UniversalLocation::get();
2757		let assets: Assets = assets.into();
2758		let mut reanchored_assets = assets.clone();
2759		reanchored_assets
2760			.reanchor(&dest, &context)
2761			.map_err(|e| {
2762				tracing::error!(target: "xcm::pezpallet_xcm::teleport_assets_program", ?e, ?dest, ?context, "Failed to re-anchor asset");
2763				Error::<T>::CannotReanchor
2764			})?;
2765
2766		// XcmContext irrelevant in teleports checks
2767		let dummy_context =
2768			XcmContext { origin: None, message_id: Default::default(), topic: None };
2769		for asset in assets.inner() {
2770			// We should check that the asset can actually be teleported out (for this to
2771			// be in error, there would need to be an accounting violation by ourselves,
2772			// so it's unlikely, but we don't want to allow that kind of bug to leak into
2773			// a trusted chain.
2774			<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
2775				&dest,
2776				asset,
2777				&dummy_context,
2778			)
2779			.map_err(|e| {
2780				tracing::error!(target: "xcm::pezpallet_xcm::teleport_assets_program", ?e, ?asset, ?dest, "Failed can_check_out asset");
2781				Error::<T>::CannotCheckOutTeleport
2782			})?;
2783		}
2784		for asset in assets.inner() {
2785			// safe to do this here, we're in a transactional call that will be reverted on any
2786			// errors down the line
2787			<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
2788				&dest,
2789				asset,
2790				&dummy_context,
2791			);
2792		}
2793
2794		// XCM instructions to be executed on local chain
2795		let mut local_execute_xcm = Xcm(vec![
2796			// withdraw assets to be teleported
2797			WithdrawAsset(assets.clone()),
2798			// burn assets on local chain
2799			BurnAsset(assets),
2800		]);
2801		// XCM instructions to be executed on destination chain
2802		let mut xcm_on_dest = Xcm(vec![
2803			// teleport `assets` in from origin chain
2804			ReceiveTeleportedAsset(reanchored_assets),
2805			// following instructions are not exec'ed on behalf of origin chain anymore
2806			ClearOrigin,
2807		]);
2808		// handle fees
2809		Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
2810
2811		// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
2812		let custom_remote_xcm = match beneficiary {
2813			Either::Right(custom_xcm) => custom_xcm,
2814			Either::Left(beneficiary) => {
2815				// deposit all remaining assets in holding to `beneficiary` location
2816				Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
2817			},
2818		};
2819		xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
2820
2821		Ok((local_execute_xcm, xcm_on_dest))
2822	}
2823
2824	/// Halve `fees` fungible amount.
2825	pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
2826		match fees.fun {
2827			Fungible(amount) => {
2828				let fee1 = amount.saturating_div(2);
2829				let fee2 = amount.saturating_sub(fee1);
2830				ensure!(fee1 > 0, Error::<T>::FeesNotMet);
2831				ensure!(fee2 > 0, Error::<T>::FeesNotMet);
2832				Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
2833			},
2834			NonFungible(_) => Err(Error::<T>::FeesNotMet),
2835		}
2836	}
2837
2838	/// Will always make progress, and will do its best not to use much more than `weight_cutoff`
2839	/// in doing so.
2840	pub(crate) fn lazy_migration(
2841		mut stage: VersionMigrationStage,
2842		weight_cutoff: Weight,
2843	) -> (Weight, Option<VersionMigrationStage>) {
2844		let mut weight_used = Weight::zero();
2845
2846		let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
2847		let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
2848		let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
2849		let vnt_notify_weight = T::WeightInfo::notify_current_targets();
2850		let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
2851		let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
2852		let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
2853
2854		use VersionMigrationStage::*;
2855
2856		if stage == MigrateSupportedVersion {
2857			// We assume that supported XCM version only ever increases, so just cycle through lower
2858			// XCM versioned from the current.
2859			for v in 0..XCM_VERSION {
2860				for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
2861					if let Ok(new_key) = old_key.into_latest() {
2862						SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
2863					}
2864					weight_used.saturating_accrue(sv_migrate_weight);
2865					if weight_used.any_gte(weight_cutoff) {
2866						return (weight_used, Some(stage));
2867					}
2868				}
2869			}
2870			stage = MigrateVersionNotifiers;
2871		}
2872		if stage == MigrateVersionNotifiers {
2873			for v in 0..XCM_VERSION {
2874				for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
2875					if let Ok(new_key) = old_key.into_latest() {
2876						VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
2877					}
2878					weight_used.saturating_accrue(vn_migrate_weight);
2879					if weight_used.any_gte(weight_cutoff) {
2880						return (weight_used, Some(stage));
2881					}
2882				}
2883			}
2884			stage = NotifyCurrentTargets(None);
2885		}
2886
2887		let xcm_version = T::AdvertisedXcmVersion::get();
2888
2889		if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
2890			let mut iter = match maybe_last_raw_key {
2891				Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
2892				None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
2893			};
2894			while let Some((key, value)) = iter.next() {
2895				let (query_id, max_weight, target_xcm_version) = value;
2896				let new_key: Location = match key.clone().try_into() {
2897					Ok(k) if target_xcm_version != xcm_version => k,
2898					_ => {
2899						// We don't early return here since we need to be certain that we
2900						// make some progress.
2901						weight_used.saturating_accrue(vnt_already_notified_weight);
2902						continue;
2903					},
2904				};
2905				let response = Response::Version(xcm_version);
2906				let message =
2907					Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
2908				let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2909					Ok((message_id, cost)) => {
2910						let value = (query_id, max_weight, xcm_version);
2911						VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
2912						Event::VersionChangeNotified {
2913							destination: new_key,
2914							result: xcm_version,
2915							cost,
2916							message_id,
2917						}
2918					},
2919					Err(e) => {
2920						VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
2921						Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
2922					},
2923				};
2924				Self::deposit_event(event);
2925				weight_used.saturating_accrue(vnt_notify_weight);
2926				if weight_used.any_gte(weight_cutoff) {
2927					let last = Some(iter.last_raw_key().into());
2928					return (weight_used, Some(NotifyCurrentTargets(last)));
2929				}
2930			}
2931			stage = MigrateAndNotifyOldTargets;
2932		}
2933		if stage == MigrateAndNotifyOldTargets {
2934			for v in 0..XCM_VERSION {
2935				for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
2936					let (query_id, max_weight, target_xcm_version) = value;
2937					let new_key = match Location::try_from(old_key.clone()) {
2938						Ok(k) => k,
2939						Err(()) => {
2940							Self::deposit_event(Event::NotifyTargetMigrationFail {
2941								location: old_key,
2942								query_id: value.0,
2943							});
2944							weight_used.saturating_accrue(vnt_migrate_fail_weight);
2945							if weight_used.any_gte(weight_cutoff) {
2946								return (weight_used, Some(stage));
2947							}
2948							continue;
2949						},
2950					};
2951
2952					let versioned_key = LatestVersionedLocation(&new_key);
2953					if target_xcm_version == xcm_version {
2954						VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
2955						weight_used.saturating_accrue(vnt_migrate_weight);
2956					} else {
2957						// Need to notify target.
2958						let response = Response::Version(xcm_version);
2959						let message = Xcm(vec![QueryResponse {
2960							query_id,
2961							response,
2962							max_weight,
2963							querier: None,
2964						}]);
2965						let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
2966							Ok((message_id, cost)) => {
2967								VersionNotifyTargets::<T>::insert(
2968									XCM_VERSION,
2969									versioned_key,
2970									(query_id, max_weight, xcm_version),
2971								);
2972								Event::VersionChangeNotified {
2973									destination: new_key,
2974									result: xcm_version,
2975									cost,
2976									message_id,
2977								}
2978							},
2979							Err(e) => Event::NotifyTargetSendFail {
2980								location: new_key,
2981								query_id,
2982								error: e.into(),
2983							},
2984						};
2985						Self::deposit_event(event);
2986						weight_used.saturating_accrue(vnt_notify_migrate_weight);
2987					}
2988					if weight_used.any_gte(weight_cutoff) {
2989						return (weight_used, Some(stage));
2990					}
2991				}
2992			}
2993		}
2994		(weight_used, None)
2995	}
2996
2997	/// Request that `dest` informs us of its version.
2998	pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
2999		let dest = dest.into();
3000		let versioned_dest = VersionedLocation::from(dest.clone());
3001		let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
3002		ensure!(!already, XcmError::InvalidLocation);
3003		let query_id = QueryCounter::<T>::mutate(|q| {
3004			let r = *q;
3005			q.saturating_inc();
3006			r
3007		});
3008		// TODO #3735: Correct weight.
3009		let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
3010		let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3011		Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
3012		VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
3013		let query_status =
3014			QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
3015		Queries::<T>::insert(query_id, query_status);
3016		Ok(())
3017	}
3018
3019	/// Request that `dest` ceases informing us of its version.
3020	pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
3021		let dest = dest.into();
3022		let versioned_dest = LatestVersionedLocation(&dest);
3023		let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
3024			.ok_or(XcmError::InvalidLocation)?;
3025		let (message_id, cost) =
3026			send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
3027		Self::deposit_event(Event::VersionNotifyUnrequested {
3028			destination: dest,
3029			cost,
3030			message_id,
3031		});
3032		Queries::<T>::remove(query_id);
3033		Ok(())
3034	}
3035
3036	/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
3037	/// location. The `fee_payer` is charged for the delivery unless `None` in which case fees
3038	/// are not charged (and instead borne by the chain).
3039	pub fn send_xcm(
3040		interior: impl Into<Junctions>,
3041		dest: impl Into<Location>,
3042		mut message: Xcm<()>,
3043	) -> Result<XcmHash, SendError> {
3044		let interior = interior.into();
3045		let local_origin = interior.clone().into();
3046		let dest = dest.into();
3047		let is_waived =
3048			<T::XcmExecutor as FeeManager>::is_waived(Some(&local_origin), FeeReason::ChargeFees);
3049		if interior != Junctions::Here {
3050			message.0.insert(0, DescendOrigin(interior.clone()));
3051		}
3052		tracing::debug!(target: "xcm::send_xcm", "{:?}, {:?}", dest.clone(), message.clone());
3053		let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
3054		if !is_waived {
3055			Self::charge_fees(local_origin, price).map_err(|e| {
3056				tracing::error!(
3057					target: "xcm::pezpallet_xcm::send_xcm",
3058					?e,
3059					"Charging fees failed with error",
3060				);
3061				SendError::Fees
3062			})?;
3063		}
3064		T::XcmRouter::deliver(ticket)
3065	}
3066
3067	pub fn check_account() -> T::AccountId {
3068		const ID: PalletId = PalletId(*b"py/xcmch");
3069		AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
3070	}
3071
3072	/// Dry-runs `call` with the given `origin`.
3073	///
3074	/// Returns not only the call result and events, but also the local XCM, if any,
3075	/// and any XCMs forwarded to other locations.
3076	/// Meant to be used in the `xcm_runtime_pezapis::dry_run::DryRunApi` runtime API.
3077	pub fn dry_run_call<Runtime, Router, OriginCaller, RuntimeCall>(
3078		origin: OriginCaller,
3079		call: RuntimeCall,
3080		result_xcms_version: XcmVersion,
3081	) -> Result<
3082		CallDryRunEffects<<Runtime as pezframe_system::Config>::RuntimeEvent>,
3083		XcmDryRunApiError,
3084	>
3085	where
3086		Runtime: crate::Config,
3087		Router: InspectMessageQueues,
3088		RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
3089		<RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
3090	{
3091		crate::Pezpallet::<Runtime>::set_record_xcm(true);
3092		// Clear other messages in queues...
3093		Router::clear_messages();
3094		// ...and reset events to make sure we only record events from current call.
3095		pezframe_system::Pezpallet::<Runtime>::reset_events();
3096		let result = call.dispatch(origin.into());
3097		crate::Pezpallet::<Runtime>::set_record_xcm(false);
3098		let local_xcm = crate::Pezpallet::<Runtime>::recorded_xcm()
3099			.map(|xcm| VersionedXcm::<()>::from(xcm).into_version(result_xcms_version))
3100			.transpose()
3101			.map_err(|()| {
3102				tracing::error!(
3103					target: "xcm::DryRunApi::dry_run_call",
3104					"Local xcm version conversion failed"
3105				);
3106
3107				XcmDryRunApiError::VersionedConversionFailed
3108			})?;
3109
3110		// Should only get messages from this call since we cleared previous ones.
3111		let forwarded_xcms =
3112			Self::convert_forwarded_xcms(result_xcms_version, Router::get_messages()).inspect_err(
3113				|error| {
3114					tracing::error!(
3115						target: "xcm::DryRunApi::dry_run_call",
3116						?error, "Forwarded xcms version conversion failed with error"
3117					);
3118				},
3119			)?;
3120		let events: Vec<<Runtime as pezframe_system::Config>::RuntimeEvent> =
3121			pezframe_system::Pezpallet::<Runtime>::read_events_no_consensus()
3122				.map(|record| record.event.clone())
3123				.collect();
3124		Ok(CallDryRunEffects {
3125			local_xcm: local_xcm.map(VersionedXcm::<()>::from),
3126			forwarded_xcms,
3127			emitted_events: events,
3128			execution_result: result,
3129		})
3130	}
3131
3132	/// Dry-runs `xcm` with the given `origin_location`.
3133	///
3134	/// Returns execution result, events, and any forwarded XCMs to other locations.
3135	/// Meant to be used in the `xcm_runtime_pezapis::dry_run::DryRunApi` runtime API.
3136	pub fn dry_run_xcm<Router>(
3137		origin_location: VersionedLocation,
3138		xcm: VersionedXcm<<T as Config>::RuntimeCall>,
3139	) -> Result<XcmDryRunEffects<<T as pezframe_system::Config>::RuntimeEvent>, XcmDryRunApiError>
3140	where
3141		Router: InspectMessageQueues,
3142	{
3143		let origin_location: Location = origin_location.try_into().map_err(|error| {
3144			tracing::error!(
3145				target: "xcm::DryRunApi::dry_run_xcm",
3146				?error, "Location version conversion failed with error"
3147			);
3148			XcmDryRunApiError::VersionedConversionFailed
3149		})?;
3150		let xcm_version = xcm.identify_version();
3151		let xcm: Xcm<<T as Config>::RuntimeCall> = xcm.try_into().map_err(|error| {
3152			tracing::error!(
3153				target: "xcm::DryRunApi::dry_run_xcm",
3154				?error, "Xcm version conversion failed with error"
3155			);
3156			XcmDryRunApiError::VersionedConversionFailed
3157		})?;
3158		let mut hash = xcm.using_encoded(pezsp_io::hashing::blake2_256);
3159
3160		// To make sure we only record events from current call.
3161		Router::clear_messages();
3162		pezframe_system::Pezpallet::<T>::reset_events();
3163
3164		let result = <T as Config>::XcmExecutor::prepare_and_execute(
3165			origin_location,
3166			xcm,
3167			&mut hash,
3168			Weight::MAX, // Max limit available for execution.
3169			Weight::zero(),
3170		);
3171		let forwarded_xcms = Self::convert_forwarded_xcms(xcm_version, Router::get_messages())
3172			.inspect_err(|error| {
3173				tracing::error!(
3174					target: "xcm::DryRunApi::dry_run_xcm",
3175					?error, "Forwarded xcms version conversion failed with error"
3176				);
3177			})?;
3178		let events: Vec<<T as pezframe_system::Config>::RuntimeEvent> =
3179			pezframe_system::Pezpallet::<T>::read_events_no_consensus()
3180				.map(|record| record.event.clone())
3181				.collect();
3182		Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
3183	}
3184
3185	fn convert_xcms(
3186		xcm_version: XcmVersion,
3187		xcms: Vec<VersionedXcm<()>>,
3188	) -> Result<Vec<VersionedXcm<()>>, ()> {
3189		xcms.into_iter()
3190			.map(|xcm| xcm.into_version(xcm_version))
3191			.collect::<Result<Vec<_>, ()>>()
3192	}
3193
3194	fn convert_forwarded_xcms(
3195		xcm_version: XcmVersion,
3196		forwarded_xcms: Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>,
3197	) -> Result<Vec<(VersionedLocation, Vec<VersionedXcm<()>>)>, XcmDryRunApiError> {
3198		forwarded_xcms
3199			.into_iter()
3200			.map(|(dest, forwarded_xcms)| {
3201				let dest = dest.into_version(xcm_version)?;
3202				let forwarded_xcms = Self::convert_xcms(xcm_version, forwarded_xcms)?;
3203
3204				Ok((dest, forwarded_xcms))
3205			})
3206			.collect::<Result<Vec<_>, ()>>()
3207			.map_err(|()| {
3208				tracing::debug!(
3209					target: "xcm::pezpallet_xcm::convert_forwarded_xcms",
3210					"Failed to convert VersionedLocation to requested version",
3211				);
3212				XcmDryRunApiError::VersionedConversionFailed
3213			})
3214	}
3215
3216	/// Given a list of asset ids, returns the correct API response for
3217	/// `XcmPaymentApi::query_acceptable_payment_assets`.
3218	///
3219	/// The assets passed in have to be supported for fee payment.
3220	pub fn query_acceptable_payment_assets(
3221		version: xcm::Version,
3222		asset_ids: Vec<AssetId>,
3223	) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
3224		Ok(asset_ids
3225			.into_iter()
3226			.map(|asset_id| VersionedAssetId::from(asset_id))
3227			.filter_map(|asset_id| asset_id.into_version(version).ok())
3228			.collect())
3229	}
3230
3231	pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
3232		let message = Xcm::<()>::try_from(message.clone())
3233			.map_err(|e| {
3234				tracing::debug!(target: "xcm::pezpallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message");
3235				XcmPaymentApiError::VersionedConversionFailed
3236			})?;
3237
3238		T::Weigher::weight(&mut message.clone().into(), Weight::MAX).map_err(|error| {
3239			tracing::debug!(target: "xcm::pezpallet_xcm::query_xcm_weight", ?error, ?message, "Error when querying XCM weight");
3240			XcmPaymentApiError::WeightNotComputable
3241		})
3242	}
3243
3244	/// Computes the weight cost using the provided `WeightTrader`.
3245	/// This function is supposed to be used ONLY in `XcmPaymentApi::query_weight_to_asset_fee`.
3246	///
3247	/// The provided `WeightTrader` must be the same as the one used in the XcmExecutor to ensure
3248	/// uniformity in the weight cost calculation.
3249	///
3250	/// NOTE: Currently this function uses a workaround that should be good enough for all practical
3251	/// uses: passes `u128::MAX / 2 == 2^127` of the specified asset to the `WeightTrader` as
3252	/// payment and computes the weight cost as the difference between this and the unspent amount.
3253	///
3254	/// Some weight traders could add the provided payment to some account's balance. However,
3255	/// it should practically never result in overflow because even currencies with a lot of decimal
3256	/// digits (say 18) usually have the total issuance of billions (`x * 10^9`) or trillions (`x *
3257	/// 10^12`) at max, much less than `2^127 / 10^18 =~ 1.7 * 10^20` (170 billion billion). Thus,
3258	/// any account's balance most likely holds less than `2^127`, so adding `2^127` won't result in
3259	/// `u128` overflow.
3260	pub fn query_weight_to_asset_fee<Trader: xcm_executor::traits::WeightTrader>(
3261		weight: Weight,
3262		asset: VersionedAssetId,
3263	) -> Result<u128, XcmPaymentApiError> {
3264		let asset: AssetId = asset.clone().try_into()
3265			.map_err(|e| {
3266				tracing::debug!(target: "xcm::pezpallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to convert versioned asset");
3267				XcmPaymentApiError::VersionedConversionFailed
3268			})?;
3269
3270		let max_amount = u128::MAX / 2;
3271		let max_payment: Asset = (asset.clone(), max_amount).into();
3272		let context = XcmContext::with_message_id(XcmHash::default());
3273
3274		// We return the unspent amount without affecting the state
3275		// as we used a big amount of the asset without any check.
3276		let unspent = with_transaction(|| {
3277			let mut trader = Trader::new();
3278			let result = trader.buy_weight(weight, max_payment.into(), &context)
3279				.map_err(|e| {
3280					tracing::error!(target: "xcm::pezpallet::query_weight_to_asset_fee", ?e, ?asset, "Failed to buy weight");
3281
3282					// Return something convertible to `DispatchError` as required by the `with_transaction` fn.
3283					DispatchError::Other("Failed to buy weight")
3284				});
3285
3286			TransactionOutcome::Rollback(result)
3287		}).map_err(|error| {
3288			tracing::debug!(target: "xcm::pezpallet::query_weight_to_asset_fee", ?error, "Failed to execute transaction");
3289			XcmPaymentApiError::AssetNotFound
3290		})?;
3291
3292		let Some(unspent) = unspent.fungible.get(&asset) else {
3293			tracing::error!(target: "xcm::pezpallet::query_weight_to_asset_fee", ?asset, "The trader didn't return the needed fungible asset");
3294			return Err(XcmPaymentApiError::AssetNotFound);
3295		};
3296
3297		let paid = max_amount - unspent;
3298		Ok(paid)
3299	}
3300
3301	/// Given a `destination` and XCM `message`, return assets to be charged as XCM delivery fees.
3302	///
3303	/// Meant to be called by the `XcmPaymentApi`.
3304	/// It's necessary to specify the asset in which fees are desired.
3305	///
3306	/// NOTE: Only use this if delivery fees consist of only 1 asset, else this function will error.
3307	pub fn query_delivery_fees<AssetExchanger: xcm_executor::traits::AssetExchange>(
3308		destination: VersionedLocation,
3309		message: VersionedXcm<()>,
3310		versioned_asset_id: VersionedAssetId,
3311	) -> Result<VersionedAssets, XcmPaymentApiError> {
3312		let result_version = destination.identify_version().max(message.identify_version());
3313
3314		let destination: Location = destination
3315			.clone()
3316			.try_into()
3317			.map_err(|e| {
3318				tracing::error!(target: "xcm::pezpallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination");
3319				XcmPaymentApiError::VersionedConversionFailed
3320			})?;
3321
3322		let message: Xcm<()> =
3323			message.clone().try_into().map_err(|e| {
3324				tracing::error!(target: "xcm::pezpallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message");
3325				XcmPaymentApiError::VersionedConversionFailed
3326			})?;
3327
3328		let (_, fees) = validate_send::<T::XcmRouter>(destination.clone(), message.clone()).map_err(|error| {
3329			tracing::error!(target: "xcm::pezpallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination");
3330			XcmPaymentApiError::Unroutable
3331		})?;
3332
3333		// This helper only works for routers that return 1 and only 1 asset for delivery fees.
3334		if fees.len() != 1 {
3335			return Err(XcmPaymentApiError::Unimplemented);
3336		}
3337
3338		let fee = fees.get(0).ok_or(XcmPaymentApiError::Unimplemented)?;
3339
3340		let asset_id = versioned_asset_id.clone().try_into().map_err(|()| {
3341			tracing::trace!(
3342				target: "xcm::xcm_runtime_pezapis::query_delivery_fees",
3343				"Failed to convert asset id: {versioned_asset_id:?}!"
3344			);
3345			XcmPaymentApiError::VersionedConversionFailed
3346		})?;
3347
3348		let assets_to_pay = if fee.id == asset_id {
3349			// If the fee asset is the same as the desired one, just return that.
3350			fees
3351		} else {
3352			// We get the fees in the desired asset.
3353			AssetExchanger::quote_exchange_price(
3354				&fees.into(),
3355				&(asset_id, Fungible(1)).into(),
3356				true, // Maximal.
3357			)
3358			.ok_or(XcmPaymentApiError::AssetNotFound)?
3359		};
3360
3361		VersionedAssets::from(assets_to_pay).into_version(result_version).map_err(|e| {
3362			tracing::trace!(
3363				target: "xcm::pezpallet_xcm::query_delivery_fees",
3364				?e,
3365				?result_version,
3366				"Failed to convert fees into desired version"
3367			);
3368			XcmPaymentApiError::VersionedConversionFailed
3369		})
3370	}
3371
3372	/// Given an Asset and a Location, returns if the provided location is a trusted reserve for the
3373	/// given asset.
3374	pub fn is_trusted_reserve(
3375		asset: VersionedAsset,
3376		location: VersionedLocation,
3377	) -> Result<bool, TrustedQueryApiError> {
3378		let location: Location = location.try_into().map_err(|e| {
3379			tracing::debug!(
3380				target: "xcm::pezpallet_xcm::is_trusted_reserve",
3381				?e, "Failed to convert versioned location",
3382			);
3383			TrustedQueryApiError::VersionedLocationConversionFailed
3384		})?;
3385
3386		let a: Asset = asset.try_into().map_err(|e| {
3387			tracing::debug!(
3388				target: "xcm::pezpallet_xcm::is_trusted_reserve",
3389				 ?e, "Failed to convert versioned asset",
3390			);
3391			TrustedQueryApiError::VersionedAssetConversionFailed
3392		})?;
3393
3394		Ok(<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&a, &location))
3395	}
3396
3397	/// Given an Asset and a Location, returns if the asset can be teleported to provided location.
3398	pub fn is_trusted_teleporter(
3399		asset: VersionedAsset,
3400		location: VersionedLocation,
3401	) -> Result<bool, TrustedQueryApiError> {
3402		let location: Location = location.try_into().map_err(|e| {
3403			tracing::debug!(
3404				target: "xcm::pezpallet_xcm::is_trusted_teleporter",
3405				?e, "Failed to convert versioned location",
3406			);
3407			TrustedQueryApiError::VersionedLocationConversionFailed
3408		})?;
3409		let a: Asset = asset.try_into().map_err(|e| {
3410			tracing::debug!(
3411				target: "xcm::pezpallet_xcm::is_trusted_teleporter",
3412				 ?e, "Failed to convert versioned asset",
3413			);
3414			TrustedQueryApiError::VersionedAssetConversionFailed
3415		})?;
3416		Ok(<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&a, &location))
3417	}
3418
3419	/// Returns locations allowed to alias into and act as `target`.
3420	pub fn authorized_aliasers(
3421		target: VersionedLocation,
3422	) -> Result<Vec<OriginAliaser>, AuthorizedAliasersApiError> {
3423		let desired_version = target.identify_version();
3424		// storage entries are always latest version
3425		let target: VersionedLocation = target.into_version(XCM_VERSION).map_err(|e| {
3426			tracing::debug!(
3427				target: "xcm::pezpallet_xcm::authorized_aliasers",
3428				?e, "Failed to convert versioned location",
3429			);
3430			AuthorizedAliasersApiError::LocationVersionConversionFailed
3431		})?;
3432		Ok(AuthorizedAliases::<T>::get(&target)
3433			.map(|authorized| {
3434				authorized
3435					.aliasers
3436					.into_iter()
3437					.filter_map(|aliaser| {
3438						let OriginAliaser { location, expiry } = aliaser;
3439						location
3440							.into_version(desired_version)
3441							.map(|location| OriginAliaser { location, expiry })
3442							.ok()
3443					})
3444					.collect()
3445			})
3446			.unwrap_or_default())
3447	}
3448
3449	/// Given an `origin` and a `target`, returns if the `origin` location was added by `target` as
3450	/// an authorized aliaser.
3451	///
3452	/// Effectively says whether `origin` is allowed to alias into and act as `target`.
3453	pub fn is_authorized_alias(
3454		origin: VersionedLocation,
3455		target: VersionedLocation,
3456	) -> Result<bool, AuthorizedAliasersApiError> {
3457		let desired_version = target.identify_version();
3458		let origin = origin.into_version(desired_version).map_err(|e| {
3459			tracing::debug!(
3460				target: "xcm::pezpallet_xcm::is_authorized_alias",
3461				?e, "mismatching origin and target versions",
3462			);
3463			AuthorizedAliasersApiError::LocationVersionConversionFailed
3464		})?;
3465		Ok(Self::authorized_aliasers(target)?.into_iter().any(|aliaser| {
3466			// `aliasers` and `origin` have already been transformed to `desired_version`, we
3467			// can just directly compare them.
3468			aliaser.location == origin
3469				&& aliaser
3470					.expiry
3471					.map(|expiry| {
3472						pezframe_system::Pezpallet::<T>::current_block_number()
3473							.saturated_into::<u64>()
3474							< expiry
3475					})
3476					.unwrap_or(true)
3477		}))
3478	}
3479
3480	/// Create a new expectation of a query response with the querier being here.
3481	fn do_new_query(
3482		responder: impl Into<Location>,
3483		maybe_notify: Option<(u8, u8)>,
3484		timeout: BlockNumberFor<T>,
3485		match_querier: impl Into<Location>,
3486	) -> u64 {
3487		QueryCounter::<T>::mutate(|q| {
3488			let r = *q;
3489			q.saturating_inc();
3490			Queries::<T>::insert(
3491				r,
3492				QueryStatus::Pending {
3493					responder: responder.into().into(),
3494					maybe_match_querier: Some(match_querier.into().into()),
3495					maybe_notify,
3496					timeout,
3497				},
3498			);
3499			r
3500		})
3501	}
3502
3503	/// Consume `message` and return another which is equivalent to it except that it reports
3504	/// back the outcome and dispatches `notify` on this chain.
3505	///
3506	/// - `message`: The message whose outcome should be reported.
3507	/// - `responder`: The origin from which a response should be expected.
3508	/// - `notify`: A dispatchable function which will be called once the outcome of `message` is
3509	///   known. It may be a dispatchable in any pezpallet of the local chain, but other than the
3510	///   usual origin, it must accept exactly two arguments: `query_id: QueryId` and `outcome:
3511	///   Response`, and in that order. It should expect that the origin is `Origin::Response` and
3512	///   will contain the responder's location.
3513	/// - `timeout`: The block number after which it is permissible for `notify` not to be called
3514	///   even if a response is received.
3515	///
3516	/// `report_outcome_notify` may return an error if the `responder` is not invertible.
3517	///
3518	/// It is assumed that the querier of the response will be `Here`.
3519	///
3520	/// NOTE: `notify` gets called as part of handling an incoming message, so it should be
3521	/// lightweight. Its weight is estimated during this function and stored ready for
3522	/// weighing `ReportOutcome` on the way back. If it turns out to be heavier once it returns
3523	/// then reporting the outcome will fail. Furthermore if the estimate is too high, then it
3524	/// may be put in the overweight queue and need to be manually executed.
3525	pub fn report_outcome_notify(
3526		message: &mut Xcm<()>,
3527		responder: impl Into<Location>,
3528		notify: impl Into<<T as Config>::RuntimeCall>,
3529		timeout: BlockNumberFor<T>,
3530	) -> Result<(), XcmError> {
3531		let responder = responder.into();
3532		let destination = T::UniversalLocation::get().invert_target(&responder).map_err(|()| {
3533			tracing::debug!(
3534				target: "xcm::pezpallet_xcm::report_outcome_notify",
3535				"Failed to invert responder location to universal location",
3536			);
3537			XcmError::LocationNotInvertible
3538		})?;
3539		let notify: <T as Config>::RuntimeCall = notify.into();
3540		let max_weight = notify.get_dispatch_info().call_weight;
3541		let query_id = Self::new_notify_query(responder, notify, timeout, Here);
3542		let response_info = QueryResponseInfo { destination, query_id, max_weight };
3543		let report_error = Xcm(vec![ReportError(response_info)]);
3544		message.0.insert(0, SetAppendix(report_error));
3545		Ok(())
3546	}
3547
3548	/// Attempt to create a new query ID and register it as a query that is yet to respond, and
3549	/// which will call a dispatchable when a response happens.
3550	pub fn new_notify_query(
3551		responder: impl Into<Location>,
3552		notify: impl Into<<T as Config>::RuntimeCall>,
3553		timeout: BlockNumberFor<T>,
3554		match_querier: impl Into<Location>,
3555	) -> u64 {
3556		let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
3557			"decode input is output of Call encode; Call guaranteed to have two enums; qed",
3558		);
3559		Self::do_new_query(responder, Some(notify), timeout, match_querier)
3560	}
3561
3562	/// Note that a particular destination to whom we would like to send a message is unknown
3563	/// and queue it for version discovery.
3564	fn note_unknown_version(dest: &Location) {
3565		tracing::trace!(
3566			target: "xcm::pezpallet_xcm::note_unknown_version",
3567			?dest, "XCM version is unknown for destination"
3568		);
3569		let versioned_dest = VersionedLocation::from(dest.clone());
3570		VersionDiscoveryQueue::<T>::mutate(|q| {
3571			if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
3572				// exists - just bump the count.
3573				q[index].1.saturating_inc();
3574			} else {
3575				let _ = q.try_push((versioned_dest, 1));
3576			}
3577		});
3578	}
3579
3580	/// Withdraw given `assets` from the given `location` and pay as XCM fees.
3581	///
3582	/// Fails if:
3583	/// - the `assets` are not known on this chain;
3584	/// - the `assets` cannot be withdrawn with that location as the Origin.
3585	fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
3586		T::XcmExecutor::charge_fees(location.clone(), assets.clone()).map_err(|error| {
3587			tracing::debug!(
3588				target: "xcm::pezpallet_xcm::charge_fees", ?error,
3589				"Failed to charge fees for location with assets",
3590			);
3591			Error::<T>::FeesNotMet
3592		})?;
3593		Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
3594		Ok(())
3595	}
3596
3597	/// Ensure the correctness of the state of this pezpallet.
3598	///
3599	/// This should be valid before and after each state transition of this pezpallet.
3600	///
3601	/// ## Invariants
3602	///
3603	/// All entries stored in the `SupportedVersion` / `VersionNotifiers` / `VersionNotifyTargets`
3604	/// need to be migrated to the `XCM_VERSION`. If they are not, then `CurrentMigration` has to be
3605	/// set.
3606	#[cfg(any(feature = "try-runtime", test))]
3607	pub fn do_try_state() -> Result<(), TryRuntimeError> {
3608		use migration::data::NeedsMigration;
3609
3610		// Take the minimum version between `SafeXcmVersion` and `latest - 1` and ensure that the
3611		// operational data is stored at least at that version, for example, to prevent issues when
3612		// removing older XCM versions.
3613		let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
3614		{
3615			XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
3616		} else {
3617			XCM_VERSION.saturating_sub(1)
3618		};
3619
3620		// check `Queries`
3621		ensure!(
3622			!Queries::<T>::iter_values()
3623				.any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3624			TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
3625		);
3626
3627		// check `LockedFungibles`
3628		ensure!(
3629			!LockedFungibles::<T>::iter_values()
3630				.any(|data| data.needs_migration(minimal_allowed_xcm_version)),
3631			TryRuntimeError::Other(
3632				"`LockedFungibles` data should be migrated to the higher xcm version!"
3633			)
3634		);
3635
3636		// check `RemoteLockedFungibles`
3637		ensure!(
3638			!RemoteLockedFungibles::<T>::iter()
3639				.any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version)
3640					|| data.needs_migration(minimal_allowed_xcm_version)),
3641			TryRuntimeError::Other(
3642				"`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
3643			)
3644		);
3645
3646		// if migration has been already scheduled, everything is ok and data will be eventually
3647		// migrated
3648		if CurrentMigration::<T>::exists() {
3649			return Ok(());
3650		}
3651
3652		// if migration has NOT been scheduled yet, we need to check all operational data
3653		for v in 0..XCM_VERSION {
3654			ensure!(
3655				SupportedVersion::<T>::iter_prefix(v).next().is_none(),
3656				TryRuntimeError::Other(
3657					"`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
3658				)
3659			);
3660			ensure!(
3661				VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
3662				TryRuntimeError::Other(
3663					"`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
3664				)
3665			);
3666			ensure!(
3667				VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
3668				TryRuntimeError::Other(
3669					"`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
3670				)
3671			);
3672		}
3673
3674		Ok(())
3675	}
3676}
3677
3678pub struct LockTicket<T: Config> {
3679	sovereign_account: T::AccountId,
3680	amount: BalanceOf<T>,
3681	unlocker: Location,
3682	item_index: Option<usize>,
3683}
3684
3685impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
3686	fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3687		use xcm_executor::traits::LockError::UnexpectedState;
3688		let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
3689		match self.item_index {
3690			Some(index) => {
3691				ensure!(locks.len() > index, UnexpectedState);
3692				ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
3693				locks[index].0 = locks[index].0.max(self.amount);
3694			},
3695			None => {
3696				locks.try_push((self.amount, self.unlocker.into())).map_err(
3697					|(balance, location)| {
3698						tracing::debug!(
3699							target: "xcm::pezpallet_xcm::enact", ?balance, ?location,
3700							"Failed to lock fungibles",
3701						);
3702						UnexpectedState
3703					},
3704				)?;
3705			},
3706		}
3707		LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3708		T::Currency::extend_lock(
3709			*b"py/xcmlk",
3710			&self.sovereign_account,
3711			self.amount,
3712			WithdrawReasons::all(),
3713		);
3714		Ok(())
3715	}
3716}
3717
3718pub struct UnlockTicket<T: Config> {
3719	sovereign_account: T::AccountId,
3720	amount: BalanceOf<T>,
3721	unlocker: Location,
3722}
3723
3724impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
3725	fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3726		use xcm_executor::traits::LockError::UnexpectedState;
3727		let mut locks =
3728			LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
3729		let mut maybe_remove_index = None;
3730		let mut locked = BalanceOf::<T>::zero();
3731		let mut found = false;
3732		// We could just as well do with an into_iter, filter_map and collect, however this way
3733		// avoids making an allocation.
3734		for (i, x) in locks.iter_mut().enumerate() {
3735			if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
3736				x.0 = x.0.saturating_sub(self.amount);
3737				if x.0.is_zero() {
3738					maybe_remove_index = Some(i);
3739				}
3740				found = true;
3741			}
3742			locked = locked.max(x.0);
3743		}
3744		ensure!(found, UnexpectedState);
3745		if let Some(remove_index) = maybe_remove_index {
3746			locks.swap_remove(remove_index);
3747		}
3748		LockedFungibles::<T>::insert(&self.sovereign_account, locks);
3749		let reasons = WithdrawReasons::all();
3750		T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
3751		Ok(())
3752	}
3753}
3754
3755pub struct ReduceTicket<T: Config> {
3756	key: (u32, T::AccountId, VersionedAssetId),
3757	amount: u128,
3758	locker: VersionedLocation,
3759	owner: VersionedLocation,
3760}
3761
3762impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
3763	fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
3764		use xcm_executor::traits::LockError::UnexpectedState;
3765		let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
3766		ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
3767		let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
3768		ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
3769		if new_amount == 0 {
3770			RemoteLockedFungibles::<T>::remove(&self.key);
3771		} else {
3772			record.amount = new_amount;
3773			RemoteLockedFungibles::<T>::insert(&self.key, &record);
3774		}
3775		Ok(())
3776	}
3777}
3778
3779impl<T: Config> xcm_executor::traits::AssetLock for Pezpallet<T> {
3780	type LockTicket = LockTicket<T>;
3781	type UnlockTicket = UnlockTicket<T>;
3782	type ReduceTicket = ReduceTicket<T>;
3783
3784	fn prepare_lock(
3785		unlocker: Location,
3786		asset: Asset,
3787		owner: Location,
3788	) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
3789		use xcm_executor::traits::LockError::*;
3790		let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3791		let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3792		ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
3793		let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3794		let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
3795		ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
3796		Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
3797	}
3798
3799	fn prepare_unlock(
3800		unlocker: Location,
3801		asset: Asset,
3802		owner: Location,
3803	) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
3804		use xcm_executor::traits::LockError::*;
3805		let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3806		let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
3807		let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
3808		let item_index =
3809			locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
3810		ensure!(locks[item_index].0 >= amount, NotLocked);
3811		Ok(UnlockTicket { sovereign_account, amount, unlocker })
3812	}
3813
3814	fn note_unlockable(
3815		locker: Location,
3816		asset: Asset,
3817		mut owner: Location,
3818	) -> Result<(), xcm_executor::traits::LockError> {
3819		use xcm_executor::traits::LockError::*;
3820		ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
3821		let amount = match asset.fun {
3822			Fungible(a) => a,
3823			NonFungible(_) => return Err(Unimplemented),
3824		};
3825		owner.remove_network_id();
3826		let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3827		let locker = locker.into();
3828		let owner = owner.into();
3829		let id: VersionedAssetId = asset.id.into();
3830		let key = (XCM_VERSION, account, id);
3831		let mut record =
3832			RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
3833		if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
3834			// Make sure that the new record wouldn't clobber any old data.
3835			ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
3836			record.consumers = old.consumers;
3837			record.amount = record.amount.max(old.amount);
3838		}
3839		RemoteLockedFungibles::<T>::insert(&key, record);
3840		Ok(())
3841	}
3842
3843	fn prepare_reduce_unlockable(
3844		locker: Location,
3845		asset: Asset,
3846		mut owner: Location,
3847	) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
3848		use xcm_executor::traits::LockError::*;
3849		let amount = match asset.fun {
3850			Fungible(a) => a,
3851			NonFungible(_) => return Err(Unimplemented),
3852		};
3853		owner.remove_network_id();
3854		let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
3855		let locker = locker.into();
3856		let owner = owner.into();
3857		let id: VersionedAssetId = asset.id.into();
3858		let key = (XCM_VERSION, sovereign_account, id);
3859
3860		let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
3861		// Make sure that the record contains what we expect and there's enough to unlock.
3862		ensure!(locker == record.locker && owner == record.owner, WouldClobber);
3863		ensure!(record.amount >= amount, NotEnoughLocked);
3864		ensure!(
3865			record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
3866			InUse
3867		);
3868		Ok(ReduceTicket { key, amount, locker, owner })
3869	}
3870}
3871
3872impl<T: Config> WrapVersion for Pezpallet<T> {
3873	fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
3874		dest: &Location,
3875		xcm: impl Into<VersionedXcm<RuntimeCall>>,
3876	) -> Result<VersionedXcm<RuntimeCall>, ()> {
3877		Self::get_version_for(dest)
3878			.or_else(|| {
3879				Self::note_unknown_version(dest);
3880				SafeXcmVersion::<T>::get()
3881			})
3882			.ok_or_else(|| {
3883				tracing::trace!(
3884					target: "xcm::pezpallet_xcm::wrap_version",
3885					?dest, "Could not determine a version to wrap XCM for destination",
3886				);
3887				()
3888			})
3889			.and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
3890	}
3891}
3892
3893impl<T: Config> GetVersion for Pezpallet<T> {
3894	fn get_version_for(dest: &Location) -> Option<XcmVersion> {
3895		SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
3896	}
3897}
3898
3899impl<T: Config> VersionChangeNotifier for Pezpallet<T> {
3900	/// Start notifying `location` should the XCM version of this chain change.
3901	///
3902	/// When it does, this type should ensure a `QueryResponse` message is sent with the given
3903	/// `query_id` & `max_weight` and with a `response` of `Response::Version`. This should happen
3904	/// until/unless `stop` is called with the correct `query_id`.
3905	///
3906	/// If the `location` has an ongoing notification and when this function is called, then an
3907	/// error should be returned.
3908	fn start(
3909		dest: &Location,
3910		query_id: QueryId,
3911		max_weight: Weight,
3912		_context: &XcmContext,
3913	) -> XcmResult {
3914		let versioned_dest = LatestVersionedLocation(dest);
3915		let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
3916		ensure!(!already, XcmError::InvalidLocation);
3917
3918		let xcm_version = T::AdvertisedXcmVersion::get();
3919		let response = Response::Version(xcm_version);
3920		let instruction = QueryResponse { query_id, response, max_weight, querier: None };
3921		let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
3922		Self::deposit_event(Event::<T>::VersionNotifyStarted {
3923			destination: dest.clone(),
3924			cost,
3925			message_id,
3926		});
3927
3928		let value = (query_id, max_weight, xcm_version);
3929		VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
3930		Ok(())
3931	}
3932
3933	/// Stop notifying `location` should the XCM change. This is a no-op if there was never a
3934	/// subscription.
3935	fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
3936		VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
3937		Ok(())
3938	}
3939
3940	/// Return true if a location is subscribed to XCM version changes.
3941	fn is_subscribed(dest: &Location) -> bool {
3942		let versioned_dest = LatestVersionedLocation(dest);
3943		VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
3944	}
3945}
3946
3947impl<T: Config> DropAssets for Pezpallet<T> {
3948	fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
3949		if assets.is_empty() {
3950			return Weight::zero();
3951		}
3952		let versioned = VersionedAssets::from(Assets::from(assets));
3953		let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
3954		AssetTraps::<T>::mutate(hash, |n| *n += 1);
3955		Self::deposit_event(Event::AssetsTrapped {
3956			hash,
3957			origin: origin.clone(),
3958			assets: versioned,
3959		});
3960		// TODO #3735: Put the real weight in there.
3961		Weight::zero()
3962	}
3963}
3964
3965impl<T: Config> ClaimAssets for Pezpallet<T> {
3966	fn claim_assets(
3967		origin: &Location,
3968		ticket: &Location,
3969		assets: &Assets,
3970		_context: &XcmContext,
3971	) -> bool {
3972		let mut versioned = VersionedAssets::from(assets.clone());
3973		match ticket.unpack() {
3974			(0, [GeneralIndex(i)]) => {
3975				versioned = match versioned.into_version(*i as u32) {
3976					Ok(v) => v,
3977					Err(()) => return false,
3978				}
3979			},
3980			(0, []) => (),
3981			_ => return false,
3982		};
3983		let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
3984		match AssetTraps::<T>::get(hash) {
3985			0 => return false,
3986			1 => AssetTraps::<T>::remove(hash),
3987			n => AssetTraps::<T>::insert(hash, n - 1),
3988		}
3989		Self::deposit_event(Event::AssetsClaimed {
3990			hash,
3991			origin: origin.clone(),
3992			assets: versioned,
3993		});
3994		return true;
3995	}
3996}
3997
3998impl<T: Config> OnResponse for Pezpallet<T> {
3999	fn expecting_response(
4000		origin: &Location,
4001		query_id: QueryId,
4002		querier: Option<&Location>,
4003	) -> bool {
4004		match Queries::<T>::get(query_id) {
4005			Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) => {
4006				Location::try_from(responder).map_or(false, |r| origin == &r)
4007					&& maybe_match_querier.map_or(true, |match_querier| {
4008						Location::try_from(match_querier).map_or(false, |match_querier| {
4009							querier.map_or(false, |q| q == &match_querier)
4010						})
4011					})
4012			},
4013			Some(QueryStatus::VersionNotifier { origin: r, .. }) => {
4014				Location::try_from(r).map_or(false, |r| origin == &r)
4015			},
4016			_ => false,
4017		}
4018	}
4019
4020	fn on_response(
4021		origin: &Location,
4022		query_id: QueryId,
4023		querier: Option<&Location>,
4024		response: Response,
4025		max_weight: Weight,
4026		_context: &XcmContext,
4027	) -> Weight {
4028		let origin = origin.clone();
4029		match (response, Queries::<T>::get(query_id)) {
4030			(
4031				Response::Version(v),
4032				Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
4033			) => {
4034				let origin: Location = match expected_origin.try_into() {
4035					Ok(o) if o == origin => o,
4036					Ok(o) => {
4037						Self::deposit_event(Event::InvalidResponder {
4038							origin: origin.clone(),
4039							query_id,
4040							expected_location: Some(o),
4041						});
4042						return Weight::zero();
4043					},
4044					_ => {
4045						Self::deposit_event(Event::InvalidResponder {
4046							origin: origin.clone(),
4047							query_id,
4048							expected_location: None,
4049						});
4050						// TODO #3735: Correct weight for this.
4051						return Weight::zero();
4052					},
4053				};
4054				// TODO #3735: Check max_weight is correct.
4055				if !is_active {
4056					Queries::<T>::insert(
4057						query_id,
4058						QueryStatus::VersionNotifier {
4059							origin: origin.clone().into(),
4060							is_active: true,
4061						},
4062					);
4063				}
4064				// We're being notified of a version change.
4065				SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
4066				Self::deposit_event(Event::SupportedVersionChanged {
4067					location: origin,
4068					version: v,
4069				});
4070				Weight::zero()
4071			},
4072			(
4073				response,
4074				Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
4075			) => {
4076				if let Some(match_querier) = maybe_match_querier {
4077					let match_querier = match Location::try_from(match_querier) {
4078						Ok(mq) => mq,
4079						Err(_) => {
4080							Self::deposit_event(Event::InvalidQuerierVersion {
4081								origin: origin.clone(),
4082								query_id,
4083							});
4084							return Weight::zero();
4085						},
4086					};
4087					if querier.map_or(true, |q| q != &match_querier) {
4088						Self::deposit_event(Event::InvalidQuerier {
4089							origin: origin.clone(),
4090							query_id,
4091							expected_querier: match_querier,
4092							maybe_actual_querier: querier.cloned(),
4093						});
4094						return Weight::zero();
4095					}
4096				}
4097				let responder = match Location::try_from(responder) {
4098					Ok(r) => r,
4099					Err(_) => {
4100						Self::deposit_event(Event::InvalidResponderVersion {
4101							origin: origin.clone(),
4102							query_id,
4103						});
4104						return Weight::zero();
4105					},
4106				};
4107				if origin != responder {
4108					Self::deposit_event(Event::InvalidResponder {
4109						origin: origin.clone(),
4110						query_id,
4111						expected_location: Some(responder),
4112					});
4113					return Weight::zero();
4114				}
4115				match maybe_notify {
4116					Some((pezpallet_index, call_index)) => {
4117						// This is a bit horrible, but we happen to know that the `Call` will
4118						// be built by `(pezpallet_index: u8, call_index: u8, QueryId, Response)`.
4119						// So we just encode that and then re-encode to a real Call.
4120						let bare = (pezpallet_index, call_index, query_id, response);
4121						if let Ok(call) = bare.using_encoded(|mut bytes| {
4122							<T as Config>::RuntimeCall::decode(&mut bytes)
4123						}) {
4124							Queries::<T>::remove(query_id);
4125							let weight = call.get_dispatch_info().call_weight;
4126							if weight.any_gt(max_weight) {
4127								let e = Event::NotifyOverweight {
4128									query_id,
4129									pezpallet_index,
4130									call_index,
4131									actual_weight: weight,
4132									max_budgeted_weight: max_weight,
4133								};
4134								Self::deposit_event(e);
4135								return Weight::zero();
4136							}
4137							let dispatch_origin = Origin::Response(origin.clone()).into();
4138							match call.dispatch(dispatch_origin) {
4139								Ok(post_info) => {
4140									let e =
4141										Event::Notified { query_id, pezpallet_index, call_index };
4142									Self::deposit_event(e);
4143									post_info.actual_weight
4144								},
4145								Err(error_and_info) => {
4146									let e = Event::NotifyDispatchError {
4147										query_id,
4148										pezpallet_index,
4149										call_index,
4150									};
4151									Self::deposit_event(e);
4152									// Not much to do with the result as it is. It's up to the
4153									// teyrchain to ensure that the message makes sense.
4154									error_and_info.post_info.actual_weight
4155								},
4156							}
4157							.unwrap_or(weight)
4158						} else {
4159							let e =
4160								Event::NotifyDecodeFailed { query_id, pezpallet_index, call_index };
4161							Self::deposit_event(e);
4162							Weight::zero()
4163						}
4164					},
4165					None => {
4166						let e = Event::ResponseReady { query_id, response: response.clone() };
4167						Self::deposit_event(e);
4168						let at = pezframe_system::Pezpallet::<T>::current_block_number();
4169						let response = response.into();
4170						Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
4171						Weight::zero()
4172					},
4173				}
4174			},
4175			_ => {
4176				let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
4177				Self::deposit_event(e);
4178				Weight::zero()
4179			},
4180		}
4181	}
4182}
4183
4184impl<T: Config> CheckSuspension for Pezpallet<T> {
4185	fn is_suspended<Call>(
4186		_origin: &Location,
4187		_instructions: &mut [Instruction<Call>],
4188		_max_weight: Weight,
4189		_properties: &mut Properties,
4190	) -> bool {
4191		XcmExecutionSuspended::<T>::get()
4192	}
4193}
4194
4195impl<T: Config> RecordXcm for Pezpallet<T> {
4196	fn should_record() -> bool {
4197		ShouldRecordXcm::<T>::get()
4198	}
4199
4200	fn set_record_xcm(enabled: bool) {
4201		ShouldRecordXcm::<T>::put(enabled);
4202	}
4203
4204	fn recorded_xcm() -> Option<Xcm<()>> {
4205		RecordedXcm::<T>::get()
4206	}
4207
4208	fn record(xcm: Xcm<()>) {
4209		RecordedXcm::<T>::put(xcm);
4210	}
4211}
4212
4213/// Ensure that the origin `o` represents an XCM (`Transact`) origin.
4214///
4215/// Returns `Ok` with the location of the XCM sender or an `Err` otherwise.
4216pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4217where
4218	OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4219{
4220	match o.into() {
4221		Ok(Origin::Xcm(location)) => Ok(location),
4222		_ => Err(BadOrigin),
4223	}
4224}
4225
4226/// Ensure that the origin `o` represents an XCM response origin.
4227///
4228/// Returns `Ok` with the location of the responder or an `Err` otherwise.
4229pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
4230where
4231	OuterOrigin: Into<Result<Origin, OuterOrigin>>,
4232{
4233	match o.into() {
4234		Ok(Origin::Response(location)) => Ok(location),
4235		_ => Err(BadOrigin),
4236	}
4237}
4238
4239/// Filter for `(origin: Location, target: Location)` to find whether `target` has explicitly
4240/// authorized `origin` to alias it.
4241///
4242/// Note: users can authorize other locations to alias them by using
4243/// `pezpallet_xcm::add_authorized_alias()`.
4244pub struct AuthorizedAliasers<T>(PhantomData<T>);
4245impl<L: Into<VersionedLocation> + Clone, T: Config> ContainsPair<L, L> for AuthorizedAliasers<T> {
4246	fn contains(origin: &L, target: &L) -> bool {
4247		let origin: VersionedLocation = origin.clone().into();
4248		let target: VersionedLocation = target.clone().into();
4249		tracing::trace!(target: "xcm::pezpallet_xcm::AuthorizedAliasers::contains", ?origin, ?target);
4250		// return true if the `origin` has been explicitly authorized by `target` as aliaser, and
4251		// the authorization has not expired
4252		Pezpallet::<T>::is_authorized_alias(origin, target).unwrap_or(false)
4253	}
4254}
4255
4256/// Filter for `Location` to find those which represent a strict majority approval of an
4257/// identified plurality.
4258///
4259/// May reasonably be used with `EnsureXcm`.
4260pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4261impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
4262	for IsMajorityOfBody<Prefix, Body>
4263{
4264	fn contains(l: &Location) -> bool {
4265		let maybe_suffix = l.match_and_split(&Prefix::get());
4266		matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
4267	}
4268}
4269
4270/// Filter for `Location` to find those which represent a voice of an identified plurality.
4271///
4272/// May reasonably be used with `EnsureXcm`.
4273pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
4274impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
4275	fn contains(l: &Location) -> bool {
4276		let maybe_suffix = l.match_and_split(&Prefix::get());
4277		matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
4278	}
4279}
4280
4281/// `EnsureOrigin` implementation succeeding with a `Location` value to recognize and filter
4282/// the `Origin::Xcm` item.
4283pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
4284impl<
4285		O: OriginTrait + From<Origin>,
4286		F: Contains<L>,
4287		L: TryFrom<Location> + TryInto<Location> + Clone,
4288	> EnsureOrigin<O> for EnsureXcm<F, L>
4289where
4290	for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4291{
4292	type Success = L;
4293
4294	fn try_origin(outer: O) -> Result<Self::Success, O> {
4295		match outer.caller().try_into() {
4296			Ok(Origin::Xcm(ref location)) => {
4297				if let Ok(location) = location.clone().try_into() {
4298					if F::contains(&location) {
4299						return Ok(location);
4300					}
4301				}
4302			},
4303			_ => (),
4304		}
4305
4306		Err(outer)
4307	}
4308
4309	#[cfg(feature = "runtime-benchmarks")]
4310	fn try_successful_origin() -> Result<O, ()> {
4311		Ok(O::from(Origin::Xcm(Here.into())))
4312	}
4313}
4314
4315/// `EnsureOrigin` implementation succeeding with a `Location` value to recognize and filter
4316/// the `Origin::Response` item.
4317pub struct EnsureResponse<F>(PhantomData<F>);
4318impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
4319where
4320	for<'a> &'a O::PalletsOrigin: TryInto<&'a Origin>,
4321{
4322	type Success = Location;
4323
4324	fn try_origin(outer: O) -> Result<Self::Success, O> {
4325		match outer.caller().try_into() {
4326			Ok(Origin::Response(responder)) => return Ok(responder.clone()),
4327			_ => (),
4328		}
4329
4330		Err(outer)
4331	}
4332
4333	#[cfg(feature = "runtime-benchmarks")]
4334	fn try_successful_origin() -> Result<O, ()> {
4335		Ok(O::from(Origin::Response(Here.into())))
4336	}
4337}
4338
4339/// A simple passthrough where we reuse the `Location`-typed XCM origin as the inner value of
4340/// this crate's `Origin::Xcm` value.
4341pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
4342impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
4343	for XcmPassthrough<RuntimeOrigin>
4344{
4345	fn convert_origin(
4346		origin: impl Into<Location>,
4347		kind: OriginKind,
4348	) -> Result<RuntimeOrigin, Location> {
4349		let origin = origin.into();
4350		match kind {
4351			OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
4352			_ => Err(origin),
4353		}
4354	}
4355}