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,
17 execution_status::ExecutionErrorKind,
18 gas_model::tables::{GasStatus, ZERO_COST_SCHEDULE},
19 };
20 use move_core_types::vm_status::StatusCode;
21 use serde::{Deserialize, Serialize};
22 use sui_protocol_config::*;
23
24 #[allow(dead_code)]
28 pub(crate) struct ComputationBucket {
29 min: u64,
30 max: u64,
31 cost: u64,
32 }
33
34 impl ComputationBucket {
35 fn new(min: u64, max: u64, cost: u64) -> Self {
36 ComputationBucket { min, max, cost }
37 }
38
39 fn simple(min: u64, max: u64) -> Self {
40 Self::new(min, max, max)
41 }
42 }
43
44 fn get_bucket_cost(table: &[ComputationBucket], computation_cost: u64) -> u64 {
45 for bucket in table {
46 if bucket.max >= computation_cost {
47 return bucket.cost;
48 }
49 }
50 match table.last() {
51 None => 5_000_000,
53 Some(bucket) => bucket.cost,
54 }
55 }
56
57 fn computation_bucket(max_bucket_cost: u64) -> Vec<ComputationBucket> {
60 assert!(max_bucket_cost >= 5_000_000);
61 vec![
62 ComputationBucket::simple(0, 1_000),
63 ComputationBucket::simple(1_000, 5_000),
64 ComputationBucket::simple(5_000, 10_000),
65 ComputationBucket::simple(10_000, 20_000),
66 ComputationBucket::simple(20_000, 50_000),
67 ComputationBucket::simple(50_000, 200_000),
68 ComputationBucket::simple(200_000, 1_000_000),
69 ComputationBucket::simple(1_000_000, max_bucket_cost),
70 ]
71 }
72
73 fn sender_rebate(storage_rebate: u64, storage_rebate_rate: u64) -> u64 {
76 const BASIS_POINTS: u128 = 10000;
79 (((storage_rebate as u128 * storage_rebate_rate as u128)
80 + (BASIS_POINTS / 2)) / BASIS_POINTS) as u64
82 }
83
84 pub struct SuiCostTable {
86 pub(crate) min_transaction_cost: u64,
89 pub(crate) max_gas_budget: u64,
91 package_publish_per_byte_cost: u64,
95 object_read_per_byte_cost: u64,
98 storage_per_byte_cost: u64,
102 pub execution_cost_table: CostTable,
104 computation_bucket: Vec<ComputationBucket>,
106 max_gas_price_rgp_factor_for_aborted_transactions: Option<u64>,
108 }
109
110 impl std::fmt::Debug for SuiCostTable {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 write!(f, "SuiCostTable(...)")
114 }
115 }
116
117 impl SuiCostTable {
118 pub(crate) fn new(c: &ProtocolConfig, gas_price: u64) -> Self {
119 let min_transaction_cost = if txn_base_cost_as_multiplier(c) {
122 c.base_tx_cost_fixed() * gas_price
123 } else {
124 c.base_tx_cost_fixed()
125 };
126 Self {
127 min_transaction_cost,
128 max_gas_budget: c.max_tx_gas(),
129 package_publish_per_byte_cost: c.package_publish_cost_per_byte(),
130 object_read_per_byte_cost: c.obj_access_cost_read_per_byte(),
131 storage_per_byte_cost: c.obj_data_cost_refundable(),
132 execution_cost_table: cost_table_for_version(c.gas_model_version()),
133 computation_bucket: computation_bucket(c.max_gas_computation_bucket()),
134 max_gas_price_rgp_factor_for_aborted_transactions: c
135 .max_gas_price_rgp_factor_for_aborted_transactions_as_option(),
136 }
137 }
138
139 pub(crate) fn unmetered() -> Self {
140 Self {
141 min_transaction_cost: 0,
142 max_gas_budget: u64::MAX,
143 package_publish_per_byte_cost: 0,
144 object_read_per_byte_cost: 0,
145 storage_per_byte_cost: 0,
146 execution_cost_table: ZERO_COST_SCHEDULE.clone(),
147 computation_bucket: computation_bucket(5_000_000),
149 max_gas_price_rgp_factor_for_aborted_transactions: None,
150 }
151 }
152 }
153
154 #[derive(Debug, Clone, Serialize, Deserialize)]
155 pub struct PerObjectStorage {
156 pub storage_cost: u64,
162 pub storage_rebate: u64,
166 pub new_size: u64,
168 }
169
170 #[derive(Debug, Clone, Copy)]
171 enum GasRoundingMode {
172 Bucketize,
174 Stepped(u64),
176 KeepHalfDigits,
178 }
179
180 #[allow(dead_code)]
181 #[derive(Debug)]
182 pub struct SuiGasStatus {
183 pub gas_status: GasStatus,
185 cost_table: SuiCostTable,
187 gas_budget: u64,
190 computation_cost: u64,
194 charge: bool,
196 gas_price: u64,
202 reference_gas_price: u64,
204 storage_gas_price: u64,
212 per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
215 rebate_rate: u64,
217 unmetered_storage_rebate: u64,
220 gas_rounding_mode: GasRoundingMode,
222 }
223
224 impl SuiGasStatus {
225 fn new(
226 move_gas_status: GasStatus,
227 gas_budget: u64,
228 charge: bool,
229 gas_price: u64,
230 reference_gas_price: u64,
231 storage_gas_price: u64,
232 rebate_rate: u64,
233 gas_rounding_mode: GasRoundingMode,
234 cost_table: SuiCostTable,
235 ) -> SuiGasStatus {
236 let gas_rounding_mode = match gas_rounding_mode {
237 GasRoundingMode::Bucketize => GasRoundingMode::Bucketize,
238 GasRoundingMode::Stepped(val) => GasRoundingMode::Stepped(val.max(1)),
239 GasRoundingMode::KeepHalfDigits => GasRoundingMode::KeepHalfDigits,
240 };
241 SuiGasStatus {
242 gas_status: move_gas_status,
243 gas_budget,
244 charge,
245 computation_cost: 0,
246 gas_price,
247 reference_gas_price,
248 storage_gas_price,
249 per_object_storage: Vec::new(),
250 rebate_rate,
251 unmetered_storage_rebate: 0,
252 gas_rounding_mode,
253 cost_table,
254 }
255 }
256
257 pub(crate) fn new_with_budget(
258 gas_budget: u64,
259 gas_price: u64,
260 reference_gas_price: u64,
261 config: &ProtocolConfig,
262 ) -> SuiGasStatus {
263 let storage_gas_price = config.storage_gas_price();
264 let max_computation_budget = config.max_gas_computation_bucket() * gas_price;
265 let computation_budget = if gas_budget > max_computation_budget {
266 max_computation_budget
267 } else {
268 gas_budget
269 };
270 let sui_cost_table = SuiCostTable::new(config, gas_price);
271 let gas_rounding_mode = if config.gas_rounding_halve_digits() {
272 GasRoundingMode::KeepHalfDigits
273 } else if let Some(step) = config.gas_rounding_step_as_option() {
274 GasRoundingMode::Stepped(step)
275 } else {
276 GasRoundingMode::Bucketize
277 };
278 Self::new(
279 GasStatus::new(
280 sui_cost_table.execution_cost_table.clone(),
281 computation_budget,
282 gas_price,
283 config.gas_model_version(),
284 ),
285 gas_budget,
286 true,
287 gas_price,
288 reference_gas_price,
289 storage_gas_price,
290 config.storage_rebate_rate(),
291 gas_rounding_mode,
292 sui_cost_table,
293 )
294 }
295
296 pub fn new_unmetered() -> SuiGasStatus {
297 Self::new(
298 GasStatus::new_unmetered(),
299 0,
300 false,
301 0,
302 0,
303 0,
304 0,
305 GasRoundingMode::Bucketize,
306 SuiCostTable::unmetered(),
307 )
308 }
309
310 pub fn reference_gas_price(&self) -> u64 {
311 self.reference_gas_price
312 }
313
314 fn check_gas_data(
316 &self,
317 gas_objs: &[&ObjectReadResult],
318 gas_budget: u64,
319 available_address_balance_gas: u64,
320 ) -> UserInputResult {
321 if gas_budget > self.cost_table.max_gas_budget {
323 return Err(UserInputError::GasBudgetTooHigh {
324 gas_budget,
325 max_budget: self.cost_table.max_gas_budget,
326 });
327 }
328 if gas_budget < self.cost_table.min_transaction_cost {
329 return Err(UserInputError::GasBudgetTooLow {
330 gas_budget,
331 min_budget: self.cost_table.min_transaction_cost,
332 });
333 }
334
335 let mut gas_balance = available_address_balance_gas as u128;
337 for gas_obj in gas_objs {
338 gas_balance += gas::get_gas_balance(gas_obj.as_object().ok_or(
339 UserInputError::InvalidGasObject {
340 object_id: gas_obj.id(),
341 },
342 )?)? as u128;
343 }
344 if gas_balance < gas_budget as u128 {
345 Err(UserInputError::GasBalanceTooLow {
346 gas_balance,
347 needed_gas_amount: gas_budget as u128,
348 })
349 } else {
350 Ok(())
351 }
352 }
353
354 fn storage_cost(&self) -> u64 {
355 self.storage_gas_units()
356 }
357 }
358
359 impl SuiGasStatusAPI for SuiGasStatus {
360 fn is_unmetered(&self) -> bool {
361 !self.charge
362 }
363
364 fn move_gas_status(&self) -> &GasStatus {
365 &self.gas_status
366 }
367
368 fn move_gas_status_mut(&mut self) -> &mut GasStatus {
369 &mut self.gas_status
370 }
371
372 fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError> {
373 let gas_used = self.gas_status.gas_used_pre_gas_price();
374 let effective_gas_price = if self
375 .cost_table
376 .max_gas_price_rgp_factor_for_aborted_transactions
377 .is_some()
378 && aborted.unwrap_or(false)
379 {
380 let max_gas_price_for_aborted_txns = self
383 .cost_table
384 .max_gas_price_rgp_factor_for_aborted_transactions
385 .unwrap()
386 * self.reference_gas_price;
387 self.gas_price.min(max_gas_price_for_aborted_txns)
388 } else {
389 self.gas_price
391 };
392 let gas_used = match self.gas_rounding_mode {
393 GasRoundingMode::KeepHalfDigits => {
394 half_digits_rounding(gas_used) * effective_gas_price
395 }
396 GasRoundingMode::Stepped(gas_rounding) => {
397 if gas_used > 0 && gas_used % gas_rounding == 0 {
398 gas_used * effective_gas_price
399 } else {
400 ((gas_used / gas_rounding) + 1) * gas_rounding * effective_gas_price
401 }
402 }
403 GasRoundingMode::Bucketize => {
404 let bucket_cost =
405 get_bucket_cost(&self.cost_table.computation_bucket, gas_used);
406 bucket_cost * effective_gas_price
409 }
410 };
411 if self.gas_budget <= gas_used {
412 self.computation_cost = self.gas_budget;
413 Err(ExecutionErrorKind::InsufficientGas.into())
414 } else {
415 self.computation_cost = gas_used;
416 Ok(())
417 }
418 }
419
420 fn summary(&self) -> GasCostSummary {
424 let storage_rebate = self.storage_rebate();
426 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
427 assert!(sender_rebate <= storage_rebate);
428 let non_refundable_storage_fee = storage_rebate - sender_rebate;
429 GasCostSummary {
430 computation_cost: self.computation_cost,
431 storage_cost: self.storage_cost(),
432 storage_rebate: sender_rebate,
433 non_refundable_storage_fee,
434 }
435 }
436
437 fn gas_budget(&self) -> u64 {
438 self.gas_budget
439 }
440
441 fn gas_price(&self) -> u64 {
442 self.gas_price
443 }
444
445 fn reference_gas_price(&self) -> u64 {
446 self.reference_gas_price
447 }
448
449 fn storage_gas_units(&self) -> u64 {
450 self.per_object_storage
451 .iter()
452 .map(|(_, per_object)| per_object.storage_cost)
453 .sum()
454 }
455
456 fn storage_rebate(&self) -> u64 {
457 self.per_object_storage
458 .iter()
459 .map(|(_, per_object)| per_object.storage_rebate)
460 .sum()
461 }
462
463 fn unmetered_storage_rebate(&self) -> u64 {
464 self.unmetered_storage_rebate
465 }
466
467 fn gas_used(&self) -> u64 {
468 self.gas_status.gas_used_pre_gas_price()
469 }
470
471 fn reset_storage_cost_and_rebate(&mut self) {
472 self.per_object_storage = Vec::new();
473 self.unmetered_storage_rebate = 0;
474 }
475
476 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError> {
477 self.gas_status
478 .charge_bytes(size, self.cost_table.object_read_per_byte_cost)
479 .map_err(|e| {
480 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
481 ExecutionErrorKind::InsufficientGas.into()
482 })
483 }
484
485 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
486 self.gas_status
487 .charge_bytes(size, self.cost_table.package_publish_per_byte_cost)
488 .map_err(|e| {
489 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
490 ExecutionErrorKind::InsufficientGas.into()
491 })
492 }
493
494 fn track_storage_mutation(
499 &mut self,
500 object_id: ObjectID,
501 new_size: usize,
502 storage_rebate: u64,
503 ) -> u64 {
504 if self.is_unmetered() {
505 self.unmetered_storage_rebate += storage_rebate;
506 return 0;
507 }
508
509 let new_size = new_size as u64;
511 let storage_cost =
512 new_size * self.cost_table.storage_per_byte_cost * self.storage_gas_price;
513 self.per_object_storage.push((
516 object_id,
517 PerObjectStorage {
518 storage_cost,
519 storage_rebate,
520 new_size,
521 },
522 ));
523 storage_cost
525 }
526
527 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError> {
528 let storage_rebate = self.storage_rebate();
529 let storage_cost = self.storage_cost();
530 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
531 assert!(sender_rebate <= storage_rebate);
532 if sender_rebate >= storage_cost {
533 Ok(())
536 } else {
537 let gas_left = self.gas_budget - self.computation_cost;
538 if gas_left < storage_cost - sender_rebate {
540 Err(ExecutionErrorKind::InsufficientGas.into())
544 } else {
545 Ok(())
546 }
547 }
548 }
549
550 fn adjust_computation_on_out_of_gas(&mut self) {
551 self.per_object_storage = Vec::new();
552 self.computation_cost = self.gas_budget;
553 }
554
555 fn gas_usage_report(&self) -> GasUsageReport {
556 GasUsageReport {
557 cost_summary: self.summary(),
558 gas_used: self.gas_used(),
559 gas_price: self.gas_price(),
560 reference_gas_price: self.reference_gas_price(),
561 per_object_storage: self.per_object_storage().clone(),
562 gas_budget: self.gas_budget(),
563 storage_gas_price: self.storage_gas_price,
564 rebate_rate: self.rebate_rate,
565 }
566 }
567
568 fn check_gas_balance(
573 &self,
574 gas_objs: &[&ObjectReadResult],
575 gas_budget: u64,
576 available_address_balance_gas: u64,
577 ) -> UserInputResult {
578 self.check_gas_objects(gas_objs)?;
579 self.check_gas_data(gas_objs, gas_budget, available_address_balance_gas)
580 }
581
582 fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult {
584 for gas_object in gas_objs {
587 if let Some(obj) = gas_object.as_object() {
590 if !obj.is_address_owned() {
591 return Err(UserInputError::GasObjectNotOwnedObject {
592 owner: obj.owner.clone(),
593 });
594 }
595 } else {
596 return Err(UserInputError::MissingGasPayment);
599 }
600 }
601 Ok(())
602 }
603
604 fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
605 &self.per_object_storage
606 }
607 }
608
609 fn half_digits_rounding(n: u64) -> u64 {
610 if n < 1000 {
611 return 1000;
612 }
613 let digits = n.ilog10();
614 let drop = digits / 2;
615 let base = 10u64.pow(drop);
616 n.div_ceil(base) * base
617 }
618
619 #[test]
620 fn test_half_digits_rounding() {
621 assert_eq!(half_digits_rounding(0), 1000);
622 assert_eq!(half_digits_rounding(1), 1000);
623 assert_eq!(half_digits_rounding(999), 1000);
624 assert_eq!(half_digits_rounding(1000), 1000);
625 assert_eq!(half_digits_rounding(1001), 1010);
626 assert_eq!(half_digits_rounding(1050), 1050);
627 assert_eq!(half_digits_rounding(1999), 2000);
628 assert_eq!(half_digits_rounding(20_000), 20_000);
629 assert_eq!(half_digits_rounding(20_001), 20_100);
630 assert_eq!(half_digits_rounding(20_500), 20_500);
631 assert_eq!(half_digits_rounding(29_999), 30_000);
632 assert_eq!(half_digits_rounding(300_000), 300_000);
633 assert_eq!(half_digits_rounding(300_001), 300_100);
634 assert_eq!(half_digits_rounding(305_500), 305_500);
635 assert_eq!(half_digits_rounding(305_501), 305_600);
636 assert_eq!(half_digits_rounding(999_999), 1_000_000);
637 assert_eq!(half_digits_rounding(1_000_000), 1_000_000);
638 assert_eq!(half_digits_rounding(1_000_001), 1_001_000);
639 assert_eq!(half_digits_rounding(1_005_000), 1_005_000);
640 assert_eq!(half_digits_rounding(1_005_001), 1_006_000);
641 assert_eq!(half_digits_rounding(1_999_999), 2_000_000);
642 assert_eq!(half_digits_rounding(10_000_001), 10_001_000);
643 assert_eq!(half_digits_rounding(100_000_001), 100_010_000);
644 }
645}