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

            
17
use codec::Decode;
18
use core::{marker::PhantomData, result::Result};
19
use 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
};
30
use sp_runtime::traits::{SaturatedConversion, Saturating, Zero};
31
use xcm::latest::{prelude::*, GetWeight, Weight};
32
use xcm_executor::{
33
	traits::{WeightBounds, WeightTrader},
34
	AssetsInHolding,
35
};
36

            
37
pub struct FixedWeightBounds<T, C, M>(PhantomData<(T, C, M)>);
38
impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M: Get<u32>> WeightBounds<C>
39
	for FixedWeightBounds<T, C, M>
40
{
41
22254
	fn weight(message: &mut Xcm<C>) -> Result<Weight, ()> {
42
22254
		log::trace!(target: "xcm::weight", "FixedWeightBounds message: {:?}", message);
43
22254
		let mut instructions_left = M::get();
44
22254
		Self::weight_with_limit(message, &mut instructions_left)
45
22254
	}
46
116319
	fn instr_weight(instruction: &Instruction<C>) -> Result<Weight, ()> {
47
116319
		Self::instr_weight_with_limit(instruction, &mut u32::max_value())
48
116319
	}
49
}
50

            
51
impl<T: Get<Weight>, C: Decode + GetDispatchInfo, M> FixedWeightBounds<T, C, M> {
52
32955
	fn weight_with_limit(message: &Xcm<C>, instrs_limit: &mut u32) -> Result<Weight, ()> {
53
32955
		let mut r: Weight = Weight::zero();
54
32955
		*instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?;
55
238434
		for m in message.0.iter() {
56
238434
			r = r.checked_add(&Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?;
57
		}
58
32886
		Ok(r)
59
32955
	}
60
354753
	fn instr_weight_with_limit(
61
354753
		instruction: &Instruction<C>,
62
354753
		instrs_limit: &mut u32,
63
354753
	) -> Result<Weight, ()> {
64
354753
		let instr_weight = match instruction {
65
11145
			Transact { require_weight_at_most, .. } => *require_weight_at_most,
66
10701
			SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?,
67
332907
			_ => Weight::zero(),
68
		};
69
354753
		T::get().checked_add(&instr_weight).ok_or(())
70
354753
	}
71
}
72

            
73
pub struct WeightInfoBounds<W, C, M>(PhantomData<(W, C, M)>);
74
impl<W, C, M> WeightBounds<C> for WeightInfoBounds<W, C, M>
75
where
76
	W: XcmWeightInfo<C>,
77
	C: Decode + GetDispatchInfo,
78
	M: Get<u32>,
79
	Instruction<C>: xcm::latest::GetWeight<W>,
80
{
81
	fn weight(message: &mut Xcm<C>) -> Result<Weight, ()> {
82
		log::trace!(target: "xcm::weight", "WeightInfoBounds message: {:?}", message);
83
		let mut instructions_left = M::get();
84
		Self::weight_with_limit(message, &mut instructions_left)
85
	}
86
	fn instr_weight(instruction: &Instruction<C>) -> Result<Weight, ()> {
87
		Self::instr_weight_with_limit(instruction, &mut u32::max_value())
88
	}
89
}
90

            
91
impl<W, C, M> WeightInfoBounds<W, C, M>
92
where
93
	W: XcmWeightInfo<C>,
94
	C: Decode + GetDispatchInfo,
95
	M: Get<u32>,
96
	Instruction<C>: xcm::latest::GetWeight<W>,
97
{
98
	fn weight_with_limit(message: &Xcm<C>, instrs_limit: &mut u32) -> Result<Weight, ()> {
99
		let mut r: Weight = Weight::zero();
100
		*instrs_limit = instrs_limit.checked_sub(message.0.len() as u32).ok_or(())?;
101
		for m in message.0.iter() {
102
			r = r.checked_add(&Self::instr_weight_with_limit(m, instrs_limit)?).ok_or(())?;
103
		}
104
		Ok(r)
105
	}
106
	fn instr_weight_with_limit(
107
		instruction: &Instruction<C>,
108
		instrs_limit: &mut u32,
109
	) -> Result<Weight, ()> {
110
		let instr_weight = match instruction {
111
			Transact { require_weight_at_most, .. } => *require_weight_at_most,
112
			SetErrorHandler(xcm) | SetAppendix(xcm) => Self::weight_with_limit(xcm, instrs_limit)?,
113
			_ => Weight::zero(),
114
		};
115
		instruction.weight().checked_add(&instr_weight).ok_or(())
116
	}
117
}
118

            
119
/// Function trait for handling some revenue. Similar to a negative imbalance (credit) handler, but
120
/// for a `Asset`. Sensible implementations will deposit the asset in some known treasury or
121
/// block-author account.
122
pub trait TakeRevenue {
123
	/// Do something with the given `revenue`, which is a single non-wildcard `Asset`.
124
	fn take_revenue(revenue: Asset);
125
}
126

            
127
/// Null implementation just burns the revenue.
128
impl TakeRevenue for () {
129
	fn take_revenue(_revenue: Asset) {}
130
}
131

            
132
/// Simple fee calculator that requires payment in a single fungible at a fixed rate.
133
///
134
/// The constant `Get` type parameter should be the fungible ID, the amount of it required for one
135
/// second of weight and the amount required for 1 MB of proof.
136
pub struct FixedRateOfFungible<T: Get<(AssetId, u128, u128)>, R: TakeRevenue>(
137
	Weight,
138
	u128,
139
	PhantomData<(T, R)>,
140
);
141
impl<T: Get<(AssetId, u128, u128)>, R: TakeRevenue> WeightTrader for FixedRateOfFungible<T, R> {
142
	fn new() -> Self {
143
		Self(Weight::zero(), 0, PhantomData)
144
	}
145

            
146
	fn buy_weight(
147
		&mut self,
148
		weight: Weight,
149
		payment: AssetsInHolding,
150
		context: &XcmContext,
151
	) -> Result<AssetsInHolding, XcmError> {
152
		log::trace!(
153
			target: "xcm::weight",
154
			"FixedRateOfFungible::buy_weight weight: {:?}, payment: {:?}, context: {:?}",
155
			weight, payment, context,
156
		);
157
		let (id, units_per_second, units_per_mb) = T::get();
158
		let amount = (units_per_second * (weight.ref_time() as u128) /
159
			(WEIGHT_REF_TIME_PER_SECOND as u128)) +
160
			(units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128));
161
		if amount == 0 {
162
			return Ok(payment)
163
		}
164
		let unused =
165
			payment.checked_sub((id, amount).into()).map_err(|_| XcmError::TooExpensive)?;
166
		self.0 = self.0.saturating_add(weight);
167
		self.1 = self.1.saturating_add(amount);
168
		Ok(unused)
169
	}
170

            
171
	fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<Asset> {
172
		log::trace!(target: "xcm::weight", "FixedRateOfFungible::refund_weight weight: {:?}, context: {:?}", weight, context);
173
		let (id, units_per_second, units_per_mb) = T::get();
174
		let weight = weight.min(self.0);
175
		let amount = (units_per_second * (weight.ref_time() as u128) /
176
			(WEIGHT_REF_TIME_PER_SECOND as u128)) +
177
			(units_per_mb * (weight.proof_size() as u128) / (WEIGHT_PROOF_SIZE_PER_MB as u128));
178
		self.0 -= weight;
179
		self.1 = self.1.saturating_sub(amount);
180
		if amount > 0 {
181
			Some((id, amount).into())
182
		} else {
183
			None
184
		}
185
	}
186
}
187

            
188
impl<T: Get<(AssetId, u128, u128)>, R: TakeRevenue> Drop for FixedRateOfFungible<T, R> {
189
	fn drop(&mut self) {
190
		if self.1 > 0 {
191
			R::take_revenue((T::get().0, self.1).into());
192
		}
193
	}
194
}
195

            
196
/// Weight trader which uses the configured `WeightToFee` to set the right price for weight and then
197
/// places any weight bought into the right account.
198
pub struct UsingComponents<
199
	WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
