lib.rs 116 KiB
Newer Older
		let max_assets = assets.len() as u32;
		let context = T::UniversalLocation::get();
		// we spend up to half of fees for execution on reserve and other half for execution on
		// destination
		let (fees_half_1, fees_half_2) = Self::halve_fees(fees)?;
		// identifies fee item as seen by `reserve` - to be used at reserve chain
		let reserve_fees = fees_half_1
Francisco Aguirre's avatar
Francisco Aguirre committed
			.reanchored(&reserve, &context)
			.map_err(|_| Error::<T>::CannotReanchor)?;
		// identifies fee item as seen by `dest` - to be used at destination chain
Francisco Aguirre's avatar
Francisco Aguirre committed
		let dest_fees = fees_half_2
			.reanchored(&dest, &context)
			.map_err(|_| Error::<T>::CannotReanchor)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
		let dest = dest.reanchored(&reserve, &context).map_err(|_| Error::<T>::CannotReanchor)?;
		let mut xcm_on_dest =
			Xcm(vec![BuyExecution { fees: dest_fees, weight_limit: weight_limit.clone() }]);
		// Use custom XCM on remote chain, or just default to depositing everything to beneficiary.
		let custom_xcm_on_dest = 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_xcm_on_dest.into_iter());
		// xcm to be executed on reserve
		let xcm_on_reserve = Xcm(vec![
			BuyExecution { fees: reserve_fees, weight_limit },
			DepositReserveAsset { assets: Wild(AllCounted(max_assets)), dest, xcm: xcm_on_dest },
		]);
		Ok(Xcm(vec![
			WithdrawAsset(assets.into()),
			SetFeesMode { jit_withdraw: true },
			InitiateReserveWithdraw {
				assets: Wild(AllCounted(max_assets)),
				reserve,
				xcm: xcm_on_reserve,
			},
		]))
	}

	fn teleport_fees_instructions(
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::XcmTeleportFilter::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)?;

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

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

	fn teleport_assets_program(
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>, Xcm<()>), Error<T>> {
		let value = (origin, assets);
		ensure!(T::XcmTeleportFilter::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 };
		let context = T::UniversalLocation::get();
Francisco Aguirre's avatar
Francisco Aguirre committed
		let assets: Assets = assets.into();
		let mut reanchored_assets = assets.clone();
		reanchored_assets
Francisco Aguirre's avatar
Francisco Aguirre committed
			.reanchor(&dest, &context)
			.map_err(|_| Error::<T>::CannotReanchor)?;

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

		// XCM instructions to be executed on local chain
		let mut local_execute_xcm = Xcm(vec![
			// withdraw assets to be teleported
			WithdrawAsset(assets.clone()),
			// burn assets on local chain
			BurnAsset(assets),
Gavin Wood's avatar
Gavin Wood committed
		]);
		// XCM instructions to be executed on destination chain
		let mut xcm_on_dest = Xcm(vec![
			// teleport `assets` in from origin chain
			ReceiveTeleportedAsset(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());
Francisco Aguirre's avatar
Francisco Aguirre committed
	pub(crate) fn halve_fees(fees: Asset) -> Result<(Asset, Asset), Error<T>> {
		match fees.fun {
			Fungible(amount) => {
				let fee1 = amount.saturating_div(2);
				let fee2 = amount.saturating_sub(fee1);
				ensure!(fee1 > 0, Error::<T>::FeesNotMet);
				ensure!(fee2 > 0, Error::<T>::FeesNotMet);
Francisco Aguirre's avatar
Francisco Aguirre committed
				Ok((Asset::from((fees.id.clone(), fee1)), Asset::from((fees.id.clone(), fee2))))
Gavin Wood's avatar
Gavin Wood committed
	}
Gavin Wood's avatar
Gavin Wood committed
	/// Will always make progress, and will do its best not to use much more than `weight_cutoff`
	/// in doing so.
	pub(crate) fn check_xcm_version_change(
		mut stage: VersionMigrationStage,
		weight_cutoff: Weight,
	) -> (Weight, Option<VersionMigrationStage>) {
		let mut weight_used = Weight::zero();

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

		use VersionMigrationStage::*;

		if stage == MigrateSupportedVersion {
			// We assume that supported XCM version only ever increases, so just cycle through lower
			// XCM versioned from the current.
			for v in 0..XCM_VERSION {
				for (old_key, value) in SupportedVersion::<T>::drain_prefix(v) {
					if let Ok(new_key) = old_key.into_latest() {
						SupportedVersion::<T>::insert(XCM_VERSION, new_key, value);
					}
					weight_used.saturating_accrue(sv_migrate_weight);
					if weight_used.any_gte(weight_cutoff) {
						return (weight_used, Some(stage))
Gavin Wood's avatar
Gavin Wood committed
			stage = MigrateVersionNotifiers;
		}
		if stage == MigrateVersionNotifiers {
			for v in 0..XCM_VERSION {
				for (old_key, value) in VersionNotifiers::<T>::drain_prefix(v) {
					if let Ok(new_key) = old_key.into_latest() {
						VersionNotifiers::<T>::insert(XCM_VERSION, new_key, value);
					}
					weight_used.saturating_accrue(vn_migrate_weight);
					if weight_used.any_gte(weight_cutoff) {
						return (weight_used, Some(stage))
Gavin Wood's avatar
Gavin Wood committed
			stage = NotifyCurrentTargets(None);
		}
Gavin Wood's avatar
Gavin Wood committed
		let xcm_version = T::AdvertisedXcmVersion::get();
Gavin Wood's avatar
Gavin Wood committed
		if let NotifyCurrentTargets(maybe_last_raw_key) = stage {
			let mut iter = match maybe_last_raw_key {
				Some(k) => VersionNotifyTargets::<T>::iter_prefix_from(XCM_VERSION, k),
				None => VersionNotifyTargets::<T>::iter_prefix(XCM_VERSION),
			};
			while let Some((key, value)) = iter.next() {
				let (query_id, max_weight, target_xcm_version) = value;
Francisco Aguirre's avatar
Francisco Aguirre committed
				let new_key: Location = match key.clone().try_into() {
Gavin Wood's avatar
Gavin Wood committed
					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 }]);
Francisco Aguirre's avatar
Francisco Aguirre committed
				let event = match send_xcm::<T::XcmRouter>(new_key.clone(), 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;
Francisco Aguirre's avatar
Francisco Aguirre committed
					let new_key = match Location::try_from(old_key.clone()) {
Gavin Wood's avatar
Gavin Wood committed
						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

Francisco Aguirre's avatar
Francisco Aguirre committed
					let versioned_key = LatestVersionedLocation(&new_key);
Gavin Wood's avatar
Gavin Wood committed
					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,
						}]);
Francisco Aguirre's avatar
Francisco Aguirre committed
						let event = match send_xcm::<T::XcmRouter>(new_key.clone(), 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.
Francisco Aguirre's avatar
Francisco Aguirre committed
	pub fn request_version_notify(dest: impl Into<Location>) -> XcmResult {
Gavin Wood's avatar
Gavin Wood committed
		let dest = dest.into();
Francisco Aguirre's avatar
Francisco Aguirre committed
		let versioned_dest = VersionedLocation::from(dest.clone());
Gavin Wood's avatar
Gavin Wood committed
		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() };
Francisco Aguirre's avatar
Francisco Aguirre committed
		let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), 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.
Francisco Aguirre's avatar
Francisco Aguirre committed
	pub fn unrequest_version_notify(dest: impl Into<Location>) -> XcmResult {
Gavin Wood's avatar
Gavin Wood committed
		let dest = dest.into();
Francisco Aguirre's avatar
Francisco Aguirre committed
		let versioned_dest = LatestVersionedLocation(&dest);
Gavin Wood's avatar
Gavin Wood committed
		let query_id = VersionNotifiers::<T>::take(XCM_VERSION, versioned_dest)
			.ok_or(XcmError::InvalidLocation)?;
Francisco Aguirre's avatar
Francisco Aguirre committed
		let (message_id, cost) =
			send_xcm::<T::XcmRouter>(dest.clone(), 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>,
Francisco Aguirre's avatar
Francisco Aguirre committed
		dest: impl Into<Location>,
Gavin Wood's avatar
Gavin Wood committed
		mut message: Xcm<()>,
	) -> Result<XcmHash, SendError> {
		let interior = interior.into();
		let dest = dest.into();
		let maybe_fee_payer = if interior != Junctions::Here {
Francisco Aguirre's avatar
Francisco Aguirre committed
			message.0.insert(0, DescendOrigin(interior.clone()));
Gavin Wood's avatar
Gavin Wood committed
			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)
	}
	pub fn query_xcm_weight(message: VersionedXcm<()>) -> Result<Weight, FeePaymentError> {
		let message =
			Xcm::<()>::try_from(message).map_err(|_| FeePaymentError::VersionedConversionFailed)?;

		T::Weigher::weight(&mut message.into()).map_err(|()| {
			log::error!(target: "xcm::pallet_xcm::query_xcm_weight", "Error when querying XCM weight");
			FeePaymentError::WeightNotComputable
		})
	}

	pub fn query_delivery_fees(
		destination: VersionedLocation,
		message: VersionedXcm<()>,
	) -> Result<VersionedAssets, FeePaymentError> {
		let result_version = destination.identify_version().max(message.identify_version());

		let destination =
			destination.try_into().map_err(|_| FeePaymentError::VersionedConversionFailed)?;

		let message = message.try_into().map_err(|_| FeePaymentError::VersionedConversionFailed)?;

		let (_, fees) = validate_send::<T::XcmRouter>(destination, message).map_err(|error| {
			log::error!(target: "xcm::pallet_xcm::query_delivery_fees", "Error when querying delivery fees: {:?}", error);
			FeePaymentError::Unroutable
		})?;

		VersionedAssets::from(fees)
			.into_version(result_version)
			.map_err(|_| FeePaymentError::VersionedConversionFailed)
	}

Gavin Wood's avatar
Gavin Wood committed
	/// Create a new expectation of a query response with the querier being here.
	fn do_new_query(
Francisco Aguirre's avatar
Francisco Aguirre committed
		responder: impl Into<Location>,
Gavin Wood's avatar
Gavin Wood committed
		maybe_notify: Option<(u8, u8)>,
Francisco Aguirre's avatar
Francisco Aguirre committed
		match_querier: impl Into<Location>,
Gavin Wood's avatar
Gavin Wood committed
	) -> 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<()>,
Francisco Aguirre's avatar
Francisco Aguirre committed
		responder: impl Into<Location>,
Gavin Wood's avatar
Gavin Wood committed
		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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		responder: impl Into<Location>,
Gavin Wood's avatar
Gavin Wood committed
		notify: impl Into<<T as Config>::RuntimeCall>,
Francisco Aguirre's avatar
Francisco Aguirre committed
		match_querier: impl Into<Location>,
Gavin Wood's avatar
Gavin Wood committed
	) -> 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.
Francisco Aguirre's avatar
Francisco Aguirre committed
	fn note_unknown_version(dest: &Location) {
Gavin Wood's avatar
Gavin Wood committed
		log::trace!(
			target: "xcm::pallet_xcm::note_unknown_version",
			"XCM version is unknown for destination: {:?}",
			dest,
		);
Francisco Aguirre's avatar
Francisco Aguirre committed
		let versioned_dest = VersionedLocation::from(dest.clone());
Gavin Wood's avatar
Gavin Wood committed
		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.
Francisco Aguirre's avatar
Francisco Aguirre committed
	fn charge_fees(location: Location, assets: Assets) -> DispatchResult {
		T::XcmExecutor::charge_fees(location.clone(), assets.clone())
Gavin Wood's avatar
Gavin Wood committed
			.map_err(|_| Error::<T>::FeesNotMet)?;
		Self::deposit_event(Event::FeesPaid { paying: location, fees: assets });
Gavin Wood's avatar
Gavin Wood committed
		Ok(())
	}

	/// Ensure the correctness of the state of this pallet.
	///
	/// This should be valid before and after each state transition of this pallet.
	///
	/// ## Invariants
	///
	/// All entries stored in the `SupportedVersion` / `VersionNotifiers` / `VersionNotifyTargets`
	/// need to be migrated to the `XCM_VERSION`. If they are not, then `CurrentMigration` has to be
	/// set.
	#[cfg(any(feature = "try-runtime", test))]
	pub fn do_try_state() -> Result<(), TryRuntimeError> {
		// if migration has been already scheduled, everything is ok and data will be eventually
		// migrated
		if CurrentMigration::<T>::exists() {
			return Ok(())
		}

		// if migration has NOT been scheduled yet, we need to check all operational data
		for v in 0..XCM_VERSION {
			ensure!(
				SupportedVersion::<T>::iter_prefix(v).next().is_none(),
				TryRuntimeError::Other(
					"`SupportedVersion` data should be migrated to the `XCM_VERSION`!`"
				)
			);
			ensure!(
				VersionNotifiers::<T>::iter_prefix(v).next().is_none(),
				TryRuntimeError::Other(
					"`VersionNotifiers` data should be migrated to the `XCM_VERSION`!`"
				)
			);
			ensure!(
				VersionNotifyTargets::<T>::iter_prefix(v).next().is_none(),
				TryRuntimeError::Other(
					"`VersionNotifyTargets` data should be migrated to the `XCM_VERSION`!`"
				)
			);
		}

		Ok(())
	}
Gavin Wood's avatar
Gavin Wood committed
}

pub struct LockTicket<T: Config> {
	sovereign_account: T::AccountId,
	amount: BalanceOf<T>,
Francisco Aguirre's avatar
Francisco Aguirre committed
	unlocker: Location,
Gavin Wood's avatar
Gavin Wood committed
	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>,
Francisco Aguirre's avatar
Francisco Aguirre committed
	unlocker: Location,
Gavin Wood's avatar
Gavin Wood committed
}

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,
Francisco Aguirre's avatar
Francisco Aguirre committed
	locker: VersionedLocation,
	owner: VersionedLocation,
Gavin Wood's avatar
Gavin Wood committed
}

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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		unlocker: Location,
		asset: Asset,
		owner: Location,
Gavin Wood's avatar
Gavin Wood committed
	) -> 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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		unlocker: Location,
		asset: Asset,
		owner: Location,
Gavin Wood's avatar
Gavin Wood committed
	) -> 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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		locker: Location,
		asset: Asset,
		mut owner: Location,
Gavin Wood's avatar
Gavin Wood committed
	) -> 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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		locker: Location,
		asset: Asset,
		mut owner: Location,
Gavin Wood's avatar
Gavin Wood committed
	) -> 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>(
Francisco Aguirre's avatar
Francisco Aguirre committed
		dest: &Location,
Gavin Wood's avatar
Gavin Wood committed
		xcm: impl Into<VersionedXcm<RuntimeCall>>,
	) -> Result<VersionedXcm<RuntimeCall>, ()> {
Gavin Wood's avatar
Gavin Wood committed
			.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> GetVersion for Pallet<T> {
Francisco Aguirre's avatar
Francisco Aguirre committed
	fn get_version_for(dest: &Location) -> Option<XcmVersion> {
		SupportedVersion::<T>::get(XCM_VERSION, LatestVersionedLocation(dest))
Gavin Wood's avatar
Gavin Wood committed
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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		dest: &Location,
Gavin Wood's avatar
Gavin Wood committed
		query_id: QueryId,
		max_weight: Weight,
		_context: &XcmContext,
	) -> XcmResult {
Francisco Aguirre's avatar
Francisco Aguirre committed
		let versioned_dest = LatestVersionedLocation(dest);
Gavin Wood's avatar
Gavin Wood committed
		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 };
Francisco Aguirre's avatar
Francisco Aguirre committed
		let (message_id, cost) = send_xcm::<T::XcmRouter>(dest.clone(), Xcm(vec![instruction]))?;
		Self::deposit_event(Event::<T>::VersionNotifyStarted {
Francisco Aguirre's avatar
Francisco Aguirre committed
			destination: dest.clone(),
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.
Francisco Aguirre's avatar
Francisco Aguirre committed
	fn stop(dest: &Location, _context: &XcmContext) -> XcmResult {
		VersionNotifyTargets::<T>::remove(XCM_VERSION, LatestVersionedLocation(dest));
Gavin Wood's avatar
Gavin Wood committed
		Ok(())
	}

	/// Return true if a location is subscribed to XCM version changes.
Francisco Aguirre's avatar
Francisco Aguirre committed
	fn is_subscribed(dest: &Location) -> bool {
		let versioned_dest = LatestVersionedLocation(dest);
Gavin Wood's avatar
Gavin Wood committed
		VersionNotifyTargets::<T>::contains_key(XCM_VERSION, versioned_dest)
	}
}

impl<T: Config> DropAssets for Pallet<T> {
Francisco Aguirre's avatar
Francisco Aguirre committed
	fn drop_assets(origin: &Location, assets: AssetsInHolding, _context: &XcmContext) -> Weight {
Gavin Wood's avatar
Gavin Wood committed
		if assets.is_empty() {
			return Weight::zero()
Francisco Aguirre's avatar
Francisco Aguirre committed
		let versioned = VersionedAssets::from(Assets::from(assets));
Gavin Wood's avatar
Gavin Wood committed
		let hash = BlakeTwo256::hash_of(&(&origin, &versioned));
		AssetTraps::<T>::mutate(hash, |n| *n += 1);
Francisco Aguirre's avatar
Francisco Aguirre committed
		Self::deposit_event(Event::AssetsTrapped {
			hash,
			origin: origin.clone(),
			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(
Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: &Location,
		ticket: &Location,
		assets: &Assets,
Gavin Wood's avatar
Gavin Wood committed
		_context: &XcmContext,
	) -> bool {
Francisco Aguirre's avatar
Francisco Aguirre committed
		let mut versioned = VersionedAssets::from(assets.clone());
		match ticket.unpack() {
			(0, [GeneralIndex(i)]) =>
Gavin Wood's avatar
Gavin Wood committed
				versioned = match versioned.into_version(*i as u32) {
					Ok(v) => v,
					Err(()) => return false,
				},
Francisco Aguirre's avatar
Francisco Aguirre committed
			(0, []) => (),
Gavin Wood's avatar
Gavin Wood committed
			_ => return false,
		};
Francisco Aguirre's avatar
Francisco Aguirre committed
		let hash = BlakeTwo256::hash_of(&(origin.clone(), versioned.clone()));
Gavin Wood's avatar
Gavin Wood committed
		match AssetTraps::<T>::get(hash) {
			0 => return false,
			1 => AssetTraps::<T>::remove(hash),
			n => AssetTraps::<T>::insert(hash, n - 1),
Francisco Aguirre's avatar
Francisco Aguirre committed
		Self::deposit_event(Event::AssetsClaimed {
			hash,
			origin: origin.clone(),
			assets: versioned,
		});
Gavin Wood's avatar
Gavin Wood committed
		return true
Gavin Wood's avatar
Gavin Wood committed
}
Gavin Wood's avatar
Gavin Wood committed
impl<T: Config> OnResponse for Pallet<T> {
	fn expecting_response(
Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: &Location,
Gavin Wood's avatar
Gavin Wood committed
		query_id: QueryId,
Francisco Aguirre's avatar
Francisco Aguirre committed
		querier: Option<&Location>,
Gavin Wood's avatar
Gavin Wood committed
	) -> bool {
		match Queries::<T>::get(query_id) {
			Some(QueryStatus::Pending { responder, maybe_match_querier, .. }) =>
Francisco Aguirre's avatar
Francisco Aguirre committed
				Location::try_from(responder).map_or(false, |r| origin == &r) &&
Gavin Wood's avatar
Gavin Wood committed
					maybe_match_querier.map_or(true, |match_querier| {
Francisco Aguirre's avatar
Francisco Aguirre committed
						Location::try_from(match_querier).map_or(false, |match_querier| {
Gavin Wood's avatar
Gavin Wood committed
							querier.map_or(false, |q| q == &match_querier)
						})
					}),
			Some(QueryStatus::VersionNotifier { origin: r, .. }) =>
Francisco Aguirre's avatar
Francisco Aguirre committed
				Location::try_from(r).map_or(false, |r| origin == &r),
Gavin Wood's avatar
Gavin Wood committed
			_ => false,
Gavin Wood's avatar
Gavin Wood committed
	}
Gavin Wood's avatar
Gavin Wood committed
	fn on_response(
Francisco Aguirre's avatar
Francisco Aguirre committed
		origin: &Location,
Gavin Wood's avatar
Gavin Wood committed
		query_id: QueryId,
Francisco Aguirre's avatar
Francisco Aguirre committed
		querier: Option<&Location>,
Gavin Wood's avatar
Gavin Wood committed
		response: Response,
		max_weight: Weight,
		_context: &XcmContext,
	) -> Weight {
Francisco Aguirre's avatar
Francisco Aguirre committed
		let origin = origin.clone();
Gavin Wood's avatar
Gavin Wood committed
		match (response, Queries::<T>::get(query_id)) {
			(
				Response::Version(v),
				Some(QueryStatus::VersionNotifier { origin: expected_origin, is_active }),
			) => {
Francisco Aguirre's avatar
Francisco Aguirre committed
				let origin: Location = match expected_origin.try_into() {
					Ok(o) if o == origin => o,
Gavin Wood's avatar
Gavin Wood committed
					Ok(o) => {
						Self::deposit_event(Event::InvalidResponder {
Francisco Aguirre's avatar
Francisco Aguirre committed
							origin: origin.clone(),
							query_id,
							expected_location: Some(o),
						});
Gavin Wood's avatar
Gavin Wood committed
						return Weight::zero()
					},
					_ => {
						Self::deposit_event(Event::InvalidResponder {
Francisco Aguirre's avatar
Francisco Aguirre committed
							origin: origin.clone(),
							query_id,
							expected_location: None,
						});
Gavin Wood's avatar
Gavin Wood committed
						// TODO #3735: Correct weight for this.
						return Weight::zero()
					},
				};
				// TODO #3735: Check max_weight is correct.
				if !is_active {
					Queries::<T>::insert(
						query_id,
Francisco Aguirre's avatar
Francisco Aguirre committed
						QueryStatus::VersionNotifier {
							origin: origin.clone().into(),
							is_active: true,
						},
Gavin Wood's avatar
Gavin Wood committed
				}
				// We're being notified of a version change.
Francisco Aguirre's avatar
Francisco Aguirre committed
				SupportedVersion::<T>::insert(XCM_VERSION, LatestVersionedLocation(&origin), v);
				Self::deposit_event(Event::SupportedVersionChanged {
					location: origin,
					version: v,
				});
Gavin Wood's avatar
Gavin Wood committed
				Weight::zero()
			},
			(
				response,
				Some(QueryStatus::Pending { responder, maybe_notify, maybe_match_querier, .. }),
			) => {
				if let Some(match_querier) = maybe_match_querier {
Francisco Aguirre's avatar
Francisco Aguirre committed
					let match_querier = match Location::try_from(match_querier) {
Gavin Wood's avatar
Gavin Wood committed
						Ok(mq) => mq,
Francisco Aguirre's avatar
Francisco Aguirre committed
							Self::deposit_event(Event::InvalidQuerierVersion {
								origin: origin.clone(),
								query_id,
							});
Gavin Wood's avatar
Gavin Wood committed
							return Weight::zero()