lib.rs 77.8 KiB
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
Gavin Wood's avatar
Gavin Wood committed
// 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,
Gavin Wood's avatar
Gavin Wood committed
// 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/>.
Gavin Wood's avatar
Gavin Wood committed

//! Pallet to handle XCM messages.

#![cfg_attr(not(feature = "std"), no_std)]

Gavin Wood's avatar
Gavin Wood committed
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;

Gavin Wood's avatar
Gavin Wood committed
pub mod migration;

use codec::{Decode, Encode, EncodeLike, MaxEncodedLen};
Gavin Wood's avatar
Gavin Wood committed
use frame_support::traits::{
	Contains, ContainsPair, Currency, Defensive, EnsureOrigin, Get, LockableCurrency, OriginTrait,
};
use sp_runtime::{
Gavin Wood's avatar
Gavin Wood committed
	traits::{
		AccountIdConversion, BadOrigin, BlakeTwo256, BlockNumberProvider, Hash, Saturating, Zero,
	},
	RuntimeDebug,
};
use sp_std::{boxed::Box, marker::PhantomData, prelude::*, result::Result, vec};
Gavin Wood's avatar
Gavin Wood committed
use xcm::{latest::QueryResponseInfo, prelude::*};
use xcm_executor::traits::{Convert, ConvertOrigin};

use frame_support::{
	dispatch::{Dispatchable, GetDispatchInfo},
	pallet_prelude::*,
	traits::WithdrawReasons,
	PalletId,
};
use frame_system::pallet_prelude::*;
pub use pallet::*;
Gavin Wood's avatar
Gavin Wood committed
use xcm_executor::{
	traits::{
		CheckSuspension, ClaimAssets, DropAssets, MatchesFungible, OnResponse,
		VersionChangeNotifier, WeightBounds,
Gavin Wood's avatar
Gavin Wood committed
	},
	Assets,
};

pub trait WeightInfo {
	fn send() -> Weight;
	fn teleport_assets() -> Weight;
	fn reserve_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 force_suspension() -> Weight;
Gavin Wood's avatar
Gavin Wood committed
	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;
}

/// fallback implementation
pub struct TestWeightInfo;
impl WeightInfo for TestWeightInfo {
	fn send() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn teleport_assets() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn reserve_transfer_assets() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn execute() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn force_xcm_version() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn force_default_xcm_version() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn force_subscribe_version_notify() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn force_unsubscribe_version_notify() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn force_suspension() -> Weight {
		Weight::from_parts(100_000_000, 0)
	}

Gavin Wood's avatar
Gavin Wood committed
	fn migrate_supported_version() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn migrate_version_notifiers() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn already_notified_target() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn notify_current_targets() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn notify_target_migration_fail() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn migrate_version_notify_targets() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}

	fn migrate_and_notify_old_targets() -> Weight {
		Weight::from_parts(100_000_000, 0)
Gavin Wood's avatar
Gavin Wood committed
	}
}
Gavin Wood's avatar
Gavin Wood committed

#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use frame_support::{
		dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo},
		parameter_types,
Gavin Wood's avatar
Gavin Wood committed
	use frame_system::Config as SysConfig;
Gavin Wood's avatar
Gavin Wood committed
	use xcm_executor::traits::{MatchesFungible, WeightBounds};
	parameter_types! {
		/// An implementation of `Get<u32>` which just returns the latest XCM version which we can
		/// support.
		pub const CurrentXcmVersion: u32 = XCM_VERSION;
	}

Gavin Wood's avatar
Gavin Wood committed
	#[pallet::pallet]
Gavin Wood's avatar
Gavin Wood committed
	#[pallet::storage_version(migration::STORAGE_VERSION)]
	#[pallet::without_storage_info]
Gavin Wood's avatar
Gavin Wood committed
	pub struct Pallet<T>(_);

Gavin Wood's avatar
Gavin Wood committed
	pub type BalanceOf<T> =
		<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

Gavin Wood's avatar
Gavin Wood committed
	#[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>;
Gavin Wood's avatar
Gavin Wood committed
		/// A lockable currency.
		// TODO: We should really use a trait which can handle multiple currencies.
		type Currency: LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>;

		/// The `MultiAsset` matcher for `Currency`.
		type CurrencyMatcher: MatchesFungible<BalanceOf<Self>>;

		/// Required origin for sending XCM messages. If successful, it resolves to `MultiLocation`
