1#![deny(clippy::arithmetic_side_effects)]
6#![deny(clippy::cast_possible_truncation)]
7#![deny(clippy::indexing_slicing)]
8#![deny(clippy::cast_possible_wrap)]
9#![deny(clippy::cast_sign_loss)]
10
11use crate::error::UserInputResult;
12use crate::gas::{GasCostSummary, GasUsageReport, SuiGasStatusAPI};
13use crate::gas_model::gas_common::{
14 StorageGas, check_gas_data, check_gas_objects, half_digits_rounding, package_read_internal_gas,
15 sender_rebate,
16};
17use crate::gas_model::gas_predicates::cost_table_for_version;
18use crate::gas_model::units_types::CostTable;
19use crate::transaction::ObjectReadResult;
20use crate::{
21 ObjectID,
22 error::ExecutionError,
23 execution_status::ExecutionErrorKind,
24 gas_model::tables::{GasStatus, ZERO_COST_SCHEDULE},
25};
26use move_core_types::gas_algebra::InternalGas;
27use move_core_types::vm_status::StatusCode;
28use sui_protocol_config::*;
29
30pub struct SuiCostTable {
32 pub(crate) min_transaction_cost: u64,
34 pub(crate) max_gas_budget: u64,
36 package_publish_per_byte_cost: u64,
38 object_read_per_byte_cost: u64,
40 package_read_per_kb_cost: Option<u64>,
42 storage_per_byte_cost: u64,
44 pub execution_cost_table: CostTable,
46 max_gas_price_rgp_factor_for_aborted_transactions: Option<u64>,
48}
49
50impl std::fmt::Debug for SuiCostTable {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 write!(f, "SuiCostTable(...)")
54 }
55}
56
57impl SuiCostTable {
58 pub(crate) fn new(c: &ProtocolConfig, gas_price: u64) -> Self {
59 let min_transaction_cost = c
62 .base_tx_cost_fixed()
63 .checked_mul(gas_price)
64 .expect("base tx cost cannot overflow: gas_price is bounded by max_gas_price");
65 Self {
66 min_transaction_cost,
67 max_gas_budget: c.max_tx_gas(),
68 package_publish_per_byte_cost: c.package_publish_cost_per_byte(),
69 object_read_per_byte_cost: c.obj_access_cost_read_per_byte(),
70 package_read_per_kb_cost: c.obj_access_cost_read_per_package_kb_as_option(),
71 storage_per_byte_cost: c.obj_data_cost_refundable(),
72 execution_cost_table: cost_table_for_version(c.gas_model_version()),
73 max_gas_price_rgp_factor_for_aborted_transactions: c
74 .max_gas_price_rgp_factor_for_aborted_transactions_as_option(),
75 }
76 }
77
78 pub(crate) fn unmetered() -> Self {
79 Self {
80 min_transaction_cost: 0,
81 max_gas_budget: u64::MAX,
82 package_publish_per_byte_cost: 0,
83 object_read_per_byte_cost: 0,
84 package_read_per_kb_cost: None,
85 storage_per_byte_cost: 0,
86 execution_cost_table: ZERO_COST_SCHEDULE.clone(),
87 max_gas_price_rgp_factor_for_aborted_transactions: None,
88 }
89 }
90}
91
92pub use crate::gas_model::gas_common::PerObjectStorage;
93
94#[allow(dead_code)]
95#[derive(Debug)]
96pub struct SuiGasStatus {
97 pub gas_status: GasStatus,
99 cost_table: SuiCostTable,
101 gas_budget: u64,
103 charge: bool,
105 effective_gas_price: u64,
108 user_gas_price: u64,
110 reference_gas_price: u64,
112 rebate_rate: u64,
114 storage: StorageGas,
116 force_computation_cost_to_budget: bool,
118}
119
120impl SuiGasStatus {
121 pub(crate) fn charge_package_object_read(&mut self, size: usize) -> Result<(), ExecutionError> {
122 if let Some(cost_per_kb) = self.cost_table.package_read_per_kb_cost {
123 self.gas_status
124 .deduct_gas(InternalGas::new(package_read_internal_gas(
125 size,
126 cost_per_kb,
127 )))
128 .map_err(|e| {
129 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
130 ExecutionErrorKind::InsufficientGas.into()
131 })
132 } else {
133 self.charge_storage_read(size)
135 }
136 }
137
138 fn new(
139 move_gas_status: GasStatus,
140 gas_budget: u64,
141 charge: bool,
142 user_gas_price: u64,
143 reference_gas_price: u64,
144 storage_gas_price: u64,
145 rebate_rate: u64,
146 cost_table: SuiCostTable,
147 ) -> SuiGasStatus {
148 SuiGasStatus {
149 gas_status: move_gas_status,
150 gas_budget,
151 charge,
152 effective_gas_price: user_gas_price,
153 user_gas_price,
154 reference_gas_price,
155 rebate_rate,
156 storage: StorageGas::new(storage_gas_price, cost_table.storage_per_byte_cost),
157 cost_table,
158 force_computation_cost_to_budget: false,
159 }
160 }
161
162 pub(crate) fn new_with_budget(
163 gas_budget: u64,
164 gas_price: u64,
165 reference_gas_price: u64,
166 config: &ProtocolConfig,
167 ) -> SuiGasStatus {
168 let storage_gas_price = config.storage_gas_price();
169 let max_computation_budget = config
170 .max_gas_computation_bucket()
171 .checked_mul(gas_price)
172 .expect("computation budget cannot overflow: gas_price is bounded by max_gas_price");
173 let computation_budget = if gas_budget > max_computation_budget {
174 max_computation_budget
175 } else {
176 gas_budget
177 };
178 let sui_cost_table = SuiCostTable::new(config, gas_price);
179 Self::new(
180 GasStatus::new(
181 sui_cost_table.execution_cost_table.clone(),
182 computation_budget,
183 gas_price,
184 config.gas_model_version(),
185 ),
186 gas_budget,
187 true,
188 gas_price,
189 reference_gas_price,
190 storage_gas_price,
191 config.storage_rebate_rate(),
192 sui_cost_table,
193 )
194 }
195
196 pub fn new_unmetered() -> SuiGasStatus {
197 Self::new(
198 GasStatus::new_unmetered(),
199 0,
200 false,
201 0,
202 0,
203 0,
204 0,
205 SuiCostTable::unmetered(),
206 )
207 }
208
209 pub fn reference_gas_price(&self) -> u64 {
210 self.reference_gas_price
211 }
212
213 fn uncapped_computation_cost(&self) -> u64 {
215 if self.force_computation_cost_to_budget {
216 return self.gas_budget;
217 }
218 let raw_units = self.gas_status.gas_used_pre_gas_price();
219 let bucketed_units = half_digits_rounding(raw_units);
220 bucketed_units.saturating_mul(self.effective_gas_price)
221 }
222
223 fn derived_computation_cost(&self) -> u64 {
227 let uncapped_cost = self.uncapped_computation_cost();
228 let storage_rebate = self.storage_rebate();
229 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
230 let net_storage = self.storage_cost().saturating_sub(sender_rebate);
231 let max_computation = self.gas_budget.saturating_sub(net_storage);
232 uncapped_cost.min(max_computation)
233 }
234
235 fn storage_cost(&self) -> u64 {
236 self.storage_gas_units()
237 }
238}
239
240impl SuiGasStatusAPI for SuiGasStatus {
241 fn is_unmetered(&self) -> bool {
242 !self.charge
243 }
244
245 fn move_gas_status(&self) -> &GasStatus {
246 &self.gas_status
247 }
248
249 fn move_gas_status_mut(&mut self) -> &mut GasStatus {
250 &mut self.gas_status
251 }
252
253 fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError> {
254 self.effective_gas_price = match self
255 .cost_table
256 .max_gas_price_rgp_factor_for_aborted_transactions
257 {
258 Some(factor) if aborted.unwrap_or(false) => {
259 let cap = factor
260 .checked_mul(self.reference_gas_price)
261 .ok_or_else(|| {
262 ExecutionError::from_kind(ExecutionErrorKind::InvariantViolation)
263 })?;
264 self.user_gas_price.min(cap)
265 }
266 _ => self.user_gas_price,
267 };
268 if self.uncapped_computation_cost() >= self.gas_budget {
269 return Err(ExecutionErrorKind::InsufficientGas.into());
270 }
271 Ok(())
272 }
273
274 fn summary(&self) -> GasCostSummary {
276 let storage_rebate = self.storage_rebate();
277 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
278 let non_refundable_storage_fee = storage_rebate
279 .checked_sub(sender_rebate)
280 .expect("sender rebate must not exceed storage rebate");
281 GasCostSummary {
282 computation_cost: self.derived_computation_cost(),
283 storage_cost: self.storage_cost(),
284 storage_rebate: sender_rebate,
285 non_refundable_storage_fee,
286 }
287 }
288
289 fn gas_budget(&self) -> u64 {
290 self.gas_budget
291 }
292
293 fn gas_price(&self) -> u64 {
294 self.user_gas_price
295 }
296
297 fn reference_gas_price(&self) -> u64 {
298 self.reference_gas_price
299 }
300
301 fn storage_gas_units(&self) -> u64 {
302 self.storage.storage_gas_units()
303 }
304
305 fn storage_rebate(&self) -> u64 {
306 self.storage.storage_rebate()
307 }
308
309 fn unmetered_storage_rebate(&self) -> u64 {
310 self.storage.unmetered_storage_rebate()
311 }
312
313 fn gas_used(&self) -> u64 {
314 self.gas_status.gas_used_pre_gas_price()
315 }
316
317 fn reset_storage_cost_and_rebate(&mut self) {
318 self.storage.reset();
319 }
320
321 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError> {
322 self.gas_status
323 .charge_bytes(size, self.cost_table.object_read_per_byte_cost)
324 .map_err(|e| {
325 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
326 ExecutionErrorKind::InsufficientGas.into()
327 })
328 }
329
330 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
331 self.gas_status
332 .charge_bytes(size, self.cost_table.package_publish_per_byte_cost)
333 .map_err(|e| {
334 debug_assert_eq!(e.major_status(), StatusCode::OUT_OF_GAS);
335 ExecutionErrorKind::InsufficientGas.into()
336 })
337 }
338
339 fn track_storage_mutation(
343 &mut self,
344 object_id: ObjectID,
345 new_size: usize,
346 storage_rebate: u64,
347 ) -> Option<u64> {
348 let unmetered = self.is_unmetered();
349 self.storage
350 .track_mutation(object_id, new_size, storage_rebate, unmetered)
351 }
352
353 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError> {
354 let storage_rebate = self.storage.storage_rebate();
355 let storage_cost = self.storage.storage_gas_units();
356 let sender_rebate = sender_rebate(storage_rebate, self.rebate_rate);
357 assert!(sender_rebate <= storage_rebate);
358 let net_storage_cost = storage_cost.saturating_sub(sender_rebate);
359 let gas_left = self
360 .gas_budget
361 .saturating_sub(self.uncapped_computation_cost());
362 if net_storage_cost > gas_left {
363 return Err(ExecutionErrorKind::InsufficientGas.into());
364 }
365 Ok(())
366 }
367
368 fn adjust_computation_on_out_of_gas(&mut self) {
370 self.storage.reset();
371 self.force_computation_cost_to_budget = true;
372 }
373
374 fn gas_usage_report(&self) -> GasUsageReport {
375 GasUsageReport {
376 cost_summary: self.summary(),
377 gas_used: self.gas_used(),
378 gas_price: self.gas_price(),
379 reference_gas_price: self.reference_gas_price(),
380 per_object_storage: self.per_object_storage().clone(),
381 gas_budget: self.gas_budget(),
382 storage_gas_price: self.storage.storage_gas_price,
383 rebate_rate: self.rebate_rate,
384 }
385 }
386
387 fn check_gas_balance(
392 &self,
393 gas_objs: &[&ObjectReadResult],
394 gas_budget: u64,
395 available_address_balance_gas: u64,
396 ) -> UserInputResult {
397 self.check_gas_objects(gas_objs)?;
398 check_gas_data(
399 gas_objs,
400 gas_budget,
401 available_address_balance_gas,
402 self.cost_table.min_transaction_cost,
403 self.cost_table.max_gas_budget,
404 )
405 }
406
407 fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult {
408 check_gas_objects(gas_objs)
409 }
410
411 fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)> {
412 self.storage.per_object_storage()
413 }
414}