1use std::collections::BTreeMap;
6
7use move_binary_format::errors::{PartialVMError, PartialVMResult};
8use move_core_types::{
9 gas_algebra::{AbstractMemorySize, InternalGas},
10 vm_status::StatusCode,
11};
12use move_vm_profiler::GasProfiler;
13use once_cell::sync::Lazy;
14
15use crate::gas_model::units_types::{CostTable, Gas, GasCost};
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);
35
36pub static ZERO_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(zero_cost_schedule);
37
38pub static INITIAL_COST_SCHEDULE: Lazy<CostTable> = Lazy::new(initial_cost_schedule_v1);
39
40#[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 profiler: Option<GasProfiler>,
73 pub num_native_calls: u64,
74}
75
76impl GasStatus {
77 pub fn new(cost_table: CostTable, budget: u64, gas_price: u64, gas_model_version: u64) -> Self {
83 assert!(gas_price > 0, "gas price cannot be 0");
84 let budget_in_unit = budget / gas_price;
85 let gas_left = Self::to_internal_units(budget_in_unit);
86 let (stack_height_current_tier_mult, stack_height_next_tier_start) =
87 cost_table.stack_height_tier(0);
88 let (stack_size_current_tier_mult, stack_size_next_tier_start) =
89 cost_table.stack_size_tier(0);
90 let (instructions_current_tier_mult, instructions_next_tier_start) =
91 cost_table.instruction_tier(0);
92 Self {
93 gas_model_version,
94 gas_left,
95 gas_price,
96 initial_budget: gas_left,
97 cost_table,
98 charge: true,
99 stack_height_high_water_mark: 0,
100 stack_height_current: 0,
101 stack_size_high_water_mark: 0,
102 stack_size_current: 0,
103 instructions_executed: 0,
104 stack_height_current_tier_mult,
105 stack_size_current_tier_mult,
106 instructions_current_tier_mult,
107 stack_height_next_tier_start,
108 stack_size_next_tier_start,
109 instructions_next_tier_start,
110 profiler: None,
111 num_native_calls: 0,
112 }
113 }
114
115 pub fn new_unmetered() -> Self {
120 Self {
121 gas_model_version: 1,
122 gas_left: InternalGas::new(0),
123 gas_price: 1,
124 initial_budget: InternalGas::new(0),
125 cost_table: ZERO_COST_SCHEDULE.clone(),
126 charge: false,
127 stack_height_high_water_mark: 0,
128 stack_height_current: 0,
129 stack_size_high_water_mark: 0,
130 stack_size_current: 0,
131 instructions_executed: 0,
132 stack_height_current_tier_mult: 0,
133 stack_size_current_tier_mult: 0,
134 instructions_current_tier_mult: 0,
135 stack_height_next_tier_start: None,
136 stack_size_next_tier_start: None,
137 instructions_next_tier_start: None,
138 profiler: None,
139 num_native_calls: 0,
140 }
141 }
142
143 const INTERNAL_UNIT_MULTIPLIER: u64 = 1000;
144
145 fn to_internal_units(val: u64) -> InternalGas {
146 InternalGas::new(val * Self::INTERNAL_UNIT_MULTIPLIER)
147 }
148
149 #[expect(dead_code)]
150 fn to_nanos(&self, val: InternalGas) -> u64 {
151 let gas: Gas = InternalGas::to_unit_round_down(val);
152 u64::from(gas) * self.gas_price
153 }
154
155 pub fn push_stack(&mut self, pushes: u64) -> PartialVMResult<()> {
156 match self.stack_height_current.checked_add(pushes) {
157 None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
159 Some(new_height) => {
160 if new_height > self.stack_height_high_water_mark {
161 self.stack_height_high_water_mark = new_height;
162 }
163 self.stack_height_current = new_height;
164 }
165 }
166
167 if let Some(stack_height_tier_next) = self.stack_height_next_tier_start {
168 if self.stack_height_current > stack_height_tier_next {
169 let (next_mul, next_tier) =
170 self.cost_table.stack_height_tier(self.stack_height_current);
171 self.stack_height_current_tier_mult = next_mul;
172 self.stack_height_next_tier_start = next_tier;
173 }
174 }
175
176 Ok(())
177 }
178
179 pub fn pop_stack(&mut self, pops: u64) {
180 self.stack_height_current = self.stack_height_current.saturating_sub(pops);
181 }
182
183 pub fn increase_instruction_count(&mut self, amount: u64) -> PartialVMResult<()> {
184 match self.instructions_executed.checked_add(amount) {
185 None => return Err(PartialVMError::new(StatusCode::PC_OVERFLOW)),
186 Some(new_pc) => {
187 self.instructions_executed = new_pc;
188 }
189 }
190
191 if let Some(instr_tier_next) = self.instructions_next_tier_start {
192 if self.instructions_executed > instr_tier_next {
193 let (instr_cost, next_tier) =
194 self.cost_table.instruction_tier(self.instructions_executed);
195 self.instructions_current_tier_mult = instr_cost;
196 self.instructions_next_tier_start = next_tier;
197 }
198 }
199
200 Ok(())
201 }
202
203 pub fn increase_stack_size(&mut self, size_amount: u64) -> PartialVMResult<()> {
204 match self.stack_size_current.checked_add(size_amount) {
205 None => return Err(PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW)),
206 Some(new_size) => {
207 if new_size > self.stack_size_high_water_mark {
208 self.stack_size_high_water_mark = new_size;
209 }
210 self.stack_size_current = new_size;
211 }
212 }
213
214 if let Some(stack_size_tier_next) = self.stack_size_next_tier_start {
215 if self.stack_size_current > stack_size_tier_next {
216 let (next_mul, next_tier) =
217 self.cost_table.stack_size_tier(self.stack_size_current);
218 self.stack_size_current_tier_mult = next_mul;
219 self.stack_size_next_tier_start = next_tier;
220 }
221 }
222
223 Ok(())
224 }
225
226 pub fn decrease_stack_size(&mut self, size_amount: u64) {
227 let new_size = self.stack_size_current.saturating_sub(size_amount);
228 if new_size > self.stack_size_high_water_mark {
229 self.stack_size_high_water_mark = new_size;
230 }
231 self.stack_size_current = new_size;
232 }
233
234 pub fn charge(
237 &mut self,
238 num_instructions: u64,
239 pushes: u64,
240 pops: u64,
241 incr_size: u64,
242 _decr_size: u64,
243 ) -> PartialVMResult<()> {
244 self.push_stack(pushes)?;
245 self.increase_instruction_count(num_instructions)?;
246 self.increase_stack_size(incr_size)?;
247
248 self.deduct_gas(
249 GasCost::new(
250 self.instructions_current_tier_mult
251 .checked_mul(num_instructions)
252 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
253 self.stack_size_current_tier_mult
254 .checked_mul(incr_size)
255 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
256 self.stack_height_current_tier_mult
257 .checked_mul(pushes)
258 .ok_or_else(|| PartialVMError::new(StatusCode::ARITHMETIC_OVERFLOW))?,
259 )
260 .total_internal(),
261 )?;
262
263 self.pop_stack(pops);
265 Ok(())
266 }
267
268 pub fn cost_table(&self) -> &CostTable {
270 &self.cost_table
271 }
272
273 pub fn remaining_gas(&self) -> Gas {
275 self.gas_left.to_unit_round_down()
276 }
277
278 pub fn deduct_gas(&mut self, amount: InternalGas) -> PartialVMResult<()> {
280 if !self.charge {
281 return Ok(());
282 }
283
284 match self.gas_left.checked_sub(amount) {
285 Some(gas_left) => {
286 self.gas_left = gas_left;
287 Ok(())
288 }
289 None => {
290 self.gas_left = InternalGas::new(0);
291 Err(PartialVMError::new(StatusCode::OUT_OF_GAS))
292 }
293 }
294 }
295
296 pub fn record_native_call(&mut self) {
297 self.num_native_calls = self.num_native_calls.saturating_add(1);
298 }
299
300 fn deduct_units(&mut self, amount: u64) -> PartialVMResult<()> {
302 self.deduct_gas(InternalGas::new(amount))
303 }
304
305 pub fn set_metering(&mut self, enabled: bool) {
306 self.charge = enabled
307 }
308
309 pub fn gas_used_pre_gas_price(&self) -> u64 {
312 let gas: Gas = match self.initial_budget.checked_sub(self.gas_left) {
313 Some(val) => InternalGas::to_unit_round_down(val),
314 None => InternalGas::to_unit_round_down(self.initial_budget),
315 };
316 u64::from(gas)
317 }
318
319 pub fn charge_bytes(&mut self, size: usize, cost_per_byte: u64) -> PartialVMResult<()> {
323 let computation_cost = size as u64 * cost_per_byte;
324 self.deduct_units(computation_cost)
325 }
326
327 pub fn gas_price(&self) -> u64 {
328 self.gas_price
329 }
330
331 pub fn stack_height_high_water_mark(&self) -> u64 {
332 self.stack_height_high_water_mark
333 }
334
335 pub fn stack_size_high_water_mark(&self) -> u64 {
336 self.stack_size_high_water_mark
337 }
338
339 pub fn instructions_executed(&self) -> u64 {
340 self.instructions_executed
341 }
342}
343
344pub fn zero_cost_schedule() -> CostTable {
345 let mut zero_tier = BTreeMap::new();
346 zero_tier.insert(0, 0);
347 CostTable {
348 instruction_tiers: zero_tier.clone(),
349 stack_size_tiers: zero_tier.clone(),
350 stack_height_tiers: zero_tier,
351 }
352}
353
354pub fn unit_cost_schedule() -> CostTable {
355 let mut unit_tier = BTreeMap::new();
356 unit_tier.insert(0, 1);
357 CostTable {
358 instruction_tiers: unit_tier.clone(),
359 stack_size_tiers: unit_tier.clone(),
360 stack_height_tiers: unit_tier,
361 }
362}
363
364pub fn initial_cost_schedule_v1() -> CostTable {
365 let instruction_tiers: BTreeMap<u64, u64> = vec![
366 (0, 1),
367 (20_000, 2),
368 (50_000, 10),
369 (100_000, 50),
370 (200_000, 100),
371 (10_000_000, 1000),
372 ]
373 .into_iter()
374 .collect();
375
376 let stack_height_tiers: BTreeMap<u64, u64> =
377 vec![(0, 1), (1_000, 2), (10_000, 10)].into_iter().collect();
378
379 let stack_size_tiers: BTreeMap<u64, u64> = vec![
380 (0, 1),
381 (100_000, 2), (500_000, 5), (1_000_000, 100), (100_000_000, 1000), ]
386 .into_iter()
387 .collect();
388
389 CostTable {
390 instruction_tiers,
391 stack_size_tiers,
392 stack_height_tiers,
393 }
394}
395
396pub fn initial_cost_schedule_for_unit_tests() -> move_vm_test_utils::gas_schedule::CostTable {
402 let table = initial_cost_schedule_v1();
403 move_vm_test_utils::gas_schedule::CostTable {
404 instruction_tiers: table.instruction_tiers,
405 stack_height_tiers: table.stack_height_tiers,
406 stack_size_tiers: table.stack_size_tiers,
407 }
408}