1#![warn(missing_docs)]
20#![cfg_attr(not(feature = "std"), no_std)]
21
22use bp_messages::LaneIdType;
23use bp_runtime::{AccountIdOf, BalanceOf, Chain};
24pub use call_info::XcmBridgeHubCall;
25use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
26use frame_support::{
27 ensure, sp_runtime::RuntimeDebug, CloneNoBound, PalletError, PartialEqNoBound,
28 RuntimeDebugNoBound,
29};
30use scale_info::TypeInfo;
31use serde::{Deserialize, Serialize};
32use sp_core::H256;
33use sp_io::hashing::blake2_256;
34use sp_std::boxed::Box;
35use xcm::{
36 latest::prelude::*, prelude::XcmVersion, IntoVersion, VersionedInteriorLocation,
37 VersionedLocation,
38};
39
40mod call_info;
41
42pub type XcmAsPlainPayload = sp_std::vec::Vec<u8>;
45
46#[derive(
58 Clone,
59 Copy,
60 Decode,
61 Encode,
62 DecodeWithMemTracking,
63 Eq,
64 Ord,
65 PartialOrd,
66 PartialEq,
67 TypeInfo,
68 MaxEncodedLen,
69 Serialize,
70 Deserialize,
71)]
72pub struct BridgeId(H256);
73
74impl BridgeId {
75 pub fn new(
80 universal_source: &InteriorLocation,
81 universal_destination: &InteriorLocation,
82 ) -> Self {
83 const VALUES_SEPARATOR: [u8; 33] = *b"bridges-bridge-id-value-separator";
84
85 BridgeId(
86 (universal_source, VALUES_SEPARATOR, universal_destination)
87 .using_encoded(blake2_256)
88 .into(),
89 )
90 }
91
92 pub fn inner(&self) -> H256 {
94 self.0
95 }
96}
97
98impl core::fmt::Debug for BridgeId {
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 core::fmt::Debug::fmt(&self.0, f)
101 }
102}
103
104pub trait LocalXcmChannelManager {
106 type Error: sp_std::fmt::Debug;
108
109 fn is_congested(with: &Location) -> bool;
114
115 fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error>;
120
121 fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error>;
126}
127
128impl LocalXcmChannelManager for () {
129 type Error = ();
130
131 fn is_congested(_with: &Location) -> bool {
132 false
133 }
134
135 fn suspend_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> {
136 Ok(())
137 }
138
139 fn resume_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> {
140 Ok(())
141 }
142}
143
144#[derive(Clone, Copy, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen, RuntimeDebug)]
146pub enum BridgeState {
147 Opened,
149 Suspended,
154 Closed,
157}
158
159#[derive(
161 CloneNoBound, Decode, Encode, Eq, PartialEqNoBound, TypeInfo, MaxEncodedLen, RuntimeDebugNoBound,
162)]
163#[scale_info(skip_type_params(ThisChain, LaneId))]
164pub struct Bridge<ThisChain: Chain, LaneId: LaneIdType> {
165 pub bridge_origin_relative_location: Box<VersionedLocation>,
168
169 pub bridge_origin_universal_location: Box<VersionedInteriorLocation>,
172 pub bridge_destination_universal_location: Box<VersionedInteriorLocation>,
175
176 pub state: BridgeState,
178 pub bridge_owner_account: AccountIdOf<ThisChain>,
180 pub deposit: BalanceOf<ThisChain>,
182
183 pub lane_id: LaneId,
185}
186
187#[derive(Clone, RuntimeDebug, PartialEq, Eq)]
189pub struct BridgeLocations {
190 bridge_origin_relative_location: Location,
192 bridge_origin_universal_location: InteriorLocation,
194 bridge_destination_universal_location: InteriorLocation,
196 bridge_id: BridgeId,
198}
199
200#[derive(
202 Encode, Decode, DecodeWithMemTracking, RuntimeDebug, PartialEq, Eq, PalletError, TypeInfo,
203)]
204pub enum BridgeLocationsError {
205 NonUniversalLocation,
207 InvalidBridgeOrigin,
209 InvalidBridgeDestination,
211 DestinationIsLocal,
213 UnreachableDestination,
215 UnsupportedDestinationLocation,
218 UnsupportedXcmVersion,
220 UnsupportedLaneIdType,
222}
223
224impl BridgeLocations {
225 pub fn bridge_locations(
244 here_universal_location: InteriorLocation,
245 bridge_origin_relative_location: Location,
246 bridge_destination_universal_location: InteriorLocation,
247 expected_remote_network: NetworkId,
248 ) -> Result<Box<Self>, BridgeLocationsError> {
249 fn strip_low_level_junctions(
250 location: InteriorLocation,
251 ) -> Result<InteriorLocation, BridgeLocationsError> {
252 let mut junctions = location.into_iter();
253
254 let global_consensus = junctions
255 .next()
256 .filter(|junction| matches!(junction, GlobalConsensus(_)))
257 .ok_or(BridgeLocationsError::NonUniversalLocation)?;
258
259 let maybe_parachain =
265 junctions.next().filter(|junction| matches!(junction, Parachain(_)));
266 Ok(match maybe_parachain {
267 Some(parachain) => [global_consensus, parachain].into(),
268 None => [global_consensus].into(),
269 })
270 }
271
272 let local_network = here_universal_location
275 .global_consensus()
276 .map_err(|_| BridgeLocationsError::NonUniversalLocation)?;
277 let remote_network = bridge_destination_universal_location
278 .global_consensus()
279 .map_err(|_| BridgeLocationsError::NonUniversalLocation)?;
280 ensure!(local_network != remote_network, BridgeLocationsError::DestinationIsLocal);
281 ensure!(
282 remote_network == expected_remote_network,
283 BridgeLocationsError::UnreachableDestination
284 );
285
286 let bridge_origin_universal_location = here_universal_location
288 .within_global(bridge_origin_relative_location.clone())
289 .map_err(|_| BridgeLocationsError::InvalidBridgeOrigin)?;
290 let bridge_origin_universal_location =
292 strip_low_level_junctions(bridge_origin_universal_location)?;
293 let bridge_destination_universal_location =
294 strip_low_level_junctions(bridge_destination_universal_location)?;
295
296 let bridge_id = BridgeId::new(
301 &bridge_origin_universal_location,
302 &bridge_destination_universal_location,
303 );
304
305 Ok(Box::new(BridgeLocations {
306 bridge_origin_relative_location,
307 bridge_origin_universal_location,
308 bridge_destination_universal_location,
309 bridge_id,
310 }))
311 }
312
313 pub fn bridge_origin_relative_location(&self) -> &Location {
315 &self.bridge_origin_relative_location
316 }
317
318 pub fn bridge_origin_universal_location(&self) -> &InteriorLocation {
320 &self.bridge_origin_universal_location
321 }
322
323 pub fn bridge_destination_universal_location(&self) -> &InteriorLocation {
325 &self.bridge_destination_universal_location
326 }
327
328 pub fn bridge_id(&self) -> &BridgeId {
330 &self.bridge_id
331 }
332
333 pub fn calculate_lane_id<LaneId: LaneIdType>(
337 &self,
338 xcm_version: XcmVersion,
339 ) -> Result<LaneId, BridgeLocationsError> {
340 #[derive(Eq, PartialEq, Ord, PartialOrd)]
343 struct EncodedVersionedInteriorLocation(sp_std::vec::Vec<u8>);
344 impl Encode for EncodedVersionedInteriorLocation {
345 fn encode(&self) -> sp_std::vec::Vec<u8> {
346 self.0.clone()
347 }
348 }
349
350 let universal_location1 =
351 VersionedInteriorLocation::from(self.bridge_origin_universal_location.clone())
352 .into_version(xcm_version)
353 .map_err(|_| BridgeLocationsError::UnsupportedXcmVersion);
354 let universal_location2 =
355 VersionedInteriorLocation::from(self.bridge_destination_universal_location.clone())
356 .into_version(xcm_version)
357 .map_err(|_| BridgeLocationsError::UnsupportedXcmVersion);
358
359 LaneId::try_new(
360 EncodedVersionedInteriorLocation(universal_location1.encode()),
361 EncodedVersionedInteriorLocation(universal_location2.encode()),
362 )
363 .map_err(|_| BridgeLocationsError::UnsupportedLaneIdType)
364 }
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370 use xcm::latest::ROCOCO_GENESIS_HASH;
371
372 const LOCAL_NETWORK: NetworkId = Kusama;
373 const REMOTE_NETWORK: NetworkId = Polkadot;
374 const UNREACHABLE_NETWORK: NetworkId = NetworkId::ByGenesis(ROCOCO_GENESIS_HASH);
375 const SIBLING_PARACHAIN: u32 = 1000;
376 const LOCAL_BRIDGE_HUB: u32 = 1001;
377 const REMOTE_PARACHAIN: u32 = 2000;
378
379 struct SuccessfulTest {
380 here_universal_location: InteriorLocation,
381 bridge_origin_relative_location: Location,
382
383 bridge_origin_universal_location: InteriorLocation,
384 bridge_destination_universal_location: InteriorLocation,
385
386 expected_remote_network: NetworkId,
387 }
388
389 fn run_successful_test(test: SuccessfulTest) -> BridgeLocations {
390 let locations = BridgeLocations::bridge_locations(
391 test.here_universal_location,
392 test.bridge_origin_relative_location.clone(),
393 test.bridge_destination_universal_location.clone(),
394 test.expected_remote_network,
395 );
396 assert_eq!(
397 locations,
398 Ok(Box::new(BridgeLocations {
399 bridge_origin_relative_location: test.bridge_origin_relative_location,
400 bridge_origin_universal_location: test.bridge_origin_universal_location.clone(),
401 bridge_destination_universal_location: test
402 .bridge_destination_universal_location
403 .clone(),
404 bridge_id: BridgeId::new(
405 &test.bridge_origin_universal_location,
406 &test.bridge_destination_universal_location,
407 ),
408 })),
409 );
410
411 *locations.unwrap()
412 }
413
414 #[test]
417 fn at_relay_from_local_relay_to_remote_relay_works() {
418 run_successful_test(SuccessfulTest {
419 here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
420 bridge_origin_relative_location: Here.into(),
421
422 bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
423 bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
424
425 expected_remote_network: REMOTE_NETWORK,
426 });
427 }
428
429 #[test]
430 fn at_relay_from_sibling_parachain_to_remote_relay_works() {
431 run_successful_test(SuccessfulTest {
432 here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
433 bridge_origin_relative_location: [Parachain(SIBLING_PARACHAIN)].into(),
434
435 bridge_origin_universal_location: [
436 GlobalConsensus(LOCAL_NETWORK),
437 Parachain(SIBLING_PARACHAIN),
438 ]
439 .into(),
440 bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
441
442 expected_remote_network: REMOTE_NETWORK,
443 });
444 }
445
446 #[test]
447 fn at_relay_from_local_relay_to_remote_parachain_works() {
448 run_successful_test(SuccessfulTest {
449 here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
450 bridge_origin_relative_location: Here.into(),
451
452 bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
453 bridge_destination_universal_location: [
454 GlobalConsensus(REMOTE_NETWORK),
455 Parachain(REMOTE_PARACHAIN),
456 ]
457 .into(),
458
459 expected_remote_network: REMOTE_NETWORK,
460 });
461 }
462
463 #[test]
464 fn at_relay_from_sibling_parachain_to_remote_parachain_works() {
465 run_successful_test(SuccessfulTest {
466 here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
467 bridge_origin_relative_location: [Parachain(SIBLING_PARACHAIN)].into(),
468
469 bridge_origin_universal_location: [
470 GlobalConsensus(LOCAL_NETWORK),
471 Parachain(SIBLING_PARACHAIN),
472 ]
473 .into(),
474 bridge_destination_universal_location: [
475 GlobalConsensus(REMOTE_NETWORK),
476 Parachain(REMOTE_PARACHAIN),
477 ]
478 .into(),
479
480 expected_remote_network: REMOTE_NETWORK,
481 });
482 }
483
484 #[test]
485 fn at_bridge_hub_from_local_relay_to_remote_relay_works() {
486 run_successful_test(SuccessfulTest {
487 here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
488 .into(),
489 bridge_origin_relative_location: Parent.into(),
490
491 bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
492 bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
493
494 expected_remote_network: REMOTE_NETWORK,
495 });
496 }
497
498 #[test]
499 fn at_bridge_hub_from_sibling_parachain_to_remote_relay_works() {
500 run_successful_test(SuccessfulTest {
501 here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
502 .into(),
503 bridge_origin_relative_location: ParentThen([Parachain(SIBLING_PARACHAIN)].into())
504 .into(),
505
506 bridge_origin_universal_location: [
507 GlobalConsensus(LOCAL_NETWORK),
508 Parachain(SIBLING_PARACHAIN),
509 ]
510 .into(),
511 bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
512
513 expected_remote_network: REMOTE_NETWORK,
514 });
515 }
516
517 #[test]
518 fn at_bridge_hub_from_local_relay_to_remote_parachain_works() {
519 run_successful_test(SuccessfulTest {
520 here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
521 .into(),
522 bridge_origin_relative_location: Parent.into(),
523
524 bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
525 bridge_destination_universal_location: [
526 GlobalConsensus(REMOTE_NETWORK),
527 Parachain(REMOTE_PARACHAIN),
528 ]
529 .into(),
530
531 expected_remote_network: REMOTE_NETWORK,
532 });
533 }
534
535 #[test]
536 fn at_bridge_hub_from_sibling_parachain_to_remote_parachain_works() {
537 run_successful_test(SuccessfulTest {
538 here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
539 .into(),
540 bridge_origin_relative_location: ParentThen([Parachain(SIBLING_PARACHAIN)].into())
541 .into(),
542
543 bridge_origin_universal_location: [
544 GlobalConsensus(LOCAL_NETWORK),
545 Parachain(SIBLING_PARACHAIN),
546 ]
547 .into(),
548 bridge_destination_universal_location: [
549 GlobalConsensus(REMOTE_NETWORK),
550 Parachain(REMOTE_PARACHAIN),
551 ]
552 .into(),
553
554 expected_remote_network: REMOTE_NETWORK,
555 });
556 }
557
558 #[test]
561 fn low_level_junctions_at_bridge_origin_are_stripped() {
562 let locations1 = run_successful_test(SuccessfulTest {
563 here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
564 bridge_origin_relative_location: Here.into(),
565
566 bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
567 bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
568
569 expected_remote_network: REMOTE_NETWORK,
570 });
571 let locations2 = run_successful_test(SuccessfulTest {
572 here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
573 bridge_origin_relative_location: [PalletInstance(0)].into(),
574
575 bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
576 bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
577
578 expected_remote_network: REMOTE_NETWORK,
579 });
580
581 assert_eq!(locations1.bridge_id, locations2.bridge_id);
582 }
583
584 #[test]
585 fn low_level_junctions_at_bridge_destination_are_stripped() {
586 let locations1 = run_successful_test(SuccessfulTest {
587 here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
588 bridge_origin_relative_location: Here.into(),
589
590 bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
591 bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
592
593 expected_remote_network: REMOTE_NETWORK,
594 });
595 let locations2 = run_successful_test(SuccessfulTest {
596 here_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
597 bridge_origin_relative_location: Here.into(),
598
599 bridge_origin_universal_location: [GlobalConsensus(LOCAL_NETWORK)].into(),
600 bridge_destination_universal_location: [GlobalConsensus(REMOTE_NETWORK)].into(),
601
602 expected_remote_network: REMOTE_NETWORK,
603 });
604
605 assert_eq!(locations1.bridge_id, locations2.bridge_id);
606 }
607
608 #[test]
609 fn calculate_lane_id_works() {
610 type TestLaneId = bp_messages::HashedLaneId;
611
612 let from_local_to_remote = run_successful_test(SuccessfulTest {
613 here_universal_location: [GlobalConsensus(LOCAL_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
614 .into(),
615 bridge_origin_relative_location: ParentThen([Parachain(SIBLING_PARACHAIN)].into())
616 .into(),
617
618 bridge_origin_universal_location: [
619 GlobalConsensus(LOCAL_NETWORK),
620 Parachain(SIBLING_PARACHAIN),
621 ]
622 .into(),
623 bridge_destination_universal_location: [
624 GlobalConsensus(REMOTE_NETWORK),
625 Parachain(REMOTE_PARACHAIN),
626 ]
627 .into(),
628
629 expected_remote_network: REMOTE_NETWORK,
630 });
631
632 let from_remote_to_local = run_successful_test(SuccessfulTest {
633 here_universal_location: [GlobalConsensus(REMOTE_NETWORK), Parachain(LOCAL_BRIDGE_HUB)]
634 .into(),
635 bridge_origin_relative_location: ParentThen([Parachain(REMOTE_PARACHAIN)].into())
636 .into(),
637
638 bridge_origin_universal_location: [
639 GlobalConsensus(REMOTE_NETWORK),
640 Parachain(REMOTE_PARACHAIN),
641 ]
642 .into(),
643 bridge_destination_universal_location: [
644 GlobalConsensus(LOCAL_NETWORK),
645 Parachain(SIBLING_PARACHAIN),
646 ]
647 .into(),
648
649 expected_remote_network: LOCAL_NETWORK,
650 });
651
652 assert_ne!(
653 from_local_to_remote.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
654 from_remote_to_local.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION - 1),
655 );
656 assert_eq!(
657 from_local_to_remote.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
658 from_remote_to_local.calculate_lane_id::<TestLaneId>(xcm::latest::VERSION),
659 );
660 }
661
662 #[test]
665 fn bridge_locations_fails_when_here_is_not_universal_location() {
666 assert_eq!(
667 BridgeLocations::bridge_locations(
668 [Parachain(1000)].into(),
669 Here.into(),
670 [GlobalConsensus(REMOTE_NETWORK)].into(),
671 REMOTE_NETWORK,
672 ),
673 Err(BridgeLocationsError::NonUniversalLocation),
674 );
675 }
676
677 #[test]
678 fn bridge_locations_fails_when_computed_destination_is_not_universal_location() {
679 assert_eq!(
680 BridgeLocations::bridge_locations(
681 [GlobalConsensus(LOCAL_NETWORK)].into(),
682 Here.into(),
683 [OnlyChild].into(),
684 REMOTE_NETWORK,
685 ),
686 Err(BridgeLocationsError::NonUniversalLocation),
687 );
688 }
689
690 #[test]
691 fn bridge_locations_fails_when_computed_destination_is_local() {
692 assert_eq!(
693 BridgeLocations::bridge_locations(
694 [GlobalConsensus(LOCAL_NETWORK)].into(),
695 Here.into(),
696 [GlobalConsensus(LOCAL_NETWORK), OnlyChild].into(),
697 REMOTE_NETWORK,
698 ),
699 Err(BridgeLocationsError::DestinationIsLocal),
700 );
701 }
702
703 #[test]
704 fn bridge_locations_fails_when_computed_destination_is_unreachable() {
705 assert_eq!(
706 BridgeLocations::bridge_locations(
707 [GlobalConsensus(LOCAL_NETWORK)].into(),
708 Here.into(),
709 [GlobalConsensus(UNREACHABLE_NETWORK)].into(),
710 REMOTE_NETWORK,
711 ),
712 Err(BridgeLocationsError::UnreachableDestination),
713 );
714 }
715}