Newer
Older
// Copyright 2020-2021 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, EnsureOrigin, Get, OriginTrait};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{BadOrigin, Saturating},
RuntimeDebug,
};
use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec};
use xcm::{latest::Weight as XcmWeight, prelude::*};
use xcm_executor::traits::ConvertOrigin;
use frame_support::PalletId;
#[frame_support::pallet]
pub mod pallet {
use super::*;
Gavin Wood
committed
use frame_support::{
dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo},
pallet_prelude::*,
Gavin Wood
committed
};
use frame_system::{pallet_prelude::*, Config as SysConfig};
use sp_core::H256;
use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, BlockNumberProvider, Hash};
use xcm_executor::{
traits::{
ClaimAssets, DropAssets, InvertLocation, OnResponse, VersionChangeNotifier,
WeightBounds,
},
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)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[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>;
/// 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
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 SysConfig>::RuntimeCall>)>;
type XcmExecutor: ExecuteXcm<<Self as SysConfig>::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 SysConfig>::RuntimeCall>;
/// Means of inverting a location.
type LocationInverter: InvertLocation;
Gavin Wood
committed
/// The outer `Origin` type.
type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
Gavin Wood
committed
/// The outer `Call` type.
Gavin Wood
committed
+ GetDispatchInfo
+ IsType<<Self as frame_system::Config>::RuntimeCall>
+ 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 maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
const MAX_ASSETS_FOR_TRANSFER: usize = 2;
#[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.
///
/// \[ outcome \]
Gavin Wood
committed
/// A XCM message was sent.
///
/// \[ origin, destination, message \]
Sent(MultiLocation, MultiLocation, Xcm<()>),
Gavin Wood
committed
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/// 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.
///
/// \[ origin location, id \]
UnexpectedResponse(MultiLocation, QueryId),
/// Query response has been received and is ready for taking with `take_response`. There is
/// no registered notification call.
///
/// \[ id, response \]
ResponseReady(QueryId, Response),
/// Query response has been received and query is removed. The registered notification has
/// been dispatched and executed successfully.
///
/// \[ id, pallet index, call index \]
Notified(QueryId, u8, 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
/// originally budgeted by this runtime for the query result.
///
/// \[ id, pallet index, call index, actual weight, max budgeted weight \]
NotifyOverweight(QueryId, u8, u8, Weight, Weight),
/// Query response has been received and query is removed. There was a general error with
/// dispatching the notification call.
///
/// \[ id, pallet index, call index \]
NotifyDispatchError(QueryId, u8, u8),
/// 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)`.
///
/// \[ id, pallet index, call index \]
NotifyDecodeFailed(QueryId, u8, u8),
/// 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.
///
/// \[ origin location, id, expected location \]
InvalidResponder(MultiLocation, QueryId, 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.
///
/// \[ origin location, id \]
InvalidResponderVersion(MultiLocation, QueryId),
/// Received query response has been read and removed.
///
/// \[ id \]
ResponseTaken(QueryId),
/// Some assets have been placed in an asset trap.
///
/// \[ 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),
/// Some assets have been claimed from an asset trap
///
/// \[ hash, origin, assets \]
AssetsClaimed(H256, MultiLocation, 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.
Gavin Wood
committed
/// 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,
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 {
responder: VersionedMultiLocation,
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<T::BlockNumber>, OptionQuery>;
/// 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]
pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;
/// The Latest versions that we know various locations support.
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
#[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);
}
}
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
}
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());
#[pallet::call]
impl<T: Config> Pallet<T> {
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.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = MultiLocation::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e {
Gavin Wood
committed
SendError::CannotReachDestination(..) => Error::<T>::Unreachable,
Self::deposit_event(Event::Sent(origin_location, dest, message));
/// Teleport some assets from the local chain to some destination chain.
///
/// 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;
let mut message = Xcm(vec![
WithdrawAsset(assets),
InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) },
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w)))
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, None)
/// Transfer some assets from the local chain to the sovereign account of a destination
/// chain and forward a notification XCM.
/// 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.
match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) {
(Ok(assets), Ok(dest)) => {
Gavin Wood
committed
use sp_std::vec;
let mut message = Xcm(vec![
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w)))
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
None,
)
/// 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(Weight::from_ref_time(max_weight.saturating_add(100_000_000u64)))]
message: Box<VersionedXcm<<T as SysConfig>::RuntimeCall>>,
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let value = (origin_location, message);
ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, message) = value;
let outcome = T::XcmExecutor::execute_xcm_in_credit(
origin_location,
message,
);
let result = Ok(Some(outcome.weight_used().saturating_add(100_000_000)).into());
Self::deposit_event(Event::Attempted(outcome));
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
/// 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)?;
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 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)?;
Keith Yeung
committed
let location: MultiLocation =
(*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
Self::unrequest_version_notify(location).map_err(|e| {
match e {
XcmError::InvalidLocation => Error::<T>::NoSubscription,
_ => Error::<T>::InvalidOrigin,
}
.into()
})
}
/// Transfer some assets from the local chain to the sovereign account of a destination
/// chain and forward a notification XCM.
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight
/// is needed than `weight_limit`, then the operation will fail and the assets send may be
/// at risk.
///
/// - `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.
/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
/// fees.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::weight({
match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w)))
}
})]
pub fn limited_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_reserve_transfer_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
Some(weight_limit),
)
}
/// Teleport some assets from the local chain to some destination chain.
///
/// Fee payment on the destination side is made from the asset in the `assets` vector of
/// index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight
/// is needed than `weight_limit`, then the operation will fail and the assets send may be
/// at risk.
///
/// - `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.
/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
#[pallet::weight({
let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
match (maybe_assets, maybe_dest) {
(Ok(assets), Ok(dest)) => {
use sp_std::vec;
let mut message = Xcm(vec![
WithdrawAsset(assets),
InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) },
]);
T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| Weight::from_ref_time(100_000_000.saturating_add(w)))
}
})]
pub fn limited_teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
weight_limit: WeightLimit,
) -> DispatchResult {
Self::do_teleport_assets(
origin,
dest,
beneficiary,
assets,
fee_asset_item,
Some(weight_limit),
)
}
fn do_reserve_transfer_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
maybe_weight_limit: Option<WeightLimit>,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let value = (origin_location, assets.drain());
ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, assets) = value;
let ancestry = T::LocationInverter::ancestry();
let fees = assets
.get(fee_asset_item as usize)
.ok_or(Error::<T>::Empty)?
.clone()
.reanchored(&dest, &ancestry)
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
.map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets: MultiAssets = assets.into();
let weight_limit = match maybe_weight_limit {
Some(weight_limit) => weight_limit,
None => {
let beneficiary = beneficiary.clone();
let fees = fees.clone();
let mut remote_message = Xcm(vec![
ReserveAssetDeposited(assets.clone()),
ClearOrigin,
BuyExecution { fees, weight_limit: Limited(0) },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
]);
// use local weight for remote message and hope for the best.
let remote_weight = T::Weigher::weight(&mut remote_message)
.map_err(|()| Error::<T>::UnweighableMessage)?;
Limited(remote_weight)
},
};
let xcm = Xcm(vec![
BuyExecution { fees, weight_limit },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
]);
let mut message = Xcm(vec![TransferReserveAsset { assets, dest, xcm }]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight);
Self::deposit_event(Event::Attempted(outcome));
Ok(())
}
fn do_teleport_assets(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
assets: Box<VersionedMultiAssets>,
fee_asset_item: u32,
maybe_weight_limit: Option<WeightLimit>,
) -> DispatchResult {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
let value = (origin_location, assets.drain());
ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, assets) = value;
let ancestry = T::LocationInverter::ancestry();
let fees = assets
.get(fee_asset_item as usize)
.ok_or(Error::<T>::Empty)?
.clone()
.reanchored(&dest, &ancestry)
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
.map_err(|_| Error::<T>::CannotReanchor)?;
let max_assets = assets.len() as u32;
let assets: MultiAssets = assets.into();
let weight_limit = match maybe_weight_limit {
Some(weight_limit) => weight_limit,
None => {
let beneficiary = beneficiary.clone();
let fees = fees.clone();
let mut remote_message = Xcm(vec![
ReceiveTeleportedAsset(assets.clone()),
ClearOrigin,
BuyExecution { fees, weight_limit: Limited(0) },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
]);
// use local weight for remote message and hope for the best.
let remote_weight = T::Weigher::weight(&mut remote_message)
.map_err(|()| Error::<T>::UnweighableMessage)?;
Limited(remote_weight)
},
};
let xcm = Xcm(vec![
BuyExecution { fees, weight_limit },
DepositAsset { assets: Wild(All), max_assets, beneficiary },
]);
let mut message =
Xcm(vec![WithdrawAsset(assets), InitiateTeleport { assets: Wild(All), dest, xcm }]);
let weight =
T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight);
Self::deposit_event(Event::Attempted(outcome));
Ok(())
}
/// 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 = Weight::zero();
// TODO: Correct weights for the components of this:
let todo_sv_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1);
let todo_vn_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1);
let todo_vnt_already_notified_weight: Weight = T::DbWeight::get().reads(1);
let todo_vnt_notify_weight: Weight = T::DbWeight::get().reads_writes(1, 3);
let todo_vnt_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 1);
let todo_vnt_migrate_fail_weight: Weight = T::DbWeight::get().reads_writes(1, 1);
let todo_vnt_notify_migrate_weight: Weight = T::DbWeight::get().reads_writes(1, 3);
use VersionMigrationStage::*;
if stage == MigrateSupportedVersion {
// 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.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
}
}
stage = MigrateVersionNotifiers;
}
if stage == MigrateVersionNotifiers {
for v in 0..XCM_VERSION {
for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
if let Ok(new_key) = old_key.into_latest() {
VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
}
weight_used.saturating_accrue(todo_vn_migrate_weight);
if weight_used.any_gte(weight_cutoff) {
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
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.any_gte(weight_cutoff) {
let last = Some(iter.last_raw_key().into());
return (weight_used, Some(NotifyCurrentTargets(last)))
}
}
stage = MigrateAndNotifyOldTargets;
}
if stage == MigrateAndNotifyOldTargets {
for v in 0..XCM_VERSION {
for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
let (query_id, max_weight, target_xcm_version) = value;
let new_key = match MultiLocation::try_from(old_key.clone()) {
Ok(k) => k,
Err(()) => {
Self::deposit_event(Event::NotifyTargetMigrationFail(
old_key, value.0,
));
weight_used.saturating_accrue(todo_vnt_migrate_fail_weight);
if weight_used.any_gte(weight_cutoff) {
return (weight_used, Some(stage))
}
continue