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