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