1use crate::{exec::ExecError, weights::WeightInfo, Config, Error};
19use core::marker::PhantomData;
20use frame_support::{
21 dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo},
22 weights::Weight,
23 DefaultNoBound,
24};
25use sp_runtime::DispatchError;
26
27#[cfg(test)]
28use std::{any::Any, fmt::Debug};
29
30#[derive(Debug, PartialEq, Eq)]
31pub struct ChargedAmount(Weight);
32
33impl ChargedAmount {
34 pub fn amount(&self) -> Weight {
35 self.0
36 }
37}
38
39#[derive(DefaultNoBound)]
41struct EngineMeter<T: Config> {
42 fuel: u64,
43 _phantom: PhantomData<T>,
44}
45
46impl<T: Config> EngineMeter<T> {
47 fn new(limit: Weight) -> Self {
49 Self {
50 fuel: limit.ref_time().saturating_div(Self::ref_time_per_fuel()),
51 _phantom: PhantomData,
52 }
53 }
54
55 fn set_fuel(&mut self, fuel: u64) -> Weight {
58 let consumed = self.fuel.saturating_sub(fuel).saturating_mul(Self::ref_time_per_fuel());
59 self.fuel = fuel;
60 Weight::from_parts(consumed, 0)
61 }
62
63 fn charge_ref_time(&mut self, ref_time: u64) -> Result<Syncable, DispatchError> {
66 let amount = ref_time
67 .checked_div(Self::ref_time_per_fuel())
68 .ok_or(Error::<T>::InvalidSchedule)?;
69
70 self.fuel.checked_sub(amount).ok_or_else(|| Error::<T>::OutOfGas)?;
71 Ok(Syncable(self.fuel.try_into().map_err(|_| Error::<T>::OutOfGas)?))
72 }
73
74 fn ref_time_per_fuel() -> u64 {
76 let loop_iteration =
77 T::WeightInfo::instr(1).saturating_sub(T::WeightInfo::instr(0)).ref_time();
78 let empty_loop_iteration = T::WeightInfo::instr_empty_loop(1)
79 .saturating_sub(T::WeightInfo::instr_empty_loop(0))
80 .ref_time();
81 loop_iteration.saturating_sub(empty_loop_iteration)
82 }
83}
84
85#[must_use]
89pub struct RefTimeLeft(u64);
90
91#[must_use]
95pub struct Syncable(polkavm::Gas);
96
97impl From<Syncable> for polkavm::Gas {
98 fn from(from: Syncable) -> Self {
99 from.0
100 }
101}
102
103#[cfg(not(test))]
104pub trait TestAuxiliaries {}
105#[cfg(not(test))]
106impl<T> TestAuxiliaries for T {}
107
108#[cfg(test)]
109pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
110#[cfg(test)]
111impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
112
113pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
120 fn weight(&self) -> Weight;
129
130 fn influence_lowest_gas_limit(&self) -> bool {
132 true
133 }
134}
135
136#[cfg(test)]
138pub struct ErasedToken {
139 pub description: String,
140 pub token: Box<dyn Any>,
141}
142
143#[derive(DefaultNoBound)]
144pub struct GasMeter<T: Config> {
145 gas_limit: Weight,
146 gas_left: Weight,
148 gas_left_lowest: Weight,
150 engine_meter: EngineMeter<T>,
154 _phantom: PhantomData<T>,
155 #[cfg(test)]
156 tokens: Vec<ErasedToken>,
157}
158
159impl<T: Config> GasMeter<T> {
160 pub fn new(gas_limit: Weight) -> Self {
161 GasMeter {
162 gas_limit,
163 gas_left: gas_limit,
164 gas_left_lowest: gas_limit,
165 engine_meter: EngineMeter::new(gas_limit),
166 _phantom: PhantomData,
167 #[cfg(test)]
168 tokens: Vec::new(),
169 }
170 }
171
172 pub fn nested_take_all(&mut self) -> Self {
177 let gas_left = self.gas_left;
178 self.gas_left -= gas_left;
179 GasMeter::new(gas_left)
180 }
181
182 pub fn nested(&mut self, amount: Weight) -> Self {
184 let amount = amount.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 charge_evm_gas(&mut self, gas: u64) -> Result<(), DispatchError> {
226 let base_cost = T::WeightInfo::evm_opcode(1).saturating_sub(T::WeightInfo::evm_opcode(0));
227 self.gas_left = self
228 .gas_left
229 .checked_sub(&base_cost.saturating_mul(gas))
230 .ok_or_else(|| Error::<T>::OutOfGas)?;
231 Ok(())
232 }
233
234 pub fn adjust_gas<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
239 if token.influence_lowest_gas_limit() {
240 self.gas_left_lowest = self.gas_left_lowest();
241 }
242 let adjustment = charged_amount.0.saturating_sub(token.weight());
243 self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
244 }
245
246 pub fn sync_from_executor(
252 &mut self,
253 engine_fuel: polkavm::Gas,
254 ) -> Result<RefTimeLeft, DispatchError> {
255 let weight_consumed = self
256 .engine_meter
257 .set_fuel(engine_fuel.try_into().map_err(|_| Error::<T>::OutOfGas)?);
258 self.gas_left
259 .checked_reduce(weight_consumed)
260 .ok_or_else(|| Error::<T>::OutOfGas)?;
261 Ok(RefTimeLeft(self.gas_left.ref_time()))
262 }
263
264 pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result<Syncable, DispatchError> {
273 let ref_time_consumed = before.0.saturating_sub(self.gas_left().ref_time());
274 self.engine_meter.charge_ref_time(ref_time_consumed)
275 }
276
277 pub fn gas_required(&self) -> Weight {
282 self.gas_limit.saturating_sub(self.gas_left_lowest())
283 }
284
285 pub fn gas_consumed(&self) -> Weight {
287 self.gas_limit.saturating_sub(self.gas_left)
288 }
289
290 pub fn gas_left(&self) -> Weight {
292 self.gas_left
293 }
294
295 pub fn engine_fuel_left(&self) -> Result<polkavm::Gas, DispatchError> {
297 self.engine_meter.fuel.try_into().map_err(|_| <Error<T>>::OutOfGas.into())
298 }
299
300 pub fn into_dispatch_result<R, E>(
302 self,
303 result: Result<R, E>,
304 base_weight: Weight,
305 ) -> DispatchResultWithPostInfo
306 where
307 E: Into<ExecError>,
308 {
309 let post_info = PostDispatchInfo {
310 actual_weight: Some(self.gas_consumed().saturating_add(base_weight)),
311 pays_fee: Default::default(),
312 };
313
314 result
315 .map(|_| post_info)
316 .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error })
317 }
318
319 fn gas_left_lowest(&self) -> Weight {
320 self.gas_left_lowest.min(self.gas_left)
321 }
322
323 #[cfg(test)]
324 pub fn tokens(&self) -> &[ErasedToken] {
325 &self.tokens
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::{GasMeter, Token, Weight};
332 use crate::tests::Test;
333
334 macro_rules! match_tokens {
337 ($tokens_iter:ident,) => {
338 };
339 ($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
340 {
341 let next = ($tokens_iter).next().unwrap();
342 let pattern = $x;
343
344 let mut _pattern_typed_next_ref = &pattern;
356 _pattern_typed_next_ref = match next.token.downcast_ref() {
357 Some(p) => {
358 assert_eq!(p, &pattern);
359 p
360 }
361 None => {
362 panic!("expected type {} got {}", stringify!($x), next.description);
363 }
364 };
365 }
366
367 match_tokens!($tokens_iter, $($rest)*);
368 };
369 }
370
371 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
373 struct SimpleToken(u64);
374 impl Token<Test> for SimpleToken {
375 fn weight(&self) -> Weight {
376 Weight::from_parts(self.0, 0)
377 }
378 }
379
380 #[test]
381 fn it_works() {
382 let gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
383 assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0));
384 }
385
386 #[test]
387 fn tracing() {
388 let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
389 assert!(!gas_meter.charge(SimpleToken(1)).is_err());
390
391 let mut tokens = gas_meter.tokens().iter();
392 match_tokens!(tokens, SimpleToken(1),);
393 }
394
395 #[test]
397 fn refuse_to_execute_anything_if_zero() {
398 let mut gas_meter = GasMeter::<Test>::new(Weight::zero());
399 assert!(gas_meter.charge(SimpleToken(1)).is_err());
400 }
401
402 #[test]
407 fn nested_zero_gas_requested() {
408 let test_weight = 50000.into();
409 let mut gas_meter = GasMeter::<Test>::new(test_weight);
410 let gas_for_nested_call = gas_meter.nested(0.into());
411
412 assert_eq!(gas_meter.gas_left(), 50000.into());
413 assert_eq!(gas_for_nested_call.gas_left(), 0.into())
414 }
415
416 #[test]
417 fn nested_some_gas_requested() {
418 let test_weight = 50000.into();
419 let mut gas_meter = GasMeter::<Test>::new(test_weight);
420 let gas_for_nested_call = gas_meter.nested(10000.into());
421
422 assert_eq!(gas_meter.gas_left(), 40000.into());
423 assert_eq!(gas_for_nested_call.gas_left(), 10000.into())
424 }
425
426 #[test]
427 fn nested_all_gas_requested() {
428 let test_weight = Weight::from_parts(50000, 50000);
429 let mut gas_meter = GasMeter::<Test>::new(test_weight);
430 let gas_for_nested_call = gas_meter.nested(test_weight);
431
432 assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0));
433 assert_eq!(gas_for_nested_call.gas_left(), 50_000.into())
434 }
435
436 #[test]
437 fn nested_excess_gas_requested() {
438 let test_weight = Weight::from_parts(50000, 50000);
439 let mut gas_meter = GasMeter::<Test>::new(test_weight);
440 let gas_for_nested_call = gas_meter.nested(test_weight + 10000.into());
441
442 assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0));
443 assert_eq!(gas_for_nested_call.gas_left(), 50_000.into())
444 }
445
446 #[test]
448 fn overcharge_does_not_charge() {
449 let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(200, 0));
450
451 assert!(gas_meter.charge(SimpleToken(300)).is_err());
453
454 assert!(gas_meter.charge(SimpleToken(200)).is_ok());
456 }
457
458 #[test]
461 fn charge_exact_amount() {
462 let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(25, 0));
463 assert!(!gas_meter.charge(SimpleToken(25)).is_err());
464 }
465}