Skip to content
lib.rs 56.8 KiB
Newer Older
								Ok(()) => {
									VersionNotifyTargets::<T>::insert(
										XCM_VERSION,
										versioned_key,
										(query_id, max_weight, xcm_version),
									);
									Event::VersionChangeNotified(new_key, xcm_version)
								},
								Err(e) => Event::NotifyTargetSendFail(new_key, query_id, e.into()),
							};
							Self::deposit_event(event);
							weight_used.saturating_accrue(todo_vnt_notify_migrate_weight);
						}
						if weight_used.any_gte(weight_cutoff) {
							return (weight_used, Some(stage))
						}
					}
				}
			}
			(weight_used, None)
		}

		/// 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.clone());
			let already = VersionNotifiers::<T>::contains_key(XCM_VERSION, &versioned_dest);
			ensure!(!already, XcmError::InvalidLocation);
			let query_id = QueryCounter::<T>::mutate(|q| {
				let r = *q;
				q.saturating_inc();
				r
			});
			// TODO #3735: Correct weight.
			let instruction = SubscribeVersion { query_id, max_response_weight: 0 };
			T::XcmRouter::send_xcm(dest, Xcm(vec![instruction]))?;
			VersionNotifiers::<T>::insert(XCM_VERSION, &versioned_dest, query_id);
			let query_status =
				QueryStatus::VersionNotifier { origin: versioned_dest, is_active: false };
			Queries::<T>::insert(query_id, query_status);
			Ok(())
		}

		/// Request that `dest` ceases informing us of its version.
		pub fn unrequest_version_notify(dest: 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)?;
			T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![UnsubscribeVersion]))?;
			Queries::<T>::remove(query_id);
			Ok(())
		}

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

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

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

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

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

		/// Attempt to create a new query ID and register it as a query that is yet to respond, and
		/// which will call a dispatchable when a response happens.
		pub fn new_notify_query(
			notify: impl Into<<T as Config>::Call>,
			timeout: T::BlockNumber,
		) -> 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)
		}

		/// Attempt to remove and return the response of query with ID `query_id`.
		///
		/// Returns `None` if the response is not (yet) available.
		pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> {
			if let Some(QueryStatus::Ready { response, at }) = Queries::<T>::get(query_id) {
				let response = response.try_into().ok()?;
				Queries::<T>::remove(query_id);
				Self::deposit_event(Event::ResponseTaken(query_id));
				Some((response, at))
			} else {
				None
			}
		}

		/// 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.clone());
			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();
				} else {
					let _ = q.try_push((versioned_dest, 1));
				}
			});
		}
	}

	impl<T: Config> WrapVersion for Pallet<T> {
		fn wrap_version<Call>(
			dest: &MultiLocation,
			xcm: impl Into<VersionedXcm<Call>>,
		) -> Result<VersionedXcm<Call>, ()> {
			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 `Repsonse::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: u64) -> 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 };
			T::XcmRouter::send_xcm(dest.clone(), Xcm(vec![instruction]))?;

			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) -> 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) -> u64 {
			if assets.is_empty() {
				return 0
			}
			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.clone(), versioned));
			// TODO #3735: Put the real weight in there.
			0
		}
	}

	impl<T: Config> ClaimAssets for Pallet<T> {
		fn claim_assets(
			origin: &MultiLocation,
			ticket: &MultiLocation,
			assets: &MultiAssets,
		) -> 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));
			match AssetTraps::<T>::get(hash) {
				0 => return false,
				1 => AssetTraps::<T>::remove(hash),
				n => AssetTraps::<T>::insert(hash, n - 1),
			}
			return true
		}
	}

	impl<T: Config> OnResponse for Pallet<T> {
		fn expecting_response(origin: &MultiLocation, query_id: QueryId) -> bool {
			match Queries::<T>::get(query_id) {
				Some(QueryStatus::Pending { responder, .. }) =>
					MultiLocation::try_from(responder).map_or(false, |r| origin == &r),
				Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
					MultiLocation::try_from(r).map_or(false, |r| origin == &r),
				_ => false,
			}
		}

		fn on_response(
			origin: &MultiLocation,
			query_id: QueryId,
			response: Response,
			max_weight: u64,
		) -> u64 {
			match (response, Queries::<T>::get(query_id)) {
				(
					Response::Version(v),
					Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
				) => {
					let origin: MultiLocation = match expected_origin.try_into() {
						Ok(o) if &o == origin => o,
						Ok(o) => {
							Self::deposit_event(Event::InvalidResponder(
								origin.clone(),
								query_id,
								Some(o),
							));
							return 0
						},
						_ => {
							Self::deposit_event(Event::InvalidResponder(
								origin.clone(),
								query_id,
								None,
							));
							// TODO #3735: Correct weight for this.
							return 0
						},
					};
					// TODO #3735: Check max_weight is correct.
					if !is_active {
						Queries::<T>::insert(
							query_id,
							QueryStatus::VersionNotifier {
								origin: origin.clone().into(),
								is_active: true,
							},
						);
					}
					// We're being notified of a version change.
					SupportedVersion::<T>::insert(
						XCM_VERSION,
						LatestVersionedMultiLocation(&origin),
						v,
					);
					Self::deposit_event(Event::SupportedVersionChanged(origin, v));
					0
				},
				(response, Some(QueryStatus::Pending { responder, maybe_notify, .. })) => {
					let responder = match MultiLocation::try_from(responder) {
						Ok(r) => r,
						Err(_) => {
							Self::deposit_event(Event::InvalidResponderVersion(
								origin.clone(),
								query_id,
							));
							return 0
						},
					};
					if origin != &responder {
						Self::deposit_event(Event::InvalidResponder(
							origin.clone(),
							query_id,
							Some(responder),
						));
						return 0
					}
					return match maybe_notify {
						Some((pallet_index, call_index)) => {
							// This is a bit horrible, but we happen to know that the `Call` will
							// be built by `(pallet_index: u8, call_index: u8, QueryId, Response)`.
							// So we just encode that and then re-encode to a real Call.
							let bare = (pallet_index, call_index, query_id, response);
							if let Ok(call) = bare
								.using_encoded(|mut bytes| <T as Config>::Call::decode(&mut bytes))
							{
								Queries::<T>::remove(query_id);
								let weight = call.get_dispatch_info().weight;
								let max_weight = Weight::from_ref_time(max_weight);
								if weight.any_gt(max_weight) {
									let e = Event::NotifyOverweight(
										weight,
										max_weight,
								let dispatch_origin = Origin::Response(origin.clone()).into();
								match call.dispatch(dispatch_origin) {
									Ok(post_info) => {
										let e = Event::Notified(query_id, pallet_index, call_index);
										Self::deposit_event(e);
										post_info.actual_weight
									},
									Err(error_and_info) => {
										let e = Event::NotifyDispatchError(
											query_id,
											pallet_index,
											call_index,
										);
										Self::deposit_event(e);
										// Not much to do with the result as it is. It's up to the parachain to ensure that the
										// message makes sense.
										error_and_info.post_info.actual_weight
									},
								}
								.unwrap_or(weight)
								.ref_time()
							} else {
								let e =
									Event::NotifyDecodeFailed(query_id, pallet_index, call_index);
							}
						},
						None => {
							let e = Event::ResponseReady(query_id, response.clone());
							Self::deposit_event(e);
							let at = frame_system::Pallet::<T>::current_block_number();
							let response = response.into();
							Queries::<T>::insert(query_id, QueryStatus::Ready { response, at });
							0
						},
				},
				_ => {
					Self::deposit_event(Event::UnexpectedResponse(origin.clone(), query_id));
					return 0
				},
/// Ensure that the origin `o` represents an XCM (`Transact`) origin.
///
/// Returns `Ok` with the location of the XCM sender or an `Err` otherwise.
Gavin Wood's avatar
Gavin Wood committed
pub fn ensure_xcm<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, BadOrigin>
where
	OuterOrigin: Into<Result<Origin, OuterOrigin>>,
Gavin Wood's avatar
Gavin Wood committed
{
	match o.into() {
		Ok(Origin::Xcm(location)) => Ok(location),
		_ => Err(BadOrigin),
	}
}

/// Ensure that the origin `o` represents an XCM response origin.
///
/// Returns `Ok` with the location of the responder or an `Err` otherwise.
pub fn ensure_response<OuterOrigin>(o: OuterOrigin) -> Result<MultiLocation, BadOrigin>
where
	OuterOrigin: Into<Result<Origin, OuterOrigin>>,
{
	match o.into() {
		Ok(Origin::Response(location)) => Ok(location),
		_ => Err(BadOrigin),
	}
}

Gavin Wood's avatar
Gavin Wood committed
/// Filter for `MultiLocation` to find those which represent a strict majority approval of an identified
/// plurality.
///
/// May reasonably be used with `EnsureXcm`.
pub struct IsMajorityOfBody<Prefix, Body>(PhantomData<(Prefix, Body)>);
impl<Prefix: Get<MultiLocation>, Body: Get<BodyId>> Contains<MultiLocation>
	for IsMajorityOfBody<Prefix, Body>
{
	fn contains(l: &MultiLocation) -> bool {
		let maybe_suffix = l.match_and_split(&Prefix::get());
		matches!(maybe_suffix, Some(Plurality { id, part }) if id == &Body::get() && part.is_majority())
/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter the
Gavin Wood's avatar
Gavin Wood committed
/// `Origin::Xcm` item.
pub struct EnsureXcm<F>(PhantomData<F>);
impl<O: OriginTrait + From<Origin>, F: Contains<MultiLocation>> EnsureOrigin<O> for EnsureXcm<F>
where
	O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
Gavin Wood's avatar
Gavin Wood committed
{
	type Success = MultiLocation;

	fn try_origin(outer: O) -> Result<Self::Success, O> {
		outer.try_with_caller(|caller| {
			caller.try_into().and_then(|o| match o {
				Origin::Xcm(location) if F::contains(&location) => Ok(location),
				Origin::Xcm(location) => Err(Origin::Xcm(location).into()),
				o => Err(o.into()),
Gavin Wood's avatar
Gavin Wood committed
	}

	#[cfg(feature = "runtime-benchmarks")]
	fn try_successful_origin() -> Result<O, ()> {
		Ok(O::from(Origin::Xcm(Here.into())))
/// `EnsureOrigin` implementation succeeding with a `MultiLocation` value to recognize and filter
/// the `Origin::Response` item.
pub struct EnsureResponse<F>(PhantomData<F>);
impl<O: OriginTrait + From<Origin>, F: Contains<MultiLocation>> EnsureOrigin<O>
	for EnsureResponse<F>
where
	O::PalletsOrigin: From<Origin> + TryInto<Origin, Error = O::PalletsOrigin>,
{
	type Success = MultiLocation;

	fn try_origin(outer: O) -> Result<Self::Success, O> {
		outer.try_with_caller(|caller| {
			caller.try_into().and_then(|o| match o {
				Origin::Response(responder) => Ok(responder),
				o => Err(o.into()),
			})
		})
	}

	#[cfg(feature = "runtime-benchmarks")]
	fn try_successful_origin() -> Result<O, ()> {
		Ok(O::from(Origin::Response(Here.into())))
/// A simple passthrough where we reuse the `MultiLocation`-typed XCM origin as the inner value of
/// this crate's `Origin::Xcm` value.
pub struct XcmPassthrough<Origin>(PhantomData<Origin>);
impl<Origin: From<crate::Origin>> ConvertOrigin<Origin> for XcmPassthrough<Origin> {
	fn convert_origin(
		origin: impl Into<MultiLocation>,
		kind: OriginKind,
	) -> Result<Origin, MultiLocation> {
		let origin = origin.into();
		match kind {
			OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
			_ => Err(origin),