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