Skip to content

Commit 501a5c0

Browse files
committed
Extract dynamic fees calculation as separate feature
1 parent 59c8b4b commit 501a5c0

3 files changed

Lines changed: 54 additions & 104 deletions

File tree

bridges/modules/xcm-bridge-hub-router/src/lib.rs

Lines changed: 52 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,12 @@ pub mod pallet {
109109
type LocalXcmChannelManager: XcmChannelStatusProvider;
110110

111111
/// Additional fee that is paid for every byte of the outbound message.
112+
/// See `calculate_fees` for more details.
112113
type ByteFee: Get<u128>;
113-
/// Asset that is used to paid bridge fee.
114-
type FeeAsset: Get<AssetId>;
114+
/// Asset used to pay the `ByteFee`.
115+
/// If not specified, the `ByteFee` is ignored.
116+
/// See `calculate_fees` for more details.
117+
type FeeAsset: Get<Option<AssetId>>;
115118
}
116119

117120
#[pallet::pallet]
@@ -216,6 +219,36 @@ pub mod pallet {
216219
*f
217220
});
218221
}
222+
223+
/// Calculates final fees based on the configuration, supporting two optional features:
224+
///
225+
/// 1. Adds an (optional) fee for message size based on `T::ByteFee` and `T::FeeAsset`.
226+
/// 2. Applies a dynamic fee factor `Self::delivery_fee_factor` to the `actual_cost` (useful for congestion handling).
227+
///
228+
/// Parameters:
229+
/// - `message_size`
230+
/// - `actual_cost`: the fee for sending a message from this chain to a child, sibling, or local bridge hub, determined by `Config::ToBridgeHubSender`.
231+
pub(crate) fn calculate_fees(message_size: u32, mut actual_cost: Assets) -> Assets {
232+
// Apply message size `T::ByteFee/T::FeeAsset` feature (if configured).
233+
if let Some(asset_id) = T::FeeAsset::get() {
234+
let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get());
235+
if message_fee > 0 {
236+
// Keep in mind that this is only the additional fee for message size.
237+
actual_cost.push((asset_id, message_fee).into());
238+
}
239+
}
240+
241+
// Apply dynamic fees feature - apply `fee_factor` to the `actual_cost`.
242+
let fee_factor = Self::delivery_fee_factor();
243+
let mut new_cost = Assets::new();
244+
for mut a in actual_cost.into_inner() {
245+
if let Fungibility::Fungible(ref mut amount) = a.fun {
246+
*amount = fee_factor.saturating_mul_int(*amount);
247+
}
248+
new_cost.push(a);
249+
}
250+
new_cost
251+
}
219252
}
220253

221254
#[pallet::event]
@@ -234,97 +267,6 @@ pub mod pallet {
234267
}
235268
}
236269

237-
// This pallet acts as the `ExporterFor` for the `SovereignPaidRemoteExporter` to compute
238-
// message fee using fee factor.
239-
impl<T: Config<I>, I: 'static> ExporterFor for Pallet<T, I> {
240-
fn exporter_for(
241-
network: &NetworkId,
242-
remote_location: &InteriorLocation,
243-
message: &Xcm<()>,
244-
) -> Option<(Location, Option<Asset>)> {
245-
log::trace!(
246-
target: LOG_TARGET,
247-
"exporter_for - network: {network:?}, remote_location: {remote_location:?}, msg: {message:?}",
248-
);
249-
// ensure that the message is sent to the expected bridged network (if specified).
250-
if let Some(bridged_network) = T::BridgedNetworkId::get() {
251-
if *network != bridged_network {
252-
log::trace!(
253-
target: LOG_TARGET,
254-
"Router with bridged_network_id {bridged_network:?} does not support bridging to network {network:?}!",
255-
);
256-
return None
257-
}
258-
}
259-
260-
// ensure that the message is sent to the expected bridged network and location.
261-
let (bridge_hub_location, maybe_payment) = match T::Bridges::exporter_for(
262-
network,
263-
remote_location,
264-
message,
265-
) {
266-
Some((bridge_hub_location, maybe_payment))
267-
if bridge_hub_location.eq(&T::SiblingBridgeHubLocation::get()) =>
268-
(bridge_hub_location, maybe_payment),
269-
_ => {
270-
log::trace!(
271-
target: LOG_TARGET,
272-
"Router configured with bridged_network_id {:?} and sibling_bridge_hub_location: {:?} does not support bridging to network {:?} and remote_location {:?}!",
273-
T::BridgedNetworkId::get(),
274-
T::SiblingBridgeHubLocation::get(),
275-
network,
276-
remote_location,
277-
);
278-
return None
279-
},
280-
};
281-
282-
// take `base_fee` from `T::Brides`, but it has to be the same `T::FeeAsset`
283-
let base_fee = match maybe_payment {
284-
Some(payment) => match payment {
285-
Asset { fun: Fungible(amount), id } if id.eq(&T::FeeAsset::get()) => amount,
286-
invalid_asset => {
287-
log::error!(
288-
target: LOG_TARGET,
289-
"Router with bridged_network_id {:?} is configured for `T::FeeAsset` {:?} \
290-
which is not compatible with {:?} for bridge_hub_location: {:?} for bridging to {:?}/{:?}!",
291-
T::BridgedNetworkId::get(),
292-
T::FeeAsset::get(),
293-
invalid_asset,
294-
bridge_hub_location,
295-
network,
296-
remote_location,
297-
);
298-
return None
299-
},
300-
},
301-
None => 0,
302-
};
303-
304-
// compute fee amount. Keep in mind that this is only the bridge fee. The fee for sending
305-
// message from this chain to child/sibling bridge hub is determined by the
306-
// `Config::ToBridgeHubSender`
307-
let message_size = message.encoded_size();
308-
let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get());
309-
let fee_sum = base_fee.saturating_add(message_fee);
310-
311-
let fee_factor = Self::delivery_fee_factor();
312-
let fee = fee_factor.saturating_mul_int(fee_sum);
313-
let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None };
314-
315-
log::info!(
316-
target: LOG_TARGET,
317-
"Going to send message to {:?} ({} bytes) over bridge. Computed bridge fee {:?} using fee factor {}",
318-
(network, remote_location),
319-
message_size,
320-
fee,
321-
fee_factor,
322-
);
323-
324-
Some((bridge_hub_location, fee))
325-
}
326-
}
327-
328270
// This pallet acts as the `SendXcm` to the sibling/child bridge hub instead of regular
329271
// XCMP/DMP transport. This allows injecting dynamic message fees into XCM programs that
330272
// are going to the bridged network.
@@ -363,7 +305,7 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
363305

