1use std::collections::BTreeMap;
6
7use move_binary_format::errors::{PartialVMError, PartialVMResult};
8use move_core_types::{
9 gas_algebra::{AbstractMemorySize, InternalGas},
10 vm_status::StatusCode,
11};
12use move_vm_profiler::GasProfiler;
13use once_cell::sync::Lazy;
14
15use crate::gas_model::units_types::{CostTable, Gas, GasCost};
16
17pub const VM_FLAT_FEE: Gas = Gas::new(8_000);
19
20pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
22
23pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
25
26pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
28
29pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
31
32pub const MIN_EXISTS_DATA_SIZE: AbstractMemorySize = AbstractMemorySize::new(100);
35
36pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
37
38pub static INITIAL_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(initial_cost_schedule_v1);
39
40#[derive(Debug)]
47pub struct GasStatus {
48 pub gas_model_version: u64,
49 cost_table: CostTable,
50 pub gas_left: InternalGas,
51 gas_price: u64,
52 initial_budget: InternalGas,
53 pub charge: bool,
54
55 stack_height_high_water_mark: u64,
57 stack_height_current: u64,
58 stack_height_next_tier_start: Option<u64>,
59 stack_height_current_tier_mult: u64,
60
61 stack_size_high_water_mark: u64,
63 stack_size_current: u64,
64 stack_size_next_tier_start: Option<u64>,
65 stack_size_current_tier_mult: u64,
66
67 instructions_executed: u64,
69 instructions_next_tier_start: Option<u64>,
70 instructions_current_tier_mult: u64,
71
72 pub profiler: Option<GasProfiler>,
73 pub num_native_calls: u64,
74}
75
76impl GasStatus {
77 pub fn new(cost_table: CostTable, budget: u64, gas_price: u64, gas_model_version: u64) -> Self {
83 assert!(gas_price > 0, "gas price cannot be 0");
84 let budget_in_unit = budget / gas_price;
85 let gas_left = Self::to_internal_units(budget_in_unit);
86
87 let (stack_height_current_tier_mult, stack_height_next_tier_start) =
88 cost_table.stack_height_tier(0);
89 let (stack_size_current_tier_mult, stack_size_next_tier_start) =
90 cost_table.stack_size_tier(0);
91 let (instructions_current_tier_mult, instructions_next_tier_start) =
92 cost_table.instruction_tier(0);
93 Self {
94 gas_model_version,
95 gas_left,
96 gas_price,
97 initial_budget: gas_left,
98 cost_table,
99 charge: true,
100 stack_height_high_water_mark: 0,
101 stack_height_current: 0,
102 stack_size_high_water_mark: 0,
103 stack_size_current: 0,
104 instructions_executed: 0,
105 stack_height_current_tier_mult,
106 stack_size_current_tier_mult,
107 instructions_current_tier_mult,
108 stack_height_next_tier_start,
109 stack_size_next_tier_start,
110 instructions_next_tier_start,
111 profiler: None,
112 num_native_calls: 0,
113 }
114 }
115
116 pub fn new_unmetered() -> Self {
121 Self {
122 gas_model_version: 1,
123 gas_left: InternalGas::new(0),
124 gas_price: 1,
125 initial_budget: InternalGas::new(0),
126 cost_table: ZERO_COST_SCHEDULE.clone(),
127 charge: false,
128 stack_height_high_water_mark: 0,
129 stack_height_current: 0,
130 stack_size_high_water_mark: 0,
131 stack_size_current: 0,
132 instructions_executed: 0,
133 stack_height_current_tier_mult: 0,
134 stack_size_current_tier_mult: 0,
135 instructions_current_tier_mult: 0,
136 stack_height_next_tier_start: None,
137 stack_size_next_tier_start: None,
138 instructions_next_tier_start: None,
139 profiler: None,
140 num_native_calls: 0,
141 }
142 }
143
144 const INTERNAL_UNIT_MULTIPLIER: u64 = 1000;
145
146 fn to_internal_units(val: u64) -> InternalGas {
147 InternalGas::new(val * Self::INTERNAL_UNIT_MULTIPLIER)
148 }
149
150 #[expect(dead_code)]
151 fn to_nanos(&self, val: InternalGas) -> u64 {
152 let gas: Gas = InternalGas::to_unit_round_down(val);
153 u64::from(gas) * self.gas_price
154 }
155
156 pub fn push_stack(&mut self, pushes: u64) -> PartialVMResult<()> {
157 match self.stack_height_current.checked_add(pushes) {
158 None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
160 Some(new_height) => {
161 if new_height > self.stack_height_high_water_mark {
162 self.stack_height_high_water_mark = new_height;
163 }
164 self.stack_height_current = new_height;
165 }
166 }
167
168 if let Some(stack_height_tier_next) = self.stack_height_next_tier_start {
169 if self.stack_height_current > stack_height_tier_next {
170 let (next_mul, next_tier) =
171 self.cost_table.stack_height_tier(self.stack_height_current);
172 self.stack_height_current_tier_mult = next_mul;
173 self.stack_height_next_tier_start = next_tier;
174 }
175 }
176
177 Ok(())
178 }
179
180 pub fn pop_stack(&mut self, pops: u64) {
181 self.stack_height_current = self.stack_height_current.saturating_sub(pops);
182 }
183
184 pub fn increase_instruction_count(&mut self, amount: u64) -> PartialVMResult<()> {
185 match self.instructions_executed.checked_add(amount) {
186 None => return Err(PartialVMError::new(StatusCode::PC_OVERFLOW)),
187 Some(new_pc) => {
188 self.instructions_executed = new_pc;
189 }
190 }
191
192 if let Some(instr_tier_next) = self.instructions_next_tier_start {
193 if self.instructions_executed > instr_tier_next {
194 let (instr_cost, next_tier) =
195 self.cost_table.instruction_tier(self.instructions_executed);
196 self.instructions_current_tier_mult = instr_cost;
197 self.instructions_next_tier_start = next_tier;
198 }
199 }
200
201 Ok(())
202 }
203
204 pub fn increase_stack_size(&mut self, size_amount: u64) -> PartialVMResult<()> {
205 match self.stack_size_current.checked_add(size_amount) {
206 None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
207 Some(new_size) => {
208 if new_size > self.stack_size_high_water_mark {
209 self.stack_size_high_water_mark = new_size;
210 }
211 self.stack_size_current = new_size;
212 }
213 }
214
215 if let Some(stack_size_tier_next) = self.stack_size_next_tier_start {
216 if self.stack_size_current > stack_size_tier_next {
217 let (next_mul, next_tier) =
218 self.cost_table.stack_size_tier(self.stack_size_current);
219 self.stack_size_current_tier_mult = next_mul;
220 self.stack_size_next_tier_start = next_tier;
221 }
222 }
223
224 Ok(())
225 }
226
227 pub fn decrease_stack_size(&mut self, size_amount: u64) {
228 let new_size = self.stack_size_current.saturating_sub(size_amount);
229 if new_size > self.stack_size_high_water_mark {
230 self.stack_size_high_water_mark = new_size;
231 }
232 self.stack_size_current = new_size;
233 }
234
235 pub fn charge(
238 &mut self,
239 num_instructions: u64,
240 pushes: u64,
241 pops: u64,
242 incr_size: u64,
243 _decr_size: u64,
244 ) -> PartialVMResult<()> {
245 self.push_stack(pushes)?;
246 self.increase_instruction_count(num_instructions)?;
247 self.increase_stack_size(incr_size)?;
248
249 self.deduct_gas(
250 GasCost::new(
251 self.instructions_current_tier_mult
252 .checked_mul(num_instructions)
253 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
254 self.stack_size_current_tier_mult
255 .checked_mul(incr_size)
256 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
257 self.stack_height_current_tier_mult
258 .checked_mul(pushes)
259 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
260 )
261 .total_internal(),
262 )?;
263
264 self.pop_stack(pops);
266 Ok(())
267 }
268
269 pub fn cost_table(&self) -> &CostTable {
271 &self.cost_table
272 }
273
274 pub fn remaining_gas(&self) -> Gas {
276 self.gas_left.to_unit_round_down()
277 }
278
279 pub fn deduct_gas(&mut self, amount: InternalGas) -> PartialVMResult<()> {
281 if !self.charge {
282 return Ok(());
283 }
284
285 match self.gas_left.checked_sub(amount) {
286 Some(gas_left) => {
287 self.gas_left = gas_left;
288 Ok(())
289 }
290 None => {
291 self.gas_left = InternalGas::new(0);
292 Err(PartialVMError::new(StatusCode::OUT_OF_GAS))
293 }
294 }
295 }
296
297 pub fn record_native_call(&mut self) {
298 self.num_native_calls = self.num_native_calls.saturating_add(1);
299 }
300
301 fn deduct_units(&mut self, amount: u64) -> PartialVMResult<()> {
303 self.deduct_gas(InternalGas::new(amount))
304 }
305
306 pub fn set_metering(&mut self, enabled: bool) {
307 self.charge = enabled
308 }
309
310 pub fn gas_used_pre_gas_price(&self) -> u64 {
313 let gas: Gas = match self.initial_budget.checked_sub(self.gas_left) {
314 Some(val) => InternalGas::to_unit_round_down(val),
315 None => InternalGas::to_unit_round_down(self.initial_budget),
316 };
317 u64::from(gas)
318 }
319
320 pub fn charge_bytes(&mut self, size: usize, cost_per_byte: u64) -> PartialVMResult<()> {
324 let computation_cost = size as u64 * cost_per_byte;
325 self.deduct_units(computation_cost)
326 }
327
328 pub fn gas_price(&self) -> u64 {
329 self.gas_price
330 }
331
332 pub fn stack_height_high_water_mark(&self) -> u64 {
333 self.stack_height_high_water_mark
334 }
335
336 pub fn stack_size_high_water_mark(&self) -> u64 {
337 self.stack_size_high_water_mark
338 }
339
340 pub fn instructions_executed(&self) -> u64 {
341 self.instructions_executed
342 }
343}
344
345pub fn zero_cost_schedule() -> CostTable {
346 let mut zero_tier = BTreeMap::new();
347 zero_tier.insert(0, 0);
348 CostTable {
349 instruction_tiers: zero_tier.clone(),
350 stack_size_tiers: zero_tier.clone(),
351 stack_height_tiers: zero_tier,
352 }
353}
354
355pub fn unit_cost_schedule() -> CostTable {
356 let mut unit_tier = BTreeMap::new();
357 unit_tier.insert(0, 1);
358 CostTable {
359 instruction_tiers: unit_tier.clone(),
360 stack_size_tiers: unit_tier.clone(),
361 stack_height_tiers: unit_tier,
362 }
363}
364
365pub fn initial_cost_schedule_v1() -> CostTable {
366 let instruction_tiers: BTreeMap<u64, u64> = vec![
367 (0, 1),
368 (20_000, 2),
369 (50_000, 10),
370 (100_000, 50),
371 (200_000, 100),
372 (10_000_000, 1000),
373 ]
374 .into_iter()
375 .collect();
376
377 let stack_height_tiers: BTreeMap<u64, u64> =
378 vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
379
380 let stack_size_tiers: BTreeMap<u64, u64> = vec![
381 (0, 1),
382 (100_000, 2), (500_000, 5), (1_000_000, 100), (100_000_000, 1000), ]
387 .into_iter()
388 .collect();
389
390 CostTable {
391 instruction_tiers,
392 stack_size_tiers,
393 stack_height_tiers,
394 }
395}
396
397pub fn initial_cost_schedule_for_unit_tests() -> move_vm_test_utils::gas_schedule::CostTable {
403 let table = initial_cost_schedule_v1();
404 move_vm_test_utils::gas_schedule::CostTable {
405 instruction_tiers: table.instruction_tiers,
406 stack_height_tiers: table.stack_height_tiers,
407 stack_size_tiers: table.stack_size_tiers,
408 }
409}