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