Skip to main content

gear_core/
gas.rs

1// Copyright (C) Gear Technologies Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3
4//! Gas module.
5
6use crate::{costs::CostToken, reservation::UnreservedReimbursement};
7use enum_iterator::Sequence;
8use scale_decode::DecodeAsType;
9use scale_encode::EncodeAsType;
10use scale_info::scale::{Decode, Encode};
11
12/// The id of the gas lock.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Sequence)]
14#[repr(u8)]
15pub enum LockId {
16    /// The gas lock is provided by the mailbox.
17    Mailbox,
18    /// The gas lock is provided by the waitlist.
19    Waitlist,
20    /// The gas lock is provided by reservation.
21    Reservation,
22    /// The gas lock is provided by dispatch stash.
23    DispatchStash,
24}
25
26/// The result of charging gas.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum ChargeResult {
29    /// There was enough gas and it has been charged.
30    Enough,
31    /// There was not enough gas.
32    NotEnough,
33}
34
35impl ChargeResult {
36    /// Checks whether there was enough gas to charge.
37    pub const fn is_enough(&self) -> bool {
38        matches!(self, Self::Enough)
39    }
40
41    /// Checks whether there was not enough gas to charge.
42    pub const fn is_not_enough(self) -> bool {
43        matches!(self, Self::NotEnough)
44    }
45}
46
47/// Gas counter with some predefined maximum gas.
48///
49/// `Copy` and `Clone` traits aren't implemented for the type (however could be)
50/// in order to make the data only moveable, preventing explicit and implicit copying.
51#[derive(Debug)]
52pub struct GasCounter {
53    left: u64,
54    burned: u64,
55}
56
57impl GasCounter {
58    /// New limited gas counter with initial gas to spend.
59    pub fn new(initial_amount: u64) -> Self {
60        Self {
61            left: initial_amount,
62            burned: 0,
63        }
64    }
65
66    /// Account for used gas.
67    ///
68    /// If there is no enough gas, then makes saturating charge and returns `NotEnough`.
69    /// Else charges gas and returns `Enough`.
70    pub fn charge<T: Into<u64> + Copy>(&mut self, amount: T) -> ChargeResult {
71        if let Some(new_left) = self.left.checked_sub(amount.into()) {
72            self.left = new_left;
73            self.burned += amount.into();
74            ChargeResult::Enough
75        } else {
76            self.burned += self.left;
77            self.left = 0;
78            ChargeResult::NotEnough
79        }
80    }
81
82    /// Account for used gas.
83    ///
84    /// If there is no enough gas, then does nothing and returns `ChargeResult::NotEnough`.
85    /// Else charges gas and returns `ChargeResult::Enough`.
86    pub fn charge_if_enough<T: Into<u64> + Copy>(&mut self, amount: T) -> ChargeResult {
87        match self.left.checked_sub(amount.into()) {
88            None => ChargeResult::NotEnough,
89            Some(new_left) => {
90                self.left = new_left;
91                self.burned += amount.into();
92
93                ChargeResult::Enough
94            }
95        }
96    }
97
98    /// Increase left gas by `amount`.
99    ///
100    /// Called when gas unreservation is occurred.
101    /// We don't decrease `burn` counter because `GasTree` manipulation is handled by separated function
102    pub fn increase(&mut self, amount: u64, _token: UnreservedReimbursement) -> bool {
103        match self.left.checked_add(amount) {
104            None => false,
105            Some(new_left) => {
106                self.left = new_left;
107                true
108            }
109        }
110    }
111
112    /// Reduce gas by `amount`.
113    ///
114    /// Called when message is sent to another program, so the gas `amount` is sent to
115    /// receiving program.
116    /// Or called when gas reservation is occurred.
117    ///
118    /// In case of gas reservation:
119    /// We don't increase `burn` counter because `GasTree` manipulation is handled by separated function
120    pub fn reduce(&mut self, amount: u64) -> ChargeResult {
121        match self.left.checked_sub(amount) {
122            None => ChargeResult::NotEnough,
123            Some(new_left) => {
124                self.left = new_left;
125
126                ChargeResult::Enough
127            }
128        }
129    }
130
131    /// Report how much gas is left.
132    pub fn left(&self) -> u64 {
133        self.left
134    }
135
136    /// Report how much gas is burned.
137    pub fn burned(&self) -> u64 {
138        self.burned
139    }
140
141    /// Get gas amount.
142    pub fn to_amount(&self) -> GasAmount {
143        GasAmount {
144            left: self.left,
145            burned: self.burned,
146        }
147    }
148
149    /// Clone the counter
150    ///
151    /// # Safety
152    ///
153    /// Use only when it's absolutely necessary to clone the counter i.e atomic implementation of `Ext`.
154    pub unsafe fn clone(&self) -> Self {
155        Self {
156            left: self.left,
157            burned: self.burned,
158        }
159    }
160}
161
162/// Read-only representation of consumed `GasCounter`.
163///
164/// `Copy` trait isn't implemented for the type (however could be)
165/// in order to make the data only moveable, preventing implicit/explicit copying.
166#[derive(Debug, Clone)]
167pub struct GasAmount {
168    left: u64,
169    burned: u64,
170}
171
172impl GasAmount {
173    /// Report how much gas were left.
174    pub fn left(&self) -> u64 {
175        self.left
176    }
177
178    /// Report how much gas were burned.
179    pub fn burned(&self) -> u64 {
180        self.burned
181    }
182}
183
184/// Value counter with some predefined maximum value.
185#[derive(Debug)]
186pub struct ValueCounter(u128);
187
188impl ValueCounter {
189    /// New limited value counter with initial value to spend.
190    pub fn new(initial_amount: u128) -> Self {
191        Self(initial_amount)
192    }
193
194    /// Reduce value by `amount`.
195    ///
196    /// Called when message is sent to another program, so the value `amount` is sent to
197    /// receiving program.
198    pub fn reduce(&mut self, amount: u128) -> ChargeResult {
199        match self.0.checked_sub(amount) {
200            None => ChargeResult::NotEnough,
201            Some(new_left) => {
202                self.0 = new_left;
203
204                ChargeResult::Enough
205            }
206        }
207    }
208
209    /// Report how much value is left.
210    pub fn left(&self) -> u128 {
211        self.0
212    }
213
214    /// Clone the counter
215    ///
216    /// # Safety
217    ///
218    /// Use only when it's absolutely necessary to clone the counter i.e atomic implementation of `Ext`.
219    pub unsafe fn clone(&self) -> Self {
220        Self(self.0)
221    }
222}
223
224/// Gas allowance counter with some predefined maximum value.
225#[derive(Clone, Debug, Encode, Decode)]
226pub struct GasAllowanceCounter(u128);
227
228impl GasAllowanceCounter {
229    /// New limited gas allowance counter with initial value to spend.
230    pub fn new(initial_amount: u64) -> Self {
231        Self(initial_amount as u128)
232    }
233
234    /// Report how much gas allowance is left.
235    pub fn left(&self) -> u64 {
236        self.0 as u64
237    }
238
239    /// Account for used gas allowance.
240    ///
241    /// If there is no enough gas, then makes saturating charge and returns `NotEnough`.
242    /// Else charges gas and returns `Enough`.
243    pub fn charge<T: Into<u64>>(&mut self, amount: T) -> ChargeResult {
244        if let Some(new_left) = self.0.checked_sub(Into::<u64>::into(amount) as u128) {
245            self.0 = new_left;
246            ChargeResult::Enough
247        } else {
248            self.0 = 0;
249            ChargeResult::NotEnough
250        }
251    }
252
253    /// Account for used gas allowance.
254    ///
255    /// If there is no enough gas, then does nothing and returns `ChargeResult::NotEnough`.
256    /// Else charges gas and returns `ChargeResult::Enough`.
257    pub fn charge_if_enough<T: Into<u64>>(&mut self, amount: T) -> ChargeResult {
258        if let Some(new_left) = self.0.checked_sub(Into::<u64>::into(amount) as u128) {
259            self.0 = new_left;
260            ChargeResult::Enough
261        } else {
262            ChargeResult::NotEnough
263        }
264    }
265
266    /// Clone the counter
267    ///
268    /// # Safety
269    ///
270    /// Use only when it's absolutely necessary to clone the counter i.e atomic implementation of `Ext`.
271    pub unsafe fn clone(&self) -> Self {
272        Self(self.0)
273    }
274}
275
276/// Charging error
277#[derive(Debug, Clone, Eq, PartialEq, derive_more::Display)]
278pub enum ChargeError {
279    /// An error occurs in attempt to charge more gas than available during execution.
280    #[display("Not enough gas to continue execution")]
281    GasLimitExceeded,
282    /// Gas allowance exceeded
283    #[display("Gas allowance exceeded")]
284    GasAllowanceExceeded,
285}
286
287/// Counters owner can change gas limit and allowance counters.
288pub trait CountersOwner {
289    /// Charge for runtime api call.
290    fn charge_gas_for_token(&mut self, token: CostToken) -> Result<(), ChargeError>;
291    /// Charge gas if enough, else just returns error.
292    fn charge_gas_if_enough(&mut self, amount: u64) -> Result<(), ChargeError>;
293    /// Returns gas limit and gas allowance left.
294    fn gas_left(&self) -> GasLeft;
295    /// Currently set gas counter type.
296    fn current_counter_type(&self) -> CounterType;
297    /// Decreases gas left by fetched single numeric of actual counter.
298    fn decrease_current_counter_to(&mut self, amount: u64);
299    /// Returns minimal amount of gas counters and set the type of current counter.
300    fn define_current_counter(&mut self) -> u64;
301    /// Returns value of gas counter currently set.
302    fn current_counter_value(&self) -> u64 {
303        let GasLeft { gas, allowance } = self.gas_left();
304        match self.current_counter_type() {
305            CounterType::GasLimit => gas,
306            CounterType::GasAllowance => allowance,
307        }
308    }
309}
310
311/// Enum representing current type of gas counter.
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, EncodeAsType, Decode, DecodeAsType)]
313pub enum CounterType {
314    /// Gas limit counter.
315    GasLimit,
316    /// Gas allowance counter.
317    GasAllowance,
318}
319
320/// Gas limit and gas allowance left.
321#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, EncodeAsType, Decode, DecodeAsType)]
322pub struct GasLeft {
323    /// Left gas from gas counter.
324    pub gas: u64,
325    /// Left gas from allowance counter.
326    pub allowance: u64,
327}
328
329impl From<(u64, u64)> for GasLeft {
330    fn from((gas, allowance): (u64, u64)) -> Self {
331        Self { gas, allowance }
332    }
333}
334
335impl From<(i64, i64)> for GasLeft {
336    fn from((gas, allowance): (i64, i64)) -> Self {
337        (gas as u64, allowance as u64).into()
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::GasCounter;
344    use crate::gas::GasAllowanceCounter;
345
346    #[test]
347    /// Test that `GasCounter` object returns `Enough` and decreases the remaining count
348    /// on calling `charge(...)` when the remaining gas exceeds the required value,
349    /// otherwise returns NotEnough
350    fn limited_gas_counter_charging() {
351        let mut counter = GasCounter::new(200);
352
353        let result = counter.charge_if_enough(100u64);
354
355        assert!(result.is_enough());
356        assert_eq!(counter.left(), 100);
357
358        let result = counter.charge_if_enough(101u64);
359
360        assert!(result.is_not_enough());
361        assert_eq!(counter.left(), 100);
362    }
363
364    #[test]
365    fn charge_fails() {
366        let mut counter = GasCounter::new(100);
367        assert!(counter.charge_if_enough(200u64).is_not_enough());
368    }
369
370    #[test]
371    fn charge_token_fails() {
372        let mut counter = GasCounter::new(10);
373        assert!(counter.charge(1000u64).is_not_enough());
374    }
375
376    #[test]
377    fn charge_allowance_token_fails() {
378        let mut counter = GasAllowanceCounter::new(10);
379        assert!(counter.charge(1000u64).is_not_enough());
380    }
381}