lib.rs 80.8 KiB
Newer Older
			let outcome = <Self as ExecuteController<_, _>>::execute(origin, message, max_weight)?;
			Ok(Some(outcome.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;
					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> {
	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)?;

		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, assets) = value;
		let context = T::UniversalLocation::get();
		let fees = assets
			.get(fee_asset_item as usize)
			.ok_or(Error::<T>::Empty)?
			.clone()
			.reanchored(&dest, context)
			.map_err(|_| Error::<T>::CannotReanchor)?;
		let max_assets = assets.len() as u32;
		let assets: MultiAssets = assets.into();
		let xcm = Xcm(vec![
			BuyExecution { fees, weight_limit },
			DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
		]);
		let mut message = Xcm(vec![
			SetFeesMode { jit_withdraw: true },
			TransferReserveAsset { assets, dest, xcm },
		]);
Gavin Wood's avatar
Gavin Wood committed
		let weight =
			T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
		let hash = message.using_encoded(sp_io::hashing::blake2_256);
		let outcome =
			T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight);
		Self::deposit_event(Event::Attempted { outcome });
Gavin Wood's avatar
Gavin Wood committed
		Ok(())
	}
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;
		let context = T::UniversalLocation::get();
		let fees = assets
			.get(fee_asset_item as usize)
			.ok_or(Error::<T>::Empty)?
			.clone()
			.reanchored(&dest, context)
			.map_err(|_| Error::<T>::CannotReanchor)?;
		let max_assets = assets.len() as u32;
		let assets: MultiAssets = assets.into();
		let xcm = Xcm(vec![
			BuyExecution { fees, weight_limit },
			DepositAsset { assets: Wild(AllCounted(max_assets)), beneficiary },
		]);
		let mut message = Xcm(vec![
			WithdrawAsset(assets),
			SetFeesMode { jit_withdraw: true },
			InitiateTeleport { assets: Wild(AllCounted(max_assets)), dest, xcm },
		]);
Gavin Wood's avatar
Gavin Wood committed
		let weight =
			T::Weigher::weight(&mut message).map_err(|()| Error::<T>::UnweighableMessage)?;
		let hash = message.using_encoded(sp_io::hashing::blake2_256);
		let outcome =
			T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight);
		Self::deposit_event(Event::Attempted { outcome });
Gavin Wood's avatar
Gavin Wood committed
		Ok(())
	}
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(())
	}
