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
//! A pallet that can be used as an alternative in the XCM router configuration — see the `SendXcm`
18
//! implementation for details.
19
//!
20
//! ## Features
21
//!
22
//! This pallet offers several optional features to customize functionality:
23
//!
24
//! ### Message Size Fee
25
//! An optional fee based on `T::FeeAsset` and `T::ByteFee`. If `T::FeeAsset` is not specified, this
26
//! fee is not calculated.
27
//!
28
//! ### Dynamic Fees and Congestion
29
//!
30
//! This pallet supports storing the congestion status of bridge outbound queues. The fee increases
31
//! exponentially if the queue between this chain and a sibling or child bridge hub becomes
32
//! congested. All other bridge hub queues provide backpressure mechanisms, so if any of these
33
//! queues are congested, it will eventually lead to increased queuing on this chain.
34
//!
35
//! There are two methods for storing congestion status:
36
//! 1. A dedicated extrinsic `update_bridge_status`, which relies on `T::UpdateBridgeStatusOrigin`.
37
//!    This allows the message exporter to send, for example, an XCM `Transact`.
38
//! 2. An implementation of `bp_xcm_bridge::LocalXcmChannelManager`.
39
//!
40
//! ## Usage
41
//!
42
//! This pallet provides several implementations, such as `ViaLocalBridgeExporter` and
43
//! `ViaRemoteBridgeExporter`, which can expose or access these features.
44
//!
45
//! This router can be used in two main scenarios, depending on where the router and message
46
//! exporter (e.g., `pallet_xcm_bridge_hub` or another pallet with an `ExportXcm` implementation)
47
//! are deployed:
48
//!
49
//! ### On the Same Chain as the Message Exporter
50
//! In this setup, the router directly calls an `ExportXcm` implementation. In this case,
51
//! `ViaLocalBridgeExporter` can be used as a wrapper with `T::MessageExporter`.
52
//!
53
//! ### On a Different Chain than the Message Exporter
54
//! In this setup, we need to provide a `SendXcm` implementation for `T::MessageExporter`, which
55
//! sends `ExportMessage`. For example, `SovereignPaidRemoteExporter` can be used with
56
//! `ViaRemoteBridgeExporter`.
57
//!
58
//! **Note on Terminology**: When we refer to the bridge hub, we mean the chain that has the
59
//! `pallet-bridge-messages` with an `ExportXcm` implementation deployed, such as
60
//! `pallet-xcm-bridge`. Depending on the deployment setup, `T::MessageExporter` can be
61
//! configured accordingly — see `T::MessageExporter` for additional documentation.
62

            
63
#![cfg_attr(not(feature = "std"), no_std)]
64

            
65
pub use bp_xcm_bridge_router::{BridgeState, ResolveBridgeId};
66
use frame_support::traits::{EnsureOriginWithArg, Get};
67
use parity_scale_codec::Encode;
68
use sp_runtime::{FixedPointNumber, FixedU128, Saturating};
69
use sp_std::vec::Vec;
70
use xcm::prelude::*;
71
use xcm_builder::InspectMessageQueues;
72

            
73
pub use pallet::*;
74
pub use weights::WeightInfo;
75

            
76
pub mod benchmarking;
77
pub mod impls;
78
pub mod weights;
79

            
80
mod mock;
81

            
82
#[cfg(test)]
83
mod tests;
84

            
85
/// Minimal delivery fee factor.
86
pub const MINIMAL_DELIVERY_FEE_FACTOR: FixedU128 = FixedU128::from_u32(1);
87

            
88
/// The factor that is used to increase current message fee factor when bridge experiencing
89
/// some lags.
90
const EXPONENTIAL_FEE_BASE: FixedU128 = FixedU128::from_rational(105, 100); // 1.05
91
/// The factor that is used to increase current message fee factor for every sent kilobyte.
92
const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0.001
93

            
94
/// Maximal size of the XCM message that may be sent over bridge.
95
///
96
/// This should be less than the maximal size, allowed by the messages pallet, because
97
/// the message itself is wrapped in other structs and is double encoded.
98
pub const HARD_MESSAGE_SIZE_LIMIT: u32 = 32 * 1024;
99

            
100
/// The target that will be used when publishing logs related to this pallet.
101
pub const LOG_TARGET: &str = "xcm::bridge-router";
102

            
103
5
#[frame_support::pallet]
104
pub mod pallet {
105
	use super::*;
106
	use frame_support::pallet_prelude::*;
107
	use frame_system::pallet_prelude::*;
108

            
109
	/// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`].
110
	pub mod config_preludes {
111
		use super::*;
112
		use frame_support::{derive_impl, traits::ConstU128};
113

            
114
		/// A type providing default configurations for this pallet in testing environment.
115
		pub struct TestDefaultConfig;
116

            
117
		#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
118
		impl frame_system::DefaultConfig for TestDefaultConfig {}
119

            
120
		#[frame_support::register_default_impl(TestDefaultConfig)]
121
		impl DefaultConfig for TestDefaultConfig {
122
			#[inject_runtime_type]
123
			type RuntimeEvent = ();
124
			type WeightInfo = ();
125
			type DestinationVersion = AlwaysLatest;
126

            
127
			// We don't need (optional) message_size fees.
128
			type ByteFee = ConstU128<0>;
129
			// We don't need (optional) message_size fees.
130
			type FeeAsset = ();
131
		}
132
	}
133

            
134
	#[pallet::config(with_default)]
135
	pub trait Config<I: 'static = ()>: frame_system::Config {
136
		/// The overarching event type.
137
		#[pallet::no_default_bounds]
138
		type RuntimeEvent: From<Event<Self, I>>
139
			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
140
		/// Benchmarks results from runtime we're plugged into.
141
		type WeightInfo: WeightInfo;
142

            
143
		/// Checks the XCM version for the destination.
144
		type DestinationVersion: GetVersion;
145

            
146
		/// The bridge hub may be:
147
		/// - A system (sibling) bridge hub parachain (or another chain), in which case we need an
148
		///   implementation for `T::MessageExporter` that sends `ExportMessage`, e.g.,
149
		///   `SovereignPaidRemoteExporter`.
150
		/// - The local chain, in which case we need an implementation for `T::MessageExporter` that
151
		///   does not use `ExportMessage` but instead directly calls the `ExportXcm`
152
		///   implementation.
153
		#[pallet::no_default]
154
		type MessageExporter: SendXcm;
155

            
156
		/// Resolves a specific `BridgeId` for `dest`, used for identifying the bridge in cases of
157
		/// congestion and dynamic fees. If it resolves to `None`, it means no congestion or
158
		/// dynamic fees are handled for `dest`.
159
		#[pallet::no_default]
160
		type BridgeIdResolver: ResolveBridgeId;
161

            
162
		/// Origin that is allowed to update bridge status,
163
		/// e.g. the sibling bridge hub or governance as root.
164
		#[pallet::no_default]
165
		type UpdateBridgeStatusOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, BridgeIdOf<Self, I>>;
166

            
167
		/// Additional fee that is paid for every byte of the outbound message.
168
		/// See `calculate_message_size_fee` for more details.
169
		type ByteFee: Get<u128>;
170
		/// Asset used to pay the `ByteFee`.
171
		/// If not specified, the `ByteFee` is ignored.
172
		/// See `calculate_fees` for more details.
173
		type FeeAsset: Get<Option<AssetId>>;
174
	}
