Skip to main content

javm_exec/
gas.rs

1//! Gas counter: a non-negative u64 representing remaining budget.
2//!
3//! The execution engine decrements the counter per instruction (or
4//! per ecall, etc.) and reports `ExitReason::OutOfGas` when it
5//! would go negative. The actual gas-per-instruction cost table
6//! lives at a higher layer (v3 spec: per-instruction debit happens
7//! against the active Instance's gas slot's meter; the engine just
8//! receives a single counter to decrement).
9
10/// Gas type: `u64` remaining budget.
11pub type Gas = u64;
12
13/// Sentinel returned by `GasCounter::charge` on exhaustion.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct OutOfGas;
16
17/// Mutable gas counter with structured charge / check semantics.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct GasCounter {
20    remaining: Gas,
21}
22
23impl GasCounter {
24    /// Construct with the given initial budget.
25    pub fn new(initial: Gas) -> Self {
26        Self { remaining: initial }
27    }
28
29    /// Current remaining gas.
30    pub fn remaining(&self) -> Gas {
31        self.remaining
32    }
33
34    /// Try to deduct `cost`. Returns `Ok(())` on success or
35    /// `Err(OutOfGas)` if the counter would go negative (caller
36    /// should produce `ExitReason::OutOfGas`).
37    #[inline(always)]
38    pub fn charge(&mut self, cost: Gas) -> Result<(), OutOfGas> {
39        match self.remaining.checked_sub(cost) {
40            Some(new) => {
41                self.remaining = new;
42                Ok(())
43            }
44            None => {
45                // Exhaust the counter so subsequent charges also fail.
46                self.remaining = 0;
47                Err(OutOfGas)
48            }
49        }
50    }
51
52    /// Set remaining gas explicitly (used by the higher layer's
53    /// SetGasMeter operation for top-ups).
54    pub fn set(&mut self, value: Gas) {
55        self.remaining = value;
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn new_remaining_matches_initial() {
65        assert_eq!(GasCounter::new(1000).remaining(), 1000);
66    }
67
68    #[test]
69    fn charge_within_budget_succeeds() {
70        let mut g = GasCounter::new(100);
71        assert!(g.charge(30).is_ok());
72        assert_eq!(g.remaining(), 70);
73        assert!(g.charge(70).is_ok());
74        assert_eq!(g.remaining(), 0);
75    }
76
77    #[test]
78    fn charge_over_budget_fails_and_exhausts() {
79        let mut g = GasCounter::new(50);
80        assert!(g.charge(100).is_err());
81        assert_eq!(g.remaining(), 0);
82        // Subsequent charges also fail.
83        assert!(g.charge(1).is_err());
84    }
85
86    #[test]
87    fn set_replaces_remaining() {
88        let mut g = GasCounter::new(0);
89        g.set(500);
90        assert_eq!(g.remaining(), 500);
91    }
92}