gear_core/
gas.rs

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