#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{BadOrigin, Saturating},
RuntimeDebug,
};
use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec};
use xcm::{latest::Weight as XcmWeight, prelude::*};
use xcm_executor::traits::ConvertOrigin;
use frame_support::PalletId;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{
dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo},
pallet_prelude::*,
parameter_types,
};
use frame_system::{pallet_prelude::*, Config as SysConfig};
use sp_core::H256;
use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, BlockNumberProvider, Hash};
use xcm_executor::{
traits::{
ClaimAssets, DropAssets, InvertLocation, OnResponse, VersionChangeNotifier,
WeightBounds,
},
Assets,
};
parameter_types! {
pub const CurrentXcmVersion: u32 = XCM_VERSION;
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type SendXcmOrigin: EnsureOrigin<
<Self as SysConfig>::RuntimeOrigin,
Success = MultiLocation,
>;
type XcmRouter: SendXcm;
type ExecuteXcmOrigin: EnsureOrigin<
<Self as SysConfig>::RuntimeOrigin,
Success = MultiLocation,
>;
type XcmExecuteFilter: Contains<(MultiLocation, Xcm<<Self as SysConfig>::RuntimeCall>)>;
type XcmExecutor: ExecuteXcm<<Self as SysConfig>::RuntimeCall>;
type XcmTeleportFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;
type XcmReserveTransferFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;
type Weigher: WeightBounds<<Self as SysConfig>::RuntimeCall>;
type LocationInverter: InvertLocation;
type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
type RuntimeCall: Parameter
+ GetDispatchInfo
+ IsType<<Self as frame_system::Config>::RuntimeCall>
+ Dispatchable<
RuntimeOrigin = <Self as Config>::RuntimeOrigin,
PostInfo = PostDispatchInfo,
>;
const VERSION_DISCOVERY_QUEUE_SIZE: u32;
type AdvertisedXcmVersion: Get<XcmVersion>;
}
const MAX_ASSETS_FOR_TRANSFER: usize = 2;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Attempted(xcm::latest::Outcome),
Sent(MultiLocation, MultiLocation, Xcm<()>),
UnexpectedResponse(MultiLocation, QueryId),
ResponseReady(QueryId, Response),
Notified(QueryId, u8, u8),
NotifyOverweight(QueryId, u8, u8, Weight, Weight),
NotifyDispatchError(QueryId, u8, u8),
NotifyDecodeFailed(QueryId, u8, u8),
InvalidResponder(MultiLocation, QueryId, Option<MultiLocation>),
InvalidResponderVersion(MultiLocation, QueryId),
ResponseTaken(QueryId),
AssetsTrapped(H256, MultiLocation, VersionedMultiAssets),
VersionChangeNotified(MultiLocation, XcmVersion),
SupportedVersionChanged(MultiLocation, XcmVersion),
NotifyTargetSendFail(MultiLocation, QueryId, XcmError),
NotifyTargetMigrationFail(VersionedMultiLocation, QueryId),
AssetsClaimed(H256, MultiLocation, VersionedMultiAssets),
}
#[pallet::origin]
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum Origin {
Xcm(MultiLocation),
Response(MultiLocation),
}
impl From<MultiLocation> for Origin {
fn from(location: MultiLocation) -> Origin {
Origin::Xcm(location)
}
}
#[pallet::error]
pub enum Error<T> {
Unreachable,
SendFailure,
Filtered,
UnweighableMessage,
DestinationNotInvertible,
Empty,
CannotReanchor,
TooManyAssets,
InvalidOrigin,
BadVersion,
BadLocation,
NoSubscription,
AlreadySubscribed,
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum QueryStatus<BlockNumber> {
Pending {
responder: VersionedMultiLocation,
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumber,
},
VersionNotifier { origin: VersionedMultiLocation, is_active: bool },
Ready { response: VersionedResponse, at: BlockNumber },
}
#[derive(Copy, Clone)]
pub(crate) struct LatestVersionedMultiLocation<'a>(pub(crate) &'a MultiLocation);
impl<'a> EncodeLike<VersionedMultiLocation> for LatestVersionedMultiLocation<'a> {}
impl<'a> Encode for LatestVersionedMultiLocation<'a> {
fn encode(&self) -> Vec<u8> {
let mut r = VersionedMultiLocation::from(MultiLocation::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<T::BlockNumber>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn asset_trap)]
pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;
#[pallet::storage]
pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
#[pallet::storage]
pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
XcmVersion,
Blake2_128Concat,
VersionedMultiLocation,
XcmVersion,
OptionQuery,
>;
#[pallet::storage]
pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
XcmVersion,
Blake2_128Concat,
VersionedMultiLocation,
QueryId,
OptionQuery,
>;
#[pallet::storage]
pub(super) type VersionNotifyTargets<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
XcmVersion,
Blake2_128Concat,
VersionedMultiLocation,
(QueryId, u64, 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]
pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
_,
BoundedVec<(VersionedMultiLocation, u32), VersionDiscoveryQueueSize<T>>,
ValueQuery,
>;
#[pallet::storage]
pub(super) type CurrentMigration<T: Config> =
StorageValue<_, VersionMigrationStage, OptionQuery>;
#[pallet::genesis_config]
pub struct GenesisConfig {
pub safe_xcm_version: Option<XcmVersion>,
}
#[cfg(feature = "std")]
impl Default for GenesisConfig {
fn default() -> Self {
Self { safe_xcm_version: Some(XCM_VERSION) }
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig {
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);
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) = MultiLocation::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
}
fn on_runtime_upgrade() -> Weight {
CurrentMigration::<T>::put(VersionMigrationStage::default());
T::DbWeight::get().writes(1)
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(100_000_000)]
pub fn send(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
message: Box<VersionedXcm<()>>,
) -> DispatchResult {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
let interior: Junctions =
origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = MultiLocation::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e {
SendError::CannotReachDestination(..) => Error::<T>::Unreachable,
_ => Error::<T>::SendFailure,
})?;
Self::deposit_event(Event::Sent(origin_location, dest, message));
Ok(())
}
#[pallet::call_index(1)]
#[pallet::weight({
let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
WithdrawAsset(assets),
InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) },
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w)))
},
_ => Weight::MAX,
}
})]
pub fn teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
) -> DispatchResult {
Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, None)
}
#[pallet::call_index(2)]
#[pallet::weight({
match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w)))
},
_ => Weight::MAX,
}
})]
pub fn reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
None,
)
}
#[pallet::call_index(3)]
#[pallet::weight(Weight::from_ref_time(max_weight.saturating_add(100_000_000u64)))]
pub fn execute(
origin: OriginFor<T>,
message: Box<VersionedXcm<<T as SysConfig>::RuntimeCall>>,
max_weight: XcmWeight,
) -> DispatchResultWithPostInfo {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
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;
let outcome = T::XcmExecutor::execute_xcm_in_credit(
origin_location,
message,
max_weight,
max_weight,
);
let result = Ok(Some(outcome.weight_used().saturating_add(100_000_000)).into());
Self::deposit_event(Event::Attempted(outcome));
result
}
#[pallet::call_index(4)]
#[pallet::weight(100_000_000u64)]
pub fn force_xcm_version(
origin: OriginFor<T>,
location: Box<MultiLocation>,
xcm_version: XcmVersion,
) -> DispatchResult {
ensure_root(origin)?;
let location = *location;
SupportedVersion::<T>::insert(
XCM_VERSION,
LatestVersionedMultiLocation(&location),
xcm_version,
);
Self::deposit_event(Event::SupportedVersionChanged(location, xcm_version));
Ok(())
}
#[pallet::call_index(5)]
#[pallet::weight(100_000_000u64)]
pub fn force_default_xcm_version(
origin: OriginFor<T>,
maybe_xcm_version: Option<XcmVersion>,
) -> DispatchResult {
ensure_root(origin)?;
SafeXcmVersion::<T>::set(maybe_xcm_version);
Ok(())
}
#[pallet::call_index(6)]
#[pallet::weight(100_000_000u64)]
pub fn force_subscribe_version_notify(
origin: OriginFor<T>,
location: Box<VersionedMultiLocation>,
) -> DispatchResult {
ensure_root(origin)?;
let location: MultiLocation =
(*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)]
#[pallet::weight(100_000_000u64)]
pub fn force_unsubscribe_version_notify(
origin: OriginFor<T>,
location: Box<VersionedMultiLocation>,
) -> DispatchResult {
ensure_root(origin)?;
let location: MultiLocation =
(*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({
match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w)))
},
_ => Weight::MAX,
}
})]
pub fn limited_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
Some(weight_limit),
)
}
#[pallet::call_index(9)]
#[pallet::weight({
let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
WithdrawAsset(assets),
InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) },
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w)))
},
_ => Weight::MAX,
}
})]
pub fn limited_teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_teleport_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
Some(weight_limit),
)
}
}
impl<T: Config> Pallet<T> {
fn do_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
maybe_weight_limit: Option<WeightLimit>,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let value = (origin_location, assets.drain());
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, assets) = value;
let ancestry = T::LocationInverter::ancestry();
let fees = assets
.get(fee_asset_item as usize)
.ok_or(Error::<T>::Empty)?
.clone()
.reanchored(&dest, &ancestry)
.map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets: MultiAssets = assets.into();
let weight_limit = match maybe_weight_limit {
Some(weight_limit) => weight_limit,
None => {
let beneficiary = beneficiary.clone();
let fees = fees.clone();
let mut remote_message = Xcm(vec![
ReserveAssetDeposited(assets.clone()),
ClearOrigin,
BuyExecution { fees, weight_limit: Limited(0) },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
]);
let remote_weight = T::Weigher::weight(&mut remote_message)
.map_err(|()| Error::<T>::UnweighableMessage)?;
Limited(remote_weight)
},
};
let xcm = Xcm(vec![
BuyExecution { fees, weight_limit },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
]);
let mut message = Xcm(vec![TransferReserveAsset { assets, dest, xcm }]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight);
Self::deposit_event(Event::Attempted(outcome));
Ok(())
}
fn do_teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
maybe_weight_limit: Option<WeightLimit>,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let value = (origin_location, assets.drain());
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, assets) = value;
let ancestry = T::LocationInverter::ancestry();
let fees = assets
.get(fee_asset_item as usize)
.ok_or(Error::<T>::Empty)?
.clone()
.reanchored(&dest, &ancestry)
.map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets: MultiAssets = assets.into();
let weight_limit = match maybe_weight_limit {
Some(weight_limit) => weight_limit,
None => {
let beneficiary = beneficiary.clone();
let fees = fees.clone();
let mut remote_message = Xcm(vec![
ReceiveTeleportedAsset(assets.clone()),
ClearOrigin,
BuyExecution { fees, weight_limit: Limited(0) },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
]);
let remote_weight = T::Weigher::weight(&mut remote_message)
.map_err(|()| Error::<T>::UnweighableMessage)?;
Limited(remote_weight)
},
};
let xcm = Xcm(vec![
BuyExecution { fees, weight_limit },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
]);
let mut message =
Xcm(vec![WithdrawAsset(assets), InitiateTeleport { assets: Wild(All), dest, xcm }]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight);
Self::deposit_event(Event::Attempted(outcome));
Ok(())
}
pub(crate) fn check_xcm_version_change(
mut stage: VersionMigrationStage,
weight_cutoff: Weight,
) -> (Weight, Option<VersionMigrationStage>) {
let mut weight_used = Weight::zero();
let todo_sv_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1);
let todo_vn_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1);
let todo_vnt_already_notified_weight: Weight = T::DbWeight::get().reads(1);
let todo_vnt_notify_weight: Weight = T::DbWeight::get().reads_writes(1, 3);
let todo_vnt_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1);
let todo_vnt_migrate_fail_weight: Weight = T::DbWeight::get().reads_writes(1, 1);
let todo_vnt_notify_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 3);
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(todo_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(todo_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: MultiLocation = match key.clone().try_into() {
Ok(k) if target_xcm_version != xcm_version => k,
_ => {
weight_used.saturating_accrue(todo_vnt_already_notified_weight);
continue
},
};
let response = Response::Version(xcm_version);
let message = Xcm(vec![QueryResponse { query_id, response, max_weight }]);
let event = match T::XcmRouter::send_xcm(new_key.clone(), message) {
Ok(()) => {
let value = (query_id, max_weight, xcm_version);
VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
Event::VersionChangeNotified(new_key, xcm_version)
},
Err(e) => {
VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
Event::NotifyTargetSendFail(new_key, query_id, e.into())
},
};
Self::deposit_event(event);
weight_used.saturating_accrue(todo_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 MultiLocation::try_from(old_key.clone()) {
Ok(k) => k,
Err(()) => {
Self::deposit_event(Event::NotifyTargetMigrationFail(
old_key, value.0,
));
weight_used.saturating_accrue(todo_vnt_migrate_fail_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
continue
},
};
let versioned_key = LatestVersionedMultiLocation(&new_key);
if target_xcm_version == xcm_version {
VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
weight_used.saturating_accrue(todo_vnt_migrate_weight);
} else {
let response = Response::Version(xcm_version);
let message =
Xcm(vec![QueryResponse { query_id, response, max_weight }]);
let event = match T::XcmRouter::send_xcm(new_key.clone(), message) {
Ok(()) => {
VersionNotifyTargets::<T>::insert(
XCM_VERSION,
versioned_key,
(query_id, max_weight, xcm_version),
);
Event::VersionChangeNotified(new_key, xcm_version)
},
Err(e) => Event::NotifyTargetSendFail(new_key, query_id, e.into()),
};
Self::deposit_event(event);
weight_used.saturating_accrue(todo_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<MultiLocation>) -> XcmResult {
let dest = dest.into();
let versioned_dest = VersionedMultiLocation::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: 0 };
T::XcmRouter::send_xcm(dest, Xcm(vec![instruction]))?;
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<MultiLocation>) -> XcmResult {
let dest = dest.into();
let versioned_dest = LatestVersionedMultiLocation(&dest);
let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
.ok_or(XcmError::InvalidLocation)?;
T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
Queries::<T>::remove(query_id);
Ok(())
}
pub fn send_xcm(
interior: impl Into<Junctions>,
dest: impl Into<MultiLocation>,
mut message: Xcm<()>,
) -> Result<(), SendError> {
let interior = interior.into();
let dest = dest.into();
if interior != Junctions::Here {
message.0.insert(0, DescendOrigin(interior))
};
log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
T::XcmRouter::send_xcm(dest, message)
}
pub fn check_account() -> T::AccountId {
const ID: PalletId = PalletId(*b"py/xcmch");
AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
}
fn do_new_query(
responder: impl Into<MultiLocation>,
maybe_notify: Option<(u8, u8)>,
timeout: T::BlockNumber,
) -> u64 {
QueryCounter::<T>::mutate(|q| {
let r = *q;
q.saturating_inc();
Queries::<T>::insert(
r,
QueryStatus::Pending {
responder: responder.into().into(),
maybe_notify,
timeout,
},
);
r
})
}
pub fn report_outcome(
message: &mut Xcm<()>,
responder: impl Into<MultiLocation>,
timeout: T::BlockNumber,
) -> Result<QueryId, XcmError> {
let responder = responder.into();
let dest = T::LocationInverter::invert_location(&responder)
.map_err(|()| XcmError::MultiLocationNotInvertible)?;
let query_id = Self::new_query(responder, timeout);
let report_error = Xcm(vec![ReportError { dest, query_id, max_response_weight: 0 }]);
message.0.insert(0, SetAppendix(report_error));
Ok(query_id)
}
pub fn report_outcome_notify(
message: &mut Xcm<()>,
responder: impl Into<MultiLocation>,
notify: impl Into<<T as Config>::RuntimeCall>,
timeout: T::BlockNumber,
) -> Result<(), XcmError> {
let responder = responder.into();
let dest = T::LocationInverter::invert_location(&responder)
.map_err(|()| XcmError::MultiLocationNotInvertible)?;
let notify: <T as Config>::RuntimeCall = notify.into();
let max_response_weight = notify.get_dispatch_info().weight;
let query_id = Self::new_notify_query(responder, notify, timeout);
let report_error = Xcm(vec![ReportError {
dest,
query_id,
max_response_weight: max_response_weight.ref_time(),
}]);
message.0.insert(0, SetAppendix(report_error));
Ok(())
}
pub fn new_query(responder: impl Into<MultiLocation>, timeout: T::BlockNumber) -> u64 {
Self::do_new_query(responder, None, timeout)
}
pub fn new_notify_query(
responder: impl Into<MultiLocation>,
notify: impl Into<<T as Config>::RuntimeCall>,
timeout: T::BlockNumber,
) -> 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)
}
pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> {
if let Some(QueryStatus::Ready { response, at }) = Queries::<T>::get(query_id) {
let response = response.try_into().ok()?;
Queries::<T>::remove(query_id);
Self::deposit_event(Event::ResponseTaken(query_id));
Some((response, at))
} else {
None
}
}
fn note_unknown_version(dest: &MultiLocation) {
log::trace!(
target: "xcm::pallet_xcm::note_unknown_version",
"XCM version is unknown for destination: {:?}",
dest,
);
let versioned_dest = VersionedMultiLocation::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));
}
});
}
}
impl<T: Config> WrapVersion for Pallet<T> {
fn wrap_version<RuntimeCall>(
dest: &MultiLocation,
xcm: impl Into<VersionedXcm<RuntimeCall>>,
) -> Result<VersionedXcm<RuntimeCall>, ()> {
SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedMultiLocation(dest))
.or_else(|| {
Self::note_unknown_version(dest);
SafeXcmVersion::<T>::get()
})
.ok_or_else(|| {
log::trace!(
target: "xcm::pallet_xcm::wrap_version",
"Could not determine a version to wrap XCM for destination: {:?}",
dest,
);
()
})
.and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
}
}
impl<T: Config> VersionChangeNotifier for Pallet<T> {
fn start(dest: &MultiLocation, query_id: QueryId, max_weight: u64) -> XcmResult {
let versioned_dest = LatestVersionedMultiLocation(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 };
T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![instruction]))?;
let value = (query_id, max_weight, xcm_version);
VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
Ok(())
}
fn stop(dest: &MultiLocation) -> XcmResult {
VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedMultiLocation(dest));
Ok(())
}
fn is_subscribed(dest: &MultiLocation) -> bool {
let versioned_dest = LatestVersionedMultiLocation(dest);
VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
}
}
impl<T: Config> DropAssets for Pallet<T> {
fn drop_assets(origin: &MultiLocation, assets: Assets) -> u64 {
if assets.is_empty() {
return 0
}
let versioned = VersionedMultiAssets::from(MultiAssets::from(assets));
let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
AssetTraps::<T>::mutate(hash, |n| *n += 1);
Self::deposit_event(Event::AssetsTrapped(hash, origin.clone(), versioned));
0
}
}
impl<T: Config> ClaimAssets for Pallet<T> {
fn claim_assets(
origin: &MultiLocation,
ticket: &MultiLocation,
assets: &MultiAssets,
) -> bool {
let mut versioned = VersionedMultiAssets::from(assets.clone());
match (ticket.parents, &ticket.interior) {
(0, X1(GeneralIndex(i))) =>
versioned = match versioned.into_version(*i as u32) {
Ok(v) => v,
Err(()) => return false,
},
(0, Here) => (),
_ => return false,
};
let hash = BlakeTwo256::hash_of(&(origin, 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.clone(), versioned));
return true
}
}
impl<T: Config> OnResponse for Pallet<T> {
fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool {
match Queries::<T>::get(query_id) {
Some(QueryStatus::Pending { responder, .. }) =>
MultiLocation::try_from(responder).map_or(false, |r| origin == &r),
Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
MultiLocation::try_from(r).map_or(false, |r| origin == &r),
_ => false,
}
}
fn on_response(
origin: &MultiLocation,
query_id: QueryId,
response: Response,
max_weight: u64,
) -> u64 {
match (response, Queries::<T>::get(query_id)) {
(
Response::Version(v),
Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
) => {
let origin: MultiLocation = match expected_origin.try_into() {
Ok(o) if &o == origin => o,
Ok(o) => {
Self::deposit_event(Event::InvalidResponder(
origin.clone(),
query_id,
Some(o),
));
return 0
},
_ => {
Self::deposit_event(Event::InvalidResponder(
origin.clone(),
query_id,
None,
));
return 0
},
};
if !is_active {
Queries::<T>::insert(
query_id,
QueryStatus::VersionNotifier {
origin: origin.clone().into(),
is_active: true,
},
);
}
SupportedVersion::<T>::insert(
XCM_VERSION,
LatestVersionedMultiLocation(&origin),
v,
);
Self::deposit_event(Event::SupportedVersionChanged(origin, v));
0
},
(response, Some(QueryStatus::Pending { responder, maybe_notify, .. })) => {
let responder = match MultiLocation::try_from(responder) {
Ok(r) => r,
Err(_) => {
Self::deposit_event(Event::InvalidResponderVersion(
origin.clone(),
query_id,
));
return 0
},
};
if origin != &responder {
Self::deposit_event(Event::InvalidResponder(
origin.clone(),
query_id,
Some(responder),
));
return 0
}
return 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().weight;
let max_weight = Weight::from_ref_time(max_weight);
if weight.any_gt(max_weight) {
let e = Event::NotifyOverweight(
query_id,
pallet_index,
call_index,
weight,
max_weight,
);
Self::deposit_event(e);
return 0
}
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)
.ref_time()
} else {
let e =
Event::NotifyDecodeFailed(query_id, pallet_index, call_index);
Self::deposit_event(e);
0
}
},
None => {
let e = Event::ResponseReady(query_id, 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 });
0
},
}
},
_ => {
Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id));
return 0
},
}
}
}
}
pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, 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<MultiLocation, 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<MultiLocation>, Body: Get<BodyId>> Contains<MultiLocation>
for IsMajorityOfBody<Prefix, Body>
{
fn contains(l: &MultiLocation) -> 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<MultiLocation>, Body: Get<BodyId>> Contains<MultiLocation>
for IsVoiceOfBody<Prefix, Body>
{
fn contains(l: &MultiLocation) -> 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>(PhantomData<F>);
impl<O: OriginTrait + From<Origin>, F: Contains<MultiLocation>> EnsureOrigin<O> for EnsureXcm<F>
where
O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
{
type Success = MultiLocation;
fn try_origin(outer: O) -> Result<Self::Success, O> {
outer.try_with_caller(|caller| {
caller.try_into().and_then(|o| match o {
Origin::Xcm(location) if F::contains(&location) => Ok(location),
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<MultiLocation>> EnsureOrigin<O>
for EnsureResponse<F>
where
O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
{
type Success = MultiLocation;
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<MultiLocation>,
kind: OriginKind,
) -> Result<RuntimeOrigin, MultiLocation> {
let origin = origin.into();
match kind {
OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
_ => Err(origin),
}
}
}