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 storage_gas_units(&self) -> u64;
37 fn storage_rebate(&self) -> u64;
38 fn unmetered_storage_rebate(&self) -> u64;
39 fn gas_used(&self) -> u64;
40 fn reset_storage_cost_and_rebate(&mut self);
41 fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError>;
42 fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError>;
43 fn track_storage_mutation(
44 &mut self,
45 object_id: ObjectID,
46 new_size: usize,
47 storage_rebate: u64,
48 ) -> u64;
49 fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError>;
50 fn adjust_computation_on_out_of_gas(&mut self);
51 }
52
53 #[enum_dispatch(IotaGasStatusAPI)]
55 #[derive(Debug)]
56 pub enum IotaGasStatus {
57 V1(IotaGasStatusV1),
58 }
59
60 impl IotaGasStatus {
61 pub fn new(
62 gas_budget: u64,
63 gas_price: u64,
64 reference_gas_price: u64,
65 config: &ProtocolConfig,
66 ) -> IotaResult<Self> {
67 if gas_price < reference_gas_price {
72 return Err(UserInputError::GasPriceUnderRGP {
73 gas_price,
74 reference_gas_price,
75 }
76 .into());
77 }
78 if gas_price > config.max_gas_price() {
79 return Err(UserInputError::GasPriceTooHigh {
80 max_gas_price: config.max_gas_price(),
81 }
82 .into());
83 }
84
85 Ok(Self::V1(IotaGasStatusV1::new_with_budget(
86 gas_budget,
87 gas_price,
88 reference_gas_price,
89 config,
90 )))
91 }
92
93 pub fn new_unmetered() -> Self {
94 Self::V1(IotaGasStatusV1::new_unmetered())
97 }
98
99 pub fn check_gas_balance(
102 &self,
103 gas_objs: &[&ObjectReadResult],
104 gas_budget: u64,
105 ) -> UserInputResult {
106 match self {
107 Self::V1(status) => status.check_gas_balance(gas_objs, gas_budget),
108 }
109 }
110 }
111
112 #[serde_as]
139 #[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
140 #[serde(rename_all = "camelCase")]
141 pub struct GasCostSummary {
142 #[schemars(with = "BigInt<u64>")]
144 #[serde_as(as = "Readable<BigInt<u64>, _>")]
145 pub computation_cost: u64,
146 #[schemars(with = "BigInt<u64>")]
148 #[serde_as(as = "Readable<BigInt<u64>, _>")]
149 pub computation_cost_burned: u64,
150 #[schemars(with = "BigInt<u64>")]
153 #[serde_as(as = "Readable<BigInt<u64>, _>")]
154 pub storage_cost: u64,
155 #[schemars(with = "BigInt<u64>")]
158 #[serde_as(as = "Readable<BigInt<u64>, _>")]
159 pub storage_rebate: u64,
160 #[schemars(with = "BigInt<u64>")]
163 #[serde_as(as = "Readable<BigInt<u64>, _>")]
164 pub non_refundable_storage_fee: u64,
165 }
166
167 impl GasCostSummary {
168 pub fn new(
169 computation_cost: u64,
170 computation_cost_burned: u64,
171 storage_cost: u64,
172 storage_rebate: u64,
173 non_refundable_storage_fee: u64,
174 ) -> GasCostSummary {
175 GasCostSummary {
176 computation_cost,
177 computation_cost_burned,
178 storage_cost,
179 storage_rebate,
180 non_refundable_storage_fee,
181 }
182 }
183
184 pub fn gas_used(&self) -> u64 {
185 self.computation_cost + self.storage_cost
186 }
187
188 pub fn sender_rebate(&self, storage_rebate_rate: u64) -> u64 {
192 const BASIS_POINTS: u128 = 10000;
195 (((self.storage_rebate as u128 * storage_rebate_rate as u128)
196 + (BASIS_POINTS / 2)) / BASIS_POINTS) as u64
198 }
199
200 pub fn net_gas_usage(&self) -> i64 {
203 self.gas_used() as i64 - self.storage_rebate as i64
204 }
205
206 #[expect(clippy::type_complexity)]
207 pub fn new_from_txn_effects<'a>(
208 transactions: impl Iterator<Item = &'a TransactionEffects>,
209 ) -> GasCostSummary {
210 let (
211 storage_costs,
212 computation_costs,
213 computation_costs_burned,
214 storage_rebates,
215 non_refundable_storage_fee,
216 ): (Vec<u64>, Vec<u64>, Vec<u64>, Vec<u64>, Vec<u64>) = transactions
217 .map(|e| {
218 (
219 e.gas_cost_summary().storage_cost,
220 e.gas_cost_summary().computation_cost,
221 e.gas_cost_summary().computation_cost_burned,
222 e.gas_cost_summary().storage_rebate,
223 e.gas_cost_summary().non_refundable_storage_fee,
224 )
225 })
226 .multiunzip();
227
228 GasCostSummary {
229 storage_cost: storage_costs.iter().sum(),
230 computation_cost: computation_costs.iter().sum(),
231 computation_cost_burned: computation_costs_burned.iter().sum(),
232 storage_rebate: storage_rebates.iter().sum(),
233 non_refundable_storage_fee: non_refundable_storage_fee.iter().sum(),
234 }
235 }
236 }
237
238 impl std::fmt::Display for GasCostSummary {
239 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 write!(
241 f,
242 "computation_cost: {}, computation_cost_burned: {}, storage_cost: {}, storage_rebate: {}, non_refundable_storage_fee: {}",
243 self.computation_cost,
244 self.computation_cost_burned,
245 self.storage_cost,
246 self.storage_rebate,
247 self.non_refundable_storage_fee,
248 )
249 }
250 }
251
252 impl std::ops::AddAssign<&Self> for GasCostSummary {
253 fn add_assign(&mut self, other: &Self) {
254 self.computation_cost += other.computation_cost;
255 self.computation_cost_burned += other.computation_cost_burned;
256 self.storage_cost += other.storage_cost;
257 self.storage_rebate += other.storage_rebate;
258 self.non_refundable_storage_fee += other.non_refundable_storage_fee;
259 }
260 }
261
262 impl std::ops::AddAssign<Self> for GasCostSummary {
263 fn add_assign(&mut self, other: Self) {
264 self.add_assign(&other)
265 }
266 }
267
268 pub fn deduct_gas(gas_object: &mut Object, charge_or_rebate: i64) {
272 let gas_coin = gas_object.data.try_as_move_mut().unwrap();
274 let balance = gas_coin.get_coin_value_unsafe();
275 let new_balance = if charge_or_rebate < 0 {
276 balance + (-charge_or_rebate as u64)
277 } else {
278 assert!(balance >= charge_or_rebate as u64);
279 balance - charge_or_rebate as u64
280 };
281 gas_coin.set_coin_value_unsafe(new_balance)
282 }
283
284 pub fn get_gas_balance(gas_object: &Object) -> UserInputResult<u64> {
285 if let Some(move_obj) = gas_object.data.try_as_move() {
286 if !move_obj.type_().is_gas_coin() {
287 return Err(UserInputError::InvalidGasObject {
288 object_id: gas_object.id(),
289 });
290 }
291 Ok(move_obj.get_coin_value_unsafe())
292 } else {
293 Err(UserInputError::InvalidGasObject {
294 object_id: gas_object.id(),
295 })
296 }
297 }
298}