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};
use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{BadOrigin, Saturating},
RuntimeDebug,
};
use sp_std::{
boxed::Box,
convert::{TryFrom, TryInto},
marker::PhantomData,
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)]
pub struct Pallet<T>(_);
#[pallet::config]
/// The module configuration trait.
pub trait Config: frame_system::Config {
/// The overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// Required origin for sending XCM messages. If successful, it resolves to `MultiLocation`
/// which exists as an interior location within this chain's XCM context.
Gavin Wood
committed
type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::Origin, 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
Gavin Wood
committed
type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::Origin, Success = MultiLocation>;
/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
Gavin Wood
committed
type XcmExecuteFilter: Contains<(MultiLocation, Xcm<<Self as SysConfig>::Call>)>;
Gavin Wood
committed
type XcmExecutor: ExecuteXcm<<Self as SysConfig>::Call>;
/// 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.
Gavin Wood
committed
type Weigher: WeightBounds<<Self as SysConfig>::Call>;
/// Means of inverting a location.
type LocationInverter: InvertLocation;
Gavin Wood
committed
/// The outer `Origin` type.
type Origin: From<Origin> + From<<Self as SysConfig>::Origin>;
/// The outer `Call` type.
type Call: Parameter
+ 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.
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
142
143
144
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
/// 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),
Gavin Wood
committed
}
#[pallet::origin]
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
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.
332
333
334
335
336
337
338
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
#[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 = 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() {
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 += 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> {
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 first asset listed in the `assets` vector and
/// fee-weight is calculated locally and thus remote weights are assumed to be equal to
/// local weights.
/// - `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_value(), |w| 100_000_000 + w)
},
_ => Weight::max_value(),
}
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 first asset listed in the `assets` vector and
/// fee-weight is calculated locally and thus remote weights are assumed to be equal to
/// local weights.
/// - `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_value(), |w| 100_000_000 + w)
},
_ => Weight::max_value(),
}
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(max_weight.saturating_add(100_000_000u64))]
Gavin Wood
committed
message: Box<VersionedXcm<<T as SysConfig>::Call>>,
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
let value = (origin_location, message);
ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
let (origin_location, message) = value;
let outcome = T::XcmExecutor::execute_xcm_in_credit(
origin_location,
message,
max_weight,
max_weight,
);
let result = Ok(Some(outcome.weight_used().saturating_add(100_000_000)).into());
Self::deposit_event(Event::Attempted(outcome));
591
592
593
594
595
596
597
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
/// 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()
})
}
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
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
/// 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 first asset listed in the `assets` vector.
///
/// - `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_value(), |w| 100_000_000 + w)
},
_ => Weight::max_value(),
}
})]
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 first asset listed in the `assets` vector.
///
/// - `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.
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
/// - `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_value(), |w| 100_000_000 + w)
},
_ => Weight::max_value(),
}
})]
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)
792
793
794
795
796
797
798
799
800
801
802
803
804
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
.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)
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
.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(())
}
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
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
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// 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.