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: 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 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
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), (500_000, 5), (1_000_000, 100), ]
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), (500_000, 5), (1_000_000, 100), (100_000_000, 1000), ]
564 .into_iter()
565 .collect();
566
567 CostTable {
568 instruction_tiers,
569 stack_size_tiers,
570 stack_height_tiers,
571 }
572}
573
574pub 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}