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)]
Adrian Catangiu
committed
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
use frame_support::{
dispatch::GetDispatchInfo,
pallet_prelude::*,
traits::{
Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency,
OriginTrait, WithdrawReasons,
},
PalletId,
use frame_system::pallet_prelude::{BlockNumberFor, *};
pub use pallet::*;
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_builder::{
ExecuteController, ExecuteControllerWeightInfo, QueryController, QueryControllerWeightInfo,
SendController, SendControllerWeightInfo,
Adrian Catangiu
committed
AssetTransferError, CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin,
DropAssets, MatchesFungible, OnResponse, Properties, QueryHandler, QueryResponseStatus,
TransactAsset, TransferType, VersionChangeNotifier, WeightBounds, XcmAssetTransfers,
},
Assets,
};
pub trait WeightInfo {
fn send() -> Weight;
fn teleport_assets() -> Weight;
fn reserve_transfer_assets() -> Weight;
fn 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;
fn new_query() -> Weight;
fn take_response() -> 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)
fn transfer_assets() -> 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)
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)
fn new_query() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn take_response() -> Weight {
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>)>;
Adrian Catangiu
committed
type XcmExecutor: ExecuteXcm<<Self as Config>::RuntimeCall> + XcmAssetTransfers;
/// 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;
}
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
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
impl<T: Config> ExecuteControllerWeightInfo for Pallet<T> {
fn execute() -> Weight {
T::WeightInfo::execute()
}
}
impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pallet<T> {
type WeightInfo = Self;
fn execute(
origin: OriginFor<T>,
message: Box<VersionedXcm<<T as Config>::RuntimeCall>>,
max_weight: Weight,
) -> Result<Outcome, DispatchError> {
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,
hash,
max_weight,
max_weight,
);
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
Ok(outcome)
}
}
impl<T: Config> SendControllerWeightInfo for Pallet<T> {
fn send() -> Weight {
T::WeightInfo::send()
}
}
impl<T: Config> SendController<OriginFor<T>> for Pallet<T> {
type WeightInfo = Self;
fn send(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
message: Box<VersionedXcm<()>>,
) -> Result<XcmHash, DispatchError> {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
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);
Ok(message_id)
}
}
impl<T: Config> QueryControllerWeightInfo for Pallet<T> {
fn query() -> Weight {
T::WeightInfo::new_query()
}
fn take_response() -> Weight {
T::WeightInfo::take_response()
}
}
impl<T: Config> QueryController<OriginFor<T>, BlockNumberFor<T>> for Pallet<T> {
type WeightInfo = Self;
fn query(
origin: OriginFor<T>,
timeout: BlockNumberFor<T>,
match_querier: VersionedMultiLocation,
) -> Result<Self::QueryId, DispatchError> {
let responder = <T as Config>::ExecuteXcmOrigin::ensure_origin(origin)?;
let query_id = <Self as QueryHandler>::new_query(
responder,
timeout,
MultiLocation::try_from(match_querier)
.map_err(|_| Into::<DispatchError>::into(Error::<T>::BadVersion))?,
);
Ok(query_id)
}
}
#[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,
Adrian Catangiu
committed
/// Could not check-out the assets for teleportation to the destination chain.
CannotCheckOutTeleport,
/// 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.
Adrian Catangiu
committed
/// Invalid non-concrete asset.
InvalidAssetNotConcrete,
/// Invalid asset, reserve chain could not be determined for it.
InvalidAssetUnknownReserve,
/// Invalid asset, do not support remote asset reserves with different fees reserves.
InvalidAssetUnsupportedReserve,
/// Too many assets with different reserve locations have been attempted for transfer.
TooManyReserves,
Xiliang Chen
committed
/// Local XCM execution incomplete.
Adrian Catangiu
committed
LocalExecutionIncomplete,
}
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,
}
}
Adrian Catangiu
committed
impl<T: Config> From<AssetTransferError> for Error<T> {
fn from(e: AssetTransferError) -> Self {
match e {
AssetTransferError::NotConcrete => Error::<T>::InvalidAssetNotConcrete,
AssetTransferError::UnknownReserve => Error::<T>::InvalidAssetUnknownReserve,
}
}
}
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.
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
#[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
}
}
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
856
857
858
859
860
861
862
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 {
<Self as SendController<_>>::send(origin, dest, message)?;
/// 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. This should include the assets used to pay the
/// fee on the `dest` chain.
/// - `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 },
Adrian Catangiu
committed
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 destination chain through their local,
/// destination or remote reserve.
///
/// `assets` must have same reserve location and may not be teleportable to `dest`.
/// - `assets` have local reserve: transfer assets to sovereign account of destination
/// chain and forward a notification XCM to `dest` to mint and deposit reserve-based
/// assets to `beneficiary`.
/// - `assets` have destination reserve: burn local assets and forward a notification to
/// `dest` chain to withdraw the reserve assets from this chain's sovereign account and
/// deposit them to `beneficiary`.
/// - `assets` have remote reserve: burn local assets, forward XCM to reserve chain to move
/// reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest`
/// to mint and deposit reserve-based assets to `beneficiary`.
/// **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` (and possibly reserve) chains.
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;
Adrian Catangiu
committed
// heaviest version of locally executed XCM program: equivalent in weight to
// transfer assets to SA, reanchor them, extend XCM program, and send onward XCM
Gavin Wood
committed
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(