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::{
Francisco Aguirre
committed
ExecuteController, ExecuteControllerWeightInfo, MaxXcmEncodedSize, QueryController,
QueryControllerWeightInfo, SendController, SendControllerWeightInfo,
Adrian Catangiu
committed
AssetTransferError, CheckSuspension, ClaimAssets, ConvertLocation, ConvertOrigin,
DropAssets, MatchesFungible, OnResponse, Properties, QueryHandler, QueryResponseStatus,
TransactAsset, TransferType, VersionChangeNotifier, WeightBounds, XcmAssetTransfers,
use xcm_fee_payment_runtime_api::Error as FeePaymentError;
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;
Francisco Aguirre
committed
fn execute_blob() -> Weight;
fn send_blob() -> 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)
}
Francisco Aguirre
committed
fn execute_blob() -> Weight {
Weight::from_parts(100_000_000, 0)
}
fn send_blob() -> 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> {
Francisco Aguirre
committed
fn execute_blob() -> Weight {
T::WeightInfo::execute_blob()
}
}
impl<T: Config> ExecuteController<OriginFor<T>, <T as Config>::RuntimeCall> for Pallet<T> {
type WeightInfo = Self;
Francisco Aguirre
committed
fn execute_blob(
origin: OriginFor<T>,
Francisco Aguirre
committed
encoded_message: BoundedVec<u8, MaxXcmEncodedSize>,
max_weight: Weight,
) -> Result<Weight, DispatchErrorWithPostInfo> {
Francisco Aguirre
committed
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let message =
VersionedXcm::<<T as Config>::RuntimeCall>::decode(&mut &encoded_message[..])
.map_err(|error| {
log::error!(target: "xcm::execute_blob", "Unable to decode XCM, error: {:?}", error);
Error::<T>::UnableToDecode
})?;
Self::execute_base(origin_location, Box::new(message), max_weight)
}
}
impl<T: Config> SendControllerWeightInfo for Pallet<T> {
Francisco Aguirre
committed
fn send_blob() -> Weight {
T::WeightInfo::send_blob()
}
}
impl<T: Config> SendController<OriginFor<T>> for Pallet<T> {
type WeightInfo = Self;
Francisco Aguirre
committed
fn send_blob(
origin: OriginFor<T>,
Francisco Aguirre
committed
encoded_message: BoundedVec<u8, MaxXcmEncodedSize>,
) -> Result<XcmHash, DispatchError> {
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
Francisco Aguirre
committed
let message =
VersionedXcm::<()>::decode(&mut &encoded_message[..]).map_err(|error| {
log::error!(target: "xcm::send_blob", "Unable to decode XCM, error: {:?}", error);
Error::<T>::UnableToDecode
})?;
Self::send_base(origin_location, dest, Box::new(message))
}
}
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<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,
Francisco Aguirre
committed
/// Could not decode XCM.
UnableToDecode,
/// XCM encoded length is too large.
/// Returned when an XCM encoded length is larger than `MaxXcmEncodedSize`.
XcmTooLarge,
}
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)
}
}
}
Francisco Aguirre
committed
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
impl<T: Config> Pallet<T> {
/// Underlying logic for both [`execute_blob`] and [`execute`].
fn execute_base(
origin_location: Location,
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 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(T::WeightInfo::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(T::WeightInfo::execute()))
})?;
Ok(weight_used)
}
/// Underlying logic for both [`send_blob`] and [`send`].
fn send_base(
origin_location: Location,
dest: Box<VersionedLocation>,
message: Box<VersionedXcm<()>>,
) -> Result<XcmHash, DispatchError> {
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)
}
}
Adrian Catangiu
committed
#[pallet::call(weight(<T as Config>::WeightInfo))]
Francisco Aguirre
committed
/// WARNING: DEPRECATED. `send` will be removed after June 2024. Use `send_blob` instead.
#[allow(deprecated)]
#[deprecated(note = "`send` will be removed after June 2024. Use `send_blob` instead.")]
thiolliere
committed
pub fn send(
origin: OriginFor<T>,
message: Box<VersionedXcm<()>>,
thiolliere
committed
) -> DispatchResult {
Francisco Aguirre
committed
let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
Self::send_base(origin_location, 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.
Adrian Catangiu
committed
#[allow(deprecated)]
#[deprecated(
note = "This extrinsic uses `WeightLimit::Unlimited`, please migrate to `limited_teleport_assets` or `transfer_assets`"
)]
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