Gavin Wood's avatar
Gavin Wood committed
		/// which exists as an interior location within this chain's XCM context.
Sergej Sakac's avatar
Sergej Sakac committed
		type SendXcmOrigin: EnsureOrigin<
			<Self as SysConfig>::RuntimeOrigin,
			Success = MultiLocation,
		>;
Gavin Wood's avatar
Gavin Wood committed

		/// 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
Sergej Sakac's avatar
Sergej Sakac committed
		type ExecuteXcmOrigin: EnsureOrigin<
			<Self as SysConfig>::RuntimeOrigin,
			Success = MultiLocation,
		>;
		/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
		type XcmExecuteFilter: Contains<(MultiLocation, Xcm<<Self as SysConfig>::RuntimeCall>)>;
Gavin Wood's avatar
Gavin Wood committed
		/// Something to execute an XCM message.
		type XcmExecutor: ExecuteXcm<<Self as SysConfig>::RuntimeCall>;
		/// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass.
		type XcmTeleportFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;

		/// Our XCM filter which messages to be reserve-transferred using the dedicated extrinsic must pass.
		type XcmReserveTransferFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;

		/// Means of measuring the weight consumed by an XCM message locally.
		type Weigher: WeightBounds<<Self as SysConfig>::RuntimeCall>;
Gavin Wood's avatar
Gavin Wood committed
		/// This chain's Universal Location.
		type UniversalLocation: Get<InteriorMultiLocation>;
Gavin Wood's avatar
Gavin Wood committed
		/// The runtime `Origin` type.
Sergej Sakac's avatar
Sergej Sakac committed
		type RuntimeOrigin: From<Origin> + From<<Self as SysConfig>::RuntimeOrigin>;
Gavin Wood's avatar
Gavin Wood committed
		/// The runtime `Call` type.
		type RuntimeCall: Parameter
			+ IsType<<Self as frame_system::Config>::RuntimeCall>
Sergej Sakac's avatar
Sergej Sakac committed
			+ 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>;

Gavin Wood's avatar
Gavin Wood committed
		/// 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: Convert<MultiLocation, Self::AccountId>;

		/// The maximum number of local XCM locks that a single account may have.
		type MaxLockers: Get<u32>;

		/// Weight information for extrinsics in this pallet.
		type WeightInfo: WeightInfo;

		/// A `MultiLocation` that can be reached via `XcmRouter`. Used only in benchmarks.
		///
		/// If `None`, the benchmarks that depend on a reachable destination will be skipped.
		#[cfg(feature = "runtime-benchmarks")]
		type ReachableDest: Get<Option<MultiLocation>>;
	}
Gavin Wood's avatar
Gavin Wood committed
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
		/// Execution of an XCM message was attempted.
		///
		/// \[ outcome \]
Gavin Wood's avatar
Gavin Wood committed
		Attempted(xcm::latest::Outcome),
		/// A XCM message was sent.
		///
		/// \[ origin, destination, message \]
		Sent(MultiLocation, MultiLocation, Xcm<()>),
		/// Query response received which does not match a registered query. This may be because a
		/// matching query was never registered, it may be because it is a duplicate response, or
		/// because the query timed out.
		///
		/// \[ origin location, id \]
		UnexpectedResponse(MultiLocation, QueryId),
		/// Query response has been received and is ready for taking with `take_response`. There is
		/// no registered notification call.
		///
		/// \[ id, response \]
		ResponseReady(QueryId, Response),
		/// Query response has been received and query is removed. The registered notification has
		/// been dispatched and executed successfully.
		///
		/// \[ id, pallet index, call index \]
		Notified(QueryId, u8, u8),
		/// Query response has been received and query is removed. The registered notification could
		/// not be dispatched because the dispatch weight is greater than the maximum weight
		/// originally budgeted by this runtime for the query result.
		///
		/// \[ id, pallet index, call index, actual weight, max budgeted weight \]
		NotifyOverweight(QueryId, u8, u8, Weight, Weight),
		/// Query response has been received and query is removed. There was a general error with
		/// dispatching the notification call.
		///
		/// \[ id, pallet index, call index \]
		NotifyDispatchError(QueryId, u8, u8),
		/// Query response has been received and query is removed. The dispatch was unable to be
		/// decoded into a `Call`; this might be due to dispatch function having a signature which
		/// is not `(origin, QueryId, Response)`.
		///
		/// \[ id, pallet index, call index \]
		NotifyDecodeFailed(QueryId, u8, u8),
		/// Expected query response has been received but the origin location of the response does
		/// not match that expected. The query remains registered for a later, valid, response to
		/// be received and acted upon.
		///
		/// \[ origin location, id, expected location \]
		InvalidResponder(MultiLocation, QueryId, Option<MultiLocation>),
		/// 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.
		///
		/// This is unexpected (since a location placed in storage in a previously executing
		/// runtime should be readable prior to query timeout) and dangerous since the possibly
		/// valid response will be dropped. Manual governance intervention is probably going to be
		/// needed.
		///
		/// \[ origin location, id \]
		InvalidResponderVersion(MultiLocation, QueryId),
		/// Received query response has been read and removed.
		///
		/// \[ id \]
		ResponseTaken(QueryId),
		/// Some assets have been placed in an asset trap.
		///
		/// \[ hash, origin, assets \]
		AssetsTrapped(H256, MultiLocation, VersionedMultiAssets),
		/// An XCM version change notification message has been attempted to be sent.
		///
