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}