sui_types/gas_model/
tables.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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
15/// VM flat fee
16pub const VM_FLAT_FEE: Gas = Gas::new(8_000);
17
18/// The size in bytes for a non-string or address constant on the stack
19pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
20
21/// The size in bytes for a reference on the stack
22pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
23
24/// The size of a struct in bytes
25pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
26
27/// The size of a vector (without its containing data) in bytes
28pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
29
30/// For exists checks on data that doesn't exists this is the multiplier that is used.
31pub const MIN_EXISTS_DATA_SIZE: AbstractMemorySize = AbstractMemorySize::new(100);
32
33pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
34
35/// The Move VM implementation of state for gas metering.
36///
37/// Initialize with a `CostTable` and the gas provided to the transaction.
38/// Provide all the proper guarantees about gas metering in the Move VM.
39///
40/// Every client must use an instance of this type to interact with the Move VM.
41#[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    // The current height of the operand stack, and the maximal height that it has reached.
52    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    // The current (abstract) size  of the operand stack and the maximal size that it has reached.
58    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    // The total number of bytecode instructions that have been executed in the transaction.
64    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    /// Initialize the gas state with metering enabled.
73    ///
74    /// Charge for every operation and fail when there is no more gas to pay for operations.
75    /// This is the instantiation that must be used when executing a user script.
76    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    /// Initialize the gas state with metering disabled.
109    ///
110    /// It should be used by clients in very specific cases and when executing system
111    /// code that does not have to charge the user.
112    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            // We should never hit this.
150            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        // Underflow is expected when charge_native still pops args (legacy
173        // double-pop). Otherwise — i.e., gas_model_version > 11 where the
174        // native-call double-pop fix is in effect — it indicates a real bug.
175        // debug_fatal! panics in debug and logs an error + bumps the
176        // system_invariant_violations metric in release.
177        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    /// Given: pushes + pops + increase + decrease in size for an instruction charge for the
235    /// execution of the instruction.
236    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    /// Return the `CostTable` behind this `GasStatus`.
268    pub fn cost_table(&self) -> &CostTable {
269        &self.cost_table
270    }
271
272    /// Return the gas left.
273    pub fn remaining_gas(&self) -> Gas {
274        self.gas_left.to_unit_round_down()
275    }
276
277    /// Charge a given amount of gas and fail if not enough gas units are left.
278    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    // Deduct the amount provided with no conversion, as if it was InternalGasUnit
300    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    // The amount of gas used, it does not include the multiplication for the gas price
309    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    // Charge the number of bytes with the cost per byte value
318    // As more bytes are read throughout the computation the cost per bytes is increased.
319    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),     // ~100K
538        (500_000, 5),     // ~500K
539        (1_000_000, 100), // ~1M
540    ]
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),        // ~100K
569        (500_000, 5),        // ~500K
570        (1_000_000, 100),    // ~1M
571        (100_000_000, 1000), // ~100M
572    ]
573    .into_iter()
574    .collect();
575
576    CostTable {
577        instruction_tiers,
578        stack_size_tiers,
579        stack_height_tiers,
580    }
581}