lib.rs 116 KiB
Newer Older
Gavin Wood's avatar
Gavin Wood committed
		///
		/// 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.
		#[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 {
			let weight_used =
				<Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
			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)]
		pub fn force_xcm_version(
			origin: OriginFor<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
			location: Box<Location>,
			version: XcmVersion,
		) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			let location = *location;
Francisco Aguirre's avatar
Francisco Aguirre committed
			SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&location), version);
			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)]
		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)]
		pub fn force_subscribe_version_notify(
			origin: OriginFor<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
			location: Box<VersionedLocation>,
		) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
			let location: 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 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)]
		pub fn force_unsubscribe_version_notify(
			origin: OriginFor<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
			location: Box<VersionedLocation>,
		) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
			let location: 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()
			})
		}
		/// Transfer some assets from the local chain to the destination chain through their local,
		/// destination or remote reserve.
		///
		/// `assets` must have same reserve location and may not be teleportable to `dest`.
		///  - `assets` have local reserve: transfer assets to sovereign account of destination
		///    chain and forward a notification XCM to `dest` to mint and deposit reserve-based
		///    assets to `beneficiary`.
		///  - `assets` have destination reserve: burn local assets and forward a notification to
		///    `dest` chain to withdraw the reserve assets from this chain's sovereign account and
		///    deposit them to `beneficiary`.
		///  - `assets` have remote reserve: burn local assets, forward XCM to reserve chain to move
		///    reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest`
		///    to mint and deposit reserve-based assets to `beneficiary`.
		/// 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 sent assets may be
		///
		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