200
	AssetIdValue: Get<Location>,
201
	AccountId,
202
	Fungible: Balanced<AccountId> + Inspect<AccountId>,
203
	OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
204
>(
205
	Weight,
206
	Fungible::Balance,
207
	PhantomData<(WeightToFee, AssetIdValue, AccountId, Fungible, OnUnbalanced)>,
208
);
209
impl<
210
		WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
211
		AssetIdValue: Get<Location>,
212
		AccountId,
213
		Fungible: Balanced<AccountId> + Inspect<AccountId>,
214
		OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
215
	> WeightTrader for UsingComponents<WeightToFee, AssetIdValue, AccountId, Fungible, OnUnbalanced>
216
{
217
20139
	fn new() -> Self {
218
20139
		Self(Weight::zero(), Zero::zero(), PhantomData)
219
20139
	}
220

            
221
	fn buy_weight(
222
		&mut self,
223
		weight: Weight,
224
		payment: AssetsInHolding,
225
		context: &XcmContext,
226
	) -> Result<AssetsInHolding, XcmError> {
227
		log::trace!(target: "xcm::weight", "UsingComponents::buy_weight weight: {:?}, payment: {:?}, context: {:?}", weight, payment, context);
228
		let amount = WeightToFee::weight_to_fee(&weight);
229
		let u128_amount: u128 = amount.try_into().map_err(|_| XcmError::Overflow)?;
230
		let required = (AssetId(AssetIdValue::get()), u128_amount).into();
231
		let unused = payment.checked_sub(required).map_err(|_| XcmError::TooExpensive)?;
232
		self.0 = self.0.saturating_add(weight);
233
		self.1 = self.1.saturating_add(amount);
234
		Ok(unused)
235
	}
236

            
237
12
	fn refund_weight(&mut self, weight: Weight, context: &XcmContext) -> Option<Asset> {
238
12
		log::trace!(target: "xcm::weight", "UsingComponents::refund_weight weight: {:?}, context: {:?}, available weight: {:?}, available amount: {:?}", weight, context, self.0, self.1);
239
12
		let weight = weight.min(self.0);
240
12
		let amount = WeightToFee::weight_to_fee(&weight);
241
12
		self.0 -= weight;
242
12
		self.1 = self.1.saturating_sub(amount);
243
12
		let amount: u128 = amount.saturated_into();
244
12
		log::trace!(target: "xcm::weight", "UsingComponents::refund_weight amount to refund: {:?}", amount);
245
12
		if amount > 0 {
246
			Some((AssetIdValue::get(), amount).into())
247
		} else {
248
12
			None
249
		}
250
12
	}
251
}
252
impl<
253
		WeightToFee: WeightToFeeT<Balance = <Fungible as Inspect<AccountId>>::Balance>,
254
		AssetId: Get<Location>,
255
		AccountId,
256
		Fungible: Balanced<AccountId> + Inspect<AccountId>,
257
		OnUnbalanced: OnUnbalancedT<Credit<AccountId, Fungible>>,
258
	> Drop for UsingComponents<WeightToFee, AssetId, AccountId, Fungible, OnUnbalanced>
259
{
260
20139
	fn drop(&mut self) {
261
20139
		OnUnbalanced::on_unbalanced(Fungible::issue(self.1));
262
20139
	}
263
}