1pub use checked::*;
6use serde::{Deserialize, Serialize};
7
8use crate::{base_types::ObjectID, gas_model::gas_v2::PerObjectStorage};
9
10#[sui_macros::with_checked_arithmetic]
11pub mod checked {
12
13 use crate::gas::GasUsageReport;
14 use crate::gas_model::gas_predicates::check_for_gas_price_too_high;
15 use crate::gas_model::gas_v2::PerObjectStorage;
16 use crate::{
17 ObjectID,
18 effects::{TransactionEffects, TransactionEffectsAPI},
19 error::{ExecutionError, SuiResult, UserInputError, UserInputResult},
20 gas_model::{
21 gas_v2::SuiGasStatus as SuiGasStatusV2, gas_v3::SuiGasStatus as SuiGasStatusV3,
22 tables::GasStatus,
23 },
24 object::Object,
25 sui_serde::{BigInt, Readable},
26 transaction::ObjectReadResult,
27 };
28 use enum_dispatch::enum_dispatch;
29 use itertools::MultiUnzip;
30 use schemars::JsonSchema;
31 use serde::{Deserialize, Serialize};
32 use serde_with::serde_as;
33 use sui_protocol_config::ProtocolConfig;
34
35 #[enum_dispatch]
36 pub trait SuiGasStatusAPI {
37 fn is_unmetered(&self) -> bool;
38 fn move_gas_status(&self) -> &GasStatus;
39 fn move_gas_status_mut(&mut self) -> &mut GasStatus;
40 fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError>;
41 fn summary(&self) -> GasCostSummary;
42 fn gas_budget(&self) -> u64;
43 fn gas_price(&self) -> u64;
44 fn reference_gas_price(&self) -> u64;
45 fn storage_gas_units(&self) -> u64;
46 fn storage_rebate(&self) -> u64;
47 fn unmetered_storage_rebate(&self) -> u64;
48 fn gas_used(&self) -> u64;
49 fn reset_storage_cost_and_rebate(&mut self);
50 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError>;
51 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError>;
52 fn track_storage_mutation(
53 &mut self,
54 object_id: ObjectID,
55 new_size: usize,
56 storage_rebate: u64,
57 ) -> Option<u64>;
58 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError>;
59 fn adjust_computation_on_out_of_gas(&mut self);
60 fn gas_usage_report(&self) -> GasUsageReport;
61 fn check_gas_balance(
62 &self,
63 gas_objs: &[&ObjectReadResult],
64 gas_budget: u64,
65 available_address_balance_gas: u64,
66 ) -> UserInputResult;
67 fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult;
68 fn per_object_storage(&self) -> &Vec<(ObjectID, PerObjectStorage)>;
69 }
70
71 #[enum_dispatch(SuiGasStatusAPI)]
74 #[derive(Debug)]
75 pub enum SuiGasStatus {
76 V2(SuiGasStatusV2),
79 V3(SuiGasStatusV3),
80 }
81
82 impl SuiGasStatus {
83 pub fn charge_package_object_read(&mut self, size: usize) -> Result<(), ExecutionError> {
85 match self {
86 Self::V2(status) => status.charge_storage_read(size),
87 Self::V3(status) => status.charge_package_object_read(size),
88 }
89 }
90
91 pub fn new(
92 gas_budget: u64,
93 gas_price: u64,
94 reference_gas_price: u64,
95 config: &ProtocolConfig,
96 ) -> SuiResult<Self> {
97 if gas_price < reference_gas_price {
102 return Err(UserInputError::GasPriceUnderRGP {
103 gas_price,
104 reference_gas_price,
105 }
106 .into());
107 }
108 if check_for_gas_price_too_high(config.gas_model_version())
109 && gas_price >= config.max_gas_price()
110 {
111 return Err(UserInputError::GasPriceTooHigh {
112 max_gas_price: config.max_gas_price(),
113 }
114 .into());
115 }
116
117 if config.gas_model_version() >= 15 {
120 Ok(Self::V3(SuiGasStatusV3::new_with_budget(
121 gas_budget,
122 gas_price,
123 reference_gas_price,
124 config,
125 )))
126 } else {
127 Ok(Self::V2(SuiGasStatusV2::new_with_budget(
128 gas_budget,
129 gas_price,
130 reference_gas_price,
131 config,
132 )))
133 }
134 }
135
136 pub fn new_unmetered(config: &ProtocolConfig) -> Self {
137 if config.gas_model_version() >= 15 {
140 Self::V3(SuiGasStatusV3::new_unmetered())
141 } else {
142 Self::V2(SuiGasStatusV2::new_unmetered())
143 }
144 }
145 }
146
147 #[serde_as]
172 #[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
173 #[serde(rename_all = "camelCase")]
174 pub struct GasCostSummary {
175 #[schemars(with = "BigInt<u64>")]
177 #[serde_as(as = "Readable<BigInt<u64>, _>")]
178 pub computation_cost: u64,
179 #[schemars(with = "BigInt<u64>")]
181 #[serde_as(as = "Readable<BigInt<u64>, _>")]
182 pub storage_cost: u64,
183 #[schemars(with = "BigInt<u64>")]
186 #[serde_as(as = "Readable<BigInt<u64>, _>")]
187 pub storage_rebate: u64,
188 #[schemars(with = "BigInt<u64>")]
190 #[serde_as(as = "Readable<BigInt<u64>, _>")]
191 pub non_refundable_storage_fee: u64,
192 }
193
194 impl GasCostSummary {
195 pub fn new(
196 computation_cost: u64,
197 storage_cost: u64,
198 storage_rebate: u64,
199 non_refundable_storage_fee: u64,
200 ) -> GasCostSummary {
201 GasCostSummary {
202 computation_cost,
203 storage_cost,
204 storage_rebate,
205 non_refundable_storage_fee,
206 }
207 }
208
209 pub fn gas_used(&self) -> u64 {
210 self.computation_cost + self.storage_cost
211 }
212
213 pub fn sender_rebate(&self, storage_rebate_rate: u64) -> u64 {
216 const BASIS_POINTS: u128 = 10000;
219 (((self.storage_rebate as u128 * storage_rebate_rate as u128)
220 + (BASIS_POINTS / 2)) / BASIS_POINTS) as u64
222 }
223
224 pub fn net_gas_usage(&self) -> i64 {
226 self.gas_used() as i64 - self.storage_rebate as i64
227 }
228
229 pub fn new_from_txn_effects<'a>(
230 transactions: impl Iterator<Item = &'a TransactionEffects>,
231 ) -> GasCostSummary {
232 let (storage_costs, computation_costs, storage_rebates, non_refundable_storage_fee): (
233 Vec<u64>,
234 Vec<u64>,
235 Vec<u64>,
236 Vec<u64>,
237 ) = transactions
238 .map(|e| {
239 (
240 e.gas_cost_summary().storage_cost,
241 e.gas_cost_summary().computation_cost,
242 e.gas_cost_summary().storage_rebate,
243 e.gas_cost_summary().non_refundable_storage_fee,
244 )
245 })
246 .multiunzip();
247
248 GasCostSummary {
249 storage_cost: storage_costs.iter().sum(),
250 computation_cost: computation_costs.iter().sum(),
251 storage_rebate: storage_rebates.iter().sum(),
252 non_refundable_storage_fee: non_refundable_storage_fee.iter().sum(),
253 }
254 }
255 }
256
257 impl std::fmt::Display for GasCostSummary {
258 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259 write!(
260 f,
261 "computation_cost: {}, storage_cost: {}, storage_rebate: {}, non_refundable_storage_fee: {}",
262 self.computation_cost,
263 self.storage_cost,
264 self.storage_rebate,
265 self.non_refundable_storage_fee,
266 )
267 }
268 }
269
270 impl std::ops::AddAssign<&Self> for GasCostSummary {
271 fn add_assign(&mut self, other: &Self) {
272 self.computation_cost += other.computation_cost;
273 self.storage_cost += other.storage_cost;
274 self.storage_rebate += other.storage_rebate;
275 self.non_refundable_storage_fee += other.non_refundable_storage_fee;
276 }
277 }
278
279 impl std::ops::AddAssign<Self> for GasCostSummary {
280 fn add_assign(&mut self, other: Self) {
281 self.add_assign(&other)
282 }
283 }
284
285 pub fn deduct_gas(gas_object: &mut Object, charge_or_rebate: i64) {
290 let gas_coin = gas_object.data.try_as_move_mut().unwrap();
292 let balance = gas_coin.get_coin_value_unsafe();
293 let new_balance = if charge_or_rebate < 0 {
294 balance + (-charge_or_rebate as u64)
295 } else {
296 assert!(balance >= charge_or_rebate as u64);
297 balance - charge_or_rebate as u64
298 };
299 gas_coin.set_coin_value_unsafe(new_balance)
300 }
301
302 pub fn get_gas_balance(gas_object: &Object) -> UserInputResult<u64> {
303 if let Some(move_obj) = gas_object.data.try_as_move() {
304 if !move_obj.type_().is_gas_coin() {
305 return Err(UserInputError::InvalidGasObject {
306 object_id: gas_object.id(),
307 });
308 }
309 Ok(move_obj.get_coin_value_unsafe())
310 } else {
311 Err(UserInputError::InvalidGasObject {
312 object_id: gas_object.id(),
313 })
314 }
315 }
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct GasUsageReport {
320 pub cost_summary: GasCostSummary,
321 pub gas_used: u64,
322 pub gas_budget: u64,
323 pub gas_price: u64,
324 pub reference_gas_price: u64,
325 pub storage_gas_price: u64,
326 pub rebate_rate: u64,
327 pub per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
328}