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 pub(crate) fn check_gas_balance(
319 &self,
320 gas_objs: &[&ObjectReadResult],
321 gas_budget: u64,
322 ) -> UserInputResult {
323 self.check_gas_objects(gas_objs)?;
324 self.check_gas_data(gas_objs, gas_budget)
325 }
326
327 pub(crate) fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult {
329 for gas_object in gas_objs {
331 if let Some(obj) = gas_object.as_object() {
334 if !obj.is_address_owned() {
335 return Err(UserInputError::GasObjectNotOwnedObject {
336 owner: obj.owner.clone(),
337 });
338 }
339 } else {
340 return Err(UserInputError::MissingGasPayment);
343 }
344 }
345 Ok(())
346 }
347
348 pub(crate) fn check_gas_data(
350 &self,
351 gas_objs: &[&ObjectReadResult],
352 gas_budget: u64,
353 ) -> UserInputResult {
354 if gas_budget > self.cost_table.max_gas_budget {
356 return Err(UserInputError::GasBudgetTooHigh {
357 gas_budget,
358 max_budget: self.cost_table.max_gas_budget,
359 });
360 }
361 if gas_budget < self.cost_table.min_transaction_cost {
362 return Err(UserInputError::GasBudgetTooLow {
363 gas_budget,
364 min_budget: self.cost_table.min_transaction_cost,
365 });
366 }
367
368 let mut gas_balance = 0u128;
370 for gas_obj in gas_objs {
371 gas_balance += gas::get_gas_balance(gas_obj.as_object().ok_or(
372 UserInputError::InvalidGasObject {
373 object_id: gas_obj.id(),
374 },
375 )?)? as u128;
376 }
377 if gas_balance < gas_budget as u128 {
378 Err(UserInputError::GasBalanceTooLow {
379 gas_balance,
380 needed_gas_amount: gas_budget as u128,
381 })
382 } else {
383 Ok(())
384 }
385 }
386
387 fn storage_cost(&self) -> u64 {
388 self.storage_gas_units()
389 }
390
391 pub fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
392 &self.per_object_storage
393 }
394 }
395
396 impl SuiGasStatusAPI for SuiGasStatus {
397 fn is_unmetered(&self) -> bool {
398 !self.charge
399 }
400
401 fn move_gas_status(&self) -> &GasStatus {
402 &self.gas_status
403 }
404
405 fn move_gas_status_mut(&mut self) -> &mut GasStatus {
406 &mut self.gas_status
407 }
408
409 fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError> {
410 let gas_used = self.gas_status.gas_used_pre_gas_price();
411 let effective_gas_price = if self
412 .cost_table
413 .max_gas_price_rgp_factor_for_aborted_transactions
414 .is_some()
415 && aborted.unwrap_or(false)
416 {
417 let max_gas_price_for_aborted_txns = self
420 .cost_table
421 .max_gas_price_rgp_factor_for_aborted_transactions
422 .unwrap()
423 * self.reference_gas_price;
424 self.gas_price.min(max_gas_price_for_aborted_txns)
425 } else {
426 self.gas_price
428 };
429 let gas_used = match self.gas_rounding_mode {
430 GasRoundingMode::KeepHalfDigits => {
431 half_digits_rounding(gas_used) * effective_gas_price
432 }
433 GasRoundingMode::Stepped(gas_rounding) => {
434 if gas_used > 0 && gas_used % gas_rounding == 0 {
435 gas_used * effective_gas_price
436 } else {
437 ((gas_used / gas_rounding) + 1) * gas_rounding * effective_gas_price
438 }
439 }
440 GasRoundingMode::Bucketize => {
441 let bucket_cost =
442 get_bucket_cost(&self.cost_table.computation_bucket, gas_used);
443 bucket_cost * effective_gas_price
446 }
447 };
448 if self.gas_budget <= gas_used {
449 self.computation_cost = self.gas_budget;
450 Err(ExecutionErrorKind::InsufficientGas.into())
451 } else {
452 self.computation_cost = gas_used;
453 Ok(())
454 }
455 }
456
457 fn summary(&self) -> GasCostSummary {
461 let storage_rebate = self.storage_rebate();
463 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
464 assert!(sender_rebate <= storage_rebate);
465 let non_refundable_storage_fee = storage_rebate - sender_rebate;
466 GasCostSummary {
467 computation_cost: self.computation_cost,
468 storage_cost: self.storage_cost(),
469 storage_rebate: sender_rebate,
470 non_refundable_storage_fee,
471 }
472 }
473
474 fn gas_budget(&self) -> u64 {
475 self.gas_budget
476 }
477
478 fn gas_price(&self) -> u64 {
479 self.gas_price
480 }
481
482 fn reference_gas_price(&self) -> u64 {
483 self.reference_gas_price
484 }
485
486 fn storage_gas_units(&self) -> u64 {
487 self.per_object_storage
488 .iter()
489 .map(|(_, per_object)| per_object.storage_cost)
490 .sum()
491 }
492
493 fn storage_rebate(&self) -> u64 {
494 self.per_object_storage
495 .iter()
496 .map(|(_, per_object)| per_object.storage_rebate)
497 .sum()
498 }
499
500 fn unmetered_storage_rebate(&self) -> u64 {
501 self.unmetered_storage_rebate
502 }
503
504 fn gas_used(&self) -> u64 {
505 self.gas_status.gas_used_pre_gas_price()
506 }
507
508 fn reset_storage_cost_and_rebate(&mut self) {
509 self.per_object_storage = Vec::new();
510 self.unmetered_storage_rebate = 0;
511 }
512
513 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError> {
514 self.gas_status
515 .charge_bytes(size, self.cost_table.object_read_per_byte_cost)
516 .map_err(|e| {
517 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
518 ExecutionErrorKind::InsufficientGas.into()
519 })
520 }
521
522 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
523 self.gas_status
524 .charge_bytes(size, self.cost_table.package_publish_per_byte_cost)
525 .map_err(|e| {
526 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
527 ExecutionErrorKind::InsufficientGas.into()
528 })
529 }
530
531 fn track_storage_mutation(
536 &mut self,
537 object_id: ObjectID,
538 new_size: usize,
539 storage_rebate: u64,
540 ) -> u64 {
541 if self.is_unmetered() {
542 self.unmetered_storage_rebate += storage_rebate;
543 return 0;
544 }
545
546 let new_size = new_size as u64;
548 let storage_cost =
549 new_size * self.cost_table.storage_per_byte_cost * self.storage_gas_price;
550 self.per_object_storage.push((
553 object_id,
554 PerObjectStorage {
555 storage_cost,
556 storage_rebate,
557 new_size,
558 },
559 ));
560 storage_cost
562 }
563
564 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError> {
565 let storage_rebate = self.storage_rebate();
566 let storage_cost = self.storage_cost();
567 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
568 assert!(sender_rebate <= storage_rebate);
569 if sender_rebate >= storage_cost {
570 Ok(())
573 } else {
574 let gas_left = self.gas_budget - self.computation_cost;
575 if gas_left < storage_cost - sender_rebate {
577 Err(ExecutionErrorKind::InsufficientGas.into())
581 } else {
582 Ok(())
583 }
584 }
585 }
586
587 fn adjust_computation_on_out_of_gas(&mut self) {
588 self.per_object_storage = Vec::new();
589 self.computation_cost = self.gas_budget;
590 }
591
592 fn gas_usage_report(&self) -> GasUsageReport {
593 GasUsageReport {
594 cost_summary: self.summary(),
595 gas_used: self.gas_used(),
596 gas_price: self.gas_price(),
597 reference_gas_price: self.reference_gas_price(),
598 per_object_storage: self.per_object_storage().clone(),
599 gas_budget: self.gas_budget(),
600 storage_gas_price: self.storage_gas_price,
601 rebate_rate: self.rebate_rate,
602 }
603 }
604 }
605
606 fn half_digits_rounding(n: u64) -> u64 {
607 if n < 1000 {
608 return 1000;
609 }
610 let digits = n.ilog10();
611 let drop = digits / 2;
612 let base = 10u64.pow(drop);
613 n.div_ceil(base) * base
614 }
615
616 #[test]
617 fn test_half_digits_rounding() {
618 assert_eq!(half_digits_rounding(0), 1000);
619 assert_eq!(half_digits_rounding(1), 1000);
620 assert_eq!(half_digits_rounding(999), 1000);
621 assert_eq!(half_digits_rounding(1000), 1000);
622 assert_eq!(half_digits_rounding(1001), 1010);
623 assert_eq!(half_digits_rounding(1050), 1050);
624 assert_eq!(half_digits_rounding(1999), 2000);
625 assert_eq!(half_digits_rounding(20_000), 20_000);
626 assert_eq!(half_digits_rounding(20_001), 20_100);
627 assert_eq!(half_digits_rounding(20_500), 20_500);
628 assert_eq!(half_digits_rounding(29_999), 30_000);
629 assert_eq!(half_digits_rounding(300_000), 300_000);
630 assert_eq!(half_digits_rounding(300_001), 300_100);
631 assert_eq!(half_digits_rounding(305_500), 305_500);
632 assert_eq!(half_digits_rounding(305_501), 305_600);
633 assert_eq!(half_digits_rounding(999_999), 1_000_000);
634 assert_eq!(half_digits_rounding(1_000_000), 1_000_000);
635 assert_eq!(half_digits_rounding(1_000_001), 1_001_000);
636 assert_eq!(half_digits_rounding(1_005_000), 1_005_000);
637 assert_eq!(half_digits_rounding(1_005_001), 1_006_000);
638 assert_eq!(half_digits_rounding(1_999_999), 2_000_000);
639 assert_eq!(half_digits_rounding(10_000_001), 10_001_000);
640 assert_eq!(half_digits_rounding(100_000_001), 100_010_000);
641 }
642}