Francisco Aguirre's avatar
Francisco Aguirre committed
		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
		///   Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from
		///   relay to parachain.
		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
		///   generally be an `AccountId32` value.
		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
		///   fee on the `dest` (and possibly reserve) chains.
		/// - `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(T::WeightInfo::reserve_transfer_assets())]
		pub fn limited_reserve_transfer_assets(
			origin: OriginFor<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
			dest: Box<VersionedLocation>,
			beneficiary: Box<VersionedLocation>,
			assets: Box<VersionedAssets>,
			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 sent assets may be
		///
		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
Francisco Aguirre's avatar
Francisco Aguirre committed
		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
		///   Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from
		///   relay to parachain.
		/// - `beneficiary`: A beneficiary location for the assets in the context of `dest`. Will
		///   generally be an `AccountId32` value.
		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
		///   fee on the `dest` chain.
		/// - `fee_asset_item`: The index into `assets` of the item which should be used to pay
		///   fees.
		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
		#[pallet::call_index(9)]
		#[pallet::weight(T::WeightInfo::teleport_assets())]
		pub fn limited_teleport_assets(
			origin: OriginFor<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
			dest: Box<VersionedLocation>,
			beneficiary: Box<VersionedLocation>,
			assets: Box<VersionedAssets>,
			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)]
		pub fn force_suspension(origin: OriginFor<T>, suspended: bool) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			XcmExecutionSuspended::<T>::set(suspended);
			Ok(())
		}

		/// Transfer some assets from the local chain to the destination chain through their local,
		/// destination or remote reserve, or through teleports.
		///
		/// Fee payment on the destination side is made from the asset in the `assets` vector of
		/// index `fee_asset_item` (hence referred to as `fees`), 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 sent assets may be at risk.
		///
		/// `assets` (excluding `fees`) must have same reserve location or otherwise be teleportable
		/// to `dest`, no limitations imposed on `fees`.
		///  - for local reserve: transfer assets to sovereign account of destination chain and
		///    forward a notification XCM to `dest` to mint and deposit reserve-based assets to
		///    `beneficiary`.
		///  - for destination reserve: burn local assets and forward a notification to `dest` chain
		///    to withdraw the reserve assets from this chain's sovereign account and deposit them
		///    to `beneficiary`.
		///  - for remote reserve: burn local assets, forward XCM to reserve chain to move reserves
		///    from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` to mint
		///    and deposit reserve-based assets to `beneficiary`.
		///  - for teleports: burn local assets and forward XCM to `dest` chain to mint/teleport
		///    assets and deposit them to `beneficiary`.
		///
		/// - `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` (and possibly reserve) chains.
		/// - `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(11)]
		pub fn transfer_assets(
			origin: OriginFor<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
			dest: Box<VersionedLocation>,
			beneficiary: Box<VersionedLocation>,
			assets: Box<VersionedAssets>,
			fee_asset_item: u32,
			weight_limit: WeightLimit,
		) -> DispatchResult {
			let origin = T::ExecuteXcmOrigin::ensure_origin(origin)?;
			let dest = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
			let beneficiary: Location =
				(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
			log::debug!(
				target: "xcm::pallet_xcm::transfer_assets",
				"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}",
				origin, dest, beneficiary, assets, fee_asset_item, weight_limit,
			);

			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);
			let fee_asset_item = fee_asset_item as usize;
			// Find transfer types for fee and non-fee assets.
			let (fees_transfer_type, assets_transfer_type) =
				Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;


		/// Claims assets trapped on this pallet because of leftover assets during XCM execution.
		///
		/// - `origin`: Anyone can call this extrinsic.
		/// - `assets`: The exact assets that were trapped. Use the version to specify what version
		/// was the latest when they were trapped.
		/// - `beneficiary`: The location/account where the claimed assets will be deposited.
		#[pallet::call_index(12)]
		pub fn claim_assets(
			origin: OriginFor<T>,
			assets: Box<VersionedAssets>,
			beneficiary: Box<VersionedLocation>,
		) -> DispatchResult {
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
			log::debug!(target: "xcm::pallet_xcm::claim_assets", "origin: {:?}, assets: {:?}, beneficiary: {:?}", origin_location, assets, beneficiary);
			// Extract version from `assets`.
			let assets_version = assets.identify_version();
			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let number_of_assets = assets.len() as u32;
			let beneficiary: Location =
				(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let ticket: Location = GeneralIndex(assets_version as u128).into();
			let mut message = Xcm(vec![
				ClaimAsset { assets, ticket },
				DepositAsset { assets: AllCounted(number_of_assets).into(), beneficiary },
			]);
			let weight =
				T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
			let mut hash = message.using_encoded(sp_io::hashing::blake2_256);
			let outcome = T::XcmExecutor::prepare_and_execute(
				origin_location,
				message,
				&mut hash,
				weight,
				weight,
			);
			outcome.ensure_complete().map_err(|error| {
				log::error!(target: "xcm::pallet_xcm::claim_assets", "XCM execution failed with error: {:?}", error);
				Error::<T>::LocalExecutionIncomplete
			})?;
			Ok(())
		}
		/// Transfer assets from the local chain to the destination chain using explicit transfer
		/// types for assets and fees.
		///
		/// `assets` must have same reserve location or may be teleportable to `dest`. Caller must
		/// provide the `assets_transfer_type` to be used for `assets`:
		///  - `TransferType::LocalReserve`: transfer assets to sovereign account of destination
		///    chain and forward a notification XCM to `dest` to mint and deposit reserve-based
		///    assets to `beneficiary`.
		///  - `TransferType::DestinationReserve`: burn local assets and forward a notification to
		///    `dest` chain to withdraw the reserve assets from this chain's sovereign account and
		///    deposit them to `beneficiary`.
		///  - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve`
		///    chain to move reserves from this chain's SA to `dest` chain's SA, and forward another
		///    XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically
		///    the remote `reserve` is Asset Hub.
		///  - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to
		///    mint/teleport assets and deposit them to `beneficiary`.
		///
		/// On the destination chain, as well as any intermediary hops, `BuyExecution` is used to
		/// buy execution using transferred `assets` identified by `remote_fees_id`.
		/// Make sure enough of the specified `remote_fees_id` asset is included in the given list
		/// of `assets`. `remote_fees_id` should be enough to pay for `weight_limit`. If more weight
		/// is needed than `weight_limit`, then the operation will fail and the sent assets may be
		/// at risk.
		///
		/// `remote_fees_id` may use different transfer type than rest of `assets` and can be
		/// specified through `fees_transfer_type`.
		/// The caller needs to specify what should happen to the transferred assets once they reach
		/// the `dest` chain. This is done through the `custom_xcm_on_dest` parameter, which
		/// contains the instructions to execute on `dest` as a final step.
		///   This is usually as simple as:
		///   `Xcm(vec![DepositAsset { assets: Wild(AllCounted(assets.len())), beneficiary }])`,
		///   but could be something more exotic like sending the `assets` even further.
		///
		/// - `origin`: Must be capable of withdrawing the `assets` and executing XCM.
		/// - `dest`: Destination context for the assets. Will typically be `[Parent,
		///   Parachain(..)]` to send from parachain to parachain, or `[Parachain(..)]` to send from
		///   relay to parachain, or `(parents: 2, (GlobalConsensus(..), ..))` to send from
		///   parachain across a bridge to another ecosystem destination.
		/// - `assets`: The assets to be withdrawn. This should include the assets used to pay the
		///   fee on the `dest` (and possibly reserve) chains.
		/// - `assets_transfer_type`: The XCM `TransferType` used to transfer the `assets`.
		/// - `remote_fees_id`: One of the included `assets` to be be used to pay fees.
		/// - `fees_transfer_type`: The XCM `TransferType` used to transfer the `fees` assets.
		/// - `custom_xcm_on_dest`: The XCM to be executed on `dest` chain as the last step of the
		///   transfer, which also determines what happens to the assets on the destination chain.
		/// - `weight_limit`: The remote-side weight limit, if any, for the XCM fee purchase.
		#[pallet::call_index(13)]
		#[pallet::weight(T::WeightInfo::transfer_assets())]
			origin: OriginFor<T>,
			dest: Box<VersionedLocation>,
			assets: Box<VersionedAssets>,
			assets_transfer_type: Box<TransferType>,
			weight_limit: WeightLimit,
		) -> DispatchResult {
			let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
			let dest: Location = (*dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let fees_id: AssetId =
				(*remote_fees_id).try_into().map_err(|()| Error::<T>::BadVersion)?;
			let remote_xcm: Xcm<()> =
				(*custom_xcm_on_dest).try_into().map_err(|()| Error::<T>::BadVersion)?;
				target: "xcm::pallet_xcm::transfer_assets_using_type_and_then",
				"origin {origin_location:?}, dest {dest:?}, assets {assets:?} through {assets_transfer_type:?}, \
				remote_fees_id {fees_id:?} through {fees_transfer_type:?}, \
				custom_xcm_on_dest {remote_xcm:?}, weight-limit {weight_limit:?}",
			);

			let assets = assets.into_inner();
			ensure!(assets.len() <= MAX_ASSETS_FOR_TRANSFER, Error::<T>::TooManyAssets);

			let fee_asset_index =
				assets.iter().position(|a| a.id == fees_id).ok_or(Error::<T>::FeesNotMet)?;
			Self::do_transfer_assets(
				origin_location,
				dest,
				assets,
				*assets_transfer_type,
				fee_asset_index,
				*fees_transfer_type,
				weight_limit,
			)
		}
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;

/// Specify how assets used for fees are handled during asset transfers.
#[derive(Clone, PartialEq)]
enum FeesHandling<T: Config> {
	/// `fees` asset can be batch-transferred with rest of assets using same XCM instructions.
Francisco Aguirre's avatar
Francisco Aguirre committed
	Batched { fees: Asset },
	/// fees cannot be batched, they are handled separately using XCM programs here.
	Separate { local_xcm: Xcm<<T as Config>::RuntimeCall>, remote_xcm: Xcm<()> },
}

impl<T: Config> sp_std::fmt::Debug for FeesHandling<T> {
	fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result {
		match self {
			Self::Batched { fees } => write!(f, "FeesHandling::Batched({:?})", fees),
			Self::Separate { local_xcm, remote_xcm } => write!(
				f,
				"FeesHandling::Separate(local: {:?}, remote: {:?})",
				local_xcm, remote_xcm
			),
		}
	}
}

impl<T: Config> QueryHandler for Pallet<T> {
	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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		responder: impl Into<Location>,
Francisco Aguirre's avatar
Francisco Aguirre committed
		match_querier: impl Into<Location>,
		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<()>,
Francisco Aguirre's avatar
Francisco Aguirre committed
		responder: impl Into<Location>,
		timeout: Self::BlockNumber,
	) -> Result<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: 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: 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> {
	/// Find `TransferType`s for `assets` and fee identified through `fee_asset_item`, when
	/// transferring to `dest`.
	///
	/// Validate `assets` to all have same `TransferType`.
	fn find_fee_and_assets_transfer_types(
Francisco Aguirre's avatar
Francisco Aguirre committed
		assets: &[Asset],
Francisco Aguirre's avatar
Francisco Aguirre committed
		dest: &Location,
	) -> Result<(TransferType, TransferType), Error<T>> {
		let mut fees_transfer_type = None;
		let mut assets_transfer_type = None;
		for (idx, asset) in assets.iter().enumerate() {
			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)?;
			if idx == fee_asset_item {
				fees_transfer_type = Some(transfer_type);
				if let Some(existing) = assets_transfer_type.as_ref() {
					// Ensure transfer for multiple assets uses same transfer type (only fee may
					// have different transfer type/path)
					ensure!(existing == &transfer_type, Error::<T>::TooManyReserves);
				} else {
					// asset reserve identified
					assets_transfer_type = Some(transfer_type);
				}
		// single asset also marked as fee item
		if assets.len() == 1 {
Francisco Aguirre's avatar
Francisco Aguirre committed
			assets_transfer_type = fees_transfer_type.clone()
		}
		Ok((
			fees_transfer_type.ok_or(Error::<T>::Empty)?,
			assets_transfer_type.ok_or(Error::<T>::Empty)?,
		))
Gavin Wood's avatar
Gavin Wood committed
	fn do_reserve_transfer_assets(
		origin: OriginFor<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
		dest: Box<VersionedLocation>,
		beneficiary: Box<VersionedLocation>,
		assets: Box<VersionedAssets>,
Gavin Wood's avatar
Gavin Wood committed
		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)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
		let beneficiary: Location =
Gavin Wood's avatar
Gavin Wood committed
			(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
		let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
			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 fee_asset_item = fee_asset_item as usize;
		let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();
		// Find transfer types for fee and non-fee assets.
		let (fees_transfer_type, assets_transfer_type) =
			Self::find_fee_and_assets_transfer_types(&assets, fee_asset_item, &dest)?;
		// Ensure assets (and fees according to check below) are not teleportable to `dest`.
		ensure!(assets_transfer_type != TransferType::Teleport, Error::<T>::Filtered);
		// Ensure all assets (including fees) have same reserve location.
		ensure!(assets_transfer_type == fees_transfer_type, Error::<T>::TooManyReserves);
		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
			origin.clone(),
			dest.clone(),
		)?;
		Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
Gavin Wood's avatar
Gavin Wood committed
	}
Gavin Wood's avatar
Gavin Wood committed
	fn do_teleport_assets(
		origin: OriginFor<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
		dest: Box<VersionedLocation>,
		beneficiary: Box<VersionedLocation>,
		assets: Box<VersionedAssets>,
Gavin Wood's avatar
Gavin Wood committed
		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)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
		let beneficiary: Location =
Gavin Wood's avatar
Gavin Wood committed
			(*beneficiary).try_into().map_err(|()| Error::<T>::BadVersion)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
		let assets: Assets = (*assets).try_into().map_err(|()| Error::<T>::BadVersion)?;
		log::debug!(
			target: "xcm::pallet_xcm::do_teleport_assets",
			"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, fee-idx {:?}, weight_limit {:?}",
			origin_location, dest, beneficiary, assets, fee_asset_item, weight_limit,
		);
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::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!(transfer_type == TransferType::Teleport, Error::<T>::Filtered);
		}
		let fees = assets.get(fee_asset_item as usize).ok_or(Error::<T>::Empty)?.clone();

		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
			origin_location.clone(),
			dest.clone(),
		)?;
		Self::execute_xcm_transfer(origin_location, dest, local_xcm, remote_xcm)
	}

	fn do_transfer_assets(
		origin: Location,
		dest: Location,
		mut assets: Vec<Asset>,
		assets_transfer_type: TransferType,
		fee_asset_index: usize,
		fees_transfer_type: TransferType,
		weight_limit: WeightLimit,
	) -> DispatchResult {
		// local and remote XCM programs to potentially handle fees separately
		let fees = if fees_transfer_type == assets_transfer_type {
			let fees = assets.get(fee_asset_index).ok_or(Error::<T>::Empty)?.clone();
			// no need for custom fees instructions, fees are batched with assets
			FeesHandling::Batched { fees }
		} 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 weight_limit = weight_limit.clone();
			// remove `fees` from `assets` and build separate fees transfer instructions to be
			// added to assets transfers XCM programs
			let fees = assets.remove(fee_asset_index);
			let (local_xcm, remote_xcm) = match fees_transfer_type {
				TransferType::LocalReserve => Self::local_reserve_fees_instructions(
					origin.clone(),
					dest.clone(),
					fees,
					weight_limit,
				)?,
				TransferType::DestinationReserve => Self::destination_reserve_fees_instructions(
					origin.clone(),
					dest.clone(),
					fees,
					weight_limit,
				)?,
				TransferType::Teleport => Self::teleport_fees_instructions(
					origin.clone(),
					dest.clone(),
					fees,
					weight_limit,
				)?,
				TransferType::RemoteReserve(_) =>
					return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
			};
			FeesHandling::Separate { local_xcm, remote_xcm }
		};

		let (local_xcm, remote_xcm) = Self::build_xcm_transfer_type(
			origin.clone(),
			dest.clone(),
			beneficiary,
			assets,
			assets_transfer_type,
			fees,
			weight_limit,
		)?;
		Self::execute_xcm_transfer(origin, dest, local_xcm, remote_xcm)
Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: Location,
		dest: Location,