175

            
176
	/// An alias for the `BridgeId` of configured `T::BridgeIdResolver`.
177
	pub type BridgeIdOf<T, I> = <<T as Config<I>>::BridgeIdResolver as ResolveBridgeId>::BridgeId;
178

            
179
3
	#[pallet::pallet]
180
	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
181

            
182
16
	#[pallet::call]
183
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
184
		/// Notification about congested bridge queue.
185
		#[pallet::call_index(0)]
186
		#[pallet::weight(T::WeightInfo::update_bridge_status())]
187
		pub fn update_bridge_status(
188
			origin: OriginFor<T>,
189
			bridge_id: BridgeIdOf<T, I>,
190
			is_congested: bool,
191
5
		) -> DispatchResult {
192
5
			let _ = T::UpdateBridgeStatusOrigin::ensure_origin(origin, &bridge_id)?;
193

            
194
5
			log::info!(
195
				target: LOG_TARGET,
196
				"Received bridge status from {:?}: congested = {}",
197
				bridge_id,
198
				is_congested,
199
			);
200

            
201
			// update status
202
5
			Self::do_update_bridge_status(bridge_id, is_congested);
203
5

            
204
5
			Ok(())
205
		}
206
	}
207

            
208
	/// Stores `BridgeState` for congestion control and dynamic fees for each resolved bridge ID