Gavin Wood's avatar
Gavin Wood committed
		/// The cost of sending it (borne by the chain) is included.
		///
		/// \[ destination, result, cost \]
		VersionChangeNotified(MultiLocation, XcmVersion, MultiAssets),
		/// The supported version of a location has been changed. This might be through an
		/// automatic notification or a manual intervention.
		///
		/// \[ location, XCM version \]
		SupportedVersionChanged(MultiLocation, XcmVersion),
		/// A given location which had a version change subscription was dropped owing to an error
		/// sending the notification to it.
		///
		/// \[ location, query ID, error \]
		NotifyTargetSendFail(MultiLocation, QueryId, XcmError),
		/// A given location which had a version change subscription was dropped owing to an error
		/// migrating the location to our new XCM format.
		///
		/// \[ location, query ID \]
		NotifyTargetMigrationFail(VersionedMultiLocation, QueryId),
Gavin Wood's avatar
Gavin Wood committed
		/// 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.
		///
		/// \[ origin location, id \]
		InvalidQuerierVersion(MultiLocation, 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.
		///
		/// \[ origin location, id, expected querier, maybe actual querier \]
		InvalidQuerier(MultiLocation, QueryId, MultiLocation, 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.
		///
		/// \[ destination location, cost \]
		VersionNotifyStarted(MultiLocation, MultiAssets),
		/// We have requested that a remote chain sends us XCM version change notifications.
		///
		/// \[ destination location, cost \]
		VersionNotifyRequested(MultiLocation, MultiAssets),
		/// We have requested that a remote chain stops sending us XCM version change notifications.
		///
		/// \[ destination location, cost \]
		VersionNotifyUnrequested(MultiLocation, MultiAssets),
		/// Fees were paid from a location for an operation (often for using `SendXcm`).
		///
		/// \[ paying location, fees \]
		FeesPaid(MultiLocation, MultiAssets),
		/// Some assets have been claimed from an asset trap
		///
		/// \[ hash, origin, assets \]
		AssetsClaimed(H256, MultiLocation, VersionedMultiAssets),
	#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
	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)
		}