Francisco Aguirre's avatar
Francisco Aguirre committed
		assets: Vec<Asset>,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Option<Xcm<()>>), Error<T>> {
			target: "xcm::pallet_xcm::build_xcm_transfer_type",
			"origin {:?}, dest {:?}, beneficiary {:?}, assets {:?}, transfer_type {:?}, \
			fees_handling {:?}, weight_limit: {:?}",
			origin, dest, beneficiary, assets, transfer_type, fees, weight_limit,
		match transfer_type {
			TransferType::LocalReserve => Self::local_reserve_transfer_programs(
				origin.clone(),
				dest.clone(),
				beneficiary,
				assets,
				fees,
				weight_limit,
			)
			.map(|(local, remote)| (local, Some(remote))),
			TransferType::DestinationReserve => Self::destination_reserve_transfer_programs(
				origin.clone(),
				dest.clone(),
				beneficiary,
				assets,
				fees,
				weight_limit,
			)
			.map(|(local, remote)| (local, Some(remote))),
			TransferType::RemoteReserve(reserve) => {
				let fees = match fees {
					FeesHandling::Batched { fees } => fees,
					_ => return Err(Error::<T>::InvalidAssetUnsupportedReserve.into()),
				};
Francisco Aguirre's avatar
Francisco Aguirre committed
					origin.clone(),
					reserve.try_into().map_err(|()| Error::<T>::BadVersion)?,
Francisco Aguirre's avatar
Francisco Aguirre committed
					dest.clone(),
			TransferType::Teleport => Self::teleport_assets_program(
				origin.clone(),
				dest.clone(),
				beneficiary,
				assets,
				fees,
				weight_limit,
			)
			.map(|(local, remote)| (local, Some(remote))),
		}
	}

	fn execute_xcm_transfer(
		origin: Location,
		dest: Location,
		mut local_xcm: Xcm<<T as Config>::RuntimeCall>,
		remote_xcm: Option<Xcm<()>>,
	) -> DispatchResult {
		log::debug!(
			target: "xcm::pallet_xcm::execute_xcm_transfer",
			"origin {:?}, dest {:?}, local_xcm {:?}, remote_xcm {:?}",
			origin, dest, local_xcm, remote_xcm,
		);

		let weight =
			T::Weigher::weight(&mut local_xcm).map_err(|()| Error::<T>::UnweighableMessage)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
		let mut hash = local_xcm.using_encoded(sp_io::hashing::blake2_256);
		let outcome = T::XcmExecutor::prepare_and_execute(
			origin.clone(),
			local_xcm,
			&mut hash,
			weight,
			weight,
		);
		Self::deposit_event(Event::Attempted { outcome: outcome.clone() });
		outcome.ensure_complete().map_err(|error| {
			log::error!(
				"XCM execution failed with error {:?}", error
			);
			Error::<T>::LocalExecutionIncomplete
		})?;
Francisco Aguirre's avatar
Francisco Aguirre committed
			let (ticket, price) = validate_send::<T::XcmRouter>(dest.clone(), remote_xcm.clone())
				.map_err(Error::<T>::from)?;
			if origin != Here.into_location() {
Francisco Aguirre's avatar
Francisco Aguirre committed
				Self::charge_fees(origin.clone(), price).map_err(|error| {
						"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(())
	}

Francisco Aguirre's avatar
Francisco Aguirre committed
		dest: Location,
		fees: FeesHandling<T>,
		weight_limit: WeightLimit,
		local: &mut Xcm<<T as Config>::RuntimeCall>,
		remote: &mut Xcm<()>,
	) -> Result<(), Error<T>> {
		match fees {
			FeesHandling::Batched { fees } => {
				let context = T::UniversalLocation::get();
				// no custom fees instructions, they are batched together with `assets` transfer;
				// BuyExecution happens after receiving all `assets`
				let reanchored_fees =
Francisco Aguirre's avatar
Francisco Aguirre committed
					fees.reanchored(&dest, &context).map_err(|_| Error::<T>::CannotReanchor)?;
				// buy execution using `fees` batched together with above `reanchored_assets`
				remote.inner_mut().push(BuyExecution { fees: reanchored_fees, weight_limit });
			},
			FeesHandling::Separate { local_xcm: mut local_fees, remote_xcm: mut remote_fees } => {
				// fees are handled by separate XCM instructions, prepend fees instructions (for
				// remote XCM they have to be prepended instead of appended to pass barriers).
				sp_std::mem::swap(local, &mut local_fees);
				sp_std::mem::swap(remote, &mut remote_fees);
				// these are now swapped so fees actually go first
				local.inner_mut().append(&mut local_fees.into_inner());
				remote.inner_mut().append(&mut remote_fees.into_inner());
			},
		}
		Ok(())
	}

Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: Location,
		dest: Location,
		fees: Asset,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
		let value = (origin, vec![fees.clone()]);
		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);

Gavin Wood's avatar
Gavin Wood committed
		let context = T::UniversalLocation::get();
Gavin Wood's avatar
Gavin Wood committed
			.clone()
Francisco Aguirre's avatar
Francisco Aguirre committed
			.reanchored(&dest, &context)
Gavin Wood's avatar
Gavin Wood committed
			.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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: Location,
		dest: Location,
Francisco Aguirre's avatar
Francisco Aguirre committed
		assets: Vec<Asset>,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
		let value = (origin, assets);
		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
		let (_, assets) = value;

		// max assets is `assets` (+ potentially separately handled fee)
		let max_assets =
			assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
Francisco Aguirre's avatar
Francisco Aguirre committed
		let assets: Assets = assets.into();
		let context = T::UniversalLocation::get();
		let mut reanchored_assets = assets.clone();
		reanchored_assets
Francisco Aguirre's avatar
Francisco Aguirre committed
			.reanchor(&dest, &context)
		// XCM instructions to be executed on local chain
		let mut local_execute_xcm = Xcm(vec![
			// locally move `assets` to `dest`s local sovereign account
Francisco Aguirre's avatar
Francisco Aguirre committed
			TransferAsset { assets, beneficiary: dest.clone() },
		]);
		// XCM instructions to be executed on destination chain
		let mut xcm_on_dest = Xcm(vec![
			// 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,
		]);
		// handle fees
		Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;

		// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
		let custom_remote_xcm = match beneficiary {
			Either::Right(custom_xcm) => custom_xcm,
			Either::Left(beneficiary) => {
				// deposit all remaining assets in holding to `beneficiary` location
				Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
			},
		};
		xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
		Ok((local_execute_xcm, xcm_on_dest))
Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: Location,
		dest: Location,
		fees: Asset,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
		let value = (origin, vec![fees.clone()]);
		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);

		let context = T::UniversalLocation::get();
		let reanchored_fees = fees
			.clone()
Francisco Aguirre's avatar
Francisco Aguirre committed
			.reanchored(&dest, &context)
			.map_err(|_| Error::<T>::CannotReanchor)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
		let fees: Assets = 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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: Location,
		dest: Location,
Francisco Aguirre's avatar
Francisco Aguirre committed
		assets: Vec<Asset>,
		weight_limit: WeightLimit,
	) -> Result<(Xcm<<T as Config>::RuntimeCall>, Xcm<()>), Error<T>> {
		let value = (origin, assets);
		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);
		let (_, assets) = value;

		// max assets is `assets` (+ potentially separately handled fee)
		let max_assets =
			assets.len() as u32 + if matches!(&fees, FeesHandling::Batched { .. }) { 0 } else { 1 };
