iota_adapter_latest/
gas_charger.rs1pub use checked::*;
7
8#[iota_macros::with_checked_arithmetic]
9pub mod checked {
10
11 use iota_protocol_config::ProtocolConfig;
12 use iota_types::{
13 base_types::{ObjectID, ObjectRef},
14 deny_list_v1::CONFIG_SETTING_DYNAMIC_FIELD_SIZE_FOR_GAS,
15 digests::TransactionDigest,
16 error::ExecutionError,
17 gas::{GasCostSummary, IotaGasStatus, deduct_gas},
18 gas_model::tables::GasStatus,
19 is_system_package,
20 object::Data,
21 };
22 use tracing::trace;
23
24 use crate::{iota_types::gas::IotaGasStatusAPI, temporary_store::TemporaryStore};
25
26 #[derive(Debug)]
36 pub struct GasCharger {
37 tx_digest: TransactionDigest,
38 #[expect(unused)]
39 gas_model_version: u64,
40 gas_coins: Vec<ObjectRef>,
41 smashed_gas_coin: Option<ObjectID>,
44 gas_status: IotaGasStatus,
45 }
46
47 impl GasCharger {
48 pub fn new(
49 tx_digest: TransactionDigest,
50 gas_coins: Vec<ObjectRef>,
51 gas_status: IotaGasStatus,
52 protocol_config: &ProtocolConfig,
53 ) -> Self {
54 let gas_model_version = protocol_config.gas_model_version();
55 Self {
56 tx_digest,
57 gas_model_version,
58 gas_coins,
59 smashed_gas_coin: None,
60 gas_status,
61 }
62 }
63
64 pub fn new_unmetered(tx_digest: TransactionDigest) -> Self {
65 Self {
66 tx_digest,
67 gas_model_version: 1, gas_coins: vec![],
69 smashed_gas_coin: None,
70 gas_status: IotaGasStatus::new_unmetered(),
71 }
72 }
73
74 pub(crate) fn gas_coins(&self) -> &[ObjectRef] {
77 &self.gas_coins
78 }
79
80 pub fn gas_coin(&self) -> Option<ObjectID> {
83 self.smashed_gas_coin
84 }
85
86 pub fn gas_budget(&self) -> u64 {
87 self.gas_status.gas_budget()
88 }
89
90 pub fn unmetered_storage_rebate(&self) -> u64 {
91 self.gas_status.unmetered_storage_rebate()
92 }
93
94 pub fn no_charges(&self) -> bool {
95 self.gas_status.gas_used() == 0
96 && self.gas_status.storage_rebate() == 0
97 && self.gas_status.storage_gas_units() == 0
98 }
99
100 pub fn is_unmetered(&self) -> bool {
101 self.gas_status.is_unmetered()
102 }
103
104 pub fn move_gas_status(&self) -> &GasStatus {
105 self.gas_status.move_gas_status()
106 }
107
108 pub fn move_gas_status_mut(&mut self) -> &mut GasStatus {
109 self.gas_status.move_gas_status_mut()
110 }
111
112 pub fn into_gas_status(self) -> IotaGasStatus {
113 self.gas_status
114 }
115
116 pub fn summary(&self) -> GasCostSummary {
117 self.gas_status.summary()
118 }
119
120 pub fn smash_gas(&mut self, temporary_store: &mut TemporaryStore<'_>) {
128 let gas_coin_count = self.gas_coins.len();
129 if gas_coin_count == 0 || (gas_coin_count == 1 && self.gas_coins[0].0 == ObjectID::ZERO)
130 {
131 return; }
133 let gas_coin_id = self.gas_coins[0].0;
136 self.smashed_gas_coin = Some(gas_coin_id);
137 if gas_coin_count == 1 {
138 return;
139 }
140
141 let new_balance = self
143 .gas_coins
144 .iter()
145 .map(|obj_ref| {
146 let obj = temporary_store.objects().get(&obj_ref.0).unwrap();
147 let Data::Move(move_obj) = &obj.data else {
148 return Err(ExecutionError::invariant_violation(
149 "Provided non-gas coin object as input for gas!",
150 ));
151 };
152 if !move_obj.type_().is_gas_coin() {
153 return Err(ExecutionError::invariant_violation(
154 "Provided non-gas coin object as input for gas!",
155 ));
156 }
157 Ok(move_obj.get_coin_value_unsafe())
158 })
159 .collect::<Result<Vec<u64>, ExecutionError>>()
160 .unwrap_or_else(|_| {
163 panic!(
164 "Invariant violation: non-gas coin object as input for gas in txn {}",
165 self.tx_digest
166 )
167 })
168 .iter()
169 .sum();
170 let mut primary_gas_object = temporary_store
171 .objects()
172 .get(&gas_coin_id)
173 .unwrap_or_else(|| {
176 panic!(
177 "Invariant violation: gas coin not found in store in txn {}",
178 self.tx_digest
179 )
180 })
181 .clone();
182 for (id, _version, _digest) in &self.gas_coins[1..] {
184 debug_assert_ne!(*id, primary_gas_object.id());
185 temporary_store.delete_input_object(id);
186 }
187 primary_gas_object
188 .data
189 .try_as_move_mut()
190 .unwrap_or_else(|| {
193 panic!(
194 "Invariant violation: invalid coin object in txn {}",
195 self.tx_digest
196 )
197 })
198 .set_coin_value_unsafe(new_balance);
199 temporary_store.mutate_input_object(primary_gas_object);
200 }
201
202 pub fn track_storage_mutation(
206 &mut self,
207 object_id: ObjectID,
208 new_size: usize,
209 storage_rebate: u64,
210 ) -> u64 {
211 self.gas_status
212 .track_storage_mutation(object_id, new_size, storage_rebate)
213 }
214
215 pub fn reset_storage_cost_and_rebate(&mut self) {
216 self.gas_status.reset_storage_cost_and_rebate();
217 }
218
219 pub fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
220 self.gas_status.charge_publish_package(size)
221 }
222
223 pub fn charge_upgrade_package(&mut self, size: usize) -> Result<(), ExecutionError> {
224 self.gas_status.charge_publish_package(size)
225 }
226
227 pub fn charge_input_objects(
228 &mut self,
229 temporary_store: &TemporaryStore<'_>,
230 ) -> Result<(), ExecutionError> {
231 let objects = temporary_store.objects();
232 let _object_count = objects.len();
234 let total_size = temporary_store
236 .objects()
237 .iter()
238 .filter(|(id, _)| !is_system_package(**id))
240 .map(|(_, obj)| obj.object_size_for_gas_metering())
241 .sum();
242 self.gas_status.charge_storage_read(total_size)
243 }
244
245 pub fn charge_coin_transfers(
246 &mut self,
247 protocol_config: &ProtocolConfig,
248 num_non_gas_coin_owners: u64,
249 ) -> Result<(), ExecutionError> {
250 let bytes_read_per_owner = CONFIG_SETTING_DYNAMIC_FIELD_SIZE_FOR_GAS;
254 let cost_per_byte =
257 protocol_config.dynamic_field_borrow_child_object_type_cost_per_byte() as usize;
258 let cost_per_owner = bytes_read_per_owner * cost_per_byte;
259 let owner_cost = cost_per_owner * (num_non_gas_coin_owners as usize);
260 self.gas_status.charge_storage_read(owner_cost)
261 }
262
263 pub fn reset(&mut self, temporary_store: &mut TemporaryStore<'_>) {
268 temporary_store.drop_writes();
269 self.gas_status.reset_storage_cost_and_rebate();
270 self.smash_gas(temporary_store);
271 }
272
273 pub fn charge_gas<T>(
286 &mut self,
287 temporary_store: &mut TemporaryStore<'_>,
288 execution_result: &mut Result<T, ExecutionError>,
289 ) -> GasCostSummary {
290 debug_assert!(self.gas_status.storage_rebate() == 0);
293 debug_assert!(self.gas_status.storage_gas_units() == 0);
294
295 if self.smashed_gas_coin.is_some() {
296 if let Err(err) = self.gas_status.bucketize_computation() {
298 if execution_result.is_ok() {
299 *execution_result = Err(err);
300 }
301 }
302
303 if execution_result.is_err() {
305 self.reset(temporary_store);
306 }
307 }
308
309 temporary_store.ensure_active_inputs_mutated();
311 temporary_store.collect_storage_and_rebate(self);
312
313 if self.smashed_gas_coin.is_some() {
314 #[skip_checked_arithmetic]
315 trace!(target: "replay_gas_info", "Gas smashing has occurred for this transaction");
316 }
317
318 if let Some(gas_object_id) = self.smashed_gas_coin {
322 self.handle_storage_and_rebate(temporary_store, execution_result);
323
324 let cost_summary = self.gas_status.summary();
325 let gas_used = cost_summary.net_gas_usage();
326
327 let mut gas_object = temporary_store.read_object(&gas_object_id).unwrap().clone();
328 deduct_gas(&mut gas_object, gas_used);
329 #[skip_checked_arithmetic]
330 trace!(gas_used, gas_obj_id =? gas_object.id(), gas_obj_ver =? gas_object.version(), "Updated gas object");
331
332 temporary_store.mutate_input_object(gas_object);
333 cost_summary
334 } else {
335 GasCostSummary::default()
336 }
337 }
338
339 fn handle_storage_and_rebate<T>(
340 &mut self,
341 temporary_store: &mut TemporaryStore<'_>,
342 execution_result: &mut Result<T, ExecutionError>,
343 ) {
344 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
345 self.reset(temporary_store);
348 temporary_store.ensure_active_inputs_mutated();
349 temporary_store.collect_storage_and_rebate(self);
350 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
351 self.reset(temporary_store);
354 self.gas_status.adjust_computation_on_out_of_gas();
355 temporary_store.ensure_active_inputs_mutated();
356 temporary_store.collect_rebate(self);
357 if execution_result.is_ok() {
358 *execution_result = Err(err);
359 }
360 } else if execution_result.is_ok() {
361 *execution_result = Err(err);
362 }
363 }
364 }
365 }
366}