1use 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
17pub const VM_FLAT_FEE: Gas = Gas::new(8_000);
19
20pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
22
23pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
25
26pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
28
29pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
31
32pub 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#[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 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 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 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 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 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 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 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.pop_stack(pops);
260 Ok(())
261 }
262
263 pub fn cost_table(&self) -> &CostTable {
265 &self.cost_table
266 }
267
268 pub fn remaining_gas(&self) -> Gas {
270 self.gas_left.to_unit_round_down()
271 }
272
273 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 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 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 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), (500_000, 5), (1_000_000, 100), ]
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), (500_000, 5), (1_000_000, 100), (100_000_000, 1000), ]
572 .into_iter()
573 .collect();
574
575 CostTable {
576 instruction_tiers,
577 stack_size_tiers,
578 stack_height_tiers,
579 }
580}
581
582pub 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}