Skip to main content

vm/vm/
fuel.rs

1use super::*;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq)]
4pub struct FuelCheckpoint {
5    remaining: Option<u64>,
6    check_interval: u32,
7    ops_until_check: u32,
8}
9
10impl FuelCheckpoint {
11    pub fn fuel(&self) -> Option<u64> {
12        self.remaining
13    }
14
15    pub fn check_interval(&self) -> u32 {
16        self.check_interval
17    }
18}
19
20impl Vm {
21    pub(super) fn pending_fuel_debt(&self) -> u64 {
22        if !self.fuel_metering_enabled() {
23            return 0;
24        }
25        let executed_since_last_check = self
26            .fuel_check_interval
27            .saturating_sub(self.fuel_ops_until_check);
28        u64::from(executed_since_last_check)
29    }
30
31    #[inline(always)]
32    pub(in crate::vm) fn charge_fuel(&mut self, amount: u64) -> VmResult<()> {
33        if amount == 0 || !self.fuel_metering_enabled() {
34            return Ok(());
35        }
36
37        let remaining = self.fuel_remaining;
38        if remaining < amount {
39            return Err(VmError::OutOfFuel {
40                needed: amount,
41                remaining,
42            });
43        }
44        self.fuel_remaining = remaining - amount;
45        Ok(())
46    }
47
48    #[inline(always)]
49    pub(in crate::vm) fn charge_fuel_tick(&mut self) -> VmResult<()> {
50        if !self.fuel_metering_enabled() {
51            return Ok(());
52        }
53        if self.fuel_ops_until_check > 1 {
54            self.fuel_ops_until_check -= 1;
55            return Ok(());
56        }
57
58        let amount = u64::from(self.fuel_check_interval);
59        self.charge_fuel(amount)?;
60        self.fuel_ops_until_check = self.fuel_check_interval;
61        Ok(())
62    }
63
64    pub(super) fn clear_fuel_internal(&mut self) {
65        if self.fuel_metering_enabled() {
66            self.interrupt_mode = InterruptMode::None;
67        }
68        self.fuel_remaining = 0;
69        self.reset_interrupt_countdown();
70    }
71
72    pub fn set_fuel(&mut self, fuel: u64) {
73        self.clear_epoch_deadline_internal();
74        self.interrupt_mode = InterruptMode::Fuel;
75        self.fuel_remaining = fuel;
76        self.reset_interrupt_countdown();
77    }
78
79    pub fn clear_fuel(&mut self) {
80        self.clear_fuel_internal();
81    }
82
83    pub fn set_fuel_check_interval(&mut self, interval: u32) -> VmResult<()> {
84        if interval == 0 {
85            return Err(VmError::InvalidFuelCheckInterval(interval));
86        }
87        if self.epoch_interruption_enabled() {
88            return Err(self.interruption_mode_conflict(InterruptMode::Fuel));
89        }
90        self.fuel_check_interval = interval;
91        self.reset_interrupt_countdown();
92        Ok(())
93    }
94
95    pub fn fuel_check_interval(&self) -> u32 {
96        self.fuel_check_interval
97    }
98
99    pub fn get_fuel(&self) -> Option<u64> {
100        self.fuel_metering_enabled()
101            .then_some(self.fuel_remaining.saturating_sub(self.pending_fuel_debt()))
102    }
103
104    pub fn add_fuel(&mut self, fuel: u64) -> VmResult<()> {
105        if fuel == 0 {
106            return Ok(());
107        }
108        if self.epoch_interruption_enabled() {
109            return Err(self.interruption_mode_conflict(InterruptMode::Fuel));
110        }
111        self.fuel_remaining = if self.fuel_metering_enabled() {
112            self.fuel_remaining
113                .checked_add(fuel)
114                .ok_or(VmError::FuelOverflow)?
115        } else {
116            self.interrupt_mode = InterruptMode::Fuel;
117            self.reset_interrupt_countdown();
118            fuel
119        };
120        Ok(())
121    }
122
123    pub fn recharge_fuel(&mut self, fuel: u64) -> VmResult<()> {
124        self.add_fuel(fuel)
125    }
126
127    pub fn consume_fuel(&mut self, fuel: u64) -> VmResult<()> {
128        if self.epoch_interruption_enabled() {
129            return Err(self.interruption_mode_conflict(InterruptMode::Fuel));
130        }
131        self.charge_fuel(fuel)
132    }
133
134    pub fn consume_fuel_tick(&mut self) -> VmResult<()> {
135        if self.epoch_interruption_enabled() {
136            return Err(self.interruption_mode_conflict(InterruptMode::Fuel));
137        }
138        self.charge_fuel_tick()
139    }
140
141    pub fn fuel_checkpoint(&self) -> FuelCheckpoint {
142        FuelCheckpoint {
143            remaining: self.fuel_metering_enabled().then_some(self.fuel_remaining),
144            check_interval: self.fuel_check_interval(),
145            ops_until_check: self.fuel_ops_until_check,
146        }
147    }
148
149    pub fn checkpoint(&self) -> FuelCheckpoint {
150        self.fuel_checkpoint()
151    }
152
153    pub fn restore_fuel(&mut self, checkpoint: FuelCheckpoint) {
154        self.clear_epoch_deadline_internal();
155        self.interrupt_mode = if checkpoint.remaining.is_some() {
156            InterruptMode::Fuel
157        } else {
158            InterruptMode::None
159        };
160        self.fuel_remaining = checkpoint.remaining.unwrap_or(0);
161        self.fuel_check_interval = checkpoint.check_interval.max(1);
162        self.fuel_ops_until_check = checkpoint
163            .ops_until_check
164            .clamp(1, self.fuel_check_interval);
165    }
166
167    pub fn restore_checkpoint(&mut self, checkpoint: FuelCheckpoint) {
168        self.restore_fuel(checkpoint);
169    }
170}