1use crate::{exec::ExecError, Config, Error};
19use core::marker::PhantomData;
20use frame_support::{
21 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo},
22 weights::Weight,
23 DefaultNoBound,
24};
25use sp_core::Get;
26use sp_runtime::{traits::Zero, DispatchError};
27
28#[cfg(test)]
29use std::{any::Any, fmt::Debug};
30
31#[derive(Debug, PartialEq, Eq)]
32pub struct ChargedAmount(Weight);
33
34impl ChargedAmount {
35 pub fn amount(&self) -> Weight {
36 self.0
37 }
38}
39
40#[derive(DefaultNoBound)]
42struct EngineMeter<T: Config> {
43 fuel: u64,
44 _phantom: PhantomData<T>,
45}
46
47impl<T: Config> EngineMeter<T> {
48 fn new(limit: Weight) -> Self {
50 Self {
51 fuel: limit.ref_time().saturating_div(T::Schedule::get().ref_time_by_fuel()),
52 _phantom: PhantomData,
53 }
54 }
55
56 fn set_fuel(&mut self, fuel: u64) -> Weight {
59 let consumed = self
60 .fuel
61 .saturating_sub(fuel)
62 .saturating_mul(T::Schedule::get().ref_time_by_fuel());
63 self.fuel = fuel;
64 Weight::from_parts(consumed, 0)
65 }
66
67 fn charge_ref_time(&mut self, ref_time: u64) -> Result<Syncable, DispatchError> {
70 let amount = ref_time
71 .checked_div(T::Schedule::get().ref_time_by_fuel())
72 .ok_or(Error::<T>::InvalidSchedule)?;
73
74 self.fuel.checked_sub(amount).ok_or_else(|| Error::<T>::OutOfGas)?;
75 Ok(Syncable(self.fuel))
76 }
77}
78
79#[must_use]
83pub struct RefTimeLeft(u64);
84
85#[must_use]
89pub struct Syncable(u64);
90
91impl From<Syncable> for u64 {
92 fn from(from: Syncable) -> u64 {
93 from.0
94 }
95}
96
97#[cfg(not(test))]
98pub trait TestAuxiliaries {}
99#[cfg(not(test))]
100impl<T> TestAuxiliaries for T {}
101
102#[cfg(test)]
103pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
104#[cfg(test)]
105impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
106
107pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
114 fn weight(&self) -> Weight;
123
124 fn influence_lowest_gas_limit(&self) -> bool {
126 true
127 }
128}
129
130#[cfg(test)]
132pub struct ErasedToken {
133 pub description: String,
134 pub token: Box<dyn Any>,
135}
136
137#[derive(DefaultNoBound)]
138pub struct GasMeter<T: Config> {
139 gas_limit: Weight,
140 gas_left: Weight,
142 gas_left_lowest: Weight,
144 engine_meter: EngineMeter<T>,
148 _phantom: PhantomData<T>,
149 #[cfg(test)]
150 tokens: Vec<ErasedToken>,
151}
152
153impl<T: Config> GasMeter<T> {
154 pub fn new(gas_limit: Weight) -> Self {
155 GasMeter {
156 gas_limit,
157 gas_left: gas_limit,
158 gas_left_lowest: gas_limit,
159 engine_meter: EngineMeter::new(gas_limit),
160 _phantom: PhantomData,
161 #[cfg(test)]
162 tokens: Vec::new(),
163 }
164 }
165
166 pub fn nested(&mut self, amount: Weight) -> Self {
172 let amount = Weight::from_parts(
173 if amount.ref_time().is_zero() {
174 self.gas_left().ref_time()
175 } else {
176 amount.ref_time()
177 },
178 if amount.proof_size().is_zero() {
179 self.gas_left().proof_size()
180 } else {
181 amount.proof_size()
182 },
183 )
184 .min(self.gas_left);
185 self.gas_left -= amount;
186 GasMeter::new(amount)
187 }
188
189 pub fn absorb_nested(&mut self, nested: Self) {
191 self.gas_left_lowest = (self.gas_left + nested.gas_limit)
192 .saturating_sub(nested.gas_required())
193 .min(self.gas_left_lowest);
194 self.gas_left += nested.gas_left;
195 }
196
197 #[inline]
207 pub fn charge<Tok: Token<T>>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError> {
208 #[cfg(test)]
209 {
210 let erased_tok =
212 ErasedToken { description: format!("{:?}", token), token: Box::new(token) };
213 self.tokens.push(erased_tok);
214 }
215 let amount = token.weight();
216 self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| Error::<T>::OutOfGas)?;
219 Ok(ChargedAmount(amount))
220 }
221
222 pub fn adjust_gas<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
227 if token.influence_lowest_gas_limit() {
228 self.gas_left_lowest = self.gas_left_lowest();
229 }
230 let adjustment = charged_amount.0.saturating_sub(token.weight());
231 self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
232 }
233
234 pub fn sync_from_executor(&mut self, engine_fuel: u64) -> Result<RefTimeLeft, DispatchError> {
240 let weight_consumed = self.engine_meter.set_fuel(engine_fuel);
241 self.gas_left
242 .checked_reduce(weight_consumed)
243 .ok_or_else(|| Error::<T>::OutOfGas)?;
244 Ok(RefTimeLeft(self.gas_left.ref_time()))
245 }
246
247 pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result<Syncable, DispatchError> {
256 let ref_time_consumed = before.0.saturating_sub(self.gas_left().ref_time());
257 self.engine_meter.charge_ref_time(ref_time_consumed)
258 }
259
260 pub fn gas_required(&self) -> Weight {
265 self.gas_limit.saturating_sub(self.gas_left_lowest())
266 }
267
268 pub fn gas_consumed(&self) -> Weight {
270 self.gas_limit.saturating_sub(self.gas_left)
271 }
272
273 pub fn gas_left(&self) -> Weight {
275 self.gas_left
276 }
277
278 pub fn into_dispatch_result<R, E>(
280 self,
281 result: Result<R, E>,
282 base_weight: Weight,
283 ) -> DispatchResultWithPostInfo
284 where
285 E: Into<ExecError>,
286 {
287 let post_info = PostDispatchInfo {
288 actual_weight: Some(self.gas_consumed().saturating_add(base_weight)),
289 pays_fee: Default::default(),
290 };
291
292 result
293 .map(|_| post_info)
294 .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error })
295 }
296
297 fn gas_left_lowest(&self) -> Weight {
298 self.gas_left_lowest.min(self.gas_left)
299 }
300
301 #[cfg(test)]
302 pub fn tokens(&self) -> &[ErasedToken] {
303 &self.tokens
304 }
305}
306
307#[cfg(test)]
308mod tests {
309 use super::{GasMeter, Token, Weight};
310 use crate::tests::Test;
311
312 macro_rules! match_tokens {
315 ($tokens_iter:ident,) => {
316 };
317 ($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
318 {
319 let next = ($tokens_iter).next().unwrap();
320 let pattern = $x;
321
322 let mut _pattern_typed_next_ref = &pattern;
334 _pattern_typed_next_ref = match next.token.downcast_ref() {
335 Some(p) => {
336 assert_eq!(p, &pattern);
337 p
338 }
339 None => {
340 panic!("expected type {} got {}", stringify!($x), next.description);
341 }
342 };
343 }
344
345 match_tokens!($tokens_iter, $($rest)*);
346 };
347 }
348
349 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
351 struct SimpleToken(u64);
352 impl Token<Test> for SimpleToken {
353 fn weight(&self) -> Weight {
354 Weight::from_parts(self.0, 0)
355 }
356 }
357
358 #[test]
359 fn it_works() {
360 let gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
361 assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0));
362 }
363
364 #[test]
365 fn tracing() {
366 let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
367 assert!(!gas_meter.charge(SimpleToken(1)).is_err());
368
369 let mut tokens = gas_meter.tokens().iter();
370 match_tokens!(tokens, SimpleToken(1),);
371 }
372
373 #[test]
375 fn refuse_to_execute_anything_if_zero() {
376 let mut gas_meter = GasMeter::<Test>::new(Weight::zero());
377 assert!(gas_meter.charge(SimpleToken(1)).is_err());
378 }
379
380 #[test]
382 fn overcharge_does_not_charge() {
383 let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(200, 0));
384
385 assert!(gas_meter.charge(SimpleToken(300)).is_err());
387
388 assert!(gas_meter.charge(SimpleToken(200)).is_ok());
390 }
391
392 #[test]
395 fn charge_exact_amount() {
396 let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(25, 0));
397 assert!(!gas_meter.charge(SimpleToken(25)).is_err());
398 }
399}