1use 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
14pub const VM_FLAT_FEE: Gas = Gas::new(8_000);
16
17pub const CONST_SIZE: AbstractMemorySize = AbstractMemorySize::new(16);
19
20pub const REFERENCE_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
22
23pub const STRUCT_SIZE: AbstractMemorySize = AbstractMemorySize::new(2);
25
26pub const VEC_SIZE: AbstractMemorySize = AbstractMemorySize::new(8);
28
29pub const MIN_EXISTS_DATA_SIZE: AbstractMemorySize = AbstractMemorySize::new(100);
31
32pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
33
34#[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 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 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 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 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 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 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 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.pop_stack(pops);
255 Ok(())
256 }
257
258 pub fn cost_table(&self) -> &CostTable {
260 &self.cost_table
261 }
262
263 pub fn remaining_gas(&self) -> Gas {
265 self.gas_left.to_unit_round_down()
266 }
267
268 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 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 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 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), (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}