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 #[allow(dead_code)]
170 #[derive(Debug)]
171 pub struct SuiGasStatus {
172 pub gas_status: GasStatus,
174 cost_table: SuiCostTable,
176 gas_budget: u64,
179 computation_cost: u64,
183 charge: bool,
185 gas_price: u64,
191 reference_gas_price: u64,
193 storage_gas_price: u64,
201 per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
204 rebate_rate: u64,
206 unmetered_storage_rebate: u64,
209 gas_rounding_step: Option<u64>,
211 }
212
213 impl SuiGasStatus {
214 fn new(
215 move_gas_status: GasStatus,
216 gas_budget: u64,
217 charge: bool,
218 gas_price: u64,
219 reference_gas_price: u64,
220 storage_gas_price: u64,
221 rebate_rate: u64,
222 gas_rounding_step: Option<u64>,
223 cost_table: SuiCostTable,
224 ) -> SuiGasStatus {
225 let gas_rounding_step = gas_rounding_step.map(|val| val.max(1));
226 SuiGasStatus {
227 gas_status: move_gas_status,
228 gas_budget,
229 charge,
230 computation_cost: 0,
231 gas_price,
232 reference_gas_price,
233 storage_gas_price,
234 per_object_storage: Vec::new(),
235 rebate_rate,
236 unmetered_storage_rebate: 0,
237 gas_rounding_step,
238 cost_table,
239 }
240 }
241
242 pub(crate) fn new_with_budget(
243 gas_budget: u64,
244 gas_price: u64,
245 reference_gas_price: u64,
246 config: &ProtocolConfig,
247 ) -> SuiGasStatus {
248 let storage_gas_price = config.storage_gas_price();
249 let max_computation_budget = config.max_gas_computation_bucket() * gas_price;
250 let computation_budget = if gas_budget > max_computation_budget {
251 max_computation_budget
252 } else {
253 gas_budget
254 };
255 let sui_cost_table = SuiCostTable::new(config, gas_price);
256 let gas_rounding_step = config.gas_rounding_step_as_option();
257 Self::new(
258 GasStatus::new(
259 sui_cost_table.execution_cost_table.clone(),
260 computation_budget,
261 gas_price,
262 config.gas_model_version(),
263 ),
264 gas_budget,
265 true,
266 gas_price,
267 reference_gas_price,
268 storage_gas_price,
269 config.storage_rebate_rate(),
270 gas_rounding_step,
271 sui_cost_table,
272 )
273 }
274
275 pub fn new_unmetered() -> SuiGasStatus {
276 Self::new(
277 GasStatus::new_unmetered(),
278 0,
279 false,
280 0,
281 0,
282 0,
283 0,
284 None,
285 SuiCostTable::unmetered(),
286 )
287 }
288
289 pub fn reference_gas_price(&self) -> u64 {
290 self.reference_gas_price
291 }
292
293 pub(crate) fn check_gas_balance(
298 &self,
299 gas_objs: &[&ObjectReadResult],
300 gas_budget: u64,
301 ) -> UserInputResult {
302 for gas_object in gas_objs {
304 if let Some(obj) = gas_object.as_object() {
307 if !obj.is_address_owned() {
308 return Err(UserInputError::GasObjectNotOwnedObject {
309 owner: obj.owner.clone(),
310 });
311 }
312 } else {
313 return Err(UserInputError::MissingGasPayment);
316 }
317 }
318
319 if gas_budget > self.cost_table.max_gas_budget {
321 return Err(UserInputError::GasBudgetTooHigh {
322 gas_budget,
323 max_budget: self.cost_table.max_gas_budget,
324 });
325 }
326 if gas_budget < self.cost_table.min_transaction_cost {
327 return Err(UserInputError::GasBudgetTooLow {
328 gas_budget,
329 min_budget: self.cost_table.min_transaction_cost,
330 });
331 }
332
333 let mut gas_balance = 0u128;
335 for gas_obj in gas_objs {
336 gas_balance +=
338 gas::get_gas_balance(gas_obj.as_object().expect("object must be owned"))?
339 as u128;
340 }
341 if gas_balance < gas_budget as u128 {
342 Err(UserInputError::GasBalanceTooLow {
343 gas_balance,
344 needed_gas_amount: gas_budget as u128,
345 })
346 } else {
347 Ok(())
348 }
349 }
350
351 fn storage_cost(&self) -> u64 {
352 self.storage_gas_units()
353 }
354
355 pub fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
356 &self.per_object_storage
357 }
358 }
359
360 impl SuiGasStatusAPI for SuiGasStatus {
361 fn is_unmetered(&self) -> bool {
362 !self.charge
363 }
364
365 fn move_gas_status(&self) -> &GasStatus {
366 &self.gas_status
367 }
368
369 fn move_gas_status_mut(&mut self) -> &mut GasStatus {
370 &mut self.gas_status
371 }
372
373 fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError> {
374 let gas_used = self.gas_status.gas_used_pre_gas_price();
375 let effective_gas_price = if self
376 .cost_table
377 .max_gas_price_rgp_factor_for_aborted_transactions
378 .is_some()
379 && aborted.unwrap_or(false)
380 {
381 let max_gas_price_for_aborted_txns = self
384 .cost_table
385 .max_gas_price_rgp_factor_for_aborted_transactions
386 .unwrap()
387 * self.reference_gas_price;
388 self.gas_price.min(max_gas_price_for_aborted_txns)
389 } else {
390 self.gas_price
392 };
393 let gas_used = if let Some(gas_rounding) = self.gas_rounding_step {
394 if gas_used > 0 && gas_used % gas_rounding == 0 {
395 gas_used * effective_gas_price
396 } else {
397 ((gas_used / gas_rounding) + 1) * gas_rounding * effective_gas_price
398 }
399 } else {
400 let bucket_cost = get_bucket_cost(&self.cost_table.computation_bucket, gas_used);
401 bucket_cost * effective_gas_price
404 };
405 if self.gas_budget <= gas_used {
406 self.computation_cost = self.gas_budget;
407 Err(ExecutionErrorKind::InsufficientGas.into())
408 } else {
409 self.computation_cost = gas_used;
410 Ok(())
411 }
412 }
413
414 fn summary(&self) -> GasCostSummary {
418 let storage_rebate = self.storage_rebate();
420 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
421 assert!(sender_rebate <= storage_rebate);
422 let non_refundable_storage_fee = storage_rebate - sender_rebate;
423 GasCostSummary {
424 computation_cost: self.computation_cost,
425 storage_cost: self.storage_cost(),
426 storage_rebate: sender_rebate,
427 non_refundable_storage_fee,
428 }
429 }
430
431 fn gas_budget(&self) -> u64 {
432 self.gas_budget
433 }
434
435 fn gas_price(&self) -> u64 {
436 self.gas_price
437 }
438
439 fn reference_gas_price(&self) -> u64 {
440 self.reference_gas_price
441 }
442
443 fn storage_gas_units(&self) -> u64 {
444 self.per_object_storage
445 .iter()
446 .map(|(_, per_object)| per_object.storage_cost)
447 .sum()
448 }
449
450 fn storage_rebate(&self) -> u64 {
451 self.per_object_storage
452 .iter()
453 .map(|(_, per_object)| per_object.storage_rebate)
454 .sum()
455 }
456
457 fn unmetered_storage_rebate(&self) -> u64 {
458 self.unmetered_storage_rebate
459 }
460
461 fn gas_used(&self) -> u64 {
462 self.gas_status.gas_used_pre_gas_price()
463 }
464
465 fn reset_storage_cost_and_rebate(&mut self) {
466 self.per_object_storage = Vec::new();
467 self.unmetered_storage_rebate = 0;
468 }
469
470 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError> {
471 self.gas_status
472 .charge_bytes(size, self.cost_table.object_read_per_byte_cost)
473 .map_err(|e| {
474 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
475 ExecutionErrorKind::InsufficientGas.into()
476 })
477 }
478
479 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
480 self.gas_status
481 .charge_bytes(size, self.cost_table.package_publish_per_byte_cost)
482 .map_err(|e| {
483 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
484 ExecutionErrorKind::InsufficientGas.into()
485 })
486 }
487
488 fn track_storage_mutation(
493 &mut self,
494 object_id: ObjectID,
495 new_size: usize,
496 storage_rebate: u64,
497 ) -> u64 {
498 if self.is_unmetered() {
499 self.unmetered_storage_rebate += storage_rebate;
500 return 0;
501 }
502
503 let new_size = new_size as u64;
505 let storage_cost =
506 new_size * self.cost_table.storage_per_byte_cost * self.storage_gas_price;
507 self.per_object_storage.push((
510 object_id,
511 PerObjectStorage {
512 storage_cost,
513 storage_rebate,
514 new_size,
515 },
516 ));
517 storage_cost
519 }
520
521 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError> {
522 let storage_rebate = self.storage_rebate();
523 let storage_cost = self.storage_cost();
524 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
525 assert!(sender_rebate <= storage_rebate);
526 if sender_rebate >= storage_cost {
527 Ok(())
530 } else {
531 let gas_left = self.gas_budget - self.computation_cost;
532 if gas_left < storage_cost - sender_rebate {
534 Err(ExecutionErrorKind::InsufficientGas.into())
538 } else {
539 Ok(())
540 }
541 }
542 }
543
544 fn adjust_computation_on_out_of_gas(&mut self) {
545 self.per_object_storage = Vec::new();
546 self.computation_cost = self.gas_budget;
547 }
548
549 fn gas_usage_report(&self) -> GasUsageReport {
550 GasUsageReport {
551 cost_summary: self.summary(),
552 gas_used: self.gas_used(),
553 gas_price: self.gas_price(),
554 reference_gas_price: self.reference_gas_price(),
555 per_object_storage: self.per_object_storage().clone(),
556 gas_budget: self.gas_budget(),
557 storage_gas_price: self.storage_gas_price,
558 rebate_rate: self.rebate_rate,
559 }
560 }
561 }
562}