sui_types/gas_model/
tables.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::collections::BTreeMap;
5
6use move_binary_format::errors::{PartialVMError, PartialVMResult};
7
8use move_core_types::gas_algebra::{AbstractMemorySize, InternalGas};
9
10use move_core_types::vm_status::StatusCode;
11use once_cell::sync::Lazy;
12
13use crate::gas_model::units_types::{CostTable, Gas, GasCost};
14
15use super::gas_predicates::charge_input_as_memory;
16
17/// VM flat fee
18pub const VM_FLAT_FEE: Gas = Gas::new(8_000);
19
20/// The size in bytes for a non-string or address constant on the stack
21pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
22
23/// The size in bytes for a reference on the stack
24pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
25
26/// The size of a struct in bytes
27pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
28
29/// The size of a vector (without its containing data) in bytes
30pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
31
32/// For exists checks on data that doesn't exists this is the multiplier that is used.
33pub const MIN_EXISTS_DATA_SIZE: AbstractMemorySize = AbstractMemorySize::new(100);
34
35pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
36
37pub static INITIAL_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(initial_cost_schedule_v1);
38
39/// The Move VM implementation of state for gas metering.
40///
41/// Initialize with a `CostTable` and the gas provided to the transaction.
42/// Provide all the proper guarantees about gas metering in the Move VM.
43///
44/// Every client must use an instance of this type to interact with the Move VM.
45#[allow(dead_code)]
46#[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    // The current height of the operand stack, and the maximal height that it has reached.
56    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    // The current (abstract) size  of the operand stack and the maximal size that it has reached.
62    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    // The total number of bytecode instructions that have been executed in the transaction.
68    instructions_executed: u64,
69    instructions_next_tier_start: Option<u64>,
70    instructions_current_tier_mult: u64,
71
72    pub num_native_calls: u64,
73}
74
75impl GasStatus {
76    /// Initialize the gas state with metering enabled.
77    ///
78    /// Charge for every operation and fail when there is no more gas to pay for operations.
79    /// This is the instantiation that must be used when executing a user script.
80    pub fn new(cost_table: CostTable, budget: u64, gas_price: u64, gas_model_version: u64) -> Self {
81        assert!(gas_price > 0, "gas price cannot be 0");
82        let budget_in_unit = budget / gas_price;
83        let gas_left = Self::to_internal_units(budget_in_unit);
84        let (stack_height_current_tier_mult, stack_height_next_tier_start) =
85            cost_table.stack_height_tier(0);
86        let (stack_size_current_tier_mult, stack_size_next_tier_start) =
87            cost_table.stack_size_tier(0);
88        let (instructions_current_tier_mult, instructions_next_tier_start) =
89            cost_table.instruction_tier(0);
90        Self {
91            gas_model_version,
92            gas_left,
93            gas_price,
94            initial_budget: gas_left,
95            cost_table,
96            charge: true,
97            stack_height_high_water_mark: 0,
98            stack_height_current: 0,
99            stack_size_high_water_mark: 0,
100            stack_size_current: 0,
101            instructions_executed: 0,
102            stack_height_current_tier_mult,
103            stack_size_current_tier_mult,
104            instructions_current_tier_mult,
105            stack_height_next_tier_start,
106            stack_size_next_tier_start,
107            instructions_next_tier_start,
108            num_native_calls: 0,
109        }
110    }
111
112    /// Initialize the gas state with metering disabled.
113    ///
114    /// It should be used by clients in very specific cases and when executing system
115    /// code that does not have to charge the user.
116    pub fn new_unmetered() -> Self {
117        Self {
118            gas_model_version: 11,
119            gas_left: InternalGas::new(0),
120            gas_price: 1,
121            initial_budget: InternalGas::new(0),
122            cost_table: ZERO_COST_SCHEDULE.clone(),
123            charge: false,
124            stack_height_high_water_mark: 0,
125            stack_height_current: 0,
126            stack_size_high_water_mark: 0,
127            stack_size_current: 0,
128            instructions_executed: 0,
129            stack_height_current_tier_mult: 0,
130            stack_size_current_tier_mult: 0,
131            instructions_current_tier_mult: 0,
132            stack_height_next_tier_start: None,
133            stack_size_next_tier_start: None,
134            instructions_next_tier_start: None,
135            num_native_calls: 0,
136        }
137    }
138
139    const INTERNAL_UNIT_MULTIPLIER: u64 = 1000;
140
141    fn to_internal_units(val: u64) -> InternalGas {
142        InternalGas::new(val * Self::INTERNAL_UNIT_MULTIPLIER)
143    }
144
145    #[allow(dead_code)]
146    fn to_mist(&self, val: InternalGas) -> u64 {
147        let gas: Gas = InternalGas::to_unit_round_down(val);
148        u64::from(gas) * self.gas_price
149    }
150
151    pub fn push_stack(&mut self, pushes: u64) -> PartialVMResult<()> {
152        match self.stack_height_current.checked_add(pushes) {
153            // We should never hit this.
154            None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
155            Some(new_height) => {
156                if new_height > self.stack_height_high_water_mark {
157                    self.stack_height_high_water_mark = new_height;
158                }
159                self.stack_height_current = new_height;
160            }
161        }
162
163        if let Some(stack_height_tier_next) = self.stack_height_next_tier_start
164            && self.stack_height_current > stack_height_tier_next
165        {
166            let (next_mul, next_tier) =
167                self.cost_table.stack_height_tier(self.stack_height_current);
168            self.stack_height_current_tier_mult = next_mul;
169            self.stack_height_next_tier_start = next_tier;
170        }
171
172        Ok(())
173    }
174
175    pub fn pop_stack(&mut self, pops: u64) {
176        self.stack_height_current = self.stack_height_current.saturating_sub(pops);
177    }
178
179    pub fn increase_instruction_count(&mut self, amount: u64) -> PartialVMResult<()> {
180        match self.instructions_executed.checked_add(amount) {
181            None => return Err(PartialVMError::new(StatusCode::PC_OVERFLOW)),
182            Some(new_pc) => {
183                self.instructions_executed = new_pc;
184            }
185        }
186
187        if let Some(instr_tier_next) = self.instructions_next_tier_start
188            && self.instructions_executed > instr_tier_next
189        {
190            let (instr_cost, next_tier) =
191                self.cost_table.instruction_tier(self.instructions_executed);
192            self.instructions_current_tier_mult = instr_cost;
193            self.instructions_next_tier_start = next_tier;
194        }
195
196        Ok(())
197    }
198
199    pub fn increase_stack_size(&mut self, size_amount: u64) -> PartialVMResult<()> {
200        match self.stack_size_current.checked_add(size_amount) {
201            None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
202            Some(new_size) => {
203                if new_size > self.stack_size_high_water_mark {
204                    self.stack_size_high_water_mark = new_size;
205                }
206                self.stack_size_current = new_size;
207            }
208        }
209
210        if let Some(stack_size_tier_next) = self.stack_size_next_tier_start
211            && self.stack_size_current > stack_size_tier_next
212        {
213            let (next_mul, next_tier) = self.cost_table.stack_size_tier(self.stack_size_current);
214            self.stack_size_current_tier_mult = next_mul;
215            self.stack_size_next_tier_start = next_tier;
216        }
217
218        Ok(())
219    }
220
221    pub fn decrease_stack_size(&mut self, size_amount: u64) {
222        let new_size = self.stack_size_current.saturating_sub(size_amount);
223        if new_size > self.stack_size_high_water_mark {
224            self.stack_size_high_water_mark = new_size;
225        }
226        self.stack_size_current = new_size;
227    }
228
229    /// Given: pushes + pops + increase + decrease in size for an instruction charge for the
230    /// execution of the instruction.
231    pub fn charge(
232        &mut self,
233        num_instructions: u64,
234        pushes: u64,
235        pops: u64,
236        incr_size: u64,
237        _decr_size: u64,
238    ) -> PartialVMResult<()> {
239        self.push_stack(pushes)?;
240        self.increase_instruction_count(num_instructions)?;
241        self.increase_stack_size(incr_size)?;
242
243        self.deduct_gas(
244            GasCost::new(
245                self.instructions_current_tier_mult
246                    .checked_mul(num_instructions)
247                    .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
248                self.stack_size_current_tier_mult
249                    .checked_mul(incr_size)
250                    .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
251                self.stack_height_current_tier_mult
252                    .checked_mul(pushes)
253                    .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
254            )
255            .total_internal(),
256        )?;
257
258        // self.decrease_stack_size(decr_size);
259        self.pop_stack(pops);
260        Ok(())
261    }
262
263    /// Return the `CostTable` behind this `GasStatus`.
264    pub fn cost_table(&self) -> &CostTable {
265        &self.cost_table
266    }
267
268    /// Return the gas left.
269    pub fn remaining_gas(&self) -> Gas {
270        self.gas_left.to_unit_round_down()
271    }
272
273    /// Charge a given amount of gas and fail if not enough gas units are left.
274    pub fn deduct_gas(&mut self, amount: InternalGas) -> PartialVMResult<()> {
275        if !self.charge {
276            return Ok(());
277        }
278
279        match self.gas_left.checked_sub(amount) {
280            Some(gas_left) => {
281                self.gas_left = gas_left;
282                Ok(())
283            }
284            None => {
285                self.gas_left = InternalGas::new(0);
286                Err(PartialVMError::new(StatusCode::OUT_OF_GAS))
287            }
288        }
289    }
290
291    pub fn record_native_call(&mut self) {
292        self.num_native_calls = self.num_native_calls.saturating_add(1);
293    }
294
295    // Deduct the amount provided with no conversion, as if it was InternalGasUnit
296    fn deduct_units(&mut self, amount: u64) -> PartialVMResult<()> {
297        self.deduct_gas(InternalGas::new(amount))
298    }
299
300    pub fn set_metering(&mut self, enabled: bool) {
301        self.charge = enabled
302    }
303
304    // The amount of gas used, it does not include the multiplication for the gas price
305    pub fn gas_used_pre_gas_price(&self) -> u64 {
306        let gas: Gas = match self.initial_budget.checked_sub(self.gas_left) {
307            Some(val) => InternalGas::to_unit_round_down(val),
308            None => InternalGas::to_unit_round_down(self.initial_budget),
309        };
310        u64::from(gas)
311    }
312
313    // Charge the number of bytes with the cost per byte value
314    // As more bytes are read throughout the computation the cost per bytes is increased.
315    pub fn charge_bytes(&mut self, size: usize, cost_per_byte: u64) -> PartialVMResult<()> {
316        let computation_cost = if charge_input_as_memory(self.gas_model_version) {
317            self.increase_stack_size(size as u64)?;
318            self.stack_size_current_tier_mult * size as u64 * cost_per_byte
319        } else {
320            size as u64 * cost_per_byte
321        };
322        self.deduct_units(computation_cost)
323    }
324
325    pub fn gas_price(&self) -> u64 {
326        self.gas_price
327    }
328
329    pub fn stack_height_high_water_mark(&self) -> u64 {
330        self.stack_height_high_water_mark
331    }
332
333    pub fn stack_size_high_water_mark(&self) -> u64 {
334        self.stack_size_high_water_mark
335    }
336
337    pub fn instructions_executed(&self) -> u64 {
338        self.instructions_executed
339    }
340
341    pub fn stack_height_current(&self) -> u64 {
342        self.stack_height_current
343    }
344
345    pub fn stack_size_current(&self) -> u64 {
346        self.stack_size_current
347    }
348}
349
350pub fn zero_cost_schedule() -> CostTable {
351    let mut zero_tier = BTreeMap::new();
352    zero_tier.insert(0, 0);
353    CostTable {
354        instruction_tiers: zero_tier.clone(),
355        stack_size_tiers: zero_tier.clone(),
356        stack_height_tiers: zero_tier,
357    }
358}
359
360pub fn unit_cost_schedule() -> CostTable {
361    let mut unit_tier = BTreeMap::new();
362    unit_tier.insert(0, 1);
363    CostTable {
364        instruction_tiers: unit_tier.clone(),
365        stack_size_tiers: unit_tier.clone(),
366        stack_height_tiers: unit_tier,
367    }
368}
369
370pub fn initial_cost_schedule_v1() -> CostTable {
371    let instruction_tiers: BTreeMap<u64, u64> = vec![
372        (0, 1),
373        (3000, 2),
374        (6000, 3),
375        (8000, 5),
376        (9000, 9),
377        (9500, 16),
378        (10000, 29),
379        (10500, 50),
380    ]
381    .into_iter()
382    .collect();
383
384    let stack_height_tiers: BTreeMap<u64, u64> = vec![
385        (0, 1),
386        (400, 2),
387        (800, 3),
388        (1200, 5),
389        (1500, 9),
390        (1800, 16),
391        (2000, 29),
392        (2200, 50),
393    ]
394    .into_iter()
395    .collect();
396
397    let stack_size_tiers: BTreeMap<u64, u64> = vec![
398        (0, 1),
399        (2000, 2),
400        (5000, 3),
401        (8000, 5),
402        (10000, 9),
403        (11000, 16),
404        (11500, 29),
405        (11500, 50),
406    ]
407    .into_iter()
408    .collect();
409
410    CostTable {
411        instruction_tiers,
412        stack_size_tiers,
413        stack_height_tiers,
414    }
415}
416
417pub fn initial_cost_schedule_v2() -> CostTable {
418    let instruction_tiers: BTreeMap<u64, u64> = vec![
419        (0, 1),
420        (3000, 2),
421        (6000, 3),
422        (8000, 5),
423        (9000, 9),
424        (9500, 16),
425        (10000, 29),
426        (10500, 50),
427        (12000, 150),
428        (15000, 250),
429    ]
430    .into_iter()
431    .collect();
432
433    let stack_height_tiers: BTreeMap<u64, u64> = vec![
434        (0, 1),
435        (400, 2),
436        (800, 3),
437        (1200, 5),
438        (1500, 9),
439        (1800, 16),
440        (2000, 29),
441        (2200, 50),
442        (3000, 150),
443        (5000, 250),
444    ]
445    .into_iter()
446    .collect();
447
448    let stack_size_tiers: BTreeMap<u64, u64> = vec![
449        (0, 1),
450        (2000, 2),
451        (5000, 3),
452        (8000, 5),
453        (10000, 9),
454        (11000, 16),
455        (11500, 29),
456        (11500, 50),
457        (15000, 150),
458        (20000, 250),
459    ]
460    .into_iter()
461    .collect();
462
463    CostTable {
464        instruction_tiers,
465        stack_size_tiers,
466        stack_height_tiers,
467    }
468}
469
470pub fn initial_cost_schedule_v3() -> CostTable {
471    let instruction_tiers: BTreeMap<u64, u64> = vec![
472        (0, 1),
473        (3000, 2),
474        (6000, 3),
475        (8000, 5),
476        (9000, 9),
477        (9500, 16),
478        (10000, 29),
479        (10500, 50),
480        (15000, 100),
481    ]
482    .into_iter()
483    .collect();
484
485    let stack_height_tiers: BTreeMap<u64, u64> = vec![
486        (0, 1),
487        (400, 2),
488        (800, 3),
489        (1200, 5),
490        (1500, 9),
491        (1800, 16),
492        (2000, 29),
493        (2200, 50),
494        (5000, 100),
495    ]
496    .into_iter()
497    .collect();
498
499    let stack_size_tiers: BTreeMap<u64, u64> = vec![
500        (0, 1),
501        (2000, 2),
502        (5000, 3),
503        (8000, 5),
504        (10000, 9),
505        (11000, 16),
506        (11500, 29),
507        (11500, 50),
508        (20000, 100),
509    ]
510    .into_iter()
511    .collect();
512
513    CostTable {
514        instruction_tiers,
515        stack_size_tiers,
516        stack_height_tiers,
517    }
518}
519
520pub fn initial_cost_schedule_v4() -> CostTable {
521    let instruction_tiers: BTreeMap<u64, u64> = vec![
522        (0, 1),
523        (20_000, 2),
524        (50_000, 10),
525        (100_000, 50),
526        (200_000, 100),
527    ]
528    .into_iter()
529    .collect();
530
531    let stack_height_tiers: BTreeMap<u64, u64> =
532        vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
533
534    let stack_size_tiers: BTreeMap<u64, u64> = vec![
535        (0, 1),
536        (100_000, 2),     // ~100K
537        (500_000, 5),     // ~500K
538        (1_000_000, 100), // ~1M
539    ]
540    .into_iter()
541    .collect();
542
543    CostTable {
544        instruction_tiers,
545        stack_size_tiers,
546        stack_height_tiers,
547    }
548}
549
550pub fn initial_cost_schedule_v5() -> CostTable {
551    let instruction_tiers: BTreeMap<u64, u64> = vec![
552        (0, 1),
553        (20_000, 2),
554        (50_000, 10),
555        (100_000, 50),
556        (200_000, 100),
557        (10_000_000, 1000),
558    ]
559    .into_iter()
560    .collect();
561
562    let stack_height_tiers: BTreeMap<u64, u64> =
563        vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
564
565    let stack_size_tiers: BTreeMap<u64, u64> = vec![
566        (0, 1),
567        (100_000, 2),        // ~100K
568        (500_000, 5),        // ~500K
569        (1_000_000, 100),    // ~1M
570        (100_000_000, 1000), // ~100M
571    ]
572    .into_iter()
573    .collect();
574
575    CostTable {
576        instruction_tiers,
577        stack_size_tiers,
578        stack_height_tiers,
579    }
580}
581
582// Convert from our representation of gas costs to the type that the MoveVM expects for unit tests.
583// We don't want our gas depending on the MoveVM test utils and we don't want to fix our
584// representation to whatever is there, so instead we perform this translation from our gas units
585// and cost schedule to the one expected by the Move unit tests.
586pub fn initial_cost_schedule_for_unit_tests() -> move_vm_test_utils::gas_schedule::CostTable {
587    let table = initial_cost_schedule_v5();
588    move_vm_test_utils::gas_schedule::CostTable {
589        instruction_tiers: table.instruction_tiers.into_iter().collect(),
590        stack_height_tiers: table.stack_height_tiers.into_iter().collect(),
591        stack_size_tiers: table.stack_size_tiers.into_iter().collect(),
592    }
593}