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::{DispatchErrorWithPostInfo, GetDispatchInfo, WithPostDispatchInfo},
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,
Branislav Kontur
committed
#[cfg(any(feature = "try-runtime", test))]
use sp_runtime::TryRuntimeError;
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;
fn claim_assets() -> 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)
}
fn claim_assets() -> 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>>;
/// Required origin for sending XCM messages. If successful, it resolves to `Location`
/// which exists as an interior location within this chain's XCM context.
type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
/// 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 `Location` which exists as an interior location
/// within this chain's XCM context.
type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::RuntimeOrigin, Success = Location>;
/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
type XcmExecuteFilter: Contains<(Location, 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<(Location, Vec<Asset>)>;
/// Our XCM filter which messages to be reserve-transferred using the dedicated extrinsic
/// must pass.
type XcmReserveTransferFilter: Contains<(Location, Vec<Asset>)>;
/// Means of measuring the weight consumed by an XCM message locally.
type Weigher: WeightBounds<<Self as Config>::RuntimeCall>;
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.
/// How to get an `AccountId` value from a `Location`, 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;
}
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<Weight, DispatchErrorWithPostInfo> {
log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight);
let outcome = (|| {
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let mut 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;
Ok(T::XcmExecutor::prepare_and_execute(
origin_location,
message,
&mut hash,
max_weight,
max_weight,
))
})()
.map_err(|e: DispatchError| {
e.with_weight(<Self::WeightInfo as ExecuteControllerWeightInfo>::execute())
})?;
Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
let weight_used = outcome.weight_used();
outcome.ensure_complete().map_err(|error| {
log::error!(target: "xcm::pallet_xcm::execute", "XCM execution failed with error {:?}", error);
Error::<T>::LocalExecutionIncomplete.with_weight(
weight_used.saturating_add(
<Self::WeightInfo as ExecuteControllerWeightInfo>::execute(),
),
)
})?;
Ok(weight_used)
}
}
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>,
message: Box<VersionedXcm<()>>,
) -> Result<XcmHash, DispatchError> {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
let interior: Junctions =
origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
let dest = Location::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.clone(), 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>,
) -> Result<Self::QueryId, DispatchError> {
let responder = <T as Config>::ExecuteXcmOrigin::ensure_origin(origin)?;
let query_id = <Self as QueryHandler>::new_query(
responder,
timeout,
.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: Location, destination: Location, 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: Location, 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.
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: Location, 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: Location, assets: VersionedAssets },
/// An XCM version change notification message has been attempted to be sent.
///
/// The cost of sending it (borne by the chain) is included.
VersionChangeNotified {
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: Location, version: XcmVersion },
/// A given location which had a version change subscription was dropped owing to an error
/// sending the notification to it.
NotifyTargetSendFail { location: Location, 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: VersionedLocation, 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: Location, 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.
expected_querier: Location,
maybe_actual_querier: Option<Location>,
/// 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: Location, cost: Assets, message_id: XcmHash },
/// We have requested that a remote chain send us XCM version change notifications.
VersionNotifyRequested { destination: Location, cost: Assets, message_id: XcmHash },
/// We have requested that a remote chain stops sending us XCM version change
/// notifications.
VersionNotifyUnrequested { destination: Location, cost: Assets, message_id: XcmHash },
/// Fees were paid from a location for an operation (often for using `SendXcm`).
/// Some assets have been claimed from an asset trap
AssetsClaimed { hash: H256, origin: Location, assets: VersionedAssets },
Branislav Kontur
committed
/// A XCM version migration finished.
VersionMigrationFinished { version: XcmVersion },
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.
Gavin Wood
committed
/// It comes as an expected response from an XCM location.
Gavin Wood
committed
}
impl From<Location> for Origin {
fn from(location: Location) -> Origin {
Gavin Wood
committed
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 `Location` provided cannot be inverted.
/// 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.
/// 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.
Gavin Wood
committed
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumber,
},
/// The query is for an ongoing version notification subscription.
VersionNotifier { origin: VersionedLocation, is_active: bool },
Gavin Wood
committed
/// A response has been received.
Ready { response: VersionedResponse, at: BlockNumber },
}
pub(crate) struct LatestVersionedLocation<'a>(pub(crate) &'a Location);
impl<'a> EncodeLike<VersionedLocation> for LatestVersionedLocation<'a> {}
impl<'a> Encode for LatestVersionedLocation<'a> {
let mut r = VersionedLocation::from(Location::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 `Assets`) 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.
#[pallet::storage]
pub(super) type SupportedVersion<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
XcmVersion,
Blake2_128Concat,
XcmVersion,
OptionQuery,
>;
/// All locations that we have requested version notifications from.
#[pallet::storage]
pub(super) type VersionNotifiers<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
XcmVersion,
Blake2_128Concat,
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,
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<(VersionedLocation, 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>, VersionedLocation), T::MaxLockers>,
/// 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);
Branislav Kontur
committed
if maybe_migration.is_none() {
Self::deposit_event(Event::VersionMigrationFinished { version: XCM_VERSION });
}
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() {
if let Ok(dest) = Location::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
}
Branislav Kontur
committed
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
Self::do_try_state()
}
pub mod migrations {
use super::*;
use frame_support::traits::{PalletInfoAccess, StorageVersion};
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
enum QueryStatusV0<BlockNumber> {
Pending {
maybe_notify: Option<(u8, u8)>,
timeout: BlockNumber,
},
VersionNotifier {
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,
},
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>,
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 `[Parent,
/// Parachain(..)]` to send from parachain to parachain, or `[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<Assets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<Location, ()> = (*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<VersionedLocation>,
beneficiary: Box<VersionedLocation>,
assets: Box<VersionedAssets>,
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 `[Parent,
/// Parachain(..)]` to send from parachain to parachain, or `[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<Assets, ()> = (*assets.clone()).try_into();
let maybe_dest: Result<Location, ()> = (*dest.clone()).try_into();
(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 },