#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod migration;
extern crate alloc;
use alloc::{boxed::Box, vec, vec::Vec};
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use core::{marker::PhantomData, result::Result};
use frame_support::{
dispatch::{
DispatchErrorWithPostInfo, GetDispatchInfo, PostDispatchInfo, WithPostDispatchInfo,
},
pallet_prelude::*,
traits::{
Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency,
OriginTrait, WithdrawReasons,
},
PalletId,
};
use frame_system::pallet_prelude::{BlockNumberFor, *};
pub use pallet::*;
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash,
Saturating, Zero,
},
Either, RuntimeDebug,
};
use xcm::{latest::QueryResponseInfo, prelude::*};
use xcm_builder::{
ExecuteController, ExecuteControllerWeightInfo, InspectMessageQueues, QueryController,
QueryControllerWeightInfo, SendController, SendControllerWeightInfo,
};
use xcm_executor::{
traits::{
AssetTransferError, CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin,
DropAssets, MatchesFungible, OnResponse, Properties, QueryHandler, QueryResponseStatus,
RecordXcm, TransactAsset, TransferType, VersionChangeNotifier, WeightBounds,
XcmAssetTransfers,
},
AssetsInHolding,
};
use xcm_runtime_apis::{
dry_run::{CallDryRunEffects, Error as XcmDryRunApiError, XcmDryRunEffects},
fees::Error as XcmPaymentApiError,
trusted_query::Error as TrustedQueryApiError,
};
#[cfg(any(feature = "try-runtime", test))]
use sp_runtime::TryRuntimeError;
use xcm_executor::traits::{FeeManager, FeeReason};
pub trait WeightInfo {
fn send() -> Weight;
fn teleport_assets() -> Weight;
fn reserve_transfer_assets() -> Weight;
fn transfer_assets() -> Weight;
fn execute() -> Weight;
fn force_xcm_version() -> Weight;
fn force_default_xcm_version() -> Weight;
fn force_subscribe_version_notify() -> Weight;
fn force_unsubscribe_version_notify() -> Weight;
fn force_suspension() -> Weight;
fn migrate_supported_version() -> Weight;
fn migrate_version_notifiers() -> Weight;
fn already_notified_target() -> Weight;
fn notify_current_targets() -> Weight;
fn notify_target_migration_fail() -> Weight;
fn migrate_version_notify_targets() -> Weight;
fn migrate_and_notify_old_targets() -> Weight;
fn new_query() -> Weight;
fn take_response() -> Weight;
fn claim_assets() -> Weight;
}
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn send() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn teleport_assets() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn reserve_transfer_assets() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn transfer_assets() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn execute() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn force_xcm_version() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn force_default_xcm_version() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn force_subscribe_version_notify() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn force_unsubscribe_version_notify() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn force_suspension() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn migrate_supported_version() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn migrate_version_notifiers() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn already_notified_target() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn notify_current_targets() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn notify_target_migration_fail() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn migrate_version_notify_targets() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn migrate_and_notify_old_targets() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn new_query() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn take_response() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn claim_assets() -> Weight {
Weight::from_parts(100_000_000, 0)
}
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{
dispatch::{GetDispatchInfo, PostDispatchInfo},
parameter_types,
};
use frame_system::Config as SysConfig;
use sp_core::H256;
use sp_runtime::traits::Dispatchable;
use xcm_executor::traits::{MatchesFungible, WeightBounds};
parameter_types! {
pub const CurrentXcmVersion: u32 = XCM_VERSION;
}
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Currency: LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>;
type CurrencyMatcher: MatchesFungible<BalanceOf<Self>>;
type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
type XcmRouter: SendXcm;
type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
type XcmExecuteFilter: Contains<(Location, Xcm<<Self as Config>::RuntimeCall>)>;
type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall> + XcmAssetTransfers + FeeManager;
type XcmTeleportFilter: Contains<(Location, Vec<Asset>)>;
type XcmReserveTransferFilter: Contains<(Location, Vec<Asset>)>;
type Weigher: WeightBounds<<Self as Config>::RuntimeCall>;
type UniversalLocation: Get<InteriorLocation>;
type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
type RuntimeCall: Parameter
+ GetDispatchInfo
+ Dispatchable<
RuntimeOrigin = <Self as Config>::RuntimeOrigin,
PostInfo = PostDispatchInfo,
>;
const VERSION_DISCOVERY_QUEUE_SIZE: u32;
type AdvertisedXcmVersion: Get<XcmVersion>;
type AdminOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin>;
type TrustedLockers: ContainsPair<Location, Asset>;
type SovereignAccountOf: ConvertLocation<Self::AccountId>;
type MaxLockers: Get<u32>;
type MaxRemoteLockConsumers: Get<u32>;
type RemoteLockConsumerIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
type WeightInfo: WeightInfo;
}
impl<T: Config> ExecuteControllerWeightInfo for Pallet<T> {
fn execute() -> Weight {
T::WeightInfo::execute()
}
}
impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pallet<T> {
type WeightInfo = Self;
fn execute(
origin: OriginFor<T>,
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
max_weight: Weight,
) -> Result<Weight, DispatchErrorWithPostInfo> {
tracing::trace!(target: "xcm::pallet_xcm::execute", ?message, ?max_weight);
let outcome = (|| {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let value = (origin_location, message);
ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, message) = value;
Ok(T::XcmExecutor::prepare_and_execute(
origin_location,
message,
&mut hash,
max_weight,
max_weight,
))
})()
.map_err(|e: DispatchError| {
e.with_weight(<Self::WeightInfo as ExecuteControllerWeightInfo>::execute())
})?;
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
let weight_used = outcome.weight_used();
outcome.ensure_complete().map_err(|error| {
tracing::error!(target: "xcm::pallet_xcm::execute", ?error, "XCM execution failed with error");
Error::<T>::LocalExecutionIncomplete.with_weight(
weight_used.saturating_add(
<Self::WeightInfo as ExecuteControllerWeightInfo>::execute(),
),
)
})?;
Ok(weight_used)
}
}
impl<T: Config> SendControllerWeightInfo for Pallet<T> {
fn send() -> Weight {
T::WeightInfo::send()
}
}
impl<T: Config> SendController<OriginFor<T>> for Pallet<T> {
type WeightInfo = Self;
fn send(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
message: Box<VersionedXcm<()>>,
) -> Result<XcmHash, DispatchError> {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
let interior: Junctions =
origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = Location::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let message_id = Self::send_xcm(interior, dest.clone(), message.clone())
.map_err(|error| {
tracing::error!(target: "xcm::pallet_xcm::send", ?error, ?dest, ?message, "XCM send failed with error");
Error::<T>::from(error)
})?;
let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
Self::deposit_event(e);
Ok(message_id)
}
}
impl<T: Config> QueryControllerWeightInfo for Pallet<T> {
fn query() -> Weight {
T::WeightInfo::new_query()
}
fn take_response() -> Weight {
T::WeightInfo::take_response()
}
}
impl<T: Config> QueryController<OriginFor<T>, BlockNumberFor<T>> for Pallet<T> {
type WeightInfo = Self;
fn query(
origin: OriginFor<T>,
timeout: BlockNumberFor<T>,
match_querier: VersionedLocation,
) -> Result<QueryId, DispatchError> {
let responder = <T as Config>::ExecuteXcmOrigin::ensure_origin(origin)?;
let query_id = <Self as QueryHandler>::new_query(
responder,
timeout,
Location::try_from(match_querier)
.map_err(|_| Into::<DispatchError>::into(Error::<T>::BadVersion))?,
);
Ok(query_id)
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Attempted { outcome: xcm::latest::Outcome },
Sent { origin: Location, destination: Location, message: Xcm<()>, message_id: XcmHash },
UnexpectedResponse { origin: Location, query_id: QueryId },
ResponseReady { query_id: QueryId, response: Response },
Notified { query_id: QueryId, pallet_index: u8, call_index: u8 },
NotifyOverweight {
query_id: QueryId,
pallet_index: u8,
call_index: u8,
actual_weight: Weight,
max_budgeted_weight: Weight,
},
NotifyDispatchError { query_id: QueryId, pallet_index: u8, call_index: u8 },
NotifyDecodeFailed { query_id: QueryId, pallet_index: u8, call_index: u8 },
InvalidResponder {
origin: Location,
query_id: QueryId,
expected_location: Option<Location>,
},
InvalidResponderVersion { origin: Location, query_id: QueryId },
ResponseTaken { query_id: QueryId },
AssetsTrapped { hash: H256, origin: Location, assets: VersionedAssets },
VersionChangeNotified {
destination: Location,
result: XcmVersion,
cost: Assets,
message_id: XcmHash,
},
SupportedVersionChanged { location: Location, version: XcmVersion },
NotifyTargetSendFail { location: Location, query_id: QueryId, error: XcmError },
NotifyTargetMigrationFail { location: VersionedLocation, query_id: QueryId },
InvalidQuerierVersion { origin: Location, query_id: QueryId },
InvalidQuerier {
origin: Location,
query_id: QueryId,
expected_querier: Location,
maybe_actual_querier: Option<Location>,
},
VersionNotifyStarted { destination: Location, cost: Assets, message_id: XcmHash },
VersionNotifyRequested { destination: Location, cost: Assets, message_id: XcmHash },
VersionNotifyUnrequested { destination: Location, cost: Assets, message_id: XcmHash },
FeesPaid { paying: Location, fees: Assets },
AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets },
VersionMigrationFinished { version: XcmVersion },
}
#[pallet::origin]
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum Origin {
Xcm(Location),
Response(Location),
}
impl From<Location> for Origin {
fn from(location: Location) -> Origin {
Origin::Xcm(location)
}
}
#[pallet::error]
pub enum Error<T> {
Unreachable,
SendFailure,
Filtered,
UnweighableMessage,
DestinationNotInvertible,
Empty,
CannotReanchor,
TooManyAssets,
InvalidOrigin,
BadVersion,
BadLocation,
NoSubscription,
AlreadySubscribed,
CannotCheckOutTeleport,
LowBalance,
TooManyLocks,
AccountNotSovereign,
FeesNotMet,
LockNotFound,
InUse,
#[codec(index = 21)]
InvalidAssetUnknownReserve,
#[codec(index = 22)]
InvalidAssetUnsupportedReserve,
#[codec(index = 23)]
TooManyReserves,
#[codec(index = 24)]
LocalExecutionIncomplete,
}
impl<T: Config> From<SendError> for Error<T> {
fn from(e: SendError) -> Self {
match e {
SendError::Fees => Error::<T>::FeesNotMet,
SendError::NotApplicable => Error::<T>::Unreachable,
_ => Error::<T>::SendFailure,
}
}
}
impl<T: Config> From<AssetTransferError> for Error<T> {
fn from(e: AssetTransferError) -> Self {
match e {
AssetTransferError::UnknownReserve => Error::<T>::InvalidAssetUnknownReserve,
}
}
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum QueryStatus<BlockNumber> {
Pending {
responder: VersionedLocation,
maybe_match_querier: Option<VersionedLocation>,
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumber,
},
VersionNotifier { origin: VersionedLocation, is_active: bool },
Ready { response: VersionedResponse, at: BlockNumber },
}
#[derive(Copy, Clone)]
pub(crate) struct LatestVersionedLocation<'a>(pub(crate) &'a Location);
impl<'a> EncodeLike<VersionedLocation> for LatestVersionedLocation<'a> {}
impl<'a> Encode for LatestVersionedLocation<'a> {
fn encode(&self) -> Vec<u8> {
let mut r = VersionedLocation::from(Location::default()).encode();
r.truncate(1);
self.0.using_encoded(|d| r.extend_from_slice(d));
r
}
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
pub enum VersionMigrationStage {
MigrateSupportedVersion,
MigrateVersionNotifiers,
NotifyCurrentTargets(Option<Vec<u8>>),
MigrateAndNotifyOldTargets,
}
impl Default for VersionMigrationStage {
fn default() -> Self {
Self::MigrateSupportedVersion
}
}
#[pallet::storage]
pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn query)]
pub(super) type Queries<T: Config> =
StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<BlockNumberFor<T>>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn asset_trap)]
pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;
#[pallet::storage]
#[pallet::whitelist_storage]
pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
#[pallet::storage]
pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
XcmVersion,
Blake2_128Concat,
VersionedLocation,
XcmVersion,
OptionQuery,
>;
#[pallet::storage]
pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
XcmVersion,
Blake2_128Concat,
VersionedLocation,
QueryId,
OptionQuery,
>;
#[pallet::storage]
pub(super) type VersionNotifyTargets<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
XcmVersion,
Blake2_128Concat,
VersionedLocation,
(QueryId, Weight, XcmVersion),
OptionQuery,
>;
pub struct VersionDiscoveryQueueSize<T>(PhantomData<T>);
impl<T: Config> Get<u32> for VersionDiscoveryQueueSize<T> {
fn get() -> u32 {
T::VERSION_DISCOVERY_QUEUE_SIZE
}
}
#[pallet::storage]
#[pallet::whitelist_storage]
pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
_,
BoundedVec<(VersionedLocation, u32), VersionDiscoveryQueueSize<T>>,
ValueQuery,
>;
#[pallet::storage]
pub(super) type CurrentMigration<T: Config> =
StorageValue<_, VersionMigrationStage, OptionQuery>;
#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(MaxConsumers))]
pub struct RemoteLockedFungibleRecord<ConsumerIdentifier, MaxConsumers: Get<u32>> {
pub amount: u128,
pub owner: VersionedLocation,
pub locker: VersionedLocation,
pub consumers: BoundedVec<(ConsumerIdentifier, u128), MaxConsumers>,
}
impl<LockId, MaxConsumers: Get<u32>> RemoteLockedFungibleRecord<LockId, MaxConsumers> {
pub fn amount_held(&self) -> Option<u128> {
self.consumers.iter().max_by(|x, y| x.1.cmp(&y.1)).map(|max| max.1)
}
}
#[pallet::storage]
pub(super) type RemoteLockedFungibles<T: Config> = StorageNMap<
_,
(
NMapKey<Twox64Concat, XcmVersion>,
NMapKey<Blake2_128Concat, T::AccountId>,
NMapKey<Blake2_128Concat, VersionedAssetId>,
),
RemoteLockedFungibleRecord<T::RemoteLockConsumerIdentifier, T::MaxRemoteLockConsumers>,
OptionQuery,
>;
#[pallet::storage]
pub(super) type LockedFungibles<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
BoundedVec<(BalanceOf<T>, VersionedLocation), T::MaxLockers>,
OptionQuery,
>;
#[pallet::storage]
pub(super) type XcmExecutionSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
#[pallet::storage]
pub(crate) type ShouldRecordXcm<T: Config> = StorageValue<_, bool, ValueQuery>;
#[pallet::storage]
pub(crate) type RecordedXcm<T: Config> = StorageValue<_, Xcm<()>>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
#[serde(skip)]
pub _config: core::marker::PhantomData<T>,
pub safe_xcm_version: Option<XcmVersion>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { safe_xcm_version: Some(XCM_VERSION), _config: Default::default() }
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
SafeXcmVersion::<T>::set(self.safe_xcm_version);
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
let mut weight_used = Weight::zero();
if let Some(migration) = CurrentMigration::<T>::get() {
let max_weight = T::BlockWeights::get().max_block / 10;
let (w, maybe_migration) = Self::check_xcm_version_change(migration, max_weight);
if maybe_migration.is_none() {
Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION });
}
CurrentMigration::<T>::set(maybe_migration);
weight_used.saturating_accrue(w);
}
let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
q.sort_by_key(|i| i.1);
while let Some((versioned_dest, _)) = q.pop() {
if let Ok(dest) = Location::try_from(versioned_dest) {
if Self::request_version_notify(dest).is_ok() {
weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
break
}
}
}
if let Ok(q) = BoundedVec::try_from(q) {
VersionDiscoveryQueue::<T>::put(q);
}
weight_used
}
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
Self::do_try_state()
}
}
pub mod migrations {
use super::*;
use frame_support::traits::{PalletInfoAccess, StorageVersion};
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
enum QueryStatusV0<BlockNumber> {
Pending {
responder: VersionedLocation,
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumber,
},
VersionNotifier {
origin: VersionedLocation,
is_active: bool,
},
Ready {
response: VersionedResponse,
at: BlockNumber,
},
}
impl<B> From<QueryStatusV0<B>> for QueryStatus<B> {
fn from(old: QueryStatusV0<B>) -> Self {
use QueryStatusV0::*;
match old {
Pending { responder, maybe_notify, timeout } => QueryStatus::Pending {
responder,
maybe_notify,
timeout,
maybe_match_querier: Some(Location::here().into()),
},
VersionNotifier { origin, is_active } =>
QueryStatus::VersionNotifier { origin, is_active },
Ready { response, at } => QueryStatus::Ready { response, at },
}
}
}
pub fn migrate_to_v1<T: Config, P: GetStorageVersion + PalletInfoAccess>(
) -> frame_support::weights::Weight {
let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
tracing::info!(
target: "runtime::xcm",
?on_chain_storage_version,
"Running migration storage v1 for xcm with storage version",
);
if on_chain_storage_version < 1 {
let mut count = 0;
Queries::<T>::translate::<QueryStatusV0<BlockNumberFor<T>>, _>(|_key, value| {
count += 1;
Some(value.into())
});
StorageVersion::new(1).put::<P>();
tracing::info!(
target: "runtime::xcm",
?on_chain_storage_version,
"Running migration storage v1 for xcm with storage version was complete",
);
T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
} else {
tracing::warn!(
target: "runtime::xcm",
?on_chain_storage_version,
"Attempted to apply migration to v1 but failed because storage version is",
);
T::DbWeight::get().reads(1)
}
}
}
#[pallet::call(weight(<T as Config>::WeightInfo))]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
pub fn send(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
message: Box<VersionedXcm<()>>,
) -> DispatchResult {
<Self as SendController<_>>::send(origin, dest, message)?;
Ok(())
}
#[pallet::call_index(1)]
#[allow(deprecated)]
#[deprecated(
note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_teleport_assets` or `transfer_assets`"
)]
pub fn teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
) -> DispatchResult {
Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
}
#[pallet::call_index(2)]
#[allow(deprecated)]
#[deprecated(
note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_reserve_transfer_assets` or `transfer_assets`"
)]
pub fn reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
Unlimited,
)
}
#[pallet::call_index(3)]
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
pub fn execute(
origin: OriginFor<T>,
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
max_weight: Weight,
) -> DispatchResultWithPostInfo {
let weight_used =
<Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())
}
#[pallet::call_index(4)]
pub fn force_xcm_version(
origin: OriginFor<T>,
location: Box<Location>,
version: XcmVersion,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location = *location;
SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&location), version);
Self::deposit_event(Event::SupportedVersionChanged { location, version });
Ok(())
}
#[pallet::call_index(5)]
pub fn force_default_xcm_version(
origin: OriginFor<T>,
maybe_xcm_version: Option<XcmVersion>,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
SafeXcmVersion::<T>::set(maybe_xcm_version);
Ok(())
}
#[pallet::call_index(6)]
pub fn force_subscribe_version_notify(
origin: OriginFor<T>,
location: Box<VersionedLocation>,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location: Location =
(*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
Self::request_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
_ => Error::<T>::InvalidOrigin,
}
.into()
})
}
#[pallet::call_index(7)]
pub fn force_unsubscribe_version_notify(
origin: OriginFor<T>,
location: Box<VersionedLocation>,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
let location: Location =
(*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
Self::unrequest_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::<T>::NoSubscription,
_ => Error::<T>::InvalidOrigin,
}
.into()
})
}
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::reserve_transfer_assets())]
pub fn limited_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
weight_limit,
)
}
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::teleport_assets())]
pub fn limited_teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_teleport_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
weight_limit,
)
}
#[pallet::call_index(10)]
pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
XcmExecutionSuspended::<T>::set(suspended);
Ok(())
}
#[pallet::call_index(11)]
pub fn transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: Location =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
tracing::debug!(
target: "xcm::pallet_xcm::transfer_assets",
?origin, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
);
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let assets = assets.into_inner();
let fee_asset_item = fee_asset_item as usize;
let (fees_transfer_type, assets_transfer_type) =
Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
Self::do_transfer_assets(
origin,
dest,
Either::Left(beneficiary),
assets,
assets_transfer_type,
fee_asset_item,
fees_transfer_type,
weight_limit,
)
}
#[pallet::call_index(12)]
pub fn claim_assets(
origin: OriginFor<T>,
assets: Box<VersionedAssets>,
beneficiary: Box<VersionedLocation>,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
tracing::debug!(target: "xcm::pallet_xcm::claim_assets", ?origin_location, ?assets, ?beneficiary);
let assets_version = assets.identify_version();
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
let number_of_assets = assets.len() as u32;
let beneficiary: Location =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let ticket: Location = GeneralIndex(assets_version as u128).into();
let mut message = Xcm(vec![
ClaimAsset { assets, ticket },
DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
let outcome = T::XcmExecutor::prepare_and_execute(
origin_location,
message,
&mut hash,
weight,
weight,
);
outcome.ensure_complete().map_err(|error| {
tracing::error!(target: "xcm::pallet_xcm::claim_assets", ?error, "XCM execution failed with error");
Error::<T>::LocalExecutionIncomplete
})?;
Ok(())
}
#[pallet::call_index(13)]
#[pallet::weight(T::WeightInfo::transfer_assets())]
pub fn transfer_assets_using_type_and_then(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
assets_transfer_type: Box<TransferType>,
remote_fees_id: Box<VersionedAssetId>,
fees_transfer_type: Box<TransferType>,
custom_xcm_on_dest: Box<VersionedXcm<()>>,
weight_limit: WeightLimit,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
let fees_id: AssetId =
(*remote_fees_id).try_into().map_err(|()| Error::<T>::BadVersion)?;
let remote_xcm: Xcm<()> =
(*custom_xcm_on_dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
tracing::debug!(
target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
?origin_location, ?dest, ?assets, ?assets_transfer_type, ?fees_id, ?fees_transfer_type,
?remote_xcm, ?weight_limit,
);
let assets = assets.into_inner();
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let fee_asset_index =
assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
Self::do_transfer_assets(
origin_location,
dest,
Either::Right(remote_xcm),
assets,
*assets_transfer_type,
fee_asset_index,
*fees_transfer_type,
weight_limit,
)
}
}
}
const MAX_ASSETS_FOR_TRANSFER: usize = 2;
#[derive(Clone, PartialEq)]
enum FeesHandling<T: Config> {
Batched { fees: Asset },
Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
}
impl<T: Config> core::fmt::Debug for FeesHandling<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
Self::Separate { local_xcm, remote_xcm } => write!(
f,
"FeesHandling::Separate(local: {:?}, remote: {:?})",
local_xcm, remote_xcm
),
}
}
}
impl<T: Config> QueryHandler for Pallet<T> {
type BlockNumber = BlockNumberFor<T>;
type Error = XcmError;
type UniversalLocation = T::UniversalLocation;
fn new_query(
responder: impl Into<Location>,
timeout: BlockNumberFor<T>,
match_querier: impl Into<Location>,
) -> QueryId {
Self::do_new_query(responder, None, timeout, match_querier)
}
fn report_outcome(
message: &mut Xcm<()>,
responder: impl Into<Location>,
timeout: Self::BlockNumber,
) -> Result<QueryId, Self::Error> {
let responder = responder.into();
let destination = Self::UniversalLocation::get()
.invert_target(&responder)
.map_err(|()| XcmError::LocationNotInvertible)?;
let query_id = Self::new_query(responder, timeout, Here);
let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
let report_error = Xcm(vec![ReportError(response_info)]);
message.0.insert(0, SetAppendix(report_error));
Ok(query_id)
}
fn take_response(query_id: QueryId) -> QueryResponseStatus<Self::BlockNumber> {
match Queries::<T>::get(query_id) {
Some(QueryStatus::Ready { response, at }) => match response.try_into() {
Ok(response) => {
Queries::<T>::remove(query_id);
Self::deposit_event(Event::ResponseTaken { query_id });
QueryResponseStatus::Ready { response, at }
},
Err(_) => QueryResponseStatus::UnexpectedVersion,
},
Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
Some(_) => QueryResponseStatus::UnexpectedVersion,
None => QueryResponseStatus::NotFound,
}
}
#[cfg(feature = "runtime-benchmarks")]
fn expect_response(id: QueryId, response: Response) {
let response = response.into();
Queries::<T>::insert(
id,
QueryStatus::Ready { response, at: frame_system::Pallet::<T>::block_number() },
);
}
}
impl<T: Config> Pallet<T> {
fn find_fee_and_assets_transfer_types(
assets: &[Asset],
fee_asset_item: usize,
dest: &Location,
) -> Result<(TransferType, TransferType), Error<T>> {
let mut fees_transfer_type = None;
let mut assets_transfer_type = None;
for (idx, asset) in assets.iter().enumerate() {
if let Fungible(x) = asset.fun {
ensure!(!x.is_zero(), Error::<T>::Empty);
}
let transfer_type =
T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
if idx == fee_asset_item {
fees_transfer_type = Some(transfer_type);
} else {
if let Some(existing) = assets_transfer_type.as_ref() {
ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
} else {
assets_transfer_type = Some(transfer_type);
}
}
}
if assets.len() == 1 {
assets_transfer_type = fees_transfer_type.clone()
}
Ok((
fees_transfer_type.ok_or(Error::<T>::Empty)?,
assets_transfer_type.ok_or(Error::<T>::Empty)?,
))
}
fn do_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: Location =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
tracing::debug!(
target: "xcm::pallet_xcm::do_reserve_transfer_assets",
?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item,
);
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (origin, assets) = value;
let fee_asset_item = fee_asset_item as usize;
let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
let (fees_transfer_type, assets_transfer_type) =
Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin.clone(),
dest.clone(),
Either::Left(beneficiary),
assets,
assets_transfer_type,
FeesHandling::Batched { fees },
weight_limit,
)?;
Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
}
fn do_teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: Location =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
tracing::debug!(
target: "xcm::pallet_xcm::do_teleport_assets",
?origin_location, ?dest, ?beneficiary, ?assets, ?fee_asset_item, ?weight_limit,
);
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let value = (origin_location, assets.into_inner());
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, assets) = value;
for asset in assets.iter() {
let transfer_type =
T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
ensure!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
}
let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin_location.clone(),
dest.clone(),
Either::Left(beneficiary),
assets,
TransferType::Teleport,
FeesHandling::Batched { fees },
weight_limit,
)?;
Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
}
fn do_transfer_assets(
origin: Location,
dest: Location,
beneficiary: Either<Location, Xcm<()>>,
mut assets: Vec<Asset>,
assets_transfer_type: TransferType,
fee_asset_index: usize,
fees_transfer_type: TransferType,
weight_limit: WeightLimit,
) -> DispatchResult {
let fees = if fees_transfer_type == assets_transfer_type {
let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
FeesHandling::Batched { fees }
} else {
ensure!(
!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
Error::<T>::InvalidAssetUnsupportedReserve
);
let weight_limit = weight_limit.clone();
let fees = assets.remove(fee_asset_index);
let (local_xcm, remote_xcm) = match fees_transfer_type {
TransferType::LocalReserve => Self::local_reserve_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::Teleport => Self::teleport_fees_instructions(
origin.clone(),
dest.clone(),
fees,
weight_limit,
)?,
TransferType::RemoteReserve(_) =>
return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
};
FeesHandling::Separate { local_xcm, remote_xcm }
};
let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
origin.clone(),
dest.clone(),
beneficiary,
assets,
assets_transfer_type,
fees,
weight_limit,
)?;
Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
}
fn build_xcm_transfer_type(
origin: Location,
dest: Location,
beneficiary: Either<Location, Xcm<()>>,
assets: Vec<Asset>,
transfer_type: TransferType,
fees: FeesHandling<T>,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
tracing::debug!(
target: "xcm::pallet_xcm::build_xcm_transfer_type",
?origin, ?dest, ?beneficiary, ?assets, ?transfer_type, ?fees, ?weight_limit,
);
match transfer_type {
TransferType::LocalReserve => Self::local_reserve_transfer_programs(
origin.clone(),
dest.clone(),
beneficiary,
assets,
fees,
weight_limit,
)
.map(|(local, remote)| (local, Some(remote))),
TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
origin.clone(),
dest.clone(),
beneficiary,
assets,
fees,
weight_limit,
)
.map(|(local, remote)| (local, Some(remote))),
TransferType::RemoteReserve(reserve) => {
let fees = match fees {
FeesHandling::Batched { fees } => fees,
_ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
};
Self::remote_reserve_transfer_program(
origin.clone(),
reserve.try_into().map_err(|()| Error::<T>::BadVersion)?,
beneficiary,
dest.clone(),
assets,
fees,
weight_limit,
)
.map(|local| (local, None))
},
TransferType::Teleport => Self::teleport_assets_program(
origin.clone(),
dest.clone(),
beneficiary,
assets,
fees,
weight_limit,
)
.map(|(local, remote)| (local, Some(remote))),
}
}
fn execute_xcm_transfer(
origin: Location,
dest: Location,
mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
remote_xcm: Option<Xcm<()>>,
) -> DispatchResult {
tracing::debug!(
target: "xcm::pallet_xcm::execute_xcm_transfer",
?origin, ?dest, ?local_xcm, ?remote_xcm,
);
let weight =
T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
let outcome = T::XcmExecutor::prepare_and_execute(
origin.clone(),
local_xcm,
&mut hash,
weight,
weight,
);
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
outcome.clone().ensure_complete().map_err(|error| {
tracing::error!(
target: "xcm::pallet_xcm::execute_xcm_transfer",
?error, "XCM execution failed with error with outcome: {:?}", outcome
);
Error::<T>::LocalExecutionIncomplete
})?;
if let Some(remote_xcm) = remote_xcm {
let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
.map_err(|error| {
tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error");
Error::<T>::from(error)
})?;
if origin != Here.into_location() {
Self::charge_fees(origin.clone(), price.clone()).map_err(|error| {
tracing::error!(
target: "xcm::pallet_xcm::execute_xcm_transfer",
?error, ?price, ?origin, "Unable to charge fee",
);
Error::<T>::FeesNotMet
})?;
}
let message_id = T::XcmRouter::deliver(ticket)
.map_err(|error| {
tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error");
Error::<T>::from(error)
})?;
let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
Self::deposit_event(e);
}
Ok(())
}
fn add_fees_to_xcm(
dest: Location,
fees: FeesHandling<T>,
weight_limit: WeightLimit,
local: &mut Xcm<<T as Config>::RuntimeCall>,
remote: &mut Xcm<()>,
) -> Result<(), Error<T>> {
match fees {
FeesHandling::Batched { fees } => {
let context = T::UniversalLocation::get();
let reanchored_fees =
fees.reanchored(&dest, &context).map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::add_fees_to_xcm", ?e, ?dest, ?context, "Failed to re-anchor fees");
Error::<T>::CannotReanchor
})?;
remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
},
FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
core::mem::swap(local, &mut local_fees);
core::mem::swap(remote, &mut remote_fees);
local.inner_mut().append(&mut local_fees.into_inner());
remote.inner_mut().append(&mut remote_fees.into_inner());
},
}
Ok(())
}
fn local_reserve_fees_instructions(
origin: Location,
dest: Location,
fees: Asset,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, vec![fees.clone()]);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let context = T::UniversalLocation::get();
let reanchored_fees = fees
.clone()
.reanchored(&dest, &context)
.map_err(|_| Error::<T>::CannotReanchor)?;
let local_execute_xcm = Xcm(vec![
TransferAsset { assets: fees.into(), beneficiary: dest },
]);
let xcm_on_dest = Xcm(vec![
ReserveAssetDeposited(reanchored_fees.clone().into()),
BuyExecution { fees: reanchored_fees, weight_limit },
]);
Ok((local_execute_xcm, xcm_on_dest))
}
fn local_reserve_transfer_programs(
origin: Location,
dest: Location,
beneficiary: Either<Location, Xcm<()>>,
assets: Vec<Asset>,
fees: FeesHandling<T>,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, assets);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (_, assets) = value;
let max_assets =
assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
let assets: Assets = assets.into();
let context = T::UniversalLocation::get();
let mut reanchored_assets = assets.clone();
reanchored_assets
.reanchor(&dest, &context)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::local_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
Error::<T>::CannotReanchor
})?;
let mut local_execute_xcm = Xcm(vec![
TransferAsset { assets, beneficiary: dest.clone() },
]);
let mut xcm_on_dest = Xcm(vec![
ReserveAssetDeposited(reanchored_assets),
ClearOrigin,
]);
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
let custom_remote_xcm = match beneficiary {
Either::Right(custom_xcm) => custom_xcm,
Either::Left(beneficiary) => {
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
},
};
xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
Ok((local_execute_xcm, xcm_on_dest))
}
fn destination_reserve_fees_instructions(
origin: Location,
dest: Location,
fees: Asset,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, vec![fees.clone()]);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
ensure!(
<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&fees, &dest),
Error::<T>::InvalidAssetUnsupportedReserve
);
let context = T::UniversalLocation::get();
let reanchored_fees = fees
.clone()
.reanchored(&dest, &context)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::destination_reserve_fees_instructions", ?e, ?dest,?context, "Failed to re-anchor fees");
Error::<T>::CannotReanchor
})?;
let fees: Assets = fees.into();
let local_execute_xcm = Xcm(vec![
WithdrawAsset(fees.clone()),
BurnAsset(fees),
]);
let xcm_on_dest = Xcm(vec![
WithdrawAsset(reanchored_fees.clone().into()),
BuyExecution { fees: reanchored_fees, weight_limit },
]);
Ok((local_execute_xcm, xcm_on_dest))
}
fn destination_reserve_transfer_programs(
origin: Location,
dest: Location,
beneficiary: Either<Location, Xcm<()>>,
assets: Vec<Asset>,
fees: FeesHandling<T>,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, assets);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (_, assets) = value;
for asset in assets.iter() {
ensure!(
<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&asset, &dest),
Error::<T>::InvalidAssetUnsupportedReserve
);
}
let max_assets =
assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
let assets: Assets = assets.into();
let context = T::UniversalLocation::get();
let mut reanchored_assets = assets.clone();
reanchored_assets
.reanchor(&dest, &context)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::destination_reserve_transfer_programs", ?e, ?dest, ?context, "Failed to re-anchor assets");
Error::<T>::CannotReanchor
})?;
let mut local_execute_xcm = Xcm(vec![
WithdrawAsset(assets.clone()),
BurnAsset(assets),
]);
let mut xcm_on_dest = Xcm(vec![
WithdrawAsset(reanchored_assets),
ClearOrigin,
]);
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
let custom_remote_xcm = match beneficiary {
Either::Right(custom_xcm) => custom_xcm,
Either::Left(beneficiary) => {
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
},
};
xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
Ok((local_execute_xcm, xcm_on_dest))
}
fn remote_reserve_transfer_program(
origin: Location,
reserve: Location,
beneficiary: Either<Location, Xcm<()>>,
dest: Location,
assets: Vec<Asset>,
fees: Asset,
weight_limit: WeightLimit,
) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
let value = (origin, assets);
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (_, assets) = value;
let max_assets = assets.len() as u32;
let context = T::UniversalLocation::get();
let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
let reserve_fees = fees_half_1
.reanchored(&reserve, &context)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor reserve_fees");
Error::<T>::CannotReanchor
})?;
let dest_fees = fees_half_2
.reanchored(&dest, &context)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?dest, ?context, "Failed to re-anchor dest_fees");
Error::<T>::CannotReanchor
})?;
let dest = dest.reanchored(&reserve, &context).map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::remote_reserve_transfer_program", ?e, ?reserve, ?context, "Failed to re-anchor dest");
Error::<T>::CannotReanchor
})?;
let mut xcm_on_dest =
Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
let custom_xcm_on_dest = match beneficiary {
Either::Right(custom_xcm) => custom_xcm,
Either::Left(beneficiary) => {
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
},
};
xcm_on_dest.0.extend(custom_xcm_on_dest.into_iter());
let xcm_on_reserve = Xcm(vec![
BuyExecution { fees: reserve_fees, weight_limit },
DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
]);
Ok(Xcm(vec![
WithdrawAsset(assets.into()),
SetFeesMode { jit_withdraw: true },
InitiateReserveWithdraw {
assets: Wild(AllCounted(max_assets)),
reserve,
xcm: xcm_on_reserve,
},
]))
}
fn teleport_fees_instructions(
origin: Location,
dest: Location,
fees: Asset,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, vec![fees.clone()]);
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
ensure!(
<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&fees, &dest),
Error::<T>::Filtered
);
let context = T::UniversalLocation::get();
let reanchored_fees = fees
.clone()
.reanchored(&dest, &context)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?dest, ?context, "Failed to re-anchor fees");
Error::<T>::CannotReanchor
})?;
let dummy_context =
XcmContext { origin: None, message_id: Default::default(), topic: None };
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
&dest,
&fees,
&dummy_context,
)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::teleport_fees_instructions", ?e, ?fees, ?dest, "Failed can_check_out");
Error::<T>::CannotCheckOutTeleport
})?;
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
&dest,
&fees,
&dummy_context,
);
let fees: Assets = fees.into();
let local_execute_xcm = Xcm(vec![
WithdrawAsset(fees.clone()),
BurnAsset(fees),
]);
let xcm_on_dest = Xcm(vec![
ReceiveTeleportedAsset(reanchored_fees.clone().into()),
BuyExecution { fees: reanchored_fees, weight_limit },
]);
Ok((local_execute_xcm, xcm_on_dest))
}
fn teleport_assets_program(
origin: Location,
dest: Location,
beneficiary: Either<Location, Xcm<()>>,
assets: Vec<Asset>,
fees: FeesHandling<T>,
weight_limit: WeightLimit,
) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
let value = (origin, assets);
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
let (_, assets) = value;
for asset in assets.iter() {
ensure!(
<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&asset, &dest),
Error::<T>::Filtered
);
}
let max_assets =
assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
let context = T::UniversalLocation::get();
let assets: Assets = assets.into();
let mut reanchored_assets = assets.clone();
reanchored_assets
.reanchor(&dest, &context)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?dest, ?context, "Failed to re-anchor asset");
Error::<T>::CannotReanchor
})?;
let dummy_context =
XcmContext { origin: None, message_id: Default::default(), topic: None };
for asset in assets.inner() {
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
&dest,
asset,
&dummy_context,
)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::teleport_assets_program", ?e, ?asset, ?dest, "Failed can_check_out asset");
Error::<T>::CannotCheckOutTeleport
})?;
}
for asset in assets.inner() {
<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
&dest,
asset,
&dummy_context,
);
}
let mut local_execute_xcm = Xcm(vec![
WithdrawAsset(assets.clone()),
BurnAsset(assets),
]);
let mut xcm_on_dest = Xcm(vec![
ReceiveTeleportedAsset(reanchored_assets),
ClearOrigin,
]);
Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
let custom_remote_xcm = match beneficiary {
Either::Right(custom_xcm) => custom_xcm,
Either::Left(beneficiary) => {
Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
},
};
xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
Ok((local_execute_xcm, xcm_on_dest))
}
pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
match fees.fun {
Fungible(amount) => {
let fee1 = amount.saturating_div(2);
let fee2 = amount.saturating_sub(fee1);
ensure!(fee1 > 0, Error::<T>::FeesNotMet);
ensure!(fee2 > 0, Error::<T>::FeesNotMet);
Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
},
NonFungible(_) => Err(Error::<T>::FeesNotMet),
}
}
pub(crate) fn check_xcm_version_change(
mut stage: VersionMigrationStage,
weight_cutoff: Weight,
) -> (Weight, Option<VersionMigrationStage>) {
let mut weight_used = Weight::zero();
let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
let vnt_notify_weight = T::WeightInfo::notify_current_targets();
let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();
use VersionMigrationStage::*;
if stage == MigrateSupportedVersion {
for v in 0..XCM_VERSION {
for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
}
weight_used.saturating_accrue(sv_migrate_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
}
}
stage = MigrateVersionNotifiers;
}
if stage == MigrateVersionNotifiers {
for v in 0..XCM_VERSION {
for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
}
weight_used.saturating_accrue(vn_migrate_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
}
}
stage = NotifyCurrentTargets(None);
}
let xcm_version = T::AdvertisedXcmVersion::get();
if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
let mut iter = match maybe_last_raw_key {
Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
};
while let Some((key, value)) = iter.next() {
let (query_id, max_weight, target_xcm_version) = value;
let new_key: Location = match key.clone().try_into() {
Ok(k) if target_xcm_version != xcm_version => k,
_ => {
weight_used.saturating_accrue(vnt_already_notified_weight);
continue
},
};
let response = Response::Version(xcm_version);
let message =
Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
Ok((message_id, cost)) => {
let value = (query_id, max_weight, xcm_version);
VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
Event::VersionChangeNotified {
destination: new_key,
result: xcm_version,
cost,
message_id,
}
},
Err(e) => {
VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
},
};
Self::deposit_event(event);
weight_used.saturating_accrue(vnt_notify_weight);
if weight_used.any_gte(weight_cutoff) {
let last = Some(iter.last_raw_key().into());
return (weight_used, Some(NotifyCurrentTargets(last)))
}
}
stage = MigrateAndNotifyOldTargets;
}
if stage == MigrateAndNotifyOldTargets {
for v in 0..XCM_VERSION {
for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
let (query_id, max_weight, target_xcm_version) = value;
let new_key = match Location::try_from(old_key.clone()) {
Ok(k) => k,
Err(()) => {
Self::deposit_event(Event::NotifyTargetMigrationFail {
location: old_key,
query_id: value.0,
});
weight_used.saturating_accrue(vnt_migrate_fail_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
continue
},
};
let versioned_key = LatestVersionedLocation(&new_key);
if target_xcm_version == xcm_version {
VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
weight_used.saturating_accrue(vnt_migrate_weight);
} else {
let response = Response::Version(xcm_version);
let message = Xcm(vec![QueryResponse {
query_id,
response,
max_weight,
querier: None,
}]);
let event = match send_xcm::<T::XcmRouter>(new_key.clone(), message) {
Ok((message_id, cost)) => {
VersionNotifyTargets::<T>::insert(
XCM_VERSION,
versioned_key,
(query_id, max_weight, xcm_version),
);
Event::VersionChangeNotified {
destination: new_key,
result: xcm_version,
cost,
message_id,
}
},
Err(e) => Event::NotifyTargetSendFail {
location: new_key,
query_id,
error: e.into(),
},
};
Self::deposit_event(event);
weight_used.saturating_accrue(vnt_notify_migrate_weight);
}
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
}
}
}
(weight_used, None)
}
pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
let dest = dest.into();
let versioned_dest = VersionedLocation::from(dest.clone());
let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
ensure!(!already, XcmError::InvalidLocation);
let query_id = QueryCounter::<T>::mutate(|q| {
let r = *q;
q.saturating_inc();
r
});
let instruction = SubscribeVersion { query_id, max_response_weight: Weight::zero() };
let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
let query_status =
QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
Queries::<T>::insert(query_id, query_status);
Ok(())
}
pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
let dest = dest.into();
let versioned_dest = LatestVersionedLocation(&dest);
let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
.ok_or(XcmError::InvalidLocation)?;
let (message_id, cost) =
send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
Self::deposit_event(Event::VersionNotifyUnrequested {
destination: dest,
cost,
message_id,
});
Queries::<T>::remove(query_id);
Ok(())
}
pub fn send_xcm(
interior: impl Into<Junctions>,
dest: impl Into<Location>,
mut message: Xcm<()>,
) -> Result<XcmHash, SendError> {
let interior = interior.into();
let local_origin = interior.clone().into();
let dest = dest.into();
let is_waived =
<T::XcmExecutor as FeeManager>::is_waived(Some(&local_origin), FeeReason::ChargeFees);
if interior != Junctions::Here {
message.0.insert(0, DescendOrigin(interior.clone()));
}
tracing::debug!(target: "xcm::send_xcm", "{:?}, {:?}", dest.clone(), message.clone());
let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
if !is_waived {
Self::charge_fees(local_origin, price).map_err(|e| {
tracing::error!(
target: "xcm::pallet_xcm::send_xcm",
?e,
"Charging fees failed with error",
);
SendError::Fees
})?;
}
T::XcmRouter::deliver(ticket)
}
pub fn check_account() -> T::AccountId {
const ID: PalletId = PalletId(*b"py/xcmch");
AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
}
pub fn dry_run_call<Runtime, Router, OriginCaller, RuntimeCall>(
origin: OriginCaller,
call: RuntimeCall,
) -> Result<CallDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
where
Runtime: crate::Config,
Router: InspectMessageQueues,
RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo>,
<RuntimeCall as Dispatchable>::RuntimeOrigin: From<OriginCaller>,
{
crate::Pallet::<Runtime>::set_record_xcm(true);
Router::clear_messages();
frame_system::Pallet::<Runtime>::reset_events();
let result = call.dispatch(origin.into());
crate::Pallet::<Runtime>::set_record_xcm(false);
let local_xcm = crate::Pallet::<Runtime>::recorded_xcm();
let forwarded_xcms = Router::get_messages();
let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
frame_system::Pallet::<Runtime>::read_events_no_consensus()
.map(|record| record.event.clone())
.collect();
Ok(CallDryRunEffects {
local_xcm: local_xcm.map(VersionedXcm::<()>::from),
forwarded_xcms,
emitted_events: events,
execution_result: result,
})
}
pub fn dry_run_xcm<Runtime, Router, RuntimeCall: Decode + GetDispatchInfo, XcmConfig>(
origin_location: VersionedLocation,
xcm: VersionedXcm<RuntimeCall>,
) -> Result<XcmDryRunEffects<<Runtime as frame_system::Config>::RuntimeEvent>, XcmDryRunApiError>
where
Runtime: frame_system::Config,
Router: InspectMessageQueues,
XcmConfig: xcm_executor::Config<RuntimeCall = RuntimeCall>,
{
let origin_location: Location = origin_location.try_into().map_err(|error| {
tracing::error!(
target: "xcm::DryRunApi::dry_run_xcm",
?error, "Location version conversion failed with error"
);
XcmDryRunApiError::VersionedConversionFailed
})?;
let xcm: Xcm<RuntimeCall> = xcm.try_into().map_err(|error| {
tracing::error!(
target: "xcm::DryRunApi::dry_run_xcm",
?error, "Xcm version conversion failed with error"
);
XcmDryRunApiError::VersionedConversionFailed
})?;
let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256);
frame_system::Pallet::<Runtime>::reset_events(); let result = xcm_executor::XcmExecutor::<XcmConfig>::prepare_and_execute(
origin_location,
xcm,
&mut hash,
Weight::MAX, Weight::zero(),
);
let forwarded_xcms = Router::get_messages();
let events: Vec<<Runtime as frame_system::Config>::RuntimeEvent> =
frame_system::Pallet::<Runtime>::read_events_no_consensus()
.map(|record| record.event.clone())
.collect();
Ok(XcmDryRunEffects { forwarded_xcms, emitted_events: events, execution_result: result })
}
pub fn query_acceptable_payment_assets(
version: xcm::Version,
asset_ids: Vec<AssetId>,
) -> Result<Vec<VersionedAssetId>, XcmPaymentApiError> {
Ok(asset_ids
.into_iter()
.map(|asset_id| VersionedAssetId::from(asset_id))
.filter_map(|asset_id| asset_id.into_version(version).ok())
.collect())
}
pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, XcmPaymentApiError> {
let message = Xcm::<()>::try_from(message.clone())
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::query_xcm_weight", ?e, ?message, "Failed to convert versioned message");
XcmPaymentApiError::VersionedConversionFailed
})?;
T::Weigher::weight(&mut message.clone().into()).map_err(|()| {
tracing::error!(target: "xcm::pallet_xcm::query_xcm_weight", ?message, "Error when querying XCM weight");
XcmPaymentApiError::WeightNotComputable
})
}
pub fn is_trusted_reserve(
asset: VersionedAsset,
location: VersionedLocation,
) -> Result<bool, TrustedQueryApiError> {
let location: Location = location.try_into().map_err(|e| {
tracing::debug!(
target: "xcm::pallet_xcm::is_trusted_reserve",
"Asset version conversion failed with error: {:?}",
e,
);
TrustedQueryApiError::VersionedLocationConversionFailed
})?;
let a: Asset = asset.try_into().map_err(|e| {
tracing::debug!(
target: "xcm::pallet_xcm::is_trusted_reserve",
"Location version conversion failed with error: {:?}",
e,
);
TrustedQueryApiError::VersionedAssetConversionFailed
})?;
Ok(<T::XcmExecutor as XcmAssetTransfers>::IsReserve::contains(&a, &location))
}
pub fn is_trusted_teleporter(
asset: VersionedAsset,
location: VersionedLocation,
) -> Result<bool, TrustedQueryApiError> {
let location: Location = location.try_into().map_err(|e| {
tracing::debug!(
target: "xcm::pallet_xcm::is_trusted_teleporter",
"Asset version conversion failed with error: {:?}",
e,
);
TrustedQueryApiError::VersionedLocationConversionFailed
})?;
let a: Asset = asset.try_into().map_err(|e| {
tracing::debug!(
target: "xcm::pallet_xcm::is_trusted_teleporter",
"Location version conversion failed with error: {:?}",
e,
);
TrustedQueryApiError::VersionedAssetConversionFailed
})?;
Ok(<T::XcmExecutor as XcmAssetTransfers>::IsTeleporter::contains(&a, &location))
}
pub fn query_delivery_fees(
destination: VersionedLocation,
message: VersionedXcm<()>,
) -> Result<VersionedAssets, XcmPaymentApiError> {
let result_version = destination.identify_version().max(message.identify_version());
let destination: Location = destination
.clone()
.try_into()
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?destination, "Failed to convert versioned destination");
XcmPaymentApiError::VersionedConversionFailed
})?;
let message: Xcm<()> =
message.clone().try_into().map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?message, "Failed to convert versioned message");
XcmPaymentApiError::VersionedConversionFailed
})?;
let (_, fees) = validate_send::<T::XcmRouter>(destination.clone(), message.clone()).map_err(|error| {
tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?error, ?destination, ?message, "Failed to validate send to destination");
XcmPaymentApiError::Unroutable
})?;
VersionedAssets::from(fees)
.into_version(result_version)
.map_err(|e| {
tracing::error!(target: "xcm::pallet_xcm::query_delivery_fees", ?e, ?result_version, "Failed to convert fees into version");
XcmPaymentApiError::VersionedConversionFailed
})
}
fn do_new_query(
responder: impl Into<Location>,
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumberFor<T>,
match_querier: impl Into<Location>,
) -> u64 {
QueryCounter::<T>::mutate(|q| {
let r = *q;
q.saturating_inc();
Queries::<T>::insert(
r,
QueryStatus::Pending {
responder: responder.into().into(),
maybe_match_querier: Some(match_querier.into().into()),
maybe_notify,
timeout,
},
);
r
})
}
pub fn report_outcome_notify(
message: &mut Xcm<()>,
responder: impl Into<Location>,
notify: impl Into<<T as Config>::RuntimeCall>,
timeout: BlockNumberFor<T>,
) -> Result<(), XcmError> {
let responder = responder.into();
let destination = T::UniversalLocation::get()
.invert_target(&responder)
.map_err(|()| XcmError::LocationNotInvertible)?;
let notify: <T as Config>::RuntimeCall = notify.into();
let max_weight = notify.get_dispatch_info().call_weight;
let query_id = Self::new_notify_query(responder, notify, timeout, Here);
let response_info = QueryResponseInfo { destination, query_id, max_weight };
let report_error = Xcm(vec![ReportError(response_info)]);
message.0.insert(0, SetAppendix(report_error));
Ok(())
}
pub fn new_notify_query(
responder: impl Into<Location>,
notify: impl Into<<T as Config>::RuntimeCall>,
timeout: BlockNumberFor<T>,
match_querier: impl Into<Location>,
) -> u64 {
let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
"decode input is output of Call encode; Call guaranteed to have two enums; qed",
);
Self::do_new_query(responder, Some(notify), timeout, match_querier)
}
fn note_unknown_version(dest: &Location) {
tracing::trace!(
target: "xcm::pallet_xcm::note_unknown_version",
?dest, "XCM version is unknown for destination"
);
let versioned_dest = VersionedLocation::from(dest.clone());
VersionDiscoveryQueue::<T>::mutate(|q| {
if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
q[index].1.saturating_inc();
} else {
let _ = q.try_push((versioned_dest, 1));
}
});
}
fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
T::XcmExecutor::charge_fees(location.clone(), assets.clone())
.map_err(|_| Error::<T>::FeesNotMet)?;
Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
Ok(())
}
#[cfg(any(feature = "try-runtime", test))]
pub fn do_try_state() -> Result<(), TryRuntimeError> {
use migration::data::NeedsMigration;
let minimal_allowed_xcm_version = if let Some(safe_xcm_version) = SafeXcmVersion::<T>::get()
{
XCM_VERSION.saturating_sub(1).min(safe_xcm_version)
} else {
XCM_VERSION.saturating_sub(1)
};
ensure!(
!Queries::<T>::iter_values()
.any(|data| data.needs_migration(minimal_allowed_xcm_version)),
TryRuntimeError::Other("`Queries` data should be migrated to the higher xcm version!")
);
ensure!(
!LockedFungibles::<T>::iter_values()
.any(|data| data.needs_migration(minimal_allowed_xcm_version)),
TryRuntimeError::Other(
"`LockedFungibles` data should be migrated to the higher xcm version!"
)
);
ensure!(
!RemoteLockedFungibles::<T>::iter()
.any(|(key, data)| key.needs_migration(minimal_allowed_xcm_version) ||
data.needs_migration(minimal_allowed_xcm_version)),
TryRuntimeError::Other(
"`RemoteLockedFungibles` data should be migrated to the higher xcm version!"
)
);
if CurrentMigration::<T>::exists() {
return Ok(())
}
for v in 0..XCM_VERSION {
ensure!(
SupportedVersion::<T>::iter_prefix(v).next().is_none(),
TryRuntimeError::Other(
"`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
)
);
ensure!(
VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
TryRuntimeError::Other(
"`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
)
);
ensure!(
VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
TryRuntimeError::Other(
"`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
)
);
}
Ok(())
}
}
pub struct LockTicket<T: Config> {
sovereign_account: T::AccountId,
amount: BalanceOf<T>,
unlocker: Location,
item_index: Option<usize>,
}
impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
match self.item_index {
Some(index) => {
ensure!(locks.len() > index, UnexpectedState);
ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
locks[index].0 = locks[index].0.max(self.amount);
},
None => {
locks
.try_push((self.amount, self.unlocker.into()))
.map_err(|(_balance, _location)| UnexpectedState)?;
},
}
LockedFungibles::<T>::insert(&self.sovereign_account, locks);
T::Currency::extend_lock(
*b"py/xcmlk",
&self.sovereign_account,
self.amount,
WithdrawReasons::all(),
);
Ok(())
}
}
pub struct UnlockTicket<T: Config> {
sovereign_account: T::AccountId,
amount: BalanceOf<T>,
unlocker: Location,
}
impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut locks =
LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
let mut maybe_remove_index = None;
let mut locked = BalanceOf::<T>::zero();
let mut found = false;
for (i, x) in locks.iter_mut().enumerate() {
if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
x.0 = x.0.saturating_sub(self.amount);
if x.0.is_zero() {
maybe_remove_index = Some(i);
}
found = true;
}
locked = locked.max(x.0);
}
ensure!(found, UnexpectedState);
if let Some(remove_index) = maybe_remove_index {
locks.swap_remove(remove_index);
}
LockedFungibles::<T>::insert(&self.sovereign_account, locks);
let reasons = WithdrawReasons::all();
T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
Ok(())
}
}
pub struct ReduceTicket<T: Config> {
key: (u32, T::AccountId, VersionedAssetId),
amount: u128,
locker: VersionedLocation,
owner: VersionedLocation,
}
impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::UnexpectedState;
let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
if new_amount == 0 {
RemoteLockedFungibles::<T>::remove(&self.key);
} else {
record.amount = new_amount;
RemoteLockedFungibles::<T>::insert(&self.key, &record);
}
Ok(())
}
}
impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
type LockTicket = LockTicket<T>;
type UnlockTicket = UnlockTicket<T>;
type ReduceTicket = ReduceTicket<T>;
fn prepare_lock(
unlocker: Location,
asset: Asset,
owner: Location,
) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::*;
let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
}
fn prepare_unlock(
unlocker: Location,
asset: Asset,
owner: Location,
) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::*;
let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
let item_index =
locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
ensure!(locks[item_index].0 >= amount, NotLocked);
Ok(UnlockTicket { sovereign_account, amount, unlocker })
}
fn note_unlockable(
locker: Location,
asset: Asset,
mut owner: Location,
) -> Result<(), xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::*;
ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
let amount = match asset.fun {
Fungible(a) => a,
NonFungible(_) => return Err(Unimplemented),
};
owner.remove_network_id();
let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
let locker = locker.into();
let owner = owner.into();
let id: VersionedAssetId = asset.id.into();
let key = (XCM_VERSION, account, id);
let mut record =
RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
record.consumers = old.consumers;
record.amount = record.amount.max(old.amount);
}
RemoteLockedFungibles::<T>::insert(&key, record);
Ok(())
}
fn prepare_reduce_unlockable(
locker: Location,
asset: Asset,
mut owner: Location,
) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
use xcm_executor::traits::LockError::*;
let amount = match asset.fun {
Fungible(a) => a,
NonFungible(_) => return Err(Unimplemented),
};
owner.remove_network_id();
let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
let locker = locker.into();
let owner = owner.into();
let id: VersionedAssetId = asset.id.into();
let key = (XCM_VERSION, sovereign_account, id);
let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
ensure!(locker == record.locker && owner == record.owner, WouldClobber);
ensure!(record.amount >= amount, NotEnoughLocked);
ensure!(
record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
InUse
);
Ok(ReduceTicket { key, amount, locker, owner })
}
}
impl<T: Config> WrapVersion for Pallet<T> {
fn wrap_version<RuntimeCall: Decode + GetDispatchInfo>(
dest: &Location,
xcm: impl Into<VersionedXcm<RuntimeCall>>,
) -> Result<VersionedXcm<RuntimeCall>, ()> {
Self::get_version_for(dest)
.or_else(|| {
Self::note_unknown_version(dest);
SafeXcmVersion::<T>::get()
})
.ok_or_else(|| {
tracing::trace!(
target: "xcm::pallet_xcm::wrap_version",
?dest, "Could not determine a version to wrap XCM for destination",
);
()
})
.and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
}
}
impl<T: Config> GetVersion for Pallet<T> {
fn get_version_for(dest: &Location) -> Option<XcmVersion> {
SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
}
}
impl<T: Config> VersionChangeNotifier for Pallet<T> {
fn start(
dest: &Location,
query_id: QueryId,
max_weight: Weight,
_context: &XcmContext,
) -> XcmResult {
let versioned_dest = LatestVersionedLocation(dest);
let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
ensure!(!already, XcmError::InvalidLocation);
let xcm_version = T::AdvertisedXcmVersion::get();
let response = Response::Version(xcm_version);
let instruction = QueryResponse { query_id, response, max_weight, querier: None };
let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
Self::deposit_event(Event::<T>::VersionNotifyStarted {
destination: dest.clone(),
cost,
message_id,
});
let value = (query_id, max_weight, xcm_version);
VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
Ok(())
}
fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
Ok(())
}
fn is_subscribed(dest: &Location) -> bool {
let versioned_dest = LatestVersionedLocation(dest);
VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
}
}
impl<T: Config> DropAssets for Pallet<T> {
fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
if assets.is_empty() {
return Weight::zero()
}
let versioned = VersionedAssets::from(Assets::from(assets));
let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
AssetTraps::<T>::mutate(hash, |n| *n += 1);
Self::deposit_event(Event::AssetsTrapped {
hash,
origin: origin.clone(),
assets: versioned,
});
Weight::zero()
}
}
impl<T: Config> ClaimAssets for Pallet<T> {
fn claim_assets(
origin: &Location,
ticket: &Location,
assets: &Assets,
_context: &XcmContext,
) -> bool {
let mut versioned = VersionedAssets::from(assets.clone());
match ticket.unpack() {
(0, [GeneralIndex(i)]) =>
versioned = match versioned.into_version(*i as u32) {
Ok(v) => v,
Err(()) => return false,
},
(0, []) => (),
_ => return false,
};
let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
match AssetTraps::<T>::get(hash) {
0 => return false,
1 => AssetTraps::<T>::remove(hash),
n => AssetTraps::<T>::insert(hash, n - 1),
}
Self::deposit_event(Event::AssetsClaimed {
hash,
origin: origin.clone(),
assets: versioned,
});
return true
}
}
impl<T: Config> OnResponse for Pallet<T> {
fn expecting_response(
origin: &Location,
query_id: QueryId,
querier: Option<&Location>,
) -> bool {
match Queries::<T>::get(query_id) {
Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
Location::try_from(responder).map_or(false, |r| origin == &r) &&
maybe_match_querier.map_or(true, |match_querier| {
Location::try_from(match_querier).map_or(false, |match_querier| {
querier.map_or(false, |q| q == &match_querier)
})
}),
Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
Location::try_from(r).map_or(false, |r| origin == &r),
_ => false,
}
}
fn on_response(
origin: &Location,
query_id: QueryId,
querier: Option<&Location>,
response: Response,
max_weight: Weight,
_context: &XcmContext,
) -> Weight {
let origin = origin.clone();
match (response, Queries::<T>::get(query_id)) {
(
Response::Version(v),
Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
) => {
let origin: Location = match expected_origin.try_into() {
Ok(o) if o == origin => o,
Ok(o) => {
Self::deposit_event(Event::InvalidResponder {
origin: origin.clone(),
query_id,
expected_location: Some(o),
});
return Weight::zero()
},
_ => {
Self::deposit_event(Event::InvalidResponder {
origin: origin.clone(),
query_id,
expected_location: None,
});
return Weight::zero()
},
};
if !is_active {
Queries::<T>::insert(
query_id,
QueryStatus::VersionNotifier {
origin: origin.clone().into(),
is_active: true,
},
);
}
SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
Self::deposit_event(Event::SupportedVersionChanged {
location: origin,
version: v,
});
Weight::zero()
},
(
response,
Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
) => {
if let Some(match_querier) = maybe_match_querier {
let match_querier = match Location::try_from(match_querier) {
Ok(mq) => mq,
Err(_) => {
Self::deposit_event(Event::InvalidQuerierVersion {
origin: origin.clone(),
query_id,
});
return Weight::zero()
},
};
if querier.map_or(true, |q| q != &match_querier) {
Self::deposit_event(Event::InvalidQuerier {
origin: origin.clone(),
query_id,
expected_querier: match_querier,
maybe_actual_querier: querier.cloned(),
});
return Weight::zero()
}
}
let responder = match Location::try_from(responder) {
Ok(r) => r,
Err(_) => {
Self::deposit_event(Event::InvalidResponderVersion {
origin: origin.clone(),
query_id,
});
return Weight::zero()
},
};
if origin != responder {
Self::deposit_event(Event::InvalidResponder {
origin: origin.clone(),
query_id,
expected_location: Some(responder),
});
return Weight::zero()
}
match maybe_notify {
Some((pallet_index, call_index)) => {
let bare = (pallet_index, call_index, query_id, response);
if let Ok(call) = bare.using_encoded(|mut bytes| {
<T as Config>::RuntimeCall::decode(&mut bytes)
}) {
Queries::<T>::remove(query_id);
let weight = call.get_dispatch_info().call_weight;
if weight.any_gt(max_weight) {
let e = Event::NotifyOverweight {
query_id,
pallet_index,
call_index,
actual_weight: weight,
max_budgeted_weight: max_weight,
};
Self::deposit_event(e);
return Weight::zero()
}
let dispatch_origin = Origin::Response(origin.clone()).into();
match call.dispatch(dispatch_origin) {
Ok(post_info) => {
let e = Event::Notified { query_id, pallet_index, call_index };
Self::deposit_event(e);
post_info.actual_weight
},
Err(error_and_info) => {
let e = Event::NotifyDispatchError {
query_id,
pallet_index,
call_index,
};
Self::deposit_event(e);
error_and_info.post_info.actual_weight
},
}
.unwrap_or(weight)
} else {
let e =
Event::NotifyDecodeFailed { query_id, pallet_index, call_index };
Self::deposit_event(e);
Weight::zero()
}
},
None => {
let e = Event::ResponseReady { query_id, response: response.clone() };
Self::deposit_event(e);
let at = frame_system::Pallet::<T>::current_block_number();
let response = response.into();
Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
Weight::zero()
},
}
},
_ => {
let e = Event::UnexpectedResponse { origin: origin.clone(), query_id };
Self::deposit_event(e);
Weight::zero()
},
}
}
}
impl<T: Config> CheckSuspension for Pallet<T> {
fn is_suspended<Call>(
_origin: &Location,
_instructions: &mut [Instruction<Call>],
_max_weight: Weight,
_properties: &mut Properties,
) -> bool {
XcmExecutionSuspended::<T>::get()
}
}
impl<T: Config> RecordXcm for Pallet<T> {
fn should_record() -> bool {
ShouldRecordXcm::<T>::get()
}
fn set_record_xcm(enabled: bool) {
ShouldRecordXcm::<T>::put(enabled);
}
fn recorded_xcm() -> Option<Xcm<()>> {
RecordedXcm::<T>::get()
}
fn record(xcm: Xcm<()>) {
RecordedXcm::<T>::put(xcm);
}
}
pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
where
OuterOrigin: Into<Result<Origin, OuterOrigin>>,
{
match o.into() {
Ok(Origin::Xcm(location)) => Ok(location),
_ => Err(BadOrigin),
}
}
pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<Location, BadOrigin>
where
OuterOrigin: Into<Result<Origin, OuterOrigin>>,
{
match o.into() {
Ok(Origin::Response(location)) => Ok(location),
_ => Err(BadOrigin),
}
}
pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location>
for IsMajorityOfBody<Prefix, Body>
{
fn contains(l: &Location) -> bool {
let maybe_suffix = l.match_and_split(&Prefix::get());
matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
}
}
pub struct IsVoiceOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
impl<Prefix: Get<Location>, Body: Get<BodyId>> Contains<Location> for IsVoiceOfBody<Prefix, Body> {
fn contains(l: &Location) -> bool {
let maybe_suffix = l.match_and_split(&Prefix::get());
matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part == &BodyPart::Voice)
}
}
pub struct EnsureXcm<F, L = Location>(PhantomData<(F, L)>);
impl<
O: OriginTrait + From<Origin>,
F: Contains<L>,
L: TryFrom<Location> + TryInto<Location> + Clone,
> EnsureOrigin<O> for EnsureXcm<F, L>
where
O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
{
type Success = L;
fn try_origin(outer: O) -> Result<Self::Success, O> {
outer.try_with_caller(|caller| {
caller.try_into().and_then(|o| match o {
Origin::Xcm(ref location)
if F::contains(&location.clone().try_into().map_err(|_| o.clone().into())?) =>
Ok(location.clone().try_into().map_err(|_| o.clone().into())?),
Origin::Xcm(location) => Err(Origin::Xcm(location).into()),
o => Err(o.into()),
})
})
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
Ok(O::from(Origin::Xcm(Here.into())))
}
}
pub struct EnsureResponse<F>(PhantomData<F>);
impl<O: OriginTrait + From<Origin>, F: Contains<Location>> EnsureOrigin<O> for EnsureResponse<F>
where
O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
{
type Success = Location;
fn try_origin(outer: O) -> Result<Self::Success, O> {
outer.try_with_caller(|caller| {
caller.try_into().and_then(|o| match o {
Origin::Response(responder) => Ok(responder),
o => Err(o.into()),
})
})
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
Ok(O::from(Origin::Response(Here.into())))
}
}
pub struct XcmPassthrough<RuntimeOrigin>(PhantomData<RuntimeOrigin>);
impl<RuntimeOrigin: From<crate::Origin>> ConvertOrigin<RuntimeOrigin>
for XcmPassthrough<RuntimeOrigin>
{
fn convert_origin(
origin: impl Into<Location>,
kind: OriginKind,
) -> Result<RuntimeOrigin, Location> {
let origin = origin.into();
match kind {
OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
_ => Err(origin),
}
}
}