// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
//! Traits and utilities to help with origin mutation and bridging.
use frame_support::{ensure, traits::Get};
use parity_scale_codec::{Decode, Encode};
use sp_std::{convert::TryInto, marker::PhantomData, prelude::*};
use xcm::prelude::*;
use xcm_executor::traits::{validate_export, ExportXcm};
use SendError::*;
/// Returns the network ID and consensus location within that network of the remote
/// location `dest` which is itself specified as a location relative to the local
/// chain, itself situated at `universal_local` within the consensus universe. If
/// `dest` is not a location in remote consensus, then an error is returned.
pub fn ensure_is_remote(
universal_local: impl Into,
dest: impl Into,
) -> Result<(NetworkId, InteriorLocation), Location> {
let dest = dest.into();
let universal_local = universal_local.into();
let local_net = match universal_local.global_consensus() {
Ok(x) => x,
Err(_) => return Err(dest),
};
let universal_destination: InteriorLocation = universal_local
.into_location()
.appended_with(dest.clone())
.map_err(|x| x.1)?
.try_into()?;
let (remote_dest, remote_net) = match universal_destination.split_first() {
(d, Some(GlobalConsensus(n))) if n != local_net => (d, n),
_ => return Err(dest),
};
Ok((remote_net, remote_dest))
}
/// Implementation of `SendXcm` which uses the given `ExportXcm` implementation in order to forward
/// the message over a bridge.
///
/// No effort is made to charge for any bridge fees, so this can only be used when it is known
/// that the message sending cannot be abused in any way.
///
/// This is only useful when the local chain has bridging capabilities.
pub struct UnpaidLocalExporter(
PhantomData<(Exporter, UniversalLocation)>,
);
impl> SendXcm
for UnpaidLocalExporter
{
type Ticket = Exporter::Ticket;
fn validate(
dest: &mut Option,
xcm: &mut Option>,
) -> SendResult {
let d = dest.take().ok_or(MissingArgument)?;
let universal_source = UniversalLocation::get();
let devolved = match ensure_is_remote(universal_source.clone(), d) {
Ok(x) => x,
Err(d) => {
*dest = Some(d);
return Err(NotApplicable)
},
};
let (network, destination) = devolved;
let xcm = xcm.take().ok_or(SendError::MissingArgument)?;
validate_export::(network, 0, universal_source, destination, xcm)
}
fn deliver(ticket: Exporter::Ticket) -> Result {
Exporter::deliver(ticket)
}
}
pub trait ExporterFor {
/// Return the locally-routable bridge (if any) capable of forwarding `message` to the
/// `remote_location` on the remote `network`, together with the payment which is required.
///
/// The payment is specified from the local context, not the bridge chain. This is the
/// total amount to withdraw in to Holding and should cover both payment for the execution on
/// the bridge chain as well as payment for the use of the `ExportMessage` instruction.
fn exporter_for(
network: &NetworkId,
remote_location: &InteriorLocation,
message: &Xcm<()>,
) -> Option<(Location, Option)>;
}
#[impl_trait_for_tuples::impl_for_tuples(30)]
impl ExporterFor for Tuple {
fn exporter_for(
network: &NetworkId,
remote_location: &InteriorLocation,
message: &Xcm<()>,
) -> Option<(Location, Option)> {
for_tuples!( #(
if let Some(r) = Tuple::exporter_for(network, remote_location, message) {
return Some(r);
}
)* );
None
}
}
/// Configuration item representing a single exporter in the `NetworkExportTable`.
pub struct NetworkExportTableItem {
/// Supported remote network.
pub remote_network: NetworkId,
/// Remote location filter.
/// If `Some`, the requested remote location must be equal to one of the items in the vector.
/// These are locations in the remote network.
/// If `None`, then the check is skipped.
pub remote_location_filter: Option>,
/// Locally-routable bridge with bridging capabilities to the `remote_network` and
/// `remote_location`. See [`ExporterFor`] for more details.
pub bridge: Location,
/// The local payment.
/// See [`ExporterFor`] for more details.
pub payment: Option,
}
impl NetworkExportTableItem {
pub fn new(
remote_network: NetworkId,
remote_location_filter: Option>,
bridge: Location,
payment: Option,
) -> Self {
Self { remote_network, remote_location_filter, bridge, payment }
}
}
/// An adapter for the implementation of `ExporterFor`, which attempts to find the
/// `(bridge_location, payment)` for the requested `network` and `remote_location` in the provided
/// `T` table containing various exporters.
pub struct NetworkExportTable(sp_std::marker::PhantomData);
impl>> ExporterFor for NetworkExportTable {
fn exporter_for(
network: &NetworkId,
remote_location: &InteriorLocation,
_: &Xcm<()>,
) -> Option<(Location, Option)> {
T::get()
.into_iter()
.find(|item| {
&item.remote_network == network &&
item.remote_location_filter
.as_ref()
.map(|filters| filters.iter().any(|filter| filter == remote_location))
.unwrap_or(true)
})
.map(|item| (item.bridge, item.payment))
}
}
pub fn forward_id_for(original_id: &XcmHash) -> XcmHash {
(b"forward_id_for", original_id).using_encoded(sp_io::hashing::blake2_256)
}
/// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction
/// and sends it to a destination known to be able to handle it.
///
/// No effort is made to make payment to the bridge for its services, so the bridge location
/// must have been configured with a barrier rule allowing unpaid execution for this message
/// coming from our origin.
///
/// This is only useful if we have special dispensation by the remote bridges to have the
/// `ExportMessage` instruction executed without payment.
///
/// The `XcmHash` value returned by `deliver` will always be the same as that returned by the
/// message exporter (`Bridges`). Generally this should take notice of the message should it
/// end with the `SetTopic` instruction.
///
/// In the case that the message ends with a `SetTopic(T)` (as should be the case if the top-level
/// router is `WithUniqueTopic`), then the forwarding message (i.e. the one carrying the
/// export instruction *to* the bridge in local consensus) will also end with a `SetTopic` whose
/// inner is `forward_id_for(T)`. If this is not the case then the onward message will not be given
/// the `SetTopic` afterword.
pub struct UnpaidRemoteExporter(
PhantomData<(Bridges, Router, UniversalLocation)>,
);
impl> SendXcm
for UnpaidRemoteExporter
{
type Ticket = Router::Ticket;
fn validate(
dest: &mut Option,
msg: &mut Option>,
) -> SendResult {
let d = dest.clone().ok_or(MissingArgument)?;
let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?;
let (remote_network, remote_location) = devolved;
let xcm = msg.take().ok_or(MissingArgument)?;
// find exporter
let Some((bridge, maybe_payment)) =
Bridges::exporter_for(&remote_network, &remote_location, &xcm)
else {
// We need to make sure that msg is not consumed in case of `NotApplicable`.
*msg = Some(xcm);
return Err(SendError::NotApplicable)
};
ensure!(maybe_payment.is_none(), Unroutable);
// `xcm` should already end with `SetTopic` - if it does, then extract and derive into
// an onward topic ID.
let maybe_forward_id = match xcm.last() {
Some(SetTopic(t)) => Some(forward_id_for(t)),
_ => None,
};
// We then send a normal message to the bridge asking it to export the prepended
// message to the remote chain. This will only work if the bridge will do the message
// export for free. Common-good chains will typically be afforded this.
let mut message = Xcm(vec![
UnpaidExecution { weight_limit: Unlimited, check_origin: None },
ExportMessage { network: remote_network, destination: remote_location, xcm },
]);
if let Some(forward_id) = maybe_forward_id {
message.0.push(SetTopic(forward_id));
}
validate_send::(bridge, message)
}
fn deliver(validation: Self::Ticket) -> Result {
Router::deliver(validation)
}
}
/// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction
/// and sends it to a destination known to be able to handle it.
///
/// The `ExportMessage` instruction on the bridge is paid for from the local chain's sovereign
/// account on the bridge. The amount paid is determined through the `ExporterFor` trait.
///
/// The `XcmHash` value returned by `deliver` will always be the same as that returned by the
/// message exporter (`Bridges`). Generally this should take notice of the message should it
/// end with the `SetTopic` instruction.
///
/// In the case that the message ends with a `SetTopic(T)` (as should be the case if the top-level
/// router is `WithUniqueTopic`), then the forwarding message (i.e. the one carrying the
/// export instruction *to* the bridge in local consensus) will also end with a `SetTopic` whose
/// inner is `forward_id_for(T)`. If this is not the case then the onward message will not be given
/// the `SetTopic` afterword.
pub struct SovereignPaidRemoteExporter(
PhantomData<(Bridges, Router, UniversalLocation)>,
);
impl> SendXcm
for SovereignPaidRemoteExporter
{
type Ticket = Router::Ticket;
fn validate(
dest: &mut Option,
msg: &mut Option>,
) -> SendResult {
let d = dest.as_ref().ok_or(MissingArgument)?;
let devolved =
ensure_is_remote(UniversalLocation::get(), d.clone()).map_err(|_| NotApplicable)?;
let (remote_network, remote_location) = devolved;
let xcm = msg.take().ok_or(MissingArgument)?;
// find exporter
let Some((bridge, maybe_payment)) =
Bridges::exporter_for(&remote_network, &remote_location, &xcm)
else {
// We need to make sure that msg is not consumed in case of `NotApplicable`.
*msg = Some(xcm);
return Err(SendError::NotApplicable)
};
// `xcm` should already end with `SetTopic` - if it does, then extract and derive into
// an onward topic ID.
let maybe_forward_id = match xcm.last() {
Some(SetTopic(t)) => Some(forward_id_for(t)),
_ => None,
};
let local_from_bridge =
UniversalLocation::get().invert_target(&bridge).map_err(|_| Unroutable)?;
let export_instruction =
ExportMessage { network: remote_network, destination: remote_location, xcm };
let mut message = Xcm(if let Some(ref payment) = maybe_payment {
let fees = payment
.clone()
.reanchored(&bridge, &UniversalLocation::get())
.map_err(|_| Unroutable)?;
vec![
WithdrawAsset(fees.clone().into()),
BuyExecution { fees, weight_limit: Unlimited },
// `SetAppendix` ensures that `fees` are not trapped in any case, for example, when
// `ExportXcm::validate` encounters an error during the processing of
// `ExportMessage`.
SetAppendix(Xcm(vec![DepositAsset {
assets: AllCounted(1).into(),
beneficiary: local_from_bridge,
}])),
export_instruction,
]
} else {
vec![export_instruction]
});
if let Some(forward_id) = maybe_forward_id {
message.0.push(SetTopic(forward_id));
}
// We then send a normal message to the bridge asking it to export the prepended
// message to the remote chain.
let (v, mut cost) = validate_send::(bridge, message)?;
if let Some(bridge_payment) = maybe_payment {
cost.push(bridge_payment);
}
Ok((v, cost))
}
fn deliver(ticket: Router::Ticket) -> Result {
Router::deliver(ticket)
}
}
pub trait DispatchBlob {
/// Takes an incoming blob from over some point-to-point link (usually from some sort of
/// inter-consensus bridge) and then does what needs to be done with it. Usually this means
/// forwarding it on into some other location sharing our consensus or possibly just enqueuing
/// it for execution locally if it is destined for the local chain.
///
/// NOTE: The API does not provide for any kind of weight or fee management; the size of the
/// `blob` is known to the caller and so the operation must have a linear weight relative to
/// `blob`'s length. This means that you will generally only want to **enqueue** the blob, not
/// enact it. Fees must be handled by the caller.
fn dispatch_blob(blob: Vec) -> Result<(), DispatchBlobError>;
}
pub trait HaulBlob {
/// Sends a blob over some point-to-point link. This will generally be implemented by a bridge.
fn haul_blob(blob: Vec) -> Result<(), HaulBlobError>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HaulBlobError {
/// Represents point-to-point link failure with a human-readable explanation of the specific
/// issue is provided.
Transport(&'static str),
}
impl From for SendError {
fn from(err: HaulBlobError) -> Self {
match err {
HaulBlobError::Transport(reason) => SendError::Transport(reason),
}
}
}
#[derive(Clone, Encode, Decode)]
pub struct BridgeMessage {
/// The message destination as a *Universal Location*. This means it begins with a
/// `GlobalConsensus` junction describing the network under which global consensus happens.
/// If this does not match our global consensus then it's a fatal error.
pub universal_dest: VersionedInteriorLocation,
pub message: VersionedXcm<()>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DispatchBlobError {
Unbridgable,
InvalidEncoding,
UnsupportedLocationVersion,
UnsupportedXcmVersion,
RoutingError,
NonUniversalDestination,
WrongGlobal,
}
pub struct BridgeBlobDispatcher(
PhantomData<(Router, OurPlace, OurPlaceBridgeInstance)>,
);
impl<
Router: SendXcm,
OurPlace: Get,
OurPlaceBridgeInstance: Get