referrerpolicy=no-referrer-when-downgrade

pallet_xcm_bridge_hub_router/
lib.rs

1// Copyright (C) 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//! Pallet that may be used instead of `SovereignPaidRemoteExporter` in the XCM router
18//! configuration. The main thing that the pallet offers is the dynamic message fee,
19//! that is computed based on the bridge queues state. It starts exponentially increasing
20//! if the queue between this chain and the sibling/child bridge hub is congested.
21//!
22//! All other bridge hub queues offer some backpressure mechanisms. So if at least one
23//! of all queues is congested, it will eventually lead to the growth of the queue at
24//! this chain.
25//!
26//! **A note on terminology**: when we mention the bridge hub here, we mean the chain that
27//! has the messages pallet deployed (`pallet-bridge-grandpa`, `pallet-bridge-messages`,
28//! `pallet-xcm-bridge-hub`, ...). It may be the system bridge hub parachain or any other
29//! chain.
30
31#![cfg_attr(not(feature = "std"), no_std)]
32
33use bp_xcm_bridge_hub_router::MINIMAL_DELIVERY_FEE_FACTOR;
34pub use bp_xcm_bridge_hub_router::{BridgeState, XcmChannelStatusProvider};
35use codec::Encode;
36use frame_support::traits::Get;
37use polkadot_runtime_parachains::FeeTracker;
38use sp_core::H256;
39use sp_runtime::{FixedPointNumber, FixedU128};
40use sp_std::vec::Vec;
41use xcm::prelude::*;
42use xcm_builder::{ExporterFor, InspectMessageQueues, SovereignPaidRemoteExporter};
43
44pub use pallet::*;
45pub use weights::WeightInfo;
46
47pub mod benchmarking;
48pub mod weights;
49
50mod mock;
51
52/// Maximal size of the XCM message that may be sent over bridge.
53///
54/// This should be less than the maximal size, allowed by the messages pallet, because
55/// the message itself is wrapped in other structs and is double encoded.
56pub const HARD_MESSAGE_SIZE_LIMIT: u32 = 32 * 1024;
57
58/// The target that will be used when publishing logs related to this pallet.
59///
60/// This doesn't match the pattern used by other bridge pallets (`runtime::bridge-*`). But this
61/// pallet has significant differences with those pallets. The main one is that is intended to
62/// be deployed at sending chains. Other bridge pallets are likely to be deployed at the separate
63/// bridge hub parachain.
64pub const LOG_TARGET: &str = "xcm::bridge-hub-router";
65
66#[frame_support::pallet]
67pub mod pallet {
68	use super::*;
69	use frame_support::pallet_prelude::*;
70	use frame_system::pallet_prelude::*;
71
72	#[pallet::config]
73	pub trait Config<I: 'static = ()>: frame_system::Config {
74		/// The overarching event type.
75		#[allow(deprecated)]
76		type RuntimeEvent: From<Event<Self, I>>
77			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
78		/// Benchmarks results from runtime we're plugged into.
79		type WeightInfo: WeightInfo;
80
81		/// Universal location of this runtime.
82		type UniversalLocation: Get<InteriorLocation>;
83		/// Relative location of the supported sibling bridge hub.
84		type SiblingBridgeHubLocation: Get<Location>;
85		/// The bridged network that this config is for if specified.
86		/// Also used for filtering `Bridges` by `BridgedNetworkId`.
87		/// If not specified, allows all networks pass through.
88		type BridgedNetworkId: Get<Option<NetworkId>>;
89		/// Configuration for supported **bridged networks/locations** with **bridge location** and
90		/// **possible fee**. Allows to externalize better control over allowed **bridged
91		/// networks/locations**.
92		type Bridges: ExporterFor;
93		/// Checks the XCM version for the destination.
94		type DestinationVersion: GetVersion;
95
96		/// Origin of the sibling bridge hub that is allowed to report bridge status.
97		type BridgeHubOrigin: EnsureOrigin<Self::RuntimeOrigin>;
98		/// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location.
99		type ToBridgeHubSender: SendXcm;
100		/// Local XCM channel manager.
101		type LocalXcmChannelManager: XcmChannelStatusProvider;
102
103		/// Additional fee that is paid for every byte of the outbound message.
104		type ByteFee: Get<u128>;
105		/// Asset that is used to paid bridge fee.
106		type FeeAsset: Get<AssetId>;
107	}
108
109	#[pallet::pallet]
110	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
111
112	#[pallet::hooks]
113	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
114		fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
115			// if XCM channel is still congested, we don't change anything
116			if T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get()) {
117				return T::WeightInfo::on_initialize_when_congested();
118			}
119
120			// if bridge has reported congestion, we don't change anything
121			let mut bridge = Self::bridge();
122			if bridge.is_congested {
123				return T::WeightInfo::on_initialize_when_congested();
124			}
125
126			let previous_factor = Self::get_fee_factor(());
127			// if we can't decrease the delivery fee factor anymore, we don't change anything
128			if !Self::do_decrease_fee_factor(&mut bridge.delivery_fee_factor) {
129				return T::WeightInfo::on_initialize_when_congested();
130			}
131
132			tracing::info!(
133				target: LOG_TARGET,
134				from=%previous_factor,
135				to=%bridge.delivery_fee_factor,
136				"Bridge channel is uncongested. Decreased fee factor"
137			);
138			Self::deposit_event(Event::DeliveryFeeFactorDecreased {
139				new_value: bridge.delivery_fee_factor,
140			});
141
142			Bridge::<T, I>::put(bridge);
143
144			T::WeightInfo::on_initialize_when_non_congested()
145		}
146	}
147
148	#[pallet::call]
149	impl<T: Config<I>, I: 'static> Pallet<T, I> {
150		/// Notification about congested bridge queue.
151		#[pallet::call_index(0)]
152		#[pallet::weight(T::WeightInfo::report_bridge_status())]
153		pub fn report_bridge_status(
154			origin: OriginFor<T>,
155			// this argument is not currently used, but to ease future migration, we'll keep it
156			// here
157			bridge_id: H256,
158			is_congested: bool,
159		) -> DispatchResult {
160			T::BridgeHubOrigin::ensure_origin(origin)?;
161
162			tracing::info!(
163				target: LOG_TARGET,
164				from=?bridge_id,
165				congested=%is_congested,
166				"Received bridge status"
167			);
168
169			Bridge::<T, I>::mutate(|bridge| {
170				bridge.is_congested = is_congested;
171			});
172			Ok(())
173		}
174	}
175
176	/// Bridge that we are using.
177	///
178	/// **bridges-v1** assumptions: all outbound messages through this router are using single lane
179	/// and to single remote consensus. If there is some other remote consensus that uses the same
180	/// bridge hub, the separate pallet instance shall be used, In `v2` we'll have all required
181	/// primitives (lane-id aka bridge-id, derived from XCM locations) to support multiple  bridges
182	/// by the same pallet instance.
183	#[pallet::storage]
184	pub type Bridge<T: Config<I>, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>;
185
186	impl<T: Config<I>, I: 'static> Pallet<T, I> {
187		/// Bridge that we are using.
188		pub fn bridge() -> BridgeState {
189			Bridge::<T, I>::get()
190		}
191
192		/// Called when new message is sent (queued to local outbound XCM queue) over the bridge.
193		pub(crate) fn on_message_sent_to_bridge(message_size: u32) {
194			tracing::trace!(
195				target: LOG_TARGET,
196				?message_size, "on_message_sent_to_bridge"
197			);
198			let _ = Bridge::<T, I>::try_mutate(|bridge| {
199				let is_channel_with_bridge_hub_congested =
200					T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get());
201				let is_bridge_congested = bridge.is_congested;
202
203				// if outbound queue is not congested AND bridge has not reported congestion, do
204				// nothing
205				if !is_channel_with_bridge_hub_congested && !is_bridge_congested {
206					return Err(());
207				}
208
209				let previous_factor = Self::get_fee_factor(());
210				// ok - we need to increase the fee factor, let's do that
211				<Self as FeeTracker>::do_increase_fee_factor(
212					&mut bridge.delivery_fee_factor,
213					message_size as u128,
214				);
215
216				tracing::info!(
217					target: LOG_TARGET,
218					from=%previous_factor,
219					to=%bridge.delivery_fee_factor,
220					"Bridge channel is congested. Increased fee factor"
221				);
222				Self::deposit_event(Event::DeliveryFeeFactorIncreased {
223					new_value: bridge.delivery_fee_factor,
224				});
225				Ok(())
226			});
227		}
228	}
229
230	#[pallet::event]
231	#[pallet::generate_deposit(pub(super) fn deposit_event)]
232	pub enum Event<T: Config<I>, I: 'static = ()> {
233		/// Delivery fee factor has been decreased.
234		DeliveryFeeFactorDecreased {
235			/// New value of the `DeliveryFeeFactor`.
236			new_value: FixedU128,
237		},
238		/// Delivery fee factor has been increased.
239		DeliveryFeeFactorIncreased {
240			/// New value of the `DeliveryFeeFactor`.
241			new_value: FixedU128,
242		},
243	}
244}
245
246/// We'll be using `SovereignPaidRemoteExporter` to send remote messages over the sibling/child
247/// bridge hub.
248type ViaBridgeHubExporter<T, I> = SovereignPaidRemoteExporter<
249	Pallet<T, I>,
250	<T as Config<I>>::ToBridgeHubSender,
251	<T as Config<I>>::UniversalLocation,
252>;
253
254// This pallet acts as the `ExporterFor` for the `SovereignPaidRemoteExporter` to compute
255// message fee using fee factor.
256impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> {
257	fn exporter_for(
258		network: &NetworkId,
259		remote_location: &InteriorLocation,
260		message: &Xcm<()>,
261	) -> Option<(Location, Option<Asset>)> {
262		tracing::trace!(
263			target: LOG_TARGET,
264			?network, ?remote_location, msg=?message, "exporter_for"
265		);
266		// ensure that the message is sent to the expected bridged network (if specified).
267		if let Some(bridged_network) = T::BridgedNetworkId::get() {
268			if *network != bridged_network {
269				tracing::trace!(
270					target: LOG_TARGET,
271					bridged_network_id=?bridged_network, ?network, "Router does not support bridging!"
272				);
273				return None;
274			}
275		}
276
277		// ensure that the message is sent to the expected bridged network and location.
278		let (bridge_hub_location, maybe_payment) =
279			match T::Bridges::exporter_for(network, remote_location, message) {
280				Some((bridge_hub_location, maybe_payment))
281					if bridge_hub_location.eq(&T::SiblingBridgeHubLocation::get()) =>
282					(bridge_hub_location, maybe_payment),
283				_ => {
284					tracing::trace!(
285						target: LOG_TARGET,
286						bridged_network_id=?T::BridgedNetworkId::get(),
287						sibling_bridge_hub_location=?T::SiblingBridgeHubLocation::get(),
288						?network,
289						?remote_location,
290						"Router configured does not support bridging!"
291					);
292					return None;
293				},
294			};
295
296		// take `base_fee` from `T::Brides`, but it has to be the same `T::FeeAsset`
297		let base_fee = match maybe_payment {
298			Some(payment) => match payment {
299				Asset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount,
300				invalid_asset => {
301					tracing::error!(
302						target: LOG_TARGET,
303						bridged_network_id=?T::BridgedNetworkId::get(),
304						fee_asset=?T::FeeAsset::get(),
305						with=?invalid_asset,
306						?bridge_hub_location,
307						?network,
308						?remote_location,
309						"Router is configured for `T::FeeAsset` which is not compatible for bridging!"
310					);
311					return None;
312				},
313			},
314			None => 0,
315		};
316
317		// compute fee amount. Keep in mind that this is only the bridge fee. The fee for sending
318		// message from this chain to child/sibling bridge hub is determined by the
319		// `Config::ToBridgeHubSender`
320		let message_size = message.encoded_size();
321		let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get());
322		let fee_sum = base_fee.saturating_add(message_fee);
323		let fee_factor = Self::get_fee_factor(());
324		let fee = fee_factor.saturating_mul_int(fee_sum);
325
326		let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None };
327
328		tracing::info!(
329			target: LOG_TARGET,
330			to=?(network, remote_location),
331			bridge_fee=?fee,
332			%fee_factor,
333			"Going to send message ({message_size} bytes) over bridge."
334		);
335
336		Some((bridge_hub_location, fee))
337	}
338}
339
340// This pallet acts as the `SendXcm` to the sibling/child bridge hub instead of regular
341// XCMP/DMP transport. This allows injecting dynamic message fees into XCM programs that
342// are going to the bridged network.
343impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
344	type Ticket = (u32, <T::ToBridgeHubSender as SendXcm>::Ticket);
345
346	fn validate(
347		dest: &mut Option<Location>,
348		xcm: &mut Option<Xcm<()>>,
349	) -> SendResult<Self::Ticket> {
350		tracing::trace!(target: LOG_TARGET, msg=?xcm, destination=?dest, "validate");
351
352		// In case of success, the `ViaBridgeHubExporter` can modify XCM instructions and consume
353		// `dest` / `xcm`, so we retain the clone of original message and the destination for later
354		// `DestinationVersion` validation.
355		let xcm_to_dest_clone = xcm.clone();
356		let dest_clone = dest.clone();
357
358		// First, use the inner exporter to validate the destination to determine if it is even
359		// routable. If it is not, return an error. If it is, then the XCM is extended with
360		// instructions to pay the message fee at the sibling/child bridge hub. The cost will
361		// include both the cost of (1) delivery to the sibling bridge hub (returned by
362		// `Config::ToBridgeHubSender`) and (2) delivery to the bridged bridge hub (returned by
363		// `Self::exporter_for`).
364		match ViaBridgeHubExporter::<T, I>::validate(dest, xcm) {
365			Ok((ticket, cost)) => {
366				// If the ticket is ok, it means we are routing with this router, so we need to
367				// apply more validations to the cloned `dest` and `xcm`, which are required here.
368				let xcm_to_dest_clone = xcm_to_dest_clone.ok_or(SendError::MissingArgument)?;
369				let dest_clone = dest_clone.ok_or(SendError::MissingArgument)?;
370
371				// We won't have access to `dest` and `xcm` in the `deliver` method, so we need to
372				// precompute everything required here. However, `dest` and `xcm` were consumed by
373				// `ViaBridgeHubExporter`, so we need to use their clones.
374				let message_size = xcm_to_dest_clone.encoded_size() as _;
375
376				// The bridge doesn't support oversized or overweight messages. Therefore, it's
377				// better to drop such messages here rather than at the bridge hub. Let's check the
378				// message size."
379				if message_size > HARD_MESSAGE_SIZE_LIMIT {
380					return Err(SendError::ExceedsMaxMessageSize);
381				}
382
383				// We need to ensure that the known `dest`'s XCM version can comprehend the current
384				// `xcm` program. This may seem like an additional, unnecessary check, but it is
385				// not. A similar check is probably performed by the `ViaBridgeHubExporter`, which
386				// attempts to send a versioned message to the sibling bridge hub. However, the
387				// local bridge hub may have a higher XCM version than the remote `dest`. Once
388				// again, it is better to discard such messages here than at the bridge hub (e.g.,
389				// to avoid losing funds).
390				let destination_version = T::DestinationVersion::get_version_for(&dest_clone)
391					.ok_or(SendError::DestinationUnsupported)?;
392				VersionedXcm::from(xcm_to_dest_clone)
393					.into_version(destination_version)
394					.map_err(|()| SendError::DestinationUnsupported)?;
395
396				Ok(((message_size, ticket), cost))
397			},
398			Err(e) => {
399				tracing::trace!(target: LOG_TARGET, error=?e, "validate - ViaBridgeHubExporter");
400				Err(e)
401			},
402		}
403	}
404
405	fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
406		// use router to enqueue message to the sibling/child bridge hub. This also should handle
407		// payment for passing through this queue.
408		let (message_size, ticket) = ticket;
409		let xcm_hash = ViaBridgeHubExporter::<T, I>::deliver(ticket)?;
410
411		// increase delivery fee factor if required
412		Self::on_message_sent_to_bridge(message_size);
413
414		tracing::trace!(target: LOG_TARGET, ?xcm_hash, "deliver - message sent");
415		Ok(xcm_hash)
416	}
417}
418
419impl<T: Config<I>, I: 'static> InspectMessageQueues for Pallet<T, I> {
420	fn clear_messages() {}
421
422	/// This router needs to implement `InspectMessageQueues` but doesn't have to
423	/// return any messages, since it just reuses the `XcmpQueue` router.
424	fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
425		Vec::new()
426	}
427}
428
429impl<T: Config<I>, I: 'static> FeeTracker for Pallet<T, I> {
430	type Id = ();
431
432	const MIN_FEE_FACTOR: FixedU128 = MINIMAL_DELIVERY_FEE_FACTOR;
433
434	fn get_fee_factor(_id: Self::Id) -> FixedU128 {
435		Self::bridge().delivery_fee_factor
436	}
437
438	fn set_fee_factor(_id: Self::Id, val: FixedU128) {
439		let mut bridge = Self::bridge();
440		bridge.delivery_fee_factor = val;
441		Bridge::<T, I>::put(bridge);
442	}
443}
444
445#[cfg(test)]
446mod tests {
447	use super::*;
448	use frame_support::assert_ok;
449	use mock::*;
450
451	use frame_support::traits::Hooks;
452	use frame_system::{EventRecord, Phase};
453	use sp_runtime::traits::One;
454
455	fn congested_bridge(delivery_fee_factor: FixedU128) -> BridgeState {
456		BridgeState { is_congested: true, delivery_fee_factor }
457	}
458
459	fn uncongested_bridge(delivery_fee_factor: FixedU128) -> BridgeState {
460		BridgeState { is_congested: false, delivery_fee_factor }
461	}
462
463	#[test]
464	fn initial_fee_factor_is_one() {
465		run_test(|| {
466			assert_eq!(
467				Bridge::<TestRuntime, ()>::get(),
468				uncongested_bridge(Pallet::<TestRuntime, ()>::MIN_FEE_FACTOR),
469			);
470		})
471	}
472
473	#[test]
474	fn fee_factor_is_not_decreased_from_on_initialize_when_xcm_channel_is_congested() {
475		run_test(|| {
476			Bridge::<TestRuntime, ()>::put(uncongested_bridge(FixedU128::from_rational(125, 100)));
477			TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get());
478
479			// it should not decrease, because queue is congested
480			let old_delivery = XcmBridgeHubRouter::bridge();
481			XcmBridgeHubRouter::on_initialize(One::one());
482			assert_eq!(XcmBridgeHubRouter::bridge(), old_delivery);
483			assert_eq!(System::events(), vec![]);
484		})
485	}
486
487	#[test]
488	fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_has_reported_congestion() {
489		run_test(|| {
490			Bridge::<TestRuntime, ()>::put(congested_bridge(FixedU128::from_rational(125, 100)));
491
492			// it should not decrease, because bridge congested
493			let old_bridge = XcmBridgeHubRouter::bridge();
494			XcmBridgeHubRouter::on_initialize(One::one());
495			assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge);
496			assert_eq!(System::events(), vec![]);
497		})
498	}
499
500	#[test]
501	fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() {
502		run_test(|| {
503			let initial_fee_factor = FixedU128::from_rational(125, 100);
504			Bridge::<TestRuntime, ()>::put(uncongested_bridge(initial_fee_factor));
505
506			// it should eventually decrease to one
507			while XcmBridgeHubRouter::bridge().delivery_fee_factor >
508				Pallet::<TestRuntime, ()>::MIN_FEE_FACTOR
509			{
510				XcmBridgeHubRouter::on_initialize(One::one());
511			}
512
513			// verify that it doesn't decrease anymore
514			XcmBridgeHubRouter::on_initialize(One::one());
515			assert_eq!(
516				XcmBridgeHubRouter::bridge(),
517				uncongested_bridge(Pallet::<TestRuntime, ()>::MIN_FEE_FACTOR)
518			);
519
520			// check emitted event
521			let first_system_event = System::events().first().cloned();
522			assert_eq!(
523				first_system_event,
524				Some(EventRecord {
525					phase: Phase::Initialization,
526					event: RuntimeEvent::XcmBridgeHubRouter(Event::DeliveryFeeFactorDecreased {
527						new_value: initial_fee_factor / XcmBridgeHubRouter::EXPONENTIAL_FEE_BASE,
528					}),
529					topics: vec![],
530				})
531			);
532		})
533	}
534
535	#[test]
536	fn not_applicable_if_destination_is_within_other_network() {
537		run_test(|| {
538			// unroutable dest
539			let dest = Location::new(2, [GlobalConsensus(ByGenesis([0; 32])), Parachain(1000)]);
540			let xcm: Xcm<()> = vec![ClearOrigin].into();
541
542			// check that router does not consume when `NotApplicable`
543			let mut xcm_wrapper = Some(xcm.clone());
544			assert_eq!(
545				XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
546				Err(SendError::NotApplicable),
547			);
548			// XCM is NOT consumed and untouched
549			assert_eq!(Some(xcm.clone()), xcm_wrapper);
550
551			// check the full `send_xcm`
552			assert_eq!(send_xcm::<XcmBridgeHubRouter>(dest, xcm,), Err(SendError::NotApplicable),);
553		});
554	}
555
556	#[test]
557	fn exceeds_max_message_size_if_size_is_above_hard_limit() {
558		run_test(|| {
559			// routable dest with XCM version
560			let dest =
561				Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]);
562			// oversized XCM
563			let xcm: Xcm<()> = vec![ClearOrigin; HARD_MESSAGE_SIZE_LIMIT as usize].into();
564
565			// dest is routable with the inner router
566			assert_ok!(ViaBridgeHubExporter::<TestRuntime, ()>::validate(
567				&mut Some(dest.clone()),
568				&mut Some(xcm.clone())
569			));
570
571			// check for oversized message
572			let mut xcm_wrapper = Some(xcm.clone());
573			assert_eq!(
574				XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
575				Err(SendError::ExceedsMaxMessageSize),
576			);
577			// XCM is consumed by the inner router
578			assert!(xcm_wrapper.is_none());
579
580			// check the full `send_xcm`
581			assert_eq!(
582				send_xcm::<XcmBridgeHubRouter>(dest, xcm,),
583				Err(SendError::ExceedsMaxMessageSize),
584			);
585		});
586	}
587
588	#[test]
589	fn destination_unsupported_if_wrap_version_fails() {
590		run_test(|| {
591			// routable dest but we don't know XCM version
592			let dest = UnknownXcmVersionForRoutableLocation::get();
593			let xcm: Xcm<()> = vec![ClearOrigin].into();
594
595			// dest is routable with the inner router
596			assert_ok!(ViaBridgeHubExporter::<TestRuntime, ()>::validate(
597				&mut Some(dest.clone()),
598				&mut Some(xcm.clone())
599			));
600
601			// check that it does not pass XCM version check
602			let mut xcm_wrapper = Some(xcm.clone());
603			assert_eq!(
604				XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut xcm_wrapper),
605				Err(SendError::DestinationUnsupported),
606			);
607			// XCM is consumed by the inner router
608			assert!(xcm_wrapper.is_none());
609
610			// check the full `send_xcm`
611			assert_eq!(
612				send_xcm::<XcmBridgeHubRouter>(dest, xcm,),
613				Err(SendError::DestinationUnsupported),
614			);
615		});
616	}
617
618	#[test]
619	fn returns_proper_delivery_price() {
620		run_test(|| {
621			let dest = Location::new(2, [GlobalConsensus(BridgedNetworkId::get())]);
622			let xcm: Xcm<()> = vec![ClearOrigin].into();
623			let msg_size = xcm.encoded_size();
624
625			// initially the base fee is used: `BASE_FEE + BYTE_FEE * msg_size + HRMP_FEE`
626			let expected_fee = BASE_FEE + BYTE_FEE * (msg_size as u128) + HRMP_FEE;
627			assert_eq!(
628				XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut Some(xcm.clone()))
629					.unwrap()
630					.1
631					.get(0),
632				Some(&(BridgeFeeAsset::get(), expected_fee).into()),
633			);
634
635			// but when factor is larger than one, it increases the fee, so it becomes:
636			// `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE`
637			let factor = FixedU128::from_rational(125, 100);
638			Bridge::<TestRuntime, ()>::put(uncongested_bridge(factor));
639			let expected_fee =
640				(FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) *
641					factor)
642					.into_inner() / FixedU128::DIV +
643					HRMP_FEE;
644			assert_eq!(
645				XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm)).unwrap().1.get(0),
646				Some(&(BridgeFeeAsset::get(), expected_fee).into()),
647			);
648		});
649	}
650
651	#[test]
652	fn sent_message_doesnt_increase_factor_if_queue_is_uncongested() {
653		run_test(|| {
654			let old_bridge = XcmBridgeHubRouter::bridge();
655			assert_eq!(
656				send_xcm::<XcmBridgeHubRouter>(
657					Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
658					vec![ClearOrigin].into(),
659				)
660				.map(drop),
661				Ok(()),
662			);
663
664			assert!(TestToBridgeHubSender::is_message_sent());
665			assert_eq!(old_bridge, XcmBridgeHubRouter::bridge());
666
667			assert_eq!(System::events(), vec![]);
668		});
669	}
670
671	#[test]
672	fn sent_message_increases_factor_if_xcm_channel_is_congested() {
673		run_test(|| {
674			TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get());
675
676			let old_bridge = XcmBridgeHubRouter::bridge();
677			assert_ok!(send_xcm::<XcmBridgeHubRouter>(
678				Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
679				vec![ClearOrigin].into(),
680			)
681			.map(drop));
682
683			assert!(TestToBridgeHubSender::is_message_sent());
684			assert!(
685				old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
686			);
687
688			// check emitted event
689			let first_system_event = System::events().first().cloned();
690			assert!(matches!(
691				first_system_event,
692				Some(EventRecord {
693					phase: Phase::Initialization,
694					event: RuntimeEvent::XcmBridgeHubRouter(
695						Event::DeliveryFeeFactorIncreased { .. }
696					),
697					..
698				})
699			));
700		});
701	}
702
703	#[test]
704	fn sent_message_increases_factor_if_bridge_has_reported_congestion() {
705		run_test(|| {
706			Bridge::<TestRuntime, ()>::put(congested_bridge(
707				Pallet::<TestRuntime, ()>::MIN_FEE_FACTOR,
708			));
709
710			let old_bridge = XcmBridgeHubRouter::bridge();
711			assert_ok!(send_xcm::<XcmBridgeHubRouter>(
712				Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]),
713				vec![ClearOrigin].into(),
714			)
715			.map(drop));
716
717			assert!(TestToBridgeHubSender::is_message_sent());
718			assert!(
719				old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor
720			);
721
722			// check emitted event
723			let first_system_event = System::events().first().cloned();
724			assert!(matches!(
725				first_system_event,
726				Some(EventRecord {
727					phase: Phase::Initialization,
728					event: RuntimeEvent::XcmBridgeHubRouter(
729						Event::DeliveryFeeFactorIncreased { .. }
730					),
731					..
732				})
733			));
734		});
735	}
736
737	#[test]
738	fn get_messages_does_not_return_anything() {
739		run_test(|| {
740			assert_ok!(send_xcm::<XcmBridgeHubRouter>(
741				(Parent, Parent, GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)).into(),
742				vec![ClearOrigin].into()
743			));
744			assert_eq!(XcmBridgeHubRouter::get_messages(), vec![]);
745		});
746	}
747}