1use super::gas_predicates::{charge_input_as_memory, legacy_charge_native_pops_args};
5use crate::gas_model::units_types::{CostTable, Gas, GasCost};
6use move_binary_format::errors::{PartialVMError, PartialVMResult};
7use mysten_common::debug_fatal;
8
9use move_core_types::gas_algebra::{AbstractMemorySize, InternalGas};
10
11use move_core_types::vm_status::StatusCode;
12use once_cell::sync::Lazy;
13use std::collections::BTreeMap;
14
15pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
17
18pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
20
21pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
23
24pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
26
27pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
28
29#[allow(dead_code)]
36#[derive(Debug)]
37pub struct GasStatus {
38 pub gas_model_version: u64,
39 cost_table: CostTable,
40 pub gas_left: InternalGas,
41 gas_price: u64,
42 initial_budget: InternalGas,
43 pub charge: bool,
44
45 stack_height_high_water_mark: u64,
47 stack_height_current: u64,
48 stack_height_next_tier_start: Option<u64>,
49 stack_height_current_tier_mult: u64,
50
51 stack_size_high_water_mark: u64,
53 stack_size_current: u64,
54 stack_size_next_tier_start: Option<u64>,
55 stack_size_current_tier_mult: u64,
56
57 instructions_executed: u64,
59 instructions_next_tier_start: Option<u64>,
60 instructions_current_tier_mult: u64,
61
62 pub num_native_calls: u64,
63}
64
65impl GasStatus {
66 pub fn new(cost_table: CostTable, budget: u64, gas_price: u64, gas_model_version: u64) -> Self {
71 assert!(gas_price > 0, "gas price cannot be 0");
72 let budget_in_unit = budget / gas_price;
73 let gas_left = Self::to_internal_units(budget_in_unit);
74 let (stack_height_current_tier_mult, stack_height_next_tier_start) =
75 cost_table.stack_height_tier(0);
76 let (stack_size_current_tier_mult, stack_size_next_tier_start) =
77 cost_table.stack_size_tier(0);
78 let (instructions_current_tier_mult, instructions_next_tier_start) =
79 cost_table.instruction_tier(0);
80 Self {
81 gas_model_version,
82 gas_left,
83 gas_price,
84 initial_budget: gas_left,
85 cost_table,
86 charge: true,
87 stack_height_high_water_mark: 0,
88 stack_height_current: 0,
89 stack_size_high_water_mark: 0,
90 stack_size_current: 0,
91 instructions_executed: 0,
92 stack_height_current_tier_mult,
93 stack_size_current_tier_mult,
94 instructions_current_tier_mult,
95 stack_height_next_tier_start,
96 stack_size_next_tier_start,
97 instructions_next_tier_start,
98 num_native_calls: 0,
99 }
100 }
101
102 pub fn new_unmetered() -> Self {
107 Self {
108 gas_model_version: 11,
109 gas_left: InternalGas::new(0),
110 gas_price: 1,
111 initial_budget: InternalGas::new(0),
112 cost_table: ZERO_COST_SCHEDULE.clone(),
113 charge: false,
114 stack_height_high_water_mark: 0,
115 stack_height_current: 0,
116 stack_size_high_water_mark: 0,
117 stack_size_current: 0,
118 instructions_executed: 0,
119 stack_height_current_tier_mult: 0,
120 stack_size_current_tier_mult: 0,
121 instructions_current_tier_mult: 0,
122 stack_height_next_tier_start: None,
123 stack_size_next_tier_start: None,
124 instructions_next_tier_start: None,
125 num_native_calls: 0,
126 }
127 }
128
129 const INTERNAL_UNIT_MULTIPLIER: u64 = 1000;
130
131 fn to_internal_units(val: u64) -> InternalGas {
132 InternalGas::new(val * Self::INTERNAL_UNIT_MULTIPLIER)
133 }
134
135 pub fn push_stack(&mut self, pushes: u64) -> PartialVMResult<()> {
136 match self.stack_height_current.checked_add(pushes) {
137 None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
139 Some(new_height) => {
140 if new_height > self.stack_height_high_water_mark {
141 self.stack_height_high_water_mark = new_height;
142 }
143 self.stack_height_current = new_height;
144 }
145 }
146
147 if let Some(stack_height_tier_next) = self.stack_height_next_tier_start
148 && self.stack_height_current > stack_height_tier_next
149 {
150 let (next_mul, next_tier) =
151 self.cost_table.stack_height_tier(self.stack_height_current);
152 self.stack_height_current_tier_mult = next_mul;
153 self.stack_height_next_tier_start = next_tier;
154 }
155
156 Ok(())
157 }
158
159 pub fn pop_stack(&mut self, pops: u64) {
160 self.stack_height_current = match self.stack_height_current.checked_sub(pops) {
166 Some(stack_height) => stack_height,
167 None if legacy_charge_native_pops_args(self.gas_model_version) => 0,
168 None => {
169 debug_fatal!(
170 "stack height underflow: current={}, pops={}. \
171 This indicates a double-pop or missing push in the gas meter.",
172 self.stack_height_current,
173 pops
174 );
175 0
176 }
177 };
178 }
179
180 pub fn increase_instruction_count(&mut self, amount: u64) -> PartialVMResult<()> {
181 match self.instructions_executed.checked_add(amount) {
182 None => return Err(PartialVMError::new(StatusCode::PC_OVERFLOW)),
183 Some(new_pc) => {
184 self.instructions_executed = new_pc;
185 }
186 }
187
188 if let Some(instr_tier_next) = self.instructions_next_tier_start
189 && self.instructions_executed > instr_tier_next
190 {
191 let (instr_cost, next_tier) =
192 self.cost_table.instruction_tier(self.instructions_executed);
193 self.instructions_current_tier_mult = instr_cost;
194 self.instructions_next_tier_start = next_tier;
195 }
196
197 Ok(())
198 }
199
200 pub fn increase_stack_size(&mut self, size_amount: u64) -> PartialVMResult<()> {
201 match self.stack_size_current.checked_add(size_amount) {
202 None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
203 Some(new_size) => {
204 if new_size > self.stack_size_high_water_mark {
205 self.stack_size_high_water_mark = new_size;
206 }
207 self.stack_size_current = new_size;
208 }
209 }
210
211 if let Some(stack_size_tier_next) = self.stack_size_next_tier_start
212 && self.stack_size_current > stack_size_tier_next
213 {
214 let (next_mul, next_tier) = self.cost_table.stack_size_tier(self.stack_size_current);
215 self.stack_size_current_tier_mult = next_mul;
216 self.stack_size_next_tier_start = next_tier;
217 }
218
219 Ok(())
220 }
221
222 pub fn charge(
225 &mut self,
226 num_instructions: u64,
227 pushes: u64,
228 pops: u64,
229 incr_size: u64,
230 _decr_size: u64,
231 ) -> PartialVMResult<()> {
232 self.push_stack(pushes)?;
233 self.increase_instruction_count(num_instructions)?;
234 self.increase_stack_size(incr_size)?;
235
236 self.deduct_gas(
237 GasCost::new(
238 self.instructions_current_tier_mult
239 .checked_mul(num_instructions)
240 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
241 self.stack_size_current_tier_mult
242 .checked_mul(incr_size)
243 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
244 self.stack_height_current_tier_mult
245 .checked_mul(pushes)
246 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
247 )
248 .total_internal(),
249 )?;
250
251 self.pop_stack(pops);
252 Ok(())
253 }
254
255 pub fn cost_table(&self) -> &CostTable {
257 &self.cost_table
258 }
259
260 pub fn remaining_gas(&self) -> Gas {
262 self.gas_left.to_unit_round_down()
263 }
264
265 pub fn deduct_gas(&mut self, amount: InternalGas) -> PartialVMResult<()> {
267 if !self.charge {
268 return Ok(());
269 }
270
271 match self.gas_left.checked_sub(amount) {
272 Some(gas_left) => {
273 self.gas_left = gas_left;
274 Ok(())
275 }
276 None => {
277 self.gas_left = InternalGas::new(0);
278 Err(PartialVMError::new(StatusCode::OUT_OF_GAS))
279 }
280 }
281 }
282
283 pub fn record_native_call(&mut self) {
284 self.num_native_calls = self.num_native_calls.saturating_add(1);
285 }
286
287 fn deduct_units(&mut self, amount: u64) -> PartialVMResult<()> {
289 self.deduct_gas(InternalGas::new(amount))
290 }
291
292 pub fn set_metering(&mut self, enabled: bool) {
293 self.charge = enabled
294 }
295
296 pub fn gas_used_pre_gas_price(&self) -> u64 {
298 let gas: Gas = match self.initial_budget.checked_sub(self.gas_left) {
299 Some(val) => InternalGas::to_unit_round_down(val),
300 None => InternalGas::to_unit_round_down(self.initial_budget),
301 };
302 u64::from(gas)
303 }
304
305 pub fn charge_bytes(&mut self, size: usize, cost_per_byte: u64) -> PartialVMResult<()> {
308 let computation_cost = if charge_input_as_memory(self.gas_model_version) {
309 self.increase_stack_size(size as u64)?;
310 self.stack_size_current_tier_mult * size as u64 * cost_per_byte
311 } else {
312 size as u64 * cost_per_byte
313 };
314 self.deduct_units(computation_cost)
315 }
316
317 pub fn gas_price(&self) -> u64 {
318 self.gas_price
319 }
320
321 pub fn stack_height_high_water_mark(&self) -> u64 {
322 self.stack_height_high_water_mark
323 }
324
325 pub fn stack_size_high_water_mark(&self) -> u64 {
326 self.stack_size_high_water_mark
327 }
328
329 pub fn instructions_executed(&self) -> u64 {
330 self.instructions_executed
331 }
332
333 pub fn stack_height_current(&self) -> u64 {
334 self.stack_height_current
335 }
336
337 pub fn stack_size_current(&self) -> u64 {
338 self.stack_size_current
339 }
340}
341
342pub fn zero_cost_schedule() -> CostTable {
343 let mut zero_tier = BTreeMap::new();
344 zero_tier.insert(0, 0);
345 CostTable {
346 instruction_tiers: zero_tier.clone(),
347 stack_size_tiers: zero_tier.clone(),
348 stack_height_tiers: zero_tier,
349 }
350}
351
352pub fn unit_cost_schedule() -> CostTable {
353 let mut unit_tier = BTreeMap::new();
354 unit_tier.insert(0, 1);
355 CostTable {
356 instruction_tiers: unit_tier.clone(),
357 stack_size_tiers: unit_tier.clone(),
358 stack_height_tiers: unit_tier,
359 }
360}
361
362pub fn initial_cost_schedule_v1() -> CostTable {
363 let instruction_tiers: BTreeMap<u64, u64> = vec![
364 (0, 1),
365 (3000, 2),
366 (6000, 3),
367 (8000, 5),
368 (9000, 9),
369 (9500, 16),
370 (10000, 29),
371 (10500, 50),
372 ]
373 .into_iter()
374 .collect();
375
376 let stack_height_tiers: BTreeMap<u64, u64> = vec![
377 (0, 1),
378 (400, 2),
379 (800, 3),
380 (1200, 5),
381 (1500, 9),
382 (1800, 16),
383 (2000, 29),
384 (2200, 50),
385 ]
386 .into_iter()
387 .collect();
388
389 let stack_size_tiers: BTreeMap<u64, u64> = vec![
390 (0, 1),
391 (2000, 2),
392 (5000, 3),
393 (8000, 5),
394 (10000, 9),
395 (11000, 16),
396 (11500, 50),
397 ]
398 .into_iter()
399 .collect();
400
401 CostTable {
402 instruction_tiers,
403 stack_size_tiers,
404 stack_height_tiers,
405 }
406}
407
408pub fn initial_cost_schedule_v2() -> CostTable {
409 let instruction_tiers: BTreeMap<u64, u64> = vec![
410 (0, 1),
411 (3000, 2),
412 (6000, 3),
413 (8000, 5),
414 (9000, 9),
415 (9500, 16),
416 (10000, 29),
417 (10500, 50),
418 (12000, 150),
419 (15000, 250),
420 ]
421 .into_iter()
422 .collect();
423
424 let stack_height_tiers: BTreeMap<u64, u64> = vec![
425 (0, 1),
426 (400, 2),
427 (800, 3),
428 (1200, 5),
429 (1500, 9),
430 (1800, 16),
431 (2000, 29),
432 (2200, 50),
433 (3000, 150),
434 (5000, 250),
435 ]
436 .into_iter()
437 .collect();
438
439 let stack_size_tiers: BTreeMap<u64, u64> = vec![
440 (0, 1),
441 (2000, 2),
442 (5000, 3),
443 (8000, 5),
444 (10000, 9),
445 (11000, 16),
446 (11500, 50),
447 (15000, 150),
448 (20000, 250),
449 ]
450 .into_iter()
451 .collect();
452
453 CostTable {
454 instruction_tiers,
455 stack_size_tiers,
456 stack_height_tiers,
457 }
458}
459
460pub fn initial_cost_schedule_v3() -> CostTable {
461 let instruction_tiers: BTreeMap<u64, u64> = vec![
462 (0, 1),
463 (3000, 2),
464 (6000, 3),
465 (8000, 5),
466 (9000, 9),
467 (9500, 16),
468 (10000, 29),
469 (10500, 50),
470 (15000, 100),
471 ]
472 .into_iter()
473 .collect();
474
475 let stack_height_tiers: BTreeMap<u64, u64> = vec![
476 (0, 1),
477 (400, 2),
478 (800, 3),
479 (1200, 5),
480 (1500, 9),
481 (1800, 16),
482 (2000, 29),
483 (2200, 50),
484 (5000, 100),
485 ]
486 .into_iter()
487 .collect();
488
489 let stack_size_tiers: BTreeMap<u64, u64> = vec![
490 (0, 1),
491 (2000, 2),
492 (5000, 3),
493 (8000, 5),
494 (10000, 9),
495 (11000, 16),
496 (11500, 50),
497 (20000, 100),
498 ]
499 .into_iter()
500 .collect();
501
502 CostTable {
503 instruction_tiers,
504 stack_size_tiers,
505 stack_height_tiers,
506 }
507}
508
509pub fn initial_cost_schedule_v4() -> CostTable {
510 let instruction_tiers: BTreeMap<u64, u64> = vec![
511 (0, 1),
512 (20_000, 2),
513 (50_000, 10),
514 (100_000, 50),
515 (200_000, 100),
516 ]
517 .into_iter()
518 .collect();
519
520 let stack_height_tiers: BTreeMap<u64, u64> =
521 vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
522
523 let stack_size_tiers: BTreeMap<u64, u64> = vec![
524 (0, 1),
525 (100_000, 2), (500_000, 5), (1_000_000, 100), ]
529 .into_iter()
530 .collect();
531
532 CostTable {
533 instruction_tiers,
534 stack_size_tiers,
535 stack_height_tiers,
536 }
537}
538
539pub fn initial_cost_schedule_v5() -> CostTable {
540 let instruction_tiers: BTreeMap<u64, u64> = vec![
541 (0, 1),
542 (20_000, 2),
543 (50_000, 10),
544 (100_000, 50),
545 (200_000, 100),
546 (10_000_000, 1000),
547 ]
548 .into_iter()
549 .collect();
550
551 let stack_height_tiers: BTreeMap<u64, u64> =
552 vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
553
554 let stack_size_tiers: BTreeMap<u64, u64> = vec![
555 (0, 1),
556 (100_000, 2), (500_000, 5), (1_000_000, 100), (100_000_000, 1000), ]
561 .into_iter()
562 .collect();
563
564 CostTable {
565 instruction_tiers,
566 stack_size_tiers,
567 stack_height_tiers,
568 }
569}