Gavin Wood's avatar
Gavin Wood committed
	}

	#[pallet::error]
	pub enum Error<T> {
		/// The desired destination was unreachable, generally because there is a no way of routing
		/// to it.
Gavin Wood's avatar
Gavin Wood committed
		Unreachable,
		/// 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.
Gavin Wood's avatar
Gavin Wood committed
		SendFailure,
		/// 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.
		CannotReanchor,
		/// Too many assets have been attempted for transfer.
		TooManyAssets,
		/// Origin is invalid for sending.
		InvalidOrigin,
		/// The version of the `Versioned` value used is not able to be interpreted.
		BadVersion,
		/// The given location could not be used (e.g. because it cannot be expressed in the
		/// desired version of XCM).
		BadLocation,
		/// The referenced subscription could not be found.
		NoSubscription,
		/// The location is invalid since it already has a subscription from us.
		AlreadySubscribed,
Gavin Wood's avatar
Gavin Wood committed
		/// Invalid asset for the operation.
		InvalidAsset,
		/// 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 users of the lock.
		InUse,
	}

	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,
			}
		}
	#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
	pub enum QueryStatus<BlockNumber> {
		/// The query was sent but no response has yet been received.
		Pending {
Gavin Wood's avatar
Gavin Wood committed
			/// The `QueryResponse` XCM must have this origin to be considered a reply for this
			/// query.
Gavin Wood's avatar
Gavin Wood committed
			/// 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>,
			maybe_notify: Option<(u8, u8)>,
			timeout: BlockNumber,
		},
		/// The query is for an ongoing version notification subscription.
		VersionNotifier { origin: VersionedMultiLocation, is_active: bool },
		/// 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
		}
	}

	/// The latest available query index.
	#[pallet::storage]
	pub(super) type QueryCounter<T: Config> = StorageValue<_, QueryId, ValueQuery>;

	/// The ongoing queries.
	#[pallet::storage]
	#[pallet::getter(fn query)]
	pub(super) type Queries<T: Config> =
		StorageMap<_, Blake2_128Concat, QueryId, QueryStatus<T::BlockNumber>, OptionQuery>;

	/// The existing asset traps.
	///
	/// Key is the blake2 256 hash of (origin, versioned `MultiAssets`) pair. Value is the number of
	/// times this pair has been trapped (usually just 1 if it exists at all).
	#[pallet::storage]
	#[pallet::getter(fn asset_trap)]
	pub(super) type AssetTraps<T: Config> = StorageMap<_, Identity, H256, u32, ValueQuery>;

	/// Default version to encode XCM when latest version of destination is unknown. If `None`,
	/// then the destinations whose XCM version is unknown are considered unreachable.
	#[pallet::storage]
	pub(super) type SafeXcmVersion<T: Config> = StorageValue<_, XcmVersion, OptionQuery>;

	/// The Latest versions that we know various locations support.
	#[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,
Gavin Wood's avatar
Gavin Wood committed
		(QueryId, Weight, XcmVersion),
		OptionQuery,
	>;

	pub struct VersionDiscoveryQueueSize<T>(PhantomData<T>);
	impl<T: Config> Get<u32> for VersionDiscoveryQueueSize<T> {
		fn get() -> u32 {
			T::VERSION_DISCOVERY_QUEUE_SIZE
		}
	}

	/// Destinations whose latest XCM version we would like to know. Duplicates not allowed, and
	/// the `u32` counter is the number of times that a send to the destination has been attempted,
	/// which is used as a prioritization.
	#[pallet::storage]
	pub(super) type VersionDiscoveryQueue<T: Config> = StorageValue<
		_,
		BoundedVec<(VersionedMultiLocation, u32), VersionDiscoveryQueueSize<T>>,
		ValueQuery,
	>;

	/// The current migration's stage, if any.
	#[pallet::storage]
	pub(super) type CurrentMigration<T: Config> =
		StorageValue<_, VersionMigrationStage, OptionQuery>;

Gavin Wood's avatar
Gavin Wood committed
	#[derive(Clone, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, TypeInfo, MaxEncodedLen)]
	pub struct RemoteLockedFungibleRecord {
		pub amount: u128,
		pub owner: VersionedMultiLocation,
		pub locker: VersionedMultiLocation,
		pub users: u32,
	}

	/// 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,
		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>;

	#[pallet::genesis_config]
	pub struct GenesisConfig {
		/// The default version to encode outgoing XCM messages with.
		pub safe_xcm_version: Option<XcmVersion>,
	}

	#[cfg(feature = "std")]
	impl Default for GenesisConfig {
		fn default() -> Self {
			Self { safe_xcm_version: Some(XCM_VERSION) }
		}
	}

	#[pallet::genesis_build]
	impl<T: Config> GenesisBuild<T> for GenesisConfig {
		fn build(&self) {
			SafeXcmVersion::<T>::set(self.safe_xcm_version);
		}
	}

Gavin Wood's avatar
Gavin Wood committed
	#[pallet::hooks]
	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() {
				if let Ok(dest) = MultiLocation::try_from(versioned_dest) {
					if Self::request_version_notify(dest).is_ok() {
						// TODO: correct weights.
						weight_used.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
						break
					}
				}
			}
			// Should never fail since we only removed items. But better safe than panicking as it's
			// way better to drop the queue than panic on initialize.
			if let Ok(q) = BoundedVec::try_from(q) {
				VersionDiscoveryQueue::<T>::put(q);
			}
			weight_used
		}
		fn on_runtime_upgrade() -> Weight {
			// Start a migration (this happens before on_initialize so it'll happen later in this
			// block, which should be good enough)...
			CurrentMigration::<T>::put(VersionMigrationStage::default());
			T::DbWeight::get().writes(1)
Gavin Wood's avatar
Gavin Wood committed
	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<T::BlockNumber>, _>(|_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)
			}
		}
	}

