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