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}