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 let Some(max_gas_price_rgp_factor_for_aborted_transactions) =
375 self.cost_table
376 .max_gas_price_rgp_factor_for_aborted_transactions
377 && aborted.unwrap_or(false)
378 {
379 let max_gas_price_for_aborted_txns =
382 max_gas_price_rgp_factor_for_aborted_transactions * self.reference_gas_price;
383 self.gas_price.min(max_gas_price_for_aborted_txns)
384 } else {
385 self.gas_price
387 };
388 let gas_used = match self.gas_rounding_mode {
389 GasRoundingMode::KeepHalfDigits => {
390 half_digits_rounding(gas_used) * effective_gas_price
391 }
392 GasRoundingMode::Stepped(gas_rounding) => {
393 if gas_used > 0 && gas_used % gas_rounding == 0 {
394 gas_used * effective_gas_price
395 } else {
396 ((gas_used / gas_rounding) + 1) * gas_rounding * effective_gas_price
397 }
398 }
399 GasRoundingMode::Bucketize => {
400 let bucket_cost =
401 get_bucket_cost(&self.cost_table.computation_bucket, gas_used);
402 bucket_cost * effective_gas_price
405 }
406 };
407 if self.gas_budget <= gas_used {
408 self.computation_cost = self.gas_budget;
409 Err(ExecutionErrorKind::InsufficientGas.into())
410 } else {
411 self.computation_cost = gas_used;
412 Ok(())
413 }
414 }
415
416 fn summary(&self) -> GasCostSummary {
420 let storage_rebate = self.storage_rebate();
422 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
423 assert!(sender_rebate <= storage_rebate);
424 let non_refundable_storage_fee = storage_rebate - sender_rebate;
425 GasCostSummary {
426 computation_cost: self.computation_cost,
427 storage_cost: self.storage_cost(),
428 storage_rebate: sender_rebate,
429 non_refundable_storage_fee,
430 }
431 }
432
433 fn gas_budget(&self) -> u64 {
434 self.gas_budget
435 }
436
437 fn gas_price(&self) -> u64 {
438 self.gas_price
439 }
440
441 fn reference_gas_price(&self) -> u64 {
442 self.reference_gas_price
443 }
444
445 fn storage_gas_units(&self) -> u64 {
446 self.per_object_storage
447 .iter()
448 .map(|(_, per_object)| per_object.storage_cost)
449 .sum()
450 }
451
452 fn storage_rebate(&self) -> u64 {
453 self.per_object_storage
454 .iter()
455 .map(|(_, per_object)| per_object.storage_rebate)
456 .sum()
457 }
458
459 fn unmetered_storage_rebate(&self) -> u64 {
460 self.unmetered_storage_rebate
461 }
462
463 fn gas_used(&self) -> u64 {
464 self.gas_status.gas_used_pre_gas_price()
465 }
466
467 fn reset_storage_cost_and_rebate(&mut self) {
468 self.per_object_storage = Vec::new();
469 self.unmetered_storage_rebate = 0;
470 }
471
472 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError> {
473 self.gas_status
474 .charge_bytes(size, self.cost_table.object_read_per_byte_cost)
475 .map_err(|e| {
476 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
477 ExecutionErrorKind::InsufficientGas.into()
478 })
479 }
480
481 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
482 self.gas_status
483 .charge_bytes(size, self.cost_table.package_publish_per_byte_cost)
484 .map_err(|e| {
485 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
486 ExecutionErrorKind::InsufficientGas.into()
487 })
488 }
489
490 fn track_storage_mutation(
495 &mut self,
496 object_id: ObjectID,
497 new_size: usize,
498 storage_rebate: u64,
499 ) -> u64 {
500 if self.is_unmetered() {
501 self.unmetered_storage_rebate += storage_rebate;
502 return 0;
503 }
504
505 let new_size = new_size as u64;
507 let storage_cost =
508 new_size * self.cost_table.storage_per_byte_cost * self.storage_gas_price;
509 self.per_object_storage.push((
512 object_id,
513 PerObjectStorage {
514 storage_cost,
515 storage_rebate,
516 new_size,
517 },
518 ));
519 storage_cost
521 }
522
523 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError> {
524 let storage_rebate = self.storage_rebate();
525 let storage_cost = self.storage_cost();
526 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
527 assert!(sender_rebate <= storage_rebate);
528 if sender_rebate >= storage_cost {
529 Ok(())
532 } else {
533 let gas_left = self.gas_budget - self.computation_cost;
534 if gas_left < storage_cost - sender_rebate {
536 Err(ExecutionErrorKind::InsufficientGas.into())
540 } else {
541 Ok(())
542 }
543 }
544 }
545
546 fn adjust_computation_on_out_of_gas(&mut self) {
547 self.per_object_storage = Vec::new();
548 self.computation_cost = self.gas_budget;
549 }
550
551 fn gas_usage_report(&self) -> GasUsageReport {
552 GasUsageReport {
553 cost_summary: self.summary(),
554 gas_used: self.gas_used(),
555 gas_price: self.gas_price(),
556 reference_gas_price: self.reference_gas_price(),
557 per_object_storage: self.per_object_storage().clone(),
558 gas_budget: self.gas_budget(),
559 storage_gas_price: self.storage_gas_price,
560 rebate_rate: self.rebate_rate,
561 }
562 }
563
564 fn check_gas_balance(
569 &self,
570 gas_objs: &[&ObjectReadResult],
571 gas_budget: u64,
572 available_address_balance_gas: u64,
573 ) -> UserInputResult {
574 self.check_gas_objects(gas_objs)?;
575 self.check_gas_data(gas_objs, gas_budget, available_address_balance_gas)
576 }
577
578 fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult {
580 for gas_object in gas_objs {
583 if let Some(obj) = gas_object.as_object() {
586 if !obj.is_address_owned() {
587 return Err(UserInputError::GasObjectNotOwnedObject {
588 owner: obj.owner.clone(),
589 });
590 }
591 } else {
592 return Err(UserInputError::MissingGasPayment);
595 }
596 }
597 Ok(())
598 }
599
600 fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
601 &self.per_object_storage
602 }
603 }
604
605 fn half_digits_rounding(n: u64) -> u64 {
606 if n < 1000 {
607 return 1000;
608 }
609 let digits = n.ilog10();
610 let drop = digits / 2;
611 let base = 10u64.pow(drop);
612 n.div_ceil(base) * base
613 }
614
615 #[test]
616 fn test_half_digits_rounding() {
617 assert_eq!(half_digits_rounding(0), 1000);
618 assert_eq!(half_digits_rounding(1), 1000);
619 assert_eq!(half_digits_rounding(999), 1000);
620 assert_eq!(half_digits_rounding(1000), 1000);
621 assert_eq!(half_digits_rounding(1001), 1010);
622 assert_eq!(half_digits_rounding(1050), 1050);
623 assert_eq!(half_digits_rounding(1999), 2000);
624 assert_eq!(half_digits_rounding(20_000), 20_000);
625 assert_eq!(half_digits_rounding(20_001), 20_100);
626 assert_eq!(half_digits_rounding(20_500), 20_500);
627 assert_eq!(half_digits_rounding(29_999), 30_000);
628 assert_eq!(half_digits_rounding(300_000), 300_000);
629 assert_eq!(half_digits_rounding(300_001), 300_100);
630 assert_eq!(half_digits_rounding(305_500), 305_500);
631 assert_eq!(half_digits_rounding(305_501), 305_600);
632 assert_eq!(half_digits_rounding(999_999), 1_000_000);
633 assert_eq!(half_digits_rounding(1_000_000), 1_000_000);
634 assert_eq!(half_digits_rounding(1_000_001), 1_001_000);
635 assert_eq!(half_digits_rounding(1_005_000), 1_005_000);
636 assert_eq!(half_digits_rounding(1_005_001), 1_006_000);
637 assert_eq!(half_digits_rounding(1_999_999), 2_000_000);
638 assert_eq!(half_digits_rounding(10_000_001), 10_001_000);
639 assert_eq!(half_digits_rounding(100_000_001), 100_010_000);
640 }
641}