Francisco Aguirre's avatar
Francisco Aguirre committed
		let assets: Assets = assets.into();
		let context = T::UniversalLocation::get();
		let mut reanchored_assets = assets.clone();
		reanchored_assets
Francisco Aguirre's avatar
Francisco Aguirre committed
			.reanchor(&dest, &context)
		// XCM instructions to be executed on local chain
		let mut local_execute_xcm = Xcm(vec![
			// withdraw reserve-based assets
			WithdrawAsset(assets.clone()),
			// burn reserve-based assets
			BurnAsset(assets),
		]);
		// XCM instructions to be executed on destination chain
		let mut xcm_on_dest = Xcm(vec![
			// withdraw `assets` from origin chain's sovereign account
			WithdrawAsset(reanchored_assets),
			// following instructions are not exec'ed on behalf of origin chain anymore
			ClearOrigin,
		]);
		// handle fees
		Self::add_fees_to_xcm(dest, fees, weight_limit, &mut local_execute_xcm, &mut xcm_on_dest)?;
		// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
		let custom_remote_xcm = match beneficiary {
			Either::Right(custom_xcm) => custom_xcm,
			Either::Left(beneficiary) => {
				// deposit all remaining assets in holding to `beneficiary` location
				Xcm(vec![DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary }])
			},
		};
		xcm_on_dest.0.extend(custom_remote_xcm.into_iter());
		Ok((local_execute_xcm, xcm_on_dest))
	}

	// function assumes fees and assets have the same remote reserve
	fn remote_reserve_transfer_program(
Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: Location,
		reserve: Location,
Francisco Aguirre's avatar
Francisco Aguirre committed
		dest: Location,
		assets: Vec<Asset>,
		fees: Asset,
		weight_limit: WeightLimit,
	) -> Result<Xcm<<T as Config>::RuntimeCall>, Error<T>> {
		let value = (origin, assets);
		ensure!(T::XcmReserveTransferFilter::contains(&value), Error::<T>::Filtered);