Gavin Wood's avatar
Gavin Wood committed
	/// Request that `dest` ceases informing us of its version.
	pub fn unrequest_version_notify(dest: impl Into<MultiLocation>) -> XcmResult {
		let dest = dest.into();
		let versioned_dest = LatestVersionedMultiLocation(&dest);
		let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
			.ok_or(XcmError::InvalidLocation)?;
		let (message_id, cost) = send_xcm::<T::XcmRouter>(dest, Xcm(vec![UnsubscribeVersion]))?;
		Self::deposit_event(Event::VersionNotifyUnrequested {
			destination: dest,
			cost,
			message_id,
		});
Gavin Wood's avatar
Gavin Wood committed
		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. The `fee_payer` is charged for the delivery unless `None` in which case fees
	/// are not charged (and instead borne by the chain).
	pub fn send_xcm(
		interior: impl Into<Junctions>,
		dest: impl Into<MultiLocation>,
		mut message: Xcm<()>,
	) -> Result<XcmHash, SendError> {
		let interior = interior.into();
		let dest = dest.into();
		let maybe_fee_payer = if interior != Junctions::Here {
			message.0.insert(0, DescendOrigin(interior));
			Some(interior.into())
		} else {
			None
		};
		log::debug!(target: "xcm::send_xcm", "dest: {:?}, message: {:?}", &dest, &message);
		let (ticket, price) = validate_send::<T::XcmRouter>(dest, message)?;
		if let Some(fee_payer) = maybe_fee_payer {
			Self::charge_fees(fee_payer, price).map_err(|_| SendError::Fees)?;
Gavin Wood's avatar
Gavin Wood committed
		T::XcmRouter::deliver(ticket)
	}
Gavin Wood's avatar
Gavin Wood committed
	pub fn check_account() -> T::AccountId {
		const ID: PalletId = PalletId(*b"py/xcmch");
		AccountIdConversion::<T::AccountId>::into_account_truncating(&ID)
	}
Gavin Wood's avatar
Gavin Wood committed
	/// Create a new expectation of a query response with the querier being here.
	fn do_new_query(
		responder: impl Into<MultiLocation>,
		maybe_notify: Option<(u8, u8)>,
Gavin Wood's avatar
Gavin Wood committed
		match_querier: impl Into<MultiLocation>,
	) -> u64 {
		QueryCounter::<T>::mutate(|q| {
			let r = *q;
			q.saturating_inc();
			Queries::<T>::insert(
				r,
				QueryStatus::Pending {
					responder: responder.into().into(),
					maybe_match_querier: Some(match_querier.into().into()),
					maybe_notify,
					timeout,
				},
			);
			r
		})
	}
Gavin Wood's avatar
Gavin Wood committed
	/// 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.
Gavin Wood's avatar
Gavin Wood committed
	///
	/// `report_outcome_notify` may return an error if the `responder` is not invertible.
	///
	/// It is assumed that the querier of the response will be `Here`.
	///
	/// 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: impl Into<MultiLocation>,
		notify: impl Into<<T as Config>::RuntimeCall>,
Gavin Wood's avatar
Gavin Wood committed
	) -> Result<(), XcmError> {
		let responder = responder.into();
		let destination = T::UniversalLocation::get()
			.invert_target(&responder)
			.map_err(|()| XcmError::LocationNotInvertible)?;
		let notify: <T as Config>::RuntimeCall = notify.into();
		let max_weight = notify.get_dispatch_info().weight;
		let query_id = Self::new_notify_query(responder, notify, timeout, Here);
		let response_info = QueryResponseInfo { destination, query_id, max_weight };
		let report_error = Xcm(vec![ReportError(response_info)]);
		message.0.insert(0, SetAppendix(report_error));
		Ok(())
	}
Gavin Wood's avatar
Gavin Wood committed
	/// Attempt to create a new query ID and register it as a query that is yet to respond, and
	/// which will call a dispatchable when a response happens.
	pub fn new_notify_query(
		responder: impl Into<MultiLocation>,
		notify: impl Into<<T as Config>::RuntimeCall>,
Gavin Wood's avatar
Gavin Wood committed
		match_querier: impl Into<MultiLocation>,
	) -> u64 {
		let notify = notify.into().using_encoded(|mut bytes| Decode::decode(&mut bytes)).expect(
			"decode input is output of Call encode; Call guaranteed to have two enums; qed",
		);
		Self::do_new_query(responder, Some(notify), timeout, match_querier)
	}

	/// Note that a particular destination to whom we would like to send a message is unknown
	/// and queue it for version discovery.
	fn note_unknown_version(dest: &MultiLocation) {
		log::trace!(
			target: "xcm::pallet_xcm::note_unknown_version",
			"XCM version is unknown for destination: {:?}",
			dest,
		);
		let versioned_dest = VersionedMultiLocation::from(*dest);
		VersionDiscoveryQueue::<T>::mutate(|q| {
			if let Some(index) = q.iter().position(|i| &i.0 == &versioned_dest) {
				// exists - just bump the count.
				q[index].1.saturating_inc();
Gavin Wood's avatar
Gavin Wood committed
				let _ = q.try_push((versioned_dest, 1));
Gavin Wood's avatar
Gavin Wood committed
		});
	}

	/// Withdraw given `assets` from the given `location` and pay as XCM fees.
	///
	/// Fails if:
	/// - the `assets` are not known on this chain;
	/// - the `assets` cannot be withdrawn with that location as the Origin.
	fn charge_fees(location: MultiLocation, assets: MultiAssets) -> DispatchResult {
		T::XcmExecutor::charge_fees(location, assets.clone())
			.map_err(|_| Error::<T>::FeesNotMet)?;
		Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
Gavin Wood's avatar
Gavin Wood committed
		Ok(())
	}
}

pub struct LockTicket<T: Config> {
	sovereign_account: T::AccountId,
	amount: BalanceOf<T>,
	unlocker: MultiLocation,
	item_index: Option<usize>,
}

impl<T: Config> xcm_executor::traits::Enact for LockTicket<T> {
	fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
		use xcm_executor::traits::LockError::UnexpectedState;
		let mut locks = LockedFungibles::<T>::get(&self.sovereign_account).unwrap_or_default();
		match self.item_index {
			Some(index) => {
				ensure!(locks.len() > index, UnexpectedState);
				ensure!(locks[index].1.try_as::<_>() == Ok(&self.unlocker), UnexpectedState);
				locks[index].0 = locks[index].0.max(self.amount);
			},
			None => {
				locks
					.try_push((self.amount, self.unlocker.into()))
					.map_err(|(_balance, _location)| UnexpectedState)?;
			},
Gavin Wood's avatar
Gavin Wood committed
		LockedFungibles::<T>::insert(&self.sovereign_account, locks);
		T::Currency::extend_lock(
			*b"py/xcmlk",
			&self.sovereign_account,
			self.amount,
			WithdrawReasons::all(),
		);
		Ok(())
	}
}
Gavin Wood's avatar
Gavin Wood committed
pub struct UnlockTicket<T: Config> {
	sovereign_account: T::AccountId,
	amount: BalanceOf<T>,
	unlocker: MultiLocation,
}

impl<T: Config> xcm_executor::traits::Enact for UnlockTicket<T> {
	fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
		use xcm_executor::traits::LockError::UnexpectedState;
		let mut locks =
			LockedFungibles::<T>::get(&self.sovereign_account).ok_or(UnexpectedState)?;
		let mut maybe_remove_index = None;
		let mut locked = BalanceOf::<T>::zero();
		let mut found = false;
		// We could just as well do with with an into_iter, filter_map and collect, however this way
		// avoids making an allocation.
		for (i, x) in locks.iter_mut().enumerate() {
			if x.1.try_as::<_>().defensive() == Ok(&self.unlocker) {
				x.0 = x.0.saturating_sub(self.amount);
				if x.0.is_zero() {
					maybe_remove_index = Some(i);
Gavin Wood's avatar
Gavin Wood committed
				found = true;
			}
			locked = locked.max(x.0);
Gavin Wood's avatar
Gavin Wood committed
		ensure!(found, UnexpectedState);
		if let Some(remove_index) = maybe_remove_index {
			locks.swap_remove(remove_index);
		}
		LockedFungibles::<T>::insert(&self.sovereign_account, locks);
		let reasons = WithdrawReasons::all();
		T::Currency::set_lock(*b"py/xcmlk", &self.sovereign_account, locked, reasons);
		Ok(())
Gavin Wood's avatar
Gavin Wood committed
}
Gavin Wood's avatar
Gavin Wood committed
pub struct ReduceTicket<T: Config> {
	key: (u32, T::AccountId, VersionedAssetId),
	amount: u128,
	locker: VersionedMultiLocation,
	owner: VersionedMultiLocation,
}

impl<T: Config> xcm_executor::traits::Enact for ReduceTicket<T> {
	fn enact(self) -> Result<(), xcm_executor::traits::LockError> {
		use xcm_executor::traits::LockError::UnexpectedState;
		let mut record = RemoteLockedFungibles::<T>::get(&self.key).ok_or(UnexpectedState)?;
		ensure!(self.locker == record.locker && self.owner == record.owner, UnexpectedState);
		let new_amount = record.amount.checked_sub(self.amount).ok_or(UnexpectedState)?;
		ensure!(record.amount_held().map_or(true, |h| new_amount >= h), UnexpectedState);
		if new_amount == 0 {
Gavin Wood's avatar
Gavin Wood committed
			RemoteLockedFungibles::<T>::remove(&self.key);
		} else {
			record.amount = new_amount;
Gavin Wood's avatar
Gavin Wood committed
			RemoteLockedFungibles::<T>::insert(&self.key, &record);
Gavin Wood's avatar
Gavin Wood committed
		Ok(())
Gavin Wood's avatar
Gavin Wood committed
}
Gavin Wood's avatar
Gavin Wood committed
impl<T: Config> xcm_executor::traits::AssetLock for Pallet<T> {
	type LockTicket = LockTicket<T>;
	type UnlockTicket = UnlockTicket<T>;
	type ReduceTicket = ReduceTicket<T>;

	fn prepare_lock(
		unlocker: MultiLocation,
		asset: MultiAsset,
		owner: MultiLocation,
	) -> Result<LockTicket<T>, xcm_executor::traits::LockError> {
		use xcm_executor::traits::LockError::*;
		let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
Gavin Wood's avatar
Gavin Wood committed
		let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
		ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
		let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
		let item_index = locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker));
		ensure!(item_index.is_some() || locks.len() < T::MaxLockers::get() as usize, NoResources);
		Ok(LockTicket { sovereign_account, amount, unlocker, item_index })
	}
Gavin Wood's avatar
Gavin Wood committed
	fn prepare_unlock(
		unlocker: MultiLocation,
		asset: MultiAsset,
		owner: MultiLocation,
	) -> Result<UnlockTicket<T>, xcm_executor::traits::LockError> {
		use xcm_executor::traits::LockError::*;
		let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
Gavin Wood's avatar
Gavin Wood committed
		let amount = T::CurrencyMatcher::matches_fungible(&asset).ok_or(UnknownAsset)?;
		ensure!(T::Currency::free_balance(&sovereign_account) >= amount, AssetNotOwned);
		let locks = LockedFungibles::<T>::get(&sovereign_account).unwrap_or_default();
		let item_index =
			locks.iter().position(|x| x.1.try_as::<_>() == Ok(&unlocker)).ok_or(NotLocked)?;
		ensure!(locks[item_index].0 >= amount, NotLocked);
		Ok(UnlockTicket { sovereign_account, amount, unlocker })
	}
Gavin Wood's avatar
Gavin Wood committed
	fn note_unlockable(
		locker: MultiLocation,
		asset: MultiAsset,
		mut owner: MultiLocation,
	) -> Result<(), xcm_executor::traits::LockError> {
		use xcm_executor::traits::LockError::*;
		ensure!(T::TrustedLockers::contains(&locker, &asset), NotTrusted);
		let amount = match asset.fun {
			Fungible(a) => a,
			NonFungible(_) => return Err(Unimplemented),
		};
		owner.remove_network_id();
		let account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
Gavin Wood's avatar
Gavin Wood committed
		let locker = locker.into();
		let owner = owner.into();
		let id: VersionedAssetId = asset.id.into();
		let key = (XCM_VERSION, account, id);
		let mut record =
			RemoteLockedFungibleRecord { amount, owner, locker, consumers: BoundedVec::default() };
Gavin Wood's avatar
Gavin Wood committed
		if let Some(old) = RemoteLockedFungibles::<T>::get(&key) {
			// Make sure that the new record wouldn't clobber any old data.
			ensure!(old.locker == record.locker && old.owner == record.owner, WouldClobber);
			record.consumers = old.consumers;
Gavin Wood's avatar
Gavin Wood committed
			record.amount = record.amount.max(old.amount);
Gavin Wood's avatar
Gavin Wood committed
		RemoteLockedFungibles::<T>::insert(&key, record);
		Ok(())
Gavin Wood's avatar
Gavin Wood committed
	fn prepare_reduce_unlockable(
		locker: MultiLocation,
		asset: MultiAsset,
		mut owner: MultiLocation,
	) -> Result<Self::ReduceTicket, xcm_executor::traits::LockError> {
		use xcm_executor::traits::LockError::*;
		let amount = match asset.fun {
			Fungible(a) => a,
			NonFungible(_) => return Err(Unimplemented),
		};
		owner.remove_network_id();
		let sovereign_account = T::SovereignAccountOf::convert_location(&owner).ok_or(BadOwner)?;
Gavin Wood's avatar
Gavin Wood committed
		let locker = locker.into();
		let owner = owner.into();
		let id: VersionedAssetId = asset.id.into();
		let key = (XCM_VERSION, sovereign_account, id);

		let record = RemoteLockedFungibles::<T>::get(&key).ok_or(NotLocked)?;
		// Make sure that the record contains what we expect and there's enough to unlock.
		ensure!(locker == record.locker && owner == record.owner, WouldClobber);
		ensure!(record.amount >= amount, NotEnoughLocked);
		ensure!(
			record.amount_held().map_or(true, |h| record.amount.saturating_sub(amount) >= h),
			InUse
		);
Gavin Wood's avatar
Gavin Wood committed
		Ok(ReduceTicket { key, amount, locker, owner })
	}
}

impl<T: Config> WrapVersion for Pallet<T> {
	fn wrap_version<RuntimeCall>(
		dest: &MultiLocation,
		xcm: impl Into<VersionedXcm<RuntimeCall>>,
	) -> Result<VersionedXcm<RuntimeCall>, ()> {
		SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedMultiLocation(dest))
			.or_else(|| {
				Self::note_unknown_version(dest);
				SafeXcmVersion::<T>::get()
			})
			.ok_or_else(|| {
				log::trace!(
					target: "xcm::pallet_xcm::wrap_version",
					"Could not determine a version to wrap XCM for destination: {:?}",
					dest,
				);
				()
			})
			.and_then(|v| xcm.into().into_version(v.min(XCM_VERSION)))
	}
}

impl<T: Config> VersionChangeNotifier for Pallet<T> {
	/// Start notifying `location` should the XCM version of this chain change.
	///
	/// When it does, this type should ensure a `QueryResponse` message is sent with the given
	/// `query_id` & `max_weight` and with a `response` of `Response::Version`. This should happen
	/// until/unless `stop` is called with the correct `query_id`.
	///
	/// If the `location` has an ongoing notification and when this function is called, then an
	/// error should be returned.
	fn start(
		dest: &MultiLocation,
		query_id: QueryId,
		max_weight: Weight,
		_context: &XcmContext,
	) -> XcmResult {
		let versioned_dest = LatestVersionedMultiLocation(dest);
		let already = VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest);
		ensure!(!already, XcmError::InvalidLocation);

		let xcm_version = T::AdvertisedXcmVersion::get();
		let response = Response::Version(xcm_version);
		let instruction = QueryResponse { query_id, response, max_weight, querier: None };
		let (message_id, cost) = send_xcm::<T::XcmRouter>(*dest, Xcm(vec![instruction]))?;
		Self::deposit_event(Event::<T>::VersionNotifyStarted {
			destination: *dest,
			cost,
			message_id,
		});
Gavin Wood's avatar
Gavin Wood committed

		let value = (query_id, max_weight, xcm_version);
		VersionNotifyTargets::<T>::insert(XCM_VERSION, versioned_dest, value);
		Ok(())
	}

	/// Stop notifying `location` should the XCM change. This is a no-op if there was never a
	/// subscription.
	fn stop(dest: &MultiLocation, _context: &XcmContext) -> XcmResult {
		VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedMultiLocation(dest));
		Ok(())
	}

	/// Return true if a location is subscribed to XCM version changes.
	fn is_subscribed(dest: &MultiLocation) -> bool {
		let versioned_dest = LatestVersionedMultiLocation(dest);
		VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
	}
}

impl<T: Config> DropAssets for Pallet<T> {
	fn drop_assets(origin: &MultiLocation, assets: Assets, _context: &XcmContext) -> Weight {
		if assets.is_empty() {
			return Weight::zero()
Gavin Wood's avatar
Gavin Wood committed
		let versioned = VersionedMultiAssets::from(MultiAssets::from(assets));
		let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
		AssetTraps::<T>::mutate(hash, |n| *n += 1);
		Self::deposit_event(Event::AssetsTrapped { hash, origin: *origin, assets: versioned });
Gavin Wood's avatar
Gavin Wood committed
		// TODO #3735: Put the real weight in there.
		Weight::zero()
Gavin Wood's avatar
Gavin Wood committed
}
Gavin Wood's avatar
Gavin Wood committed
impl<T: Config> ClaimAssets for Pallet<T> {
	fn claim_assets(
		origin: &MultiLocation,
		ticket: &MultiLocation,
		assets: &MultiAssets,
		_context: &XcmContext,
	) -> bool {
		let mut versioned = VersionedMultiAssets::from(assets.clone());
		match (ticket.parents, &ticket.interior) {
			(0, X1(GeneralIndex(i))) =>
				versioned = match versioned.into_version(*i as u32) {
					Ok(v) => v,
					Err(()) => return false,
				},
			(0, Here) => (),
			_ => return false,
		};
		let hash = BlakeTwo256::hash_of(&(origin, versioned.clone()));
		match AssetTraps::<T>::get(hash) {
			0 => return false,
			1 => AssetTraps::<T>::remove(hash),
			n => AssetTraps::<T>::insert(hash, n - 1),
		Self::deposit_event(Event::AssetsClaimed { hash, origin: *origin, assets: versioned });
Gavin Wood's avatar
Gavin Wood committed
		return true
Gavin Wood's avatar
Gavin Wood committed
}