snowbridge_runtime_common/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
7
8#[cfg(test)]
9mod tests;
10
11use codec::FullCodec;
12use core::marker::PhantomData;
13use frame_support::traits::Get;
14use snowbridge_core::outbound::SendMessageFeeProvider;
15use sp_arithmetic::traits::{BaseArithmetic, Unsigned};
16use sp_std::fmt::Debug;
17use xcm::prelude::*;
18use xcm_builder::HandleFee;
19use xcm_executor::traits::{FeeReason, TransactAsset};
20
21pub const LOG_TARGET: &str = "xcm::export-fee-to-sibling";
22
23pub struct XcmExportFeeToSibling<
28 Balance,
29 AccountId,
30 FeeAssetLocation,
31 EthereumNetwork,
32 AssetTransactor,
33 FeeProvider,
34>(
35 PhantomData<(
36 Balance,
37 AccountId,
38 FeeAssetLocation,
39 EthereumNetwork,
40 AssetTransactor,
41 FeeProvider,
42 )>,
43);
44
45impl<Balance, AccountId, FeeAssetLocation, EthereumNetwork, AssetTransactor, FeeProvider> HandleFee
46 for XcmExportFeeToSibling<
47 Balance,
48 AccountId,
49 FeeAssetLocation,
50 EthereumNetwork,
51 AssetTransactor,
52 FeeProvider,
53 >
54where
55 Balance: BaseArithmetic + Unsigned + Copy + From<u128> + Into<u128> + Debug,
56 AccountId: Clone + FullCodec,
57 FeeAssetLocation: Get<Location>,
58 EthereumNetwork: Get<NetworkId>,
59 AssetTransactor: TransactAsset,
60 FeeProvider: SendMessageFeeProvider<Balance = Balance>,
61{
62 fn handle_fee(fees: Assets, context: Option<&XcmContext>, reason: FeeReason) -> Assets {
63 let token_location = FeeAssetLocation::get();
64
65 if !matches!(
67 reason,
68 FeeReason::Export { network: bridged_network, ref destination }
69 if bridged_network == EthereumNetwork::get() && destination == &Here
70 ) {
71 return fees
72 }
73
74 let maybe_para_id: Option<u32> =
76 if let Some(XcmContext { origin: Some(Location { parents: 1, interior }), .. }) =
77 context
78 {
79 if let Some(Parachain(sibling_para_id)) = interior.first() {
80 Some(*sibling_para_id)
81 } else {
82 None
83 }
84 } else {
85 None
86 };
87 if maybe_para_id.is_none() {
88 log::error!(
89 target: LOG_TARGET,
90 "invalid location in context {:?}",
91 context,
92 );
93 return fees
94 }
95 let para_id = maybe_para_id.unwrap();
96
97 let maybe_total_supplied_fee: Option<(usize, Balance)> = fees
99 .inner()
100 .iter()
101 .enumerate()
102 .filter_map(|(index, asset)| {
103 if let Asset { id: location, fun: Fungible(amount) } = asset {
104 if location.0 == token_location {
105 return Some((index, (*amount).into()))
106 }
107 }
108 None
109 })
110 .next();
111 if maybe_total_supplied_fee.is_none() {
112 log::error!(
113 target: LOG_TARGET,
114 "could not find fee asset item in fees: {:?}",
115 fees,
116 );
117 return fees
118 }
119 let (fee_index, total_fee) = maybe_total_supplied_fee.unwrap();
120 let local_fee = FeeProvider::local_fee();
121 let remote_fee = total_fee.saturating_sub(local_fee);
122 if local_fee == Balance::zero() || remote_fee == Balance::zero() {
123 log::error!(
124 target: LOG_TARGET,
125 "calculated refund incorrect with local_fee: {:?} and remote_fee: {:?}",
126 local_fee,
127 remote_fee,
128 );
129 return fees
130 }
131 let result = AssetTransactor::deposit_asset(
133 &Asset { id: AssetId(token_location.clone()), fun: Fungible(remote_fee.into()) },
134 &Location::new(1, [Parachain(para_id)]),
135 context,
136 );
137 if result.is_err() {
138 log::error!(
139 target: LOG_TARGET,
140 "transact fee asset failed: {:?}",
141 result.unwrap_err()
142 );
143 return fees
144 }
145
146 let mut modified_fees = fees.inner().clone();
148 modified_fees.remove(fee_index);
149 modified_fees.push(Asset { id: AssetId(token_location), fun: Fungible(local_fee.into()) });
150 modified_fees.into()
151 }
152}