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
//! Various implementations supporting easier configuration of the pallet.
18

            
19
use crate::{BridgeIdOf, Bridges, Config, Pallet, LOG_TARGET};
20
use bp_xcm_bridge_router::ResolveBridgeId;
21
use frame_support::{ensure, pallet_prelude::PhantomData, traits::Get};
22
use parity_scale_codec::Encode;
23
use xcm::prelude::*;
24
use xcm_builder::{ensure_is_remote, ExporterFor};
25

            
26
/// Implementation of [`bp_xcm_bridge::LocalXcmChannelManager`] which tracks and updates
27
/// `is_congested` for a given `BridgeId`. This implementation is useful for managing congestion and
28
/// dynamic fees with the local `ExportXcm` implementation.
29
impl<T: Config<I>, I: 'static> bp_xcm_bridge::LocalXcmChannelManager<BridgeIdOf<T, I>>
30
	for Pallet<T, I>
31
{
32
	type Error = ();
33

            
34
	/// Suspends the given bridge.
35
	///
36
	/// This function ensures that the `local_origin` matches the expected `Location::here()`. If
37
	/// the check passes, it updates the bridge status to congested.
38
1
	fn suspend_bridge(
39
1
		local_origin: &Location,
40
1
		bridge: BridgeIdOf<T, I>,
41
1
	) -> Result<(), Self::Error> {
42
1
		log::trace!(
43
			target: LOG_TARGET,
44
			"LocalXcmChannelManager::suspend_bridge(local_origin: {local_origin:?}, bridge: {bridge:?})",
45
		);
46
1
		ensure!(local_origin.eq(&Location::here()), ());
47

            
48
		// update status
49
1
		Self::do_update_bridge_status(bridge, true);
50
1

            
51
1
		Ok(())
52
1
	}
53

            
54
	/// Resumes the given bridge.
55
	///
56
	/// This function ensures that the `local_origin` matches the expected `Location::here()`. If
57
	/// the check passes, it updates the bridge status to not congested.
58
1
	fn resume_bridge(local_origin: &Location, bridge: BridgeIdOf<T, I>) -> Result<(), Self::Error> {
59
1
		log::trace!(
60
			target: LOG_TARGET,
61
			"LocalXcmChannelManager::resume_bridge(local_origin: {local_origin:?}, bridge: {bridge:?})",
62
		);
63
1
		ensure!(local_origin.eq(&Location::here()), ());
64

            
65
		// update status
66
1
		Self::do_update_bridge_status(bridge, false);
67
1

            
68
1
		Ok(())
69
1
	}
70
}
71

            
72
/// Adapter implementation for [`ExporterFor`] that allows exporting message size fee and/or dynamic
73
/// fees based on the `BridgeId` resolved by the `T::BridgeIdResolver` resolver, if and only if the
74
/// `E` exporter supports bridging. This adapter acts as an [`ExporterFor`], for example, for the
75
/// [`xcm_builder::SovereignPaidRemoteExporter`], enabling it to compute message and/or dynamic fees
76
/// using a fee factor.
77
pub struct ViaRemoteBridgeExporter<T, I, E, BNF, BHLF>(PhantomData<(T, I, E, BNF, BHLF)>);
78
impl<T: Config<I>, I: 'static, E, BridgedNetworkIdFilter, BridgeHubLocationFilter> ExporterFor
79
	for ViaRemoteBridgeExporter<T, I, E, BridgedNetworkIdFilter, BridgeHubLocationFilter>
80
where
81
	E: ExporterFor,
82
	BridgedNetworkIdFilter: Get<Option<NetworkId>>,
83
	BridgeHubLocationFilter: Get<Option<Location>>,
