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, 50),
405    ]
406    .into_iter()
407    .collect();
408
409    CostTable {
410        instruction_tiers,
411        stack_size_tiers,
412        stack_height_tiers,
413    }
414}
415
416pub fn initial_cost_schedule_v2() -> CostTable {
417    let instruction_tiers: BTreeMap<u64, u64> = vec![
418        (0, 1),
419        (3000, 2),
420        (6000, 3),
421        (8000, 5),
422        (9000, 9),
423        (9500, 16),
424        (10000, 29),
425        (10500, 50),
426        (12000, 150),
427        (15000, 250),
428    ]
429    .into_iter()
430    .collect();
431
432    let stack_height_tiers: BTreeMap<u64, u64> = vec![
433        (0, 1),
434        (400, 2),
435        (800, 3),
436        (1200, 5),
437        (1500, 9),
438        (1800, 16),
439        (2000, 29),
440        (2200, 50),
441        (3000, 150),
442        (5000, 250),
443    ]
444    .into_iter()
445    .collect();
446
447    let stack_size_tiers: BTreeMap<u64, u64> = vec![
448        (0, 1),
449        (2000, 2),
450        (5000, 3),
451        (8000, 5),
452        (10000, 9),
453        (11000, 16),
454        (11500, 50),
455        (15000, 150),
456        (20000, 250),
457    ]
458    .into_iter()
459    .collect();
460
461    CostTable {
462        instruction_tiers,
463        stack_size_tiers,
464        stack_height_tiers,
465    }
466}
467
468pub fn initial_cost_schedule_v3() -> CostTable {
469    let instruction_tiers: BTreeMap<u64, u64> = vec![
470        (0, 1),
471        (3000, 2),
472        (6000, 3),
473        (8000, 5),
474        (9000, 9),
475        (9500, 16),
476        (10000, 29),
477        (10500, 50),
478        (15000, 100),
479    ]
480    .into_iter()
481    .collect();
482
483    let stack_height_tiers: BTreeMap<u64, u64> = vec![
484        (0, 1),
485        (400, 2),
486        (800, 3),
487        (1200, 5),
488        (1500, 9),
489        (1800, 16),
490        (2000, 29),
491        (2200, 50),
492        (5000, 100),
493    ]
494    .into_iter()
495    .collect();
496
497    let stack_size_tiers: BTreeMap<u64, u64> = vec![
498        (0, 1),
499        (2000, 2),
500        (5000, 3),
501        (8000, 5),
502        (10000, 9),
503        (11000, 16),
504        (11500, 50),
505        (20000, 100),
506    ]
507    .into_iter()
508    .collect();
509
510    CostTable {
511        instruction_tiers,
512        stack_size_tiers,
513        stack_height_tiers,
514    }
515}
516
517pub fn initial_cost_schedule_v4() -> CostTable {
518    let instruction_tiers: BTreeMap<u64, u64> = vec![
519        (0, 1),
520        (20_000, 2),
521        (50_000, 10),
522        (100_000, 50),
523        (200_000, 100),
524    ]
525    .into_iter()
526    .collect();
527
528    let stack_height_tiers: BTreeMap<u64, u64> =
529        vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
530
531    let stack_size_tiers: BTreeMap<u64, u64> = vec![
532        (0, 1),
533        (100_000, 2),     // ~100K
534        (500_000, 5),     // ~500K
535        (1_000_000, 100), // ~1M
536    ]
537    .into_iter()
538    .collect();
539
540    CostTable {
541        instruction_tiers,
542        stack_size_tiers,
543        stack_height_tiers,
544    }
545}
546
547pub fn initial_cost_schedule_v5() -> CostTable {
548    let instruction_tiers: BTreeMap<u64, u64> = vec![
549        (0, 1),
550        (20_000, 2),
551        (50_000, 10),
552        (100_000, 50),
553        (200_000, 100),
554        (10_000_000, 1000),
555    ]
556    .into_iter()
557    .collect();
558
559    let stack_height_tiers: BTreeMap<u64, u64> =
560        vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
561
562    let stack_size_tiers: BTreeMap<u64, u64> = vec![
563        (0, 1),
564        (100_000, 2),        // ~100K
565        (500_000, 5),        // ~500K
566        (1_000_000, 100),    // ~1M
567        (100_000_000, 1000), // ~100M
568    ]
569    .into_iter()
570    .collect();
571
572    CostTable {
573        instruction_tiers,
574        stack_size_tiers,
575        stack_height_tiers,
576    }
577}
578
579// Convert from our representation of gas costs to the type that the MoveVM expects for unit tests.
580// We don't want our gas depending on the MoveVM test utils and we don't want to fix our
581// representation to whatever is there, so instead we perform this translation from our gas units
582// and cost schedule to the one expected by the Move unit tests.
583pub fn initial_cost_schedule_for_unit_tests() -> move_vm_test_utils::gas_schedule::CostTable {
584    let table = initial_cost_schedule_v5();
585    move_vm_test_utils::gas_schedule::CostTable {
586        instruction_tiers: table.instruction_tiers.into_iter().collect(),
587        stack_height_tiers: table.stack_height_tiers.into_iter().collect(),
588        stack_size_tiers: table.stack_size_tiers.into_iter().collect(),
589    }
590}