referrerpolicy=no-referrer-when-downgrade

staging_xcm_builder/
weight.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot 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// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17use 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
226/// Function trait for handling some revenue. Similar to a negative imbalance (credit) handler, but
227/// for a `Asset`. Sensible implementations will deposit the asset in some known treasury or
228/// block-author account.
229pub trait TakeRevenue {
230	/// Do something with the given `revenue`.
231	fn take_revenue(revenue: AssetsInHolding);
232}
233
234/// Null implementation just burns the revenue (drops imbalance).
235impl TakeRevenue for () {
236	fn take_revenue(_revenue: AssetsInHolding) {}
237}
238
239/// Simple fee calculator that requires payment in a single fungible at a fixed rate.
240///
241/// The constant `Get` type parameter should be the fungible ID, the amount of it required for one
242/// second of weight and the amount required for 1 MB of proof.
243pub 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		// Subtract refunded weight.
288		self.0 -= weight;
289		// Refund equivalent assets in holding from trader to caller.
290		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
333/// Weight trader which uses the configured `WeightToFee` to set the right price for weight and then
334/// places any weight bought into the right account.
335pub 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		// self.1 = self.1.saturating_sub(amount);
402		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}