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}