209
	/// associated with a destination.
210
32823
	#[pallet::storage]
211
	pub type Bridges<T: Config<I>, I: 'static = ()> =
212
		StorageMap<_, Blake2_128Concat, BridgeIdOf<T, I>, BridgeState, OptionQuery>;
213

            
214
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
215
		/// Called when new message is sent to the `dest` (queued to local outbound XCM queue).
216
16392
		pub(crate) fn on_message_sent_to(message_size: u32, dest: Location) {
217
16392
			let Some(bridge_id) = T::BridgeIdResolver::resolve_for_dest(&dest) else {
218
				// not supported bridge id, so do nothing
219
				return;
220
			};
221

            
222
			// handle congestion and fee factor (if detected)
223
16392
			Bridges::<T, I>::mutate_exists(&bridge_id, |bridge_state| match bridge_state {
224
5
				Some(ref mut bridge_state) if bridge_state.is_congested => {
225
4
					// found congested bridge
226
4
					// ok - we need to increase the fee factor, let's do that
227
4
					let message_size_factor =
228
4
						FixedU128::from_u32(message_size.saturating_div(1024))
229
4
							.saturating_mul(MESSAGE_SIZE_FEE_BASE);
230
4
					let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor);
231
4

            
232
4
					let previous_factor = bridge_state.delivery_fee_factor;
233
4
					bridge_state.delivery_fee_factor = bridge_state
234
4
						.delivery_fee_factor
235
4
						.saturating_mul(total_factor);
236
4

            
237
4
					log::info!(
238
						target: LOG_TARGET,
239
						"Bridge channel with id {:?} is congested. Increased fee factor from {} to {} for {:?}",
240
						bridge_id,
241
						previous_factor,
242
						bridge_state.delivery_fee_factor,
243
						dest
244
					);
245
4
					Self::deposit_event(Event::DeliveryFeeFactorIncreased {
246
4
						previous_value: previous_factor,
247
4
						new_value: bridge_state.delivery_fee_factor,
248
4
						bridge_id: bridge_id.clone(),
249
4
					});
250
				}
251
16388
				_ => {
252
16388
					// not congested, do nothing
253
16388
				}
254
16392
			});
255
16392
		}
256

            
257
		/// Returns the recalculated dynamic fee for a given asset based on the bridge state.
258
		///
259
		/// This function adjusts the amount of a fungible asset according to the delivery fee
260
		/// factor specified in the `bridge_state`. If the asset is fungible, the
261
		/// `delivery_fee_factor` is applied to the asset’s amount, potentially altering its
262
		/// value.
263
3
		pub(crate) fn apply_dynamic_fee_factor(bridge_state: &BridgeState, asset: Asset) -> Asset {
264
3
			match asset.fun {
265
3
				Fungible(amount) => {
266
3
					let adjusted_amount =
267
3
						bridge_state.delivery_fee_factor.saturating_mul_int(amount);
268
3
					Asset {
269
3
						fun: Fungible(adjusted_amount),
270
3
						..asset
271
3
					}
272
				}
273
				_ => asset,
274
			}
275
3
		}
276

            
277
		/// Calculates an (optional) fee for message size based on `T::ByteFee` and `T::FeeAsset`.
278
16399
		pub(crate) fn calculate_message_size_fee(
279
16399
			message_size: impl FnOnce() -> u32,
280
16399
		) -> Option<Asset> {
281
			// Apply message size `T::ByteFee/T::FeeAsset` feature (if configured).
282
16399
			if let Some(asset_id) = T::FeeAsset::get() {
283
11
				let message_fee = (message_size() as u128).saturating_mul(T::ByteFee::get());
284
11
				if message_fee > 0 {
285
11
					return Some((asset_id, message_fee).into());
286
				}
287
16388
			}
288
16388
			None
289
16399
		}
