#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
use bp_messages::LaneIdType;
use bp_runtime::{AccountIdOf, BalanceOf, Chain};
pub use call_info::XcmBridgeHubCall;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
ensure, sp_runtime::RuntimeDebug, CloneNoBound, PalletError, PartialEqNoBound,
RuntimeDebugNoBound,
};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_core::H256;
use sp_io::hashing::blake2_256;
use sp_std::boxed::Box;
use xcm::{
latest::prelude::*, prelude::XcmVersion, IntoVersion, VersionedInteriorLocation,
VersionedLocation,
};
mod call_info;
pub type XcmAsPlainPayload = sp_std::vec::Vec<u8>;
#[derive(
Clone,
Copy,
Decode,
Encode,
Eq,
Ord,
PartialOrd,
PartialEq,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub struct BridgeId(H256);
impl BridgeId {
pub fn new(
universal_source: &InteriorLocation,
universal_destination: &InteriorLocation,
) -> Self {
const VALUES_SEPARATOR: [u8; 33] = *b"bridges-bridge-id-value-separator";
BridgeId(
(universal_source, VALUES_SEPARATOR, universal_destination)
.using_encoded(blake2_256)
.into(),
)
}
pub fn inner(&self) -> H256 {
self.0
}
}
impl core::fmt::Debug for BridgeId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.0, f)
}
}
pub trait LocalXcmChannelManager {
type Error: sp_std::fmt::Debug;
fn is_congested(with: &Location) -> bool;
fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error>;
fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error>;
}
impl LocalXcmChannelManager for () {
type Error = ();
fn is_congested(_with: &Location) -> bool {
false
}
fn suspend_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> {
Ok(())
}
fn resume_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> {
Ok(())
}
}
#[derive(Clone, Copy, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen, RuntimeDebug)]
pub enum BridgeState {
Opened,
Suspended,
Closed,
}
#[derive(
CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound,
)]
#[scale_info(skip_type_params(ThisChain, LaneId))]
pub struct Bridge<ThisChain: Chain, LaneId: LaneIdType> {
pub bridge_origin_relative_location: Box<VersionedLocation>,
pub bridge_origin_universal_location: Box<VersionedInteriorLocation>,
pub bridge_destination_universal_location: Box<VersionedInteriorLocation>,
pub state: BridgeState,
pub bridge_owner_account: AccountIdOf<ThisChain>,
pub deposit: BalanceOf<ThisChain>,
pub lane_id: LaneId,
}
#[derive(Clone, RuntimeDebug, PartialEq, Eq)]
pub struct BridgeLocations {
bridge_origin_relative_location: Location,
bridge_origin_universal_location: InteriorLocation,
bridge_destination_universal_location: InteriorLocation,
bridge_id: BridgeId,
}
#[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo)]
pub enum BridgeLocationsError {
NonUniversalLocation,
InvalidBridgeOrigin,
InvalidBridgeDestination,
DestinationIsLocal,
UnreachableDestination,
UnsupportedDestinationLocation,
UnsupportedXcmVersion,
UnsupportedLaneIdType,
}
impl BridgeLocations {
pub fn bridge_locations(
here_universal_location: InteriorLocation,
bridge_origin_relative_location: Location,
bridge_destination_universal_location: InteriorLocation,
expected_remote_network: NetworkId,
) -> Result<Box<Self>, BridgeLocationsError> {
fn strip_low_level_junctions(
location: InteriorLocation,
) -> Result<InteriorLocation, BridgeLocationsError> {
let mut junctions = location.into_iter();
let global_consensus = junctions
.next()
.filter(|junction| matches!(junction, GlobalConsensus(_)))
.ok_or(BridgeLocationsError::NonUniversalLocation)?;
let maybe_parachain =
junctions.next().filter(|junction| matches!(junction, Parachain(_)));
Ok(match maybe_parachain {
Some(parachain) => [global_consensus, parachain].into(),
None => [global_consensus].into(),
})
}
let local_network = here_universal_location
.global_consensus()
.map_err(|_| BridgeLocationsError::NonUniversalLocation)?;
let remote_network = bridge_destination_universal_location
.global_consensus()
.map_err(|_| BridgeLocationsError::NonUniversalLocation)?;
ensure!(local_network != remote_network, BridgeLocationsError::DestinationIsLocal);
ensure!(
remote_network == expected_remote_network,
BridgeLocationsError::UnreachableDestination
);
let bridge_origin_universal_location = here_universal_location
.within_global(bridge_origin_relative_location.clone())
.map_err(|_| BridgeLocationsError::InvalidBridgeOrigin)?;
let bridge_origin_universal_location =
strip_low_level_junctions(bridge_origin_universal_location)?;
let bridge_destination_universal_location =
strip_low_level_junctions(bridge_destination_universal_location)?;
let bridge_id = BridgeId::new(
&bridge_origin_universal_location,
&bridge_destination_universal_location,
);
Ok(Box::new(BridgeLocations {
bridge_origin_relative_location,
bridge_origin_universal_location,
bridge_destination_universal_location,
bridge_id,
}))
}
pub fn bridge_origin_relative_location(&self) -> &Location {
&self.bridge_origin_relative_location
}
pub fn bridge_origin_universal_location(&self) -> &InteriorLocation {
&self.bridge_origin_universal_location
}
pub fn bridge_destination_universal_location(&self) -> &InteriorLocation {
&self.bridge_destination_universal_location
}
pub fn bridge_id(&self) -> &BridgeId {
&self.bridge_id
}
pub fn calculate_lane_id<LaneId: LaneIdType>(
&self,
xcm_version: XcmVersion,
) -> Result<LaneId, BridgeLocationsError> {
#[derive(Eq, PartialEq, Ord, PartialOrd)]
struct EncodedVersionedInteriorLocation(sp_std::vec::Vec<u8>);
impl Encode for EncodedVersionedInteriorLocation {
fn encode(&self) -> sp_std::vec::Vec<u8> {
self.0.clone()
}
}
let universal_location1 =
VersionedInteriorLocation::from(self.bridge_origin_universal_location.clone())
.into_version(xcm_version)
.map_err(|_| BridgeLocationsError::UnsupportedXcmVersion);
let universal_location2 =
VersionedInteriorLocation::from(self.bridge_destination_universal_location.clone())
.into_version(xcm_version)
.map_err(|_| BridgeLocationsError::UnsupportedXcmVersion);
LaneId::try_new(
EncodedVersionedInteriorLocation(universal_location1.encode()),
EncodedVersionedInteriorLocation(universal_location2.encode()),
)
.map_err(|_| BridgeLocationsError::UnsupportedLaneIdType)
}
}
#[cfg(test)]
mod tests {
use super::*;
use xcm::latest::ROCOCO_GENESIS_HASH;
const LOCAL_NETWORK: NetworkId = Kusama;
const REMOTE_NETWORK: NetworkId = Polkadot;
const UNREACHABLE_NETWORK: NetworkId = NetworkId::ByGenesis(ROCOCO_GENESIS_HASH);
const SIBLING_PARACHAIN: u32 = 1000;
const LOCAL_BRIDGE_HUB: u32 = 1001;
const REMOTE_PARACHAIN: u32 = 2000;
struct SuccessfulTest {
here_universal_location: InteriorLocation,
bridge_origin_relative_location: Location,
bridge_origin_universal_location: InteriorLocation,
bridge_destination_universal_location: InteriorLocation,
expected_remote_network: NetworkId,
}
fn run_successful_test(test: SuccessfulTest) -> BridgeLocations {
let locations = BridgeLocations::bridge_locations(
test.here_universal_location,
test.bridge_origin_relative_location.clone(),
test.bridge_destination_universal_location.clone(),
test.expected_remote_network,
);
assert_eq!(
locations,
Ok(Box::new(BridgeLocations {
bridge_origin_relative_location: test.bridge_origin_relative_location,
bridge_origin_universal_location: test.bridge_origin_universal_location.clone(),
bridge_destination_universal_location: test
.bridge_destination_universal_location
.clone(),
bridge_id: BridgeId::new(
&test.bridge_origin_universal_location,
&test.bridge_destination_universal_location,
),
})),
);
*locations.unwrap()
}
#[test]
fn at_relay_from_local_relay_to_remote_relay_works() {
run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_origin_relative_location: Here.into(),
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
expected_remote_network: REMOTE_NETWORK,
});
}
#[test]
fn at_relay_from_sibling_parachain_to_remote_relay_works() {
run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_origin_relative_location: [Parachain(SIBLING_PARACHAIN)].into(),
bridge_origin_universal_location: [
GlobalConsensus(LOCAL_NETWORK),
Parachain(SIBLING_PARACHAIN),
]
.into(),
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
expected_remote_network: REMOTE_NETWORK,
});
}
#[test]
fn at_relay_from_local_relay_to_remote_parachain_works() {
run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_origin_relative_location: Here.into(),
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_destination_universal_location: [
GlobalConsensus(REMOTE_NETWORK),
Parachain(REMOTE_PARACHAIN),
]
.into(),
expected_remote_network: REMOTE_NETWORK,
});
}
#[test]
fn at_relay_from_sibling_parachain_to_remote_parachain_works() {
run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_origin_relative_location: [Parachain(SIBLING_PARACHAIN)].into(),
bridge_origin_universal_location: [
GlobalConsensus(LOCAL_NETWORK),
Parachain(SIBLING_PARACHAIN),
]
.into(),
bridge_destination_universal_location: [
GlobalConsensus(REMOTE_NETWORK),
Parachain(REMOTE_PARACHAIN),
]
.into(),
expected_remote_network: REMOTE_NETWORK,
});
}
#[test]
fn at_bridge_hub_from_local_relay_to_remote_relay_works() {
run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
.into(),
bridge_origin_relative_location: Parent.into(),
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
expected_remote_network: REMOTE_NETWORK,
});
}
#[test]
fn at_bridge_hub_from_sibling_parachain_to_remote_relay_works() {
run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
.into(),
bridge_origin_relative_location: ParentThen([Parachain(SIBLING_PARACHAIN)].into())
.into(),
bridge_origin_universal_location: [
GlobalConsensus(LOCAL_NETWORK),
Parachain(SIBLING_PARACHAIN),
]
.into(),
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
expected_remote_network: REMOTE_NETWORK,
});
}
#[test]
fn at_bridge_hub_from_local_relay_to_remote_parachain_works() {
run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
.into(),
bridge_origin_relative_location: Parent.into(),
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_destination_universal_location: [
GlobalConsensus(REMOTE_NETWORK),
Parachain(REMOTE_PARACHAIN),
]
.into(),
expected_remote_network: REMOTE_NETWORK,
});
}
#[test]
fn at_bridge_hub_from_sibling_parachain_to_remote_parachain_works() {
run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
.into(),
bridge_origin_relative_location: ParentThen([Parachain(SIBLING_PARACHAIN)].into())
.into(),
bridge_origin_universal_location: [
GlobalConsensus(LOCAL_NETWORK),
Parachain(SIBLING_PARACHAIN),
]
.into(),
bridge_destination_universal_location: [
GlobalConsensus(REMOTE_NETWORK),
Parachain(REMOTE_PARACHAIN),
]
.into(),
expected_remote_network: REMOTE_NETWORK,
});
}
#[test]
fn low_level_junctions_at_bridge_origin_are_stripped() {
let locations1 = run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_origin_relative_location: Here.into(),
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
expected_remote_network: REMOTE_NETWORK,
});
let locations2 = run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_origin_relative_location: [PalletInstance(0)].into(),
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
expected_remote_network: REMOTE_NETWORK,
});
assert_eq!(locations1.bridge_id, locations2.bridge_id);
}
#[test]
fn low_level_junctions_at_bridge_destination_are_stripped() {
let locations1 = run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_origin_relative_location: Here.into(),
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
expected_remote_network: REMOTE_NETWORK,
});
let locations2 = run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_origin_relative_location: Here.into(),
bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
expected_remote_network: REMOTE_NETWORK,
});
assert_eq!(locations1.bridge_id, locations2.bridge_id);
}
#[test]
fn calculate_lane_id_works() {
type TestLaneId = bp_messages::HashedLaneId;
let from_local_to_remote = run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
.into(),
bridge_origin_relative_location: ParentThen([Parachain(SIBLING_PARACHAIN)].into())
.into(),
bridge_origin_universal_location: [
GlobalConsensus(LOCAL_NETWORK),
Parachain(SIBLING_PARACHAIN),
]
.into(),
bridge_destination_universal_location: [
GlobalConsensus(REMOTE_NETWORK),
Parachain(REMOTE_PARACHAIN),
]
.into(),
expected_remote_network: REMOTE_NETWORK,
});
let from_remote_to_local = run_successful_test(SuccessfulTest {
here_universal_location: [GlobalConsensus(REMOTE_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
.into(),
bridge_origin_relative_location: ParentThen([Parachain(REMOTE_PARACHAIN)].into())
.into(),
bridge_origin_universal_location: [
GlobalConsensus(REMOTE_NETWORK),
Parachain(REMOTE_PARACHAIN),
]
.into(),
bridge_destination_universal_location: [
GlobalConsensus(LOCAL_NETWORK),
Parachain(SIBLING_PARACHAIN),
]
.into(),
expected_remote_network: LOCAL_NETWORK,
});
assert_ne!(
from_local_to_remote.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
from_remote_to_local.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION - 1),
);
assert_eq!(
from_local_to_remote.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
from_remote_to_local.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
);
}
#[test]
fn bridge_locations_fails_when_here_is_not_universal_location() {
assert_eq!(
BridgeLocations::bridge_locations(
[Parachain(1000)].into(),
Here.into(),
[GlobalConsensus(REMOTE_NETWORK)].into(),
REMOTE_NETWORK,
),
Err(BridgeLocationsError::NonUniversalLocation),
);
}
#[test]
fn bridge_locations_fails_when_computed_destination_is_not_universal_location() {
assert_eq!(
BridgeLocations::bridge_locations(
[GlobalConsensus(LOCAL_NETWORK)].into(),
Here.into(),
[OnlyChild].into(),
REMOTE_NETWORK,
),
Err(BridgeLocationsError::NonUniversalLocation),
);
}
#[test]
fn bridge_locations_fails_when_computed_destination_is_local() {
assert_eq!(
BridgeLocations::bridge_locations(
[GlobalConsensus(LOCAL_NETWORK)].into(),
Here.into(),
[GlobalConsensus(LOCAL_NETWORK), OnlyChild].into(),
REMOTE_NETWORK,
),
Err(BridgeLocationsError::DestinationIsLocal),
);
}
#[test]
fn bridge_locations_fails_when_computed_destination_is_unreachable() {
assert_eq!(
BridgeLocations::bridge_locations(
[GlobalConsensus(LOCAL_NETWORK)].into(),
Here.into(),
[GlobalConsensus(UNREACHABLE_NETWORK)].into(),
REMOTE_NETWORK,
),
Err(BridgeLocationsError::UnreachableDestination),
);
}
}