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}