1use frame_support::{
37 traits::{ContainsPair, Get},
38 weights::constants::WEIGHT_REF_TIME_PER_SECOND,
39};
40use sp_runtime::traits::{Bounded, MaybeEquivalence, Zero};
41use sp_std::marker::PhantomData;
42
43use xcm::latest::{prelude::*, Weight};
45use xcm_builder::TakeRevenue;
46use xcm_executor::{
47 traits::{TransactAsset, TransferType, WeightTrader, XcmAssetTransfers},
48 AssetsInHolding,
49};
50
51use pallet_xc_asset_config::{ExecutionPaymentRate, XcAssetLocation};
52
53#[cfg(test)]
54mod tests;
55
56pub const XCM_SIZE_LIMIT: u32 = 2u32.pow(16);
57pub const MAX_ASSETS: u32 = 64;
58pub const ASSET_HUB_PARA_ID: u32 = 1000;
59
60pub struct AssetLocationIdConverter<AssetId, AssetMapper>(PhantomData<(AssetId, AssetMapper)>);
65impl<AssetId, AssetMapper> MaybeEquivalence<Location, AssetId>
66 for AssetLocationIdConverter<AssetId, AssetMapper>
67where
68 AssetId: Clone + Eq + Bounded,
69 AssetMapper: XcAssetLocation<AssetId>,
70{
71 fn convert(location: &Location) -> Option<AssetId> {
72 AssetMapper::get_asset_id(location.clone())
73 }
74
75 fn convert_back(id: &AssetId) -> Option<Location> {
76 AssetMapper::get_xc_asset_location(id.clone())
77 }
78}
79
80pub struct FixedRateOfForeignAsset<T: ExecutionPaymentRate, R: TakeRevenue> {
85 weight: Weight,
87 taken: AssetsInHolding,
89 asset_location_and_units_per_second: Option<(Location, u128)>,
91 _pd: PhantomData<(T, R)>,
92}
93
94impl<T: ExecutionPaymentRate, R: TakeRevenue> WeightTrader for FixedRateOfForeignAsset<T, R> {
95 fn new() -> Self {
96 Self {
97 weight: Weight::zero(),
98 taken: AssetsInHolding::new(),
99 asset_location_and_units_per_second: None,
100 _pd: PhantomData,
101 }
102 }
103
104 fn buy_weight(
105 &mut self,
106 weight: Weight,
107 mut payment: AssetsInHolding,
108 _: &XcmContext,
109 ) -> Result<AssetsInHolding, (AssetsInHolding, XcmError)> {
110 log::trace!(
111 target: "xcm::weight",
112 "FixedRateOfForeignAsset::buy_weight weight: {:?}, payment: {:?}",
113 weight, payment,
114 );
115
116 let Some(Asset {
118 id: AssetId(asset_location),
119 fun: Fungibility::Fungible(_),
120 }) = payment.fungible_assets_iter().next()
121 else {
122 return Err((payment, XcmError::TooExpensive));
123 };
124
125 let Some(units_per_second) = T::get_units_per_second(asset_location.clone()) else {
126 return Err((payment, XcmError::TooExpensive));
127 };
128
129 let amount = execution_fee(weight, units_per_second);
130 if amount == 0 {
131 return Ok(payment);
132 }
133
134 if let Some((tracked_asset_location, _)) = &self.asset_location_and_units_per_second {
136 if *tracked_asset_location != asset_location {
137 return Err((payment, XcmError::NotWithdrawable));
138 }
139 }
140
141 let to_charge: Asset = (asset_location.clone(), amount).into();
142 let Ok(taken) = payment.try_take(to_charge.into()) else {
143 return Err((payment, XcmError::TooExpensive));
144 };
145
146 self.weight = self.weight.saturating_add(weight);
147 self.taken.subsume_assets(taken);
148 self.asset_location_and_units_per_second = Some((asset_location, units_per_second));
149
150 Ok(payment)
151 }
152
153 fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<AssetsInHolding> {
154 log::trace!(target: "xcm::weight", "FixedRateOfForeignAsset::refund_weight weight: {:?}", weight);
155
156 let (asset_location, _) = self.asset_location_and_units_per_second.clone()?;
157 let weight = weight.min(self.weight);
158 let quote = self
159 .quote_weight(weight, AssetId(asset_location), context)
160 .ok()?;
161
162 self.weight = self.weight.saturating_sub(weight);
163 let refunded = self.taken.saturating_take(quote.into());
165
166 if refunded.is_empty() {
167 None
168 } else {
169 Some(refunded)
170 }
171 }
172
173 fn quote_weight(
174 &mut self,
175 weight: Weight,
176 given: AssetId,
177 _: &XcmContext,
178 ) -> Result<Asset, XcmError> {
179 log::trace!(
180 target: "xcm::weight",
181 "FixedRateOfForeignAsset::quote_weight weight: {:?}, given: {:?}",
182 weight, given,
183 );
184
185 if weight.is_zero() {
186 return Err(XcmError::NoDeal);
187 }
188 let units_per_second =
189 T::get_units_per_second(given.0.clone()).ok_or(XcmError::NotHoldingFees)?;
190
191 Ok((given, execution_fee(weight, units_per_second)).into())
192 }
193}
194
195impl<T: ExecutionPaymentRate, R: TakeRevenue> Drop for FixedRateOfForeignAsset<T, R> {
196 fn drop(&mut self) {
197 if !self.taken.is_empty() {
198 R::take_revenue(core::mem::replace(&mut self.taken, AssetsInHolding::new()));
199 }
200 }
201}
202
203fn execution_fee(weight: Weight, units_per_second: u128) -> u128 {
205 units_per_second.saturating_mul(weight.ref_time() as u128)
206 / (WEIGHT_REF_TIME_PER_SECOND as u128)
207}
208
209pub struct ReserveAssetFilter;
215impl ContainsPair<Asset, Location> for ReserveAssetFilter {
216 fn contains(asset: &Asset, origin: &Location) -> bool {
217 let AssetId(location) = &asset.id;
218 match (location.parents, location.first_interior()) {
219 (1, Some(Parachain(id))) => origin == &Location::new(1, [Parachain(*id)]),
221 (1, None) => origin == &Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]),
223 _ => false,
224 }
225 }
226}
227
228pub struct XcmFungibleFeeHandler<AssetTransactor, FeeDestination>(
235 sp_std::marker::PhantomData<(AssetTransactor, FeeDestination)>,
236);
237impl<AssetTransactor: TransactAsset, FeeDestination: Get<Location>> TakeRevenue
238 for XcmFungibleFeeHandler<AssetTransactor, FeeDestination>
239{
240 fn take_revenue(revenue: AssetsInHolding) {
241 let destination = FeeDestination::get();
242 for (id, credit) in revenue.fungible.into_iter() {
243 let fee = AssetsInHolding::new_from_fungible_credit(id, credit);
244 log::trace!(target: "xcm::weight", "XcmFeeHandler::take_revenue depositing {:?}", fee);
245 if let Err((unspent, error)) = AssetTransactor::deposit_asset(fee, &destination, None) {
247 log::error!(
248 target: "xcm::weight",
249 "XcmFeeHandler::take_revenue failed to deposit {:?}: {:?}, it has been burned.",
250 unspent, error,
251 );
252 }
253 }
254 }
255}
256
257pub fn split_location_into_chain_part_and_beneficiary(
265 mut location: Location,
266) -> Option<(Location, Location)> {
267 let mut beneficiary_junctions = Junctions::Here;
268
269 while let Some(junction) = location.last() {
270 if matches!(
271 junction,
272 Junction::Parachain(_) | Junction::GlobalConsensus(_)
273 ) {
274 return Some((location, beneficiary_junctions.into_location()));
275 }
276
277 let (prefix, maybe_last) = location.split_last_interior();
278 location = prefix;
279 if let Some(junction) = maybe_last {
280 beneficiary_junctions.push_front(junction).ok()?;
281 }
282 }
283
284 if location.parent_count() == 1 {
286 Some((Location::parent(), beneficiary_junctions.into_location()))
287 } else {
288 None
289 }
290}
291
292pub fn resolve_transfer_type<XcmExecutor: XcmAssetTransfers>(
303 asset: &Asset,
304 dest: &Location,
305) -> Option<TransferType> {
306 if let Ok(transfer_type) = XcmExecutor::determine_for(asset, dest) {
307 return Some(transfer_type);
308 }
309
310 let AssetId(location) = &asset.id;
311 if location == &Location::parent() {
312 return Some(TransferType::RemoteReserve(
313 Location::new(1, [Parachain(ASSET_HUB_PARA_ID)]).into(),
314 ));
315 }
316
317 None
318}