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