84
{
85
8207
	fn exporter_for(
86
8207
		network: &NetworkId,
87
8207
		remote_location: &InteriorLocation,
88
8207
		message: &Xcm<()>,
89
8207
	) -> Option<(Location, Option<Asset>)> {
90
8207
		log::trace!(
91
			target: LOG_TARGET,
92
			"exporter_for - network: {network:?}, remote_location: {remote_location:?}, msg: {message:?}",
93
		);
94
		// ensure that the message is sent to the expected bridged network (if specified).
95
8207
		if let Some(bridged_network) = BridgedNetworkIdFilter::get() {
96
8207
			if *network != bridged_network {
97
2
				log::trace!(
98
					target: LOG_TARGET,
99
					"Router with bridged_network_id filter({bridged_network:?}) does not support bridging to network {network:?}!",
100
				);
101
2
				return None;
102
8205
			}
103
		}
104

            
105
		// ensure that the message is sent to the expected bridged network and location.
106
8205
		let (bridge_hub_location, maybe_payment) = match E::exporter_for(
107
8205
			network,
108
8205
			remote_location,
109
8205
			message,
110
8205
		) {
111
8205
			Some((bridge_hub_location, maybe_payment)) => match BridgeHubLocationFilter::get() {
112
8205
				Some(expected_bridge_hub_location)
113
8205
					if expected_bridge_hub_location.eq(&bridge_hub_location) =>
114
8205
				{
115
8205
					(bridge_hub_location, maybe_payment)
116
				}
117
				None => (bridge_hub_location, maybe_payment),
118
				_ => {
119
					log::trace!(
120
						target: LOG_TARGET,
121
						"Resolved bridge_hub_location: {:?} does not match expected one: {:?} for bridging to network {:?} and remote_location {:?}!",
122
						bridge_hub_location,
123
						BridgeHubLocationFilter::get(),
124
						network,
125
						remote_location,
126
					);
127
					return None;
128
				}
129
			},
130
			_ => {
131
				log::trace!(
132
					target: LOG_TARGET,
133
					"Inner `E` router does not support bridging to network {:?} and remote_location {:?}!",
134
					network,
135
					remote_location,
136
				);
137
				return None;
138
			}
139
		};
140

            
141
		// calculate message size fees (if configured)
142
8205
		let maybe_message_size_fees =
143
8205
			Pallet::<T, I>::calculate_message_size_fee(|| message.encoded_size() as _);
144

            
145
		// compute actual fees - sum(actual payment, message size fees) if possible
146
8205
		let mut fees = match (maybe_payment, maybe_message_size_fees) {
147
			(Some(payment), None) => Some(payment),
148
			(None, Some(message_size_fees)) => Some(message_size_fees),
149
8194
			(None, None) => None,
150
			(
151
				Some(Asset {
152
11
					id: payment_asset_id,
153
11
					fun: Fungible(payment_amount),
154
11
				}),
155
11
				Some(Asset {
156
11
					id: message_size_fees_asset_id,
157
11
					fun: Fungible(message_size_fees_amount),
158
				}),
159
11
			) if payment_asset_id.eq(&message_size_fees_asset_id) => {
160
11
				// we can subsume two assets with the same asset_id and fungibility.
161
11
				Some(
162
11
					(
163
11
						payment_asset_id,
164
11
						payment_amount.saturating_add(message_size_fees_amount),
165
11
					)
166
11
						.into(),
167
11
				)
168
			}
169
			(Some(payment), Some(message_size_fees)) => {
170
				log::error!(
171
					target: LOG_TARGET,
172
					"Router is configured for `T::FeeAsset` {:?} \
173
					but we have two different assets which cannot be calculated as one result asset: payment: {:?} and message_size_fees: {:?} for bridge_hub_location: {:?} for bridging to {:?}/{:?}!",
174
					T::FeeAsset::get(),
175
					payment,
176
					message_size_fees,
177
					bridge_hub_location,
178
					network,
179
					remote_location,
180
				);
181
				return None;
182
			}
183
		};
184

            
185
		// `fees` is populated with base bridge fees, now let's apply congestion/dynamic fees if
186
		// required.
187
8205
		if let Some(bridge_id) = T::BridgeIdResolver::resolve_for(network, remote_location) {
188
8205
			if let Some(bridge_state) = Bridges::<T, I>::get(bridge_id) {
189
3
				if let Some(f) = fees {
190
3
					fees = Some(Pallet::<T, I>::apply_dynamic_fee_factor(&bridge_state, f));
191
3
				}
192
8202
			}
193
		}
194

            
195
8205
		Some((bridge_hub_location, fees))
196
8207
	}
197
}
198

            
199
/// Adapter implementation for [`SendXcm`] that allows adding a message size fee and/or dynamic fees
200
/// based on the `BridgeId` resolved by the `T::BridgeIdResolver` resolver, if and only if `E`
201
/// supports routing. This adapter can be used, for example, as a wrapper over
202
/// [`xcm_builder::LocalExporter`], enabling it to compute message and/or dynamic fees using a
203
/// fee factor.
204
pub struct ViaLocalBridgeExporter<T, I, E>(PhantomData<(T, I, E)>);
205
impl<T: Config<I>, I: 'static, E: SendXcm> SendXcm for ViaLocalBridgeExporter<T, I, E> {
206
	type Ticket = E::Ticket;
