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