lib.rs 97.7 KiB
Newer Older
		/// execution attempt will be made.
		/// NOTE: A successful return to this does *not* imply that the `msg` was executed
		/// successfully to completion; only that it was attempted.
		#[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 Config>::RuntimeCall>>,
Gavin Wood's avatar
Gavin Wood committed
			max_weight: Weight,
		) -> DispatchResultWithPostInfo {
			log::trace!(target: "xcm::pallet_xcm::execute", "message {:?}, max_weight {:?}", message, max_weight);
			let outcome = <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
			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
			})?;
			Ok(Some(weight_used.saturating_add(T::WeightInfo::execute())).into())

		/// 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>,
			version: XcmVersion,
		) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			let location = *location;
			SupportedVersion::<T>::insert(
				XCM_VERSION,
				LatestVersionedMultiLocation(&location),
			Self::deposit_event(Event::SupportedVersionChanged { location, 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()
			})
		}
		/// 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`, up to enough to pay for `weight_limit` of weight. If more weight
		/// is needed than `weight_limit`, then the operation will fail and the assets send may be
		/// at risk.
		///
		/// - `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.
		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
		#[pallet::call_index(8)]
		#[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;
					// heaviest version of locally executed XCM program: equivalent in weight to
					// transfer assets to SA, reanchor them, extend XCM program, and send onward XCM
					let mut message = Xcm(vec![
						SetFeesMode { jit_withdraw: true },
						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 limited_reserve_transfer_assets(
			origin: OriginFor<T>,
			dest: Box<VersionedMultiLocation>,
			beneficiary: Box<VersionedMultiLocation>,
			assets: Box<VersionedMultiAssets>,
			fee_asset_item: u32,
			weight_limit: WeightLimit,
		) -> DispatchResult {
			Self::do_reserve_transfer_assets(
				origin,
				dest,
				beneficiary,
				assets,
				fee_asset_item,
				weight_limit,
			)
		}

		/// 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`, up to enough to pay for `weight_limit` of weight. If more weight
		/// is needed than `weight_limit`, then the operation will fail and the assets send may be
		/// at risk.
		///
		/// - `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. 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.
		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
		#[pallet::call_index(9)]
		#[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 count = assets.len() as u32;
					let mut message = Xcm(vec![
						WithdrawAsset(assets),
						SetFeesMode { jit_withdraw: true },
						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 limited_teleport_assets(
			origin: OriginFor<T>,
			dest: Box<VersionedMultiLocation>,
			beneficiary: Box<VersionedMultiLocation>,
			assets: Box<VersionedMultiAssets>,
			fee_asset_item: u32,
			weight_limit: WeightLimit,
		) -> DispatchResult {
			Self::do_teleport_assets(
				origin,
				dest,
				beneficiary,
				assets,
				fee_asset_item,
				weight_limit,

		/// Set or unset the global suspension state of the XCM executor.
		///
		/// - `origin`: Must be an origin specified by AdminOrigin.
		/// - `suspended`: `true` to suspend, `false` to resume.
		#[pallet::call_index(10)]
		#[pallet::weight(T::WeightInfo::force_suspension())]
		pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			XcmExecutionSuspended::<T>::set(suspended);
			Ok(())
		}
Gavin Wood's avatar
Gavin Wood committed
}
Gavin Wood's avatar
Gavin Wood committed
/// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
const MAX_ASSETS_FOR_TRANSFER: usize = 2;

impl<T: Config> QueryHandler for Pallet<T> {
	type QueryId = u64;
	type BlockNumber = BlockNumberFor<T>;
	type Error = XcmError;
	type UniversalLocation = T::UniversalLocation;

	/// Attempt to create a new query ID and register it as a query that is yet to respond.
	fn new_query(
		responder: impl Into<MultiLocation>,
		match_querier: impl Into<MultiLocation>,
	) -> Self::QueryId {
		Self::do_new_query(responder, None, timeout, match_querier)
	}

	/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
	/// value.
	fn report_outcome(
		message: &mut Xcm<()>,
		responder: impl Into<MultiLocation>,
		timeout: Self::BlockNumber,
	) -> Result<Self::QueryId, Self::Error> {
		let responder = responder.into();
		let destination = Self::UniversalLocation::get()
			.invert_target(&responder)
			.map_err(|()| XcmError::LocationNotInvertible)?;
		let query_id = Self::new_query(responder, timeout, Here);
		let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
		let report_error = Xcm(vec![ReportError(response_info)]);
		message.0.insert(0, SetAppendix(report_error));
		Ok(query_id)
	}

	/// Removes response when ready and emits [Event::ResponseTaken] event.
	fn take_response(query_id: Self::QueryId) -> QueryResponseStatus<Self::BlockNumber> {
		match Queries::<T>::get(query_id) {
			Some(QueryStatus::Ready { response, at }) => match response.try_into() {
				Ok(response) => {
					Queries::<T>::remove(query_id);
					Self::deposit_event(Event::ResponseTaken { query_id });
					QueryResponseStatus::Ready { response, at }
				},
				Err(_) => QueryResponseStatus::UnexpectedVersion,
			},
			Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
			Some(_) => QueryResponseStatus::UnexpectedVersion,
			None => QueryResponseStatus::NotFound,
		}
	}

	#[cfg(feature = "runtime-benchmarks")]
	fn expect_response(id: Self::QueryId, response: Response) {
		let response = response.into();
		Queries::<T>::insert(
			id,
			QueryStatus::Ready { response, at: frame_system::Pallet::<T>::block_number() },
		);
	}
}

Gavin Wood's avatar
Gavin Wood committed
impl<T: Config> Pallet<T> {
	/// Validate `assets` to be reserve-transferred and return their reserve location.
	fn validate_assets_and_find_reserve(
		assets: &[MultiAsset],
		dest: &MultiLocation,
	) -> Result<TransferType, Error<T>> {
		let mut reserve = None;
		for asset in assets.iter() {
			if let Fungible(x) = asset.fun {
				// If fungible asset, ensure non-zero amount.
				ensure!(!x.is_zero(), Error::<T>::Empty);
			}
			let transfer_type =
				T::XcmExecutor::determine_for(&asset, dest).map_err(Error::<T>::from)?;
			// Ensure asset is not teleportable to `dest`.
			ensure!(transfer_type != TransferType::Teleport, Error::<T>::Filtered);
			if let Some(reserve) = reserve.as_ref() {
				// Ensure transfer for multiple assets uses same reserve location (only fee may have
				// different reserve location)
				ensure!(reserve == &transfer_type, Error::<T>::TooManyReserves);
			} else {
				// asset reserve identified
				reserve = Some(transfer_type);
			}
		}
		reserve.ok_or(Error::<T>::Empty)
	}

Gavin Wood's avatar
Gavin Wood committed
	fn do_reserve_transfer_assets(
		origin: OriginFor<T>,
		dest: Box<VersionedMultiLocation>,
		beneficiary: Box<VersionedMultiLocation>,
		assets: Box<VersionedMultiAssets>,
		fee_asset_item: u32,
		weight_limit: WeightLimit,
Gavin Wood's avatar
Gavin Wood committed
	) -> DispatchResult {
		let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
		let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
		let beneficiary: MultiLocation =
			(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
		let assets: MultiAssets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
		log::trace!(
			target: "xcm::pallet_xcm::do_reserve_transfer_assets",
			"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}",
			origin_location, dest, beneficiary, assets, fee_asset_item,
		);
Gavin Wood's avatar
Gavin Wood committed

		ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
		let value = (origin_location, assets.into_inner());
		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
		let (origin_location, mut assets) = value;

		if fee_asset_item as usize >= assets.len() {
			return Err(Error::<T>::Empty.into())
		}
		let fees = assets.swap_remove(fee_asset_item as usize);
		let fees_transfer_type =
			T::XcmExecutor::determine_for(&fees, &dest).map_err(Error::<T>::from)?;
		let assets_transfer_type = if assets.is_empty() {
			// Single asset to transfer (one used for fees where transfer type is determined above).
			ensure!(fees_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
			fees_transfer_type
		} else {
			// Find reserve for non-fee assets.
			Self::validate_assets_and_find_reserve(&assets, &dest)?
		};

		// local and remote XCM programs to potentially handle fees separately
		let separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>;
		if fees_transfer_type == assets_transfer_type {
			// Same reserve location (fees not teleportable), we can batch together fees and assets
			// in same reserve-based-transfer.
			assets.push(fees.clone());
			// no need for custom fees instructions, fees are batched with assets
			separate_fees_instructions = None;
		} else {
			// Disallow _remote reserves_ unless assets & fees have same remote reserve (covered by
			// branch above). The reason for this is that we'd need to send XCMs to separate chains
			// with no guarantee of delivery order on final destination; therefore we cannot
			// guarantee to have fees in place on final destination chain to pay for assets
			// transfer.
			ensure!(
				!matches!(assets_transfer_type, TransferType::RemoteReserve(_)),
				Error::<T>::InvalidAssetUnsupportedReserve
			);
			let fees = fees.clone();
			let weight_limit = weight_limit.clone();
			// build fees transfer instructions to be added to assets transfers XCM programs
			separate_fees_instructions = Some(match fees_transfer_type {
				TransferType::LocalReserve =>
					Self::local_reserve_fees_instructions(dest, fees, weight_limit)?,
				TransferType::DestinationReserve =>
					Self::destination_reserve_fees_instructions(dest, fees, weight_limit)?,
				TransferType::Teleport =>
					Self::teleport_fees_instructions(origin_location, dest, fees, weight_limit)?,
				TransferType::RemoteReserve(_) =>
					return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
			});
		};

		Self::build_and_execute_xcm_transfer_type(
			origin_location,
			dest,
			beneficiary,
			assets,
			assets_transfer_type,
			fees,
			separate_fees_instructions,
			weight_limit,
		)
Gavin Wood's avatar
Gavin Wood committed
	}
Gavin Wood's avatar
Gavin Wood committed
	fn do_teleport_assets(
		origin: OriginFor<T>,
		dest: Box<VersionedMultiLocation>,
		beneficiary: Box<VersionedMultiLocation>,
		assets: Box<VersionedMultiAssets>,
		fee_asset_item: u32,
		weight_limit: WeightLimit,
Gavin Wood's avatar
Gavin Wood committed
	) -> DispatchResult {
		let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
		let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
		let beneficiary: MultiLocation =
			(*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);
		let value = (origin_location, assets.into_inner());
		ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);
		let (origin_location, assets) = value;
		for asset in assets.iter() {
			let transfer_type =
				T::XcmExecutor::determine_for(asset, &dest).map_err(Error::<T>::from)?;
			ensure!(matches!(transfer_type, TransferType::Teleport), Error::<T>::Filtered);
		}
		let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();

		Self::build_and_execute_xcm_transfer_type(
			origin_location,
			dest,
			beneficiary,
			assets,
			TransferType::Teleport,
			fees,
			None,
			weight_limit,
		)
	}

	fn build_and_execute_xcm_transfer_type(
		origin: MultiLocation,
		dest: MultiLocation,
		beneficiary: MultiLocation,
		assets: Vec<MultiAsset>,
		transfer_type: TransferType,
		fees: MultiAsset,
		separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
		weight_limit: WeightLimit,
	) -> DispatchResult {
		log::trace!(
			target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
			"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
			fees {:?}, fees_xcm: {:?}, weight_limit: {:?}",
			origin, dest, beneficiary, assets, transfer_type, fees, separate_fees_instructions, weight_limit,
		);
		let (mut local_xcm, remote_xcm) = match transfer_type {
			TransferType::LocalReserve => {
				let (local, remote) = Self::local_reserve_transfer_programs(
					dest,
					beneficiary,
					assets,
					fees,
					separate_fees_instructions,
					weight_limit,
				)?;
				(local, Some(remote))
			},
			TransferType::DestinationReserve => {
				let (local, remote) = Self::destination_reserve_transfer_programs(
					dest,
					beneficiary,
					assets,
					fees,
					separate_fees_instructions,
					weight_limit,
				)?;
				(local, Some(remote))
			},
			TransferType::RemoteReserve(reserve) => (
				Self::remote_reserve_transfer_program(
					reserve,
					dest,
					beneficiary,
					assets,
					fees,
					weight_limit,
				)?,
				None,
			),
			TransferType::Teleport => (
				Self::teleport_assets_program(dest, beneficiary, assets, fees, weight_limit)?,
				None,
			),
		};
		let weight =
			T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
		let hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
		let outcome =
			T::XcmExecutor::execute_xcm_in_credit(origin, local_xcm, hash, weight, weight);
		Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
		outcome.ensure_complete().map_err(|error| {
			log::error!(
				target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
				"XCM execution failed with error {:?}", error
			);
			Error::<T>::LocalExecutionIncomplete
		})?;
			let (ticket, price) = validate_send::<T::XcmRouter>(dest, remote_xcm.clone())
				.map_err(Error::<T>::from)?;
			if origin != Here.into_location() {
				Self::charge_fees(origin, price).map_err(|error| {
					log::error!(
						target: "xcm::pallet_xcm::build_and_execute_xcm_transfer_type",
						"Unable to charge fee with error {:?}", error
					);
					Error::<T>::FeesNotMet
				})?;
			}
			let message_id = T::XcmRouter::deliver(ticket).map_err(Error::<T>::from)?;

			let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id };
			Self::deposit_event(e);
		}
		Ok(())
	}

	fn local_reserve_fees_instructions(
		dest: MultiLocation,
		fees: MultiAsset,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
Gavin Wood's avatar
Gavin Wood committed
		let context = T::UniversalLocation::get();
Gavin Wood's avatar
Gavin Wood committed
			.clone()
			.reanchored(&dest, context)
			.map_err(|_| Error::<T>::CannotReanchor)?;

		let local_execute_xcm = Xcm(vec![
			// move `fees` to `dest`s local sovereign account
			TransferAsset { assets: fees.into(), beneficiary: dest },
		]);
		let xcm_on_dest = Xcm(vec![
			// let (dest) chain know `fees` are in its SA on reserve
			ReserveAssetDeposited(reanchored_fees.clone().into()),
			// buy exec using `fees` in holding deposited in above instruction
			BuyExecution { fees: reanchored_fees, weight_limit },
		]);
		Ok((local_execute_xcm, xcm_on_dest))
	}

	fn local_reserve_transfer_programs(
		dest: MultiLocation,
		beneficiary: MultiLocation,
		assets: Vec<MultiAsset>,
		fees: MultiAsset,
		separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
		// max assets is `assets` (+ potentially separately handled fee)
		let max_assets =
			assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0);
Gavin Wood's avatar
Gavin Wood committed
		let assets: MultiAssets = assets.into();
		let context = T::UniversalLocation::get();
		let mut reanchored_assets = assets.clone();
		reanchored_assets
			.reanchor(&dest, context)
			.map_err(|_| Error::<T>::CannotReanchor)?;

		// fees are either handled through dedicated instructions, or batched together with assets
		let fees_already_handled = separate_fees_instructions.is_some();
		let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions
			.map(|(local, remote)| (local.into_inner(), remote.into_inner()))
			.unwrap_or_default();

		// start off with any necessary local fees specific instructions
		let mut local_execute_xcm = fees_local_xcm;
		// move `assets` to `dest`s local sovereign account
		local_execute_xcm.push(TransferAsset { assets, beneficiary: dest });

		// on destination chain, start off with custom fee instructions
		let mut xcm_on_dest = fees_remote_xcm;
		// continue with rest of assets
		xcm_on_dest.extend_from_slice(&[
			// let (dest) chain know assets are in its SA on reserve
			ReserveAssetDeposited(reanchored_assets),
			// following instructions are not exec'ed on behalf of origin chain anymore
			ClearOrigin,
		]);
		if !fees_already_handled {
			// no custom fees instructions, they are batched together with `assets` transfer;
			// BuyExecution happens after receiving all `assets`
			let reanchored_fees =
				fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
			// buy execution using `fees` batched together with above `reanchored_assets`
			xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit });
		}
		// deposit all remaining assets in holding to `beneficiary` location
		xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });

		Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest)))
	}

	fn destination_reserve_fees_instructions(
		dest: MultiLocation,
		fees: MultiAsset,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
		let context = T::UniversalLocation::get();
		let reanchored_fees = fees
			.clone()
			.reanchored(&dest, context)
			.map_err(|_| Error::<T>::CannotReanchor)?;
		let fees: MultiAssets = fees.into();

		let local_execute_xcm = Xcm(vec![
			// withdraw reserve-based fees (derivatives)
			WithdrawAsset(fees.clone()),
			// burn derivatives
			BurnAsset(fees),
		]);
		let xcm_on_dest = Xcm(vec![
			// withdraw `fees` from origin chain's sovereign account
			WithdrawAsset(reanchored_fees.clone().into()),
			// buy exec using `fees` in holding withdrawn in above instruction
			BuyExecution { fees: reanchored_fees, weight_limit },
		]);
		Ok((local_execute_xcm, xcm_on_dest))
	}

	fn destination_reserve_transfer_programs(
		dest: MultiLocation,
		beneficiary: MultiLocation,
		assets: Vec<MultiAsset>,
		fees: MultiAsset,
		separate_fees_instructions: Option<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>)>,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
		// max assets is `assets` (+ potentially separately handled fee)
		let max_assets =
			assets.len() as u32 + separate_fees_instructions.as_ref().map(|_| 1).unwrap_or(0);
		let assets: MultiAssets = assets.into();
		let context = T::UniversalLocation::get();
		let mut reanchored_assets = assets.clone();
		reanchored_assets
			.reanchor(&dest, context)
			.map_err(|_| Error::<T>::CannotReanchor)?;

		// fees are either handled through dedicated instructions, or batched together with assets
		let fees_already_handled = separate_fees_instructions.is_some();
		let (fees_local_xcm, fees_remote_xcm) = separate_fees_instructions
			.map(|(local, remote)| (local.into_inner(), remote.into_inner()))
			.unwrap_or_default();

		// start off with any necessary local fees specific instructions
		let mut local_execute_xcm = fees_local_xcm;
		// continue with rest of assets
		local_execute_xcm.extend_from_slice(&[
			// withdraw reserve-based assets
			WithdrawAsset(assets.clone()),
			// burn reserve-based assets
			BurnAsset(assets),
		]);

		// on destination chain, start off with custom fee instructions
		let mut xcm_on_dest = fees_remote_xcm;
		// continue with rest of assets
		xcm_on_dest.extend_from_slice(&[
			// withdraw `assets` from origin chain's sovereign account
			WithdrawAsset(reanchored_assets),
			// following instructions are not exec'ed on behalf of origin chain anymore
			ClearOrigin,
		]);
		if !fees_already_handled {
			// no custom fees instructions, they are batched together with `assets` transfer;
			// BuyExecution happens after receiving all `assets`
			let reanchored_fees =
				fees.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
			// buy execution using `fees` batched together with above `reanchored_assets`
			xcm_on_dest.push(BuyExecution { fees: reanchored_fees, weight_limit });
		}
		// deposit all remaining assets in holding to `beneficiary` location
		xcm_on_dest.push(DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary });

		Ok((Xcm(local_execute_xcm), Xcm(xcm_on_dest)))
	}

	// function assumes fees and assets have the same remote reserve
	fn remote_reserve_transfer_program(
		reserve: MultiLocation,
		dest: MultiLocation,
		beneficiary: MultiLocation,
		assets: Vec<MultiAsset>,
		fees: MultiAsset,
		weight_limit: WeightLimit,
	) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
		let max_assets = assets.len() as u32;
		let context = T::UniversalLocation::get();
		// we spend up to half of fees for execution on reserve and other half for execution on
		// destination
		let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
		// identifies fee item as seen by `reserve` - to be used at reserve chain
		let reserve_fees = fees_half_1
			.reanchored(&reserve, context)
			.map_err(|_| Error::<T>::CannotReanchor)?;
		// identifies fee item as seen by `dest` - to be used at destination chain
		let dest_fees =
			fees_half_2.reanchored(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
		// identifies `dest` as seen by `reserve`
		let dest = dest.reanchored(&reserve, context).map_err(|_| Error::<T>::CannotReanchor)?;
		// xcm to be executed at dest
		let xcm_on_dest = Xcm(vec![
			BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() },
			DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
		]);
		// xcm to be executed on reserve
		let xcm_on_reserve = Xcm(vec![
			BuyExecution { fees: reserve_fees, weight_limit },
			DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
		]);
		Ok(Xcm(vec![
			WithdrawAsset(assets.into()),
			InitiateReserveWithdraw {
				assets: Wild(AllCounted(max_assets)),
				reserve,
				xcm: xcm_on_reserve,
			},
		]))
	}

	fn teleport_fees_instructions(
		dest: MultiLocation,
		fees: MultiAsset,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
		let value = (origin, vec![fees.clone()]);
		ensure!(T::XcmTeleportFilter::contains(&value), Error::<T>::Filtered);

		let context = T::UniversalLocation::get();
		let reanchored_fees = fees
			.clone()
			.reanchored(&dest, context)
			.map_err(|_| Error::<T>::CannotReanchor)?;

		// XcmContext irrelevant in teleports checks
		let dummy_context =
			XcmContext { origin: None, message_id: Default::default(), topic: None };
		// We should check that the asset can actually be teleported out (for this to
		// be in error, there would need to be an accounting violation by ourselves,
		// so it's unlikely, but we don't want to allow that kind of bug to leak into
		// a trusted chain.
		<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::can_check_out(
			&dest,
			&fees,
			&dummy_context,
		)
		.map_err(|_| Error::<T>::CannotCheckOutTeleport)?;
		<T::XcmExecutor as XcmAssetTransfers>::AssetTransactor::check_out(
			&dest,
			&fees,
			&dummy_context,
		);

		let fees: MultiAssets = fees.into();
		let local_execute_xcm = Xcm(vec![
			// withdraw fees
			WithdrawAsset(fees.clone()),
			// burn fees
			BurnAsset(fees),
		]);
		let xcm_on_dest = Xcm(vec![
			// (dest) chain receive teleported assets burned on origin chain
			ReceiveTeleportedAsset(reanchored_fees.clone().into()),
			// buy exec using `fees` in holding received in above instruction
			BuyExecution { fees: reanchored_fees, weight_limit },
		]);
		Ok((local_execute_xcm, xcm_on_dest))
	}

	fn teleport_assets_program(
		dest: MultiLocation,
		beneficiary: MultiLocation,
		assets: Vec<MultiAsset>,
		mut fees: MultiAsset,
		weight_limit: WeightLimit,
	) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
		let context = T::UniversalLocation::get();
		fees.reanchor(&dest, context).map_err(|_| Error::<T>::CannotReanchor)?;
		let max_assets = assets.len() as u32;
		let xcm_on_dest = Xcm(vec![
Gavin Wood's avatar
Gavin Wood committed
			BuyExecution { fees, weight_limit },
			DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
		]);
			SetFeesMode { jit_withdraw: true },
			InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
		]))
	}

	/// Halve `fees` fungible amount.
	pub(crate) fn halve_fees(fees: MultiAsset) -> Result<(MultiAsset, MultiAsset), Error<T>> {
		match fees.fun {
			Fungible(amount) => {
				let fee1 = amount.saturating_div(2);
				let fee2 = amount.saturating_sub(fee1);
				ensure!(fee1 > 0, Error::<T>::FeesNotMet);
				ensure!(fee2 > 0, Error::<T>::FeesNotMet);
				Ok((MultiAsset::from((fees.id, fee1)), MultiAsset::from((fees.id, fee2))))
			},
			NonFungible(_) => Err(Error::<T>::FeesNotMet),
		}
Gavin Wood's avatar
Gavin Wood committed
	}
