1pub use checked::*;
7
8#[iota_macros::with_checked_arithmetic]
9pub mod checked {
10
11 use enum_dispatch::enum_dispatch;
12 use iota_protocol_config::ProtocolConfig;
13 use itertools::MultiUnzip;
14 use schemars::JsonSchema;
15 use serde::{Deserialize, Serialize};
16 use serde_with::serde_as;
17
18 use crate::{
19 ObjectID,
20 effects::{TransactionEffects, TransactionEffectsAPI},
21 error::{ExecutionError, IotaResult, UserInputError, UserInputResult},
22 gas_model::{gas_v1::IotaGasStatus as IotaGasStatusV1, tables::GasStatus},
23 iota_serde::{BigInt, Readable},
24 object::Object,
25 transaction::ObjectReadResult,
26 };
27
28 #[enum_dispatch]
29 pub trait IotaGasStatusAPI {
30 fn is_unmetered(&self) -> bool;
31 fn move_gas_status(&self) -> &GasStatus;
32 fn move_gas_status_mut(&mut self) -> &mut GasStatus;
33 fn bucketize_computation(&mut self) -> Result<(), ExecutionError>;
34 fn summary(&self) -> GasCostSummary;
35 fn gas_budget(&self) -> u64;
36 fn gas_price(&self) -> u64;
37 fn reference_gas_price(&self) -> u64;
38 fn storage_gas_units(&self) -> u64;
39 fn storage_rebate(&self) -> u64;
40 fn unmetered_storage_rebate(&self) -> u64;
41 fn gas_used(&self) -> u64;
42 fn reset_storage_cost_and_rebate(&mut self);
43 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError>;
44 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError>;
45 fn track_storage_mutation(
46 &mut self,
47 object_id: ObjectID,
48 new_size: usize,
49 storage_rebate: u64,
50 ) -> u64;
51 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError>;
52 fn adjust_computation_on_out_of_gas(&mut self);
53 }
54
55 #[enum_dispatch(IotaGasStatusAPI)]
57 #[derive(Debug)]
58 pub enum IotaGasStatus {
59 V1(IotaGasStatusV1),
60 }
61
62 impl IotaGasStatus {
63 pub fn new(
64 gas_budget: u64,
65 gas_price: u64,
66 reference_gas_price: u64,
67 config: &ProtocolConfig,
68 ) -> IotaResult<Self> {
69 Self::check_gas_preconditions(gas_price, reference_gas_price, config)?;
70
71 Ok(Self::V1(IotaGasStatusV1::new_with_budget(
72 gas_budget,
73 gas_price,
74 reference_gas_price,
75 config,
76 )))
77 }
78
79 pub fn new_unmetered() -> Self {
80 Self::V1(IotaGasStatusV1::new_unmetered())
83 }
84
85 pub fn check_gas_balance(
88 &self,
89 gas_objs: &[&ObjectReadResult],
90 gas_budget: u64,
91 ) -> UserInputResult {
92 match self {
93 Self::V1(status) => status.check_gas_balance(gas_objs, gas_budget),
94 }
95 }
96
97 fn check_gas_preconditions(
98 gas_price: u64,
99 reference_gas_price: u64,
100 config: &ProtocolConfig,
101 ) -> IotaResult<()> {
102 if gas_price < reference_gas_price {
107 return Err(UserInputError::GasPriceUnderRGP {
108 gas_price,
109 reference_gas_price,
110 }
111 .into());
112 }
113 if gas_price > config.max_gas_price() {
114 return Err(UserInputError::GasPriceTooHigh {
115 max_gas_price: config.max_gas_price(),
116 }
117 .into());
118 }
119
120 Ok(())
121 }
122 }
123
124 #[serde_as]
151 #[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
152 #[serde(rename_all = "camelCase")]
153 pub struct GasCostSummary {
154 #[schemars(with = "BigInt<u64>")]
156 #[serde_as(as = "Readable<BigInt<u64>, _>")]
157 pub computation_cost: u64,
158 #[schemars(with = "BigInt<u64>")]
160 #[serde_as(as = "Readable<BigInt<u64>, _>")]
161 pub computation_cost_burned: u64,
162 #[schemars(with = "BigInt<u64>")]
165 #[serde_as(as = "Readable<BigInt<u64>, _>")]
166 pub storage_cost: u64,
167 #[schemars(with = "BigInt<u64>")]
170 #[serde_as(as = "Readable<BigInt<u64>, _>")]
171 pub storage_rebate: u64,
172 #[schemars(with = "BigInt<u64>")]
175 #[serde_as(as = "Readable<BigInt<u64>, _>")]
176 pub non_refundable_storage_fee: u64,
177 }
178
179 impl GasCostSummary {
180 pub fn new(
181 computation_cost: u64,
182 computation_cost_burned: u64,
183 storage_cost: u64,
184 storage_rebate: u64,
185 non_refundable_storage_fee: u64,
186 ) -> GasCostSummary {
187 GasCostSummary {
188 computation_cost,
189 computation_cost_burned,
190 storage_cost,
191 storage_rebate,
192 non_refundable_storage_fee,
193 }
194 }
195
196 pub fn gas_used(&self) -> u64 {
197 self.computation_cost + self.storage_cost
198 }
199
200 pub fn sender_rebate(&self, storage_rebate_rate: u64) -> u64 {
204 const BASIS_POINTS: u128 = 10000;
207 (((self.storage_rebate as u128 * storage_rebate_rate as u128)
208 + (BASIS_POINTS / 2)) / BASIS_POINTS) as u64
210 }
211
212 pub fn net_gas_usage(&self) -> i64 {
215 self.gas_used() as i64 - self.storage_rebate as i64
216 }
217
218 #[expect(clippy::type_complexity)]
219 pub fn new_from_txn_effects<'a>(
220 transactions: impl Iterator<Item = &'a TransactionEffects>,
221 ) -> GasCostSummary {
222 let (
223 storage_costs,
224 computation_costs,
225 computation_costs_burned,
226 storage_rebates,
227 non_refundable_storage_fee,
228 ): (Vec<u64>, Vec<u64>, Vec<u64>, Vec<u64>, Vec<u64>) = transactions
229 .map(|e| {
230 (
231 e.gas_cost_summary().storage_cost,
232 e.gas_cost_summary().computation_cost,
233 e.gas_cost_summary().computation_cost_burned,
234 e.gas_cost_summary().storage_rebate,
235 e.gas_cost_summary().non_refundable_storage_fee,
236 )
237 })
238 .multiunzip();
239
240 GasCostSummary {
241 storage_cost: storage_costs.iter().sum(),
242 computation_cost: computation_costs.iter().sum(),
243 computation_cost_burned: computation_costs_burned.iter().sum(),
244 storage_rebate: storage_rebates.iter().sum(),
245 non_refundable_storage_fee: non_refundable_storage_fee.iter().sum(),
246 }
247 }
248 }
249
250 impl std::fmt::Display for GasCostSummary {
251 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252 write!(
253 f,
254 "computation_cost: {}, computation_cost_burned: {}, storage_cost: {}, storage_rebate: {}, non_refundable_storage_fee: {}",
255 self.computation_cost,
256 self.computation_cost_burned,
257 self.storage_cost,
258 self.storage_rebate,
259 self.non_refundable_storage_fee,
260 )
261 }
262 }
263
264 impl std::ops::AddAssign<&Self> for GasCostSummary {
265 fn add_assign(&mut self, other: &Self) {
266 self.computation_cost += other.computation_cost;
267 self.computation_cost_burned += other.computation_cost_burned;
268 self.storage_cost += other.storage_cost;
269 self.storage_rebate += other.storage_rebate;
270 self.non_refundable_storage_fee += other.non_refundable_storage_fee;
271 }
272 }
273
274 impl std::ops::AddAssign<Self> for GasCostSummary {
275 fn add_assign(&mut self, other: Self) {
276 self.add_assign(&other)
277 }
278 }
279
280 pub fn deduct_gas(gas_object: &mut Object, charge_or_rebate: i64) {
284 let gas_coin = gas_object.data.try_as_move_mut().unwrap();
286 let balance = gas_coin.get_coin_value_unsafe();
287 let new_balance = if charge_or_rebate < 0 {
288 balance + (-charge_or_rebate as u64)
289 } else {
290 assert!(balance >= charge_or_rebate as u64);
291 balance - charge_or_rebate as u64
292 };
293 gas_coin.set_coin_value_unsafe(new_balance)
294 }
295
296 pub fn get_gas_balance(gas_object: &Object) -> UserInputResult<u64> {
297 if let Some(move_obj) = gas_object.data.try_as_move() {
298 if !move_obj.type_().is_gas_coin() {
299 return Err(UserInputError::InvalidGasObject {
300 object_id: gas_object.id(),
301 });
302 }
303 Ok(move_obj.get_coin_value_unsafe())
304 } else {
305 Err(UserInputError::InvalidGasObject {
306 object_id: gas_object.id(),
307 })
308 }
309 }
310}