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 = &(*bridge.bridge_origin_relative_location).try_into()
668				.map_err(|_| "`bridge.bridge_origin_relative_location` cannot be converted to the `latest` XCM, needs migration!")?;
669			let bridge_origin_universal_location_as_latest: &InteriorLocation = &(*bridge.bridge_origin_universal_location).try_into()
670				.map_err(|_| "`bridge.bridge_origin_universal_location` cannot be converted to the `latest` XCM, needs migration!")?;
671			let bridge_destination_universal_location_as_latest: &InteriorLocation = &(*bridge.bridge_destination_universal_location).try_into()
672				.map_err(|_| "`bridge.bridge_destination_universal_location` cannot be converted to the `latest` XCM, needs migration!")?;
673
674			// check `BridgeId` does not change
675			ensure!(
676				bridge_id == BridgeId::new(bridge_origin_universal_location_as_latest, bridge_destination_universal_location_as_latest),
677				"`bridge_id` is different than calculated from `bridge_origin_universal_location_as_latest` and `bridge_destination_universal_location_as_latest`, needs migration!"
678			);
679
680			// check bridge account owner
681			ensure!(
682				T::BridgeOriginAccountIdConverter::convert_location(bridge_origin_relative_location_as_latest) == Some(bridge.bridge_owner_account),
683				"`bridge.bridge_owner_account` is different than calculated from `bridge.bridge_origin_relative_location`, needs migration!"
684			);
685
686			Ok(bridge.lane_id)
687		}
688
689		/// Ensure the correctness of the state of the connected `pallet_bridge_messages` instance.
690		pub fn do_try_state_for_messages() -> Result<(), sp_runtime::TryRuntimeError> {
691			// check that all `InboundLanes` laneIds have mapping to some bridge.
692			for lane_id in pallet_bridge_messages::InboundLanes::<T, T::BridgeMessagesPalletInstance>::iter_keys() {
693				log::info!(target: LOG_TARGET, "Checking `do_try_state_for_messages` for `InboundLanes`'s lane_id: {lane_id:?}...");
694				ensure!(
695					LaneToBridge::<T, I>::get(lane_id).is_some(),
696					"Found `LaneToBridge` inconsistency for `InboundLanes`'s lane_id - missing mapping!"
697				);
698			}
699
700			// check that all `OutboundLanes` laneIds have mapping to some bridge.
701			for lane_id in pallet_bridge_messages::OutboundLanes::<T, T::BridgeMessagesPalletInstance>::iter_keys() {
702				log::info!(target: LOG_TARGET, "Checking `do_try_state_for_messages` for `OutboundLanes`'s lane_id: {lane_id:?}...");
703				ensure!(
704					LaneToBridge::<T, I>::get(lane_id).is_some(),
705					"Found `LaneToBridge` inconsistency for `OutboundLanes`'s lane_id - missing mapping!"
706				);
707			}
708
709			Ok(())
710		}
711	}
712
713	/// All registered bridges.
714	#[pallet::storage]
715	pub type Bridges<T: Config<I>, I: 'static = ()> =
716		StorageMap<_, Identity, BridgeId, BridgeOf<T, I>>;
717	/// All registered `lane_id` and `bridge_id` mappings.
718	#[pallet::storage]
719	pub type LaneToBridge<T: Config<I>, I: 'static = ()> =
720		StorageMap<_, Identity, T::LaneId, BridgeId>;
721
722	#[pallet::genesis_config]
723	#[derive(DefaultNoBound)]
724	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
725		/// Opened bridges.
726		///
727		/// Keep in mind that we are **NOT** reserving any amount for the bridges opened at
728		/// genesis. We are **NOT** opening lanes, used by this bridge. It all must be done using
729		/// other pallets genesis configuration or some other means.
730		pub opened_bridges: Vec<(Location, InteriorLocation, Option<T::LaneId>)>,
731		/// Dummy marker.
732		#[serde(skip)]
733		pub _phantom: sp_std::marker::PhantomData<(T, I)>,
734	}
735
736	#[pallet::genesis_build]
737	impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I>
738	where
739		T: frame_system::Config<AccountId = AccountIdOf<ThisChainOf<T, I>>>,
740	{
741		fn build(&self) {
742			for (
743				bridge_origin_relative_location,
744				bridge_destination_universal_location,
745				maybe_lane_id,
746			) in &self.opened_bridges
747			{
748				let locations = Pallet::<T, I>::bridge_locations(
749					bridge_origin_relative_location.clone(),
750					bridge_destination_universal_location.clone().into(),
751				)
752				.expect("Invalid genesis configuration");
753
754				let lane_id = match maybe_lane_id {
755					Some(lane_id) => *lane_id,
756					None =>
757						locations.calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"),
758				};
759
760				Pallet::<T, I>::do_open_bridge(locations, lane_id, true)
761					.expect("Valid opened bridge!");
762			}
763		}
764	}
765
766	#[pallet::event]
767	#[pallet::generate_deposit(pub(super) fn deposit_event)]
768	pub enum Event<T: Config<I>, I: 'static = ()> {
769		/// The bridge between two locations has been opened.
770		BridgeOpened {
771			/// Bridge identifier.
772			bridge_id: BridgeId,
773			/// Amount of deposit held.
774			bridge_deposit: BalanceOf<ThisChainOf<T, I>>,
775
776			/// Universal location of local bridge endpoint.
777			local_endpoint: Box<InteriorLocation>,
778			/// Universal location of remote bridge endpoint.
779			remote_endpoint: Box<InteriorLocation>,
780			/// Lane identifier.
781			lane_id: T::LaneId,
782		},
783		/// Bridge is going to be closed, but not yet fully pruned from the runtime storage.
784		ClosingBridge {
785			/// Bridge identifier.
786			bridge_id: BridgeId,
787			/// Lane identifier.
788			lane_id: T::LaneId,
789			/// Number of pruned messages during the close call.
790			pruned_messages: MessageNonce,
791			/// Number of enqueued messages that need to be pruned in follow up calls.
792			enqueued_messages: MessageNonce,
793		},
794		/// Bridge has been closed and pruned from the runtime storage. It now may be reopened
795		/// again by any participant.
796		BridgePruned {
797			/// Bridge identifier.
798			bridge_id: BridgeId,
799			/// Lane identifier.
800			lane_id: T::LaneId,
801			/// Amount of deposit released.
802			bridge_deposit: BalanceOf<ThisChainOf<T, I>>,
803			/// Number of pruned messages during the close call.
804			pruned_messages: MessageNonce,
805		},
806	}
807
808	#[pallet::error]
809	pub enum Error<T, I = ()> {
810		/// Bridge locations error.
811		BridgeLocations(BridgeLocationsError),
812		/// Invalid local bridge origin account.
813		InvalidBridgeOriginAccount,
814		/// The bridge is already registered in this pallet.
815		BridgeAlreadyExists,
816		/// The local origin already owns a maximal number of bridges.
817		TooManyBridgesForLocalOrigin,
818		/// Trying to close already closed bridge.
819		BridgeAlreadyClosed,
820		/// Lanes manager error.
821		LanesManager(LanesManagerError),
822		/// Trying to access unknown bridge.
823		UnknownBridge,
824		/// The bridge origin can't pay the required amount for opening the bridge.
825		FailedToReserveBridgeDeposit,
826		/// The version of XCM location argument is unsupported.
827		UnsupportedXcmVersion,
828	}
829}
830
831#[cfg(test)]
832mod tests {
833	use super::*;
834	use bp_messages::LaneIdType;
835	use mock::*;
836
837	use frame_support::{assert_err, assert_noop, assert_ok, traits::fungible::Mutate, BoundedVec};
838	use frame_system::{EventRecord, Phase};
839	use sp_runtime::TryRuntimeError;
840
841	fn fund_origin_sovereign_account(locations: &BridgeLocations, balance: Balance) -> AccountId {
842		let bridge_owner_account =
843			LocationToAccountId::convert_location(locations.bridge_origin_relative_location())
844				.unwrap();
845		assert_ok!(Balances::mint_into(&bridge_owner_account, balance));
846		bridge_owner_account
847	}
848
849	fn mock_open_bridge_from_with(
850		origin: RuntimeOrigin,
851		deposit: Balance,
852		with: InteriorLocation,
853	) -> (BridgeOf<TestRuntime, ()>, BridgeLocations) {
854		let locations =
855			XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap();
856		let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
857		let bridge_owner_account =
858			fund_origin_sovereign_account(&locations, deposit + ExistentialDeposit::get());
859		Balances::hold(&HoldReason::BridgeDeposit.into(), &bridge_owner_account, deposit).unwrap();
860
861		let bridge = Bridge {
862			bridge_origin_relative_location: Box::new(
863				locations.bridge_origin_relative_location().clone().into(),
864			),
865			bridge_origin_universal_location: Box::new(
866				locations.bridge_origin_universal_location().clone().into(),
867			),
868			bridge_destination_universal_location: Box::new(
869				locations.bridge_destination_universal_location().clone().into(),
870			),
871			state: BridgeState::Opened,
872			bridge_owner_account,
873			deposit,
874			lane_id,
875		};
876		Bridges::<TestRuntime, ()>::insert(locations.bridge_id(), bridge.clone());
877		LaneToBridge::<TestRuntime, ()>::insert(bridge.lane_id, locations.bridge_id());
878
879		let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
880		lanes_manager.create_inbound_lane(bridge.lane_id).unwrap();
881		lanes_manager.create_outbound_lane(bridge.lane_id).unwrap();
882
883		assert_ok!(XcmOverBridge::do_try_state());
884
885		(bridge, *locations)
886	}
887
888	fn mock_open_bridge_from(
889		origin: RuntimeOrigin,
890		deposit: Balance,
891	) -> (BridgeOf<TestRuntime, ()>, BridgeLocations) {
892		mock_open_bridge_from_with(origin, deposit, bridged_asset_hub_universal_location())
893	}
894
895	fn enqueue_message(lane: TestLaneIdType) {
896		let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
897		lanes_manager
898			.active_outbound_lane(lane)
899			.unwrap()
900			.send_message(BoundedVec::try_from(vec![42]).expect("We craft valid messages"));
901	}
902
903	#[test]
904	fn open_bridge_fails_if_origin_is_not_allowed() {
905		run_test(|| {
906			assert_noop!(
907				XcmOverBridge::open_bridge(
908					OpenBridgeOrigin::disallowed_origin(),
909					Box::new(bridged_asset_hub_universal_location().into()),
910				),
911				sp_runtime::DispatchError::BadOrigin,
912			);
913		})
914	}
915
916	#[test]
917	fn open_bridge_fails_if_origin_is_not_relative() {
918		run_test(|| {
919			assert_noop!(
920				XcmOverBridge::open_bridge(
921					OpenBridgeOrigin::parent_relay_chain_universal_origin(),
922					Box::new(bridged_asset_hub_universal_location().into()),
923				),
924				Error::<TestRuntime, ()>::BridgeLocations(
925					BridgeLocationsError::InvalidBridgeOrigin
926				),
927			);
928
929			assert_noop!(
930				XcmOverBridge::open_bridge(
931					OpenBridgeOrigin::sibling_parachain_universal_origin(),
932					Box::new(bridged_asset_hub_universal_location().into()),
933				),
934				Error::<TestRuntime, ()>::BridgeLocations(
935					BridgeLocationsError::InvalidBridgeOrigin
936				),
937			);
938		})
939	}
940
941	#[test]
942	fn open_bridge_fails_if_destination_is_not_remote() {
943		run_test(|| {
944			assert_noop!(
945				XcmOverBridge::open_bridge(
946					OpenBridgeOrigin::parent_relay_chain_origin(),
947					Box::new(
948						[GlobalConsensus(RelayNetwork::get()), Parachain(BRIDGED_ASSET_HUB_ID)]
949							.into()
950					),
951				),
952				Error::<TestRuntime, ()>::BridgeLocations(BridgeLocationsError::DestinationIsLocal),
953			);
954		});
955	}
956
957	#[test]
958	fn open_bridge_fails_if_outside_of_bridged_consensus() {
959		run_test(|| {
960			assert_noop!(
961				XcmOverBridge::open_bridge(
962					OpenBridgeOrigin::parent_relay_chain_origin(),
963					Box::new(
964						[
965							GlobalConsensus(NonBridgedRelayNetwork::get()),
966							Parachain(BRIDGED_ASSET_HUB_ID)
967						]
968						.into()
969					),
970				),
971				Error::<TestRuntime, ()>::BridgeLocations(
972					BridgeLocationsError::UnreachableDestination
973				),
974			);
975		});
976	}
977
978	#[test]
979	fn open_bridge_fails_if_origin_has_no_sovereign_account() {
980		run_test(|| {
981			assert_noop!(
982				XcmOverBridge::open_bridge(
983					OpenBridgeOrigin::origin_without_sovereign_account(),
984					Box::new(bridged_asset_hub_universal_location().into()),
985				),
986				Error::<TestRuntime, ()>::InvalidBridgeOriginAccount,
987			);
988		});
989	}
990
991	#[test]
992	fn open_bridge_fails_if_origin_sovereign_account_has_no_enough_funds() {
993		run_test(|| {
994			assert_noop!(
995				XcmOverBridge::open_bridge(
996					OpenBridgeOrigin::sibling_parachain_origin(),
997					Box::new(bridged_asset_hub_universal_location().into()),
998				),
999				Error::<TestRuntime, ()>::FailedToReserveBridgeDeposit,
1000			);
1001		});
1002	}
1003
1004	#[test]
1005	fn open_bridge_fails_if_it_already_exists() {
1006		run_test(|| {
1007			let origin = OpenBridgeOrigin::parent_relay_chain_origin();
1008			let locations = XcmOverBridge::bridge_locations_from_origin(
1009				origin.clone(),
1010				Box::new(bridged_asset_hub_universal_location().into()),
1011			)
1012			.unwrap();
1013			let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
1014			fund_origin_sovereign_account(
1015				&locations,
1016				BridgeDeposit::get() + ExistentialDeposit::get(),
1017			);
1018
1019			Bridges::<TestRuntime, ()>::insert(
1020				locations.bridge_id(),
1021				Bridge {
1022					bridge_origin_relative_location: Box::new(
1023						locations.bridge_origin_relative_location().clone().into(),
1024					),
1025					bridge_origin_universal_location: Box::new(
1026						locations.bridge_origin_universal_location().clone().into(),
1027					),
1028					bridge_destination_universal_location: Box::new(
1029						locations.bridge_destination_universal_location().clone().into(),
1030					),
1031					state: BridgeState::Opened,
1032					bridge_owner_account: [0u8; 32].into(),
1033					deposit: 0,
1034					lane_id,
1035				},
1036			);
1037
1038			assert_noop!(
1039				XcmOverBridge::open_bridge(
1040					origin,
1041					Box::new(bridged_asset_hub_universal_location().into()),
1042				),
1043				Error::<TestRuntime, ()>::BridgeAlreadyExists,
1044			);
1045		})
1046	}
1047
1048	#[test]
1049	fn open_bridge_fails_if_its_lanes_already_exists() {
1050		run_test(|| {
1051			let origin = OpenBridgeOrigin::parent_relay_chain_origin();
1052			let locations = XcmOverBridge::bridge_locations_from_origin(
1053				origin.clone(),
1054				Box::new(bridged_asset_hub_universal_location().into()),
1055			)
1056			.unwrap();
1057			let lane_id = locations.calculate_lane_id(xcm::latest::VERSION).unwrap();
1058			fund_origin_sovereign_account(
1059				&locations,
1060				BridgeDeposit::get() + ExistentialDeposit::get(),
1061			);
1062
1063			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1064
1065			lanes_manager.create_inbound_lane(lane_id).unwrap();
1066			assert_noop!(
1067				XcmOverBridge::open_bridge(
1068					origin.clone(),
1069					Box::new(bridged_asset_hub_universal_location().into()),
1070				),
1071				Error::<TestRuntime, ()>::LanesManager(LanesManagerError::InboundLaneAlreadyExists),
1072			);
1073
1074			lanes_manager.active_inbound_lane(lane_id).unwrap().purge();
1075			lanes_manager.create_outbound_lane(lane_id).unwrap();
1076			assert_noop!(
1077				XcmOverBridge::open_bridge(
1078					origin,
1079					Box::new(bridged_asset_hub_universal_location().into()),
1080				),
1081				Error::<TestRuntime, ()>::LanesManager(
1082					LanesManagerError::OutboundLaneAlreadyExists
1083				),
1084			);
1085		})
1086	}
1087
1088	#[test]
1089	fn open_bridge_works() {
1090		run_test(|| {
1091			// in our test runtime, we expect that bridge may be opened by parent relay chain
1092			// and any sibling parachain
1093			let origins = [
1094				(OpenBridgeOrigin::parent_relay_chain_origin(), 0),
1095				(OpenBridgeOrigin::sibling_parachain_origin(), BridgeDeposit::get()),
1096			];
1097
1098			// check that every origin may open the bridge
1099			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1100			let existential_deposit = ExistentialDeposit::get();
1101			for (origin, expected_deposit) in origins {
1102				// reset events
1103				System::set_block_number(1);
1104				System::reset_events();
1105
1106				// compute all other locations
1107				let xcm_version = xcm::latest::VERSION;
1108				let locations = XcmOverBridge::bridge_locations_from_origin(
1109					origin.clone(),
1110					Box::new(
1111						VersionedInteriorLocation::from(bridged_asset_hub_universal_location())
1112							.into_version(xcm_version)
1113							.expect("valid conversion"),
1114					),
1115				)
1116				.unwrap();
1117				let lane_id = locations.calculate_lane_id(xcm_version).unwrap();
1118
1119				// ensure that there's no bridge and lanes in the storage
1120				assert_eq!(Bridges::<TestRuntime, ()>::get(locations.bridge_id()), None);
1121				assert_eq!(
1122					lanes_manager.active_inbound_lane(lane_id).map(drop),
1123					Err(LanesManagerError::UnknownInboundLane)
1124				);
1125				assert_eq!(
1126					lanes_manager.active_outbound_lane(lane_id).map(drop),
1127					Err(LanesManagerError::UnknownOutboundLane)
1128				);
1129				assert_eq!(LaneToBridge::<TestRuntime, ()>::get(lane_id), None);
1130
1131				// give enough funds to the sovereign account of the bridge origin
1132				let bridge_owner_account = fund_origin_sovereign_account(
1133					&locations,
1134					expected_deposit + existential_deposit,
1135				);
1136				assert_eq!(
1137					Balances::free_balance(&bridge_owner_account),
1138					expected_deposit + existential_deposit
1139				);
1140				assert_eq!(Balances::reserved_balance(&bridge_owner_account), 0);
1141
1142				// now open the bridge
1143				assert_ok!(XcmOverBridge::open_bridge(
1144					origin,
1145					Box::new(locations.bridge_destination_universal_location().clone().into()),
1146				));
1147
1148				// ensure that everything has been set up in the runtime storage
1149				assert_eq!(
1150					Bridges::<TestRuntime, ()>::get(locations.bridge_id()),
1151					Some(Bridge {
1152						bridge_origin_relative_location: Box::new(
1153							locations.bridge_origin_relative_location().clone().into()
1154						),
1155						bridge_origin_universal_location: Box::new(
1156							locations.bridge_origin_universal_location().clone().into(),
1157						),
1158						bridge_destination_universal_location: Box::new(
1159							locations.bridge_destination_universal_location().clone().into(),
1160						),
1161						state: BridgeState::Opened,
1162						bridge_owner_account: bridge_owner_account.clone(),
1163						deposit: expected_deposit,
1164						lane_id
1165					}),
1166				);
1167				assert_eq!(
1168					lanes_manager.active_inbound_lane(lane_id).map(|l| l.state()),
1169					Ok(LaneState::Opened)
1170				);
1171				assert_eq!(
1172					lanes_manager.active_outbound_lane(lane_id).map(|l| l.state()),
1173					Ok(LaneState::Opened)
1174				);
1175				assert_eq!(
1176					LaneToBridge::<TestRuntime, ()>::get(lane_id),
1177					Some(*locations.bridge_id())
1178				);
1179				assert_eq!(Balances::free_balance(&bridge_owner_account), existential_deposit);
1180				assert_eq!(Balances::reserved_balance(&bridge_owner_account), expected_deposit);
1181
1182				// ensure that the proper event is deposited
1183				assert_eq!(
1184					System::events().last(),
1185					Some(&EventRecord {
1186						phase: Phase::Initialization,
1187						event: RuntimeEvent::XcmOverBridge(Event::BridgeOpened {
1188							bridge_id: *locations.bridge_id(),
1189							bridge_deposit: expected_deposit,
1190							local_endpoint: Box::new(
1191								locations.bridge_origin_universal_location().clone()
1192							),
1193							remote_endpoint: Box::new(
1194								locations.bridge_destination_universal_location().clone()
1195							),
1196							lane_id: lane_id.into()
1197						}),
1198						topics: vec![],
1199					}),
1200				);
1201
1202				// check state
1203				assert_ok!(XcmOverBridge::do_try_state());
1204			}
1205		});
1206	}
1207
1208	#[test]
1209	fn close_bridge_fails_if_origin_is_not_allowed() {
1210		run_test(|| {
1211			assert_noop!(
1212				XcmOverBridge::close_bridge(
1213					OpenBridgeOrigin::disallowed_origin(),
1214					Box::new(bridged_asset_hub_universal_location().into()),
1215					0,
1216				),
1217				sp_runtime::DispatchError::BadOrigin,
1218			);
1219		})
1220	}
1221
1222	#[test]
1223	fn close_bridge_fails_if_origin_is_not_relative() {
1224		run_test(|| {
1225			assert_noop!(
1226				XcmOverBridge::close_bridge(
1227					OpenBridgeOrigin::parent_relay_chain_universal_origin(),
1228					Box::new(bridged_asset_hub_universal_location().into()),
1229					0,
1230				),
1231				Error::<TestRuntime, ()>::BridgeLocations(
1232					BridgeLocationsError::InvalidBridgeOrigin
1233				),
1234			);
1235
1236			assert_noop!(
1237				XcmOverBridge::close_bridge(
1238					OpenBridgeOrigin::sibling_parachain_universal_origin(),
1239					Box::new(bridged_asset_hub_universal_location().into()),
1240					0,
1241				),
1242				Error::<TestRuntime, ()>::BridgeLocations(
1243					BridgeLocationsError::InvalidBridgeOrigin
1244				),
1245			);
1246		})
1247	}
1248
1249	#[test]
1250	fn close_bridge_fails_if_its_lanes_are_unknown() {
1251		run_test(|| {
1252			let origin = OpenBridgeOrigin::parent_relay_chain_origin();
1253			let (bridge, locations) = mock_open_bridge_from(origin.clone(), 0);
1254
1255			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1256			lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().purge();
1257			assert_noop!(
1258				XcmOverBridge::close_bridge(
1259					origin.clone(),
1260					Box::new(locations.bridge_destination_universal_location().clone().into()),
1261					0,
1262				),
1263				Error::<TestRuntime, ()>::LanesManager(LanesManagerError::UnknownInboundLane),
1264			);
1265			lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().purge();
1266
1267			let (_, locations) = mock_open_bridge_from(origin.clone(), 0);
1268			lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().purge();
1269			assert_noop!(
1270				XcmOverBridge::close_bridge(
1271					origin,
1272					Box::new(locations.bridge_destination_universal_location().clone().into()),
1273					0,
1274				),
1275				Error::<TestRuntime, ()>::LanesManager(LanesManagerError::UnknownOutboundLane),
1276			);
1277		});
1278	}
1279
1280	#[test]
1281	fn close_bridge_works() {
1282		run_test(|| {
1283			let origin = OpenBridgeOrigin::parent_relay_chain_origin();
1284			let expected_deposit = BridgeDeposit::get();
1285			let (bridge, locations) = mock_open_bridge_from(origin.clone(), expected_deposit);
1286			System::set_block_number(1);
1287
1288			// remember owner balances
1289			let free_balance = Balances::free_balance(&bridge.bridge_owner_account);
1290			let reserved_balance = Balances::reserved_balance(&bridge.bridge_owner_account);
1291
1292			// enqueue some messages
1293			for _ in 0..32 {
1294				enqueue_message(bridge.lane_id);
1295			}
1296
1297			// now call the `close_bridge`, which will only partially prune messages
1298			assert_ok!(XcmOverBridge::close_bridge(
1299				origin.clone(),
1300				Box::new(locations.bridge_destination_universal_location().clone().into()),
1301				16,
1302			),);
1303
1304			// as a result, the bridge and lanes are switched to the `Closed` state, some messages
1305			// are pruned, but funds are not unreserved
1306			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1307			assert_eq!(
1308				Bridges::<TestRuntime, ()>::get(locations.bridge_id()).map(|b| b.state),
1309				Some(BridgeState::Closed)
1310			);
1311			assert_eq!(
1312				lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().state(),
1313				LaneState::Closed
1314			);
1315			assert_eq!(
1316				lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().state(),
1317				LaneState::Closed
1318			);
1319			assert_eq!(
1320				lanes_manager
1321					.any_state_outbound_lane(bridge.lane_id)
1322					.unwrap()
1323					.queued_messages()
1324					.checked_len(),
1325				Some(16)
1326			);
1327			assert_eq!(
1328				LaneToBridge::<TestRuntime, ()>::get(bridge.lane_id),
1329				Some(*locations.bridge_id())
1330			);
1331			assert_eq!(Balances::free_balance(&bridge.bridge_owner_account), free_balance);
1332			assert_eq!(Balances::reserved_balance(&bridge.bridge_owner_account), reserved_balance);
1333			assert_eq!(
1334				System::events().last(),
1335				Some(&EventRecord {
1336					phase: Phase::Initialization,
1337					event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge {
1338						bridge_id: *locations.bridge_id(),
1339						lane_id: bridge.lane_id.into(),
1340						pruned_messages: 16,
1341						enqueued_messages: 16,
1342					}),
1343					topics: vec![],
1344				}),
1345			);
1346
1347			// now call the `close_bridge` again, which will only partially prune messages
1348			assert_ok!(XcmOverBridge::close_bridge(
1349				origin.clone(),
1350				Box::new(locations.bridge_destination_universal_location().clone().into()),
1351				8,
1352			),);
1353
1354			// nothing is changed (apart from the pruned messages)
1355			assert_eq!(
1356				Bridges::<TestRuntime, ()>::get(locations.bridge_id()).map(|b| b.state),
1357				Some(BridgeState::Closed)
1358			);
1359			assert_eq!(
1360				lanes_manager.any_state_inbound_lane(bridge.lane_id).unwrap().state(),
1361				LaneState::Closed
1362			);
1363			assert_eq!(
1364				lanes_manager.any_state_outbound_lane(bridge.lane_id).unwrap().state(),
1365				LaneState::Closed
1366			);
1367			assert_eq!(
1368				lanes_manager
1369					.any_state_outbound_lane(bridge.lane_id)
1370					.unwrap()
1371					.queued_messages()
1372					.checked_len(),
1373				Some(8)
1374			);
1375			assert_eq!(
1376				LaneToBridge::<TestRuntime, ()>::get(bridge.lane_id),
1377				Some(*locations.bridge_id())
1378			);
1379			assert_eq!(Balances::free_balance(&bridge.bridge_owner_account), free_balance);
1380			assert_eq!(Balances::reserved_balance(&bridge.bridge_owner_account), reserved_balance);
1381			assert_eq!(
1382				System::events().last(),
1383				Some(&EventRecord {
1384					phase: Phase::Initialization,
1385					event: RuntimeEvent::XcmOverBridge(Event::ClosingBridge {
1386						bridge_id: *locations.bridge_id(),
1387						lane_id: bridge.lane_id.into(),
1388						pruned_messages: 8,
1389						enqueued_messages: 8,
1390					}),
1391					topics: vec![],
1392				}),
1393			);
1394
1395			// now call the `close_bridge` again that will prune all remaining messages and the
1396			// bridge
1397			assert_ok!(XcmOverBridge::close_bridge(
1398				origin,
1399				Box::new(locations.bridge_destination_universal_location().clone().into()),
1400				9,
1401			),);
1402
1403			// there's no traces of bridge in the runtime storage and funds are unreserved
1404			assert_eq!(
1405				Bridges::<TestRuntime, ()>::get(locations.bridge_id()).map(|b| b.state),
1406				None
1407			);
1408			assert_eq!(
1409				lanes_manager.any_state_inbound_lane(bridge.lane_id).map(drop),
1410				Err(LanesManagerError::UnknownInboundLane)
1411			);
1412			assert_eq!(
1413				lanes_manager.any_state_outbound_lane(bridge.lane_id).map(drop),
1414				Err(LanesManagerError::UnknownOutboundLane)
1415			);
1416			assert_eq!(LaneToBridge::<TestRuntime, ()>::get(bridge.lane_id), None);
1417			assert_eq!(
1418				Balances::free_balance(&bridge.bridge_owner_account),
1419				free_balance + reserved_balance
1420			);
1421			assert_eq!(Balances::reserved_balance(&bridge.bridge_owner_account), 0);
1422			assert_eq!(
1423				System::events().last(),
1424				Some(&EventRecord {
1425					phase: Phase::Initialization,
1426					event: RuntimeEvent::XcmOverBridge(Event::BridgePruned {
1427						bridge_id: *locations.bridge_id(),
1428						lane_id: bridge.lane_id.into(),
1429						bridge_deposit: expected_deposit,
1430						pruned_messages: 8,
1431					}),
1432					topics: vec![],
1433				}),
1434			);
1435		});
1436	}
1437
1438	#[test]
1439	fn do_try_state_works() {
1440		let bridge_origin_relative_location = SiblingLocation::get();
1441		let bridge_origin_universal_location = SiblingUniversalLocation::get();
1442		let bridge_destination_universal_location = BridgedUniversalDestination::get();
1443		let bridge_owner_account =
1444			LocationToAccountId::convert_location(&bridge_origin_relative_location)
1445				.expect("valid accountId");
1446		let bridge_owner_account_mismatch =
1447			LocationToAccountId::convert_location(&Location::parent()).expect("valid accountId");
1448		let bridge_id = BridgeId::new(
1449			&bridge_origin_universal_location,
1450			&bridge_destination_universal_location,
1451		);
1452		let bridge_id_mismatch = BridgeId::new(&InteriorLocation::Here, &InteriorLocation::Here);
1453		let lane_id = TestLaneIdType::try_new(1, 2).unwrap();
1454		let lane_id_mismatch = TestLaneIdType::try_new(3, 4).unwrap();
1455
1456		let test_bridge_state =
1457			|id,
1458			 bridge,
1459			 (lane_id, bridge_id),
1460			 (inbound_lane_id, outbound_lane_id),
1461			 expected_error: Option<TryRuntimeError>| {
1462				Bridges::<TestRuntime, ()>::insert(id, bridge);
1463				LaneToBridge::<TestRuntime, ()>::insert(lane_id, bridge_id);
1464
1465				let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1466				lanes_manager.create_inbound_lane(inbound_lane_id).unwrap();
1467				lanes_manager.create_outbound_lane(outbound_lane_id).unwrap();
1468
1469				let result = XcmOverBridge::do_try_state();
1470				if let Some(e) = expected_error {
1471					assert_err!(result, e);
1472				} else {
1473					assert_ok!(result);
1474				}
1475			};
1476		let cleanup = |bridge_id, lane_ids| {
1477			Bridges::<TestRuntime, ()>::remove(bridge_id);
1478			for lane_id in lane_ids {
1479				LaneToBridge::<TestRuntime, ()>::remove(lane_id);
1480				let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1481				if let Ok(lane) = lanes_manager.any_state_inbound_lane(lane_id) {
1482					lane.purge();
1483				}
1484				if let Ok(lane) = lanes_manager.any_state_outbound_lane(lane_id) {
1485					lane.purge();
1486				}
1487			}
1488			assert_ok!(XcmOverBridge::do_try_state());
1489		};
1490
1491		run_test(|| {
1492			// ok state
1493			test_bridge_state(
1494				bridge_id,
1495				Bridge {
1496					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1497						bridge_origin_relative_location.clone(),
1498					)),
1499					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1500						bridge_origin_universal_location.clone(),
1501					)),
1502					bridge_destination_universal_location: Box::new(
1503						VersionedInteriorLocation::from(
1504							bridge_destination_universal_location.clone(),
1505						),
1506					),
1507					state: BridgeState::Opened,
1508					bridge_owner_account: bridge_owner_account.clone(),
1509					deposit: Zero::zero(),
1510					lane_id,
1511				},
1512				(lane_id, bridge_id),
1513				(lane_id, lane_id),
1514				None,
1515			);
1516			cleanup(bridge_id, vec![lane_id]);
1517
1518			// error - missing `LaneToBridge` mapping
1519			test_bridge_state(
1520				bridge_id,
1521				Bridge {
1522					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1523						bridge_origin_relative_location.clone(),
1524					)),
1525					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1526						bridge_origin_universal_location.clone(),
1527					)),
1528					bridge_destination_universal_location: Box::new(
1529						VersionedInteriorLocation::from(
1530							bridge_destination_universal_location.clone(),
1531						),
1532					),
1533					state: BridgeState::Opened,
1534					bridge_owner_account: bridge_owner_account.clone(),
1535					deposit: Zero::zero(),
1536					lane_id,
1537				},
1538				(lane_id, bridge_id_mismatch),
1539				(lane_id, lane_id),
1540				Some(TryRuntimeError::Other(
1541					"Found `LaneToBridge` inconsistency for bridge_id - missing mapping!",
1542				)),
1543			);
1544			cleanup(bridge_id, vec![lane_id]);
1545
1546			// error bridge owner account cannot be calculated
1547			test_bridge_state(
1548				bridge_id,
1549				Bridge {
1550					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1551						bridge_origin_relative_location.clone(),
1552					)),
1553					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1554						bridge_origin_universal_location.clone(),
1555					)),
1556					bridge_destination_universal_location: Box::new(VersionedInteriorLocation::from(
1557						bridge_destination_universal_location.clone(),
1558					)),
1559					state: BridgeState::Opened,
1560					bridge_owner_account: bridge_owner_account_mismatch.clone(),
1561					deposit: Zero::zero(),
1562					lane_id,
1563				},
1564				(lane_id, bridge_id),
1565				(lane_id, lane_id),
1566				Some(TryRuntimeError::Other("`bridge.bridge_owner_account` is different than calculated from `bridge.bridge_origin_relative_location`, needs migration!")),
1567			);
1568			cleanup(bridge_id, vec![lane_id]);
1569
1570			// error when (bridge_origin_universal_location + bridge_destination_universal_location)
1571			// produces different `BridgeId`
1572			test_bridge_state(
1573				bridge_id_mismatch,
1574				Bridge {
1575					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1576						bridge_origin_relative_location.clone(),
1577					)),
1578					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1579						bridge_origin_universal_location.clone(),
1580					)),
1581					bridge_destination_universal_location: Box::new(VersionedInteriorLocation::from(
1582						bridge_destination_universal_location.clone(),
1583					)),
1584					state: BridgeState::Opened,
1585					bridge_owner_account: bridge_owner_account_mismatch.clone(),
1586					deposit: Zero::zero(),
1587					lane_id,
1588				},
1589				(lane_id, bridge_id_mismatch),
1590				(lane_id, lane_id),
1591				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!")),
1592			);
1593			cleanup(bridge_id_mismatch, vec![lane_id]);
1594
1595			// missing inbound lane for a bridge
1596			test_bridge_state(
1597				bridge_id,
1598				Bridge {
1599					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1600						bridge_origin_relative_location.clone(),
1601					)),
1602					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1603						bridge_origin_universal_location.clone(),
1604					)),
1605					bridge_destination_universal_location: Box::new(
1606						VersionedInteriorLocation::from(
1607							bridge_destination_universal_location.clone(),
1608						),
1609					),
1610					state: BridgeState::Opened,
1611					bridge_owner_account: bridge_owner_account.clone(),
1612					deposit: Zero::zero(),
1613					lane_id,
1614				},
1615				(lane_id, bridge_id),
1616				(lane_id_mismatch, lane_id),
1617				Some(TryRuntimeError::Other("Inbound lane not found!")),
1618			);
1619			cleanup(bridge_id, vec![lane_id, lane_id_mismatch]);
1620
1621			// missing outbound lane for a bridge
1622			test_bridge_state(
1623				bridge_id,
1624				Bridge {
1625					bridge_origin_relative_location: Box::new(VersionedLocation::from(
1626						bridge_origin_relative_location.clone(),
1627					)),
1628					bridge_origin_universal_location: Box::new(VersionedInteriorLocation::from(
1629						bridge_origin_universal_location.clone(),
1630					)),
1631					bridge_destination_universal_location: Box::new(
1632						VersionedInteriorLocation::from(
1633							bridge_destination_universal_location.clone(),
1634						),
1635					),
1636					state: BridgeState::Opened,
1637					bridge_owner_account: bridge_owner_account.clone(),
1638					deposit: Zero::zero(),
1639					lane_id,
1640				},
1641				(lane_id, bridge_id),
1642				(lane_id, lane_id_mismatch),
1643				Some(TryRuntimeError::Other("Outbound lane not found!")),
1644			);
1645			cleanup(bridge_id, vec![lane_id, lane_id_mismatch]);
1646
1647			// ok state with old XCM version
1648			test_bridge_state(
1649				bridge_id,
1650				Bridge {
1651					bridge_origin_relative_location: Box::new(
1652						VersionedLocation::from(bridge_origin_relative_location.clone())
1653							.into_version(XCM_VERSION - 1)
1654							.unwrap(),
1655					),
1656					bridge_origin_universal_location: Box::new(
1657						VersionedInteriorLocation::from(bridge_origin_universal_location.clone())
1658							.into_version(XCM_VERSION - 1)
1659							.unwrap(),
1660					),
1661					bridge_destination_universal_location: Box::new(
1662						VersionedInteriorLocation::from(
1663							bridge_destination_universal_location.clone(),
1664						)
1665						.into_version(XCM_VERSION - 1)
1666						.unwrap(),
1667					),
1668					state: BridgeState::Opened,
1669					bridge_owner_account: bridge_owner_account.clone(),
1670					deposit: Zero::zero(),
1671					lane_id,
1672				},
1673				(lane_id, bridge_id),
1674				(lane_id, lane_id),
1675				None,
1676			);
1677			cleanup(bridge_id, vec![lane_id]);
1678
1679			// missing bridge for inbound lane
1680			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1681			assert!(lanes_manager.create_inbound_lane(lane_id).is_ok());
1682			assert_err!(XcmOverBridge::do_try_state(), TryRuntimeError::Other("Found `LaneToBridge` inconsistency for `InboundLanes`'s lane_id - missing mapping!"));
1683			cleanup(bridge_id, vec![lane_id]);
1684
1685			// missing bridge for outbound lane
1686			let lanes_manager = LanesManagerOf::<TestRuntime, ()>::new();
1687			assert!(lanes_manager.create_outbound_lane(lane_id).is_ok());
1688			assert_err!(XcmOverBridge::do_try_state(), TryRuntimeError::Other("Found `LaneToBridge` inconsistency for `OutboundLanes`'s lane_id - missing mapping!"));
1689			cleanup(bridge_id, vec![lane_id]);
1690		});
1691	}
1692
1693	#[test]
1694	fn ensure_encoding_compatibility() {
1695		use codec::Encode;
1696
1697		let bridge_destination_universal_location = BridgedUniversalDestination::get();
1698		let may_prune_messages = 13;
1699
1700		assert_eq!(
1701			bp_xcm_bridge_hub::XcmBridgeHubCall::open_bridge {
1702				bridge_destination_universal_location: Box::new(
1703					bridge_destination_universal_location.clone().into()
1704				)
1705			}
1706			.encode(),
1707			Call::<TestRuntime, ()>::open_bridge {
1708				bridge_destination_universal_location: Box::new(
1709					bridge_destination_universal_location.clone().into()
1710				)
1711			}
1712			.encode()
1713		);
1714		assert_eq!(
1715			bp_xcm_bridge_hub::XcmBridgeHubCall::close_bridge {
1716				bridge_destination_universal_location: Box::new(
1717					bridge_destination_universal_location.clone().into()
1718				),
1719				may_prune_messages,
1720			}
1721			.encode(),
1722			Call::<TestRuntime, ()>::close_bridge {
1723				bridge_destination_universal_location: Box::new(
1724					bridge_destination_universal_location.clone().into()
1725				),
1726				may_prune_messages,
1727			}
1728			.encode()
1729		);
1730	}
1731}