Skip to content
lib.rs 49.3 KiB
Newer Older
		/// which will call a dispatchable when a response happens.
		pub fn new_notify_query(
			responder: MultiLocation,
			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) {
			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(())
				.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(())
		}
	}
	impl<T: Config> DropAssets for Pallet<T> {
		fn drop_assets(origin: &MultiLocation, assets: Assets) -> Weight {
			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: Weight,
		) -> Weight {
			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;
								if weight > 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)
							} 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 successful_origin() -> O {
		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 successful_origin() -> O {
		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: MultiLocation, kind: OriginKind) -> Result<Origin, MultiLocation> {
		match kind {
			OriginKind::Xcm => Ok(crate::Origin::Xcm(origin).into()),
			_ => Err(origin),