Gavin Wood's avatar
Gavin Wood committed
	/// 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 = Weight::zero();

		let sv_migrate_weight = T::WeightInfo::migrate_supported_version();
		let vn_migrate_weight = T::WeightInfo::migrate_version_notifiers();
		let vnt_already_notified_weight = T::WeightInfo::already_notified_target();
		let vnt_notify_weight = T::WeightInfo::notify_current_targets();
		let vnt_migrate_weight = T::WeightInfo::migrate_version_notify_targets();
		let vnt_migrate_fail_weight = T::WeightInfo::notify_target_migration_fail();
		let vnt_notify_migrate_weight = T::WeightInfo::migrate_and_notify_old_targets();

		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(sv_migrate_weight);
					if weight_used.any_gte(weight_cutoff) {
						return (weight_used, Some(stage))
Gavin Wood's avatar
Gavin Wood committed
			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(vn_migrate_weight);
					if weight_used.any_gte(weight_cutoff) {
						return (weight_used, Some(stage))
Gavin Wood's avatar
Gavin Wood committed
			stage = NotifyCurrentTargets(None);
		}
Gavin Wood's avatar
Gavin Wood committed
		let xcm_version = T::AdvertisedXcmVersion::get();
Gavin Wood's avatar
Gavin Wood committed
		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(vnt_already_notified_weight);
						continue
					},
