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