lib.rs 49.3 KiB
Newer Older
Gavin Wood's avatar
Gavin Wood committed
// Copyright 2020-2021 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)]

#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;

use codec::{Decode, Encode, EncodeLike};
use frame_support::traits::{Contains, EnsureOrigin, Get, OriginTrait};
use sp_runtime::{
	traits::{BadOrigin, Saturating},
	RuntimeDebug,
};
use sp_std::{
	boxed::Box,
	convert::{TryFrom, TryInto},
	marker::PhantomData,
	prelude::*,
	result::Result,
use xcm::prelude::*;
use xcm_executor::traits::ConvertOrigin;
pub use pallet::*;
Gavin Wood's avatar
Gavin Wood committed

#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use frame_support::{
		dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo},
		pallet_prelude::*,
		parameter_types,
	};
	use frame_system::{pallet_prelude::*, Config as SysConfig};
	use sp_core::H256;
	use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, BlockNumberProvider, Hash};
	use xcm_executor::{
		traits::{
			ClaimAssets, DropAssets, InvertLocation, OnResponse, VersionChangeNotifier,
			WeightBounds,
		},
	parameter_types! {
		/// An implementation of `Get<u32>` which just returns the latest XCM version which we can
		/// support.
		pub const CurrentXcmVersion: u32 = XCM_VERSION;
	}

Gavin Wood's avatar
Gavin Wood committed
	#[pallet::pallet]
	#[pallet::generate_store(pub(super) trait Store)]
	pub struct Pallet<T>(_);

	#[pallet::config]
	/// The module configuration trait.
	pub trait Config: frame_system::Config {
		/// The overarching event type.
		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;

		/// Required origin for sending XCM messages. If successful, the it resolves to `MultiLocation`
		/// which exists as an interior location within this chain's XCM context.
		type SendXcmOrigin: EnsureOrigin<<Self as SysConfig>::Origin, 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
		type ExecuteXcmOrigin: EnsureOrigin<<Self as SysConfig>::Origin, Success = MultiLocation>;
		/// Our XCM filter which messages to be executed using `XcmExecutor` must pass.
		type XcmExecuteFilter: Contains<(MultiLocation, Xcm<<Self as SysConfig>::Call>)>;
Gavin Wood's avatar
Gavin Wood committed
		/// Something to execute an XCM message.
		type XcmExecutor: ExecuteXcm<<Self as SysConfig>::Call>;
		/// Our XCM filter which messages to be teleported using the dedicated extrinsic must pass.
		type XcmTeleportFilter: Contains<(MultiLocation, Vec<MultiAsset>)>;

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

		/// Means of measuring the weight consumed by an XCM message locally.
		type Weigher: WeightBounds<<Self as SysConfig>::Call>;

		/// Means of inverting a location.
		type LocationInverter: InvertLocation;

		/// The outer `Origin` type.
		type Origin: From<Origin> + From<<Self as SysConfig>::Origin>;

		/// The outer `Call` type.
		type Call: Parameter
			+ GetDispatchInfo
			+ IsType<<Self as frame_system::Config>::Call>
			+ Dispatchable<Origin = <Self as Config>::Origin, PostInfo = PostDispatchInfo>;

		const VERSION_DISCOVERY_QUEUE_SIZE: u32;

		/// The latest supported version that we advertise. Generally just set it to
		/// `pallet_xcm::CurrentXcmVersion`.
		type AdvertisedXcmVersion: Get<XcmVersion>;
	/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
	const MAX_ASSETS_FOR_TRANSFER: usize = 2;

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
		/// storate 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.
		///
		/// \[ destination, result \]
		VersionChangeNotified(MultiLocation, XcmVersion),
		/// The supported version of a location has been changed. This might be through an
		/// automatic notification or a manual intervention.
		///
		/// \[ location, XCM version \]
		SupportedVersionChanged(MultiLocation, XcmVersion),
		/// A given location which had a version change subscription was dropped owing to an error
		/// sending the notification to it.
		///
		/// \[ location, query ID, error \]
		NotifyTargetSendFail(MultiLocation, QueryId, XcmError),
		/// A given location which had a version change subscription was dropped owing to an error
		/// migrating the location to our new XCM format.
		///
		/// \[ location, query ID \]
		NotifyTargetMigrationFail(VersionedMultiLocation, QueryId),
	#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
	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,
	#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
	pub enum QueryStatus<BlockNumber> {
		/// The query was sent but no response has yet been received.
		Pending {
			responder: VersionedMultiLocation,
			maybe_notify: Option<(u8, u8)>,
			timeout: BlockNumber,
		},
		/// The query is for an ongoing version notification subscription.
		VersionNotifier { origin: VersionedMultiLocation, is_active: bool },
		/// 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>;

	/// 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,
		(QueryId, u64, XcmVersion),
		OptionQuery,
	>;

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

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

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

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

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

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

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 = 0;
			if let Some(migration) = CurrentMigration::<T>::get() {
				// Consume 10% of block at most
				let max_weight = T::BlockWeights::get().max_block / 10;
				let (w, maybe_migration) = Self::check_xcm_version_change(migration, max_weight);
				CurrentMigration::<T>::set(maybe_migration);
				weight_used.saturating_accrue(w);
			}

			// Here we aim to get one successful version negotiation request sent per block, ordered
			// by the destinations being most sent to.
			let mut q = VersionDiscoveryQueue::<T>::take().into_inner();
			// TODO: correct weights.
			weight_used += T::DbWeight::get().read + T::DbWeight::get().write;
			q.sort_by_key(|i| i.1);
			while let Some((versioned_dest, _)) = q.pop() {
				if let Ok(dest) = versioned_dest.try_into() {
					if Self::request_version_notify(dest).is_ok() {
						// TODO: correct weights.
						weight_used += T::DbWeight::get().read + T::DbWeight::get().write;
						break
					}
				}
			}
			// Should never fail since we only removed items. But better safe than panicking as it's
			// way better to drop the queue than panic on initialize.
			if let Ok(q) = BoundedVec::try_from(q) {
				VersionDiscoveryQueue::<T>::put(q);
			}
			weight_used
		}
		fn on_runtime_upgrade() -> Weight {
			// Start a migration (this happens before on_initialize so it'll happen later in this
			// block, which should be good enough)...
			CurrentMigration::<T>::put(VersionMigrationStage::default());
			T::DbWeight::get().write
		}
	}
Gavin Wood's avatar
Gavin Wood committed

	#[pallet::call]
	impl<T: Config> Pallet<T> {
		#[pallet::weight(100_000_000)]
			dest: Box<VersionedMultiLocation>,
			message: Box<VersionedXcm<()>>,
Gavin Wood's avatar
Gavin Wood committed
			let origin_location = T::SendXcmOrigin::ensure_origin(origin)?;
			let interior =
				origin_location.clone().try_into().map_err(|_| Error::<T>::InvalidOrigin)?;
			let dest = MultiLocation::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
			let message: Xcm<()> = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;

			Self::send_xcm(interior, dest.clone(), message.clone()).map_err(|e| match e {
				SendError::CannotReachDestination(..) => Error::<T>::Unreachable,
				_ => Error::<T>::SendFailure,
			})?;
			Self::deposit_event(Event::Sent(origin_location, dest, message));
		/// Teleport some assets from the local chain to some destination chain.
		///
		/// Fee payment on the destination side is made from the first asset listed in the `assets` vector.
		///
		/// - `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.
		/// - `dest_weight`: Equal to the total weight on `dest` of the XCM message
		///   `Teleport { assets, effects: [ BuyExecution{..}, DepositAsset{..} ] }`.
		#[pallet::weight({
			let maybe_assets: Result<MultiAssets, ()> = (*assets.clone()).try_into();
			let maybe_dest: Result<MultiLocation, ()> = (*dest.clone()).try_into();
			match (maybe_assets, maybe_dest) {
				(Ok(assets), Ok(dest)) => {
					use sp_std::vec;
					let mut message = Xcm(vec![
						WithdrawAsset(assets),
						InitiateTeleport { assets: Wild(All), dest, xcm: Xcm(vec![]) },
					]);
					T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w)
				},
				_ => Weight::max_value(),
			}
		pub fn 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 {
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
			let dest = MultiLocation::try_from(*dest).map_err(|()| Error::<T>::BadVersion)?;
			let beneficiary =
				MultiLocation::try_from(*beneficiary).map_err(|()| Error::<T>::BadVersion)?;
			let assets = MultiAssets::try_from(*assets).map_err(|()| Error::<T>::BadVersion)?;

			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
Gavin Wood's avatar
Gavin Wood committed
			let value = (origin_location, assets.drain());
			ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
			let (origin_location, assets) = value;
			let inv_dest = T::LocationInverter::invert_location(&dest)
				.map_err(|()| Error::<T>::DestinationNotInvertible)?;
Gavin Wood's avatar
Gavin Wood committed
			let fees = assets
				.get(fee_asset_item as usize)
				.ok_or(Error::<T>::Empty)?
				.clone()
				.reanchored(&inv_dest)
				.map_err(|_| Error::<T>::CannotReanchor)?;
			let max_assets = assets.len() as u32;
			let assets = assets.into();
			let mut message = Xcm(vec![
				WithdrawAsset(assets),
				InitiateTeleport {
Gavin Wood's avatar
Gavin Wood committed
					assets: Wild(All),
					xcm: Xcm(vec![
						BuyExecution { fees, weight_limit: Unlimited },
						DepositAsset { assets: Wild(All), max_assets, beneficiary },
			let weight =
				T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
			let outcome =
				T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight);
			Self::deposit_event(Event::Attempted(outcome));
			Ok(())
		}

		/// Transfer some assets from the local chain to the sovereign account of a destination chain and forward
		/// a notification XCM.
		///
		/// Fee payment on the destination side is made from the first asset listed in the `assets` vector.
		///
		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
		/// - `dest`: Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send
		///   from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain.
		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will generally be
		///   an `AccountId32` value.
		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the
		///   `dest` side.
		/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
		///   fees.
		#[pallet::weight({
			match ((*assets.clone()).try_into(), (*dest.clone()).try_into()) {
				(Ok(assets), Ok(dest)) => {
					use sp_std::vec;
					let mut message = Xcm(vec![
						TransferReserveAsset { assets, dest, xcm: Xcm(vec![]) }
					]);
					T::Weigher::weight(&mut message).map_or(Weight::max_value(), |w| 100_000_000 + w)
				},
				_ => Weight::max_value(),
			}
		pub fn 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 {
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
			let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let beneficiary = (*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;

			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
Gavin Wood's avatar
Gavin Wood committed
			let value = (origin_location, assets.drain());
			ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
			let (origin_location, assets) = value;
			let inv_dest = T::LocationInverter::invert_location(&dest)
				.map_err(|()| Error::<T>::DestinationNotInvertible)?;
Gavin Wood's avatar
Gavin Wood committed
			let fees = assets
				.get(fee_asset_item as usize)
				.ok_or(Error::<T>::Empty)?
				.clone()
				.reanchored(&inv_dest)
				.map_err(|_| Error::<T>::CannotReanchor)?;
			let max_assets = assets.len() as u32;
			let assets = assets.into();
			let mut message = Xcm(vec![TransferReserveAsset {
				xcm: Xcm(vec![
					BuyExecution { fees, weight_limit: Unlimited },
					DepositAsset { assets: Wild(All), max_assets, beneficiary },
			let weight =
				T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
			let outcome =
				T::XcmExecutor::execute_xcm_in_credit(origin_location, message, weight, weight);
			Self::deposit_event(Event::Attempted(outcome));
			Ok(())
		}

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::weight(max_weight.saturating_add(100_000_000u64))]
		pub fn execute(
			origin: OriginFor<T>,
			message: Box<VersionedXcm<<T as SysConfig>::Call>>,
			max_weight: Weight,
		) -> DispatchResult {
Gavin Wood's avatar
Gavin Wood committed
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
			let message = (*message).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let value = (origin_location, message);
			ensure!(T::XcmExecuteFilter::contains(&value), Error::<T>::Filtered);
			let (origin_location, message) = value;
			let outcome = T::XcmExecutor::execute_xcm(origin_location, message, max_weight);
Gavin Wood's avatar
Gavin Wood committed
			Self::deposit_event(Event::Attempted(outcome));
			Ok(())
		}

		/// Extoll that a particular destination can be communicated with through a particular
		/// version of XCM.
		///
		/// - `origin`: Must be Root.
		/// - `location`: The destination that is being described.
		/// - `xcm_version`: The latest version of XCM that `location` supports.
		#[pallet::weight(100_000_000u64)]
		pub fn force_xcm_version(
			origin: OriginFor<T>,
			location: Box<MultiLocation>,
			xcm_version: XcmVersion,
		) -> DispatchResult {
			ensure_root(origin)?;
			let location = *location;
			SupportedVersion::<T>::insert(
				XCM_VERSION,
				LatestVersionedMultiLocation(&location),
				xcm_version,
			);
			Self::deposit_event(Event::SupportedVersionChanged(location, xcm_version));
			Ok(())
		}

		/// Set a safe XCM version (the version that XCM should be encoded with if the most recent
		/// version a destination can accept is unknown).
		///
		/// - `origin`: Must be Root.
		/// - `maybe_xcm_version`: The default XCM encoding version, or `None` to disable.
		#[pallet::weight(100_000_000u64)]
		pub fn force_default_xcm_version(
			origin: OriginFor<T>,
			maybe_xcm_version: Option<XcmVersion>,
		) -> DispatchResult {
			ensure_root(origin)?;
			SafeXcmVersion::<T>::set(maybe_xcm_version);
			Ok(())
		}

		/// Ask a location to notify us regarding their XCM version and any changes to it.
		///
		/// - `origin`: Must be Root.
		/// - `location`: The location to which we should subscribe for XCM version notifications.
		#[pallet::weight(100_000_000u64)]
		pub fn force_subscribe_version_notify(
			origin: OriginFor<T>,
			location: Box<VersionedMultiLocation>,
		) -> DispatchResult {
			ensure_root(origin)?;
			let location = (*location).try_into().map_err(|()| Error::<T>::BadLocation)?;
			Self::request_version_notify(location).map_err(|e| {
				match e {
					XcmError::InvalidLocation => Error::<T>::AlreadySubscribed,
					_ => Error::<T>::InvalidOrigin,
				}
				.into()
			})
		}

		/// Require that a particular destination should no longer notify us regarding any XCM
		/// version changes.
		///
		/// - `origin`: Must be Root.
		/// - `location`: The location to which we are currently subscribed for XCM version
		///   notifications which we no longer desire.
		#[pallet::weight(100_000_000u64)]
		pub fn force_unsubscribe_version_notify(
			origin: OriginFor<T>,
			location: Box<VersionedMultiLocation>,
		) -> DispatchResult {
			ensure_root(origin)?;
			let location = (*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()
			})
		}
Gavin Wood's avatar
Gavin Wood committed
	}

	impl<T: Config> Pallet<T> {
		/// Will always make progress, and will do its best not to use much more than `weight_cutoff`
		/// in doing so.
		pub(crate) fn check_xcm_version_change(
			mut stage: VersionMigrationStage,
			weight_cutoff: Weight,
		) -> (Weight, Option<VersionMigrationStage>) {
			let mut weight_used = 0;

			// TODO: Correct weights for the components of this:
			let todo_sv_migrate_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write;
			let todo_vn_migrate_weight: Weight = T::DbWeight::get().read + T::DbWeight::get().write;
			let todo_vnt_already_notified_weight: Weight = T::DbWeight::get().read;
			let todo_vnt_notify_weight: Weight =
				T::DbWeight::get().read + T::DbWeight::get().write * 3;
			let todo_vnt_migrate_weight: Weight =
				T::DbWeight::get().read + T::DbWeight::get().write;
			let todo_vnt_migrate_fail_weight: Weight =
				T::DbWeight::get().read + T::DbWeight::get().write;
			let todo_vnt_notify_migrate_weight: Weight =
				T::DbWeight::get().read + T::DbWeight::get().write * 3;

			use VersionMigrationStage::*;

			if stage == MigrateSupportedVersion {
				// We assume that supported XCM version only ever increases, so just cycle through lower
				// XCM versioned from the current.
				for v in 0..XCM_VERSION {
					for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
						if let Ok(new_key) = old_key.into_latest() {
							SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
						}
						weight_used.saturating_accrue(todo_sv_migrate_weight);
						if weight_used >= weight_cutoff {
							return (weight_used, Some(stage))
						}
					}
				}
				stage = MigrateVersionNotifiers;
			}
			if stage == MigrateVersionNotifiers {
				for v in 0..XCM_VERSION {
					for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
						if let Ok(new_key) = old_key.into_latest() {
							VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
						}
						weight_used.saturating_accrue(todo_vn_migrate_weight);
						if weight_used >= weight_cutoff {
							return (weight_used, Some(stage))
						}
					}
				}
				stage = NotifyCurrentTargets(None);
			}

			let xcm_version = T::AdvertisedXcmVersion::get();

			if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
				let mut iter = match maybe_last_raw_key {
					Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
					None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
				};
				while let Some((key, value)) = iter.next() {
					let (query_id, max_weight, target_xcm_version) = value;
					let new_key: MultiLocation = match key.clone().try_into() {
						Ok(k) if target_xcm_version != xcm_version => k,
						_ => {
							// We don't early return here since we need to be certain that we
							// make some progress.
							weight_used.saturating_accrue(todo_vnt_already_notified_weight);
							continue
						},
					};
					let response = Response::Version(xcm_version);
					let message = Xcm(vec![QueryResponse { query_id, response, max_weight }]);
					let event = match T::XcmRouter::send_xcm(new_key.clone(), message) {
						Ok(()) => {
							let value = (query_id, max_weight, xcm_version);
							VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
							Event::VersionChangeNotified(new_key, xcm_version)
						},
						Err(e) => {
							VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
							Event::NotifyTargetSendFail(new_key, query_id, e.into())
						},
					};
					Self::deposit_event(event);
					weight_used.saturating_accrue(todo_vnt_notify_weight);
					if weight_used >= weight_cutoff {
						let last = Some(iter.last_raw_key().into());
						return (weight_used, Some(NotifyCurrentTargets(last)))
					}
				}
				stage = MigrateAndNotifyOldTargets;
			}
			if stage == MigrateAndNotifyOldTargets {
				for v in 0..XCM_VERSION {
					for (old_key, value) in VersionNotifyTargets::<T>::drain_prefix(v) {
						let (query_id, max_weight, target_xcm_version) = value;
						let new_key = match MultiLocation::try_from(old_key.clone()) {
							Ok(k) => k,
							Err(()) => {
								Self::deposit_event(Event::NotifyTargetMigrationFail(
									old_key, value.0,
								));
								weight_used.saturating_accrue(todo_vnt_migrate_fail_weight);
								if weight_used >= weight_cutoff {
									return (weight_used, Some(stage))
								}
								continue
							},
						};

						let versioned_key = LatestVersionedMultiLocation(&new_key);
						if target_xcm_version == xcm_version {
							VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
							weight_used.saturating_accrue(todo_vnt_migrate_weight);
						} else {
							// Need to notify target.
							let response = Response::Version(xcm_version);
							let message =
								Xcm(vec![QueryResponse { query_id, response, max_weight }]);
							let event = match T::XcmRouter::send_xcm(new_key.clone(), message) {
								Ok(()) => {
									VersionNotifyTargets::<T>::insert(
										XCM_VERSION,
										versioned_key,
										(query_id, max_weight, xcm_version),
									);
									Event::VersionChangeNotified(new_key, xcm_version)
								},
								Err(e) => Event::NotifyTargetSendFail(new_key, query_id, e.into()),
							};
							Self::deposit_event(event);
							weight_used.saturating_accrue(todo_vnt_notify_migrate_weight);
						}
						if weight_used >= weight_cutoff {
							return (weight_used, Some(stage))
						}
					}
				}
			}
			(weight_used, None)
		}

		/// Request that `dest` informs us of its version.
		pub fn request_version_notify(dest: MultiLocation) -> XcmResult {
			let versioned_dest = VersionedMultiLocation::from(dest.clone());
			let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
			ensure!(!already, XcmError::InvalidLocation);
			let query_id = QueryCounter::<T>::mutate(|q| {
				let r = *q;
				q.saturating_inc();
				r
			});
			// TODO #3735: Correct weight.
			let instruction = SubscribeVersion { query_id, max_response_weight: 0 };
			T::XcmRouter::send_xcm(dest, Xcm(vec![instruction]))?;
			VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
			let query_status =
				QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
			Queries::<T>::insert(query_id, query_status);
			Ok(())
		}

		/// Request that `dest` ceases informing us of its version.
		pub fn unrequest_version_notify(dest: MultiLocation) -> XcmResult {
			let versioned_dest = LatestVersionedMultiLocation(&dest);
			let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
				.ok_or(XcmError::InvalidLocation)?;
			T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
			Queries::<T>::remove(query_id);
			Ok(())
		}

Gavin Wood's avatar
Gavin Wood committed
		/// Relay an XCM `message` from a given `interior` location in this context to a given `dest`
		/// location. A null `dest` is not handled.
		pub fn send_xcm(
			interior: Junctions,
			dest: MultiLocation,
			mut message: Xcm<()>,
		) -> Result<(), SendError> {
			if interior != Junctions::Here {
				message.0.insert(0, DescendOrigin(interior))
			log::trace!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
Gavin Wood's avatar
Gavin Wood committed
			T::XcmRouter::send_xcm(dest, message)
		}

		pub fn check_account() -> T::AccountId {
			const ID: PalletId = PalletId(*b"py/xcmch");
			AccountIdConversion::<T::AccountId>::into_account(&ID)
		}
		fn do_new_query(
			responder: MultiLocation,
			maybe_notify: Option<(u8, u8)>,
			timeout: T::BlockNumber,
		) -> u64 {
			QueryCounter::<T>::mutate(|q| {
				q.saturating_inc();
				Queries::<T>::insert(
					r,
					QueryStatus::Pending { responder: responder.into(), maybe_notify, timeout },
				);
				r
			})
		}

		/// Consume `message` and return another which is equivalent to it except that it reports
		/// back the outcome.
		///
		/// - `message`: The message whose outcome should be reported.
		/// - `responder`: The origin from which a response should be expected.
		/// - `timeout`: The block number after which it is permissible for `notify` not to be
		///   called even if a response is received.
		///
		/// `report_outcome` may return an error if the `responder` is not invertible.
		///
		/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
		/// value.
		pub fn report_outcome(
			message: &mut Xcm<()>,
			responder: MultiLocation,
			timeout: T::BlockNumber,
		) -> Result<QueryId, XcmError> {
			let dest = T::LocationInverter::invert_location(&responder)
				.map_err(|()| XcmError::MultiLocationNotInvertible)?;
			let query_id = Self::new_query(responder, timeout);
			let report_error = Xcm(vec![ReportError { dest, query_id, max_response_weight: 0 }]);
			message.0.insert(0, SetAppendix(report_error));
		}

		/// Consume `message` and return another which is equivalent to it except that it reports
		/// back the outcome and dispatches `notify` on this chain.
		///
		/// - `message`: The message whose outcome should be reported.
		/// - `responder`: The origin from which a response should be expected.
		/// - `notify`: A dispatchable function which will be called once the outcome of `message`
		///   is known. It may be a dispatchable in any pallet of the local chain, but other than
		///   the usual origin, it must accept exactly two arguments: `query_id: QueryId` and
		///   `outcome: Response`, and in that order. It should expect that the origin is
		///   `Origin::Response` and will contain the responder's location.
		/// - `timeout`: The block number after which it is permissible for `notify` not to be
		///   called even if a response is received.
		///
		/// `report_outcome_notify` may return an error if the `responder` is not invertible.
		///
		/// NOTE: `notify` gets called as part of handling an incoming message, so it should be
		/// lightweight. Its weight is estimated during this function and stored ready for
		/// weighing `ReportOutcome` on the way back. If it turns out to be heavier once it returns
		/// then reporting the outcome will fail. Futhermore if the estimate is too high, then it
		/// may be put in the overweight queue and need to be manually executed.
		pub fn report_outcome_notify(
			message: &mut Xcm<()>,
			responder: MultiLocation,
			notify: impl Into<<T as Config>::Call>,
			timeout: T::BlockNumber,
		) -> Result<(), XcmError> {
			let dest = T::LocationInverter::invert_location(&responder)
				.map_err(|()| XcmError::MultiLocationNotInvertible)?;
			let notify: <T as Config>::Call = notify.into();
			let max_response_weight = notify.get_dispatch_info().weight;
			let query_id = Self::new_notify_query(responder, notify, timeout);
			let report_error = Xcm(vec![ReportError { dest, query_id, max_response_weight }]);
			message.0.insert(0, SetAppendix(report_error));
		}

		/// Attempt to create a new query ID and register it as a query that is yet to respond.
		pub fn new_query(responder: MultiLocation, timeout: T::BlockNumber) -> u64 {
			Self::do_new_query(responder, None, timeout)
		}

		/// Attempt to create a new query ID and register it as a query that is yet to respond, and