iota_types/gas_model/
tables.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::collections::BTreeMap;
6
7use move_binary_format::errors::{PartialVMError, PartialVMResult};
8use move_core_types::{
9    gas_algebra::{AbstractMemorySize, InternalGas, NumArgs, NumBytes},
10    language_storage::ModuleId,
11    vm_status::StatusCode,
12};
13use move_vm_profiler::GasProfiler;
14use move_vm_types::{
15    gas::{GasMeter, SimpleInstruction},
16    loaded_data::runtime_types::Type,
17    views::{TypeView, ValueView},
18};
19use once_cell::sync::Lazy;
20
21use super::gas_predicates::native_function_threshold_exceeded;
22use crate::gas_model::units_types::{CostTable, Gas, GasCost};
23
24/// VM flat fee
25pub const VM_FLAT_FEE: Gas = Gas::new(8_000);
26
27/// The size in bytes for a non-string or address constant on the stack
28pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
29
30/// The size in bytes for a reference on the stack
31pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
32
33/// The size of a struct in bytes
34pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
35
36/// The size of a vector (without its containing data) in bytes
37pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
38
39/// For exists checks on data that doesn't exists this is the multiplier that is
40/// used.
41pub const MIN_EXISTS_DATA_SIZE: AbstractMemorySize = AbstractMemorySize::new(100);
42
43pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
44
45pub static INITIAL_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(initial_cost_schedule_v1);
46
47/// The Move VM implementation of state for gas metering.
48///
49/// Initialize with a `CostTable` and the gas provided to the transaction.
50/// Provide all the proper guarantees about gas metering in the Move VM.
51///
52/// Every client must use an instance of this type to interact with the Move VM.
53#[derive(Debug)]
54pub struct GasStatus {
55    pub gas_model_version: u64,
56    cost_table: CostTable,
57    gas_left: InternalGas,
58    gas_price: u64,
59    initial_budget: InternalGas,
60    charge: bool,
61
62    // The current height of the operand stack, and the maximal height that it has reached.
63    stack_height_high_water_mark: u64,
64    stack_height_current: u64,
65    stack_height_next_tier_start: Option<u64>,
66    stack_height_current_tier_mult: u64,
67
68    // The current (abstract) size  of the operand stack and the maximal size that it has reached.
69    stack_size_high_water_mark: u64,
70    stack_size_current: u64,
71    stack_size_next_tier_start: Option<u64>,
72    stack_size_current_tier_mult: u64,
73
74    // The total number of bytecode instructions that have been executed in the transaction.
75    instructions_executed: u64,
76    instructions_next_tier_start: Option<u64>,
77    instructions_current_tier_mult: u64,
78
79    profiler: Option<GasProfiler>,
80    num_native_calls: u64,
81}
82
83impl GasStatus {
84    /// Initialize the gas state with metering enabled.
85    ///
86    /// Charge for every operation and fail when there is no more gas to pay for
87    /// operations. This is the instantiation that must be used when
88    /// executing a user script.
89    pub fn new(cost_table: CostTable, budget: u64, gas_price: u64, gas_model_version: u64) -> Self {
90        assert!(gas_price > 0, "gas price cannot be 0");
91        let budget_in_unit = budget / gas_price;
92        let gas_left = Self::to_internal_units(budget_in_unit);
93        let (stack_height_current_tier_mult, stack_height_next_tier_start) =
94            cost_table.stack_height_tier(0);
95        let (stack_size_current_tier_mult, stack_size_next_tier_start) =
96            cost_table.stack_size_tier(0);
97        let (instructions_current_tier_mult, instructions_next_tier_start) =
98            cost_table.instruction_tier(0);
99        Self {
100            gas_model_version,
101            gas_left,
102            gas_price,
103            initial_budget: gas_left,
104            cost_table,
105            charge: true,
106            stack_height_high_water_mark: 0,
107            stack_height_current: 0,
108            stack_size_high_water_mark: 0,
109            stack_size_current: 0,
110            instructions_executed: 0,
111            stack_height_current_tier_mult,
112            stack_size_current_tier_mult,
113            instructions_current_tier_mult,
114            stack_height_next_tier_start,
115            stack_size_next_tier_start,
116            instructions_next_tier_start,
117            profiler: None,
118            num_native_calls: 0,
119        }
120    }
121
122    /// Initialize the gas state with metering disabled.
123    ///
124    /// It should be used by clients in very specific cases and when executing
125    /// system code that does not have to charge the user.
126    pub fn new_unmetered() -> Self {
127        Self {
128            gas_model_version: 1,
129            gas_left: InternalGas::new(0),
130            gas_price: 1,
131            initial_budget: InternalGas::new(0),
132            cost_table: ZERO_COST_SCHEDULE.clone(),
133            charge: false,
134            stack_height_high_water_mark: 0,
135            stack_height_current: 0,
136            stack_size_high_water_mark: 0,
137            stack_size_current: 0,
138            instructions_executed: 0,
139            stack_height_current_tier_mult: 0,
140            stack_size_current_tier_mult: 0,
141            instructions_current_tier_mult: 0,
142            stack_height_next_tier_start: None,
143            stack_size_next_tier_start: None,
144            instructions_next_tier_start: None,
145            profiler: None,
146            num_native_calls: 0,
147        }
148    }
149
150    const INTERNAL_UNIT_MULTIPLIER: u64 = 1000;
151
152    fn to_internal_units(val: u64) -> InternalGas {
153        InternalGas::new(val * Self::INTERNAL_UNIT_MULTIPLIER)
154    }
155
156    #[expect(dead_code)]
157    fn to_nanos(&self, val: InternalGas) -> u64 {
158        let gas: Gas = InternalGas::to_unit_round_down(val);
159        u64::from(gas) * self.gas_price
160    }
161
162    pub fn push_stack(&mut self, pushes: u64) -> PartialVMResult<()> {
163        match self.stack_height_current.checked_add(pushes) {
164            // We should never hit this.
165            None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
166            Some(new_height) => {
167                if new_height > self.stack_height_high_water_mark {
168                    self.stack_height_high_water_mark = new_height;
169                }
170                self.stack_height_current = new_height;
171            }
172        }
173
174        if let Some(stack_height_tier_next) = self.stack_height_next_tier_start {
175            if self.stack_height_current > stack_height_tier_next {
176                let (next_mul, next_tier) =
177                    self.cost_table.stack_height_tier(self.stack_height_current);
178                self.stack_height_current_tier_mult = next_mul;
179                self.stack_height_next_tier_start = next_tier;
180            }
181        }
182
183        Ok(())
184    }
185
186    pub fn pop_stack(&mut self, pops: u64) {
187        self.stack_height_current = self.stack_height_current.saturating_sub(pops);
188    }
189
190    pub fn increase_instruction_count(&mut self, amount: u64) -> PartialVMResult<()> {
191        match self.instructions_executed.checked_add(amount) {
192            None => return Err(PartialVMError::new(StatusCode::PC_OVERFLOW)),
193            Some(new_pc) => {
194                self.instructions_executed = new_pc;
195            }
196        }
197
198        if let Some(instr_tier_next) = self.instructions_next_tier_start {
199            if self.instructions_executed > instr_tier_next {
200                let (instr_cost, next_tier) =
201                    self.cost_table.instruction_tier(self.instructions_executed);
202                self.instructions_current_tier_mult = instr_cost;
203                self.instructions_next_tier_start = next_tier;
204            }
205        }
206
207        Ok(())
208    }
209
210    pub fn increase_stack_size(&mut self, size_amount: u64) -> PartialVMResult<()> {
211        match self.stack_size_current.checked_add(size_amount) {
212            None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
213            Some(new_size) => {
214                if new_size > self.stack_size_high_water_mark {
215                    self.stack_size_high_water_mark = new_size;
216                }
217                self.stack_size_current = new_size;
218            }
219        }
220
221        if let Some(stack_size_tier_next) = self.stack_size_next_tier_start {
222            if self.stack_size_current > stack_size_tier_next {
223                let (next_mul, next_tier) =
224                    self.cost_table.stack_size_tier(self.stack_size_current);
225                self.stack_size_current_tier_mult = next_mul;
226                self.stack_size_next_tier_start = next_tier;
227            }
228        }
229
230        Ok(())
231    }
232
233    pub fn decrease_stack_size(&mut self, size_amount: u64) {
234        let new_size = self.stack_size_current.saturating_sub(size_amount);
235        if new_size > self.stack_size_high_water_mark {
236            self.stack_size_high_water_mark = new_size;
237        }
238        self.stack_size_current = new_size;
239    }
240
241    /// Given: pushes + pops + increase + decrease in size for an instruction
242    /// charge for the execution of the instruction.
243    pub fn charge(
244        &mut self,
245        num_instructions: u64,
246        pushes: u64,
247        pops: u64,
248        incr_size: u64,
249        _decr_size: u64,
250    ) -> PartialVMResult<()> {
251        self.push_stack(pushes)?;
252        self.increase_instruction_count(num_instructions)?;
253        self.increase_stack_size(incr_size)?;
254
255        self.deduct_gas(
256            GasCost::new(
257                self.instructions_current_tier_mult
258                    .checked_mul(num_instructions)
259                    .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
260                self.stack_size_current_tier_mult
261                    .checked_mul(incr_size)
262                    .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
263                self.stack_height_current_tier_mult
264                    .checked_mul(pushes)
265                    .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
266            )
267            .total_internal(),
268        )?;
269
270        // self.decrease_stack_size(decr_size);
271        self.pop_stack(pops);
272        Ok(())
273    }
274
275    /// Return the `CostTable` behind this `GasStatus`.
276    pub fn cost_table(&self) -> &CostTable {
277        &self.cost_table
278    }
279
280    /// Return the gas left.
281    pub fn remaining_gas(&self) -> Gas {
282        self.gas_left.to_unit_round_down()
283    }
284
285    /// Charge a given amount of gas and fail if not enough gas units are left.
286    pub fn deduct_gas(&mut self, amount: InternalGas) -> PartialVMResult<()> {
287        if !self.charge {
288            return Ok(());
289        }
290
291        match self.gas_left.checked_sub(amount) {
292            Some(gas_left) => {
293                self.gas_left = gas_left;
294                Ok(())
295            }
296            None => {
297                self.gas_left = InternalGas::new(0);
298                Err(PartialVMError::new(StatusCode::OUT_OF_GAS))
299            }
300        }
301    }
302
303    // Deduct the amount provided with no conversion, as if it was InternalGasUnit
304    fn deduct_units(&mut self, amount: u64) -> PartialVMResult<()> {
305        self.deduct_gas(InternalGas::new(amount))
306    }
307
308    pub fn set_metering(&mut self, enabled: bool) {
309        self.charge = enabled
310    }
311
312    // The amount of gas used, it does not include the multiplication for the gas
313    // price
314    pub fn gas_used_pre_gas_price(&self) -> u64 {
315        let gas: Gas = match self.initial_budget.checked_sub(self.gas_left) {
316            Some(val) => InternalGas::to_unit_round_down(val),
317            None => InternalGas::to_unit_round_down(self.initial_budget),
318        };
319        u64::from(gas)
320    }
321
322    // Charge the number of bytes with the cost per byte value
323    // As more bytes are read throughout the computation the cost per bytes is
324    // increased.
325    pub fn charge_bytes(&mut self, size: usize, cost_per_byte: u64) -> PartialVMResult<()> {
326        let computation_cost = size as u64 * cost_per_byte;
327        self.deduct_units(computation_cost)
328    }
329
330    fn abstract_memory_size(&self, val: impl ValueView) -> AbstractMemorySize {
331        val.abstract_memory_size()
332    }
333
334    pub fn gas_price(&self) -> u64 {
335        self.gas_price
336    }
337
338    pub fn stack_height_high_water_mark(&self) -> u64 {
339        self.stack_height_high_water_mark
340    }
341
342    pub fn stack_size_high_water_mark(&self) -> u64 {
343        self.stack_size_high_water_mark
344    }
345
346    pub fn instructions_executed(&self) -> u64 {
347        self.instructions_executed
348    }
349}
350
351/// Returns a tuple of (<pops>, <pushes>, <stack_size_decrease>,
352/// <stack_size_increase>)
353fn get_simple_instruction_stack_change(
354    instr: SimpleInstruction,
355) -> (u64, u64, AbstractMemorySize, AbstractMemorySize) {
356    use SimpleInstruction::*;
357
358    match instr {
359        // NB: The `Ret` pops are accounted for in `Call` instructions, so we say `Ret` has no pops.
360        Nop | Ret => (0, 0, 0.into(), 0.into()),
361        BrTrue | BrFalse => (1, 0, Type::Bool.size(), 0.into()),
362        Branch => (0, 0, 0.into(), 0.into()),
363        LdU8 => (0, 1, 0.into(), Type::U8.size()),
364        LdU16 => (0, 1, 0.into(), Type::U16.size()),
365        LdU32 => (0, 1, 0.into(), Type::U32.size()),
366        LdU64 => (0, 1, 0.into(), Type::U64.size()),
367        LdU128 => (0, 1, 0.into(), Type::U128.size()),
368        LdU256 => (0, 1, 0.into(), Type::U256.size()),
369        LdTrue | LdFalse => (0, 1, 0.into(), Type::Bool.size()),
370        FreezeRef => (1, 1, REFERENCE_SIZE, REFERENCE_SIZE),
371        ImmBorrowLoc | MutBorrowLoc => (0, 1, 0.into(), REFERENCE_SIZE),
372        ImmBorrowField | MutBorrowField | ImmBorrowFieldGeneric | MutBorrowFieldGeneric => {
373            (1, 1, REFERENCE_SIZE, REFERENCE_SIZE)
374        }
375        // Since we don't have the size of the value being cast here we take a conservative
376        // over-approximation: it is _always_ getting cast from the smallest integer type.
377        CastU8 => (1, 1, Type::U8.size(), Type::U8.size()),
378        CastU16 => (1, 1, Type::U8.size(), Type::U16.size()),
379        CastU32 => (1, 1, Type::U8.size(), Type::U32.size()),
380        CastU64 => (1, 1, Type::U8.size(), Type::U64.size()),
381        CastU128 => (1, 1, Type::U8.size(), Type::U128.size()),
382        CastU256 => (1, 1, Type::U8.size(), Type::U256.size()),
383        // NB: We don't know the size of what integers we're dealing with, so we conservatively
384        // over-approximate by popping the smallest integers, and push the largest.
385        Add | Sub | Mul | Mod | Div => (2, 1, Type::U8.size() + Type::U8.size(), Type::U256.size()),
386        BitOr | BitAnd | Xor => (2, 1, Type::U8.size() + Type::U8.size(), Type::U256.size()),
387        Shl | Shr => (2, 1, Type::U8.size() + Type::U8.size(), Type::U256.size()),
388        Or | And => (
389            2,
390            1,
391            Type::Bool.size() + Type::Bool.size(),
392            Type::Bool.size(),
393        ),
394        Lt | Gt | Le | Ge => (2, 1, Type::U8.size() + Type::U8.size(), Type::Bool.size()),
395        Not => (1, 1, Type::Bool.size(), Type::Bool.size()),
396        Abort => (1, 0, Type::U64.size(), 0.into()),
397    }
398}
399
400impl GasMeter for GasStatus {
401    /// Charge an instruction and fail if not enough gas units are left.
402    fn charge_simple_instr(&mut self, instr: SimpleInstruction) -> PartialVMResult<()> {
403        let (pops, pushes, pop_size, push_size) = get_simple_instruction_stack_change(instr);
404        self.charge(1, pushes, pops, push_size.into(), pop_size.into())
405    }
406
407    fn charge_pop(&mut self, popped_val: impl ValueView) -> PartialVMResult<()> {
408        self.charge(1, 0, 1, 0, self.abstract_memory_size(popped_val).into())
409    }
410
411    fn charge_native_function(
412        &mut self,
413        amount: InternalGas,
414        ret_vals: Option<impl ExactSizeIterator<Item = impl ValueView>>,
415    ) -> PartialVMResult<()> {
416        // Charge for the number of pushes on to the stack that the return of this
417        // function is going to cause.
418        let pushes = ret_vals
419            .as_ref()
420            .map(|ret_vals| ret_vals.len())
421            .unwrap_or(0) as u64;
422        // Calculate the number of bytes that are getting pushed onto the stack.
423        let size_increase = ret_vals
424            .map(|ret_vals| {
425                ret_vals.fold(AbstractMemorySize::zero(), |acc, elem| {
426                    acc + self.abstract_memory_size(elem)
427                })
428            })
429            .unwrap_or_else(AbstractMemorySize::zero);
430        self.num_native_calls = self.num_native_calls.saturating_add(1);
431        if native_function_threshold_exceeded(self.gas_model_version, self.num_native_calls) {
432            // Charge for the stack operations. We don't count this as an "instruction"
433            // since we already accounted for the `Call` instruction in the
434            // `charge_native_function_before_execution` call.
435            // The amount returned by the native function is viewed as the "virtual"
436            // instruction cost for the native function, and will be charged and
437            // contribute to the overall cost tier of the transaction
438            // accordingly.
439            self.charge(amount.into(), pushes, 0, size_increase.into(), 0)
440        } else {
441            // Charge for the stack operations. We don't count this as an "instruction"
442            // since we already accounted for the `Call` instruction in the
443            // `charge_native_function_before_execution` call.
444            self.charge(0, pushes, 0, size_increase.into(), 0)?;
445            // Now charge the gas that the native function told us to charge.
446            self.deduct_gas(amount)
447        }
448    }
449
450    fn charge_native_function_before_execution(
451        &mut self,
452        _ty_args: impl ExactSizeIterator<Item = impl TypeView>,
453        args: impl ExactSizeIterator<Item = impl ValueView>,
454    ) -> PartialVMResult<()> {
455        // Determine the number of pops that are going to be needed for this function
456        // call, and charge for them.
457        let pops = args.len() as u64;
458        // Calculate the size decrease of the stack from the above pops.
459        let stack_reduction_size = args.fold(AbstractMemorySize::new(pops), |acc, elem| {
460            acc + self.abstract_memory_size(elem)
461        });
462        // Track that this is going to be popping from the operand stack. We also
463        // increment the instruction count as we need to account for the `Call`
464        // bytecode that initiated this native call.
465        self.charge(1, 0, pops, 0, stack_reduction_size.into())
466    }
467
468    fn charge_call(
469        &mut self,
470        _module_id: &ModuleId,
471        _func_name: &str,
472        args: impl ExactSizeIterator<Item = impl ValueView>,
473        _num_locals: NumArgs,
474    ) -> PartialVMResult<()> {
475        // We will have to perform this many pops for the call.
476        let pops = args.len() as u64;
477        // Size stays the same -- we're just moving it from the operand stack to the
478        // locals. But the size on the operand stack is reduced by sum_{args}
479        // arg.size().
480        let stack_reduction_size = args.fold(AbstractMemorySize::new(0), |acc, elem| {
481            acc + self.abstract_memory_size(elem)
482        });
483        self.charge(1, 0, pops, 0, stack_reduction_size.into())
484    }
485
486    fn charge_call_generic(
487        &mut self,
488        _module_id: &ModuleId,
489        _func_name: &str,
490        _ty_args: impl ExactSizeIterator<Item = impl TypeView>,
491        args: impl ExactSizeIterator<Item = impl ValueView>,
492        _num_locals: NumArgs,
493    ) -> PartialVMResult<()> {
494        // We have to perform this many pops from the operand stack for this function
495        // call.
496        let pops = args.len() as u64;
497        // Calculate the size reduction on the operand stack.
498        let stack_reduction_size = args.fold(AbstractMemorySize::new(0), |acc, elem| {
499            acc + self.abstract_memory_size(elem)
500        });
501        // Charge for the pops, no pushes, and account for the stack size decrease. Also
502        // track the `CallGeneric` instruction we must have encountered for
503        // this.
504        self.charge(1, 0, pops, 0, stack_reduction_size.into())
505    }
506
507    fn charge_ld_const(&mut self, size: NumBytes) -> PartialVMResult<()> {
508        // Charge for the load from the locals onto the stack.
509        self.charge(1, 1, 0, u64::from(size), 0)
510    }
511
512    fn charge_ld_const_after_deserialization(
513        &mut self,
514        _val: impl ValueView,
515    ) -> PartialVMResult<()> {
516        // We already charged for this based on the bytes that we're loading so don't
517        // charge again.
518        Ok(())
519    }
520
521    fn charge_copy_loc(&mut self, val: impl ValueView) -> PartialVMResult<()> {
522        // Charge for the copy of the local onto the stack.
523        self.charge(1, 1, 0, self.abstract_memory_size(val).into(), 0)
524    }
525
526    fn charge_move_loc(&mut self, val: impl ValueView) -> PartialVMResult<()> {
527        // Charge for the move of the local on to the stack. Note that we charge here
528        // since we aren't tracking the local size (at least not yet). If we
529        // were, this should be a net-zero operation in terms of memory usage.
530        self.charge(1, 1, 0, self.abstract_memory_size(val).into(), 0)
531    }
532
533    fn charge_store_loc(&mut self, val: impl ValueView) -> PartialVMResult<()> {
534        // Charge for the storing of the value on the stack into a local. Note here that
535        // if we were also accounting for the size of the locals that this would
536        // be a net-zero operation in terms of memory.
537        self.charge(1, 0, 1, 0, self.abstract_memory_size(val).into())
538    }
539
540    fn charge_pack(
541        &mut self,
542        _is_generic: bool,
543        args: impl ExactSizeIterator<Item = impl ValueView>,
544    ) -> PartialVMResult<()> {
545        // We perform `num_fields` number of pops.
546        let num_fields = args.len() as u64;
547        // The actual amount of memory on the stack is staying the same with the
548        // addition of some extra size for the struct, so the size doesn't
549        // really change much.
550        self.charge(1, 1, num_fields, STRUCT_SIZE.into(), 0)
551    }
552
553    fn charge_unpack(
554        &mut self,
555        _is_generic: bool,
556        args: impl ExactSizeIterator<Item = impl ValueView>,
557    ) -> PartialVMResult<()> {
558        // We perform `num_fields` number of pushes.
559        let num_fields = args.len() as u64;
560        self.charge(1, num_fields, 1, 0, STRUCT_SIZE.into())
561    }
562
563    fn charge_variant_switch(&mut self, val: impl ValueView) -> PartialVMResult<()> {
564        // We perform a single pop of a value from the stack.
565        self.charge(1, 0, 1, 0, self.abstract_memory_size(val).into())
566    }
567
568    fn charge_read_ref(&mut self, ref_val: impl ValueView) -> PartialVMResult<()> {
569        // We read the reference so we are decreasing the size of the stack by the size
570        // of the reference, and adding to it the size of the value that has
571        // been read from that reference.
572        self.charge(
573            1,
574            1,
575            1,
576            self.abstract_memory_size(ref_val).into(),
577            REFERENCE_SIZE.into(),
578        )
579    }
580
581    fn charge_write_ref(
582        &mut self,
583        new_val: impl ValueView,
584        old_val: impl ValueView,
585    ) -> PartialVMResult<()> {
586        // TODO(tzakian): We should account for this elsewhere as the owner of data the
587        // the reference points to won't be on the stack. For now though, we
588        // treat it as adding to the stack size.
589        self.charge(
590            1,
591            1,
592            2,
593            self.abstract_memory_size(new_val).into(),
594            self.abstract_memory_size(old_val).into(),
595        )
596    }
597
598    fn charge_eq(&mut self, lhs: impl ValueView, rhs: impl ValueView) -> PartialVMResult<()> {
599        let size_reduction = self.abstract_memory_size(lhs) + self.abstract_memory_size(rhs);
600        self.charge(
601            1,
602            1,
603            2,
604            (Type::Bool.size() + size_reduction).into(),
605            size_reduction.into(),
606        )
607    }
608
609    fn charge_neq(&mut self, lhs: impl ValueView, rhs: impl ValueView) -> PartialVMResult<()> {
610        let size_reduction = self.abstract_memory_size(lhs) + self.abstract_memory_size(rhs);
611        self.charge(1, 1, 2, Type::Bool.size().into(), size_reduction.into())
612    }
613
614    fn charge_vec_pack<'a>(
615        &mut self,
616        _ty: impl TypeView + 'a,
617        args: impl ExactSizeIterator<Item = impl ValueView>,
618    ) -> PartialVMResult<()> {
619        // We will perform `num_args` number of pops.
620        let num_args = args.len() as u64;
621        // The amount of data on the stack stays constant except we have some extra
622        // metadata for the vector to hold the length of the vector.
623        self.charge(1, 1, num_args, VEC_SIZE.into(), 0)
624    }
625
626    fn charge_vec_len(&mut self, _ty: impl TypeView) -> PartialVMResult<()> {
627        self.charge(1, 1, 1, Type::U64.size().into(), REFERENCE_SIZE.into())
628    }
629
630    fn charge_vec_borrow(
631        &mut self,
632        _is_mut: bool,
633        _ty: impl TypeView,
634        _is_success: bool,
635    ) -> PartialVMResult<()> {
636        self.charge(
637            1,
638            1,
639            2,
640            REFERENCE_SIZE.into(),
641            (REFERENCE_SIZE + Type::U64.size()).into(),
642        )
643    }
644
645    fn charge_vec_push_back(
646        &mut self,
647        _ty: impl TypeView,
648        _val: impl ValueView,
649    ) -> PartialVMResult<()> {
650        // The value was already on the stack, so we aren't increasing the number of
651        // bytes on the stack.
652        self.charge(1, 0, 2, 0, REFERENCE_SIZE.into())
653    }
654
655    fn charge_vec_pop_back(
656        &mut self,
657        _ty: impl TypeView,
658        _val: Option<impl ValueView>,
659    ) -> PartialVMResult<()> {
660        self.charge(1, 1, 1, 0, REFERENCE_SIZE.into())
661    }
662
663    fn charge_vec_unpack(
664        &mut self,
665        _ty: impl TypeView,
666        expect_num_elements: NumArgs,
667        _elems: impl ExactSizeIterator<Item = impl ValueView>,
668    ) -> PartialVMResult<()> {
669        // Charge for the pushes
670        let pushes = u64::from(expect_num_elements);
671        // The stack size stays pretty much the same modulo the additional vector size
672        self.charge(1, pushes, 1, 0, VEC_SIZE.into())
673    }
674
675    fn charge_vec_swap(&mut self, _ty: impl TypeView) -> PartialVMResult<()> {
676        let size_decrease = REFERENCE_SIZE + Type::U64.size() + Type::U64.size();
677        self.charge(1, 1, 1, 0, size_decrease.into())
678    }
679
680    fn charge_drop_frame(
681        &mut self,
682        _locals: impl Iterator<Item = impl ValueView>,
683    ) -> PartialVMResult<()> {
684        Ok(())
685    }
686
687    fn remaining_gas(&self) -> InternalGas {
688        if !self.charge {
689            return InternalGas::new(u64::MAX);
690        }
691        self.gas_left
692    }
693
694    fn get_profiler_mut(&mut self) -> Option<&mut GasProfiler> {
695        self.profiler.as_mut()
696    }
697
698    fn set_profiler(&mut self, profiler: GasProfiler) {
699        self.profiler = Some(profiler);
700    }
701}
702
703pub fn zero_cost_schedule() -> CostTable {
704    let mut zero_tier = BTreeMap::new();
705    zero_tier.insert(0, 0);
706    CostTable {
707        instruction_tiers: zero_tier.clone(),
708        stack_size_tiers: zero_tier.clone(),
709        stack_height_tiers: zero_tier,
710    }
711}
712
713pub fn unit_cost_schedule() -> CostTable {
714    let mut unit_tier = BTreeMap::new();
715    unit_tier.insert(0, 1);
716    CostTable {
717        instruction_tiers: unit_tier.clone(),
718        stack_size_tiers: unit_tier.clone(),
719        stack_height_tiers: unit_tier,
720    }
721}
722
723pub fn initial_cost_schedule_v1() -> CostTable {
724    let instruction_tiers: BTreeMap<u64, u64> = vec![
725        (0, 1),
726        (20_000, 2),
727        (50_000, 10),
728        (100_000, 50),
729        (200_000, 100),
730        (10_000_000, 1000),
731    ]
732    .into_iter()
733    .collect();
734
735    let stack_height_tiers: BTreeMap<u64, u64> =
736        vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
737
738    let stack_size_tiers: BTreeMap<u64, u64> = vec![
739        (0, 1),
740        (100_000, 2),        // ~100K
741        (500_000, 5),        // ~500K
742        (1_000_000, 100),    // ~1M
743        (100_000_000, 1000), // ~100M
744    ]
745    .into_iter()
746    .collect();
747
748    CostTable {
749        instruction_tiers,
750        stack_size_tiers,
751        stack_height_tiers,
752    }
753}
754
755// Convert from our representation of gas costs to the type that the MoveVM
756// expects for unit tests. We don't want our gas depending on the MoveVM test
757// utils and we don't want to fix our representation to whatever is there, so
758// instead we perform this translation from our gas units and cost schedule to
759// the one expected by the Move unit tests.
760pub fn initial_cost_schedule_for_unit_tests() -> move_vm_test_utils::gas_schedule::CostTable {
761    let table = initial_cost_schedule_v1();
762    move_vm_test_utils::gas_schedule::CostTable {
763        instruction_tiers: table.instruction_tiers,
764        stack_height_tiers: table.stack_height_tiers,
765        stack_size_tiers: table.stack_size_tiers,
766    }
767}