1pub use checked::*;
6
7#[sui_macros::with_checked_arithmetic]
8mod checked {
9 use crate::error::{UserInputError, UserInputResult};
10 use crate::gas::{self, GasCostSummary, GasUsageReport, SuiGasStatusAPI};
11 use crate::gas_model::gas_predicates::{cost_table_for_version, txn_base_cost_as_multiplier};
12 use crate::gas_model::units_types::CostTable;
13 use crate::transaction::ObjectReadResult;
14 use crate::{
15 ObjectID,
16 error::{ExecutionError, ExecutionErrorKind},
17 gas_model::tables::{GasStatus, ZERO_COST_SCHEDULE},
18 };
19 use move_core_types::vm_status::StatusCode;
20 use serde::{Deserialize, Serialize};
21 use sui_protocol_config::*;
22
23 #[allow(dead_code)]
27 pub(crate) struct ComputationBucket {
28 min: u64,
29 max: u64,
30 cost: u64,
31 }
32
33 impl ComputationBucket {
34 fn new(min: u64, max: u64, cost: u64) -> Self {
35 ComputationBucket { min, max, cost }
36 }
37
38 fn simple(min: u64, max: u64) -> Self {
39 Self::new(min, max, max)
40 }
41 }
42
43 fn get_bucket_cost(table: &[ComputationBucket], computation_cost: u64) -> u64 {
44 for bucket in table {
45 if bucket.max >= computation_cost {
46 return bucket.cost;
47 }
48 }
49 match table.last() {
50 None => 5_000_000,
52 Some(bucket) => bucket.cost,
53 }
54 }
55
56 fn computation_bucket(max_bucket_cost: u64) -> Vec<ComputationBucket> {
59 assert!(max_bucket_cost >= 5_000_000);
60 vec![
61 ComputationBucket::simple(0, 1_000),
62 ComputationBucket::simple(1_000, 5_000),
63 ComputationBucket::simple(5_000, 10_000),
64 ComputationBucket::simple(10_000, 20_000),
65 ComputationBucket::simple(20_000, 50_000),
66 ComputationBucket::simple(50_000, 200_000),
67 ComputationBucket::simple(200_000, 1_000_000),
68 ComputationBucket::simple(1_000_000, max_bucket_cost),
69 ]
70 }
71
72 fn sender_rebate(storage_rebate: u64, storage_rebate_rate: u64) -> u64 {
75 const BASIS_POINTS: u128 = 10000;
78 (((storage_rebate as u128 * storage_rebate_rate as u128)
79 + (BASIS_POINTS / 2)) / BASIS_POINTS) as u64
81 }
82
83 pub struct SuiCostTable {
85 pub(crate) min_transaction_cost: u64,
88 pub(crate) max_gas_budget: u64,
90 package_publish_per_byte_cost: u64,
94 object_read_per_byte_cost: u64,
97 storage_per_byte_cost: u64,
101 pub execution_cost_table: CostTable,
103 computation_bucket: Vec<ComputationBucket>,
105 max_gas_price_rgp_factor_for_aborted_transactions: Option<u64>,
107 }
108
109 impl std::fmt::Debug for SuiCostTable {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 write!(f, "SuiCostTable(...)")
113 }
114 }
115
116 impl SuiCostTable {
117 pub(crate) fn new(c: &ProtocolConfig, gas_price: u64) -> Self {
118 let min_transaction_cost = if txn_base_cost_as_multiplier(c) {
121 c.base_tx_cost_fixed() * gas_price
122 } else {
123 c.base_tx_cost_fixed()
124 };
125 Self {
126 min_transaction_cost,
127 max_gas_budget: c.max_tx_gas(),
128 package_publish_per_byte_cost: c.package_publish_cost_per_byte(),
129 object_read_per_byte_cost: c.obj_access_cost_read_per_byte(),
130 storage_per_byte_cost: c.obj_data_cost_refundable(),
131 execution_cost_table: cost_table_for_version(c.gas_model_version()),
132 computation_bucket: computation_bucket(c.max_gas_computation_bucket()),
133 max_gas_price_rgp_factor_for_aborted_transactions: c
134 .max_gas_price_rgp_factor_for_aborted_transactions_as_option(),
135 }
136 }
137
138 pub(crate) fn unmetered() -> Self {
139 Self {
140 min_transaction_cost: 0,
141 max_gas_budget: u64::MAX,
142 package_publish_per_byte_cost: 0,
143 object_read_per_byte_cost: 0,
144 storage_per_byte_cost: 0,
145 execution_cost_table: ZERO_COST_SCHEDULE.clone(),
146 computation_bucket: computation_bucket(5_000_000),
148 max_gas_price_rgp_factor_for_aborted_transactions: None,
149 }
150 }
151 }
152
153 #[derive(Debug, Clone, Serialize, Deserialize)]
154 pub struct PerObjectStorage {
155 pub storage_cost: u64,
161 pub storage_rebate: u64,
165 pub new_size: u64,
167 }
168
169 #[derive(Debug, Clone, Copy)]
170 enum GasRoundingMode {
171 Bucketize,
173 Stepped(u64),
175 KeepHalfDigits,
177 }
178
179 #[allow(dead_code)]
180 #[derive(Debug)]
181 pub struct SuiGasStatus {
182 pub gas_status: GasStatus,
184 cost_table: SuiCostTable,
186 gas_budget: u64,
189 computation_cost: u64,
193 charge: bool,
195 gas_price: u64,
201 reference_gas_price: u64,
203 storage_gas_price: u64,
211 per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
214 rebate_rate: u64,
216 unmetered_storage_rebate: u64,
219 gas_rounding_mode: GasRoundingMode,
221 }
222
223 impl SuiGasStatus {
224 fn new(
225 move_gas_status: GasStatus,
226 gas_budget: u64,
227 charge: bool,
228 gas_price: u64,
229 reference_gas_price: u64,
230 storage_gas_price: u64,
231 rebate_rate: u64,
232 gas_rounding_mode: GasRoundingMode,
233 cost_table: SuiCostTable,
234 ) -> SuiGasStatus {
235 let gas_rounding_mode = match gas_rounding_mode {
236 GasRoundingMode::Bucketize => GasRoundingMode::Bucketize,
237 GasRoundingMode::Stepped(val) => GasRoundingMode::Stepped(val.max(1)),
238 GasRoundingMode::KeepHalfDigits => GasRoundingMode::KeepHalfDigits,
239 };
240 SuiGasStatus {
241 gas_status: move_gas_status,
242 gas_budget,
243 charge,
244 computation_cost: 0,
245 gas_price,
246 reference_gas_price,
247 storage_gas_price,
248 per_object_storage: Vec::new(),
249 rebate_rate,
250 unmetered_storage_rebate: 0,
251 gas_rounding_mode,
252 cost_table,
253 }
254 }
255
256 pub(crate) fn new_with_budget(
257 gas_budget: u64,
258 gas_price: u64,
259 reference_gas_price: u64,
260 config: &ProtocolConfig,
261 ) -> SuiGasStatus {
262 let storage_gas_price = config.storage_gas_price();
263 let max_computation_budget = config.max_gas_computation_bucket() * gas_price;
264 let computation_budget = if gas_budget > max_computation_budget {
265 max_computation_budget
266 } else {
267 gas_budget
268 };
269 let sui_cost_table = SuiCostTable::new(config, gas_price);
270 let gas_rounding_mode = if config.gas_rounding_halve_digits() {
271 GasRoundingMode::KeepHalfDigits
272 } else if let Some(step) = config.gas_rounding_step_as_option() {
273 GasRoundingMode::Stepped(step)
274 } else {
275 GasRoundingMode::Bucketize
276 };
277 Self::new(
278 GasStatus::new(
279 sui_cost_table.execution_cost_table.clone(),
280 computation_budget,
281 gas_price,
282 config.gas_model_version(),
283 ),
284 gas_budget,
285 true,
286 gas_price,
287 reference_gas_price,
288 storage_gas_price,
289 config.storage_rebate_rate(),
290 gas_rounding_mode,
291 sui_cost_table,
292 )
293 }
294
295 pub fn new_unmetered() -> SuiGasStatus {
296 Self::new(
297 GasStatus::new_unmetered(),
298 0,
299 false,
300 0,
301 0,
302 0,
303 0,
304 GasRoundingMode::Bucketize,
305 SuiCostTable::unmetered(),
306 )
307 }
308
309 pub fn reference_gas_price(&self) -> u64 {
310 self.reference_gas_price
311 }
312
313 pub(crate) fn check_gas_balance(
318 &self,
319 gas_objs: &[&ObjectReadResult],
320 gas_budget: u64,
321 ) -> UserInputResult {
322 for gas_object in gas_objs {
324 if let Some(obj) = gas_object.as_object() {
327 if !obj.is_address_owned() {
328 return Err(UserInputError::GasObjectNotOwnedObject {
329 owner: obj.owner.clone(),
330 });
331 }
332 } else {
333 return Err(UserInputError::MissingGasPayment);
336 }
337 }
338
339 if gas_budget > self.cost_table.max_gas_budget {
341 return Err(UserInputError::GasBudgetTooHigh {
342 gas_budget,
343 max_budget: self.cost_table.max_gas_budget,
344 });
345 }
346 if gas_budget < self.cost_table.min_transaction_cost {
347 return Err(UserInputError::GasBudgetTooLow {
348 gas_budget,
349 min_budget: self.cost_table.min_transaction_cost,
350 });
351 }
352
353 let mut gas_balance = 0u128;
355 for gas_obj in gas_objs {
356 gas_balance +=
358 gas::get_gas_balance(gas_obj.as_object().expect("object must be owned"))?
359 as u128;
360 }
361 if gas_balance < gas_budget as u128 {
362 Err(UserInputError::GasBalanceTooLow {
363 gas_balance,
364 needed_gas_amount: gas_budget as u128,
365 })
366 } else {
367 Ok(())
368 }
369 }
370
371 fn storage_cost(&self) -> u64 {
372 self.storage_gas_units()
373 }
374
375 pub fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
376 &self.per_object_storage
377 }
378 }
379
380 impl SuiGasStatusAPI for SuiGasStatus {
381 fn is_unmetered(&self) -> bool {
382 !self.charge
383 }
384
385 fn move_gas_status(&self) -> &GasStatus {
386 &self.gas_status
387 }
388
389 fn move_gas_status_mut(&mut self) -> &mut GasStatus {
390 &mut self.gas_status
391 }
392
393 fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError> {
394 let gas_used = self.gas_status.gas_used_pre_gas_price();
395 let effective_gas_price = if self
396 .cost_table
397 .max_gas_price_rgp_factor_for_aborted_transactions
398 .is_some()
399 && aborted.unwrap_or(false)
400 {
401 let max_gas_price_for_aborted_txns = self
404 .cost_table
405 .max_gas_price_rgp_factor_for_aborted_transactions
406 .unwrap()
407 * self.reference_gas_price;
408 self.gas_price.min(max_gas_price_for_aborted_txns)
409 } else {
410 self.gas_price
412 };
413 let gas_used = match self.gas_rounding_mode {
414 GasRoundingMode::KeepHalfDigits => {
415 half_digits_rounding(gas_used) * effective_gas_price
416 }
417 GasRoundingMode::Stepped(gas_rounding) => {
418 if gas_used > 0 && gas_used % gas_rounding == 0 {
419 gas_used * effective_gas_price
420 } else {
421 ((gas_used / gas_rounding) + 1) * gas_rounding * effective_gas_price
422 }
423 }
424 GasRoundingMode::Bucketize => {
425 let bucket_cost =
426 get_bucket_cost(&self.cost_table.computation_bucket, gas_used);
427 bucket_cost * effective_gas_price
430 }
431 };
432 if self.gas_budget <= gas_used {
433 self.computation_cost = self.gas_budget;
434 Err(ExecutionErrorKind::InsufficientGas.into())
435 } else {
436 self.computation_cost = gas_used;
437 Ok(())
438 }
439 }
440
441 fn summary(&self) -> GasCostSummary {
445 let storage_rebate = self.storage_rebate();
447 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
448 assert!(sender_rebate <= storage_rebate);
449 let non_refundable_storage_fee = storage_rebate - sender_rebate;
450 GasCostSummary {
451 computation_cost: self.computation_cost,
452 storage_cost: self.storage_cost(),
453 storage_rebate: sender_rebate,
454 non_refundable_storage_fee,
455 }
456 }
457
458 fn gas_budget(&self) -> u64 {
459 self.gas_budget
460 }
461
462 fn gas_price(&self) -> u64 {
463 self.gas_price
464 }
465
466 fn reference_gas_price(&self) -> u64 {
467 self.reference_gas_price
468 }
469
470 fn storage_gas_units(&self) -> u64 {
471 self.per_object_storage
472 .iter()
473 .map(|(_, per_object)| per_object.storage_cost)
474 .sum()
475 }
476
477 fn storage_rebate(&self) -> u64 {
478 self.per_object_storage
479 .iter()
480 .map(|(_, per_object)| per_object.storage_rebate)
481 .sum()
482 }
483
484 fn unmetered_storage_rebate(&self) -> u64 {
485 self.unmetered_storage_rebate
486 }
487
488 fn gas_used(&self) -> u64 {
489 self.gas_status.gas_used_pre_gas_price()
490 }
491
492 fn reset_storage_cost_and_rebate(&mut self) {
493 self.per_object_storage = Vec::new();
494 self.unmetered_storage_rebate = 0;
495 }
496
497 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError> {
498 self.gas_status
499 .charge_bytes(size, self.cost_table.object_read_per_byte_cost)
500 .map_err(|e| {
501 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
502 ExecutionErrorKind::InsufficientGas.into()
503 })
504 }
505
506 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
507 self.gas_status
508 .charge_bytes(size, self.cost_table.package_publish_per_byte_cost)
509 .map_err(|e| {
510 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
511 ExecutionErrorKind::InsufficientGas.into()
512 })
513 }
514
515 fn track_storage_mutation(
520 &mut self,
521 object_id: ObjectID,
522 new_size: usize,
523 storage_rebate: u64,
524 ) -> u64 {
525 if self.is_unmetered() {
526 self.unmetered_storage_rebate += storage_rebate;
527 return 0;
528 }
529
530 let new_size = new_size as u64;
532 let storage_cost =
533 new_size * self.cost_table.storage_per_byte_cost * self.storage_gas_price;
534 self.per_object_storage.push((
537 object_id,
538 PerObjectStorage {
539 storage_cost,
540 storage_rebate,
541 new_size,
542 },
543 ));
544 storage_cost
546 }
547
548 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError> {
549 let storage_rebate = self.storage_rebate();
550 let storage_cost = self.storage_cost();
551 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
552 assert!(sender_rebate <= storage_rebate);
553 if sender_rebate >= storage_cost {
554 Ok(())
557 } else {
558 let gas_left = self.gas_budget - self.computation_cost;
559 if gas_left < storage_cost - sender_rebate {
561 Err(ExecutionErrorKind::InsufficientGas.into())
565 } else {
566 Ok(())
567 }
568 }
569 }
570
571 fn adjust_computation_on_out_of_gas(&mut self) {
572 self.per_object_storage = Vec::new();
573 self.computation_cost = self.gas_budget;
574 }
575
576 fn gas_usage_report(&self) -> GasUsageReport {
577 GasUsageReport {
578 cost_summary: self.summary(),
579 gas_used: self.gas_used(),
580 gas_price: self.gas_price(),
581 reference_gas_price: self.reference_gas_price(),
582 per_object_storage: self.per_object_storage().clone(),
583 gas_budget: self.gas_budget(),
584 storage_gas_price: self.storage_gas_price,
585 rebate_rate: self.rebate_rate,
586 }
587 }
588 }
589
590 fn half_digits_rounding(n: u64) -> u64 {
591 if n < 1000 {
592 return 1000;
593 }
594 let digits = n.ilog10();
595 let drop = digits / 2;
596 let base = 10u64.pow(drop);
597 n.div_ceil(base) * base
598 }
599
600 #[test]
601 fn test_half_digits_rounding() {
602 assert_eq!(half_digits_rounding(0), 1000);
603 assert_eq!(half_digits_rounding(1), 1000);
604 assert_eq!(half_digits_rounding(999), 1000);
605 assert_eq!(half_digits_rounding(1000), 1000);
606 assert_eq!(half_digits_rounding(1001), 1010);
607 assert_eq!(half_digits_rounding(1050), 1050);
608 assert_eq!(half_digits_rounding(1999), 2000);
609 assert_eq!(half_digits_rounding(20_000), 20_000);
610 assert_eq!(half_digits_rounding(20_001), 20_100);
611 assert_eq!(half_digits_rounding(20_500), 20_500);
612 assert_eq!(half_digits_rounding(29_999), 30_000);
613 assert_eq!(half_digits_rounding(300_000), 300_000);
614 assert_eq!(half_digits_rounding(300_001), 300_100);
615 assert_eq!(half_digits_rounding(305_500), 305_500);
616 assert_eq!(half_digits_rounding(305_501), 305_600);
617 assert_eq!(half_digits_rounding(999_999), 1_000_000);
618 assert_eq!(half_digits_rounding(1_000_000), 1_000_000);
619 assert_eq!(half_digits_rounding(1_000_001), 1_001_000);
620 assert_eq!(half_digits_rounding(1_005_000), 1_005_000);
621 assert_eq!(half_digits_rounding(1_005_001), 1_006_000);
622 assert_eq!(half_digits_rounding(1_999_999), 2_000_000);
623 assert_eq!(half_digits_rounding(10_000_001), 10_001_000);
624 assert_eq!(half_digits_rounding(100_000_001), 100_010_000);
625 }
626}