Newer
Older
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! Pallet to handle XCM messages.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use frame_support::traits::{
Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency, OriginTrait,
};
use scale_info::TypeInfo;
AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Dispatchable, Hash,
Saturating, Zero,
use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec};
use xcm_executor::traits::{ConvertOrigin, Properties};
dispatch::GetDispatchInfo, pallet_prelude::*, traits::WithdrawReasons, PalletId,
CheckSuspension, ClaimAssets, ConvertLocation, DropAssets, MatchesFungible, OnResponse,
QueryHandler, QueryResponseStatus, VersionChangeNotifier, WeightBounds,
},
Assets,
};
pub trait WeightInfo {
fn send() -> Weight;
fn teleport_assets() -> Weight;
fn reserve_transfer_assets() -> Weight;
fn execute() -> Weight;
fn force_xcm_version() -> Weight;
fn force_default_xcm_version() -> Weight;
fn force_subscribe_version_notify() -> Weight;
fn force_unsubscribe_version_notify() -> Weight;
fn migrate_supported_version() -> Weight;
fn migrate_version_notifiers() -> Weight;
fn already_notified_target() -> Weight;
fn notify_current_targets() -> Weight;
fn notify_target_migration_fail() -> Weight;
fn migrate_version_notify_targets() -> Weight;
fn migrate_and_notify_old_targets() -> Weight;
}
/// fallback implementation
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
fn send() -> Weight {
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
fn force_suspension() -> Weight {
Weight::from_parts(100_000_000, 0)
}
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
Weight::from_parts(100_000_000, 0)
#[frame_support::pallet]
pub mod pallet {
use super::*;
Gavin Wood
committed
use frame_support::{
dispatch::{GetDispatchInfo, PostDispatchInfo},
Gavin Wood
committed
};
use sp_core::H256;
use sp_runtime::traits::Dispatchable;
parameter_types! {
/// An implementation of `Get<u32>` which just returns the latest XCM version which we can
/// support.
pub const CurrentXcmVersion: u32 = XCM_VERSION;
}
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
#[pallet::config]
/// The module configuration trait.
pub trait Config: frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// A lockable currency.
// TODO: We should really use a trait which can handle multiple currencies.
type Currency: LockableCurrency<Self::AccountId, Moment = BlockNumberFor<Self>>;
/// The `MultiAsset` matcher for `Currency`.
type CurrencyMatcher: MatchesFungible<BalanceOf<Self>>;
/// Required origin for sending XCM messages. If successful, it resolves to `MultiLocation`
/// which exists as an interior location within this chain's XCM context.
type SendXcmOrigin: EnsureOrigin<
<Self as SysConfig>::RuntimeOrigin,
Success = MultiLocation,
>;
/// The type used to actually dispatch an XCM to its destination.
type XcmRouter: SendXcm;
/// Required origin for executing XCM messages, including the teleport functionality. If
/// successful, then it resolves to `MultiLocation` which exists as an interior location
/// within this chain's XCM context.
type ExecuteXcmOrigin: EnsureOrigin<
<Self as SysConfig>::RuntimeOrigin,
Success = MultiLocation,
>;
/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
type XcmExecuteFilter: Contains<(MultiLocation, Xcm<<Self as Config>::RuntimeCall>)>;
type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall>;
/// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass.
type XcmTeleportFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;
/// Our XCM filter which messages to be reserve-transferred using the dedicated extrinsic
/// must pass.
type XcmReserveTransferFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;
/// Means of measuring the weight consumed by an XCM message locally.
type Weigher: WeightBounds<<Self as Config>::RuntimeCall>;
/// This chain's Universal Location.
type UniversalLocation: Get<InteriorMultiLocation>;
Gavin Wood
committed
type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
Gavin Wood
committed
Gavin Wood
committed
+ GetDispatchInfo
+ Dispatchable<
RuntimeOrigin = <Self as Config>::RuntimeOrigin,
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 origin that is allowed to call privileged operations on the XCM pallet
type AdminOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin>;
/// The assets which we consider a given origin is trusted if they claim to have placed a
/// lock.
type TrustedLockers: ContainsPair<MultiLocation, MultiAsset>;
/// How to get an `AccountId` value from a `MultiLocation`, useful for handling asset locks.
type SovereignAccountOf: ConvertLocation<Self::AccountId>;
/// The maximum number of local XCM locks that a single account may have.
type MaxLockers: Get<u32>;
/// The maximum number of consumers a single remote lock may have.
type MaxRemoteLockConsumers: Get<u32>;
/// The ID type for local consumers of remote locks.
type RemoteLockConsumerIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// A `MultiLocation` that can be reached via `XcmRouter`. Used only in benchmarks.
///
/// If `None`, the benchmarks that depend on a reachable destination will be skipped.
#[cfg(feature = "runtime-benchmarks")]
type ReachableDest: Get<Option<MultiLocation>>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Gavin Wood
committed
/// Execution of an XCM message was attempted.
Attempted { outcome: xcm::latest::Outcome },
Gavin Wood
committed
/// A XCM message was sent.
Sent {
origin: MultiLocation,
destination: MultiLocation,
message: Xcm<()>,
message_id: XcmHash,
},
Gavin Wood
committed
/// Query response received which does not match a registered query. This may be because a
/// matching query was never registered, it may be because it is a duplicate response, or
/// because the query timed out.
UnexpectedResponse { origin: MultiLocation, query_id: QueryId },
Gavin Wood
committed
/// Query response has been received and is ready for taking with `take_response`. There is
/// no registered notification call.
ResponseReady { query_id: QueryId, response: Response },
Gavin Wood
committed
/// Query response has been received and query is removed. The registered notification has
/// been dispatched and executed successfully.
Notified { query_id: QueryId, pallet_index: u8, call_index: u8 },
/// Query response has been received and query is removed. The registered notification
/// could not be dispatched because the dispatch weight is greater than the maximum weight
Gavin Wood
committed
/// originally budgeted by this runtime for the query result.
NotifyOverweight {
query_id: QueryId,
pallet_index: u8,
call_index: u8,
actual_weight: Weight,
max_budgeted_weight: Weight,
},
Gavin Wood
committed
/// Query response has been received and query is removed. There was a general error with
/// dispatching the notification call.
NotifyDispatchError { query_id: QueryId, pallet_index: u8, call_index: u8 },
Gavin Wood
committed
/// Query response has been received and query is removed. The dispatch was unable to be
/// decoded into a `Call`; this might be due to dispatch function having a signature which
/// is not `(origin, QueryId, Response)`.
NotifyDecodeFailed { query_id: QueryId, pallet_index: u8, call_index: u8 },
Gavin Wood
committed
/// Expected query response has been received but the origin location of the response does
/// not match that expected. The query remains registered for a later, valid, response to
/// be received and acted upon.
InvalidResponder {
origin: MultiLocation,
query_id: QueryId,
expected_location: Option<MultiLocation>,
},
Gavin Wood
committed
/// Expected query response has been received but the expected origin location placed in
/// storage by this runtime previously cannot be decoded. The query remains registered.
Gavin Wood
committed
///
/// This is unexpected (since a location placed in storage in a previously executing
/// runtime should be readable prior to query timeout) and dangerous since the possibly
/// valid response will be dropped. Manual governance intervention is probably going to be
/// needed.
InvalidResponderVersion { origin: MultiLocation, query_id: QueryId },
Gavin Wood
committed
/// Received query response has been read and removed.
ResponseTaken { query_id: QueryId },
/// Some assets have been placed in an asset trap.
AssetsTrapped { hash: H256, origin: MultiLocation, assets: VersionedMultiAssets },
/// An XCM version change notification message has been attempted to be sent.
///
/// The cost of sending it (borne by the chain) is included.
VersionChangeNotified {
destination: MultiLocation,
result: XcmVersion,
cost: MultiAssets,
message_id: XcmHash,
},
/// The supported version of a location has been changed. This might be through an
/// automatic notification or a manual intervention.
SupportedVersionChanged { location: MultiLocation, version: XcmVersion },
/// A given location which had a version change subscription was dropped owing to an error
/// sending the notification to it.
NotifyTargetSendFail { location: MultiLocation, query_id: QueryId, error: XcmError },
/// A given location which had a version change subscription was dropped owing to an error
/// migrating the location to our new XCM format.
NotifyTargetMigrationFail { location: VersionedMultiLocation, query_id: QueryId },
/// Expected query response has been received but the expected querier location placed in
/// storage by this runtime previously cannot be decoded. The query remains registered.
///
/// This is unexpected (since a location placed in storage in a previously executing
/// runtime should be readable prior to query timeout) and dangerous since the possibly
/// valid response will be dropped. Manual governance intervention is probably going to be
/// needed.
InvalidQuerierVersion { origin: MultiLocation, query_id: QueryId },
/// Expected query response has been received but the querier location of the response does
/// not match the expected. The query remains registered for a later, valid, response to
/// be received and acted upon.
InvalidQuerier {
origin: MultiLocation,
query_id: QueryId,
expected_querier: MultiLocation,
maybe_actual_querier: Option<MultiLocation>,
},
/// A remote has requested XCM version change notification from us and we have honored it.
/// A version information message is sent to them and its cost is included.
VersionNotifyStarted { destination: MultiLocation, cost: MultiAssets, message_id: XcmHash },
/// We have requested that a remote chain send us XCM version change notifications.
VersionNotifyRequested {
destination: MultiLocation,
cost: MultiAssets,
message_id: XcmHash,
},
/// We have requested that a remote chain stops sending us XCM version change
/// notifications.
VersionNotifyUnrequested {
destination: MultiLocation,
cost: MultiAssets,
message_id: XcmHash,
},
/// Fees were paid from a location for an operation (often for using `SendXcm`).
FeesPaid { paying: MultiLocation, fees: MultiAssets },
/// Some assets have been claimed from an asset trap
AssetsClaimed { hash: H256, origin: MultiLocation, assets: VersionedMultiAssets },
Gavin Wood
committed
}
#[pallet::origin]
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
Gavin Wood
committed
pub enum Origin {
/// It comes from somewhere in the XCM space wanting to transact.
Xcm(MultiLocation),
/// It comes as an expected response from an XCM location.
Response(MultiLocation),
}
impl From<MultiLocation> for Origin {
fn from(location: MultiLocation) -> Origin {
Origin::Xcm(location)
}
}
#[pallet::error]
pub enum Error<T> {
Gavin Wood
committed
/// The desired destination was unreachable, generally because there is a no way of routing
/// to it.
/// There was some other issue (i.e. not to do with routing) in sending the message.
/// Perhaps a lack of space for buffering the message.
/// The message execution fails the filter.
Filtered,
/// The message's weight could not be determined.
UnweighableMessage,
/// The destination `MultiLocation` provided cannot be inverted.
DestinationNotInvertible,
/// The assets to be sent are empty.
Empty,
/// Could not re-anchor the assets to declare the fees for the destination chain.
/// Too many assets have been attempted for transfer.
TooManyAssets,
/// Origin is invalid for sending.
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,
/// Invalid asset for the operation.
InvalidAsset,
/// The owner does not own (all) of the asset that they wish to do the operation on.
LowBalance,
/// The asset owner has too many locks on the asset.
TooManyLocks,
/// The given account is not an identifiable sovereign account for any location.
AccountNotSovereign,
/// The operation required fees to be paid which the initiator could not meet.
FeesNotMet,
/// A remote lock with the corresponding data could not be found.
LockNotFound,
/// The unlock operation cannot succeed because there are still consumers of the lock.
InUse,
}
impl<T: Config> From<SendError> for Error<T> {
fn from(e: SendError) -> Self {
match e {
SendError::Fees => Error::<T>::FeesNotMet,
SendError::NotApplicable => Error::<T>::Unreachable,
_ => Error::<T>::SendFailure,
}
}
Gavin Wood
committed
/// The status of a query.
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
Gavin Wood
committed
pub enum QueryStatus<BlockNumber> {
/// The query was sent but no response has yet been received.
Pending {
/// The `QueryResponse` XCM must have this origin to be considered a reply for this
/// query.
Gavin Wood
committed
responder: VersionedMultiLocation,
/// The `QueryResponse` XCM must have this value as the `querier` field to be
/// considered a reply for this query. If `None` then the querier is ignored.
maybe_match_querier: Option<VersionedMultiLocation>,
Gavin Wood
committed
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumber,
},
/// The query is for an ongoing version notification subscription.
VersionNotifier { origin: VersionedMultiLocation, is_active: bool },
Gavin Wood
committed
/// A response has been received.
Ready { response: VersionedResponse, at: BlockNumber },
}
#[derive(Copy, Clone)]
pub(crate) struct LatestVersionedMultiLocation<'a>(pub(crate) &'a MultiLocation);
impl<'a> EncodeLike<VersionedMultiLocation> for LatestVersionedMultiLocation<'a> {}
impl<'a> Encode for LatestVersionedMultiLocation<'a> {
fn encode(&self) -> Vec<u8> {
let mut r = VersionedMultiLocation::from(MultiLocation::default()).encode();
r.truncate(1);
self.0.using_encoded(|d| r.extend_from_slice(d));
r
}
}
#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo)]
pub enum VersionMigrationStage {
MigrateSupportedVersion,
MigrateVersionNotifiers,
NotifyCurrentTargets(Option<Vec<u8>>),
MigrateAndNotifyOldTargets,
}
impl Default for VersionMigrationStage {
fn default() -> Self {
Self::MigrateSupportedVersion
}
}
Gavin Wood
committed
/// The latest available query index.
#[pallet::storage]
pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;
Gavin Wood
committed
/// The ongoing queries.
#[pallet::storage]
#[pallet::getter(fn query)]
pub(super) type Queries<T: Config> =
StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<BlockNumberFor<T>>, OptionQuery>;
Gavin Wood
committed
/// The existing asset traps.
///
/// Key is the blake2 256 hash of (origin, versioned `MultiAssets`) pair. Value is the number of
/// times this pair has been trapped (usually just 1 if it exists at all).
#[pallet::storage]
#[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]
#[pallet::whitelist_storage]
pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
/// The Latest versions that we know various locations support.
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
#[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,
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]
#[pallet::whitelist_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>;
#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(MaxConsumers))]
pub struct RemoteLockedFungibleRecord<ConsumerIdentifier, MaxConsumers: Get<u32>> {
/// Total amount of the asset held by the remote lock.
/// The location which holds the original lock.
/// Local consumers of the remote lock with a consumer identifier and the amount
/// of fungible asset every consumer holds.
/// Every consumer can hold up to total amount of the remote lock.
pub consumers: BoundedVec<(ConsumerIdentifier, u128), MaxConsumers>,
}
impl<LockId, MaxConsumers: Get<u32>> RemoteLockedFungibleRecord<LockId, MaxConsumers> {
/// Amount of the remote lock in use by consumers.
/// Returns `None` if the remote lock has no consumers.
pub fn amount_held(&self) -> Option<u128> {
self.consumers.iter().max_by(|x, y| x.1.cmp(&y.1)).map(|max| max.1)
}
}
/// Fungible assets which we know are locked on a remote chain.
#[pallet::storage]
pub(super) type RemoteLockedFungibles<T: Config> = StorageNMap<
_,
(
NMapKey<Twox64Concat, XcmVersion>,
NMapKey<Blake2_128Concat, T::AccountId>,
NMapKey<Blake2_128Concat, VersionedAssetId>,
),
RemoteLockedFungibleRecord<T::RemoteLockConsumerIdentifier, T::MaxRemoteLockConsumers>,
OptionQuery,
>;
/// Fungible assets which we know are locked on this chain.
#[pallet::storage]
pub(super) type LockedFungibles<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
BoundedVec<(BalanceOf<T>, VersionedMultiLocation), T::MaxLockers>,
OptionQuery,
>;
/// Global suspension state of the XCM executor.
#[pallet::storage]
pub(super) type XcmExecutionSuspended<T: Config> = StorageValue<_, bool, ValueQuery>;
pub struct GenesisConfig<T: Config> {
#[serde(skip)]
pub _config: sp_std::marker::PhantomData<T>,
/// The default version to encode outgoing XCM messages with.
pub safe_xcm_version: Option<XcmVersion>,
}
impl<T: Config> Default for GenesisConfig<T> {
Self { safe_xcm_version: Some(XCM_VERSION), _config: Default::default() }
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
SafeXcmVersion::<T>::set(self.safe_xcm_version);
}
}
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
let mut weight_used = Weight::zero();
if let Some(migration) = CurrentMigration::<T>::get() {
// 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.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
q.sort_by_key(|i| i.1);
while let Some((versioned_dest, _)) = q.pop() {
Keith Yeung
committed
if let Ok(dest) = MultiLocation::try_from(versioned_dest) {
if Self::request_version_notify(dest).is_ok() {
// TODO: correct weights.
weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
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
}
}
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
pub mod migrations {
use super::*;
use frame_support::traits::{PalletInfoAccess, StorageVersion};
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
enum QueryStatusV0<BlockNumber> {
Pending {
responder: VersionedMultiLocation,
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumber,
},
VersionNotifier {
origin: VersionedMultiLocation,
is_active: bool,
},
Ready {
response: VersionedResponse,
at: BlockNumber,
},
}
impl<B> From<QueryStatusV0<B>> for QueryStatus<B> {
fn from(old: QueryStatusV0<B>) -> Self {
use QueryStatusV0::*;
match old {
Pending { responder, maybe_notify, timeout } => QueryStatus::Pending {
responder,
maybe_notify,
timeout,
maybe_match_querier: Some(MultiLocation::here().into()),
},
VersionNotifier { origin, is_active } =>
QueryStatus::VersionNotifier { origin, is_active },
Ready { response, at } => QueryStatus::Ready { response, at },
}
}
}
pub fn migrate_to_v1<T: Config, P: GetStorageVersion + PalletInfoAccess>(
) -> frame_support::weights::Weight {
let on_chain_storage_version = <P as GetStorageVersion>::on_chain_storage_version();
log::info!(
target: "runtime::xcm",
"Running migration storage v1 for xcm with storage version {:?}",
on_chain_storage_version,
);
if on_chain_storage_version < 1 {
let mut count = 0;
Queries::<T>::translate::<QueryStatusV0<BlockNumberFor<T>>, _>(|_key, value| {
count += 1;
Some(value.into())
});
StorageVersion::new(1).put::<P>();
log::info!(
target: "runtime::xcm",
"Running migration storage v1 for xcm with storage version {:?} was complete",
on_chain_storage_version,
);
// calculate and return migration weights
T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)
} else {
log::warn!(
target: "runtime::xcm",
"Attempted to apply migration to v1 but failed because storage version is {:?}",
on_chain_storage_version,
);
T::DbWeight::get().reads(1)
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::WeightInfo::send())]
thiolliere
committed
pub fn send(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
message: Box<VersionedXcm<()>>,
thiolliere
committed
) -> DispatchResult {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
Keith Yeung
committed
let interior: Junctions =
origin_location.try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = MultiLocation::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let message_id =
Self::send_xcm(interior, dest, message.clone()).map_err(Error::<T>::from)?;
let e = Event::Sent { origin: origin_location, destination: dest, message, message_id };
Self::deposit_event(e);
/// Teleport some assets from the local chain to some destination chain.
///
/// **This function is deprecated: Use `limited_teleport_assets` instead.**
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited,
/// with all fees taken as needed from the asset.
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent,
/// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send
/// from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
/// generally be an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. The first item should be the currency used to to
/// pay the fee on the `dest` side. May not be empty.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
Gavin Wood
committed
use sp_std::vec;
Gavin Wood
committed
let mut message = Xcm(vec![
WithdrawAsset(assets),
SetFeesMode { jit_withdraw: true },
InitiateTeleport {
assets: Wild(AllCounted(count)),
dest,
xcm: Xcm(vec![]),
},
Gavin Wood
committed
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w))
}
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, Unlimited)
/// Transfer some assets from the local chain to the sovereign account of a destination
/// chain and forward a notification XCM.
/// **This function is deprecated: Use `limited_reserve_transfer_assets` instead.**
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited,
/// with all fees taken as needed from the asset.
/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
/// - `dest`: Destination context for the assets. Will typically be `X2(Parent,
/// Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send
/// from relay to parachain.
/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
/// generally be an `AccountId32` value.
/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
/// fee on the `dest` side.
Gavin Wood
committed
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
Gavin Wood
committed
use sp_std::vec;
let mut message = Xcm(vec![
SetFeesMode { jit_withdraw: true },
Gavin Wood
committed
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::reserve_transfer_assets().saturating_add(w))
}
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
/// Execute an XCM message from a local, signed, origin.
///
/// An event is deposited indicating whether `msg` could be executed completely or only
/// partially.
///
/// No more than `max_weight` will be used in its attempted execution. If this is less than
/// the maximum amount of weight that the message could take to be executed, then no
/// execution attempt will be made.
/// NOTE: A successful return to this does *not* imply that the `msg` was executed
/// successfully to completion; only that *some* of it was executed.
#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let hash = message.using_encoded(sp_io::hashing::blake2_256);
let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let value = (origin_location, message);
ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, message) = value;
let outcome = T::XcmExecutor::execute_xcm_in_credit(
origin_location,
message,
let result =
Ok(Some(outcome.weight_used().saturating_add(T::WeightInfo::execute())).into());
Self::deposit_event(Event::Attempted { outcome });
/// Extoll that a particular destination can be communicated with through a particular
/// version of XCM.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `location`: The destination that is being described.
/// - `xcm_version`: The latest version of XCM that `location` supports.
pub fn force_xcm_version(
origin: OriginFor<T>,
location: Box<MultiLocation>,
let location = *location;
SupportedVersion::<T>::insert(
XCM_VERSION,
LatestVersionedMultiLocation(&location),
Self::deposit_event(Event::SupportedVersionChanged { location, 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 an origin specified by AdminOrigin.
/// - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable.
#[pallet::weight(T::WeightInfo::force_default_xcm_version())]
pub fn force_default_xcm_version(
origin: OriginFor<T>,
maybe_xcm_version: Option<XcmVersion>,
) -> DispatchResult {
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 an origin specified by AdminOrigin.
/// - `location`: The location to which we should subscribe for XCM version notifications.
#[pallet::weight(T::WeightInfo::force_subscribe_version_notify())]
pub fn force_subscribe_version_notify(
origin: OriginFor<T>,
location: Box<VersionedMultiLocation>,
) -> DispatchResult {
Keith Yeung
committed
let location: MultiLocation =
(*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
Self::request_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
_ => Error::<T>::InvalidOrigin,
}
.into()
})
}
/// Require that a particular destination should no longer notify us regarding any XCM
/// version changes.
///
/// - `origin`: Must be an origin specified by AdminOrigin.
/// - `location`: The location to which we are currently subscribed for XCM version
/// notifications which we no longer desire.
#[pallet::weight(T::WeightInfo::force_unsubscribe_version_notify())]
pub fn force_unsubscribe_version_notify(
origin: OriginFor<T>,
location: Box<VersionedMultiLocation>,
) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;