364306
// The bridge doesn't support oversized or overweight messages. Therefore, it's
365307
// better to drop such messages here rather than at the bridge hub. Let's check the
366-
// message size."
308+
// message size.
367309
if message_size > HARD_MESSAGE_SIZE_LIMIT {
368310
return Err(SendError::ExceedsMaxMessageSize)
369311
}
@@ -381,6 +323,14 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
381323
.into_version(destination_version)
382324
.map_err(|()| SendError::DestinationUnsupported)?;
383325

326+
// now let's apply fees features
327+
let cost = Self::calculate_fees(message_size, cost);
328+
329+
log::info!(
330+
target: LOG_TARGET,
331+
"Going to send message to {dest_clone:?} ({message_size:?} bytes) with actual cost: {cost:?}"
332+
);
333+
384334
Ok(((message_size, ticket), cost))
385335
},
386336
Err(e) => {
@@ -566,8 +516,11 @@ mod tests {
566516
let xcm: Xcm<()> = vec![ClearOrigin].into();
567517
let msg_size = xcm.encoded_size();
568518

569-
// initially the base fee is used: `BASE_FEE + BYTE_FEE * msg_size + HRMP_FEE`
570-
let expected_fee = BASE_FEE + BYTE_FEE * (msg_size as u128) + HRMP_FEE;
519+
// `BASE_FEE + BYTE_FEE * msg_size + HRMP_FEE`
520+
let base_cost_formula = || BASE_FEE + BYTE_FEE * (msg_size as u128) + HRMP_FEE;
521+
522+
// initially the base fee is used
523+
let expected_fee = base_cost_formula();
571524
assert_eq!(
572525
XcmBridgeHubRouter::validate(&mut Some(dest.clone()), &mut Some(xcm.clone()))
573526
.unwrap()
@@ -577,14 +530,12 @@ mod tests {
577530
);
578531

579532
// but when factor is larger than one, it increases the fee, so it becomes:
580-
// `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE`
533+
// `base_cost_formula() * F`
581534
let factor = FixedU128::from_rational(125, 100);
582535
DeliveryFeeFactor::<TestRuntime, ()>::put(factor);
583536
let expected_fee =
584-
(FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) *
585-
factor)
586-
.into_inner() / FixedU128::DIV +
587-
HRMP_FEE;
537+
(FixedU128::saturating_from_integer(base_cost_formula()) * factor)
538+
.into_inner() / FixedU128::DIV;
588539
assert_eq!(
589540
XcmBridgeHubRouter::validate(&mut Some(dest), &mut Some(xcm)).unwrap().1.get(0),
590541
Some(&(BridgeFeeAsset::get(), expected_fee).into()),

bridges/modules/xcm-bridge-hub-router/src/mock.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
8383
LatestOrNoneForLocationVersionChecker<Equals<UnknownXcmVersionForRoutableLocation>>;
8484

8585
type ToBridgeHubSender = SovereignPaidRemoteExporter<
86-
// use pallet itself as `ExportFor` provider.
87-
XcmBridgeHubRouter,
86+
NetworkExportTable<BridgeTable>,
8887
TestToBridgeHubSender,
8988
Self::UniversalLocation,
9089
>;

bridges/modules/xcm-bridge-hub/src/mock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime {
231231
// produced by `pallet_xcm_bridge_hub_router` is compatible with the `ExportXcm` implementation
232232
// of `pallet_xcm_bridge_hub`.
233233
type ToBridgeHubSender = SovereignPaidRemoteExporter<
234-
XcmOverBridgeWrappedWithExportMessageRouter,
234+
NetworkExportTable<BridgeTable>,
235235
// **Note**: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which
236236
// calls the `ExportXcm` implementation of `pallet_xcm_bridge_hub` as the
237237
// `MessageExporter`.

0 commit comments

Comments
 (0)