Gavin Wood's avatar
Gavin Wood committed
	#[pallet::call]
	impl<T: Config> Pallet<T> {
		#[pallet::call_index(0)]
Gavin Wood's avatar
Gavin Wood committed
		#[pallet::weight({
			let maybe_msg: Result<Xcm<()>, ()> = (*message.clone()).try_into();
			match maybe_msg {
				Ok(msg) => {
					T::Weigher::weight(&mut msg.into())
						.map_or(Weight::MAX, |w| T::WeightInfo::send().saturating_add(w))
				}
				_ => Weight::MAX,
			}
		})]
			dest: Box<VersionedMultiLocation>,
			message: Box<VersionedXcm<()>>,
Gavin Wood's avatar
Gavin Wood committed
			let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
Gavin Wood's avatar
Gavin Wood committed
				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)?;

Gavin Wood's avatar
Gavin Wood committed
			Self::send_xcm(interior, dest, message.clone()).map_err(Error::<T>::from)?;
			Self::deposit_event(Event::Sent(origin_location, dest, message));
		/// Teleport some assets from the local chain to some destination chain.
		///
		/// Fee payment on the destination side is made from the asset in the `assets` vector of
		/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited,
		/// with all fees taken as needed from the asset.
		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
		/// - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send
		///   from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.
		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be
		///   an `AccountId32` value.
