referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
universal_exports.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Traits and utilities to help with origin mutation and bridging.
18
19#![allow(deprecated)]
20
21use crate::InspectMessageQueues;
22use alloc::{vec, vec::Vec};
23use codec::{Decode, Encode};
24use core::{convert::TryInto, marker::PhantomData};
25use frame_support::{ensure, traits::Get};
26use xcm::prelude::*;
27use xcm_executor::traits::{validate_export, ExportXcm};
28use SendError::*;
29
30/// Returns the network ID and consensus location within that network of the remote
31/// location `dest` which is itself specified as a location relative to the local
32/// chain, itself situated at `universal_local` within the consensus universe. If
33/// `dest` is not a location in remote consensus, then an error is returned.
34pub fn ensure_is_remote(
35	universal_local: impl Into<InteriorLocation>,
36	dest: impl Into<Location>,
37) -> Result<(NetworkId, InteriorLocation), Location> {
38	let dest = dest.into();
39	let universal_local = universal_local.into();
40	let local_net = match universal_local.global_consensus() {
41		Ok(x) => x,
42		Err(_) => return Err(dest),
43	};
44	let universal_destination: InteriorLocation = universal_local
45		.into_location()
46		.appended_with(dest.clone())
47		.map_err(|x| x.1)?
48		.try_into()?;
49	let (remote_dest, remote_net) = match universal_destination.split_first() {
50		(d, Some(GlobalConsensus(n))) if n != local_net => (d, n),
51		_ => return Err(dest),
52	};
53	Ok((remote_net, remote_dest))
54}
55
56/// Implementation of `SendXcm` which uses the given `ExportXcm` implementation in order to forward
57/// the message over a bridge.
58///
59/// No effort is made to charge for any bridge fees, so this can only be used when it is known
60/// that the message sending cannot be abused in any way.
61///
62/// This is only useful when the local chain has bridging capabilities.
63#[deprecated(note = "Will be removed after July 2025; It uses hard-coded channel `0`, \
64	use `xcm_builder::LocalExporter` directly instead.")]
65pub struct UnpaidLocalExporter<Exporter, UniversalLocation>(
66	PhantomData<(Exporter, UniversalLocation)>,
67);
68impl<Exporter: ExportXcm, UniversalLocation: Get<InteriorLocation>> SendXcm
69	for UnpaidLocalExporter<Exporter, UniversalLocation>
70{
71	type Ticket = Exporter::Ticket;
72
73	fn validate(
74		dest: &mut Option<Location>,
75		msg: &mut Option<Xcm<()>>,
76	) -> SendResult<Exporter::Ticket> {
77		// This `clone` ensures that `dest` is not consumed in any case.
78		let d = dest.clone().ok_or(MissingArgument)?;
79		let universal_source = UniversalLocation::get();
80		let devolved = ensure_is_remote(universal_source.clone(), d).map_err(|error| {
81			tracing::debug!(target: "xcm::universal_exports", ?error, "Failed to devolve location");
82			NotApplicable
83		})?;
84		let (remote_network, remote_location) = devolved;
85		let xcm = msg.take().ok_or(MissingArgument)?;
86
87		validate_export::<Exporter>(
88			remote_network,
89			0,
90			universal_source,
91			remote_location,
92			xcm.clone(),
93		)
94		.inspect_err(|err| {
95			if let NotApplicable = err {
96				// We need to make sure that msg is not consumed in case of `NotApplicable`.
97				*msg = Some(xcm);
98			}
99		})
100	}
101
102	fn deliver(ticket: Exporter::Ticket) -> Result<XcmHash, SendError> {
103		Exporter::deliver(ticket)
104	}
105
106	#[cfg(feature = "runtime-benchmarks")]
107	fn ensure_successful_delivery(_: Option<Location>) {}
108}
109
110/// Implementation of `SendXcm` which uses the given `ExportXcm` implementation in order to forward
111/// the message over a bridge.
112///
113/// This is only useful when the local chain has bridging capabilities.
114pub struct LocalExporter<Exporter, UniversalLocation>(PhantomData<(Exporter, UniversalLocation)>);
115impl<Exporter: ExportXcm, UniversalLocation: Get<InteriorLocation>> SendXcm
116	for LocalExporter<Exporter, UniversalLocation>
117{
118	type Ticket = Exporter::Ticket;
119
120	fn validate(
121		dest: &mut Option<Location>,
122		msg: &mut Option<Xcm<()>>,
123	) -> SendResult<Exporter::Ticket> {
124		// This `clone` ensures that `dest` is not consumed in any case.
125		let d = dest.clone().ok_or(MissingArgument)?;
126		let universal_source = UniversalLocation::get();
127		let devolved = ensure_is_remote(universal_source.clone(), d).map_err(|error| {
128			tracing::debug!(target: "xcm::universal_exports", ?error, "Failed to devolve location");
129			NotApplicable
130		})?;
131		let (remote_network, remote_location) = devolved;
132		let xcm = msg.take().ok_or(MissingArgument)?;
133
134		let hash =
135			(Some(Location::here()), &remote_location).using_encoded(sp_io::hashing::blake2_128);
136		let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0);
137
138		validate_export::<Exporter>(
139			remote_network,
140			channel,
141			universal_source,
142			remote_location,
143			xcm.clone(),
144		)
145		.inspect_err(|err| {
146			if let NotApplicable = err {
147				// We need to make sure that msg is not consumed in case of `NotApplicable`.
148				*msg = Some(xcm);
149			}
150		})
151	}
152
153	fn deliver(ticket: Exporter::Ticket) -> Result<XcmHash, SendError> {
154		Exporter::deliver(ticket)
155	}
156
157	#[cfg(feature = "runtime-benchmarks")]
158	fn ensure_successful_delivery(_: Option<Location>) {}
159}
160
161pub trait ExporterFor {
162	/// Return the locally-routable bridge (if any) capable of forwarding `message` to the
163	/// `remote_location` on the remote `network`, together with the payment which is required.
164	///
165	/// The payment is specified from the local context, not the bridge chain. This is the
166	/// total amount to withdraw in to Holding and should cover both payment for the execution on
167	/// the bridge chain and payment for the use of the `ExportMessage` instruction.
168	fn exporter_for(
169		network: &NetworkId,
170		remote_location: &InteriorLocation,
171		message: &Xcm<()>,
172	) -> Option<(Location, Option<Asset>)>;
173}
174
175#[impl_trait_for_tuples::impl_for_tuples(30)]
176impl ExporterFor for Tuple {
177	fn exporter_for(
178		network: &NetworkId,
179		remote_location: &InteriorLocation,
180		message: &Xcm<()>,
181	) -> Option<(Location, Option<Asset>)> {
182		for_tuples!( #(
183			if let Some(r) = Tuple::exporter_for(network, remote_location, message) {
184				return Some(r);
185			}
186		)* );
187		None
188	}
189}
190
191/// Configuration item representing a single exporter in the `NetworkExportTable`.
192pub struct NetworkExportTableItem {
193	/// Supported remote network.
194	pub remote_network: NetworkId,
195	/// Remote location filter.
196	/// If `Some`, the requested remote location must be equal to one of the items in the vector.
197	/// These are locations in the remote network.
198	/// If `None`, then the check is skipped.
199	pub remote_location_filter: Option<Vec<InteriorLocation>>,
200	/// Locally-routable bridge with bridging capabilities to the `remote_network` and
201	/// `remote_location`. See [`ExporterFor`] for more details.
202	pub bridge: Location,
203	/// The local payment.
204	/// See [`ExporterFor`] for more details.
205	pub payment: Option<Asset>,
206}
207
208impl NetworkExportTableItem {
209	pub fn new(
210		remote_network: NetworkId,
211		remote_location_filter: Option<Vec<InteriorLocation>>,
212		bridge: Location,
213		payment: Option<Asset>,
214	) -> Self {
215		Self { remote_network, remote_location_filter, bridge, payment }
216	}
217}
218
219/// An adapter for the implementation of `ExporterFor`, which attempts to find the
220/// `(bridge_location, payment)` for the requested `network` and `remote_location` in the provided
221/// `T` table containing various exporters.
222pub struct NetworkExportTable<T>(core::marker::PhantomData<T>);
223impl<T: Get<Vec<NetworkExportTableItem>>> ExporterFor for NetworkExportTable<T> {
224	fn exporter_for(
225		network: &NetworkId,
226		remote_location: &InteriorLocation,
227		_: &Xcm<()>,
228	) -> Option<(Location, Option<Asset>)> {
229		T::get()
230			.into_iter()
231			.find(|item| {
232				&item.remote_network == network &&
233					item.remote_location_filter
234						.as_ref()
235						.map(|filters| filters.iter().any(|filter| filter == remote_location))
236						.unwrap_or(true)
237			})
238			.map(|item| (item.bridge, item.payment))
239	}
240}
241
242/// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction
243/// and sends it to a destination known to be able to handle it.
244///
245/// No effort is made to make payment to the bridge for its services, so the bridge location
246/// must have been configured with a barrier rule allowing unpaid execution for this message
247/// coming from our origin.
248///
249/// This is only useful if we have special dispensation by the remote bridges to have the
250/// `ExportMessage` instruction executed without payment.
251///
252/// The `XcmHash` value returned by `deliver` will always be the same as that returned by the
253/// message exporter (`Bridges`). Generally this should take notice of the message should it
254/// end with the `SetTopic` instruction.
255///
256/// In the case that the message ends with a `SetTopic(T)` (as should be the case if the top-level
257/// router is `WithUniqueTopic`), then the forwarding message (i.e. the one carrying the
258/// export instruction *to* the bridge in local consensus) will also end with the same
259/// `SetTopic(T)`. If this is not the case, then the onward message will not be given the `SetTopic`
260/// afterward.
261pub struct UnpaidRemoteExporter<Bridges, Router, UniversalLocation>(
262	PhantomData<(Bridges, Router, UniversalLocation)>,
263);
264impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocation>> SendXcm
265	for UnpaidRemoteExporter<Bridges, Router, UniversalLocation>
266{
267	type Ticket = Router::Ticket;
268
269	fn validate(
270		dest: &mut Option<Location>,
271		msg: &mut Option<Xcm<()>>,
272	) -> SendResult<Router::Ticket> {
273		// This `clone` ensures that `dest` is not consumed in any case.
274		let d = dest.clone().ok_or(MissingArgument)?;
275		let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|error| {
276			tracing::debug!(target: "xcm::universal_exports", ?error, "Failed to devolve location");
277			NotApplicable
278		})?;
279		let (remote_network, remote_location) = devolved;
280		let xcm = msg.take().ok_or(MissingArgument)?;
281
282		// find exporter
283		let Some((bridge, maybe_payment)) =
284			Bridges::exporter_for(&remote_network, &remote_location, &xcm)
285		else {
286			// We need to make sure that msg is not consumed in case of `NotApplicable`.
287			*msg = Some(xcm);
288			return Err(NotApplicable)
289		};
290
291		// `xcm` should already end with `SetTopic` - if it does, then extract and derive into
292		// an onward topic ID.
293		let maybe_forward_id = match xcm.last() {
294			Some(SetTopic(t)) => Some(*t),
295			_ => None,
296		};
297
298		// We then send a normal message to the bridge asking it to export the prepended
299		// message to the remote chain. This will only work if the bridge will do the message
300		// export for free. Common-good chains will typically be afforded this.
301		let mut message = Xcm(vec![
302			UnpaidExecution { weight_limit: Unlimited, check_origin: None },
303			ExportMessage {
304				network: remote_network,
305				destination: remote_location,
306				xcm: xcm.clone(),
307			},
308		]);
309		if let Some(forward_id) = maybe_forward_id {
310			message.0.push(SetTopic(forward_id));
311		}
312		let (v, mut cost) = validate_send::<Router>(bridge, message).inspect_err(|err| {
313			if let NotApplicable = err {
314				// We need to make sure that msg is not consumed in case of `NotApplicable`.
315				*msg = Some(xcm);
316			}
317		})?;
318		if let Some(bridge_payment) = maybe_payment {
319			cost.push(bridge_payment);
320		}
321		Ok((v, cost))
322	}
323
324	fn deliver(validation: Self::Ticket) -> Result<XcmHash, SendError> {
325		Router::deliver(validation)
326	}
327
328	#[cfg(feature = "runtime-benchmarks")]
329	fn ensure_successful_delivery(location: Option<Location>) {
330		Router::ensure_successful_delivery(location);
331	}
332}
333
334impl<Bridges, Router, UniversalLocation> InspectMessageQueues
335	for UnpaidRemoteExporter<Bridges, Router, UniversalLocation>
336{
337	fn clear_messages() {}
338
339	/// This router needs to implement `InspectMessageQueues` but doesn't have to
340	/// return any messages, since it just reuses the `XcmpQueue` router.
341	fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
342		Vec::new()
343	}
344}
345
346/// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction
347/// and sends it to a destination known to be able to handle it.
348///
349/// The `ExportMessage` instruction on the bridge is paid for from the local chain's sovereign
350/// account on the bridge. The amount paid is determined through the `ExporterFor` trait.
351///
352/// The `XcmHash` value returned by `deliver` will always be the same as that returned by the
353/// message exporter (`Bridges`). Generally this should take notice of the message should it
354/// end with the `SetTopic` instruction.
355///
356/// In the case that the message ends with a `SetTopic(T)` (as should be the case if the top-level
357/// router is `WithUniqueTopic`), then the forwarding message (i.e. the one carrying the
358/// export instruction *to* the bridge in local consensus) will also end with the same
359/// `SetTopic(T)`. If this is not the case, then the onward message will not be given the `SetTopic`
360/// afterward.
361pub struct SovereignPaidRemoteExporter<Bridges, Router, UniversalLocation>(
362	PhantomData<(Bridges, Router, UniversalLocation)>,
363);
364impl<Bridges: ExporterFor, Router: SendXcm, UniversalLocation: Get<InteriorLocation>> SendXcm
365	for SovereignPaidRemoteExporter<Bridges, Router, UniversalLocation>
366{
367	type Ticket = Router::Ticket;
368
369	fn validate(
370		dest: &mut Option<Location>,
371		msg: &mut Option<Xcm<()>>,
372	) -> SendResult<Router::Ticket> {
373		// This `clone` ensures that `dest` is not consumed in any case.
374		let d = dest.clone().ok_or(MissingArgument)?;
375		let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|error| {
376			tracing::debug!(target: "xcm::universal_exports", ?error, "Failed to devolve location");
377			NotApplicable
378		})?;
379		let (remote_network, remote_location) = devolved;
380		let xcm = msg.take().ok_or(MissingArgument)?;
381
382		// find exporter
383		let Some((bridge, maybe_payment)) =
384			Bridges::exporter_for(&remote_network, &remote_location, &xcm)
385		else {
386			// We need to make sure that msg is not consumed in case of `NotApplicable`.
387			*msg = Some(xcm);
388			return Err(NotApplicable)
389		};
390
391		// `xcm` should already end with `SetTopic` - if it does, then extract and derive into
392		// an onward topic ID.
393		let maybe_forward_id = match xcm.last() {
394			Some(SetTopic(t)) => Some(*t),
395			_ => None,
396		};
397
398		let local_from_bridge = UniversalLocation::get().invert_target(&bridge).map_err(|_| {
399			tracing::debug!(target: "xcm::universal_exports", "Failed to invert bridge location");
400			Unroutable
401		})?;
402		let export_instruction = ExportMessage {
403			network: remote_network,
404			destination: remote_location,
405			xcm: xcm.clone(),
406		};
407
408		let mut message = Xcm(if let Some(ref payment) = maybe_payment {
409			let fees =
410				payment.clone().reanchored(&bridge, &UniversalLocation::get()).map_err(|_| {
411					tracing::debug!(target: "xcm::universal_exports", "Failed to reanchor payment");
412					Unroutable
413				})?;
414			vec![
415				WithdrawAsset(fees.clone().into()),
416				BuyExecution { fees, weight_limit: Unlimited },
417				// `SetAppendix` ensures that `fees` are not trapped in any case, for example, when
418				// `ExportXcm::validate` encounters an error during the processing of
419				// `ExportMessage`.
420				SetAppendix(Xcm(vec![DepositAsset {
421					assets: AllCounted(1).into(),
422					beneficiary: local_from_bridge,
423				}])),
424				export_instruction,
425			]
426		} else {
427			vec![export_instruction]
428		});
429		if let Some(forward_id) = maybe_forward_id {
430			message.0.push(SetTopic(forward_id));
431		}
432
433		// We then send a normal message to the bridge asking it to export the prepended
434		// message to the remote chain.
435		let (v, mut cost) = validate_send::<Router>(bridge, message).inspect_err(|err| {
436			if let NotApplicable = err {
437				// We need to make sure that msg is not consumed in case of `NotApplicable`.
438				*msg = Some(xcm);
439			}
440		})?;
441		if let Some(bridge_payment) = maybe_payment {
442			cost.push(bridge_payment);
443		}
444		Ok((v, cost))
445	}
446
447	fn deliver(ticket: Router::Ticket) -> Result<XcmHash, SendError> {
448		Router::deliver(ticket)
449	}
450
451	#[cfg(feature = "runtime-benchmarks")]
452	fn ensure_successful_delivery(location: Option<Location>) {
453		Router::ensure_successful_delivery(location);
454	}
455}
456
457impl<Bridges, Router, UniversalLocation> InspectMessageQueues
458	for SovereignPaidRemoteExporter<Bridges, Router, UniversalLocation>
459{
460	fn clear_messages() {}
461
462	/// This router needs to implement `InspectMessageQueues` but doesn't have to
463	/// return any messages, since it just reuses the `XcmpQueue` router.
464	fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
465		Vec::new()
466	}
467}
468
469pub trait DispatchBlob {
470	/// Takes an incoming blob from over some point-to-point link (usually from some sort of
471	/// inter-consensus bridge) and then does what needs to be done with it. Usually this means
472	/// forwarding it on into some other location sharing our consensus or possibly just enqueuing
473	/// it for execution locally if it is destined for the local chain.
474	///
475	/// NOTE: The API does not provide for any kind of weight or fee management; the size of the
476	/// `blob` is known to the caller and so the operation must have a linear weight relative to
477	/// `blob`'s length. This means that you will generally only want to **enqueue** the blob, not
478	/// enact it. Fees must be handled by the caller.
479	fn dispatch_blob(blob: Vec<u8>) -> Result<(), DispatchBlobError>;
480}
481
482pub trait HaulBlob {
483	/// Sends a blob over some point-to-point link. This will generally be implemented by a bridge.
484	fn haul_blob(blob: Vec<u8>) -> Result<(), HaulBlobError>;
485}
486
487#[derive(Clone, Copy, Debug, PartialEq, Eq)]
488pub enum HaulBlobError {
489	/// Represents point-to-point link failure with a human-readable explanation of the specific
490	/// issue is provided.
491	Transport(&'static str),
492}
493
494impl From<HaulBlobError> for SendError {
495	fn from(err: HaulBlobError) -> Self {
496		match err {
497			HaulBlobError::Transport(reason) => SendError::Transport(reason),
498		}
499	}
500}
501
502#[derive(Clone, Encode, Decode)]
503pub struct BridgeMessage {
504	/// The message destination as a *Universal Location*. This means it begins with a
505	/// `GlobalConsensus` junction describing the network under which global consensus happens.
506	/// If this does not match our global consensus then it's a fatal error.
507	pub universal_dest: VersionedInteriorLocation,
508	pub message: VersionedXcm<()>,
509}
510
511#[derive(Clone, Copy, Debug, PartialEq, Eq)]
512pub enum DispatchBlobError {
513	Unbridgable,
514	InvalidEncoding,
515	UnsupportedLocationVersion,
516	UnsupportedXcmVersion,
517	RoutingError,
518	NonUniversalDestination,
519	WrongGlobal,
520}
521
522pub struct BridgeBlobDispatcher<Router, OurPlace, OurPlaceBridgeInstance>(
523	PhantomData<(Router, OurPlace, OurPlaceBridgeInstance)>,
524);
525impl<
526		Router: SendXcm,
527		OurPlace: Get<InteriorLocation>,
528		OurPlaceBridgeInstance: Get<Option<InteriorLocation>>,
529	> DispatchBlob for BridgeBlobDispatcher<Router, OurPlace, OurPlaceBridgeInstance>
530{
531	fn dispatch_blob(blob: Vec<u8>) -> Result<(), DispatchBlobError> {
532		let our_universal = OurPlace::get();
533		let our_global = our_universal.global_consensus().map_err(|()| {
534			tracing::debug!(target: "xcm::universal_exports", "Failed to get global consensus");
535			DispatchBlobError::Unbridgable
536		})?;
537		let BridgeMessage { universal_dest, message } =
538			Decode::decode(&mut &blob[..]).map_err(|error| {
539				tracing::debug!(target: "xcm::universal_exports", ?error, "Failed to decode blob");
540				DispatchBlobError::InvalidEncoding
541			})?;
542		let universal_dest: InteriorLocation = universal_dest.try_into().map_err(|_| {
543			tracing::debug!(target: "xcm::universal_exports", "Failed to convert universal destination");
544			DispatchBlobError::UnsupportedLocationVersion
545		})?;
546		// `universal_dest` is the desired destination within the universe: first we need to check
547		// we're in the right global consensus.
548		let intended_global = universal_dest
549			.global_consensus()
550			.map_err(|()| {
551				tracing::debug!(target: "xcm::universal_exports", "Failed to get global consensus from universal destination");
552				DispatchBlobError::NonUniversalDestination })?;
553		ensure!(intended_global == our_global, DispatchBlobError::WrongGlobal);
554		let dest = universal_dest.relative_to(&our_universal);
555		let mut message: Xcm<()> = message.try_into().map_err(|_| {
556			tracing::debug!(target: "xcm::universal_exports", "Failed to convert message");
557			DispatchBlobError::UnsupportedXcmVersion
558		})?;
559
560		// Prepend our bridge instance discriminator.
561		// Can be used for fine-grained control of origin on destination in case of multiple bridge
562		// instances, e.g. restrict `type UniversalAliases` and `UniversalOrigin` instruction to
563		// trust just particular bridge instance for `NetworkId`.
564		if let Some(bridge_instance) = OurPlaceBridgeInstance::get() {
565			message.0.insert(0, DescendOrigin(bridge_instance));
566		}
567
568		send_xcm::<Router>(dest, message).map_err(|error| {
569			tracing::debug!(target: "xcm::universal_exports", ?error, "Failed to send XCM");
570			DispatchBlobError::RoutingError
571		})?;
572		Ok(())
573	}
574}
575
576pub struct HaulBlobExporter<Bridge, BridgedNetwork, DestinationVersion, Price>(
577	PhantomData<(Bridge, BridgedNetwork, DestinationVersion, Price)>,
578);
579/// `ExportXcm` implementation for `HaulBlobExporter`.
580///
581/// # Type Parameters
582///
583/// ```text
584/// - Bridge: Implements `HaulBlob`.
585/// - BridgedNetwork: The relative location of the bridged consensus system with the expected `GlobalConsensus` junction.
586/// - DestinationVersion: Implements `GetVersion` for retrieving XCM version for the destination.
587/// - Price: potential fees for exporting.
588/// ```
589impl<
590		Bridge: HaulBlob,
591		BridgedNetwork: Get<Location>,
592		DestinationVersion: GetVersion,
593		Price: Get<Assets>,
594	> ExportXcm for HaulBlobExporter<Bridge, BridgedNetwork, DestinationVersion, Price>
595{
596	type Ticket = (Vec<u8>, XcmHash);
597
598	fn validate(
599		network: NetworkId,
600		_channel: u32,
601		universal_source: &mut Option<InteriorLocation>,
602		destination: &mut Option<InteriorLocation>,
603		message: &mut Option<Xcm<()>>,
604	) -> Result<((Vec<u8>, XcmHash), Assets), SendError> {
605		let (bridged_network, bridged_network_location_parents) = {
606			let Location { parents, interior: mut junctions } = BridgedNetwork::get();
607			match junctions.take_first() {
608				Some(GlobalConsensus(network)) => (network, parents),
609				_ => return Err(NotApplicable),
610			}
611		};
612		ensure!(&network == &bridged_network, NotApplicable);
613		// We don't/can't use the `channel` for this adapter.
614		let dest = destination.take().ok_or(SendError::MissingArgument)?;
615
616		// Let's resolve the known/supported XCM version for the destination because we don't know
617		// if it supports the same/latest version.
618		let (universal_dest, version) =
619			match dest.pushed_front_with(GlobalConsensus(bridged_network)) {
620				Ok(d) => {
621					let version = DestinationVersion::get_version_for(&Location::from(
622						AncestorThen(bridged_network_location_parents, d.clone()),
623					))
624					.ok_or(SendError::DestinationUnsupported)?;
625					(d, version)
626				},
627				Err((dest, _)) => {
628					*destination = Some(dest);
629					return Err(NotApplicable)
630				},
631			};
632
633		// Let's adjust XCM with `UniversalOrigin`, `DescendOrigin` and`SetTopic`.
634		let (local_net, local_sub) = universal_source
635			.take()
636			.ok_or(SendError::MissingArgument)?
637			.split_global()
638			.map_err(|()| {
639				tracing::debug!(target: "xcm::universal_exports", "Failed to split global consensus");
640				SendError::Unroutable
641			})?;
642		let mut message = message.take().ok_or(SendError::MissingArgument)?;
643		let maybe_id = match message.last() {
644			Some(SetTopic(t)) => Some(*t),
645			_ => None,
646		};
647		message.0.insert(0, UniversalOrigin(GlobalConsensus(local_net)));
648		if local_sub != Here {
649			message.0.insert(1, DescendOrigin(local_sub));
650		}
651
652		// We cannot use the latest `Versioned` because we don't know if the target chain already
653		// supports the same version. Therefore, we better control the destination version with best
654		// efforts.
655		let message = VersionedXcm::from(message).into_version(version).map_err(|()| {
656			tracing::debug!(target: "xcm::universal_exports", "Failed to convert message to versioned XCM");
657			SendError::DestinationUnsupported
658		})?;
659		let universal_dest = VersionedInteriorLocation::from(universal_dest)
660			.into_version(version)
661			.map_err(|()| {
662				tracing::debug!(target: "xcm::universal_exports", "Failed to convert destination to versioned location");
663				SendError::DestinationUnsupported })?;
664
665		let id = maybe_id.unwrap_or_else(|| message.using_encoded(sp_io::hashing::blake2_256));
666		let blob = BridgeMessage { universal_dest, message }.encode();
667		Ok(((blob, id), Price::get()))
668	}
669
670	fn deliver((blob, id): (Vec<u8>, XcmHash)) -> Result<XcmHash, SendError> {
671		Bridge::haul_blob(blob)?;
672		Ok(id)
673	}
674}
675
676#[cfg(test)]
677mod tests {
678	use super::*;
679	use frame_support::{
680		assert_err, assert_ok,
681		traits::{Contains, Equals},
682	};
683
684	#[test]
685	fn ensure_is_remote_works() {
686		// A Kusama parachain is remote from the Polkadot Relay.
687		let x = ensure_is_remote(Polkadot, (Parent, Kusama, Parachain(1000)));
688		assert_eq!(x, Ok((Kusama, Parachain(1000).into())));
689
690		// Polkadot Relay is remote from a Kusama parachain.
691		let x = ensure_is_remote((Kusama, Parachain(1000)), (Parent, Parent, Polkadot));
692		assert_eq!(x, Ok((Polkadot, Here)));
693
694		// Our own parachain is local.
695		let x = ensure_is_remote(Polkadot, Parachain(1000));
696		assert_eq!(x, Err(Parachain(1000).into()));
697
698		// Polkadot's parachain is not remote if we are Polkadot.
699		let x = ensure_is_remote(Polkadot, (Parent, Polkadot, Parachain(1000)));
700		assert_eq!(x, Err((Parent, Polkadot, Parachain(1000)).into()));
701
702		// If we don't have a consensus ancestor, then we cannot determine remoteness.
703		let x = ensure_is_remote((), (Parent, Polkadot, Parachain(1000)));
704		assert_eq!(x, Err((Parent, Polkadot, Parachain(1000)).into()));
705	}
706
707	pub struct OkFor<Filter>(PhantomData<Filter>);
708	impl<Filter: Contains<Location>> SendXcm for OkFor<Filter> {
709		type Ticket = ();
710
711		fn validate(
712			destination: &mut Option<Location>,
713			_message: &mut Option<Xcm<()>>,
714		) -> SendResult<Self::Ticket> {
715			if let Some(d) = destination.as_ref() {
716				if Filter::contains(&d) {
717					return Ok(((), Assets::new()))
718				}
719			}
720			Err(NotApplicable)
721		}
722
723		fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> {
724			Ok([0; 32])
725		}
726
727		#[cfg(feature = "runtime-benchmarks")]
728		fn ensure_successful_delivery(_: Option<Location>) {}
729	}
730	impl<Filter: Contains<(NetworkId, InteriorLocation)>> ExportXcm for OkFor<Filter> {
731		type Ticket = ();
732
733		fn validate(
734			network: NetworkId,
735			_: u32,
736			_: &mut Option<InteriorLocation>,
737			destination: &mut Option<InteriorLocation>,
738			_: &mut Option<Xcm<()>>,
739		) -> SendResult<Self::Ticket> {
740			if let Some(d) = destination.as_ref() {
741				if Filter::contains(&(network, d.clone())) {
742					return Ok(((), Assets::new()))
743				}
744			}
745			Err(NotApplicable)
746		}
747
748		fn deliver(_ticket: Self::Ticket) -> Result<XcmHash, SendError> {
749			Ok([1; 32])
750		}
751	}
752
753	/// Generic test case asserting that dest and msg is not consumed by `validate` implementation
754	/// of `SendXcm` in case of expected result.
755	fn ensure_validate_does_not_consume_dest_or_msg<S: SendXcm>(
756		dest: Location,
757		assert_result: impl Fn(SendResult<S::Ticket>),
758	) {
759		let mut dest_wrapper = Some(dest.clone());
760		let msg = Xcm::<()>::new();
761		let mut msg_wrapper = Some(msg.clone());
762
763		assert_result(S::validate(&mut dest_wrapper, &mut msg_wrapper));
764
765		// ensure dest and msg are untouched
766		assert_eq!(Some(dest), dest_wrapper);
767		assert_eq!(Some(msg), msg_wrapper);
768	}
769
770	#[test]
771	fn local_exporters_works() {
772		frame_support::parameter_types! {
773			pub Local: NetworkId = ByGenesis([0; 32]);
774			pub UniversalLocation: InteriorLocation = [GlobalConsensus(Local::get()), Parachain(1234)].into();
775			pub DifferentRemote: NetworkId = ByGenesis([22; 32]);
776			pub RemoteDestination: Junction = Parachain(9657);
777			pub RoutableBridgeFilter: (NetworkId, InteriorLocation) = (DifferentRemote::get(), RemoteDestination::get().into());
778		}
779		type RoutableBridgeExporter = OkFor<Equals<RoutableBridgeFilter>>;
780		type NotApplicableBridgeExporter = OkFor<()>;
781		assert_ok!(validate_export::<RoutableBridgeExporter>(
782			DifferentRemote::get(),
783			0,
784			UniversalLocation::get(),
785			RemoteDestination::get().into(),
786			Xcm::default()
787		));
788		assert_err!(
789			validate_export::<NotApplicableBridgeExporter>(
790				DifferentRemote::get(),
791				0,
792				UniversalLocation::get(),
793				RemoteDestination::get().into(),
794				Xcm::default()
795			),
796			NotApplicable
797		);
798
799		// 1. check with local destination (should be remote)
800		let local_dest: Location = (Parent, Parachain(5678)).into();
801		assert!(ensure_is_remote(UniversalLocation::get(), local_dest.clone()).is_err());
802
803		// LocalExporter
804		ensure_validate_does_not_consume_dest_or_msg::<
805			LocalExporter<RoutableBridgeExporter, UniversalLocation>,
806		>(local_dest.clone(), |result| assert_eq!(Err(NotApplicable), result));
807
808		// 2. check with not applicable from the inner router (using `NotApplicableBridgeSender`)
809		let remote_dest: Location =
810			(Parent, Parent, DifferentRemote::get(), RemoteDestination::get()).into();
811		assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok());
812
813		// LocalExporter
814		ensure_validate_does_not_consume_dest_or_msg::<
815			LocalExporter<NotApplicableBridgeExporter, UniversalLocation>,
816		>(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result));
817
818		// 3. Ok - deliver
819		// UnpaidRemoteExporter
820		assert_ok!(send_xcm::<LocalExporter<RoutableBridgeExporter, UniversalLocation>>(
821			remote_dest,
822			Xcm::default()
823		));
824	}
825
826	#[test]
827	fn remote_exporters_works() {
828		frame_support::parameter_types! {
829			pub Local: NetworkId = ByGenesis([0; 32]);
830			pub UniversalLocation: InteriorLocation = [GlobalConsensus(Local::get()), Parachain(1234)].into();
831			pub DifferentRemote: NetworkId = ByGenesis([22; 32]);
832			pub RoutableBridge: Location = Location::new(1, Parachain(9657));
833			// not routable
834			pub NotApplicableBridgeTable: Vec<NetworkExportTableItem> = vec![];
835			// routable
836			pub RoutableBridgeTable: Vec<NetworkExportTableItem> = vec![
837				NetworkExportTableItem::new(
838					DifferentRemote::get(),
839					None,
840					RoutableBridge::get(),
841					None
842				)
843			];
844		}
845		type RoutableBridgeSender = OkFor<Equals<RoutableBridge>>;
846		type NotApplicableBridgeSender = OkFor<()>;
847		assert_ok!(validate_send::<RoutableBridgeSender>(RoutableBridge::get(), Xcm::default()));
848		assert_err!(
849			validate_send::<NotApplicableBridgeSender>(RoutableBridge::get(), Xcm::default()),
850			NotApplicable
851		);
852
853		// 1. check with local destination (should be remote)
854		let local_dest: Location = (Parent, Parachain(5678)).into();
855		assert!(ensure_is_remote(UniversalLocation::get(), local_dest.clone()).is_err());
856
857		// UnpaidRemoteExporter
858		ensure_validate_does_not_consume_dest_or_msg::<
859			UnpaidRemoteExporter<
860				NetworkExportTable<RoutableBridgeTable>,
861				RoutableBridgeSender,
862				UniversalLocation,
863			>,
864		>(local_dest.clone(), |result| assert_eq!(Err(NotApplicable), result));
865		// SovereignPaidRemoteExporter
866		ensure_validate_does_not_consume_dest_or_msg::<
867			SovereignPaidRemoteExporter<
868				NetworkExportTable<RoutableBridgeTable>,
869				RoutableBridgeSender,
870				UniversalLocation,
871			>,
872		>(local_dest, |result| assert_eq!(Err(NotApplicable), result));
873
874		// 2. check with not applicable destination (`NotApplicableBridgeTable`)
875		let remote_dest: Location = (Parent, Parent, DifferentRemote::get()).into();
876		assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok());
877
878		// UnpaidRemoteExporter
879		ensure_validate_does_not_consume_dest_or_msg::<
880			UnpaidRemoteExporter<
881				NetworkExportTable<NotApplicableBridgeTable>,
882				RoutableBridgeSender,
883				UniversalLocation,
884			>,
885		>(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result));
886		// SovereignPaidRemoteExporter
887		ensure_validate_does_not_consume_dest_or_msg::<
888			SovereignPaidRemoteExporter<
889				NetworkExportTable<NotApplicableBridgeTable>,
890				RoutableBridgeSender,
891				UniversalLocation,
892			>,
893		>(remote_dest, |result| assert_eq!(Err(NotApplicable), result));
894
895		// 3. check with not applicable from the inner router (using `NotApplicableBridgeSender`)
896		let remote_dest: Location = (Parent, Parent, DifferentRemote::get()).into();
897		assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok());
898
899		// UnpaidRemoteExporter
900		ensure_validate_does_not_consume_dest_or_msg::<
901			UnpaidRemoteExporter<
902				NetworkExportTable<RoutableBridgeTable>,
903				NotApplicableBridgeSender,
904				UniversalLocation,
905			>,
906		>(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result));
907		// SovereignPaidRemoteExporter
908		ensure_validate_does_not_consume_dest_or_msg::<
909			SovereignPaidRemoteExporter<
910				NetworkExportTable<RoutableBridgeTable>,
911				NotApplicableBridgeSender,
912				UniversalLocation,
913			>,
914		>(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result));
915
916		// 4. Ok - deliver
917		// UnpaidRemoteExporter
918		assert_ok!(send_xcm::<
919			UnpaidRemoteExporter<
920				NetworkExportTable<RoutableBridgeTable>,
921				RoutableBridgeSender,
922				UniversalLocation,
923			>,
924		>(remote_dest.clone(), Xcm::default()));
925		// SovereignPaidRemoteExporter
926		assert_ok!(send_xcm::<
927			SovereignPaidRemoteExporter<
928				NetworkExportTable<RoutableBridgeTable>,
929				RoutableBridgeSender,
930				UniversalLocation,
931			>,
932		>(remote_dest, Xcm::default()));
933	}
934
935	#[test]
936	fn network_export_table_works() {
937		frame_support::parameter_types! {
938			pub NetworkA: NetworkId = ByGenesis([0; 32]);
939			pub Parachain1000InNetworkA: InteriorLocation = [Parachain(1000)].into();
940			pub Parachain2000InNetworkA: InteriorLocation = [Parachain(2000)].into();
941
942			pub NetworkB: NetworkId = ByGenesis([1; 32]);
943
944			pub BridgeToALocation: Location = Location::new(1, [Parachain(1234)]);
945			pub BridgeToBLocation: Location = Location::new(1, [Parachain(4321)]);
946
947			pub PaymentForNetworkAAndParachain2000: Asset = (Location::parent(), 150).into();
948
949			pub BridgeTable: alloc::vec::Vec<NetworkExportTableItem> = alloc::vec![
950				// NetworkA allows `Parachain(1000)` as remote location WITHOUT payment.
951				NetworkExportTableItem::new(
952					NetworkA::get(),
953					Some(vec![Parachain1000InNetworkA::get()]),
954					BridgeToALocation::get(),
955					None
956				),
957				// NetworkA allows `Parachain(2000)` as remote location WITH payment.
958				NetworkExportTableItem::new(
959					NetworkA::get(),
960					Some(vec![Parachain2000InNetworkA::get()]),
961					BridgeToALocation::get(),
962					Some(PaymentForNetworkAAndParachain2000::get())
963				),
964				// NetworkB allows all remote location.
965				NetworkExportTableItem::new(
966					NetworkB::get(),
967					None,
968					BridgeToBLocation::get(),
969					None
970				)
971			];
972		}
973
974		let test_data: Vec<(NetworkId, InteriorLocation, Option<(Location, Option<Asset>)>)> = vec![
975			(NetworkA::get(), [Parachain(1000)].into(), Some((BridgeToALocation::get(), None))),
976			(NetworkA::get(), [Parachain(1000), GeneralIndex(1)].into(), None),
977			(
978				NetworkA::get(),
979				[Parachain(2000)].into(),
980				Some((BridgeToALocation::get(), Some(PaymentForNetworkAAndParachain2000::get()))),
981			),
982			(NetworkA::get(), [Parachain(2000), GeneralIndex(1)].into(), None),
983			(NetworkA::get(), [Parachain(3000)].into(), None),
984			(NetworkB::get(), [Parachain(1000)].into(), Some((BridgeToBLocation::get(), None))),
985			(NetworkB::get(), [Parachain(2000)].into(), Some((BridgeToBLocation::get(), None))),
986			(NetworkB::get(), [Parachain(3000)].into(), Some((BridgeToBLocation::get(), None))),
987		];
988
989		for (network, remote_location, expected_result) in test_data {
990			assert_eq!(
991				NetworkExportTable::<BridgeTable>::exporter_for(
992					&network,
993					&remote_location,
994					&Xcm::default()
995				),
996				expected_result,
997				"expected_result: {:?} not matched for network: {:?} and remote_location: {:?}",
998				expected_result,
999				network,
1000				remote_location,
1001			)
1002		}
1003	}
1004}