207

            
208
8194
	fn validate(
209
8194
		destination: &mut Option<Location>,
210
8194
		message: &mut Option<Xcm<()>>,
211
8194
	) -> SendResult<Self::Ticket> {
212
8194
		let dest_clone = destination.clone().ok_or(SendError::MissingArgument)?;
213
8194
		let message_size = message.as_ref().map_or(0, |message| message.encoded_size()) as _;
214
8194

            
215
8194
		match E::validate(destination, message) {
216
8194
			Ok((ticket, mut fees)) => {
217
8194
				// calculate message size fees (if configured)
218
8194
				let maybe_message_size_fees =
219
8194
					Pallet::<T, I>::calculate_message_size_fee(|| message_size);
220
8194
				if let Some(message_size_fees) = maybe_message_size_fees {
221
					fees.push(message_size_fees);
222
8194
				}
223

            
224
				// Here, we have the actual result fees covering bridge fees, so now we need to
225
				// check/apply the congestion and dynamic_fees features (if possible).
226
8194
				if let Some(bridge_id) = T::BridgeIdResolver::resolve_for_dest(&dest_clone) {
227
8194
					if let Some(bridge_state) = Bridges::<T, I>::get(bridge_id) {
228
						let mut dynamic_fees = sp_std::vec::Vec::with_capacity(fees.len());
229
						for fee in fees.into_inner() {
230
							dynamic_fees
231
								.push(Pallet::<T, I>::apply_dynamic_fee_factor(&bridge_state, fee));
232
						}
233
						fees = Assets::from(dynamic_fees);
234
8194
					}
235
				}
236

            
237
				// return original ticket with possibly extended fees
238
8194
				Ok((ticket, fees))
239
			}
240
			error => error,
241
		}
242
8194
	}
243

            
244
8194
	fn deliver(ticket: Self::Ticket) -> Result<XcmHash, SendError> {
245
8194
		E::deliver(ticket)
246
8194
	}
247
}
248

            
249
/// Implementation of [`ResolveBridgeId`] returning [`bp_xcm_bridge::BridgeId`] based on the
250
/// configured `UniversalLocation` and remote universal location.
251
pub struct EnsureIsRemoteBridgeIdResolver<UniversalLocation>(PhantomData<UniversalLocation>);
252
impl<UniversalLocation: Get<InteriorLocation>> ResolveBridgeId
253
	for EnsureIsRemoteBridgeIdResolver<UniversalLocation>