Gavin Wood's avatar
Gavin Wood committed
		/// - `assets`: The assets to be withdrawn. The first item should be the currency used to to pay the fee on the
		///   `dest` side. May not be empty.
		/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
		///   fees.
		#[pallet::call_index(1)]
		#[pallet::weight({
			let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
			let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
			match (maybe_assets, maybe_dest) {
				(Ok(assets), Ok(dest)) => {
Gavin Wood's avatar
Gavin Wood committed
					let count = assets.len() as u32;
					let mut message = Xcm(vec![
						WithdrawAsset(assets),
Gavin Wood's avatar
Gavin Wood committed
						InitiateTeleport {
							assets: Wild(AllCounted(count)),
							dest,
							xcm: Xcm(vec![]),
						},
Gavin Wood's avatar
Gavin Wood committed
					T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::teleport_assets().saturating_add(w))
				}
				_ => Weight::MAX,
		pub fn teleport_assets(
			origin: OriginFor<T>,
			dest: Box<VersionedMultiLocation>,
			beneficiary: Box<VersionedMultiLocation>,
			assets: Box<VersionedMultiAssets>,
Gavin Wood's avatar
Gavin Wood committed
			fee_asset_item: u32,
		) -> DispatchResult {
			Self::do_teleport_assets(origin, dest, beneficiary, assets, fee_asset_item, None)
		/// Transfer some assets from the local chain to the sovereign account of a destination
		/// chain and forward a notification XCM.
		/// Fee payment on the destination side is made from the asset in the `assets` vector of
		/// index `fee_asset_item`. The weight limit for fees is not provided and thus is unlimited,
		/// with all fees taken as needed from the asset.
		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
		/// - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send
		///   from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.
		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be
		///   an `AccountId32` value.
		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the
		///   `dest` side.
		/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
		///   fees.
		#[pallet::call_index(2)]
		#[pallet::weight({
Gavin Wood's avatar
Gavin Wood committed
			let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
			let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
			match (maybe_assets, maybe_dest) {
				(Ok(assets), Ok(dest)) => {
					use sp_std::vec;
					let mut message = Xcm(vec![
						TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
					]);
Gavin Wood's avatar
Gavin Wood committed
					T::Weigher::weight(&mut message).map_or(Weight::MAX, |w| T::WeightInfo::reserve_transfer_assets().saturating_add(w))
				}
				_ => Weight::MAX,
		pub fn reserve_transfer_assets(
			origin: OriginFor<T>,
			dest: Box<VersionedMultiLocation>,
			beneficiary: Box<VersionedMultiLocation>,
			assets: Box<VersionedMultiAssets>,
Gavin Wood's avatar
Gavin Wood committed
			fee_asset_item: u32,
		) -> DispatchResult {
			Self::do_reserve_transfer_assets(
				origin,
				dest,
				beneficiary,
				assets,
				fee_asset_item,
				None,
			)
Gavin Wood's avatar
Gavin Wood committed
		/// Execute an XCM message from a local, signed, origin.
		///
		/// An event is deposited indicating whether `msg` could be executed completely or only
		/// partially.
		///
		/// No more than `max_weight` will be used in its attempted execution. If this is less than the
		/// maximum amount of weight that the message could take to be executed, then no execution
		/// attempt will be made.
		///
		/// NOTE: A successful return to this does *not* imply that the `msg` was executed successfully
		/// to completion; only that *some* of it was executed.
		#[pallet::call_index(3)]
Gavin Wood's avatar
Gavin Wood committed
		#[pallet::weight(max_weight.saturating_add(T::WeightInfo::execute()))]
		pub fn execute(
			origin: OriginFor<T>,
			message: Box<VersionedXcm<<T as SysConfig>::RuntimeCall>>,
Gavin Wood's avatar
Gavin Wood committed
			max_weight: Weight,
		) -> DispatchResultWithPostInfo {
Gavin Wood's avatar
Gavin Wood committed
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
Gavin Wood's avatar
Gavin Wood committed
			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,
Gavin Wood's avatar
Gavin Wood committed
				hash,
				max_weight,
				max_weight,
Gavin Wood's avatar
Gavin Wood committed
			let result =
				Ok(Some(outcome.weight_used().saturating_add(T::WeightInfo::execute())).into());
Gavin Wood's avatar
Gavin Wood committed
			Self::deposit_event(Event::Attempted(outcome));

		/// Extoll that a particular destination can be communicated with through a particular
		/// version of XCM.
		///
		/// - `origin`: Must be an origin specified by AdminOrigin.
		/// - `location`: The destination that is being described.
		/// - `xcm_version`: The latest version of XCM that `location` supports.
		#[pallet::call_index(4)]
Gavin Wood's avatar
Gavin Wood committed
		#[pallet::weight(T::WeightInfo::force_xcm_version())]
		pub fn force_xcm_version(
			origin: OriginFor<T>,
			location: Box<MultiLocation>,
			xcm_version: XcmVersion,
		) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			let location = *location;
			SupportedVersion::<T>::insert(
				XCM_VERSION,
				LatestVersionedMultiLocation(&location),
				xcm_version,
			);
			Self::deposit_event(Event::SupportedVersionChanged(location, xcm_version));
			Ok(())
		}

		/// Set a safe XCM version (the version that XCM should be encoded with if the most recent
		/// version a destination can accept is unknown).
		///
		/// - `origin`: Must be an origin specified by AdminOrigin.
		/// - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable.
		#[pallet::call_index(5)]
Gavin Wood's avatar
Gavin Wood committed
		#[pallet::weight(T::WeightInfo::force_default_xcm_version())]
		pub fn force_default_xcm_version(
			origin: OriginFor<T>,
			maybe_xcm_version: Option<XcmVersion>,
		) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			SafeXcmVersion::<T>::set(maybe_xcm_version);
			Ok(())
		}

		/// Ask a location to notify us regarding their XCM version and any changes to it.
		///
		/// - `origin`: Must be an origin specified by AdminOrigin.
		/// - `location`: The location to which we should subscribe for XCM version notifications.
		#[pallet::call_index(6)]
Gavin Wood's avatar
Gavin Wood committed
		#[pallet::weight(T::WeightInfo::force_subscribe_version_notify())]
		pub fn force_subscribe_version_notify(
			origin: OriginFor<T>,
			location: Box<VersionedMultiLocation>,
		) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			let location: MultiLocation =
				(*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
			Self::request_version_notify(location).map_err(|e| {
				match e {
					XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
					_ => Error::<T>::InvalidOrigin,
				}
				.into()
			})
		}

		/// Require that a particular destination should no longer notify us regarding any XCM
		/// version changes.
		///
		/// - `origin`: Must be an origin specified by AdminOrigin.
		/// - `location`: The location to which we are currently subscribed for XCM version
		///   notifications which we no longer desire.
		#[pallet::call_index(7)]
Gavin Wood's avatar
Gavin Wood committed
		#[pallet::weight(T::WeightInfo::force_unsubscribe_version_notify())]
		pub fn force_unsubscribe_version_notify(
			origin: OriginFor<T>,
			location: Box<VersionedMultiLocation>,
		) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			let location: MultiLocation =
				(*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
			Self::unrequest_version_notify(location).map_err(|e| {
				match e {
					XcmError::InvalidLocation => Error::<T>::NoSubscription,
					_ => Error::<T>::InvalidOrigin,
				}
				.into()