pallet_xcm_bridge_hub/
lib.rs

1// Copyright 2019-2021 Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common 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// Parity Bridges Common 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 Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Module that adds XCM support to bridge pallets. The pallet allows to dynamically
18//! open and close bridges between local (to this pallet location) and remote XCM
19//! destinations.
20//!
21//! The `pallet_xcm_bridge_hub` pallet is used to manage (open, close) bridges between chains from
22//! different consensuses. The new extrinsics `fn open_bridge` and `fn close_bridge` are introduced.
23//! Other chains can manage channels with different bridged global consensuses.
24//!
25//! # Concept of `lane` and `LaneId`
26//!
27//! There is another `pallet_bridge_messages` pallet that handles inbound/outbound lanes for
28//! messages. Each lane is a unique connection between two chains from different consensuses and is
29//! identified by `LaneId`. `LaneId` is generated once when a new bridge is requested by `fn
30//! open_bridge`. It is generated by `BridgeLocations::calculate_lane_id` based on the following
31//! parameters:
32//! - Source `bridge_origin_universal_location` (latest XCM)
33//! - Destination `bridge_destination_universal_location` (latest XCM)
34//! - XCM version (both sides of the bridge must use the same parameters to generate the same
35//!   `LaneId`)
36//!   - `bridge_origin_universal_location`, `bridge_destination_universal_location` is converted to
37//!     the `Versioned*` structs
38//!
39//! `LaneId` is expected to never change because:
40//! - We need the same `LaneId` on both sides of the bridge, as `LaneId` is part of the message key
41//!   proofs.
42//! - Runtime upgrades are entirely asynchronous.
43//! - We already have a running production Polkadot/Kusama bridge that uses `LaneId([0, 0, 0, 0])`.
44//!
45//! `LaneId` is backward compatible, meaning it can be encoded/decoded from the older format `[u8;
46//! 4]` used for static lanes, as well as the new format `H256` generated by
47//! `BridgeLocations::calculate_lane_id`.
48//!
49//! # Concept of `bridge` and `BridgeId`
50//!
51//! The `pallet_xcm_bridge_hub` pallet needs to store some metadata about opened bridges. The bridge
52//! (or bridge metadata) is stored under the `BridgeId` key.
53//!
54//! `BridgeId` is generated from `bridge_origin_relative_location` and
55//! `bridge_origin_universal_location` using the `latest` XCM structs. `BridgeId` is not transferred
56//! over the bridge; it is only important for local consensus. It essentially serves as an index/key
57//! to bridge metadata. All the XCM infrastructure around `XcmExecutor`, `SendXcm`, `ExportXcm` use
58//! the `latest` XCM, so `BridgeId` must remain compatible with the `latest` XCM. For example, we
59//! have an `ExportXcm` implementation in `exporter.rs` that handles the `ExportMessage` instruction
60//! with `universal_source` and `destination` (latest XCM), so we need to create `BridgeId` and the
61//! corresponding `LaneId`.
62//!
63//! # Migrations and State
64//!
65//! This pallet implements `try_state`, ensuring compatibility and checking everything so we know if
66//! any migration is needed. `do_try_state` checks for `BridgeId` compatibility, which is
67//! recalculated on runtime upgrade. Upgrading to a new XCM version should not break anything,
68//! except removing older XCM versions. In such cases, we need to add migration for `BridgeId` and
69//! stored `Versioned*` structs and update `LaneToBridge` mapping, but this won't affect `LaneId`
70//! over the bridge.
71//!
72//! # How to Open a Bridge?
73//!
74//! The `pallet_xcm_bridge_hub` pallet has the extrinsic `fn open_bridge` and an important
75//! configuration `pallet_xcm_bridge_hub::Config::OpenBridgeOrigin`, which translates the call's
76//! origin to the XCM `Location` and converts it to the `bridge_origin_universal_location`. With the
77//! current setup, this origin/location is expected to be either the relay chain or a sibling
78//! parachain as one side of the bridge. Another parameter is
79//! `bridge_destination_universal_location`, which is the other side of the bridge from a different
80//! global consensus.
81//!
82//! Every bridge between two XCM locations has a dedicated lane in associated
83//! messages pallet. Assuming that this pallet is deployed at the bridge hub
84//! parachain and there's a similar pallet at the bridged network, the dynamic
85//! bridge lifetime is as follows:
86//!
87//! 1) the sibling parachain opens a XCMP channel with this bridge hub;
88//!
89//! 2) the sibling parachain funds its sovereign parachain account at this bridge hub. It shall hold
90//!    enough funds to pay for the bridge (see `BridgeDeposit`);
91//!
92//! 3) the sibling parachain opens the bridge by sending XCM `Transact` instruction with the
93//!    `open_bridge` call. The `BridgeDeposit` amount is reserved on the sovereign account of
94//!    sibling parachain;
95//!
96//! 4) at the other side of the bridge, the same thing (1, 2, 3) happens. Parachains that need to
97//!    connect over the bridge need to coordinate the moment when they start sending messages over
98//!    the bridge. Otherwise they may lose messages and/or bundled assets;
99//!
100//! 5) when either side wants to close the bridge, it sends the XCM `Transact` with the
101//!    `close_bridge` call. The bridge is closed immediately if there are no queued messages.
102//!    Otherwise, the owner must repeat the `close_bridge` call to prune all queued messages first.
103//!
104//! The pallet doesn't provide any mechanism for graceful closure, because it always involves
105//! some contract between two connected chains and the bridge hub knows nothing about that. It
106//! is the task for the connected chains to make sure that all required actions are completed
107//! before the closure. In the end, the bridge hub can't even guarantee that all messages that
108//! are delivered to the destination, are processed in the way their sender expects. So if we
109//! can't guarantee that, we shall not care about more complex procedures and leave it to the
110//! participating parties.
111//!
112//! # Example
113//!
114//! Example of opening a bridge between some random parachains from Polkadot and Kusama:
115//!
116//! 0. Let's have:
117//! 	- BridgeHubPolkadot with `UniversalLocation` = `[GlobalConsensus(Polkadot), Parachain(1002)]`
118//! 	- BridgeHubKusama with `UniversalLocation` = `[GlobalConsensus(Kusama), Parachain(1002)]`
119//! 1. The Polkadot local sibling parachain `Location::new(1, Parachain(1234))` must send some DOTs
120//!    to its sovereign account on BridgeHubPolkadot to cover `BridgeDeposit`, fees for `Transact`,
121//!    and the existential deposit.
122//! 2. Send a call to the BridgeHubPolkadot from the local sibling parachain: `Location::new(1,
123//!    Parachain(1234))` ``` xcm::Transact( origin_kind: OriginKind::Xcm,
124//!    XcmOverBridgeHubKusama::open_bridge( VersionedInteriorLocation::V4([GlobalConsensus(Kusama),
125//!    Parachain(4567)].into()), ); ) ```
126//! 3. Check the stored bridge metadata and generated `LaneId`.
127//! 4. The Kusama local sibling parachain `Location::new(1, Parachain(4567))` must send some KSMs to
128//!    its sovereign account
129//! on BridgeHubKusama to cover `BridgeDeposit`, fees for `Transact`, and the existential deposit.
130//! 5. Send a call to the BridgeHubKusama from the local sibling parachain: `Location::new(1,
131//!    Parachain(4567))` ``` xcm::Transact( origin_kind: OriginKind::Xcm,
132//!    XcmOverBridgeHubKusama::open_bridge(
133//!    VersionedInteriorLocation::V4([GlobalConsensus(Polkadot), Parachain(1234)].into()), ); ) ```
134//! 6. Check the stored bridge metadata and generated `LaneId`.
135//! 7. Both `LaneId`s from steps 3 and 6 must be the same (see above _Concept of `lane` and
136//!    `LaneId`_).
137//! 8. Run the bridge messages relayer for `LaneId`.
138//! 9. Send messages from both sides.
139//!
140//! The opening bridge holds the configured `BridgeDeposit` from the origin's sovereign account, but
141//! this deposit is returned when the bridge is closed with `fn close_bridge`.
142
143#![warn(missing_docs)]
144#![cfg_attr(not(feature = "std"), no_std)]
145
146use bp_messages::{LaneState, MessageNonce};
147use bp_runtime::{AccountIdOf, BalanceOf, RangeInclusiveExt};
148use bp_xcm_bridge_hub::BridgeLocationsError;
149pub use bp_xcm_bridge_hub::{
150	Bridge, BridgeId, BridgeLocations, BridgeState, LocalXcmChannelManager,
151};
152use frame_support::{traits::fungible::MutateHold, DefaultNoBound};
153use frame_system::Config as SystemConfig;
154use pallet_bridge_messages::{Config as BridgeMessagesConfig, LanesManagerError};
155use sp_runtime::traits::Zero;
156use sp_std::{boxed::Box, vec::Vec};
157use xcm::prelude::*;
158use xcm_builder::DispatchBlob;
159use xcm_executor::traits::ConvertLocation;
160
161pub use bp_xcm_bridge_hub::XcmAsPlainPayload;
162pub use dispatcher::XcmBlobMessageDispatchResult;
163pub use exporter::PalletAsHaulBlobExporter;
164pub use pallet::*;
165
166mod dispatcher;
167mod exporter;
168pub mod migration;
169mod mock;
170
171/// The target that will be used when publishing logs related to this pallet.
172pub const LOG_TARGET: &str = "runtime::bridge-xcm";
173
174#[frame_support::pallet]
175pub mod pallet {
176	use super::*;
177	use frame_support::{
178		pallet_prelude::*,
179		traits::{tokens::Precision, Contains},
180	};
181	use frame_system::pallet_prelude::{BlockNumberFor, *};
182
183	/// The reason for this pallet placing a hold on funds.
184	#[pallet::composite_enum]
185	pub enum HoldReason<I: 'static = ()> {
186		/// The funds are held as a deposit for opened bridge.
187		#[codec(index = 0)]
188		BridgeDeposit,
189	}
190
191	#[pallet::config]
192	#[pallet::disable_frame_system_supertrait_check]
193	pub trait Config<I: 'static = ()>:
194		BridgeMessagesConfig<Self::BridgeMessagesPalletInstance>
195	{
196		/// The overarching event type.
197		type RuntimeEvent: From<Event<Self, I>>
198			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
199
200		/// Runtime's universal location.
201		type UniversalLocation: Get<InteriorLocation>;
202		// TODO: https://github.com/paritytech/parity-bridges-common/issues/1666 remove `ChainId` and
203		// replace it with the `NetworkId` - then we'll be able to use
204		// `T as pallet_bridge_messages::Config<T::BridgeMessagesPalletInstance>::BridgedChain::NetworkId`
205		/// Bridged network as relative location of bridged `GlobalConsensus`.
206		#[pallet::constant]
207		type BridgedNetwork: Get<Location>;
208		/// Associated messages pallet instance that bridges us with the
209		/// `BridgedNetworkId` consensus.
210		type BridgeMessagesPalletInstance: 'static;
211
212		/// Price of single message export to the bridged consensus (`Self::BridgedNetwork`).
213		type MessageExportPrice: Get<Assets>;
214		/// Checks the XCM version for the destination.
215		type DestinationVersion: GetVersion;
216
217		/// The origin that is allowed to call privileged operations on the pallet, e.g. open/close
218		/// bridge for locations.
219		type ForceOrigin: EnsureOrigin<<Self as SystemConfig>::RuntimeOrigin>;
220		/// A set of XCM locations within local consensus system that are allowed to open
221		/// bridges with remote destinations.
222		type OpenBridgeOrigin: EnsureOrigin<
223			<Self as SystemConfig>::RuntimeOrigin,
224			Success = Location,
225		>;
226		/// A converter between a location and a sovereign account.
227		type BridgeOriginAccountIdConverter: ConvertLocation<AccountIdOf<ThisChainOf<Self, I>>>;
228
229		/// Amount of this chain native tokens that is reserved on the sibling parachain account
230		/// when bridge open request is registered.
231		#[pallet::constant]
232		type BridgeDeposit: Get<BalanceOf<ThisChainOf<Self, I>>>;
233		/// Currency used to pay for bridge registration.
234		type Currency: MutateHold<
235			AccountIdOf<ThisChainOf<Self, I>>,
236			Balance = BalanceOf<ThisChainOf<Self, I>>,
237			Reason = Self::RuntimeHoldReason,
238		>;
239		/// The overarching runtime hold reason.
240		type RuntimeHoldReason: From<HoldReason<I>>;
241		/// Do not hold `Self::BridgeDeposit` for the location of `Self::OpenBridgeOrigin`.
242		/// For example, it is possible to make an exception for a system parachain or relay.
243		type AllowWithoutBridgeDeposit: Contains<Location>;
244
245		/// Local XCM channel manager.
246		type LocalXcmChannelManager: LocalXcmChannelManager;
247		/// XCM-level dispatcher for inbound bridge messages.
248		type BlobDispatcher: DispatchBlob;
249	}
250
251	/// An alias for the bridge metadata.
252	pub type BridgeOf<T, I> = Bridge<ThisChainOf<T, I>, LaneIdOf<T, I>>;
253	/// An alias for this chain.
254	pub type ThisChainOf<T, I> =
255		pallet_bridge_messages::ThisChainOf<T, <T as Config<I>>::BridgeMessagesPalletInstance>;
256	/// An alias for lane identifier type.
257	pub type LaneIdOf<T, I> =
258		<T as BridgeMessagesConfig<<T as Config<I>>::BridgeMessagesPalletInstance>>::LaneId;
259	/// An alias for the associated lanes manager.
260	pub type LanesManagerOf<T, I> =
261		pallet_bridge_messages::LanesManager<T, <T as Config<I>>::BridgeMessagesPalletInstance>;
262
263	#[pallet::pallet]
264	#[pallet::storage_version(migration::STORAGE_VERSION)]
265	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
266
267	#[pallet::hooks]
268	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
269		fn integrity_test() {
270			assert!(
271				Self::bridged_network_id().is_ok(),
272				"Configured `T::BridgedNetwork`: {:?} does not contain `GlobalConsensus` junction with `NetworkId`",
273				T::BridgedNetwork::get()
274			)
275		}
276
277		#[cfg(feature = "try-runtime")]
278		fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
279			Self::do_try_state()
280		}
281	}
282
283	#[pallet::call]
284	impl<T: Config<I>, I: 'static> Pallet<T, I> {
285		/// Open a bridge between two locations.
286		///
287		/// The caller must be within the `T::OpenBridgeOrigin` filter (presumably: a sibling
288		/// parachain or a parent relay chain). The `bridge_destination_universal_location` must be
289		/// a destination within the consensus of the `T::BridgedNetwork` network.
290		///
291		/// The `BridgeDeposit` amount is reserved on the caller account. This deposit
292		/// is unreserved after bridge is closed.
293		///
294		/// The states after this call: bridge is `Opened`, outbound lane is `Opened`, inbound lane
295		/// is `Opened`.
296		#[pallet::call_index(0)]
297		#[pallet::weight(Weight::zero())] // TODO:(bridges-v2) - https://github.com/paritytech/parity-bridges-common/issues/3046 - add benchmarks impl
298		pub fn open_bridge(
299			origin: OriginFor<T>,
300			bridge_destination_universal_location: Box<VersionedInteriorLocation>,
301		) -> DispatchResult {
302			// check and compute required bridge locations and laneId
303			let xcm_version = bridge_destination_universal_location.identify_version();
304			let locations =
305				Self::bridge_locations_from_origin(origin, bridge_destination_universal_location)?;
306			let lane_id = locations.calculate_lane_id(xcm_version).map_err(|e| {
307				log::trace!(
308					target: LOG_TARGET,
309					"calculate_lane_id error: {e:?}",
310				);
311				Error::<T, I>::BridgeLocations(e)
312			})?;
313
314			Self::do_open_bridge(locations, lane_id, true)
315		}
316
317		/// Try to close the bridge.
318		///
319		/// Can only be called by the "owner" of this side of the bridge, meaning that the
320		/// inbound XCM channel with the local origin chain is working.
321		///
322		/// Closed bridge is a bridge without any traces in the runtime storage. So this method
323		/// first tries to prune all queued messages at the outbound lane. When there are no
324		/// outbound messages left, outbound and inbound lanes are purged. After that, funds
325		/// are returned back to the owner of this side of the bridge.
326		///
327		/// The number of messages that we may prune in a single call is limited by the
328		/// `may_prune_messages` argument. If there are more messages in the queue, the method
329		/// prunes exactly `may_prune_messages` and exits early. The caller may call it again
330		/// until outbound queue is depleted and get his funds back.
331		///
332		/// The states after this call: everything is either `Closed`, or purged from the
333		/// runtime storage.
334		#[pallet::call_index(1)]
335		#[pallet::weight(Weight::zero())] // TODO:(bridges-v2) - https://github.com/paritytech/parity-bridges-common/issues/3046 - add benchmarks impl
336		pub fn close_bridge(
337			origin: OriginFor<T>,
338			bridge_destination_universal_location: Box<VersionedInteriorLocation>,
339			may_prune_messages: MessageNonce,
340		) -> DispatchResult {
341			// compute required bridge locations
342			let locations =
343				Self::bridge_locations_from_origin(origin, bridge_destination_universal_location)?;
344
345			// TODO: https://github.com/paritytech/parity-bridges-common/issues/1760 - may do refund here, if
346			// bridge/lanes are already closed + for messages that are not pruned
347
348			// update bridge metadata - this also guarantees that the bridge is in the proper state
349			let bridge =
350				Bridges::<T, I>::try_mutate_exists(locations.bridge_id(), |bridge| match bridge {
351					Some(bridge) => {
352						bridge.state = BridgeState::Closed;
353						Ok(bridge.clone())
354					},
355					None => Err(Error::<T, I>::UnknownBridge),
356				})?;
357
358			// close inbound and outbound lanes
359			let lanes_manager = LanesManagerOf::<T, I>::new();
360			let mut inbound_lane = lanes_manager
361				.any_state_inbound_lane(bridge.lane_id)
362				.map_err(Error::<T, I>::LanesManager)?;
363			let mut outbound_lane = lanes_manager
364				.any_state_outbound_lane(bridge.lane_id)
365				.map_err(Error::<T, I>::LanesManager)?;
366
367			// now prune queued messages
368			let mut pruned_messages = 0;
369			for _ in outbound_lane.queued_messages() {
370				if pruned_messages == may_prune_messages {
371					break
372				}
373
374				outbound_lane.remove_oldest_unpruned_message();
375				pruned_messages += 1;
376			}
377
378			// if there are outbound messages in the queue, just update states and early exit
379			if !outbound_lane.queued_messages().is_empty() {
380				// update lanes state. Under normal circumstances, following calls shall never fail
381				inbound_lane.set_state(LaneState::Closed);
382				outbound_lane.set_state(LaneState::Closed);
383
384				// write something to log
385				let enqueued_messages = outbound_lane.queued_messages().saturating_len();
386				log::trace!(
387					target: LOG_TARGET,
388					"Bridge {:?} between {:?} and {:?} is closing lane_id: {:?}. {} messages remaining",
389					locations.bridge_id(),
390					locations.bridge_origin_universal_location(),
391					locations.bridge_destination_universal_location(),
392					bridge.lane_id,
393					enqueued_messages,
394				);
395
396				// deposit the `ClosingBridge` event
397				Self::deposit_event(Event::<T, I>::ClosingBridge {
398					bridge_id: *locations.bridge_id(),
399					lane_id: bridge.lane_id.into(),
400					pruned_messages,
401					enqueued_messages,
402				});
403
404				return Ok(())
405			}
406
407			// else we have pruned all messages, so lanes and the bridge itself may gone
408			inbound_lane.purge();
409			outbound_lane.purge();
410			Bridges::<T, I>::remove(locations.bridge_id());
411			LaneToBridge::<T, I>::remove(bridge.lane_id);
412
413			// return deposit
414			let released_deposit = T::Currency::release(
415				&HoldReason::BridgeDeposit.into(),
416				&bridge.bridge_owner_account,
417				bridge.deposit,
418				Precision::BestEffort,
419			)
420			.inspect_err(|e| {
421				// we can't do anything here - looks like funds have been (partially) unreserved
422				// before by someone else. Let's not fail, though - it'll be worse for the caller
423				log::error!(
424					target: LOG_TARGET,
425					"Failed to unreserve during the bridge {:?} closure with error: {e:?}",
426					locations.bridge_id(),
427				);
428			})
429			.ok()
430			.unwrap_or(BalanceOf::<ThisChainOf<T, I>>::zero());
431
432			// write something to log
433			log::trace!(
434				target: LOG_TARGET,
435				"Bridge {:?} between {:?} and {:?} has closed lane_id: {:?}, the bridge deposit {released_deposit:?} was returned",
436				locations.bridge_id(),
437				bridge.lane_id,
438				locations.bridge_origin_universal_location(),
439				locations.bridge_destination_universal_location(),
440			);
441
442			// deposit the `BridgePruned` event
443			Self::deposit_event(Event::<T, I>::BridgePruned {
444				bridge_id: *locations.bridge_id(),
445				lane_id: bridge.lane_id.into(),
446				bridge_deposit: released_deposit,
447				pruned_messages,
448			});
449
450			Ok(())
451		}
452	}
453
454	impl<T: Config<I>, I: 'static> Pallet<T, I> {
455		/// Open bridge for lane.
456		pub fn do_open_bridge(
457			locations: Box<BridgeLocations>,
458			lane_id: T::LaneId,
459			create_lanes: bool,
460		) -> Result<(), DispatchError> {
461			// reserve balance on the origin's sovereign account (if needed)
462			let bridge_owner_account = T::BridgeOriginAccountIdConverter::convert_location(
463				locations.bridge_origin_relative_location(),
464			)
465			.ok_or(Error::<T, I>::InvalidBridgeOriginAccount)?;
466			let deposit = if T::AllowWithoutBridgeDeposit::contains(
467				locations.bridge_origin_relative_location(),
468			) {
469				BalanceOf::<ThisChainOf<T, I>>::zero()
470			} else {
471				let deposit = T::BridgeDeposit::get();
472				T::Currency::hold(
473					&HoldReason::BridgeDeposit.into(),
474					&bridge_owner_account,
475					deposit,
476				)
477				.map_err(|e| {
478					log::error!(
479						target: LOG_TARGET,
480						"Failed to hold bridge deposit: {deposit:?} \
481						from bridge_owner_account: {bridge_owner_account:?} derived from \
482						bridge_origin_relative_location: {:?} with error: {e:?}",
483						locations.bridge_origin_relative_location(),
484					);
485					Error::<T, I>::FailedToReserveBridgeDeposit
486				})?;
487				deposit
488			};
489
490			// save bridge metadata
491			Bridges::<T, I>::try_mutate(locations.bridge_id(), |bridge| match bridge {
492				Some(_) => Err(Error::<T, I>::BridgeAlreadyExists),
493				None => {
494					*bridge = Some(BridgeOf::<T, I> {
495						bridge_origin_relative_location: Box::new(
496							locations.bridge_origin_relative_location().clone().into(),
497						),
498						bridge_origin_universal_location: Box::new(
499							locations.bridge_origin_universal_location().clone().into(),
500						),
501						bridge_destination_universal_location: Box::new(
502							locations.bridge_destination_universal_location().clone().into(),
503						),
504						state: BridgeState::Opened,
505						bridge_owner_account,
506						deposit,
507						lane_id,
508					});
509					Ok(())
510				},
511			})?;
512			// save lane to bridge mapping
513			LaneToBridge::<T, I>::try_mutate(lane_id, |bridge| match bridge {
514				Some(_) => Err(Error::<T, I>::BridgeAlreadyExists),
515				None => {
516					*bridge = Some(*locations.bridge_id());
517					Ok(())
518				},
519			})?;
520
521			if create_lanes {
522				// create new lanes. Under normal circumstances, following calls shall never fail
523				let lanes_manager = LanesManagerOf::<T, I>::new();
524				lanes_manager
525					.create_inbound_lane(lane_id)
526					.map_err(Error::<T, I>::LanesManager)?;
527				lanes_manager
528					.create_outbound_lane(lane_id)
529					.map_err(Error::<T, I>::LanesManager)?;
530			}
531
532			// write something to log
533			log::trace!(
534				target: LOG_TARGET,
535				"Bridge {:?} between {:?} and {:?} has been opened using lane_id: {lane_id:?}",
536				locations.bridge_id(),
537				locations.bridge_origin_universal_location(),
538				locations.bridge_destination_universal_location(),
539			);
540
541			// deposit `BridgeOpened` event
542			Self::deposit_event(Event::<T, I>::BridgeOpened {
543				bridge_id: *locations.bridge_id(),
544				bridge_deposit: deposit,
545				local_endpoint: Box::new(locations.bridge_origin_universal_location().clone()),
546				remote_endpoint: Box::new(
547					locations.bridge_destination_universal_location().clone(),
548				),
549				lane_id: lane_id.into(),
550			});
551
552			Ok(())
553		}
554	}
555
556	impl<T: Config<I>, I: 'static> Pallet<T, I> {
557		/// Return bridge endpoint locations and dedicated lane identifier. This method converts
558		/// runtime `origin` argument to relative `Location` using the `T::OpenBridgeOrigin`
559		/// converter.
560		pub fn bridge_locations_from_origin(
561			origin: OriginFor<T>,
562			bridge_destination_universal_location: Box<VersionedInteriorLocation>,
563		) -> Result<Box<BridgeLocations>, sp_runtime::DispatchError> {
564			Self::bridge_locations(
565				T::OpenBridgeOrigin::ensure_origin(origin)?,
566				(*bridge_destination_universal_location)
567					.try_into()
568					.map_err(|_| Error::<T, I>::UnsupportedXcmVersion)?,
569			)
570		}
571
572		/// Return bridge endpoint locations and dedicated **bridge** identifier (`BridgeId`).
573		pub fn bridge_locations(
574			bridge_origin_relative_location: Location,
575			bridge_destination_universal_location: InteriorLocation,
576		) -> Result<Box<BridgeLocations>, sp_runtime::DispatchError> {
577			BridgeLocations::bridge_locations(
578				T::UniversalLocation::get(),
579				bridge_origin_relative_location,
580				bridge_destination_universal_location,
581				Self::bridged_network_id()?,
582			)
583			.map_err(|e| {
584				log::trace!(
585					target: LOG_TARGET,
586					"bridge_locations error: {e:?}",
587				);
588				Error::<T, I>::BridgeLocations(e).into()
589			})
590		}
591
592		/// Return bridge metadata by bridge_id
593		pub fn bridge(bridge_id: &BridgeId) -> Option<BridgeOf<T, I>> {
594			Bridges::<T, I>::get(bridge_id)
595		}
596
597		/// Return bridge metadata by lane_id
598		pub fn bridge_by_lane_id(lane_id: &T::LaneId) -> Option<(BridgeId, BridgeOf<T, I>)> {
599			LaneToBridge::<T, I>::get(lane_id)
600				.and_then(|bridge_id| Self::bridge(&bridge_id).map(|bridge| (bridge_id, bridge)))
601		}
602	}
603
604	impl<T: Config<I>, I: 'static> Pallet<T, I> {
605		/// Returns some `NetworkId` if contains `GlobalConsensus` junction.
606		fn bridged_network_id() -> Result<NetworkId, sp_runtime::DispatchError> {
607			match T::BridgedNetwork::get().take_first_interior() {
608				Some(GlobalConsensus(network)) => Ok(network),
609				_ => Err(Error::<T, I>::BridgeLocations(
610					BridgeLocationsError::InvalidBridgeDestination,
611				)
612				.into()),
613			}
614		}
615	}
616
617	#[cfg(any(test, feature = "try-runtime", feature = "std"))]
618	impl<T: Config<I>, I: 'static> Pallet<T, I> {
619		/// Ensure the correctness of the state of this pallet.
620		pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
621			use sp_std::collections::btree_set::BTreeSet;
622
623			let mut lanes = BTreeSet::new();
624
625			// check all known bridge configurations
626			for (bridge_id, bridge) in Bridges::<T, I>::iter() {
627				lanes.insert(Self::do_try_state_for_bridge(bridge_id, bridge)?);
628			}
629			ensure!(
630				lanes.len() == Bridges::<T, I>::iter().count(),
631				"Invalid `Bridges` configuration, probably two bridges handle the same laneId!"
632			);
633			ensure!(
634				lanes.len() == LaneToBridge::<T, I>::iter().count(),
635				"Invalid `LaneToBridge` configuration, probably missing or not removed laneId!"
636			);
637
638			// check connected `pallet_bridge_messages` state.
639			Self::do_try_state_for_messages()
640		}
641
642		/// Ensure the correctness of the state of the bridge.
643		pub fn do_try_state_for_bridge(
644			bridge_id: BridgeId,
645			bridge: BridgeOf<T, I>,
646		) -> Result<T::LaneId, sp_runtime::TryRuntimeError> {
647			log::info!(target: LOG_TARGET, "Checking `do_try_state_for_bridge` for bridge_id: {bridge_id:?} and bridge: {bridge:?}");
648
649			// check `BridgeId` points to the same `LaneId` and vice versa.
650			ensure!(
651				Some(bridge_id) == LaneToBridge::<T, I>::get(bridge.lane_id),
652				"Found `LaneToBridge` inconsistency for bridge_id - missing mapping!"
653			);
654
655			// check `pallet_bridge_messages` state for that `LaneId`.
656			let lanes_manager = LanesManagerOf::<T, I>::new();
657			ensure!(
658				lanes_manager.any_state_inbound_lane(bridge.lane_id).is_ok(),
659				"Inbound lane not found!",
660			);
661			ensure!(
662				lanes_manager.any_state_outbound_lane(bridge.lane_id).is_ok(),
663				"Outbound lane not found!",
664			);
665
666			// check that `locations` are convertible to the `latest` XCM.
667			let bridge_origin_relative_location_as_latest: &Location =
668				bridge.bridge_origin_relative_location.try_as().map_err(|_| {
669					"`bridge.bridge_origin_relative_location` cannot be converted to the `latest` XCM, needs migration!"
670				})?;
671			let bridge_origin_universal_location_as_latest: &InteriorLocation = bridge.bridge_origin_universal_location
672				.try_as()
673				.map_err(|_| "`bridge.bridge_origin_universal_location` cannot be converted to the `latest` XCM, needs migration!")?;
674			let bridge_destination_universal_location_as_latest: &InteriorLocation = bridge.bridge_destination_universal_location
675				.try_as()
676				.map_err(|_| "`bridge.bridge_destination_universal_location` cannot be converted to the `latest` XCM, needs migration!")?;
677
678			// check `BridgeId` does not change
679			ensure!(
680				bridge_id == BridgeId::new(bridge_origin_universal_location_as_latest, bridge_destination_universal_location_as_latest),
681				"`bridge_id` is different than calculated from `bridge_origin_universal_location_as_latest` and `bridge_destination_universal_location_as_latest`, needs migration!"
682			);
683
684			// check bridge account owner
685			ensure!(
686				T::BridgeOriginAccountIdConverter::convert_location(bridge_origin_relative_location_as_latest) == Some(bridge.bridge_owner_account),
687				"`bridge.bridge_owner_account` is different than calculated from `bridge.bridge_origin_relative_location`, needs migration!"
688			);
689
690			Ok(bridge.lane_id)
691		}
692
693		/// Ensure the correctness of the state of the connected `pallet_bridge_messages` instance.
694		pub fn do_try_state_for_messages() -> Result<(), sp_runtime::TryRuntimeError> {
695			// check that all `InboundLanes` laneIds have mapping to some bridge.
696			for lane_id in pallet_bridge_messages::InboundLanes::<T, T::BridgeMessagesPalletInstance>::iter_keys() {
697				log::info!(target: LOG_TARGET, "Checking `do_try_state_for_messages` for `InboundLanes`'s lane_id: {lane_id:?}...");
698				ensure!(
699					LaneToBridge::<T, I>::get(lane_id).is_some(),
700					"Found `LaneToBridge` inconsistency for `InboundLanes`'s lane_id - missing mapping!"
701				);
702			}
703
704			// check that all `OutboundLanes` laneIds have mapping to some bridge.
705			for lane_id in pallet_bridge_messages::OutboundLanes::<T, T::BridgeMessagesPalletInstance>::iter_keys() {
706				log::info!(target: LOG_TARGET, "Checking `do_try_state_for_messages` for `OutboundLanes`'s lane_id: {lane_id:?}...");
707				ensure!(
708					LaneToBridge::<T, I>::get(lane_id).is_some(),
709					"Found `LaneToBridge` inconsistency for `OutboundLanes`'s lane_id - missing mapping!"
710				);
711			}
712
713			Ok(())
714		}
715	}
716
717	/// All registered bridges.
718	#[pallet::storage]
719	pub type Bridges<T: Config<I>, I: 'static = ()> =
720		StorageMap<_, Identity, BridgeId, BridgeOf<T, I>>;
721	/// All registered `lane_id` and `bridge_id` mappings.
722	#[pallet::storage]
723	pub type LaneToBridge<T: Config<I>, I: 'static = ()> =
724		StorageMap<_, Identity, T::LaneId, BridgeId>;
725
726	#[pallet::genesis_config]
727	#[derive(DefaultNoBound)]
728	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
729		/// Opened bridges.
730		///
731		/// Keep in mind that we are **NOT** reserving any amount for the bridges opened at
732		/// genesis. We are **NOT** opening lanes, used by this bridge. It all must be done using
733		/// other pallets genesis configuration or some other means.
734		pub opened_bridges: Vec<(Location, InteriorLocation, Option<T::LaneId>)>,
735		/// Dummy marker.
736		#[serde(skip)]
737		pub _phantom: sp_std::marker::PhantomData<(T, I)>,
738	}
739
740	#[pallet::genesis_build]
741	impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I>
742	where
743		T: frame_system::Config<AccountId = AccountIdOf<ThisChainOf<T, I>>>,
744	{
745		fn build(&self) {
746			for (
747				bridge_origin_relative_location,
748				bridge_destination_universal_location,
749				maybe_lane_id,
750			) in &self.opened_bridges
751			{
752				let locations = Pallet::<T, I>::bridge_locations(
753					bridge_origin_relative_location.clone(),
754					bridge_destination_universal_location.clone().into(),
755				)
756				.expect("Invalid genesis configuration");
757
758				let lane_id = match maybe_lane_id {
759					Some(lane_id) => *lane_id,
760					None =>
761						locations.calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"),
762				};
763
764				Pallet::<T, I>::do_open_bridge(locations, lane_id, true)
765					.expect("Valid opened bridge!");
766			}
767		}
768	}
769
770	#[pallet::event]
771	#[pallet::generate_deposit(pub(super) fn deposit_event)]
772	pub enum Event<T: Config<I>, I: 'static = ()> {
773		/// The bridge between two locations has been opened.
774		BridgeOpened {
775			/// Bridge identifier.
776			bridge_id: BridgeId,
777			/// Amount of deposit held.
778			bridge_deposit: BalanceOf<ThisChainOf<T, I>>,
779
780			/// Universal location of local bridge endpoint.
781			local_endpoint: Box<InteriorLocation>,
782			/// Universal location of remote bridge endpoint.
783			remote_endpoint: Box<InteriorLocation>,
784			/// Lane identifier.
785			lane_id: T::LaneId,
786		},
787		/// Bridge is going to be closed, but not yet fully pruned from the runtime storage.
788		ClosingBridge {
789			/// Bridge identifier.
790			bridge_id: BridgeId,
791			/// Lane identifier.
792			lane_id: T::LaneId,
793			/// Number of pruned messages during the close call.
794			pruned_messages: MessageNonce,
795			/// Number of enqueued messages that need to be pruned in follow up calls.
796			enqueued_messages: MessageNonce,
797		},
798		/// Bridge has been closed and pruned from the runtime storage. It now may be reopened
799		/// again by any participant.
800		BridgePruned {
801			/// Bridge identifier.
802			bridge_id: BridgeId,
803			/// Lane identifier.
804			lane_id: T::LaneId,
805			/// Amount of deposit released.
806			bridge_deposit: BalanceOf<ThisChainOf<T, I>>,
807			/// Number of pruned messages during the close call.
808			pruned_messages: MessageNonce,
809		},
810	}
811
812	#[pallet::error]
813	pub enum Error<T, I = ()> {
814		/// Bridge locations error.
815		BridgeLocations(BridgeLocationsError),
816		/// Invalid local bridge origin account.
817		InvalidBridgeOriginAccount,
818		/// The bridge is already registered in this pallet.
819		BridgeAlreadyExists,
820		/// The local origin already owns a maximal number of bridges.
821		TooManyBridgesForLocalOrigin,
822		/// Trying to close already closed bridge.
823		BridgeAlreadyClosed,
824		/// Lanes manager error.
825		LanesManager(LanesManagerError),
826		/// Trying to access unknown bridge.
827		UnknownBridge,
828		/// The bridge origin can't pay the required amount for opening the bridge.
829		FailedToReserveBridgeDeposit,
830		/// The version of XCM location argument is unsupported.
831		UnsupportedXcmVersion,
832	}
833}
834
835#[cfg(test)]
836mod tests {
837	use super::*;
838	use bp_messages::LaneIdType;
839	use mock::*;
840
841	use frame_support::{assert_err, assert_noop, assert_ok, traits::fungible::Mutate, BoundedVec};
842	use frame_system::{EventRecord, Phase};
843	use sp_runtime::TryRuntimeError;
844
845	fn fund_origin_sovereign_account(locations: &BridgeLocations, balance: Balance) -> AccountId {
846		let bridge_owner_account =
847			LocationToAccountId::convert_location(locations.bridge_origin_relative_location())
848				.unwrap();
849		assert_ok!(Balances::mint_into(&bridge_owner_account, balance));
850		bridge_owner_account
851	}
852
853	fn mock_open_bridge_from_with(
854		origin: RuntimeOrigin,
855		deposit: Balance,
856		with: InteriorLocation,
857	) -> (BridgeOf<TestRuntime, ()>, BridgeLocations) {
858		let locations =
859			XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
860		let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
861		let bridge_owner_account =
862			fund_origin_sovereign_account(&locations, deposit + ExistentialDeposit::get());
863		Balances::hold(&HoldReason::BridgeDeposit.into(), &bridge_owner_account, deposit).unwrap();
864
865		let bridge = Bridge {
866			bridge_origin_relative_location: Box::new(
867				locations.bridge_origin_relative_location().clone().into(),
868			),
869			bridge_origin_universal_location: Box::new(
870				locations.bridge_origin_universal_location().clone().into(),
871			),
872			bridge_destination_universal_location: Box::new(
873				locations.bridge_destination_universal_location().clone().into(),
874			),
875			state: BridgeState::Opened,
876			bridge_owner_account,
877			deposit,
878			lane_id,
879		};
880		Bridges::<TestRuntime, ()>::insert(locations.bridge_id(), bridge.clone());
881		LaneToBridge::<TestRuntime, ()>::insert(bridge.lane_id, locations.bridge_id());
882
883		let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
884		lanes_manager.create_inbound_lane(bridge.lane_id).unwrap();
885		lanes_manager.create_outbound_lane(bridge.lane_id).unwrap();
886
887		assert_ok!(XcmOverBridge::do_try_state());
888
889		(bridge, *locations)
890	}
891
892	fn mock_open_bridge_from(
893		origin: RuntimeOrigin,
894		deposit: Balance,
895	) -> (BridgeOf<TestRuntime, ()>, BridgeLocations) {
896		mock_open_bridge_from_with(origin, deposit, bridged_asset_hub_universal_location())
897	}
898
899	fn enqueue_message(lane: TestLaneIdType) {
900		let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
901		lanes_manager
902			.active_outbound_lane(lane)
903			.unwrap()
904			.send_message(BoundedVec::try_from(vec![42]).expect("We craft valid messages"));
905	}
906
907	#[test]
908	fn open_bridge_fails_if_origin_is_not_allowed() {
909		run_test(|| {
910			assert_noop!(
911				XcmOverBridge::open_bridge(
912					OpenBridgeOrigin::disallowed_origin(),
913					Box::new(bridged_asset_hub_universal_location().into()),
914				),
915				sp_runtime::DispatchError::BadOrigin,
916			);
917		})
918	}
919
920	#[test]
921	fn open_bridge_fails_if_origin_is_not_relative() {
922		run_test(|| {
923			assert_noop!(
924				XcmOverBridge::open_bridge(
925					OpenBridgeOrigin::parent_relay_chain_universal_origin(),
926					Box::new(bridged_asset_hub_universal_location().into()),
927				),
928				Error::<TestRuntime, ()>::BridgeLocations(
929					BridgeLocationsError::InvalidBridgeOrigin
930				),
931			);
932
933			assert_noop!(
934				XcmOverBridge::open_bridge(
935					OpenBridgeOrigin::sibling_parachain_universal_origin(),
936					Box::new(bridged_asset_hub_universal_location().into()),
937				),
938				Error::<TestRuntime, ()>::BridgeLocations(
939					BridgeLocationsError::InvalidBridgeOrigin
940				),
941			);
942		})
943	}
944
945	#[test]
946	fn open_bridge_fails_if_destination_is_not_remote() {
947		run_test(|| {
948			assert_noop!(
949				XcmOverBridge::open_bridge(
950					OpenBridgeOrigin::parent_relay_chain_origin(),
951					Box::new(
952						[GlobalConsensus(RelayNetwork::get()), Parachain(BRIDGED_ASSET_HUB_ID)]
953							.into()
954					),
955				),
956				Error::<TestRuntime, ()>::BridgeLocations(BridgeLocationsError::DestinationIsLocal),
957			);
958		});
959	}
960
961	#[test]
962	fn open_bridge_fails_if_outside_of_bridged_consensus() {
963		run_test(|| {
964			assert_noop!(
965				XcmOverBridge::open_bridge(
966					OpenBridgeOrigin::parent_relay_chain_origin(),
967					Box::new(
968						[
969							GlobalConsensus(NonBridgedRelayNetwork::get()),
970							Parachain(BRIDGED_ASSET_HUB_ID)
971						]
972						.into()
973					),
974				),
975				Error::<TestRuntime, ()>::BridgeLocations(
976					BridgeLocationsError::UnreachableDestination
977				),
978			);
979		});
980	}
981
982	#[test]
983	fn open_bridge_fails_if_origin_has_no_sovereign_account() {
984		run_test(|| {
985			assert_noop!(
986				XcmOverBridge::open_bridge(
987					OpenBridgeOrigin::origin_without_sovereign_account(),
988					Box::new(bridged_asset_hub_universal_location().into()),
989				),
990				Error::<TestRuntime, ()>::InvalidBridgeOriginAccount,
991			);
992		});
993	}
994
995	#[test]
996	fn open_bridge_fails_if_origin_sovereign_account_has_no_enough_funds() {
997		run_test(|| {
998			assert_noop!(
999				XcmOverBridge::open_bridge(
1000					OpenBridgeOrigin::sibling_parachain_origin(),
1001					Box::new(bridged_asset_hub_universal_location().into()),
1002				),
1003				Error::<TestRuntime, ()>::FailedToReserveBridgeDeposit,
1004			);
1005		});
1006	}
1007
1008	#[test]
1009	fn open_bridge_fails_if_it_already_exists() {
1010		run_test(|| {
1011			let origin = OpenBridgeOrigin::parent_relay_chain_origin();
1012			let locations = XcmOverBridge::bridge_locations_from_origin(
1013				origin.clone(),
1014				Box::new(bridged_asset_hub_universal_location().into()),
1015			)
1016			.unwrap();
1017			let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
1018			fund_origin_sovereign_account(
1019				&locations,
1020				BridgeDeposit::get() + ExistentialDeposit::get(),
1021			);
1022
1023			Bridges::<TestRuntime, ()>::insert(
1024				locations.bridge_id(),
1025				Bridge {
1026					bridge_origin_relative_location: Box::new(
1027						locations.bridge_origin_relative_location().clone().into(),
1028					),
1029					bridge_origin_universal_location: Box::new(
1030						locations.bridge_origin_universal_location().clone().into(),
1031					),
1032					bridge_destination_universal_location: Box::new(
1033						locations.bridge_destination_universal_location().clone().into(),
1034					),
1035					state: BridgeState::Opened,
1036					bridge_owner_account: [0u8; 32].into(),
1037					deposit: 0,
1038					lane_id,
1039				},
1040			);
1041
1042			assert_noop!(
1043				XcmOverBridge::open_bridge(
1044					origin,
1045					Box::new(bridged_asset_hub_universal_location().into()),
1046				),
1047				Error::<TestRuntime, ()>::BridgeAlreadyExists,
1048			);
1049		})
1050	}
1051
1052	#[test]
1053	fn open_bridge_fails_if_its_lanes_already_exists() {
1054		run_test(|| {
1055			let origin = OpenBridgeOrigin::parent_relay_chain_origin();
1056			let locations = XcmOverBridge::bridge_locations_from_origin(
1057				origin.clone(),
1058				Box::new(bridged_asset_hub_universal_location().into()),
1059			)
1060			.unwrap();
1061			let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
1062			fund_origin_sovereign_account(
1063				&locations,
1064				BridgeDeposit::get() + ExistentialDeposit::get(),
1065			);
1066
1067			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1068
1069			lanes_manager.create_inbound_lane(lane_id).unwrap();
1070			assert_noop!(
1071				XcmOverBridge::open_bridge(
1072					origin.clone(),
1073					Box::new(bridged_asset_hub_universal_location().into()),
1074				),
1075				Error::<TestRuntime, ()>::LanesManager(LanesManagerError::InboundLaneAlreadyExists),
1076			);
1077
1078			lanes_manager.active_inbound_lane(lane_id).unwrap().purge();
1079			lanes_manager.create_outbound_lane(lane_id).unwrap();
1080			assert_noop!(
1081				XcmOverBridge::open_bridge(
1082					origin,
1083					Box::new(bridged_asset_hub_universal_location().into()),
1084				),
1085				Error::<TestRuntime, ()>::LanesManager(
1086					LanesManagerError::OutboundLaneAlreadyExists
1087				),
1088			);
1089		})
1090	}
1091
1092	#[test]
1093	fn open_bridge_works() {
1094		run_test(|| {
1095			// in our test runtime, we expect that bridge may be opened by parent relay chain
1096			// and any sibling parachain
1097			let origins = [
1098				(OpenBridgeOrigin::parent_relay_chain_origin(), 0),
1099				(OpenBridgeOrigin::sibling_parachain_origin(), BridgeDeposit::get()),
1100			];
1101
1102			// check that every origin may open the bridge
1103			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1104			let existential_deposit = ExistentialDeposit::get();
1105			for (origin, expected_deposit) in origins {
1106				// reset events
1107				System::set_block_number(1);
1108				System::reset_events();
1109
1110				// compute all other locations
1111				let xcm_version = xcm::latest::VERSION;
1112				let locations = XcmOverBridge::bridge_locations_from_origin(
1113					origin.clone(),
1114					Box::new(
1115						VersionedInteriorLocation::from(bridged_asset_hub_universal_location())
1116							.into_version(xcm_version)
1117							.expect("valid conversion"),
1118					),
1119				)
1120				.unwrap();
1121				let lane_id = locations.calculate_lane_id(xcm_version).unwrap();
1122
1123				// ensure that there's no bridge and lanes in the storage
1124				assert_eq!(Bridges::<TestRuntime, ()>::get(locations.bridge_id()), None);
1125				assert_eq!(
1126					lanes_manager.active_inbound_lane(lane_id).map(drop),
1127					Err(LanesManagerError::UnknownInboundLane)
1128				);
1129				assert_eq!(
1130					lanes_manager.active_outbound_lane(lane_id).map(drop),
1131					Err(LanesManagerError::UnknownOutboundLane)
1132				);
1133				assert_eq!(LaneToBridge::<TestRuntime, ()>::get(lane_id), None);
1134
1135				// give enough funds to the sovereign account of the bridge origin
1136				let bridge_owner_account = fund_origin_sovereign_account(
1137					&locations,
1138					expected_deposit + existential_deposit,
1139				);
1140				assert_eq!(
1141					Balances::free_balance(&bridge_owner_account),
1142					expected_deposit + existential_deposit
1143				);
1144				assert_eq!(Balances::reserved_balance(&bridge_owner_account), 0);
1145
1146				// now open the bridge
1147				assert_ok!(XcmOverBridge::open_bridge(
1148					origin,
1149					Box::new(locations.bridge_destination_universal_location().clone().into()),
1150				));
1151
1152				// ensure that everything has been set up in the runtime storage
1153				assert_eq!(
1154					Bridges::<TestRuntime, ()>::get(locations.bridge_id()),
1155					Some(Bridge {
1156						bridge_origin_relative_location: Box::new(
1157							locations.bridge_origin_relative_location().clone().into()
1158						),
1159						bridge_origin_universal_location: Box::new(
1160							locations.bridge_origin_universal_location().clone().into(),
1161						),
1162						bridge_destination_universal_location: Box::new(
1163							locations.bridge_destination_universal_location().clone().into(),
1164						),
1165						state: BridgeState::Opened,
1166						bridge_owner_account: bridge_owner_account.clone(),
1167						deposit: expected_deposit,
1168						lane_id
1169					}),
1170				);
1171				assert_eq!(
1172					lanes_manager.active_inbound_lane(lane_id).map(|l| l.state()),
1173					Ok(LaneState::Opened)
1174				);
1175				assert_eq!(
1176					lanes_manager.active_outbound_lane(lane_id).map(|l| l.state()),
1177					Ok(LaneState::Opened)
1178				);
1179				assert_eq!(
1180					LaneToBridge::<TestRuntime, ()>::get(lane_id),
1181					Some(*locations.bridge_id())
1182				);
1183				assert_eq!(Balances::free_balance(&bridge_owner_account), existential_deposit);
1184				assert_eq!(Balances::reserved_balance(&bridge_owner_account), expected_deposit);
1185
1186				// ensure that the proper event is deposited
1187				assert_eq!(
1188					System::events().last(),
1189					Some(&EventRecord {
1190						phase: Phase::Initialization,
1191						event: RuntimeEvent::XcmOverBridge(Event::BridgeOpened {
1192							bridge_id: *locations.bridge_id(),
1193							bridge_deposit: expected_deposit,
1194							local_endpoint: Box::new(
1195								locations.bridge_origin_universal_location().clone()
1196							),
1197							remote_endpoint: Box::new(
1198								locations.bridge_destination_universal_location().clone()
1199							),
1200							lane_id: lane_id.into()
1201						}),
1202						topics: vec![],
1203					}),
1204				);
1205
1206				// check state
1207				assert_ok!(XcmOverBridge::do_try_state());
1208			}
1209		});
1210	}
1211
1212	#[test]
1213	fn close_bridge_fails_if_origin_is_not_allowed() {
1214		run_test(|| {
1215			assert_noop!(
1216				XcmOverBridge::close_bridge(
1217					OpenBridgeOrigin::disallowed_origin(),
1218					Box::new(bridged_asset_hub_universal_location().into()),
1219					0,
1220				),
1221				sp_runtime::DispatchError::BadOrigin,
1222			);
1223		})
1224	}
1225
1226	#[test]
1227	fn close_bridge_fails_if_origin_is_not_relative() {
1228		run_test(|| {
1229			assert_noop!(
1230				XcmOverBridge::close_bridge(
1231					OpenBridgeOrigin::parent_relay_chain_universal_origin(),
1232					Box::new(bridged_asset_hub_universal_location().into()),
1233					0,
1234				),
1235				Error::<TestRuntime, ()>::BridgeLocations(
1236					BridgeLocationsError::InvalidBridgeOrigin
1237				),
1238			);
1239
1240			assert_noop!(
1241				XcmOverBridge::close_bridge(
1242					OpenBridgeOrigin::sibling_parachain_universal_origin(),
1243					Box::new(bridged_asset_hub_universal_location().into()),
1244					0,
1245				),
1246				Error::<TestRuntime, ()>::BridgeLocations(
1247					BridgeLocationsError::InvalidBridgeOrigin
1248				),
1249			);
1250		})
1251	}
1252
1253	#[test]
1254	fn close_bridge_fails_if_its_lanes_are_unknown() {
1255		run_test(|| {
1256			let origin = OpenBridgeOrigin::parent_relay_chain_origin();
1257			let (bridge, locations) = mock_open_bridge_from(origin.clone(), 0);
1258
1259			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1260			lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().purge();
1261			assert_noop!(
1262				XcmOverBridge::close_bridge(
1263					origin.clone(),
1264					Box::new(locations.bridge_destination_universal_location().clone().into()),
1265					0,
1266				),
1267				Error::<TestRuntime, ()>::LanesManager(LanesManagerError::UnknownInboundLane),
1268			);
1269			lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().purge();
1270
1271			let (_, locations) = mock_open_bridge_from(origin.clone(), 0);
1272			lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().purge();
1273			assert_noop!(
1274				XcmOverBridge::close_bridge(
1275					origin,
1276					Box::new(locations.bridge_destination_universal_location().clone().into()),
1277					0,
1278				),
1279				Error::<TestRuntime, ()>::LanesManager(LanesManagerError::UnknownOutboundLane),
1280			);
1281		});
1282	}
1283
1284	#[test]
1285	fn close_bridge_works() {
1286		run_test(|| {
1287			let origin = OpenBridgeOrigin::parent_relay_chain_origin();
1288			let expected_deposit = BridgeDeposit::get();
1289			let (bridge, locations) = mock_open_bridge_from(origin.clone(), expected_deposit);
1290			System::set_block_number(1);
1291
1292			// remember owner balances
1293			let free_balance = Balances::free_balance(&bridge.bridge_owner_account);
1294			let reserved_balance = Balances::reserved_balance(&bridge.bridge_owner_account);
1295
1296			// enqueue some messages
1297			for _ in 0..32 {
1298				enqueue_message(bridge.lane_id);
1299			}
1300
1301			// now call the `close_bridge`, which will only partially prune messages
1302			assert_ok!(XcmOverBridge::close_bridge(
1303				origin.clone(),
1304				Box::new(locations.bridge_destination_universal_location().clone().into()),
1305				16,
1306			),);
1307
1308			// as a result, the bridge and lanes are switched to the `Closed` state, some messages
1309			// are pruned, but funds are not unreserved
1310			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1311			assert_eq!(
1312				Bridges::<TestRuntime, ()>::get(locations.bridge_id()).map(|b| b.state),
1313				Some(BridgeState::Closed)
1314			);
1315			assert_eq!(
1316				lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().state(),
1317				LaneState::Closed
1318			);
1319			assert_eq!(
1320				lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().state(),
1321				LaneState::Closed
1322			);
1323			assert_eq!(
1324				lanes_manager
1325					.any_state_outbound_lane(bridge.lane_id)
1326					.unwrap()
1327					.queued_messages()
1328					.checked_len(),
1329				Some(16)
1330			);
1331			assert_eq!(
1332				LaneToBridge::<TestRuntime, ()>::get(bridge.lane_id),
1333				Some(*locations.bridge_id())
1334			);
1335			assert_eq!(Balances::free_balance(&bridge.bridge_owner_account), free_balance);
1336			assert_eq!(Balances::reserved_balance(&bridge.bridge_owner_account), reserved_balance);
1337			assert_eq!(
1338				System::events().last(),
1339				Some(&EventRecord {
1340					phase: Phase::Initialization,
1341					event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge {
1342						bridge_id: *locations.bridge_id(),
1343						lane_id: bridge.lane_id.into(),
1344						pruned_messages: 16,
1345						enqueued_messages: 16,
1346					}),
1347					topics: vec![],
1348				}),
1349			);
1350
1351			// now call the `close_bridge` again, which will only partially prune messages
1352			assert_ok!(XcmOverBridge::close_bridge(
1353				origin.clone(),
1354				Box::new(locations.bridge_destination_universal_location().clone().into()),
1355				8,
1356			),);
1357
1358			// nothing is changed (apart from the pruned messages)
1359			assert_eq!(
1360				Bridges::<TestRuntime, ()>::get(locations.bridge_id()).map(|b| b.state),
1361				Some(BridgeState::Closed)
1362			);
1363			assert_eq!(
1364				lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().state(),
1365				LaneState::Closed
1366			);
1367			assert_eq!(
1368				lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().state(),
1369				LaneState::Closed
1370			);
1371			assert_eq!(
1372				lanes_manager
1373					.any_state_outbound_lane(bridge.lane_id)
1374					.unwrap()
1375					.queued_messages()
1376					.checked_len(),
1377				Some(8)
1378			);
1379			assert_eq!(
1380				LaneToBridge::<TestRuntime, ()>::get(bridge.lane_id),
1381				Some(*locations.bridge_id())
1382			);
1383			assert_eq!(Balances::free_balance(&bridge.bridge_owner_account), free_balance);
1384			assert_eq!(Balances::reserved_balance(&bridge.bridge_owner_account), reserved_balance);
1385			assert_eq!(
1386				System::events().last(),
1387				Some(&EventRecord {
1388					phase: Phase::Initialization,
1389					event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge {
1390						bridge_id: *locations.bridge_id(),
1391						lane_id: bridge.lane_id.into(),
1392						pruned_messages: 8,
1393						enqueued_messages: 8,
1394					}),
1395					topics: vec![],
1396				}),
1397			);
1398
1399			// now call the `close_bridge` again that will prune all remaining messages and the
1400			// bridge
1401			assert_ok!(XcmOverBridge::close_bridge(
1402				origin,
1403				Box::new(locations.bridge_destination_universal_location().clone().into()),
1404				9,
1405			),);
1406
1407			// there's no traces of bridge in the runtime storage and funds are unreserved
1408			assert_eq!(
1409				Bridges::<TestRuntime, ()>::get(locations.bridge_id()).map(|b| b.state),
1410				None
1411			);
1412			assert_eq!(
1413				lanes_manager.any_state_inbound_lane(bridge.lane_id).map(drop),
1414				Err(LanesManagerError::UnknownInboundLane)
1415			);
1416			assert_eq!(
1417				lanes_manager.any_state_outbound_lane(bridge.lane_id).map(drop),
1418				Err(LanesManagerError::UnknownOutboundLane)
1419			);
1420			assert_eq!(LaneToBridge::<TestRuntime, ()>::get(bridge.lane_id), None);
1421			assert_eq!(
1422				Balances::free_balance(&bridge.bridge_owner_account),
1423				free_balance + reserved_balance
1424			);
1425			assert_eq!(Balances::reserved_balance(&bridge.bridge_owner_account), 0);
1426			assert_eq!(
1427				System::events().last(),
1428				Some(&EventRecord {
1429					phase: Phase::Initialization,
1430					event: RuntimeEvent::XcmOverBridge(Event::BridgePruned {
1431						bridge_id: *locations.bridge_id(),
1432						lane_id: bridge.lane_id.into(),
1433						bridge_deposit: expected_deposit,
1434						pruned_messages: 8,
1435					}),
1436					topics: vec![],
1437				}),
1438			);
1439		});
1440	}
1441
1442	#[test]
1443	fn do_try_state_works() {
1444		let bridge_origin_relative_location = SiblingLocation::get();
1445		let bridge_origin_universal_location = SiblingUniversalLocation::get();
1446		let bridge_destination_universal_location = BridgedUniversalDestination::get();
1447		let bridge_owner_account =
1448			LocationToAccountId::convert_location(&bridge_origin_relative_location)
1449				.expect("valid accountId");
1450		let bridge_owner_account_mismatch =
1451			LocationToAccountId::convert_location(&Location::parent()).expect("valid accountId");
1452		let bridge_id = BridgeId::new(
1453			&bridge_origin_universal_location,
1454			&bridge_destination_universal_location,
1455		);
1456		let bridge_id_mismatch = BridgeId::new(&InteriorLocation::Here, &InteriorLocation::Here);
1457		let lane_id = TestLaneIdType::try_new(1, 2).unwrap();
1458		let lane_id_mismatch = TestLaneIdType::try_new(3, 4).unwrap();
1459
1460		let test_bridge_state =
1461			|id,
1462			 bridge,
1463			 (lane_id, bridge_id),
1464			 (inbound_lane_id, outbound_lane_id),
1465			 expected_error: Option<TryRuntimeError>| {
1466				Bridges::<TestRuntime, ()>::insert(id, bridge);
1467				LaneToBridge::<TestRuntime, ()>::insert(lane_id, bridge_id);
1468
1469				let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1470				lanes_manager.create_inbound_lane(inbound_lane_id).unwrap();
1471				lanes_manager.create_outbound_lane(outbound_lane_id).unwrap();
1472
1473				let result = XcmOverBridge::do_try_state();
1474				if let Some(e) = expected_error {
1475					assert_err!(result, e);
1476				} else {
1477					assert_ok!(result);
1478				}
1479			};
1480		let cleanup = |bridge_id, lane_ids| {
1481			Bridges::<TestRuntime, ()>::remove(bridge_id);
1482			for lane_id in lane_ids {
1483				LaneToBridge::<TestRuntime, ()>::remove(lane_id);
1484				let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1485				if let Ok(lane) = lanes_manager.any_state_inbound_lane(lane_id) {
1486					lane.purge();
1487				}
1488				if let Ok(lane) = lanes_manager.any_state_outbound_lane(lane_id) {
1489					lane.purge();
1490				}
1491			}
1492			assert_ok!(XcmOverBridge::do_try_state());
1493		};
1494
1495		run_test(|| {
1496			// ok state
1497			test_bridge_state(
1498				bridge_id,
1499				Bridge {
1500					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1501						bridge_origin_relative_location.clone(),
1502					)),
1503					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1504						bridge_origin_universal_location.clone(),
1505					)),
1506					bridge_destination_universal_location: Box::new(
1507						VersionedInteriorLocation::from(
1508							bridge_destination_universal_location.clone(),
1509						),
1510					),
1511					state: BridgeState::Opened,
1512					bridge_owner_account: bridge_owner_account.clone(),
1513					deposit: Zero::zero(),
1514					lane_id,
1515				},
1516				(lane_id, bridge_id),
1517				(lane_id, lane_id),
1518				None,
1519			);
1520			cleanup(bridge_id, vec![lane_id]);
1521
1522			// error - missing `LaneToBridge` mapping
1523			test_bridge_state(
1524				bridge_id,
1525				Bridge {
1526					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1527						bridge_origin_relative_location.clone(),
1528					)),
1529					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1530						bridge_origin_universal_location.clone(),
1531					)),
1532					bridge_destination_universal_location: Box::new(
1533						VersionedInteriorLocation::from(
1534							bridge_destination_universal_location.clone(),
1535						),
1536					),
1537					state: BridgeState::Opened,
1538					bridge_owner_account: bridge_owner_account.clone(),
1539					deposit: Zero::zero(),
1540					lane_id,
1541				},
1542				(lane_id, bridge_id_mismatch),
1543				(lane_id, lane_id),
1544				Some(TryRuntimeError::Other(
1545					"Found `LaneToBridge` inconsistency for bridge_id - missing mapping!",
1546				)),
1547			);
1548			cleanup(bridge_id, vec![lane_id]);
1549
1550			// error bridge owner account cannot be calculated
1551			test_bridge_state(
1552				bridge_id,
1553				Bridge {
1554					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1555						bridge_origin_relative_location.clone(),
1556					)),
1557					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1558						bridge_origin_universal_location.clone(),
1559					)),
1560					bridge_destination_universal_location: Box::new(VersionedInteriorLocation::from(
1561						bridge_destination_universal_location.clone(),
1562					)),
1563					state: BridgeState::Opened,
1564					bridge_owner_account: bridge_owner_account_mismatch.clone(),
1565					deposit: Zero::zero(),
1566					lane_id,
1567				},
1568				(lane_id, bridge_id),
1569				(lane_id, lane_id),
1570				Some(TryRuntimeError::Other("`bridge.bridge_owner_account` is different than calculated from `bridge.bridge_origin_relative_location`, needs migration!")),
1571			);
1572			cleanup(bridge_id, vec![lane_id]);
1573
1574			// error when (bridge_origin_universal_location + bridge_destination_universal_location)
1575			// produces different `BridgeId`
1576			test_bridge_state(
1577				bridge_id_mismatch,
1578				Bridge {
1579					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1580						bridge_origin_relative_location.clone(),
1581					)),
1582					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1583						bridge_origin_universal_location.clone(),
1584					)),
1585					bridge_destination_universal_location: Box::new(VersionedInteriorLocation::from(
1586						bridge_destination_universal_location.clone(),
1587					)),
1588					state: BridgeState::Opened,
1589					bridge_owner_account: bridge_owner_account_mismatch.clone(),
1590					deposit: Zero::zero(),
1591					lane_id,
1592				},
1593				(lane_id, bridge_id_mismatch),
1594				(lane_id, lane_id),
1595				Some(TryRuntimeError::Other("`bridge_id` is different than calculated from `bridge_origin_universal_location_as_latest` and `bridge_destination_universal_location_as_latest`, needs migration!")),
1596			);
1597			cleanup(bridge_id_mismatch, vec![lane_id]);
1598
1599			// missing inbound lane for a bridge
1600			test_bridge_state(
1601				bridge_id,
1602				Bridge {
1603					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1604						bridge_origin_relative_location.clone(),
1605					)),
1606					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1607						bridge_origin_universal_location.clone(),
1608					)),
1609					bridge_destination_universal_location: Box::new(
1610						VersionedInteriorLocation::from(
1611							bridge_destination_universal_location.clone(),
1612						),
1613					),
1614					state: BridgeState::Opened,
1615					bridge_owner_account: bridge_owner_account.clone(),
1616					deposit: Zero::zero(),
1617					lane_id,
1618				},
1619				(lane_id, bridge_id),
1620				(lane_id_mismatch, lane_id),
1621				Some(TryRuntimeError::Other("Inbound lane not found!")),
1622			);
1623			cleanup(bridge_id, vec![lane_id, lane_id_mismatch]);
1624
1625			// missing outbound lane for a bridge
1626			test_bridge_state(
1627				bridge_id,
1628				Bridge {
1629					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1630						bridge_origin_relative_location.clone(),
1631					)),
1632					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1633						bridge_origin_universal_location.clone(),
1634					)),
1635					bridge_destination_universal_location: Box::new(
1636						VersionedInteriorLocation::from(
1637							bridge_destination_universal_location.clone(),
1638						),
1639					),
1640					state: BridgeState::Opened,
1641					bridge_owner_account: bridge_owner_account.clone(),
1642					deposit: Zero::zero(),
1643					lane_id,
1644				},
1645				(lane_id, bridge_id),
1646				(lane_id, lane_id_mismatch),
1647				Some(TryRuntimeError::Other("Outbound lane not found!")),
1648			);
1649			cleanup(bridge_id, vec![lane_id, lane_id_mismatch]);
1650
1651			// missing bridge for inbound lane
1652			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1653			assert!(lanes_manager.create_inbound_lane(lane_id).is_ok());
1654			assert_err!(XcmOverBridge::do_try_state(), TryRuntimeError::Other("Found `LaneToBridge` inconsistency for `InboundLanes`'s lane_id - missing mapping!"));
1655			cleanup(bridge_id, vec![lane_id]);
1656
1657			// missing bridge for outbound lane
1658			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1659			assert!(lanes_manager.create_outbound_lane(lane_id).is_ok());
1660			assert_err!(XcmOverBridge::do_try_state(), TryRuntimeError::Other("Found `LaneToBridge` inconsistency for `OutboundLanes`'s lane_id - missing mapping!"));
1661			cleanup(bridge_id, vec![lane_id]);
1662		});
1663	}
1664
1665	#[test]
1666	fn ensure_encoding_compatibility() {
1667		use codec::Encode;
1668
1669		let bridge_destination_universal_location = BridgedUniversalDestination::get();
1670		let may_prune_messages = 13;
1671
1672		assert_eq!(
1673			bp_xcm_bridge_hub::XcmBridgeHubCall::open_bridge {
1674				bridge_destination_universal_location: Box::new(
1675					bridge_destination_universal_location.clone().into()
1676				)
1677			}
1678			.encode(),
1679			Call::<TestRuntime, ()>::open_bridge {
1680				bridge_destination_universal_location: Box::new(
1681					bridge_destination_universal_location.clone().into()
1682				)
1683			}
1684			.encode()
1685		);
1686		assert_eq!(
1687			bp_xcm_bridge_hub::XcmBridgeHubCall::close_bridge {
1688				bridge_destination_universal_location: Box::new(
1689					bridge_destination_universal_location.clone().into()
1690				),
1691				may_prune_messages,
1692			}
1693			.encode(),
1694			Call::<TestRuntime, ()>::close_bridge {
1695				bridge_destination_universal_location: Box::new(
1696					bridge_destination_universal_location.clone().into()
1697				),
1698				may_prune_messages,
1699			}
1700			.encode()
1701		);
1702	}
1703}