254
{
255
	type BridgeId = bp_xcm_bridge::BridgeId;
256

            
257
24617
	fn resolve_for_dest(dest: &Location) -> Option<Self::BridgeId> {
258
24614
		let Ok((remote_network, remote_dest)) =
259
24617
			ensure_is_remote(UniversalLocation::get(), dest.clone())
260
		else {
261
3
			log::trace!(
262
				target: LOG_TARGET,
263
				"EnsureIsRemoteBridgeIdResolver - does not recognize a remote destination for: {dest:?}!"
264
			);
265
3
			return None;
266
		};
267
24614
		Self::resolve_for(&remote_network, &remote_dest)
268
24617
	}
269

            
270
32823
	fn resolve_for(
271
32823
		bridged_network: &NetworkId,
272
32823
		bridged_dest: &InteriorLocation,
273
32823
	) -> Option<Self::BridgeId> {
274
32823
		let bridged_universal_location = if let Ok(network) = bridged_dest.global_consensus() {
275
1
			if network.ne(bridged_network) {
276
				log::error!(
277
					target: LOG_TARGET,
278
					"EnsureIsRemoteBridgeIdResolver - bridged_dest: {bridged_dest:?} contains invalid network: {network:?}, expected bridged_network: {bridged_network:?}!"
279
				);
280
				return None;
281
			} else {
282
1
				bridged_dest.clone()
283
			}
284
		} else {
285
			// if `bridged_dest` does not contain `GlobalConsensus`, let's prepend one
286
32822
			match bridged_dest.clone().pushed_front_with(*bridged_network) {
287
32822
				Ok(bridged_universal_location) => bridged_universal_location,
288
				Err((original, prepend_with)) => {
289
					log::error!(
290
						target: LOG_TARGET,
291
						"EnsureIsRemoteBridgeIdResolver - bridged_dest: {original:?} cannot be prepended with: {prepend_with:?}!"
292
					);
293
					return None;
294
				}
295
			}
296
		};
297

            
298
		match (
299
32823
			UniversalLocation::get().global_consensus(),
300
32823
			bridged_universal_location.global_consensus(),
301
		) {
302
32823
			(Ok(local), Ok(remote)) if local != remote => (),
303
1
			(local, remote) => {
304
1
				log::error!(
305
					target: LOG_TARGET,
306
					"EnsureIsRemoteBridgeIdResolver - local: {local:?} and remote: {remote:?} must be different!"
307
				);
308
1
				return None;
309
			}
310
		}
311

            
312
		// calculate `BridgeId` from universal locations
313
32822
		Some(Self::BridgeId::new(
314
32822
			&UniversalLocation::get(),
315
32822
			&bridged_universal_location,
316
32822
		))
317
32823
	}
318
}
319

            
320
#[cfg(test)]
321
mod tests {
322
	use super::*;
323

            
324
	#[test]
325
1
	fn ensure_is_remote_bridge_id_resolver_works() {
326
1
		frame_support::parameter_types! {
327
1
			pub ThisNetwork: NetworkId = NetworkId::ByGenesis([0; 32]);
328
1
			pub BridgedNetwork: NetworkId = NetworkId::ByGenesis([1; 32]);
329
1
			pub UniversalLocation: InteriorLocation = [GlobalConsensus(ThisNetwork::get()), Parachain(1000)].into();
330
1
		}
331
1
		assert_ne!(ThisNetwork::get(), BridgedNetwork::get());
332

            
333
		type Resolver = EnsureIsRemoteBridgeIdResolver<UniversalLocation>;
334

            
335
		// not remote dest
336
1
		assert!(Resolver::resolve_for_dest(&Location::new(1, Here)).is_none());
337
		// not a valid remote dest
338
1
		assert!(Resolver::resolve_for_dest(&Location::new(2, Here)).is_none());
339
		// the same network for remote dest
340
1
		assert!(
341
1
			Resolver::resolve_for_dest(&Location::new(2, GlobalConsensus(ThisNetwork::get())))
342
1
				.is_none()
343
1
		);
344
1
		assert!(Resolver::resolve_for(&ThisNetwork::get(), &Here.into()).is_none());
345

            
346
		// ok
347
1
		assert!(Resolver::resolve_for_dest(&Location::new(
348
1
			2,
349
1
			GlobalConsensus(BridgedNetwork::get())
350
1
		))
351
1
		.is_some());
352
1
		assert!(Resolver::resolve_for_dest(&Location::new(
353
1
			2,
354
1
			[GlobalConsensus(BridgedNetwork::get()), Parachain(2013)]
355
1
		))
356
1
		.is_some());
357

            
358
		// ok - resolves the same
359
1
		assert_eq!(
360
1
			Resolver::resolve_for_dest(&Location::new(2, GlobalConsensus(BridgedNetwork::get()))),
361
1
			Resolver::resolve_for(&BridgedNetwork::get(), &Here.into()),
362
1
		);
363
1
		assert_eq!(
364
1
			Resolver::resolve_for_dest(&Location::new(
365
1
				2,
366
1
				[GlobalConsensus(BridgedNetwork::get()), Parachain(2013)]
367
1
			)),
368
1
			Resolver::resolve_for(&BridgedNetwork::get(), &Parachain(2013).into()),
369
1
		);
370
1
		assert_eq!(
371
1
			Resolver::resolve_for_dest(&Location::new(
372
1
				2,
373
1
				[GlobalConsensus(BridgedNetwork::get()), Parachain(2013)]
374
1
			)),
375
1
			Resolver::resolve_for(
376
1
				&BridgedNetwork::get(),
377
1
				&[GlobalConsensus(BridgedNetwork::get()), Parachain(2013)].into()
378
1
			),
379
1
		);
380
1
	}
381
}