1pub use checked::*;
6
7#[sui_macros::with_checked_arithmetic]
8mod checked {
9 use crate::error::UserInputResult;
10 use crate::gas::{GasCostSummary, GasUsageReport, SuiGasStatusAPI};
11 pub use crate::gas_model::gas_common::PerObjectStorage;
12 use crate::gas_model::gas_common::{
13 StorageGas, check_gas_data, check_gas_objects, half_digits_rounding, sender_rebate,
14 };
15 use crate::gas_model::gas_predicates::{cost_table_for_version, txn_base_cost_as_multiplier};
16 use crate::gas_model::units_types::CostTable;
17 use crate::transaction::ObjectReadResult;
18 use crate::{
19 ObjectID,
20 error::ExecutionError,
21 execution_status::ExecutionErrorKind,
22 gas_model::tables::{GasStatus, ZERO_COST_SCHEDULE},
23 };
24 use move_core_types::vm_status::StatusCode;
25 use sui_protocol_config::*;
26
27 #[allow(dead_code)]
31 pub(crate) struct ComputationBucket {
32 min: u64,
33 max: u64,
34 cost: u64,
35 }
36
37 impl ComputationBucket {
38 fn new(min: u64, max: u64, cost: u64) -> Self {
39 ComputationBucket { min, max, cost }
40 }
41
42 fn simple(min: u64, max: u64) -> Self {
43 Self::new(min, max, max)
44 }
45 }
46
47 fn get_bucket_cost(table: &[ComputationBucket], computation_cost: u64) -> u64 {
48 for bucket in table {
49 if bucket.max >= computation_cost {
50 return bucket.cost;
51 }
52 }
53 match table.last() {
54 None => 5_000_000,
56 Some(bucket) => bucket.cost,
57 }
58 }
59
60 fn computation_bucket(max_bucket_cost: u64) -> Vec<ComputationBucket> {
63 assert!(max_bucket_cost >= 5_000_000);
64 vec![
65 ComputationBucket::simple(0, 1_000),
66 ComputationBucket::simple(1_000, 5_000),
67 ComputationBucket::simple(5_000, 10_000),
68 ComputationBucket::simple(10_000, 20_000),
69 ComputationBucket::simple(20_000, 50_000),
70 ComputationBucket::simple(50_000, 200_000),
71 ComputationBucket::simple(200_000, 1_000_000),
72 ComputationBucket::simple(1_000_000, max_bucket_cost),
73 ]
74 }
75
76 pub struct SuiCostTable {
78 pub(crate) min_transaction_cost: u64,
81 pub(crate) max_gas_budget: u64,
83 package_publish_per_byte_cost: u64,
87 object_read_per_byte_cost: u64,
90 storage_per_byte_cost: u64,
94 pub execution_cost_table: CostTable,
96 computation_bucket: Vec<ComputationBucket>,
98 max_gas_price_rgp_factor_for_aborted_transactions: Option<u64>,
100 }
101
102 impl std::fmt::Debug for SuiCostTable {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 write!(f, "SuiCostTable(...)")
106 }
107 }
108
109 impl SuiCostTable {
110 pub(crate) fn new(c: &ProtocolConfig, gas_price: u64) -> Self {
111 let min_transaction_cost = if txn_base_cost_as_multiplier(c) {
114 c.base_tx_cost_fixed() * gas_price
115 } else {
116 c.base_tx_cost_fixed()
117 };
118 Self {
119 min_transaction_cost,
120 max_gas_budget: c.max_tx_gas(),
121 package_publish_per_byte_cost: c.package_publish_cost_per_byte(),
122 object_read_per_byte_cost: c.obj_access_cost_read_per_byte(),
123 storage_per_byte_cost: c.obj_data_cost_refundable(),
124 execution_cost_table: cost_table_for_version(c.gas_model_version()),
125 computation_bucket: computation_bucket(c.max_gas_computation_bucket()),
126 max_gas_price_rgp_factor_for_aborted_transactions: c
127 .max_gas_price_rgp_factor_for_aborted_transactions_as_option(),
128 }
129 }
130
131 pub(crate) fn unmetered() -> Self {
132 Self {
133 min_transaction_cost: 0,
134 max_gas_budget: u64::MAX,
135 package_publish_per_byte_cost: 0,
136 object_read_per_byte_cost: 0,
137 storage_per_byte_cost: 0,
138 execution_cost_table: ZERO_COST_SCHEDULE.clone(),
139 computation_bucket: computation_bucket(5_000_000),
141 max_gas_price_rgp_factor_for_aborted_transactions: None,
142 }
143 }
144 }
145
146 #[derive(Debug, Clone, Copy)]
147 enum GasRoundingMode {
148 Bucketize,
150 Stepped(u64),
152 KeepHalfDigits,
154 }
155
156 #[allow(dead_code)]
157 #[derive(Debug)]
158 pub struct SuiGasStatus {
159 pub gas_status: GasStatus,
161 cost_table: SuiCostTable,
163 gas_budget: u64,
166 computation_cost: u64,
170 charge: bool,
172 gas_price: u64,
178 reference_gas_price: u64,
180 rebate_rate: u64,
182 storage: StorageGas,
185 gas_rounding_mode: GasRoundingMode,
187 }
188
189 impl SuiGasStatus {
190 fn new(
191 move_gas_status: GasStatus,
192 gas_budget: u64,
193 charge: bool,
194 gas_price: u64,
195 reference_gas_price: u64,
196 storage_gas_price: u64,
197 rebate_rate: u64,
198 gas_rounding_mode: GasRoundingMode,
199 cost_table: SuiCostTable,
200 ) -> SuiGasStatus {
201 let gas_rounding_mode = match gas_rounding_mode {
202 GasRoundingMode::Bucketize => GasRoundingMode::Bucketize,
203 GasRoundingMode::Stepped(val) => GasRoundingMode::Stepped(val.max(1)),
204 GasRoundingMode::KeepHalfDigits => GasRoundingMode::KeepHalfDigits,
205 };
206 SuiGasStatus {
207 gas_status: move_gas_status,
208 gas_budget,
209 charge,
210 computation_cost: 0,
211 gas_price,
212 reference_gas_price,
213 rebate_rate,
214 storage: StorageGas::new(storage_gas_price, cost_table.storage_per_byte_cost),
215 gas_rounding_mode,
216 cost_table,
217 }
218 }
219
220 pub(crate) fn new_with_budget(
221 gas_budget: u64,
222 gas_price: u64,
223 reference_gas_price: u64,
224 config: &ProtocolConfig,
225 ) -> SuiGasStatus {
226 let storage_gas_price = config.storage_gas_price();
227 let max_computation_budget = config.max_gas_computation_bucket() * gas_price;
228 let computation_budget = if gas_budget > max_computation_budget {
229 max_computation_budget
230 } else {
231 gas_budget
232 };
233 let sui_cost_table = SuiCostTable::new(config, gas_price);
234 let gas_rounding_mode = if config.gas_rounding_halve_digits() {
235 GasRoundingMode::KeepHalfDigits
236 } else if let Some(step) = config.gas_rounding_step_as_option() {
237 GasRoundingMode::Stepped(step)
238 } else {
239 GasRoundingMode::Bucketize
240 };
241 Self::new(
242 GasStatus::new(
243 sui_cost_table.execution_cost_table.clone(),
244 computation_budget,
245 gas_price,
246 config.gas_model_version(),
247 ),
248 gas_budget,
249 true,
250 gas_price,
251 reference_gas_price,
252 storage_gas_price,
253 config.storage_rebate_rate(),
254 gas_rounding_mode,
255 sui_cost_table,
256 )
257 }
258
259 pub fn new_unmetered() -> SuiGasStatus {
260 Self::new(
261 GasStatus::new_unmetered(),
262 0,
263 false,
264 0,
265 0,
266 0,
267 0,
268 GasRoundingMode::Bucketize,
269 SuiCostTable::unmetered(),
270 )
271 }
272
273 pub fn reference_gas_price(&self) -> u64 {
274 self.reference_gas_price
275 }
276
277 fn storage_cost(&self) -> u64 {
278 self.storage_gas_units()
279 }
280 }
281
282 impl SuiGasStatusAPI for SuiGasStatus {
283 fn is_unmetered(&self) -> bool {
284 !self.charge
285 }
286
287 fn move_gas_status(&self) -> &GasStatus {
288 &self.gas_status
289 }
290
291 fn move_gas_status_mut(&mut self) -> &mut GasStatus {
292 &mut self.gas_status
293 }
294
295 fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError> {
296 let gas_used = self.gas_status.gas_used_pre_gas_price();
297 let effective_gas_price = if let Some(max_gas_price_rgp_factor_for_aborted_transactions) =
298 self.cost_table
299 .max_gas_price_rgp_factor_for_aborted_transactions
300 && aborted.unwrap_or(false)
301 {
302 let max_gas_price_for_aborted_txns =
305 max_gas_price_rgp_factor_for_aborted_transactions * self.reference_gas_price;
306 self.gas_price.min(max_gas_price_for_aborted_txns)
307 } else {
308 self.gas_price
310 };
311 let gas_used = match self.gas_rounding_mode {
312 GasRoundingMode::KeepHalfDigits => {
313 half_digits_rounding(gas_used) * effective_gas_price
314 }
315 GasRoundingMode::Stepped(gas_rounding) => {
316 if gas_used > 0 && gas_used % gas_rounding == 0 {
317 gas_used * effective_gas_price
318 } else {
319 ((gas_used / gas_rounding) + 1) * gas_rounding * effective_gas_price
320 }
321 }
322 GasRoundingMode::Bucketize => {
323 let bucket_cost =
324 get_bucket_cost(&self.cost_table.computation_bucket, gas_used);
325 bucket_cost * effective_gas_price
328 }
329 };
330 if self.gas_budget <= gas_used {
331 self.computation_cost = self.gas_budget;
332 Err(ExecutionErrorKind::InsufficientGas.into())
333 } else {
334 self.computation_cost = gas_used;
335 Ok(())
336 }
337 }
338
339 fn summary(&self) -> GasCostSummary {
343 let storage_rebate = self.storage_rebate();
345 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
346 assert!(sender_rebate <= storage_rebate);
347 let non_refundable_storage_fee = storage_rebate - sender_rebate;
348 GasCostSummary {
349 computation_cost: self.computation_cost,
350 storage_cost: self.storage_cost(),
351 storage_rebate: sender_rebate,
352 non_refundable_storage_fee,
353 }
354 }
355
356 fn gas_budget(&self) -> u64 {
357 self.gas_budget
358 }
359
360 fn gas_price(&self) -> u64 {
361 self.gas_price
362 }
363
364 fn reference_gas_price(&self) -> u64 {
365 self.reference_gas_price
366 }
367
368 fn storage_gas_units(&self) -> u64 {
369 self.storage.storage_gas_units()
370 }
371
372 fn storage_rebate(&self) -> u64 {
373 self.storage.storage_rebate()
374 }
375
376 fn unmetered_storage_rebate(&self) -> u64 {
377 self.storage.unmetered_storage_rebate()
378 }
379
380 fn gas_used(&self) -> u64 {
381 self.gas_status.gas_used_pre_gas_price()
382 }
383
384 fn reset_storage_cost_and_rebate(&mut self) {
385 self.storage.reset();
386 }
387
388 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError> {
389 self.gas_status
390 .charge_bytes(size, self.cost_table.object_read_per_byte_cost)
391 .map_err(|e| {
392 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
393 ExecutionErrorKind::InsufficientGas.into()
394 })
395 }
396
397 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
398 self.gas_status
399 .charge_bytes(size, self.cost_table.package_publish_per_byte_cost)
400 .map_err(|e| {
401 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
402 ExecutionErrorKind::InsufficientGas.into()
403 })
404 }
405
406 fn track_storage_mutation(
411 &mut self,
412 object_id: ObjectID,
413 new_size: usize,
414 storage_rebate: u64,
415 ) -> Option<u64> {
416 let unmetered = self.is_unmetered();
417 self.storage
418 .track_mutation(object_id, new_size, storage_rebate, unmetered)
419 }
420
421 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError> {
422 let storage_rebate = self.storage_rebate();
423 let storage_cost = self.storage_cost();
424 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
425 assert!(sender_rebate <= storage_rebate);
426 if sender_rebate >= storage_cost {
427 Ok(())
430 } else {
431 let gas_left = self.gas_budget - self.computation_cost;
432 if gas_left < storage_cost - sender_rebate {
434 Err(ExecutionErrorKind::InsufficientGas.into())
438 } else {
439 Ok(())
440 }
441 }
442 }
443
444 fn adjust_computation_on_out_of_gas(&mut self) {
445 self.storage.reset();
446 self.computation_cost = self.gas_budget;
447 }
448
449 fn gas_usage_report(&self) -> GasUsageReport {
450 GasUsageReport {
451 cost_summary: self.summary(),
452 gas_used: self.gas_used(),
453 gas_price: self.gas_price(),
454 reference_gas_price: self.reference_gas_price(),
455 per_object_storage: self.per_object_storage().clone(),
456 gas_budget: self.gas_budget(),
457 storage_gas_price: self.storage.storage_gas_price,
458 rebate_rate: self.rebate_rate,
459 }
460 }
461
462 fn check_gas_balance(
467 &self,
468 gas_objs: &[&ObjectReadResult],
469 gas_budget: u64,
470 available_address_balance_gas: u64,
471 ) -> UserInputResult {
472 self.check_gas_objects(gas_objs)?;
473 check_gas_data(
474 gas_objs,
475 gas_budget,
476 available_address_balance_gas,
477 self.cost_table.min_transaction_cost,
478 self.cost_table.max_gas_budget,
479 )
480 }
481
482 fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult {
483 check_gas_objects(gas_objs)
484 }
485
486 fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
487 self.storage.per_object_storage()
488 }
489 }
490}