Skip to main content

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/// The size in bytes for a non-string or address constant on the stack
16pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
17
18/// The size in bytes for a reference on the stack
19pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
20
21/// The size of a struct in bytes
22pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
23
24/// The size of a vector (without its containing data) in bytes
25pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
26
27pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
28
29/// The Move VM implementation of state for gas metering.
30///
31/// Initialize with a `CostTable` and the gas provided to the transaction.
32/// Provide all the proper guarantees about gas metering in the Move VM.
33///
34/// Every client must use an instance of this type to interact with the Move VM.
35#[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    // The current height of the operand stack, and the maximal height that it has reached.
46    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    // The current (abstract) size  of the operand stack and the maximal size that it has reached.
52    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    // The total number of bytecode instructions that have been executed in the transaction.
58    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    /// Initialize the gas state with metering enabled.
67    ///
68    /// Charge for every operation and fail when there is no more gas to pay for operations.
69    /// This is the instantiation that must be used when executing a user script.
70    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    /// Initialize the gas state with metering disabled.
103    ///
104    /// It should be used by clients in very specific cases and when executing system
105    /// code that does not have to charge the user.
106    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            // We should never hit this.
138            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        // Underflow is expected when charge_native still pops args (legacy
161        // double-pop). Otherwise — i.e., gas_model_version > 11 where the
162        // native-call double-pop fix is in effect — it indicates a real bug.
163        // debug_fatal! panics in debug and logs an error + bumps the
164        // system_invariant_violations metric in release.
165        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    /// Given: pushes + pops + increase + decrease in size for an instruction charge for the
223    /// execution of the instruction.
224    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    /// Return the `CostTable` behind this `GasStatus`.
256    pub fn cost_table(&self) -> &CostTable {
257        &self.cost_table
258    }
259
260    /// Return the gas left.
261    pub fn remaining_gas(&self) -> Gas {
262        self.gas_left.to_unit_round_down()
263    }
264
265    /// Charge a given amount of gas and fail if not enough gas units are left.
266    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    // Deduct the amount provided with no conversion, as if it was InternalGasUnit
288    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    // The amount of gas used, it does not include the multiplication for the gas price
297    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    // Charge the number of bytes with the cost per byte value
306    // As more bytes are read throughout the computation the cost per bytes is increased.
307    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),     // ~100K
526        (500_000, 5),     // ~500K
527        (1_000_000, 100), // ~1M
528    ]
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),        // ~100K
557        (500_000, 5),        // ~500K
558        (1_000_000, 100),    // ~1M
559        (100_000_000, 1000), // ~100M
560    ]
561    .into_iter()
562    .collect();
563
564    CostTable {
565        instruction_tiers,
566        stack_size_tiers,
567        stack_height_tiers,
568    }
569}