1use alloc::boxed::Box;
18use codec::Decode;
19use core::{marker::PhantomData, result::Result};
20use frame_support::{
21 dispatch::GetDispatchInfo,
22 traits::{
23 fungible::{Balanced, Credit, Imbalance, Inspect},
24 tokens::imbalance::{ImbalanceAccounting, UnsafeManualAccounting},
25 Get, Imbalance as ImbalanceT, OnUnbalanced as OnUnbalancedT,
26 },
27 weights::{
28 constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND},
29 WeightToFee as WeightToFeeT,
30 },
31};
32use sp_runtime::traits::Zero;
33use xcm::latest::{prelude::*, GetWeight, Weight};
34use xcm_executor::{
35 traits::{WeightBounds, WeightTrader},
36 AssetsInHolding,
37};
38
39pub struct FixedWeightBounds<T, C, M>(PhantomData<(T, C, M)>);
40
41impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M: Get<u32>> WeightBounds<C>
42 for FixedWeightBounds<T, C, M>
43{
44 fn weight(message: &mut Xcm<C>, weight_limit: Weight) -> Result<Weight, InstructionError> {
45 tracing::trace!(target: "xcm::weight", ?message, "FixedWeightBounds");
46 let mut instructions_left = M::get();
47 Self::weight_with_limit(message, &mut instructions_left, weight_limit).inspect_err(
48 |&error| {
49 tracing::debug!(
50 target: "xcm::weight",
51 ?error,
52 ?instructions_left,
53 message_length = ?message.0.len(),
54 "Weight calculation failed for message"
55 );
56 },
57 )
58 }
59 fn instr_weight(instruction: &mut Instruction<C>) -> Result<Weight, XcmError> {
60 let mut max_value = u32::MAX;
61 Self::instr_weight_with_limit(instruction, &mut max_value, Weight::MAX).inspect_err(
62 |&error| {
63 tracing::debug!(
64 target: "xcm::weight",
65 ?error,
66 ?instruction,
67 instrs_limit = ?max_value,
68 "Weight calculation failed for instruction"
69 );
70 },
71 )
72 }
73}
74
75impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M> FixedWeightBounds<T, C, M> {
76 fn weight_with_limit(
77 message: &mut Xcm<C>,
78 instructions_left: &mut u32,
79 weight_limit: Weight,
80 ) -> Result<Weight, InstructionError> {
81 let mut total_weight: Weight = Weight::zero();
82 for (index, instruction) in message.0.iter_mut().enumerate() {
83 let index = index.try_into().unwrap_or(InstructionIndex::MAX);
84 *instructions_left = instructions_left
85 .checked_sub(1)
86 .ok_or_else(|| InstructionError { index, error: XcmError::ExceedsStackLimit })?;
87 let instruction_weight =
88 &Self::instr_weight_with_limit(instruction, instructions_left, weight_limit)
89 .map_err(|error| InstructionError { index, error })?;
90 total_weight = total_weight
91 .checked_add(instruction_weight)
92 .ok_or(InstructionError { index, error: XcmError::Overflow })?;
93 if total_weight.any_gt(weight_limit) {
94 return Err(InstructionError {
95 index,
96 error: XcmError::WeightLimitReached(total_weight),
97 });
98 }
99 }
100 Ok(total_weight)
101 }
102
103 fn instr_weight_with_limit(
104 instruction: &mut Instruction<C>,
105 instructions_left: &mut u32,
106 weight_limit: Weight,
107 ) -> Result<Weight, XcmError> {
108 let instruction_weight = match instruction {
109 Transact { ref mut call, .. } => {
110 call.ensure_decoded()
111 .map_err(|_| XcmError::FailedToDecode)?
112 .get_dispatch_info()
113 .call_weight
114 },
115 SetErrorHandler(xcm) | SetAppendix(xcm) | ExecuteWithOrigin { xcm, .. } => {
116 Self::weight_with_limit(xcm, instructions_left, weight_limit)
117 .map_err(|outcome_error| outcome_error.error)?
118 },
119 _ => Weight::zero(),
120 };
121 let total_weight = T::get().checked_add(&instruction_weight).ok_or(XcmError::Overflow)?;
122 Ok(total_weight)
123 }
124}
125
126pub struct WeightInfoBounds<W, C, M>(PhantomData<(W, C, M)>);
127
128impl<W, C, M> WeightBounds<C> for WeightInfoBounds<W, C, M>
129where
130 W: XcmWeightInfo<C>,
131 C: Decode + GetDispatchInfo,
132 M: Get<u32>,
133 Instruction<C>: xcm::latest::GetWeight<W>,
134{
135 fn weight(message: &mut Xcm<C>, weight_limit: Weight) -> Result<Weight, InstructionError> {
136 tracing::trace!(target: "xcm::weight", ?message, "WeightInfoBounds");
137 let mut instructions_left = M::get();
138 Self::weight_with_limit(message, &mut instructions_left, weight_limit).inspect_err(
139 |&error| {
140 tracing::debug!(
141 target: "xcm::weight",
142 ?error,
143 ?instructions_left,
144 message_length = ?message.0.len(),
145 "Weight calculation failed for message"
146 );
147 },
148 )
149 }
150 fn instr_weight(instruction: &mut Instruction<C>) -> Result<Weight, XcmError> {
151 let mut max_value = u32::MAX;
152 Self::instr_weight_with_limit(instruction, &mut max_value, Weight::MAX).inspect_err(
153 |&error| {
154 tracing::debug!(
155 target: "xcm::weight",
156 ?error,
157 ?instruction,
158 instrs_limit = ?max_value,
159 "Weight calculation failed for instruction"
160 );
161 },
162 )
163 }
164}
165
166impl<W, C, M> WeightInfoBounds<W, C, M>
167where
168 W: XcmWeightInfo<C>,
169 C: Decode + GetDispatchInfo,
170 M: Get<u32>,
171 Instruction<C>: xcm::latest::GetWeight<W>,
172{
173 fn weight_with_limit(
174 message: &mut Xcm<C>,
175 instructions_left: &mut u32,
176 weight_limit: Weight,
177 ) -> Result<Weight, InstructionError> {
178 let mut total_weight: Weight = Weight::zero();
179 for (index, instruction) in message.0.iter_mut().enumerate() {
180 let index = index.try_into().unwrap_or(u8::MAX);
181 *instructions_left = instructions_left
182 .checked_sub(1)
183 .ok_or_else(|| InstructionError { index, error: XcmError::ExceedsStackLimit })?;
184 let instruction_weight =
185 &Self::instr_weight_with_limit(instruction, instructions_left, weight_limit)
186 .map_err(|error| InstructionError { index, error })?;
187 total_weight = total_weight
188 .checked_add(instruction_weight)
189 .ok_or(InstructionError { index, error: XcmError::Overflow })?;
190 if total_weight.any_gt(weight_limit) {
191 return Err(InstructionError {
192 index,
193 error: XcmError::WeightLimitReached(total_weight),
194 });
195 }
196 }
197 Ok(total_weight)
198 }
199
200 fn instr_weight_with_limit(
201 instruction: &mut Instruction<C>,
202 instructions_left: &mut u32,
203 weight_limit: Weight,
204 ) -> Result<Weight, XcmError> {
205 let instruction_weight = match instruction {
206 Transact { ref mut call, .. } => {
207 call.ensure_decoded()
208 .map_err(|_| XcmError::FailedToDecode)?
209 .get_dispatch_info()
210 .call_weight
211 },
212 SetErrorHandler(xcm) | SetAppendix(xcm) => {
213 Self::weight_with_limit(xcm, instructions_left, weight_limit)
214 .map_err(|outcome_error| outcome_error.error)?
215 },
216 _ => Weight::zero(),
217 };
218 let total_weight = instruction
219 .weight()
220 .checked_add(&instruction_weight)
221 .ok_or(XcmError::Overflow)?;
222 Ok(total_weight)
223 }
224}
225
226pub trait TakeRevenue {
230 fn take_revenue(revenue: AssetsInHolding);
232}
233
234impl TakeRevenue for () {
236 fn take_revenue(_revenue: AssetsInHolding) {}
237}
238
239pub struct FixedRateOfFungible<T: Get<(AssetId, u128, u128)>, R: TakeRevenue>(
244 Weight,
245 AssetsInHolding,
246 PhantomData<(T, R)>,
247);
248
249impl<T: Get<(AssetId, u128, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungible<T, R> {
250 fn new() -> Self {
251 Self(Weight::zero(), AssetsInHolding::new(), PhantomData)
252 }
253
254 fn buy_weight(
255 &mut self,
256 weight: Weight,
257 mut payment: AssetsInHolding,
258 context: &XcmContext,
259 ) -> Result<AssetsInHolding, (AssetsInHolding, XcmError)> {
260 let (id, units_per_second, units_per_mb) = T::get();
261 tracing::trace!(
262 target: "xcm::weight",
263 ?id, ?weight, ?payment, ?context,
264 "FixedRateOfFungible::buy_weight",
265 );
266 let amount = (units_per_second * (weight.ref_time() as u128) /
267 (WEIGHT_REF_TIME_PER_SECOND as u128)) +
268 (units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128));
269 if amount == 0 {
270 return Ok(payment);
271 }
272 let to_charge: Asset = (id, amount).into();
273 if let Ok(taken) = payment.try_take(to_charge.into()) {
274 self.0 = self.0.saturating_add(weight);
275 self.1.subsume_assets(taken);
276 Ok(payment)
277 } else {
278 Err((payment, XcmError::TooExpensive))
279 }
280 }
281
282 fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<AssetsInHolding> {
283 let (id, _, _) = T::get();
284 let weight = weight.min(self.0);
285 tracing::trace!(target: "xcm::weight", ?id, ?weight, ?context, "FixedRateOfFungible::refund_weight");
286 let quote = self.quote_weight(weight, id, context).ok()?;
287 self.0 -= weight;
289 let refunded = self.1.saturating_take(quote.into());
291 if refunded.is_empty() {
292 None
293 } else {
294 Some(refunded)
295 }
296 }
297
298 fn quote_weight(
299 &mut self,
300 weight: Weight,
301 given: AssetId,
302 context: &XcmContext,
303 ) -> Result<Asset, XcmError> {
304 let (id, units_per_second, units_per_mb) = T::get();
305 tracing::trace!(
306 target: "xcm::weight",
307 ?id, ?weight, ?given, ?context,
308 "FixedRateOfFungible::quote_weight",
309 );
310 if weight.is_zero() {
311 return Err(XcmError::NoDeal);
312 }
313 if given != id {
314 return Err(XcmError::NotHoldingFees);
315 }
316 let amount = (units_per_second * (weight.ref_time() as u128) /
317 (WEIGHT_REF_TIME_PER_SECOND as u128)) +
318 (units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128));
319 Ok((id, amount).into())
320 }
321}
322
323impl<T: Get<(AssetId, u128, u128)>, R: TakeRevenue> Drop for FixedRateOfFungible<T, R> {
324 fn drop(&mut self) {
325 if !self.1.is_empty() {
326 let mut taken = AssetsInHolding::new();
327 core::mem::swap(&mut self.1, &mut taken);
328 R::take_revenue(taken);
329 }
330 }
331}
332
333pub struct UsingComponents<
336 WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
337 AssetIdValue: Get<Location>,
338 AccountId,
339 Fungible: Balanced<AccountId> + Inspect<AccountId>,
340 OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
341>(
342 Weight,
343 Credit<AccountId, Fungible>,
344 PhantomData<(WeightToFee, AssetIdValue, AccountId, Fungible, OnUnbalanced)>,
345);
346
347impl<
348 WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
349 AssetIdValue: Get<Location>,
350 AccountId,
351 Fungible: Balanced<AccountId, OnDropDebt: 'static, OnDropCredit: 'static> + Inspect<AccountId>,
352 OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
353 > WeightTrader for UsingComponents<WeightToFee, AssetIdValue, AccountId, Fungible, OnUnbalanced>
354where
355 Imbalance<
356 <Fungible as Inspect<AccountId>>::Balance,
357 <Fungible as Balanced<AccountId>>::OnDropCredit,
358 <Fungible as Balanced<AccountId>>::OnDropDebt,
359 >: ImbalanceAccounting<u128>,
360{
361 fn new() -> Self {
362 Self(Weight::zero(), Default::default(), PhantomData)
363 }
364
365 fn buy_weight(
366 &mut self,
367 weight: Weight,
368 mut payment: AssetsInHolding,
369 context: &XcmContext,
370 ) -> Result<AssetsInHolding, (AssetsInHolding, XcmError)> {
371 tracing::trace!(target: "xcm::weight", ?weight, ?payment, ?context, "UsingComponents::buy_weight");
372 let amount = WeightToFee::weight_to_fee(&weight);
373 let Ok(u128_amount): Result<u128, _> = TryInto::<u128>::try_into(amount) else {
374 tracing::debug!(target: "xcm::weight", ?amount, "Weight fee could not be converted");
375 return Err((payment, XcmError::Overflow));
376 };
377 let asset_id = AssetId(AssetIdValue::get());
378 let required = Asset { id: asset_id.clone(), fun: Fungible(u128_amount) };
379 if let Ok(mut taken) = payment.try_take(required.into()) {
380 self.0 = self.0.saturating_add(weight);
381 if let Some(imbalance) = taken.fungible.remove(&asset_id) {
382 self.1.saturating_subsume(imbalance);
383 Ok(payment)
384 } else {
385 payment.subsume_assets(taken);
386 Err((payment, XcmError::TooExpensive))
387 }
388 } else {
389 Err((payment, XcmError::TooExpensive))
390 }
391 }
392
393 fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<AssetsInHolding> {
394 tracing::trace!(target: "xcm::weight", ?weight, ?context, available_weight = ?self.0, available_amount = ?self.1, "UsingComponents::refund_weight");
395 let weight = weight.min(self.0);
396 if weight.is_zero() {
397 return None;
398 }
399 let amount = WeightToFee::weight_to_fee(&weight);
400 self.0 -= weight;
401 let refund = self.1.extract(amount);
403 tracing::trace!(target: "xcm::weight", ?amount, "UsingComponents::refund_weight");
404 if refund.peek() != Zero::zero() {
405 Some(AssetsInHolding::new_from_fungible_credit(
406 AssetId(AssetIdValue::get()),
407 Box::new(refund),
408 ))
409 } else {
410 None
411 }
412 }
413
414 fn quote_weight(
415 &mut self,
416 weight: Weight,
417 given: AssetId,
418 context: &XcmContext,
419 ) -> Result<Asset, XcmError> {
420 tracing::trace!(target: "xcm::weight", ?weight, ?given, ?context, "UsingComponents::quote_weight");
421 if weight.is_zero() {
422 return Err(XcmError::NoDeal);
423 }
424 let supported_id = AssetId(AssetIdValue::get());
425 if given != supported_id {
426 return Err(XcmError::NotHoldingFees);
427 }
428 let amount = WeightToFee::weight_to_fee(&weight);
429 let u128_amount: u128 = TryInto::<u128>::try_into(amount).map_err(|_| {
430 tracing::debug!(target: "xcm::weight", ?amount, "Weight fee could not be converted");
431 XcmError::Overflow
432 })?;
433 let required = Asset { id: supported_id, fun: Fungible(u128_amount) };
434 Ok(required)
435 }
436}
437
438impl<
439 WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
440 AssetId: Get<Location>,
441 AccountId,
442 Fungible: Balanced<AccountId> + Inspect<AccountId>,
443 OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
444 > Drop for UsingComponents<WeightToFee, AssetId, AccountId, Fungible, OnUnbalanced>
445{
446 fn drop(&mut self) {
447 if self.1.peek().is_zero() {
448 return;
449 }
450 let total_fee = self.1.extract(self.1.peek());
451 OnUnbalanced::on_unbalanced(total_fee);
452 }
453}