Gavin Wood's avatar
Gavin Wood committed
				let response = Response::Version(xcm_version);
				let message =
					Xcm(vec![QueryResponse { query_id, response, max_weight, querier: None }]);
				let event = match send_xcm::<T::XcmRouter>(new_key, message) {
					Ok((message_id, cost)) => {
Gavin Wood's avatar
Gavin Wood committed
						let value = (query_id, max_weight, xcm_version);
						VersionNotifyTargets::<T>::insert(XCM_VERSION, key, value);
						Event::VersionChangeNotified {
							destination: new_key,
							result: xcm_version,
							cost,
							message_id,
						}
Gavin Wood's avatar
Gavin Wood committed
					},
					Err(e) => {
						VersionNotifyTargets::<T>::remove(XCM_VERSION, key);
						Event::NotifyTargetSendFail { location: new_key, query_id, error: e.into() }
Gavin Wood's avatar
Gavin Wood committed
					},
				};
				Self::deposit_event(event);
				weight_used.saturating_accrue(vnt_notify_weight);
				if weight_used.any_gte(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;
Gavin Wood's avatar
Gavin Wood committed
					let new_key = match MultiLocation::try_from(old_key.clone()) {
						Ok(k) => k,
						Err(()) => {
							Self::deposit_event(Event::NotifyTargetMigrationFail {
								location: old_key,
								query_id: value.0,
							});
Gavin Wood's avatar
Gavin Wood committed
							weight_used.saturating_accrue(vnt_migrate_fail_weight);
							if weight_used.any_gte(weight_cutoff) {
								return (weight_used, Some(stage))
							}
Gavin Wood's avatar
Gavin Wood committed

					let versioned_key = LatestVersionedMultiLocation(&new_key);
					if target_xcm_version == xcm_version {
						VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_key, value);
						weight_used.saturating_accrue(vnt_migrate_weight);
					} else {
						// Need to notify target.
						let response = Response::Version(xcm_version);
						let message = Xcm(vec![QueryResponse {
							query_id,
							response,
							max_weight,
							querier: None,
						}]);
						let event = match send_xcm::<T::XcmRouter>(new_key, message) {
							Ok((message_id, cost)) => {
Gavin Wood's avatar
Gavin Wood committed
								VersionNotifyTargets::<T>::insert(
									XCM_VERSION,
									versioned_key,
									(query_id, max_weight, xcm_version),
								);
								Event::VersionChangeNotified {
									destination: new_key,
									result: xcm_version,
									cost,
									message_id,
								}
							},
							Err(e) => Event::NotifyTargetSendFail {
								location: new_key,
								query_id,
								error: e.into(),
Gavin Wood's avatar
Gavin Wood committed
						Self::deposit_event(event);
						weight_used.saturating_accrue(vnt_notify_migrate_weight);
					}
					if weight_used.any_gte(weight_cutoff) {
						return (weight_used, Some(stage))
Gavin Wood's avatar
Gavin Wood committed
		(weight_used, None)
	}
Gavin Wood's avatar
Gavin Wood committed
	/// Request that `dest` informs us of its version.
	pub fn request_version_notify(dest: impl Into<MultiLocation>) -> XcmResult {
		let dest = dest.into();
		let versioned_dest = VersionedMultiLocation::from(dest);
		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: Weight::zero() };
		let (message_id, cost) = send_xcm::<T::XcmRouter>(dest, Xcm(vec![instruction]))?;
		Self::deposit_event(Event::VersionNotifyRequested { destination: dest, cost, message_id });
Gavin Wood's avatar
Gavin Wood committed
		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(())
	}