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}