mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 08:11:03 +00:00
XCM: Automatic Version Negotiation (#3736)
* XCM: Automatic Version Negotiation * Introduce the version instructions and subscription trait * Notification and subscription data migration * Version change subscriptions * Fixes * Formatting * Spelling * Fixes * Fixes * Automatic unsubscription * Formatting * Expose remote origin in VM and ensure it is unchanged from actual origin in subscription instructions. * Barrier * Unsubscription extrinsic * Remove top_level param * Formatting * Fixes * Automatic subscription * Formatting * Spelling * Unit tests for XCM executor * Formatting * Spellin * Unit test for XCM pallet subscriber side * Formatting * More tests * Formatting * Fixes * Subscription-side tests * Formatting * Unit tests for XCM pallet * Formatting * Update roadmap/implementers-guide/src/types/overseer-protocol.md Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> * Remove commented code * Grumbles * Multi-stage XCM version migration * Formatting * v1 subscriptions backport * Warning * Spelling * Fix grumbles * Formatting * Avoid running through old notifications * Formatting Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
@@ -441,7 +441,7 @@ enum DisputeCoordinatorMessage {
|
||||
pending_confirmation: oneshot::Sender<ImportStatementsResult>
|
||||
},
|
||||
/// Fetch a list of all recent disputes that the co-ordinator is aware of.
|
||||
/// These are disputes which have occured any time in recent sessions, which may have already concluded.
|
||||
/// These are disputes which have occurred any time in recent sessions, which may have already concluded.
|
||||
RecentDisputes(ResponseChannel<Vec<(SessionIndex, CandidateHash)>>),
|
||||
/// Fetch a list of all active disputes that the co-ordinator is aware of.
|
||||
/// These disputes are either unconcluded or recently concluded.
|
||||
@@ -699,7 +699,7 @@ The Runtime API subsystem is responsible for providing an interface to the state
|
||||
|
||||
This is fueled by an auxiliary type encapsulating all request types defined in the Runtime API section of the guide.
|
||||
|
||||
> TODO: link to the Runtime API section. Not possible currently because of https://github.com/Michael-F-Bryan/mdbook-linkcheck/issues/25. Once v0.7.1 is released it will work.
|
||||
> To do: link to the Runtime API section. Not possible currently because of https://github.com/Michael-F-Bryan/mdbook-linkcheck/issues/25. Once v0.7.1 is released it will work.
|
||||
|
||||
```rust
|
||||
enum RuntimeApiRequest {
|
||||
|
||||
@@ -1307,6 +1307,7 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type ResponseHandler = XcmPallet;
|
||||
type AssetTrap = XcmPallet;
|
||||
type AssetClaims = XcmPallet;
|
||||
type SubscriptionService = XcmPallet;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
@@ -1326,7 +1327,6 @@ pub type LocalOriginToLocation = (
|
||||
// And a usual Signed origin to be used in XCM as a corresponding AccountId32
|
||||
SignedToAccountId32<Origin, AccountId, KusamaNetwork>,
|
||||
);
|
||||
|
||||
impl pallet_xcm::Config for Runtime {
|
||||
type Event = Event;
|
||||
type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<Origin, LocalOriginToLocation>;
|
||||
@@ -1342,6 +1342,8 @@ impl pallet_xcm::Config for Runtime {
|
||||
type LocationInverter = LocationInverter<Ancestry>;
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -675,6 +675,7 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type ResponseHandler = XcmPallet;
|
||||
type AssetTrap = XcmPallet;
|
||||
type AssetClaims = XcmPallet;
|
||||
type SubscriptionService = XcmPallet;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
@@ -706,6 +707,8 @@ impl pallet_xcm::Config for Runtime {
|
||||
type LocationInverter = LocationInverter<Ancestry>;
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
|
||||
}
|
||||
|
||||
impl parachains_session_info::Config for Runtime {}
|
||||
|
||||
@@ -518,6 +518,8 @@ impl pallet_xcm::Config for Runtime {
|
||||
type XcmReserveTransferFilter = Everything;
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
|
||||
}
|
||||
|
||||
impl parachains_hrmp::Config for Runtime {
|
||||
@@ -540,7 +542,7 @@ impl pallet_test_notifier::Config for Runtime {
|
||||
pub mod pallet_test_notifier {
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
use pallet_xcm::{ensure_response, QueryId};
|
||||
use pallet_xcm::ensure_response;
|
||||
use sp_runtime::DispatchResult;
|
||||
use xcm::latest::prelude::*;
|
||||
|
||||
|
||||
@@ -88,4 +88,5 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type ResponseHandler = super::Xcm;
|
||||
type AssetTrap = super::Xcm;
|
||||
type AssetClaims = super::Xcm;
|
||||
type SubscriptionService = super::Xcm;
|
||||
}
|
||||
|
||||
@@ -949,6 +949,7 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type ResponseHandler = XcmPallet;
|
||||
type AssetTrap = XcmPallet;
|
||||
type AssetClaims = XcmPallet;
|
||||
type SubscriptionService = XcmPallet;
|
||||
}
|
||||
|
||||
/// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location
|
||||
@@ -973,6 +974,8 @@ impl pallet_xcm::Config for Runtime {
|
||||
type LocationInverter = LocationInverter<Ancestry>;
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
|
||||
}
|
||||
|
||||
construct_runtime! {
|
||||
|
||||
@@ -23,9 +23,12 @@ mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use codec::{Decode, Encode, EncodeLike};
|
||||
use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait};
|
||||
use sp_runtime::{traits::BadOrigin, RuntimeDebug};
|
||||
use sp_runtime::{
|
||||
traits::{BadOrigin, Saturating},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::{
|
||||
boxed::Box,
|
||||
convert::{TryFrom, TryInto},
|
||||
@@ -34,10 +37,7 @@ use sp_std::{
|
||||
result::Result,
|
||||
vec,
|
||||
};
|
||||
use xcm::{
|
||||
latest::prelude::*, VersionedMultiAssets, VersionedMultiLocation, VersionedResponse,
|
||||
VersionedXcm,
|
||||
};
|
||||
use xcm::prelude::*;
|
||||
use xcm_executor::traits::ConvertOrigin;
|
||||
|
||||
use frame_support::PalletId;
|
||||
@@ -49,15 +49,25 @@ pub mod pallet {
|
||||
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, WeightBounds},
|
||||
traits::{
|
||||
ClaimAssets, DropAssets, InvertLocation, OnResponse, VersionChangeNotifier,
|
||||
WeightBounds,
|
||||
},
|
||||
Assets,
|
||||
};
|
||||
|
||||
parameter_types! {
|
||||
/// An implementation of `Get<u32>` which just returns the latest XCM version which we can
|
||||
/// support.
|
||||
pub const CurrentXcmVersion: u32 = XCM_VERSION;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(super) trait Store)]
|
||||
pub struct Pallet<T>(_);
|
||||
@@ -106,6 +116,12 @@ pub mod pallet {
|
||||
+ GetDispatchInfo
|
||||
+ IsType<<Self as frame_system::Config>::Call>
|
||||
+ Dispatchable<Origin = <Self as Config>::Origin, PostInfo = PostDispatchInfo>;
|
||||
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32;
|
||||
|
||||
/// The latest supported version that we advertise. Generally just set it to
|
||||
/// `pallet_xcm::CurrentXcmVersion`.
|
||||
type AdvertisedXcmVersion: Get<XcmVersion>;
|
||||
}
|
||||
|
||||
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
|
||||
@@ -160,7 +176,7 @@ pub mod pallet {
|
||||
/// be received and acted upon.
|
||||
///
|
||||
/// \[ origin location, id, expected location \]
|
||||
InvalidResponder(MultiLocation, QueryId, MultiLocation),
|
||||
InvalidResponder(MultiLocation, QueryId, Option<MultiLocation>),
|
||||
/// Expected query response has been received but the expected origin location placed in
|
||||
/// storate by this runtime previously cannot be decoded. The query remains registered.
|
||||
///
|
||||
@@ -179,6 +195,25 @@ pub mod pallet {
|
||||
///
|
||||
/// \[ hash, origin, assets \]
|
||||
AssetsTrapped(H256, MultiLocation, VersionedMultiAssets),
|
||||
/// An XCM version change notification message has been attempted to be sent.
|
||||
///
|
||||
/// \[ destination, result \]
|
||||
VersionChangeNotified(MultiLocation, XcmVersion),
|
||||
/// The supported version of a location has been changed. This might be through an
|
||||
/// automatic notification or a manual intervention.
|
||||
///
|
||||
/// \[ location, XCM version \]
|
||||
SupportedVersionChanged(MultiLocation, XcmVersion),
|
||||
/// A given location which had a version change subscription was dropped owing to an error
|
||||
/// sending the notification to it.
|
||||
///
|
||||
/// \[ location, query ID, error \]
|
||||
NotifyTargetSendFail(MultiLocation, QueryId, XcmError),
|
||||
/// A given location which had a version change subscription was dropped owing to an error
|
||||
/// migrating the location to our new XCM format.
|
||||
///
|
||||
/// \[ location, query ID \]
|
||||
NotifyTargetMigrationFail(VersionedMultiLocation, QueryId),
|
||||
}
|
||||
|
||||
#[pallet::origin]
|
||||
@@ -219,6 +254,13 @@ pub mod pallet {
|
||||
InvalidOrigin,
|
||||
/// The version of the `Versioned` value used is not able to be interpreted.
|
||||
BadVersion,
|
||||
/// The given location could not be used (e.g. because it cannot be expressed in the
|
||||
/// desired version of XCM).
|
||||
BadLocation,
|
||||
/// The referenced subscription could not be found.
|
||||
NoSubscription,
|
||||
/// The location is invalid since it already has a subscription from us.
|
||||
AlreadySubscribed,
|
||||
}
|
||||
|
||||
/// The status of a query.
|
||||
@@ -230,16 +272,41 @@ pub mod pallet {
|
||||
maybe_notify: Option<(u8, u8)>,
|
||||
timeout: BlockNumber,
|
||||
},
|
||||
/// The query is for an ongoing version notification subscription.
|
||||
VersionNotifier { origin: VersionedMultiLocation, is_active: bool },
|
||||
/// A response has been received.
|
||||
Ready { response: VersionedResponse, at: BlockNumber },
|
||||
}
|
||||
|
||||
/// Value of a query, must be unique for each query.
|
||||
pub type QueryId = u64;
|
||||
#[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)]
|
||||
pub enum VersionMigrationStage {
|
||||
MigrateSupportedVersion,
|
||||
MigrateVersionNotifiers,
|
||||
NotifyCurrentTargets(Option<Vec<u8>>),
|
||||
MigrateAndNotifyOldTargets,
|
||||
}
|
||||
|
||||
impl Default for VersionMigrationStage {
|
||||
fn default() -> Self {
|
||||
Self::MigrateSupportedVersion
|
||||
}
|
||||
}
|
||||
|
||||
/// The latest available query index.
|
||||
#[pallet::storage]
|
||||
pub(super) type QueryCount<T: Config> = StorageValue<_, QueryId, ValueQuery>;
|
||||
pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;
|
||||
|
||||
/// The ongoing queries.
|
||||
#[pallet::storage]
|
||||
@@ -255,8 +322,131 @@ pub mod pallet {
|
||||
#[pallet::getter(fn asset_trap)]
|
||||
pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;
|
||||
|
||||
/// Default version to encode XCM when latest version of destination is unknown. If `None`,
|
||||
/// then the destinations whose XCM version is unknown are considered unreachable.
|
||||
#[pallet::storage]
|
||||
pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
|
||||
|
||||
/// Latest versions that we know various locations support.
|
||||
#[pallet::storage]
|
||||
pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
XcmVersion,
|
||||
Blake2_128Concat,
|
||||
VersionedMultiLocation,
|
||||
XcmVersion,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// All locations that we have requested version notifications from.
|
||||
#[pallet::storage]
|
||||
pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
XcmVersion,
|
||||
Blake2_128Concat,
|
||||
VersionedMultiLocation,
|
||||
QueryId,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// The target locations that are subscribed to our version changes, as well as the most recent
|
||||
/// of our versions we informed them of.
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
/// Destinations whose latest XCM version we would like to know. Duplicates not allowed, and
|
||||
/// the `u32` counter is the number of times that a send to the destination has been attempted,
|
||||
/// which is used as a prioritization.
|
||||
#[pallet::storage]
|
||||
pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
|
||||
_,
|
||||
BoundedVec<(VersionedMultiLocation, u32), VersionDiscoveryQueueSize<T>>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
/// The current migration's stage, if any.
|
||||
#[pallet::storage]
|
||||
pub(super) type CurrentMigration<T: Config> =
|
||||
StorageValue<_, VersionMigrationStage, OptionQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig {
|
||||
/// The default version to encode outgoing XCM messages with.
|
||||
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> {}
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
|
||||
let mut weight_used = 0;
|
||||
if let Some(migration) = CurrentMigration::<T>::get() {
|
||||
// Consume 10% of block at most
|
||||
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);
|
||||
}
|
||||
|
||||
// Here we aim to get one successful version negotiation request sent per block, ordered
|
||||
// by the destinations being most sent to.
|
||||
let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
|
||||
// TODO: correct weights.
|
||||
weight_used += T::DbWeight::get().read + T::DbWeight::get().write;
|
||||
q.sort_by_key(|i| i.1);
|
||||
while let Some((versioned_dest, _)) = q.pop() {
|
||||
if let Ok(dest) = versioned_dest.try_into() {
|
||||
if Self::request_version_notify(dest).is_ok() {
|
||||
// TODO: correct weights.
|
||||
weight_used += T::DbWeight::get().read + T::DbWeight::get().write;
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Should never fail since we only removed items. But better safe than panicking as it's
|
||||
// way better to drop the queue than panic on initialize.
|
||||
if let Ok(q) = BoundedVec::try_from(q) {
|
||||
VersionDiscoveryQueue::<T>::put(q);
|
||||
}
|
||||
weight_used
|
||||
}
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
// Start a migration (this happens before on_initialize so it'll happen later in this
|
||||
// block, which should be good enough)...
|
||||
CurrentMigration::<T>::put(VersionMigrationStage::default());
|
||||
T::DbWeight::get().write
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
@@ -448,9 +638,263 @@ pub mod pallet {
|
||||
Self::deposit_event(Event::Attempted(outcome));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extoll that a particular destination can be communicated with through a particular
|
||||
/// version of XCM.
|
||||
///
|
||||
/// - `origin`: Must be Root.
|
||||
/// - `location`: The destination that is being described.
|
||||
/// - `xcm_version`: The latest version of XCM that `location` supports.
|
||||
#[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(())
|
||||
}
|
||||
|
||||
/// Set a safe XCM version (the version that XCM should be encoded with if the most recent
|
||||
/// version a destination can accept is unknown).
|
||||
///
|
||||
/// - `origin`: Must be Root.
|
||||
/// - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable.
|
||||
#[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(())
|
||||
}
|
||||
|
||||
/// Ask a location to notify us regarding their XCM version and any changes to it.
|
||||
///
|
||||
/// - `origin`: Must be Root.
|
||||
/// - `location`: The location to which we should subscribe for XCM version notifications.
|
||||
#[pallet::weight(100_000_000u64)]
|
||||
pub fn force_subscribe_version_notify(
|
||||
origin: OriginFor<T>,
|
||||
location: Box<VersionedMultiLocation>,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
let 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()
|
||||
})
|
||||
}
|
||||
|
||||
/// Require that a particular destination should no longer notify us regarding any XCM
|
||||
/// version changes.
|
||||
///
|
||||
/// - `origin`: Must be Root.
|
||||
/// - `location`: The location to which we are currently subscribed for XCM version
|
||||
/// notifications which we no longer desire.
|
||||
#[pallet::weight(100_000_000u64)]
|
||||
pub fn force_unsubscribe_version_notify(
|
||||
origin: OriginFor<T>,
|
||||
location: Box<VersionedMultiLocation>,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
let 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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Will always make progress, and will do its best not to use much more than `weight_cutoff`
|
||||
/// in doing so.
|
||||
pub(crate) fn check_xcm_version_change(
|
||||
mut stage: VersionMigrationStage,
|
||||
weight_cutoff: Weight,
|
||||
) -> (Weight, Option<VersionMigrationStage>) {
|
||||
let mut weight_used = 0;
|
||||
|
||||
// TODO: Correct weights for the components of this:
|
||||
let todo_sv_migrate_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write;
|
||||
let todo_vn_migrate_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write;
|
||||
let todo_vnt_already_notified_weight: Weight = T::DbWeight::get().read;
|
||||
let todo_vnt_notify_weight: Weight =
|
||||
T::DbWeight::get().read + T::DbWeight::get().write * 3;
|
||||
let todo_vnt_migrate_weight: Weight =
|
||||
T::DbWeight::get().read + T::DbWeight::get().write;
|
||||
let todo_vnt_migrate_fail_weight: Weight =
|
||||
T::DbWeight::get().read + T::DbWeight::get().write;
|
||||
let todo_vnt_notify_migrate_weight: Weight =
|
||||
T::DbWeight::get().read + T::DbWeight::get().write * 3;
|
||||
|
||||
use VersionMigrationStage::*;
|
||||
|
||||
if stage == MigrateSupportedVersion {
|
||||
// We assume that supported XCM version only ever increases, so just cycle through lower
|
||||
// XCM versioned from the current.
|
||||
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 >= 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 >= 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,
|
||||
_ => {
|
||||
// We don't early return here since we need to be certain that we
|
||||
// make some progress.
|
||||
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 >= 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 >= 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 {
|
||||
// Need to notify target.
|
||||
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 >= weight_cutoff {
|
||||
return (weight_used, Some(stage))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(weight_used, None)
|
||||
}
|
||||
|
||||
/// Request that `dest` informs us of its version.
|
||||
pub fn request_version_notify(dest: MultiLocation) -> XcmResult {
|
||||
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
|
||||
});
|
||||
// TODO #3735: Correct weight.
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Request that `dest` ceases informing us of its version.
|
||||
pub fn unrequest_version_notify(dest: MultiLocation) -> XcmResult {
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
|
||||
/// location. A null `dest` is not handled.
|
||||
pub fn send_xcm(
|
||||
@@ -475,9 +919,9 @@ pub mod pallet {
|
||||
maybe_notify: Option<(u8, u8)>,
|
||||
timeout: T::BlockNumber,
|
||||
) -> u64 {
|
||||
QueryCount::<T>::mutate(|q| {
|
||||
QueryCounter::<T>::mutate(|q| {
|
||||
let r = *q;
|
||||
*q += 1;
|
||||
q.saturating_inc();
|
||||
Queries::<T>::insert(
|
||||
r,
|
||||
QueryStatus::Pending { responder: responder.into(), maybe_notify, timeout },
|
||||
@@ -579,8 +1023,68 @@ pub mod pallet {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Note that a particular destination to whom we would like to send a message is unknown
|
||||
/// and queue it for version discovery.
|
||||
fn note_unknown_version(dest: &MultiLocation) {
|
||||
let versioned_dest = VersionedMultiLocation::from(dest.clone());
|
||||
VersionDiscoveryQueue::<T>::mutate(|q| {
|
||||
if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
|
||||
// exists - just bump the count.
|
||||
q[index].1.saturating_inc();
|
||||
} else {
|
||||
let _ = q.try_push((versioned_dest, 1));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> WrapVersion for Pallet<T> {
|
||||
fn wrap_version<Call>(
|
||||
dest: &MultiLocation,
|
||||
xcm: impl Into<VersionedXcm<Call>>,
|
||||
) -> Result<VersionedXcm<Call>, ()> {
|
||||
SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedMultiLocation(dest))
|
||||
.or_else(|| {
|
||||
Self::note_unknown_version(dest);
|
||||
SafeXcmVersion::<T>::get()
|
||||
})
|
||||
.ok_or(())
|
||||
.and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> VersionChangeNotifier for Pallet<T> {
|
||||
/// Start notifying `location` should the XCM version of this chain change.
|
||||
///
|
||||
/// When it does, this type should ensure a `QueryResponse` message is sent with the given
|
||||
/// `query_id` & `max_weight` and with a `response` of `Repsonse::Version`. This should happen
|
||||
/// until/unless `stop` is called with the correct `query_id`.
|
||||
///
|
||||
/// If the `location` has an ongoing notification and when this function is called, then an
|
||||
/// error should be returned.
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Stop notifying `location` should the XCM change. This is a no-op if there was never a
|
||||
/// subscription.
|
||||
fn stop(dest: &MultiLocation) -> XcmResult {
|
||||
VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedMultiLocation(dest));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl<T: Config> DropAssets for Pallet<T> {
|
||||
fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight {
|
||||
if assets.is_empty() {
|
||||
@@ -590,7 +1094,7 @@ pub mod pallet {
|
||||
let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
|
||||
AssetTraps::<T>::mutate(hash, |n| *n += 1);
|
||||
Self::deposit_event(Event::AssetsTrapped(hash, origin.clone(), versioned));
|
||||
// TODO: Put the real weight in there.
|
||||
// TODO #3735: Put the real weight in there.
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -623,10 +1127,13 @@ pub mod pallet {
|
||||
|
||||
impl<T: Config> OnResponse for Pallet<T> {
|
||||
fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool {
|
||||
if let Some(QueryStatus::Pending { responder, .. }) = Queries::<T>::get(query_id) {
|
||||
return MultiLocation::try_from(responder).map_or(false, |r| origin == &r)
|
||||
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,
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn on_response(
|
||||
@@ -635,87 +1142,133 @@ pub mod pallet {
|
||||
response: Response,
|
||||
max_weight: Weight,
|
||||
) -> Weight {
|
||||
if let Some(QueryStatus::Pending { responder, maybe_notify, .. }) =
|
||||
Queries::<T>::get(query_id)
|
||||
{
|
||||
if let Ok(responder) = MultiLocation::try_from(responder) {
|
||||
if origin == &responder {
|
||||
return match maybe_notify {
|
||||
Some((pallet_index, call_index)) => {
|
||||
// This is a bit horrible, but we happen to know that the `Call` will
|
||||
// be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`.
|
||||
// So we just encode that and then re-encode to a real Call.
|
||||
let bare = (pallet_index, call_index, query_id, response);
|
||||
if let Ok(call) = bare.using_encoded(|mut bytes| {
|
||||
<T as Config>::Call::decode(&mut bytes)
|
||||
}) {
|
||||
Queries::<T>::remove(query_id);
|
||||
let weight = call.get_dispatch_info().weight;
|
||||
if weight > 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);
|
||||
// Not much to do with the result as it is. It's up to the parachain to ensure that the
|
||||
// message makes sense.
|
||||
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);
|
||||
0
|
||||
}
|
||||
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,
|
||||
));
|
||||
// TODO #3735: Correct weight for this.
|
||||
return 0
|
||||
},
|
||||
};
|
||||
// TODO #3735: Check max_weight is correct.
|
||||
if !is_active {
|
||||
Queries::<T>::insert(
|
||||
query_id,
|
||||
QueryStatus::VersionNotifier {
|
||||
origin: origin.clone().into(),
|
||||
is_active: true,
|
||||
},
|
||||
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
|
||||
},
|
||||
}
|
||||
} else {
|
||||
);
|
||||
}
|
||||
// We're being notified of a version change.
|
||||
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,
|
||||
responder,
|
||||
Some(responder),
|
||||
));
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
Self::deposit_event(Event::InvalidResponderVersion(origin.clone(), query_id));
|
||||
}
|
||||
} else {
|
||||
Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id));
|
||||
return match maybe_notify {
|
||||
Some((pallet_index, call_index)) => {
|
||||
// This is a bit horrible, but we happen to know that the `Call` will
|
||||
// be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`.
|
||||
// So we just encode that and then re-encode to a real Call.
|
||||
let bare = (pallet_index, call_index, query_id, response);
|
||||
if let Ok(call) = bare
|
||||
.using_encoded(|mut bytes| <T as Config>::Call::decode(&mut bytes))
|
||||
{
|
||||
Queries::<T>::remove(query_id);
|
||||
let weight = call.get_dispatch_info().weight;
|
||||
if weight > 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);
|
||||
// Not much to do with the result as it is. It's up to the parachain to ensure that the
|
||||
// message makes sense.
|
||||
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);
|
||||
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
|
||||
},
|
||||
}
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ use sp_runtime::{testing::Header, traits::IdentityLookup, AccountId32};
|
||||
pub use sp_std::{cell::RefCell, fmt::Debug, marker::PhantomData};
|
||||
use xcm::latest::prelude::*;
|
||||
use xcm_builder::{
|
||||
AccountId32Aliases, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, Case,
|
||||
ChildParachainAsNative, ChildParachainConvertsVia, ChildSystemParachainAsSuperuser,
|
||||
CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible, FixedWeightBounds, IsConcrete,
|
||||
LocationInverter, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation,
|
||||
TakeWeightCredit,
|
||||
AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
|
||||
AllowTopLevelPaidExecutionFrom, Case, ChildParachainAsNative, ChildParachainConvertsVia,
|
||||
ChildSystemParachainAsSuperuser, CurrencyAdapter as XcmCurrencyAdapter, FixedRateOfFungible,
|
||||
FixedWeightBounds, IsConcrete, LocationInverter, SignedAccountId32AsNative,
|
||||
SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
|
||||
};
|
||||
use xcm_executor::XcmExecutor;
|
||||
|
||||
@@ -133,9 +133,16 @@ construct_runtime!(
|
||||
thread_local! {
|
||||
pub static SENT_XCM: RefCell<Vec<(MultiLocation, Xcm<()>)>> = RefCell::new(Vec::new());
|
||||
}
|
||||
pub fn sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> {
|
||||
pub(crate) fn sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> {
|
||||
SENT_XCM.with(|q| (*q.borrow()).clone())
|
||||
}
|
||||
pub(crate) fn take_sent_xcm() -> Vec<(MultiLocation, Xcm<()>)> {
|
||||
SENT_XCM.with(|q| {
|
||||
let mut r = Vec::new();
|
||||
std::mem::swap(&mut r, &mut *q.borrow_mut());
|
||||
r
|
||||
})
|
||||
}
|
||||
/// Sender that never returns error, always sends
|
||||
pub struct TestSendXcm;
|
||||
impl SendXcm for TestSendXcm {
|
||||
@@ -236,6 +243,7 @@ pub type Barrier = (
|
||||
TakeWeightCredit,
|
||||
AllowTopLevelPaidExecutionFrom<Everything>,
|
||||
AllowKnownQueryResponses<XcmPallet>,
|
||||
AllowSubscriptionsFrom<Everything>,
|
||||
);
|
||||
|
||||
pub struct XcmConfig;
|
||||
@@ -253,10 +261,15 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type ResponseHandler = XcmPallet;
|
||||
type AssetTrap = XcmPallet;
|
||||
type AssetClaims = XcmPallet;
|
||||
type SubscriptionService = XcmPallet;
|
||||
}
|
||||
|
||||
pub type LocalOriginToLocation = SignedToAccountId32<Origin, AccountId, AnyNetwork>;
|
||||
|
||||
parameter_types! {
|
||||
pub static AdvertisedXcmVersion: pallet_xcm::XcmVersion = 2;
|
||||
}
|
||||
|
||||
impl pallet_xcm::Config for Test {
|
||||
type Event = Event;
|
||||
type SendXcmOrigin = xcm_builder::EnsureXcmOrigin<Origin, LocalOriginToLocation>;
|
||||
@@ -270,6 +283,8 @@ impl pallet_xcm::Config for Test {
|
||||
type LocationInverter = LocationInverter<Ancestry>;
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = AdvertisedXcmVersion;
|
||||
}
|
||||
|
||||
impl origin::Config for Test {}
|
||||
|
||||
@@ -14,13 +14,20 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::{mock::*, AssetTraps, QueryStatus};
|
||||
use frame_support::{assert_noop, assert_ok, traits::Currency};
|
||||
use crate::{
|
||||
mock::*, AssetTraps, CurrentMigration, Error, LatestVersionedMultiLocation, Queries,
|
||||
QueryStatus, VersionDiscoveryQueue, VersionNotifiers, VersionNotifyTargets,
|
||||
};
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok,
|
||||
traits::{Currency, Hooks},
|
||||
};
|
||||
use polkadot_parachain::primitives::{AccountIdConversion, Id as ParaId};
|
||||
use sp_runtime::traits::{BlakeTwo256, Hash};
|
||||
use std::convert::TryInto;
|
||||
use xcm::{latest::prelude::*, VersionedMultiAssets, VersionedXcm};
|
||||
use xcm_executor::XcmExecutor;
|
||||
use xcm::prelude::*;
|
||||
use xcm_builder::AllowKnownQueryResponses;
|
||||
use xcm_executor::{traits::ShouldExecute, XcmExecutor};
|
||||
|
||||
const ALICE: AccountId = AccountId::new([0u8; 32]);
|
||||
const BOB: AccountId = AccountId::new([1u8; 32]);
|
||||
@@ -375,3 +382,423 @@ fn trapped_assets_can_be_claimed() {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fake_latest_versioned_multilocation_works() {
|
||||
use codec::Encode;
|
||||
let remote = Parachain(1000).into();
|
||||
let versioned_remote = LatestVersionedMultiLocation(&remote);
|
||||
assert_eq!(versioned_remote.encode(), VersionedMultiLocation::from(remote.clone()).encode());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_subscription_works() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
let remote = Parachain(1000).into();
|
||||
assert_ok!(XcmPallet::force_subscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote.clone().into()),
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
Queries::<Test>::iter().collect::<Vec<_>>(),
|
||||
vec![(
|
||||
0,
|
||||
QueryStatus::VersionNotifier { origin: remote.clone().into(), is_active: false }
|
||||
)]
|
||||
);
|
||||
assert_eq!(
|
||||
VersionNotifiers::<Test>::iter().collect::<Vec<_>>(),
|
||||
vec![(2, remote.clone().into(), 0)]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
take_sent_xcm(),
|
||||
vec![(
|
||||
remote.clone(),
|
||||
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]),
|
||||
),]
|
||||
);
|
||||
|
||||
let weight = BaseXcmWeight::get();
|
||||
let mut message = Xcm::<()>(vec![
|
||||
// Remote supports XCM v1
|
||||
QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) },
|
||||
]);
|
||||
assert_ok!(AllowKnownQueryResponses::<XcmPallet>::should_execute(
|
||||
&remote,
|
||||
&mut message,
|
||||
weight,
|
||||
&mut 0
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subscriptions_increment_id() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
let remote = Parachain(1000).into();
|
||||
assert_ok!(XcmPallet::force_subscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote.clone().into()),
|
||||
));
|
||||
|
||||
let remote2 = Parachain(1001).into();
|
||||
assert_ok!(XcmPallet::force_subscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote2.clone().into()),
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
take_sent_xcm(),
|
||||
vec![
|
||||
(
|
||||
remote.clone(),
|
||||
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]),
|
||||
),
|
||||
(
|
||||
remote2.clone(),
|
||||
Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: 0 }]),
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_subscription_fails() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
let remote = Parachain(1000).into();
|
||||
assert_ok!(XcmPallet::force_subscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote.clone().into()),
|
||||
));
|
||||
assert_noop!(
|
||||
XcmPallet::force_subscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote.clone().into())
|
||||
),
|
||||
Error::<Test>::AlreadySubscribed,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsubscribe_works() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
let remote = Parachain(1000).into();
|
||||
assert_ok!(XcmPallet::force_subscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote.clone().into()),
|
||||
));
|
||||
assert_ok!(XcmPallet::force_unsubscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote.clone().into())
|
||||
));
|
||||
assert_noop!(
|
||||
XcmPallet::force_unsubscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote.clone().into())
|
||||
),
|
||||
Error::<Test>::NoSubscription,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
take_sent_xcm(),
|
||||
vec![
|
||||
(
|
||||
remote.clone(),
|
||||
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]),
|
||||
),
|
||||
(remote.clone(), Xcm(vec![UnsubscribeVersion]),),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Parachain 1000 is asking us for a version subscription.
|
||||
#[test]
|
||||
fn subscription_side_works() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
AdvertisedXcmVersion::set(1);
|
||||
|
||||
let remote = Parachain(1000).into();
|
||||
let weight = BaseXcmWeight::get();
|
||||
let message = Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]);
|
||||
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote.clone(), message, weight);
|
||||
assert_eq!(r, Outcome::Complete(weight));
|
||||
|
||||
let instr = QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) };
|
||||
assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]);
|
||||
|
||||
// A runtime upgrade which doesn't alter the version sends no notifications.
|
||||
XcmPallet::on_runtime_upgrade();
|
||||
XcmPallet::on_initialize(1);
|
||||
assert_eq!(take_sent_xcm(), vec![]);
|
||||
|
||||
// New version.
|
||||
AdvertisedXcmVersion::set(2);
|
||||
|
||||
// A runtime upgrade which alters the version does send notifications.
|
||||
XcmPallet::on_runtime_upgrade();
|
||||
XcmPallet::on_initialize(2);
|
||||
let instr = QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) };
|
||||
assert_eq!(take_sent_xcm(), vec![(remote.clone(), Xcm(vec![instr]))]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subscription_side_upgrades_work_with_notify() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
AdvertisedXcmVersion::set(1);
|
||||
|
||||
// An entry from a previous runtime with v0 XCM.
|
||||
let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000));
|
||||
let v0_location = VersionedMultiLocation::from(v0_location);
|
||||
VersionNotifyTargets::<Test>::insert(0, v0_location, (69, 0, 1));
|
||||
let v1_location = Parachain(1001).into().versioned();
|
||||
VersionNotifyTargets::<Test>::insert(1, v1_location, (70, 0, 1));
|
||||
let v2_location = Parachain(1002).into().versioned();
|
||||
VersionNotifyTargets::<Test>::insert(2, v2_location, (71, 0, 1));
|
||||
|
||||
// New version.
|
||||
AdvertisedXcmVersion::set(2);
|
||||
|
||||
// A runtime upgrade which alters the version does send notifications.
|
||||
XcmPallet::on_runtime_upgrade();
|
||||
XcmPallet::on_initialize(1);
|
||||
|
||||
let instr0 = QueryResponse { query_id: 69, max_weight: 0, response: Response::Version(2) };
|
||||
let instr1 = QueryResponse { query_id: 70, max_weight: 0, response: Response::Version(2) };
|
||||
let instr2 = QueryResponse { query_id: 71, max_weight: 0, response: Response::Version(2) };
|
||||
let mut sent = take_sent_xcm();
|
||||
sent.sort_by_key(|k| match (k.1).0[0] {
|
||||
QueryResponse { query_id: q, .. } => q,
|
||||
_ => 0,
|
||||
});
|
||||
assert_eq!(
|
||||
sent,
|
||||
vec![
|
||||
(Parachain(1000).into(), Xcm(vec![instr0])),
|
||||
(Parachain(1001).into(), Xcm(vec![instr1])),
|
||||
(Parachain(1002).into(), Xcm(vec![instr2])),
|
||||
]
|
||||
);
|
||||
|
||||
let mut contents = VersionNotifyTargets::<Test>::iter().collect::<Vec<_>>();
|
||||
contents.sort_by_key(|k| k.2);
|
||||
assert_eq!(
|
||||
contents,
|
||||
vec![
|
||||
(2, Parachain(1000).into().versioned(), (69, 0, 2)),
|
||||
(2, Parachain(1001).into().versioned(), (70, 0, 2)),
|
||||
(2, Parachain(1002).into().versioned(), (71, 0, 2)),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subscription_side_upgrades_work_without_notify() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
// An entry from a previous runtime with v0 XCM.
|
||||
let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000));
|
||||
let v0_location = VersionedMultiLocation::from(v0_location);
|
||||
VersionNotifyTargets::<Test>::insert(0, v0_location, (69, 0, 2));
|
||||
let v1_location = Parachain(1001).into().versioned();
|
||||
VersionNotifyTargets::<Test>::insert(1, v1_location, (70, 0, 2));
|
||||
let v2_location = Parachain(1002).into().versioned();
|
||||
VersionNotifyTargets::<Test>::insert(2, v2_location, (71, 0, 2));
|
||||
|
||||
// A runtime upgrade which alters the version does send notifications.
|
||||
XcmPallet::on_runtime_upgrade();
|
||||
XcmPallet::on_initialize(1);
|
||||
|
||||
let mut contents = VersionNotifyTargets::<Test>::iter().collect::<Vec<_>>();
|
||||
contents.sort_by_key(|k| k.2);
|
||||
assert_eq!(
|
||||
contents,
|
||||
vec![
|
||||
(2, Parachain(1000).into().versioned(), (69, 0, 2)),
|
||||
(2, Parachain(1001).into().versioned(), (70, 0, 2)),
|
||||
(2, Parachain(1002).into().versioned(), (71, 0, 2)),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subscriber_side_subscription_works() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
let remote = Parachain(1000).into();
|
||||
assert_ok!(XcmPallet::force_subscribe_version_notify(
|
||||
Origin::root(),
|
||||
Box::new(remote.clone().into()),
|
||||
));
|
||||
take_sent_xcm();
|
||||
|
||||
// Assume subscription target is working ok.
|
||||
|
||||
let weight = BaseXcmWeight::get();
|
||||
let message = Xcm(vec![
|
||||
// Remote supports XCM v1
|
||||
QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(1) },
|
||||
]);
|
||||
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote.clone(), message, weight);
|
||||
assert_eq!(r, Outcome::Complete(weight));
|
||||
assert_eq!(take_sent_xcm(), vec![]);
|
||||
|
||||
// This message cannot be sent to a v1 remote.
|
||||
let v2_msg = Xcm::<()>(vec![Trap(0)]);
|
||||
assert_eq!(XcmPallet::wrap_version(&remote, v2_msg.clone()), Err(()));
|
||||
|
||||
let message = Xcm(vec![
|
||||
// Remote upgraded to XCM v2
|
||||
QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) },
|
||||
]);
|
||||
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote.clone(), message, weight);
|
||||
assert_eq!(r, Outcome::Complete(weight));
|
||||
|
||||
// This message can now be sent to remote as it's v2.
|
||||
assert_eq!(
|
||||
XcmPallet::wrap_version(&remote, v2_msg.clone()),
|
||||
Ok(VersionedXcm::from(v2_msg))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// We should autosubscribe when we don't know the remote's version.
|
||||
#[test]
|
||||
fn auto_subscription_works() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
let remote0 = Parachain(1000).into();
|
||||
let remote1 = Parachain(1001).into();
|
||||
|
||||
assert_ok!(XcmPallet::force_default_xcm_version(Origin::root(), Some(1)));
|
||||
|
||||
// Wrapping a version for a destination we don't know elicits a subscription.
|
||||
let v1_msg = xcm::v1::Xcm::<()>::QueryResponse {
|
||||
query_id: 1,
|
||||
response: xcm::v1::Response::Assets(vec![].into()),
|
||||
};
|
||||
let v2_msg = Xcm::<()>(vec![Trap(0)]);
|
||||
assert_eq!(
|
||||
XcmPallet::wrap_version(&remote0, v1_msg.clone()),
|
||||
Ok(VersionedXcm::from(v1_msg.clone())),
|
||||
);
|
||||
assert_eq!(XcmPallet::wrap_version(&remote0, v2_msg.clone()), Err(()));
|
||||
let expected = vec![(remote0.clone().into(), 2)];
|
||||
assert_eq!(VersionDiscoveryQueue::<Test>::get().into_inner(), expected);
|
||||
|
||||
assert_eq!(XcmPallet::wrap_version(&remote0, v2_msg.clone()), Err(()));
|
||||
assert_eq!(XcmPallet::wrap_version(&remote1, v2_msg.clone()), Err(()));
|
||||
let expected = vec![(remote0.clone().into(), 3), (remote1.clone().into(), 1)];
|
||||
assert_eq!(VersionDiscoveryQueue::<Test>::get().into_inner(), expected);
|
||||
|
||||
XcmPallet::on_initialize(1);
|
||||
assert_eq!(
|
||||
take_sent_xcm(),
|
||||
vec![(
|
||||
remote0.clone(),
|
||||
Xcm(vec![SubscribeVersion { query_id: 0, max_response_weight: 0 }]),
|
||||
)]
|
||||
);
|
||||
|
||||
// Assume remote0 is working ok and XCM version 2.
|
||||
|
||||
let weight = BaseXcmWeight::get();
|
||||
let message = Xcm(vec![
|
||||
// Remote supports XCM v2
|
||||
QueryResponse { query_id: 0, max_weight: 0, response: Response::Version(2) },
|
||||
]);
|
||||
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote0.clone(), message, weight);
|
||||
assert_eq!(r, Outcome::Complete(weight));
|
||||
|
||||
// This message can now be sent to remote0 as it's v2.
|
||||
assert_eq!(
|
||||
XcmPallet::wrap_version(&remote0, v2_msg.clone()),
|
||||
Ok(VersionedXcm::from(v2_msg.clone()))
|
||||
);
|
||||
|
||||
XcmPallet::on_initialize(2);
|
||||
assert_eq!(
|
||||
take_sent_xcm(),
|
||||
vec![(
|
||||
remote1.clone(),
|
||||
Xcm(vec![SubscribeVersion { query_id: 1, max_response_weight: 0 }]),
|
||||
)]
|
||||
);
|
||||
|
||||
// Assume remote1 is working ok and XCM version 1.
|
||||
|
||||
let weight = BaseXcmWeight::get();
|
||||
let message = Xcm(vec![
|
||||
// Remote supports XCM v1
|
||||
QueryResponse { query_id: 1, max_weight: 0, response: Response::Version(1) },
|
||||
]);
|
||||
let r = XcmExecutor::<XcmConfig>::execute_xcm(remote1.clone(), message, weight);
|
||||
assert_eq!(r, Outcome::Complete(weight));
|
||||
|
||||
// v2 messages cannot be sent to remote1...
|
||||
assert_eq!(XcmPallet::wrap_version(&remote1, v1_msg.clone()), Ok(VersionedXcm::V1(v1_msg)));
|
||||
assert_eq!(XcmPallet::wrap_version(&remote1, v2_msg.clone()), Err(()));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subscription_side_upgrades_work_with_multistage_notify() {
|
||||
new_test_ext_with_balances(vec![]).execute_with(|| {
|
||||
AdvertisedXcmVersion::set(1);
|
||||
|
||||
// An entry from a previous runtime with v0 XCM.
|
||||
let v0_location = xcm::v0::MultiLocation::X1(xcm::v0::Junction::Parachain(1000));
|
||||
let v0_location = VersionedMultiLocation::from(v0_location);
|
||||
VersionNotifyTargets::<Test>::insert(0, v0_location, (69, 0, 1));
|
||||
let v1_location = Parachain(1001).into().versioned();
|
||||
VersionNotifyTargets::<Test>::insert(1, v1_location, (70, 0, 1));
|
||||
let v2_location = Parachain(1002).into().versioned();
|
||||
VersionNotifyTargets::<Test>::insert(2, v2_location, (71, 0, 1));
|
||||
|
||||
// New version.
|
||||
AdvertisedXcmVersion::set(2);
|
||||
|
||||
// A runtime upgrade which alters the version does send notifications.
|
||||
XcmPallet::on_runtime_upgrade();
|
||||
let mut maybe_migration = CurrentMigration::<Test>::take();
|
||||
let mut counter = 0;
|
||||
while let Some(migration) = maybe_migration.take() {
|
||||
counter += 1;
|
||||
let (_, m) = XcmPallet::check_xcm_version_change(migration, 0);
|
||||
maybe_migration = m;
|
||||
}
|
||||
assert_eq!(counter, 4);
|
||||
|
||||
let instr0 = QueryResponse { query_id: 69, max_weight: 0, response: Response::Version(2) };
|
||||
let instr1 = QueryResponse { query_id: 70, max_weight: 0, response: Response::Version(2) };
|
||||
let instr2 = QueryResponse { query_id: 71, max_weight: 0, response: Response::Version(2) };
|
||||
let mut sent = take_sent_xcm();
|
||||
sent.sort_by_key(|k| match (k.1).0[0] {
|
||||
QueryResponse { query_id: q, .. } => q,
|
||||
_ => 0,
|
||||
});
|
||||
assert_eq!(
|
||||
sent,
|
||||
vec![
|
||||
(Parachain(1000).into(), Xcm(vec![instr0])),
|
||||
(Parachain(1001).into(), Xcm(vec![instr1])),
|
||||
(Parachain(1002).into(), Xcm(vec![instr2])),
|
||||
]
|
||||
);
|
||||
|
||||
let mut contents = VersionNotifyTargets::<Test>::iter().collect::<Vec<_>>();
|
||||
contents.sort_by_key(|k| k.2);
|
||||
assert_eq!(
|
||||
contents,
|
||||
vec![
|
||||
(2, Parachain(1000).into().versioned(), (69, 0, 2)),
|
||||
(2, Parachain(1001).into().versioned(), (70, 0, 2)),
|
||||
(2, Parachain(1002).into().versioned(), (71, 0, 2)),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
+66
-2
@@ -45,6 +45,9 @@ pub use double_encoded::DoubleEncoded;
|
||||
/// Maximum nesting level for XCM decoding.
|
||||
pub const MAX_XCM_DECODE_DEPTH: u32 = 8;
|
||||
|
||||
/// A version of XCM.
|
||||
pub type Version = u32;
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Unsupported {}
|
||||
impl Encode for Unsupported {}
|
||||
@@ -54,6 +57,17 @@ impl Decode for Unsupported {
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to convert `self` into a particular version of itself.
|
||||
pub trait IntoVersion: Sized {
|
||||
/// Consume `self` and return same value expressed in some particular `version` of XCM.
|
||||
fn into_version(self, version: Version) -> Result<Self, ()>;
|
||||
|
||||
/// Consume `self` and return same value expressed the latest version of XCM.
|
||||
fn into_latest(self) -> Result<Self, ()> {
|
||||
self.into_version(latest::VERSION)
|
||||
}
|
||||
}
|
||||
|
||||
/// A single `MultiLocation` value, together with its version code.
|
||||
#[derive(Derivative, Encode, Decode)]
|
||||
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
|
||||
@@ -64,6 +78,16 @@ pub enum VersionedMultiLocation {
|
||||
V1(v1::MultiLocation),
|
||||
}
|
||||
|
||||
impl IntoVersion for VersionedMultiLocation {
|
||||
fn into_version(self, n: Version) -> Result<Self, ()> {
|
||||
Ok(match n {
|
||||
0 => Self::V0(self.try_into()?),
|
||||
1 | 2 => Self::V1(self.try_into()?),
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v0::MultiLocation> for VersionedMultiLocation {
|
||||
fn from(x: v0::MultiLocation) -> Self {
|
||||
VersionedMultiLocation::V0(x)
|
||||
@@ -109,6 +133,17 @@ pub enum VersionedResponse {
|
||||
V2(v2::Response),
|
||||
}
|
||||
|
||||
impl IntoVersion for VersionedResponse {
|
||||
fn into_version(self, n: Version) -> Result<Self, ()> {
|
||||
Ok(match n {
|
||||
0 => Self::V0(self.try_into()?),
|
||||
1 => Self::V1(self.try_into()?),
|
||||
2 => Self::V2(self.try_into()?),
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v0::Response> for VersionedResponse {
|
||||
fn from(x: v0::Response) -> Self {
|
||||
VersionedResponse::V0(x)
|
||||
@@ -173,6 +208,16 @@ pub enum VersionedMultiAsset {
|
||||
V1(v1::MultiAsset),
|
||||
}
|
||||
|
||||
impl IntoVersion for VersionedMultiAsset {
|
||||
fn into_version(self, n: Version) -> Result<Self, ()> {
|
||||
Ok(match n {
|
||||
0 => Self::V0(self.try_into()?),
|
||||
1 | 2 => Self::V1(self.try_into()?),
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<v0::MultiAsset> for VersionedMultiAsset {
|
||||
fn from(x: v0::MultiAsset) -> Self {
|
||||
VersionedMultiAsset::V0(x)
|
||||
@@ -217,8 +262,8 @@ pub enum VersionedMultiAssets {
|
||||
V1(v1::MultiAssets),
|
||||
}
|
||||
|
||||
impl VersionedMultiAssets {
|
||||
pub fn into_version(self, n: u32) -> Result<Self, ()> {
|
||||
impl IntoVersion for VersionedMultiAssets {
|
||||
fn into_version(self, n: Version) -> Result<Self, ()> {
|
||||
Ok(match n {
|
||||
0 => Self::V0(self.try_into()?),
|
||||
1 | 2 => Self::V1(self.try_into()?),
|
||||
@@ -272,6 +317,17 @@ pub enum VersionedXcm<Call> {
|
||||
V2(v2::Xcm<Call>),
|
||||
}
|
||||
|
||||
impl<C> IntoVersion for VersionedXcm<C> {
|
||||
fn into_version(self, n: Version) -> Result<Self, ()> {
|
||||
Ok(match n {
|
||||
0 => Self::V0(self.try_into()?),
|
||||
1 => Self::V1(self.try_into()?),
|
||||
2 => Self::V2(self.try_into()?),
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<Call> From<v0::Xcm<Call>> for VersionedXcm<Call> {
|
||||
fn from(x: v0::Xcm<Call>) -> Self {
|
||||
VersionedXcm::V0(x)
|
||||
@@ -383,6 +439,14 @@ pub type AlwaysLatest = AlwaysV1;
|
||||
/// `WrapVersion` implementation which attempts to always convert the XCM to the release version before wrapping it.
|
||||
pub type AlwaysRelease = AlwaysV0;
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::{
|
||||
latest::prelude::*, AlwaysLatest, AlwaysRelease, AlwaysV0, AlwaysV1, AlwaysV2, IntoVersion,
|
||||
Unsupported, Version as XcmVersion, VersionedMultiAsset, VersionedMultiAssets,
|
||||
VersionedMultiLocation, VersionedResponse, VersionedXcm, WrapVersion,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod opaque {
|
||||
pub mod v0 {
|
||||
// Everything from v0
|
||||
|
||||
@@ -326,6 +326,7 @@ impl TryFrom<Response1> for Response {
|
||||
fn try_from(new_response: Response1) -> result::Result<Self, ()> {
|
||||
Ok(match new_response {
|
||||
Response1::Assets(assets) => Self::Assets(assets.try_into()?),
|
||||
Response1::Version(..) => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -379,6 +380,7 @@ impl<Call> TryFrom<Xcm1<Call>> for Xcm<Call> {
|
||||
who: MultiLocation1 { interior: who, parents: 0 }.try_into()?,
|
||||
message: alloc::boxed::Box::new((*message).try_into()?),
|
||||
},
|
||||
Xcm1::SubscribeVersion { .. } | Xcm1::UnsubscribeVersion => return Err(()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ pub mod prelude {
|
||||
pub enum Response {
|
||||
/// Some assets.
|
||||
Assets(MultiAssets),
|
||||
/// An XCM version.
|
||||
Version(super::Version),
|
||||
}
|
||||
|
||||
/// Cross-Consensus Message: A message from one consensus system to another.
|
||||
@@ -270,6 +272,25 @@ pub enum Xcm<Call> {
|
||||
/// Errors:
|
||||
#[codec(index = 10)]
|
||||
RelayedFrom { who: InteriorMultiLocation, message: alloc::boxed::Box<Xcm<Call>> },
|
||||
|
||||
/// Ask the destination system to respond with the most recent version of XCM that they
|
||||
/// support in a `QueryResponse` instruction. Any changes to this should also elicit similar
|
||||
/// responses when they happen.
|
||||
///
|
||||
/// Kind: *Instruction*
|
||||
#[codec(index = 11)]
|
||||
SubscribeVersion {
|
||||
#[codec(compact)]
|
||||
query_id: u64,
|
||||
#[codec(compact)]
|
||||
max_response_weight: u64,
|
||||
},
|
||||
|
||||
/// Cancel the effect of a previous `SubscribeVersion` instruction.
|
||||
///
|
||||
/// Kind: *Instruction*
|
||||
#[codec(index = 12)]
|
||||
UnsubscribeVersion,
|
||||
}
|
||||
|
||||
impl<Call> Xcm<Call> {
|
||||
@@ -302,6 +323,9 @@ impl<Call> Xcm<Call> {
|
||||
Transact { origin_type, require_weight_at_most, call: call.into() },
|
||||
RelayedFrom { who, message } =>
|
||||
RelayedFrom { who, message: alloc::boxed::Box::new((*message).into()) },
|
||||
SubscribeVersion { query_id, max_response_weight } =>
|
||||
SubscribeVersion { query_id, max_response_weight },
|
||||
UnsubscribeVersion => UnsubscribeVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,6 +451,9 @@ impl<Call> TryFrom<NewXcm<Call>> for Xcm<Call> {
|
||||
HrmpChannelClosing { initiator, sender, recipient },
|
||||
Instruction::Transact { origin_type, require_weight_at_most, call } =>
|
||||
Transact { origin_type, require_weight_at_most, call },
|
||||
Instruction::SubscribeVersion { query_id, max_response_weight } =>
|
||||
SubscribeVersion { query_id, max_response_weight },
|
||||
Instruction::UnsubscribeVersion => UnsubscribeVersion,
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
@@ -438,6 +465,7 @@ impl TryFrom<NewResponse> for Response {
|
||||
fn try_from(response: NewResponse) -> result::Result<Self, ()> {
|
||||
match response {
|
||||
NewResponse::Assets(assets) => Ok(Self::Assets(assets)),
|
||||
NewResponse::Version(version) => Ok(Self::Version(version)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,23 +54,28 @@ pub struct MultiLocation {
|
||||
pub interior: Junctions,
|
||||
}
|
||||
|
||||
impl Default for MultiLocation {
|
||||
fn default() -> Self {
|
||||
Self { parents: 0, interior: Junctions::Here }
|
||||
}
|
||||
}
|
||||
|
||||
/// A relative location which is constrained to be an interior location of the context.
|
||||
///
|
||||
/// See also `MultiLocation`.
|
||||
pub type InteriorMultiLocation = Junctions;
|
||||
|
||||
impl Default for MultiLocation {
|
||||
fn default() -> Self {
|
||||
Self::here()
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiLocation {
|
||||
/// Creates a new `MultiLocation` with the given number of parents and interior junctions.
|
||||
pub fn new(parents: u8, junctions: Junctions) -> MultiLocation {
|
||||
MultiLocation { parents, interior: junctions }
|
||||
}
|
||||
|
||||
/// Consume `self` and return the equivalent `VersionedMultiLocation` value.
|
||||
pub fn versioned(self) -> crate::VersionedMultiLocation {
|
||||
self.into()
|
||||
}
|
||||
|
||||
/// Creates a new `MultiLocation` with 0 parents and a `Here` interior.
|
||||
///
|
||||
/// The resulting `MultiLocation` can be interpreted as the "current consensus system".
|
||||
|
||||
@@ -37,6 +37,12 @@ pub use super::v1::{
|
||||
MultiLocation, NetworkId, OriginKind, Parent, ParentThen, WildFungibility, WildMultiAsset,
|
||||
};
|
||||
|
||||
/// This module's XCM version.
|
||||
pub const VERSION: super::Version = 2;
|
||||
|
||||
/// An identifier for a query.
|
||||
pub type QueryId = u64;
|
||||
|
||||
#[derive(Derivative, Default, Encode, Decode)]
|
||||
#[derivative(Clone(bound = ""), Eq(bound = ""), PartialEq(bound = ""), Debug(bound = ""))]
|
||||
#[codec(encode_bound())]
|
||||
@@ -116,11 +122,12 @@ pub mod prelude {
|
||||
MultiAssetFilter::{self, *},
|
||||
MultiAssets, MultiLocation,
|
||||
NetworkId::{self, *},
|
||||
OriginKind, Outcome, Parent, ParentThen, Response, Result as XcmResult, SendError,
|
||||
SendResult, SendXcm,
|
||||
OriginKind, Outcome, Parent, ParentThen, QueryId, Response, Result as XcmResult,
|
||||
SendError, SendResult, SendXcm,
|
||||
WeightLimit::{self, *},
|
||||
WildFungibility::{self, Fungible as WildFungible, NonFungible as WildNonFungible},
|
||||
WildMultiAsset::{self, *},
|
||||
VERSION as XCM_VERSION,
|
||||
};
|
||||
}
|
||||
pub use super::{Instruction, Xcm};
|
||||
@@ -142,6 +149,8 @@ pub enum Response {
|
||||
Assets(MultiAssets),
|
||||
/// The outcome of an XCM instruction.
|
||||
ExecutionResult(result::Result<(), (u32, Error)>),
|
||||
/// An XCM version.
|
||||
Version(super::Version),
|
||||
}
|
||||
|
||||
impl Default for Response {
|
||||
@@ -239,7 +248,7 @@ pub enum Instruction<Call> {
|
||||
/// Errors:
|
||||
QueryResponse {
|
||||
#[codec(compact)]
|
||||
query_id: u64,
|
||||
query_id: QueryId,
|
||||
response: Response,
|
||||
#[codec(compact)]
|
||||
max_weight: u64,
|
||||
@@ -291,7 +300,12 @@ pub enum Instruction<Call> {
|
||||
/// Kind: *Instruction*.
|
||||
///
|
||||
/// Errors:
|
||||
Transact { origin_type: OriginKind, require_weight_at_most: u64, call: DoubleEncoded<Call> },
|
||||
Transact {
|
||||
origin_type: OriginKind,
|
||||
#[codec(compact)]
|
||||
require_weight_at_most: u64,
|
||||
call: DoubleEncoded<Call>,
|
||||
},
|
||||
|
||||
/// A message to notify about a new incoming HRMP channel. This message is meant to be sent by the
|
||||
/// relay-chain to a para.
|
||||
@@ -377,7 +391,7 @@ pub enum Instruction<Call> {
|
||||
/// Errors:
|
||||
ReportError {
|
||||
#[codec(compact)]
|
||||
query_id: u64,
|
||||
query_id: QueryId,
|
||||
dest: MultiLocation,
|
||||
#[codec(compact)]
|
||||
max_response_weight: u64,
|
||||
@@ -395,7 +409,12 @@ pub enum Instruction<Call> {
|
||||
/// Kind: *Instruction*
|
||||
///
|
||||
/// Errors:
|
||||
DepositAsset { assets: MultiAssetFilter, max_assets: u32, beneficiary: MultiLocation },
|
||||
DepositAsset {
|
||||
assets: MultiAssetFilter,
|
||||
#[codec(compact)]
|
||||
max_assets: u32,
|
||||
beneficiary: MultiLocation,
|
||||
},
|
||||
|
||||
/// Remove the asset(s) (`assets`) from the Holding Register and place equivalent assets under
|
||||
/// the ownership of `dest` within this consensus system (i.e. deposit them into its sovereign
|
||||
@@ -418,6 +437,7 @@ pub enum Instruction<Call> {
|
||||
/// Errors:
|
||||
DepositReserveAsset {
|
||||
assets: MultiAssetFilter,
|
||||
#[codec(compact)]
|
||||
max_assets: u32,
|
||||
dest: MultiLocation,
|
||||
xcm: Xcm<()>,
|
||||
@@ -487,7 +507,7 @@ pub enum Instruction<Call> {
|
||||
/// Errors:
|
||||
QueryHolding {
|
||||
#[codec(compact)]
|
||||
query_id: u64,
|
||||
query_id: QueryId,
|
||||
dest: MultiLocation,
|
||||
assets: MultiAssetFilter,
|
||||
#[codec(compact)]
|
||||
@@ -571,7 +591,24 @@ pub enum Instruction<Call> {
|
||||
///
|
||||
/// Errors:
|
||||
/// - `Trap`: All circumstances, whose inner value is the same as this item's inner value.
|
||||
Trap(u64),
|
||||
Trap(#[codec(compact)] u64),
|
||||
|
||||
/// Ask the destination system to respond with the most recent version of XCM that they
|
||||
/// support in a `QueryResponse` instruction. Any changes to this should also elicit similar
|
||||
/// responses when they happen.
|
||||
///
|
||||
/// Kind: *Instruction*
|
||||
SubscribeVersion {
|
||||
#[codec(compact)]
|
||||
query_id: QueryId,
|
||||
#[codec(compact)]
|
||||
max_response_weight: u64,
|
||||
},
|
||||
|
||||
/// Cancel the effect of a previous `SubscribeVersion` instruction.
|
||||
///
|
||||
/// Kind: *Instruction*
|
||||
UnsubscribeVersion,
|
||||
}
|
||||
|
||||
impl<Call> Xcm<Call> {
|
||||
@@ -626,6 +663,9 @@ impl<Call> Instruction<Call> {
|
||||
ClearError => ClearError,
|
||||
ClaimAsset { assets, ticket } => ClaimAsset { assets, ticket },
|
||||
Trap(code) => Trap(code),
|
||||
SubscribeVersion { query_id, max_response_weight } =>
|
||||
SubscribeVersion { query_id, max_response_weight },
|
||||
UnsubscribeVersion => UnsubscribeVersion,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -646,6 +686,7 @@ impl TryFrom<OldResponse> for Response {
|
||||
fn try_from(old_response: OldResponse) -> result::Result<Self, ()> {
|
||||
match old_response {
|
||||
OldResponse::Assets(assets) => Ok(Self::Assets(assets)),
|
||||
OldResponse::Version(version) => Ok(Self::Version(version)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -695,6 +736,9 @@ impl<Call> TryFrom<OldXcm<Call>> for Xcm<Call> {
|
||||
vec![Transact { origin_type, require_weight_at_most, call }],
|
||||
// We don't handle this one at all due to nested XCM.
|
||||
OldXcm::RelayedFrom { .. } => return Err(()),
|
||||
OldXcm::SubscribeVersion { query_id, max_response_weight } =>
|
||||
vec![SubscribeVersion { query_id, max_response_weight }],
|
||||
OldXcm::UnsubscribeVersion => vec![UnsubscribeVersion],
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,8 @@ pub enum Error {
|
||||
Trap(u64),
|
||||
/// The given claim could not be recognized/found.
|
||||
UnknownClaim,
|
||||
/// The location given was invalid for some reason specific to the operation at hand.
|
||||
InvalidLocation,
|
||||
}
|
||||
|
||||
impl From<()> for Error {
|
||||
|
||||
@@ -31,7 +31,6 @@ pub struct TakeWeightCredit;
|
||||
impl ShouldExecute for TakeWeightCredit {
|
||||
fn should_execute<Call>(
|
||||
_origin: &MultiLocation,
|
||||
_top_level: bool,
|
||||
_message: &mut Xcm<Call>,
|
||||
max_weight: Weight,
|
||||
weight_credit: &mut Weight,
|
||||
@@ -44,19 +43,17 @@ impl ShouldExecute for TakeWeightCredit {
|
||||
/// Allows execution from `origin` if it is contained in `T` (i.e. `T::Contains(origin)`) taking
|
||||
/// payments into account.
|
||||
///
|
||||
/// Only allows for `TeleportAsset`, `WithdrawAsset` and `ReserveAssetDeposit` XCMs because they are
|
||||
/// the only ones that place assets in the Holding Register to pay for execution.
|
||||
/// Only allows for `TeleportAsset`, `WithdrawAsset`, `ClaimAsset` and `ReserveAssetDeposit` XCMs
|
||||
/// because they are the only ones that place assets in the Holding Register to pay for execution.
|
||||
pub struct AllowTopLevelPaidExecutionFrom<T>(PhantomData<T>);
|
||||
impl<T: Contains<MultiLocation>> ShouldExecute for AllowTopLevelPaidExecutionFrom<T> {
|
||||
fn should_execute<Call>(
|
||||
origin: &MultiLocation,
|
||||
top_level: bool,
|
||||
message: &mut Xcm<Call>,
|
||||
max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
ensure!(T::contains(origin), ());
|
||||
ensure!(top_level, ());
|
||||
let mut iter = message.0.iter_mut();
|
||||
let i = iter.next().ok_or(())?;
|
||||
match i {
|
||||
@@ -90,7 +87,6 @@ pub struct AllowUnpaidExecutionFrom<T>(PhantomData<T>);
|
||||
impl<T: Contains<MultiLocation>> ShouldExecute for AllowUnpaidExecutionFrom<T> {
|
||||
fn should_execute<Call>(
|
||||
origin: &MultiLocation,
|
||||
_top_level: bool,
|
||||
_message: &mut Xcm<Call>,
|
||||
_max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
@@ -117,7 +113,6 @@ pub struct AllowKnownQueryResponses<ResponseHandler>(PhantomData<ResponseHandler
|
||||
impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<ResponseHandler> {
|
||||
fn should_execute<Call>(
|
||||
origin: &MultiLocation,
|
||||
_top_level: bool,
|
||||
message: &mut Xcm<Call>,
|
||||
_max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
@@ -130,3 +125,21 @@ impl<ResponseHandler: OnResponse> ShouldExecute for AllowKnownQueryResponses<Res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows execution from `origin` if it is just a straight `SubscribeVerison` or
|
||||
/// `UnsubscribeVersion` instruction.
|
||||
pub struct AllowSubscriptionsFrom<T>(PhantomData<T>);
|
||||
impl<T: Contains<MultiLocation>> ShouldExecute for AllowSubscriptionsFrom<T> {
|
||||
fn should_execute<Call>(
|
||||
origin: &MultiLocation,
|
||||
message: &mut Xcm<Call>,
|
||||
_max_weight: Weight,
|
||||
_weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
ensure!(T::contains(origin), ());
|
||||
match (message.0.len(), message.0.first()) {
|
||||
(1, Some(SubscribeVersion { .. })) | (1, Some(UnsubscribeVersion)) => Ok(()),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ pub use origin_conversion::{
|
||||
|
||||
mod barriers;
|
||||
pub use barriers::{
|
||||
AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom,
|
||||
IsChildSystemParachain, TakeWeightCredit,
|
||||
AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom,
|
||||
AllowUnpaidExecutionFrom, IsChildSystemParachain, TakeWeightCredit,
|
||||
};
|
||||
|
||||
mod currency_adapter;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::barriers::AllowSubscriptionsFrom;
|
||||
pub use crate::{
|
||||
AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom,
|
||||
FixedRateOfFungible, FixedWeightBounds, LocationInverter, TakeWeightCredit,
|
||||
@@ -35,7 +36,7 @@ pub use sp_std::{
|
||||
marker::PhantomData,
|
||||
};
|
||||
pub use xcm::latest::prelude::*;
|
||||
use xcm_executor::traits::{ClaimAssets, DropAssets};
|
||||
use xcm_executor::traits::{ClaimAssets, DropAssets, VersionChangeNotifier};
|
||||
pub use xcm_executor::{
|
||||
traits::{ConvertOrigin, FilterAssetLocation, InvertLocation, OnResponse, TransactAsset},
|
||||
Assets, Config,
|
||||
@@ -258,6 +259,7 @@ parameter_types! {
|
||||
// Nothing is allowed to be paid/unpaid by default.
|
||||
pub static AllowUnpaidFrom: Vec<MultiLocation> = vec![];
|
||||
pub static AllowPaidFrom: Vec<MultiLocation> = vec![];
|
||||
pub static AllowSubsFrom: Vec<MultiLocation> = vec![];
|
||||
// 1_000_000_000_000 => 1 unit of asset for 1 unit of Weight.
|
||||
pub static WeightPrice: (AssetId, u128) = (From::from(Here), 1_000_000_000_000);
|
||||
pub static MaxInstructions: u32 = 100;
|
||||
@@ -268,6 +270,7 @@ pub type TestBarrier = (
|
||||
AllowKnownQueryResponses<TestResponseHandler>,
|
||||
AllowTopLevelPaidExecutionFrom<IsInVec<AllowPaidFrom>>,
|
||||
AllowUnpaidExecutionFrom<IsInVec<AllowUnpaidFrom>>,
|
||||
AllowSubscriptionsFrom<IsInVec<AllowSubsFrom>>,
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
@@ -301,6 +304,26 @@ impl ClaimAssets for TestAssetTrap {
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static SubscriptionRequests: Vec<(MultiLocation, Option<(QueryId, u64)>)> = vec![];
|
||||
}
|
||||
pub struct TestSubscriptionService;
|
||||
|
||||
impl VersionChangeNotifier for TestSubscriptionService {
|
||||
fn start(location: &MultiLocation, query_id: QueryId, max_weight: u64) -> XcmResult {
|
||||
let mut r = SubscriptionRequests::get();
|
||||
r.push((location.clone(), Some((query_id, max_weight))));
|
||||
SubscriptionRequests::set(r);
|
||||
Ok(())
|
||||
}
|
||||
fn stop(location: &MultiLocation) -> XcmResult {
|
||||
let mut r = SubscriptionRequests::get();
|
||||
r.push((location.clone(), None));
|
||||
SubscriptionRequests::set(r);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestConfig;
|
||||
impl Config for TestConfig {
|
||||
type Call = TestCall;
|
||||
@@ -316,4 +339,5 @@ impl Config for TestConfig {
|
||||
type ResponseHandler = TestResponseHandler;
|
||||
type AssetTrap = TestAssetTrap;
|
||||
type AssetClaims = TestAssetTrap;
|
||||
type SubscriptionService = TestSubscriptionService;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// Copyright 2020 Parity Technologies query_id: (), max_response_weight: () query_id: (), max_response_weight: () (UK) Ltd.
|
||||
// This file is part of Polkadot.
|
||||
|
||||
// Polkadot is free software: you can redistribute it and/or modify
|
||||
@@ -57,23 +57,11 @@ fn take_weight_credit_barrier_should_work() {
|
||||
let mut message =
|
||||
Xcm::<()>(vec![TransferAsset { assets: (Parent, 100).into(), beneficiary: Here.into() }]);
|
||||
let mut weight_credit = 10;
|
||||
let r = TakeWeightCredit::should_execute(
|
||||
&Parent.into(),
|
||||
true,
|
||||
&mut message,
|
||||
10,
|
||||
&mut weight_credit,
|
||||
);
|
||||
let r = TakeWeightCredit::should_execute(&Parent.into(), &mut message, 10, &mut weight_credit);
|
||||
assert_eq!(r, Ok(()));
|
||||
assert_eq!(weight_credit, 0);
|
||||
|
||||
let r = TakeWeightCredit::should_execute(
|
||||
&Parent.into(),
|
||||
true,
|
||||
&mut message,
|
||||
10,
|
||||
&mut weight_credit,
|
||||
);
|
||||
let r = TakeWeightCredit::should_execute(&Parent.into(), &mut message, 10, &mut weight_credit);
|
||||
assert_eq!(r, Err(()));
|
||||
assert_eq!(weight_credit, 0);
|
||||
}
|
||||
@@ -87,7 +75,6 @@ fn allow_unpaid_should_work() {
|
||||
|
||||
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
true,
|
||||
&mut message,
|
||||
10,
|
||||
&mut 0,
|
||||
@@ -96,7 +83,6 @@ fn allow_unpaid_should_work() {
|
||||
|
||||
let r = AllowUnpaidExecutionFrom::<IsInVec<AllowUnpaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
true,
|
||||
&mut message,
|
||||
10,
|
||||
&mut 0,
|
||||
@@ -113,7 +99,6 @@ fn allow_paid_should_work() {
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
true,
|
||||
&mut message,
|
||||
10,
|
||||
&mut 0,
|
||||
@@ -129,7 +114,6 @@ fn allow_paid_should_work() {
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
true,
|
||||
&mut underpaying_message,
|
||||
30,
|
||||
&mut 0,
|
||||
@@ -145,7 +129,6 @@ fn allow_paid_should_work() {
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parachain(1).into(),
|
||||
true,
|
||||
&mut paying_message,
|
||||
30,
|
||||
&mut 0,
|
||||
@@ -154,7 +137,6 @@ fn allow_paid_should_work() {
|
||||
|
||||
let r = AllowTopLevelPaidExecutionFrom::<IsInVec<AllowPaidFrom>>::should_execute(
|
||||
&Parent.into(),
|
||||
true,
|
||||
&mut paying_message,
|
||||
30,
|
||||
&mut 0,
|
||||
@@ -480,6 +462,120 @@ fn reserve_transfer_should_work() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_version_subscriptions_should_work() {
|
||||
AllowSubsFrom::set(vec![Parent.into()]);
|
||||
|
||||
let origin = Parachain(1000).into();
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
SetAppendix(Xcm(vec![])),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: 5000 },
|
||||
]);
|
||||
let weight_limit = 20;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let origin = Parachain(1000).into();
|
||||
let message =
|
||||
Xcm::<TestCall>(vec![SubscribeVersion { query_id: 42, max_response_weight: 5000 }]);
|
||||
let weight_limit = 10;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message.clone(), weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let origin = Parent.into();
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(10));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), Some((42, 5000)))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_subscription_instruction_should_work() {
|
||||
let origin = Parachain(1000).into();
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
DescendOrigin(X1(AccountIndex64 { index: 1, network: Any })),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: 5000 },
|
||||
]);
|
||||
let weight_limit = 20;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin.clone(),
|
||||
message.clone(),
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(20, XcmError::BadOrigin));
|
||||
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
SetAppendix(Xcm(vec![])),
|
||||
SubscribeVersion { query_id: 42, max_response_weight: 5000 },
|
||||
]);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin,
|
||||
message.clone(),
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(20));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), Some((42, 5000)))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_version_unsubscriptions_should_work() {
|
||||
AllowSubsFrom::set(vec![Parent.into()]);
|
||||
|
||||
let origin = Parachain(1000).into();
|
||||
let message = Xcm::<TestCall>(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]);
|
||||
let weight_limit = 20;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let origin = Parachain(1000).into();
|
||||
let message = Xcm::<TestCall>(vec![UnsubscribeVersion]);
|
||||
let weight_limit = 10;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message.clone(), weight_limit);
|
||||
assert_eq!(r, Outcome::Error(XcmError::Barrier));
|
||||
|
||||
let origin = Parent.into();
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm(origin, message, weight_limit);
|
||||
assert_eq!(r, Outcome::Complete(10));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parent.into(), None)]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_unsubscription_instruction_should_work() {
|
||||
let origin = Parachain(1000).into();
|
||||
|
||||
// Not allowed to do it when origin has been changed.
|
||||
let message = Xcm::<TestCall>(vec![
|
||||
DescendOrigin(X1(AccountIndex64 { index: 1, network: Any })),
|
||||
UnsubscribeVersion,
|
||||
]);
|
||||
let weight_limit = 20;
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin.clone(),
|
||||
message.clone(),
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Incomplete(20, XcmError::BadOrigin));
|
||||
|
||||
// Fine to do it when origin is untouched.
|
||||
let message = Xcm::<TestCall>(vec![SetAppendix(Xcm(vec![])), UnsubscribeVersion]);
|
||||
let r = XcmExecutor::<TestConfig>::execute_xcm_in_credit(
|
||||
origin,
|
||||
message.clone(),
|
||||
weight_limit,
|
||||
weight_limit,
|
||||
);
|
||||
assert_eq!(r, Outcome::Complete(20));
|
||||
|
||||
assert_eq!(SubscriptionRequests::get(), vec![(Parachain(1000).into(), None)]);
|
||||
assert_eq!(sent_xcm(), vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transacting_should_work() {
|
||||
AllowUnpaidFrom::set(vec![Parent.into()]);
|
||||
|
||||
@@ -169,6 +169,7 @@ impl xcm_executor::Config for XcmConfig {
|
||||
type ResponseHandler = XcmPallet;
|
||||
type AssetTrap = XcmPallet;
|
||||
type AssetClaims = XcmPallet;
|
||||
type SubscriptionService = XcmPallet;
|
||||
}
|
||||
|
||||
pub type LocalOriginToLocation = SignedToAccountId32<Origin, AccountId, KusamaNetwork>;
|
||||
@@ -187,6 +188,8 @@ impl pallet_xcm::Config for Runtime {
|
||||
type Weigher = FixedWeightBounds<BaseXcmWeight, Call, MaxInstructions>;
|
||||
type Call = Call;
|
||||
type Origin = Origin;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
|
||||
}
|
||||
|
||||
impl origin::Config for Runtime {}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
use crate::traits::{
|
||||
ClaimAssets, ConvertOrigin, DropAssets, FilterAssetLocation, InvertLocation, OnResponse,
|
||||
ShouldExecute, TransactAsset, WeightBounds, WeightTrader,
|
||||
ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader,
|
||||
};
|
||||
use frame_support::{
|
||||
dispatch::{Dispatchable, Parameter},
|
||||
@@ -65,4 +65,7 @@ pub trait Config {
|
||||
|
||||
/// The handler for when there is an instruction to claim assets.
|
||||
type AssetClaims: ClaimAssets;
|
||||
|
||||
/// How we handle version subscription requests.
|
||||
type SubscriptionService: VersionChangeNotifier;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ use xcm::latest::{
|
||||
pub mod traits;
|
||||
use traits::{
|
||||
ClaimAssets, ConvertOrigin, DropAssets, FilterAssetLocation, InvertLocation, OnResponse,
|
||||
ShouldExecute, TransactAsset, WeightBounds, WeightTrader,
|
||||
ShouldExecute, TransactAsset, VersionChangeNotifier, WeightBounds, WeightTrader,
|
||||
};
|
||||
|
||||
mod assets;
|
||||
@@ -44,8 +44,9 @@ pub use config::Config;
|
||||
pub struct XcmExecutor<Config: config::Config> {
|
||||
holding: Assets,
|
||||
origin: Option<MultiLocation>,
|
||||
original_origin: MultiLocation,
|
||||
trader: Config::Trader,
|
||||
/// The most recent error result and instruction index into the fragment in which it occured,
|
||||
/// The most recent error result and instruction index into the fragment in which it occurred,
|
||||
/// if any.
|
||||
error: Option<(u32, XcmError)>,
|
||||
/// The surplus weight, defined as the amount by which `max_weight` is
|
||||
@@ -87,17 +88,13 @@ impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
|
||||
return Outcome::Error(XcmError::WeightLimitReached(xcm_weight))
|
||||
}
|
||||
|
||||
if let Err(_) = Config::Barrier::should_execute(
|
||||
&origin,
|
||||
true,
|
||||
&mut message,
|
||||
xcm_weight,
|
||||
&mut weight_credit,
|
||||
) {
|
||||
if let Err(_) =
|
||||
Config::Barrier::should_execute(&origin, &mut message, xcm_weight, &mut weight_credit)
|
||||
{
|
||||
return Outcome::Error(XcmError::Barrier)
|
||||
}
|
||||
|
||||
let mut vm = Self::new(origin.clone());
|
||||
let mut vm = Self::new(origin);
|
||||
|
||||
while !message.0.is_empty() {
|
||||
let result = vm.execute(message);
|
||||
@@ -118,7 +115,8 @@ impl<Config: config::Config> ExecuteXcm<Config::Call> for XcmExecutor<Config> {
|
||||
let mut weight_used = xcm_weight.saturating_sub(vm.total_surplus);
|
||||
|
||||
if !vm.holding.is_empty() {
|
||||
weight_used.saturating_accrue(Config::AssetTrap::drop_assets(&origin, vm.holding));
|
||||
let trap_weight = Config::AssetTrap::drop_assets(&vm.original_origin, vm.holding);
|
||||
weight_used.saturating_accrue(trap_weight);
|
||||
};
|
||||
|
||||
match vm.error {
|
||||
@@ -134,7 +132,8 @@ impl<Config: config::Config> XcmExecutor<Config> {
|
||||
fn new(origin: MultiLocation) -> Self {
|
||||
Self {
|
||||
holding: Assets::new(),
|
||||
origin: Some(origin),
|
||||
origin: Some(origin.clone()),
|
||||
original_origin: origin,
|
||||
trader: Config::Trader::new(),
|
||||
error: None,
|
||||
total_surplus: 0,
|
||||
@@ -419,6 +418,18 @@ impl<Config: config::Config> XcmExecutor<Config> {
|
||||
Ok(())
|
||||
},
|
||||
Trap(code) => Err(XcmError::Trap(code)),
|
||||
SubscribeVersion { query_id, max_response_weight } => {
|
||||
let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?.clone();
|
||||
// We don't allow derivative origins to subscribe since it would otherwise pose a
|
||||
// DoS risk.
|
||||
ensure!(self.original_origin == origin, XcmError::BadOrigin);
|
||||
Config::SubscriptionService::start(&origin, query_id, max_response_weight)
|
||||
},
|
||||
UnsubscribeVersion => {
|
||||
let origin = self.origin.as_ref().ok_or(XcmError::BadOrigin)?;
|
||||
ensure!(&self.original_origin == origin, XcmError::BadOrigin);
|
||||
Config::SubscriptionService::stop(origin)
|
||||
},
|
||||
ExchangeAsset { .. } => Err(XcmError::Unimplemented),
|
||||
HrmpNewChannelOpenRequest { .. } => Err(XcmError::Unimplemented),
|
||||
HrmpChannelAccepted { .. } => Err(XcmError::Unimplemented),
|
||||
|
||||
@@ -27,7 +27,7 @@ pub use matches_fungible::MatchesFungible;
|
||||
mod matches_fungibles;
|
||||
pub use matches_fungibles::{Error, MatchesFungibles};
|
||||
mod on_response;
|
||||
pub use on_response::OnResponse;
|
||||
pub use on_response::{OnResponse, VersionChangeNotifier};
|
||||
mod should_execute;
|
||||
pub use should_execute::ShouldExecute;
|
||||
mod transact_asset;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use frame_support::weights::Weight;
|
||||
use xcm::latest::{MultiLocation, Response};
|
||||
use xcm::latest::{Error as XcmError, MultiLocation, QueryId, Response, Result as XcmResult};
|
||||
|
||||
/// Define what needs to be done upon receiving a query response.
|
||||
pub trait OnResponse {
|
||||
@@ -42,3 +42,29 @@ impl OnResponse for () {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for a type which handles notifying a destination of XCM version changes.
|
||||
pub trait VersionChangeNotifier {
|
||||
/// Start notifying `location` should the XCM version of this chain change.
|
||||
///
|
||||
/// When it does, this type should ensure a `QueryResponse` message is sent with the given
|
||||
/// `query_id` & `max_weight` and with a `response` of `Repsonse::Version`. This should happen
|
||||
/// until/unless `stop` is called with the correct `query_id`.
|
||||
///
|
||||
/// If the `location` has an ongoing notification and when this function is called, then an
|
||||
/// error should be returned.
|
||||
fn start(location: &MultiLocation, query_id: QueryId, max_weight: u64) -> XcmResult;
|
||||
|
||||
/// Stop notifying `location` should the XCM change. Returns an error if there is no existing
|
||||
/// notification set up.
|
||||
fn stop(location: &MultiLocation) -> XcmResult;
|
||||
}
|
||||
|
||||
impl VersionChangeNotifier for () {
|
||||
fn start(_: &MultiLocation, _: QueryId, _: u64) -> XcmResult {
|
||||
Err(XcmError::Unimplemented)
|
||||
}
|
||||
fn stop(_: &MultiLocation) -> XcmResult {
|
||||
Err(XcmError::Unimplemented)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ pub trait ShouldExecute {
|
||||
/// Returns `true` if the given `message` may be executed.
|
||||
///
|
||||
/// - `origin`: The origin (sender) of the message.
|
||||
/// - `top_level`: `true` indicates the initial XCM coming from the `origin`, `false` indicates
|
||||
/// an embedded XCM executed internally as part of another message or an `Order`.
|
||||
/// - `message`: The message itself.
|
||||
/// - `max_weight`: The (possibly over-) estimation of the weight of execution of the message.
|
||||
/// - `weight_credit`: The pre-established amount of weight that the system has determined this
|
||||
@@ -35,7 +33,6 @@ pub trait ShouldExecute {
|
||||
/// payment, but could in principle be due to other factors.
|
||||
fn should_execute<Call>(
|
||||
origin: &MultiLocation,
|
||||
top_level: bool,
|
||||
message: &mut Xcm<Call>,
|
||||
max_weight: Weight,
|
||||
weight_credit: &mut Weight,
|
||||
@@ -46,22 +43,20 @@ pub trait ShouldExecute {
|
||||
impl ShouldExecute for Tuple {
|
||||
fn should_execute<Call>(
|
||||
origin: &MultiLocation,
|
||||
top_level: bool,
|
||||
message: &mut Xcm<Call>,
|
||||
max_weight: Weight,
|
||||
weight_credit: &mut Weight,
|
||||
) -> Result<(), ()> {
|
||||
for_tuples!( #(
|
||||
match Tuple::should_execute(origin, top_level, message, max_weight, weight_credit) {
|
||||
match Tuple::should_execute(origin, message, max_weight, weight_credit) {
|
||||
Ok(()) => return Ok(()),
|
||||
_ => (),
|
||||
}
|
||||
)* );
|
||||
log::trace!(
|
||||
target: "xcm::should_execute",
|
||||
"did not pass barrier: origin: {:?}, top_level: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
"did not pass barrier: origin: {:?}, message: {:?}, max_weight: {:?}, weight_credit: {:?}",
|
||||
origin,
|
||||
top_level,
|
||||
message,
|
||||
max_weight,
|
||||
weight_credit,
|
||||
|
||||
@@ -145,6 +145,7 @@ impl Config for XcmConfig {
|
||||
type ResponseHandler = ();
|
||||
type AssetTrap = ();
|
||||
type AssetClaims = ();
|
||||
type SubscriptionService = ();
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
@@ -305,6 +306,8 @@ impl pallet_xcm::Config for Runtime {
|
||||
type LocationInverter = LocationInverter<Ancestry>;
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
|
||||
}
|
||||
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
|
||||
|
||||
@@ -135,6 +135,7 @@ impl Config for XcmConfig {
|
||||
type ResponseHandler = ();
|
||||
type AssetTrap = ();
|
||||
type AssetClaims = ();
|
||||
type SubscriptionService = ();
|
||||
}
|
||||
|
||||
pub type LocalOriginToLocation = SignedToAccountId32<Origin, AccountId, KusamaNetwork>;
|
||||
@@ -153,6 +154,8 @@ impl pallet_xcm::Config for Runtime {
|
||||
type LocationInverter = LocationInverter<Ancestry>;
|
||||
type Origin = Origin;
|
||||
type Call = Call;
|
||||
const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100;
|
||||
type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
Reference in New Issue
Block a user