290

            
291
		/// Updates the congestion status of a bridge for a given `bridge_id`.
292
		///
293
		/// If the bridge does not exist and:
294
		/// - `is_congested` is true, a new `BridgeState` is created with a default
295
		///   `delivery_fee_factor`.
296
		/// - `is_congested` is false, does nothing and no `BridgeState` is created.
297
11
		pub(crate) fn do_update_bridge_status(bridge_id: BridgeIdOf<T, I>, is_congested: bool) {
298
11
			Bridges::<T, I>::mutate_exists(&bridge_id, |maybe_bridge| match maybe_bridge.take() {
299
5
				Some(mut bridge) => {
300
5
					if is_congested {
301
1
						bridge.is_congested = is_congested;
302
1
						*maybe_bridge = Some(bridge);
303
4
					} else {
304
4
						Self::deposit_event(Event::DeliveryFeeFactorDecreased {
305
4
							previous_value: bridge.delivery_fee_factor,
306
4
							new_value: 0.into(),
307
4
							bridge_id: bridge_id.clone(),
308
4
						});
309
4
					}
310
				}
311
				None => {
312
6
					if is_congested {
313
4
						*maybe_bridge = Some(BridgeState {
314
4
							delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR,
315
4
							is_congested,
316
4
						});
317
4

            
318
4
						Self::deposit_event(Event::DeliveryFeeFactorIncreased {
319
4
							previous_value: 0.into(),
320
4
							new_value: MINIMAL_DELIVERY_FEE_FACTOR,
321
4
							bridge_id: bridge_id.clone(),
322
4
						});
323
4
					}
324
				}
325
11
			});
326
11
		}
327
	}
328

            
329
	#[pallet::event]
330
12
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
331
	pub enum Event<T: Config<I>, I: 'static = ()> {
332
		/// Delivery fee factor has been decreased.
333
		DeliveryFeeFactorDecreased {
334
			/// Previous value of the `DeliveryFeeFactor`.
335
			previous_value: FixedU128,
336
			/// New value of the `DeliveryFeeFactor`.
337
			new_value: FixedU128,
338
			/// Bridge identifier.
339
			bridge_id: BridgeIdOf<T, I>,
340
		},
341
1
		/// Delivery fee factor has been increased.
342
		DeliveryFeeFactorIncreased {
343
			/// Previous value of the `DeliveryFeeFactor`.
344
			previous_value: FixedU128,
345
			/// New value of the `DeliveryFeeFactor`.
346
			new_value: FixedU128,
347
			/// Bridge identifier.
348
			bridge_id: BridgeIdOf<T, I>,
349
		},
350
	}
351
}
352

            
353
// This pallet acts as the `SendXcm` to the sibling/child bridge hub instead of regular
354
// XCMP/DMP transport. This allows injecting dynamic message fees into XCM programs that
355
// are going to the bridged network.
356
impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
357
	type Ticket = (u32, Location, <T::MessageExporter as SendXcm>::Ticket);
