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 codec::Decode;
18use core::{marker::PhantomData, result::Result};
19use frame_support::{
20	dispatch::GetDispatchInfo,
21	traits::{
22		fungible::{Balanced, Credit, Inspect},
23		Get, OnUnbalanced as OnUnbalancedT,
24	},
25	weights::{
26		constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND},
27		WeightToFee as WeightToFeeT,
28	},
29};
30use sp_runtime::traits::{SaturatedConversion, Saturating, Zero};
31use xcm::latest::{prelude::*, GetWeight, Weight};
32use xcm_executor::{
33	traits::{WeightBounds, WeightTrader},
34	AssetsInHolding,
35};
36
37pub struct FixedWeightBounds<T, C, M>(PhantomData<(T, C, M)>);
38impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M: Get<u32>> WeightBounds<C>
39	for FixedWeightBounds<T, C, M>
40{
41	fn weight(message: &mut Xcm<C>, weight_limit: Weight) -> Result<Weight, InstructionError> {
42		tracing::trace!(target: "xcm::weight", ?message, "FixedWeightBounds");
43		let mut instructions_left = M::get();
44		Self::weight_with_limit(message, &mut instructions_left, weight_limit).inspect_err(
45			|&error| {
46				tracing::debug!(
47					target: "xcm::weight",
48					?error,
49					?instructions_left,
50					message_length = ?message.0.len(),
51					"Weight calculation failed for message"
52				);
53			},
54		)
55	}
56	fn instr_weight(instruction: &mut Instruction<C>) -> Result<Weight, XcmError> {
57		let mut max_value = u32::MAX;
58		Self::instr_weight_with_limit(instruction, &mut max_value, Weight::MAX).inspect_err(
59			|&error| {
60				tracing::debug!(
61					target: "xcm::weight",
62					?error,
63					?instruction,
64					instrs_limit = ?max_value,
65					"Weight calculation failed for instruction"
66				);
67			},
68		)
69	}
70}
71
72impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M> FixedWeightBounds<T, C, M> {
73	fn weight_with_limit(
74		message: &mut Xcm<C>,
75		instructions_left: &mut u32,
76		weight_limit: Weight,
77	) -> Result<Weight, InstructionError> {
78		let mut total_weight: Weight = Weight::zero();
79		for (index, instruction) in message.0.iter_mut().enumerate() {
80			let index = index.try_into().unwrap_or(InstructionIndex::MAX);
81			*instructions_left = instructions_left
82				.checked_sub(1)
83				.ok_or_else(|| InstructionError { index, error: XcmError::ExceedsStackLimit })?;
84			let instruction_weight =
85				&Self::instr_weight_with_limit(instruction, instructions_left, weight_limit)
86					.map_err(|error| InstructionError { index, error })?;
87			total_weight = total_weight
88				.checked_add(instruction_weight)
89				.ok_or(InstructionError { index, error: XcmError::Overflow })?;
90			if total_weight.any_gt(weight_limit) {
91				return Err(InstructionError {
92					index,
93					error: XcmError::WeightLimitReached(total_weight),
94				});
95			}
96		}
97		Ok(total_weight)
98	}
99
100	fn instr_weight_with_limit(
101		instruction: &mut Instruction<C>,
102		instructions_left: &mut u32,
103		weight_limit: Weight,
104	) -> Result<Weight, XcmError> {
105		let instruction_weight = match instruction {
106			Transact { ref mut call, .. } =>
107				call.ensure_decoded()
108					.map_err(|_| XcmError::FailedToDecode)?
109					.get_dispatch_info()
110					.call_weight,
111			SetErrorHandler(xcm) | SetAppendix(xcm) | ExecuteWithOrigin { xcm, .. } =>
112				Self::weight_with_limit(xcm, instructions_left, weight_limit)
113					.map_err(|outcome_error| outcome_error.error)?,
114			_ => Weight::zero(),
115		};
116		let total_weight = T::get().checked_add(&instruction_weight).ok_or(XcmError::Overflow)?;
117		Ok(total_weight)
118	}
119}
120
121pub struct WeightInfoBounds<W, C, M>(PhantomData<(W, C, M)>);
122impl<W, C, M> WeightBounds<C> for WeightInfoBounds<W, C, M>
123where
124	W: XcmWeightInfo<C>,
125	C: Decode + GetDispatchInfo,
126	M: Get<u32>,
127	Instruction<C>: xcm::latest::GetWeight<W>,
128{
129	fn weight(message: &mut Xcm<C>, weight_limit: Weight) -> Result<Weight, InstructionError> {
130		tracing::trace!(target: "xcm::weight", ?message, "WeightInfoBounds");
131		let mut instructions_left = M::get();
132		Self::weight_with_limit(message, &mut instructions_left, weight_limit).inspect_err(
133			|&error| {
134				tracing::debug!(
135					target: "xcm::weight",
136					?error,
137					?instructions_left,
138					message_length = ?message.0.len(),
139					"Weight calculation failed for message"
140				);
141			},
142		)
143	}
144	fn instr_weight(instruction: &mut Instruction<C>) -> Result<Weight, XcmError> {
145		let mut max_value = u32::MAX;
146		Self::instr_weight_with_limit(instruction, &mut max_value, Weight::MAX).inspect_err(
147			|&error| {
148				tracing::debug!(
149					target: "xcm::weight",
150					?error,
151					?instruction,
152					instrs_limit = ?max_value,
153					"Weight calculation failed for instruction"
154				);
155			},
156		)
157	}
158}
159
160impl<W, C, M> WeightInfoBounds<W, C, M>
161where
162	W: XcmWeightInfo<C>,
163	C: Decode + GetDispatchInfo,
164	M: Get<u32>,
165	Instruction<C>: xcm::latest::GetWeight<W>,
166{
167	fn weight_with_limit(
168		message: &mut Xcm<C>,
169		instructions_left: &mut u32,
170		weight_limit: Weight,
171	) -> Result<Weight, InstructionError> {
172		let mut total_weight: Weight = Weight::zero();
173		for (index, instruction) in message.0.iter_mut().enumerate() {
174			let index = index.try_into().unwrap_or(u8::MAX);
175			*instructions_left = instructions_left
176				.checked_sub(1)
177				.ok_or_else(|| InstructionError { index, error: XcmError::ExceedsStackLimit })?;
178			let instruction_weight =
179				&Self::instr_weight_with_limit(instruction, instructions_left, weight_limit)
180					.map_err(|error| InstructionError { index, error })?;
181			total_weight = total_weight
182				.checked_add(instruction_weight)
183				.ok_or(InstructionError { index, error: XcmError::Overflow })?;
184			if total_weight.any_gt(weight_limit) {
185				return Err(InstructionError {
186					index,
187					error: XcmError::WeightLimitReached(total_weight),
188				});
189			}
190		}
191		Ok(total_weight)
192	}
193
194	fn instr_weight_with_limit(
195		instruction: &mut Instruction<C>,
196		instructions_left: &mut u32,
197		weight_limit: Weight,
198	) -> Result<Weight, XcmError> {
199		let instruction_weight = match instruction {
200			Transact { ref mut call, .. } =>
201				call.ensure_decoded()
202					.map_err(|_| XcmError::FailedToDecode)?
203					.get_dispatch_info()
204					.call_weight,
205			SetErrorHandler(xcm) | SetAppendix(xcm) =>
206				Self::weight_with_limit(xcm, instructions_left, weight_limit)
207					.map_err(|outcome_error| outcome_error.error)?,
208			_ => Weight::zero(),
209		};
210		let total_weight = instruction
211			.weight()
212			.checked_add(&instruction_weight)
213			.ok_or(XcmError::Overflow)?;
214		Ok(total_weight)
215	}
216}
217
218/// Function trait for handling some revenue. Similar to a negative imbalance (credit) handler, but
219/// for a `Asset`. Sensible implementations will deposit the asset in some known treasury or
220/// block-author account.
221pub trait TakeRevenue {
222	/// Do something with the given `revenue`, which is a single non-wildcard `Asset`.
223	fn take_revenue(revenue: Asset);
224}
225
226/// Null implementation just burns the revenue.
227impl TakeRevenue for () {
228	fn take_revenue(_revenue: Asset) {}
229}
230
231/// Simple fee calculator that requires payment in a single fungible at a fixed rate.
232///
233/// The constant `Get` type parameter should be the fungible ID, the amount of it required for one
234/// second of weight and the amount required for 1 MB of proof.
235pub struct FixedRateOfFungible<T: Get<(AssetId, u128, u128)>, R: TakeRevenue>(
236	Weight,
237	u128,
238	PhantomData<(T, R)>,
239);
240impl<T: Get<(AssetId, u128, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungible<T, R> {
241	fn new() -> Self {
242		Self(Weight::zero(), 0, PhantomData)
243	}
244
245	fn buy_weight(
246		&mut self,
247		weight: Weight,
248		payment: AssetsInHolding,
249		context: &XcmContext,
250	) -> Result<AssetsInHolding, XcmError> {
251		let (id, units_per_second, units_per_mb) = T::get();
252		tracing::trace!(
253			target: "xcm::weight",
254			?id, ?weight, ?payment, ?context,
255			"FixedRateOfFungible::buy_weight",
256		);
257		let amount = (units_per_second * (weight.ref_time() as u128) /
258			(WEIGHT_REF_TIME_PER_SECOND as u128)) +
259			(units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128));
260		if amount == 0 {
261			return Ok(payment)
262		}
263		let unused = payment.checked_sub((id, amount).into()).map_err(|error| {
264			tracing::error!(target: "xcm::weight", ?amount, ?error, "FixedRateOfFungible::buy_weight Failed to substract from payment");
265			XcmError::TooExpensive
266		})?;
267		self.0 = self.0.saturating_add(weight);
268		self.1 = self.1.saturating_add(amount);
269		Ok(unused)
270	}
271
272	fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<Asset> {
273		let (id, units_per_second, units_per_mb) = T::get();
274		tracing::trace!(target: "xcm::weight", ?id, ?weight, ?context, "FixedRateOfFungible::refund_weight");
275		let weight = weight.min(self.0);
276		let amount = (units_per_second * (weight.ref_time() as u128) /
277			(WEIGHT_REF_TIME_PER_SECOND as u128)) +
278			(units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128));
279		self.0 -= weight;
280		self.1 = self.1.saturating_sub(amount);
281		if amount > 0 {
282			Some((id, amount).into())
283		} else {
284			None
285		}
286	}
287}
288
289impl<T: Get<(AssetId, u128, u128)>, R: TakeRevenue> Drop for FixedRateOfFungible<T, R> {
290	fn drop(&mut self) {
291		if self.1 > 0 {
292			R::take_revenue((T::get().0, self.1).into());
293		}
294	}
295}
296
297/// Weight trader which uses the configured `WeightToFee` to set the right price for weight and then
298/// places any weight bought into the right account.
299pub struct UsingComponents<
300	WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
301	AssetIdValue: Get<Location>,
302	AccountId,
303	Fungible: Balanced<AccountId> + Inspect<AccountId>,
304	OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
305>(
306	Weight,
307	Fungible::Balance,
308	PhantomData<(WeightToFee, AssetIdValue, AccountId, Fungible, OnUnbalanced)>,
309);
310impl<
311		WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
312		AssetIdValue: Get<Location>,
313		AccountId,
314		Fungible: Balanced<AccountId> + Inspect<AccountId>,
315		OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
316	> WeightTrader for UsingComponents<WeightToFee, AssetIdValue, AccountId, Fungible, OnUnbalanced>
317{
318	fn new() -> Self {
319		Self(Weight::zero(), Zero::zero(), PhantomData)
320	}
321
322	fn buy_weight(
323		&mut self,
324		weight: Weight,
325		payment: AssetsInHolding,
326		context: &XcmContext,
327	) -> Result<AssetsInHolding, XcmError> {
328		tracing::trace!(target: "xcm::weight", ?weight, ?payment, ?context, "UsingComponents::buy_weight");
329		let amount = WeightToFee::weight_to_fee(&weight);
330		let u128_amount: u128 = amount.try_into().map_err(|_| {
331			tracing::debug!(target: "xcm::weight", ?amount, "Weight fee could not be converted");
332			XcmError::Overflow
333		})?;
334		let required = Asset { id: AssetId(AssetIdValue::get()), fun: Fungible(u128_amount) };
335		let unused = payment.checked_sub(required).map_err(|error| {
336			tracing::debug!(target: "xcm::weight", ?error, "Failed to substract from payment");
337			XcmError::TooExpensive
338		})?;
339		self.0 = self.0.saturating_add(weight);
340		self.1 = self.1.saturating_add(amount);
341		Ok(unused)
342	}
343
344	fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<Asset> {
345		tracing::trace!(target: "xcm::weight", ?weight, ?context, available_weight = ?self.0, available_amount = ?self.1, "UsingComponents::refund_weight");
346		let weight = weight.min(self.0);
347		let amount = WeightToFee::weight_to_fee(&weight);
348		self.0 -= weight;
349		self.1 = self.1.saturating_sub(amount);
350		let amount: u128 = amount.saturated_into();
351		tracing::trace!(target: "xcm::weight", ?amount, "UsingComponents::refund_weight");
352		if amount > 0 {
353			Some((AssetIdValue::get(), amount).into())
354		} else {
355			None
356		}
357	}
358}
359impl<
360		WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
361		AssetId: Get<Location>,
362		AccountId,
363		Fungible: Balanced<AccountId> + Inspect<AccountId>,
364		OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
365	> Drop for UsingComponents<WeightToFee, AssetId, AccountId, Fungible, OnUnbalanced>
366{
367	fn drop(&mut self) {
368		OnUnbalanced::on_unbalanced(Fungible::issue(self.1));
369	}
370}