358

            
359
16399
	fn validate(
360
16399
		dest: &mut Option<Location>,
361
16399
		xcm: &mut Option<Xcm<()>>,
362
16399
	) -> SendResult<Self::Ticket> {
363
16399
		log::trace!(target: LOG_TARGET, "validate - msg: {xcm:?}, destination: {dest:?}");
364

            
365
		// In case of success, the `T::MessageExporter` can modify XCM instructions and consume
366
		// `dest` / `xcm`, so we retain the clone of original message and the destination for later
367
		// `DestinationVersion` validation.
368
16399
		let xcm_to_dest_clone = xcm.clone();
369
16399
		let dest_clone = dest.clone();
370
16399

            
371
16399
		// First, use the inner exporter to validate the destination to determine if it is even
372
16399
		// routable. If it is not, return an error. If it is, then the XCM is extended with
373
16399
		// instructions to pay the message fee at the sibling/child bridge hub. The cost will
374
16399
		// include both the cost of (1) delivery to the sibling bridge hub (returned by
375
16399
		// `Config::MessageExporter`) and (2) delivery to the bridged bridge hub (returned by
376
16399
		// `Self::exporter_for`).
377
16399
		match T::MessageExporter::validate(dest, xcm) {
378
16397
			Ok((ticket, cost)) => {
379
				// If the ticket is ok, it means we are routing with this router, so we need to
380
				// apply more validations to the cloned `dest` and `xcm`, which are required here.
381
16397
				let xcm_to_dest_clone = xcm_to_dest_clone.ok_or(SendError::MissingArgument)?;
382
16397
				let dest_clone = dest_clone.ok_or(SendError::MissingArgument)?;
383

            
384
				// We won't have access to `dest` and `xcm` in the `deliver` method, so we need to
385
				// precompute everything required here. However, `dest` and `xcm` were consumed by
386
				// `T::MessageExporter`, so we need to use their clones.
387
16397
				let message_size = xcm_to_dest_clone.encoded_size() as _;
388
16397

            
389
16397
				// The bridge doesn't support oversized or overweight messages. Therefore, it's
390
16397
				// better to drop such messages here rather than at the bridge hub. Let's check the
391
16397
				// message size.
392
16397
				if message_size > HARD_MESSAGE_SIZE_LIMIT {
393
2
					return Err(SendError::ExceedsMaxMessageSize);
394
16395
				}
395

            
396
				// We need to ensure that the known `dest`'s XCM version can comprehend the current
397
				// `xcm` program. This may seem like an additional, unnecessary check, but it is
398
				// not. A similar check is probably performed by the `T::MessageExporter`, which
399
				// attempts to send a versioned message to the sibling bridge hub. However, the
400
				// local bridge hub may have a higher XCM version than the remote `dest`. Once
401
				// again, it is better to discard such messages here than at the bridge hub (e.g.,
402
				// to avoid losing funds).
403
16395
				let destination_version = T::DestinationVersion::get_version_for(&dest_clone)
404
16395
					.ok_or(SendError::DestinationUnsupported)?;
405
16393
				let _ = VersionedXcm::from(xcm_to_dest_clone)
406
16393
					.into_version(destination_version)
407
16393
					.map_err(|()| SendError::DestinationUnsupported)?;
408

            
409
16393
				log::info!(
410
					target: LOG_TARGET,
411
					"Going to send message to {dest_clone:?} ({message_size:?} bytes) with actual cost: {cost:?}"
412
				);
413

            
414
16393
				Ok(((message_size, dest_clone, ticket), cost))
415
			}
416
2
			Err(e) => {
417
2
				log::trace!(target: LOG_TARGET, "`T::MessageExporter` validates for dest: {dest_clone:?} with error: {e:?}");
418
2
				Err(e)
419
			}
420
		}
421
16399
	}
422

            
423
16391
	fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
424
16391
		// use router to enqueue message to the sibling/child bridge hub. This also should handle
425
16391
		// payment for passing through this queue.
426
16391
		let (message_size, dest, ticket) = ticket;
427
16391
		let xcm_hash = T::MessageExporter::deliver(ticket)?;
428

            
429
16391
		log::trace!(
430
			target: LOG_TARGET,
431
			"deliver - message (size: {message_size:?}) sent to the dest: {dest:?}, xcm_hash: {xcm_hash:?}"
432
		);
433

            
434
		// increase delivery fee factor (if required)
435
16391
		Self::on_message_sent_to(message_size, dest);
436
16391

            
437
16391
		Ok(xcm_hash)
438
16391
	}
439
}
440

            
441
impl<T: Config<I>, I: 'static> InspectMessageQueues for Pallet<T, I> {
442
	fn clear_messages() {}
443

            
444
	/// This router needs to implement `InspectMessageQueues` but doesn't have to
445
	/// return any messages, since it just reuses the `XcmpQueue` router.
446
1
	fn get_messages() -> Vec<(VersionedLocation, Vec<VersionedXcm<()>>)> {
447
1
		Vec::new()
448
1
	}
449
}