1pub use checked::*;
6
7#[sui_macros::with_checked_arithmetic]
8pub mod checked {
9
10 use crate::sui_types::gas::SuiGasStatusAPI;
11 use crate::temporary_store::TemporaryStore;
12 use sui_protocol_config::ProtocolConfig;
13 use sui_types::deny_list_v2::CONFIG_SETTING_DYNAMIC_FIELD_SIZE_FOR_GAS;
14 use sui_types::gas::{GasCostSummary, SuiGasStatus, deduct_gas};
15 use sui_types::gas_model::gas_predicates::{
16 charge_upgrades, dont_charge_budget_on_storage_oog,
17 };
18 use sui_types::{
19 accumulator_event::AccumulatorEvent,
20 base_types::{ObjectID, ObjectRef, SuiAddress},
21 digests::TransactionDigest,
22 error::ExecutionError,
23 gas_model::tables::GasStatus,
24 is_system_package,
25 object::Data,
26 };
27 use tracing::trace;
28
29 #[derive(Debug)]
38 pub struct GasCharger {
39 tx_digest: TransactionDigest,
40 gas_model_version: u64,
41 gas_coins: Vec<ObjectRef>,
42 smashed_gas_coin: Option<ObjectID>,
45 gas_status: SuiGasStatus,
46 address_balance_gas_payer: Option<SuiAddress>,
48 }
49
50 impl GasCharger {
51 pub fn new(
52 tx_digest: TransactionDigest,
53 gas_coins: Vec<ObjectRef>,
54 gas_status: SuiGasStatus,
55 protocol_config: &ProtocolConfig,
56 address_balance_gas_payer: Option<SuiAddress>,
57 ) -> Self {
58 let gas_model_version = protocol_config.gas_model_version();
59 Self {
60 tx_digest,
61 gas_model_version,
62 gas_coins,
63 smashed_gas_coin: None,
64 gas_status,
65 address_balance_gas_payer,
66 }
67 }
68
69 pub fn new_unmetered(tx_digest: TransactionDigest) -> Self {
70 Self {
71 tx_digest,
72 gas_model_version: 6, gas_coins: vec![],
74 smashed_gas_coin: None,
75 gas_status: SuiGasStatus::new_unmetered(),
76 address_balance_gas_payer: None,
77 }
78 }
79
80 pub(crate) fn gas_coins(&self) -> &[ObjectRef] {
83 &self.gas_coins
84 }
85
86 pub fn gas_coin(&self) -> Option<ObjectID> {
89 self.smashed_gas_coin
90 }
91
92 pub fn gas_budget(&self) -> u64 {
93 self.gas_status.gas_budget()
94 }
95
96 pub fn unmetered_storage_rebate(&self) -> u64 {
97 self.gas_status.unmetered_storage_rebate()
98 }
99
100 pub fn no_charges(&self) -> bool {
101 self.gas_status.gas_used() == 0
102 && self.gas_status.storage_rebate() == 0
103 && self.gas_status.storage_gas_units() == 0
104 }
105
106 pub fn is_unmetered(&self) -> bool {
107 self.gas_status.is_unmetered()
108 }
109
110 pub fn move_gas_status(&self) -> &GasStatus {
111 self.gas_status.move_gas_status()
112 }
113
114 pub fn move_gas_status_mut(&mut self) -> &mut GasStatus {
115 self.gas_status.move_gas_status_mut()
116 }
117
118 pub fn into_gas_status(self) -> SuiGasStatus {
119 self.gas_status
120 }
121
122 pub fn summary(&self) -> GasCostSummary {
123 self.gas_status.summary()
124 }
125
126 pub fn smash_gas(&mut self, temporary_store: &mut TemporaryStore<'_>) {
134 let gas_coin_count = self.gas_coins.len();
135 if gas_coin_count == 0 || (gas_coin_count == 1 && self.gas_coins[0].0 == ObjectID::ZERO)
136 {
137 return; }
139 let gas_coin_id = self.gas_coins[0].0;
142 self.smashed_gas_coin = Some(gas_coin_id);
143 if gas_coin_count == 1 {
144 return;
145 }
146
147 let new_balance = self
149 .gas_coins
150 .iter()
151 .map(|obj_ref| {
152 let obj = temporary_store.objects().get(&obj_ref.0).unwrap();
153 let Data::Move(move_obj) = &obj.data else {
154 return Err(ExecutionError::invariant_violation(
155 "Provided non-gas coin object as input for gas!",
156 ));
157 };
158 if !move_obj.type_().is_gas_coin() {
159 return Err(ExecutionError::invariant_violation(
160 "Provided non-gas coin object as input for gas!",
161 ));
162 }
163 Ok(move_obj.get_coin_value_unsafe())
164 })
165 .collect::<Result<Vec<u64>, ExecutionError>>()
166 .unwrap_or_else(|_| {
169 panic!(
170 "Invariant violation: non-gas coin object as input for gas in txn {}",
171 self.tx_digest
172 )
173 })
174 .iter()
175 .sum();
176 let mut primary_gas_object = temporary_store
177 .objects()
178 .get(&gas_coin_id)
179 .unwrap_or_else(|| {
181 panic!(
182 "Invariant violation: gas coin not found in store in txn {}",
183 self.tx_digest
184 )
185 })
186 .clone();
187 for (id, _version, _digest) in &self.gas_coins[1..] {
189 debug_assert_ne!(*id, primary_gas_object.id());
190 temporary_store.delete_input_object(id);
191 }
192 primary_gas_object
193 .data
194 .try_as_move_mut()
195 .unwrap_or_else(|| {
197 panic!(
198 "Invariant violation: invalid coin object in txn {}",
199 self.tx_digest
200 )
201 })
202 .set_coin_value_unsafe(new_balance);
203 temporary_store.mutate_input_object(primary_gas_object);
204 }
205
206 pub fn track_storage_mutation(
211 &mut self,
212 object_id: ObjectID,
213 new_size: usize,
214 storage_rebate: u64,
215 ) -> u64 {
216 self.gas_status
217 .track_storage_mutation(object_id, new_size, storage_rebate)
218 }
219
220 pub fn reset_storage_cost_and_rebate(&mut self) {
221 self.gas_status.reset_storage_cost_and_rebate();
222 }
223
224 pub fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
225 self.gas_status.charge_publish_package(size)
226 }
227
228 pub fn charge_upgrade_package(&mut self, size: usize) -> Result<(), ExecutionError> {
229 if charge_upgrades(self.gas_model_version) {
230 self.gas_status.charge_publish_package(size)
231 } else {
232 Ok(())
233 }
234 }
235
236 pub fn charge_input_objects(
237 &mut self,
238 temporary_store: &TemporaryStore<'_>,
239 ) -> Result<(), ExecutionError> {
240 let objects = temporary_store.objects();
241 let _object_count = objects.len();
243 let total_size = temporary_store
245 .objects()
246 .iter()
247 .filter(|(id, _)| !is_system_package(**id))
249 .map(|(_, obj)| obj.object_size_for_gas_metering())
250 .sum();
251 self.gas_status.charge_storage_read(total_size)
252 }
253
254 pub fn charge_coin_transfers(
255 &mut self,
256 protocol_config: &ProtocolConfig,
257 num_non_gas_coin_owners: u64,
258 ) -> Result<(), ExecutionError> {
259 let bytes_read_per_owner = CONFIG_SETTING_DYNAMIC_FIELD_SIZE_FOR_GAS;
263 let cost_per_byte =
266 protocol_config.dynamic_field_borrow_child_object_type_cost_per_byte() as usize;
267 let cost_per_owner = bytes_read_per_owner * cost_per_byte;
268 let owner_cost = cost_per_owner * (num_non_gas_coin_owners as usize);
269 self.gas_status.charge_storage_read(owner_cost)
270 }
271
272 pub fn reset(&mut self, temporary_store: &mut TemporaryStore<'_>) {
275 temporary_store.drop_writes();
276 self.gas_status.reset_storage_cost_and_rebate();
277 self.smash_gas(temporary_store);
278 }
279
280 pub fn charge_gas<T>(
291 &mut self,
292 temporary_store: &mut TemporaryStore<'_>,
293 execution_result: &mut Result<T, ExecutionError>,
294 ) -> GasCostSummary {
295 debug_assert!(self.gas_status.storage_rebate() == 0);
298 debug_assert!(self.gas_status.storage_gas_units() == 0);
299
300 if self.smashed_gas_coin.is_some() || self.address_balance_gas_payer.is_some() {
301 let is_move_abort = execution_result
303 .as_ref()
304 .err()
305 .map(|err| {
306 matches!(
307 err.kind(),
308 sui_types::execution_status::ExecutionFailureStatus::MoveAbort(_, _)
309 )
310 })
311 .unwrap_or(false);
312 if let Err(err) = self.gas_status.bucketize_computation(Some(is_move_abort))
314 && execution_result.is_ok()
315 {
316 *execution_result = Err(err);
317 }
318
319 if execution_result.is_err() {
321 self.reset(temporary_store);
322 }
323 }
324
325 temporary_store.ensure_active_inputs_mutated();
327 temporary_store.collect_storage_and_rebate(self);
328
329 if self.smashed_gas_coin.is_some() {
330 #[skip_checked_arithmetic]
331 trace!(target: "replay_gas_info", "Gas smashing has occurred for this transaction");
332 }
333
334 if let Some(payer_address) = self.address_balance_gas_payer {
335 let is_insufficient_balance_error = execution_result
336 .as_ref()
337 .err()
338 .map(|err| {
339 matches!(
340 err.kind(),
341 sui_types::execution_status::ExecutionFailureStatus::InsufficientBalanceForWithdraw
342 )
343 })
344 .unwrap_or(false);
345
346 if is_insufficient_balance_error {
349 GasCostSummary::default()
350 } else {
351 self.compute_storage_and_rebate(temporary_store, execution_result);
352
353 let cost_summary = self.gas_status.summary();
354 let net_change = cost_summary.net_gas_usage();
355
356 if net_change != 0 {
357 let balance_type = sui_types::balance::Balance::type_tag(
358 sui_types::gas_coin::GAS::type_tag(),
359 );
360 let accumulator_event = AccumulatorEvent::from_balance_change(
361 payer_address,
362 balance_type,
363 net_change,
364 )
365 .expect("Failed to create accumulator event for gas balance");
366
367 temporary_store.add_accumulator_event(accumulator_event);
368 }
369
370 cost_summary
371 }
372 } else if let Some(gas_object_id) = self.smashed_gas_coin {
373 self.compute_storage_and_rebate(temporary_store, execution_result);
374
375 let cost_summary = self.gas_status.summary();
376 let gas_used = cost_summary.net_gas_usage();
377
378 let mut gas_object = temporary_store.read_object(&gas_object_id).unwrap().clone();
379 deduct_gas(&mut gas_object, gas_used);
380 #[skip_checked_arithmetic]
381 trace!(gas_used, gas_obj_id =? gas_object.id(), gas_obj_ver =? gas_object.version(), "Updated gas object");
382
383 temporary_store.mutate_input_object(gas_object);
384 cost_summary
385 } else {
386 GasCostSummary::default()
389 }
390 }
391
392 fn compute_storage_and_rebate<T>(
404 &mut self,
405 temporary_store: &mut TemporaryStore<'_>,
406 execution_result: &mut Result<T, ExecutionError>,
407 ) {
408 if dont_charge_budget_on_storage_oog(self.gas_model_version) {
409 self.handle_storage_and_rebate_v2(temporary_store, execution_result)
410 } else {
411 self.handle_storage_and_rebate_v1(temporary_store, execution_result)
412 }
413 }
414
415 fn handle_storage_and_rebate_v1<T>(
416 &mut self,
417 temporary_store: &mut TemporaryStore<'_>,
418 execution_result: &mut Result<T, ExecutionError>,
419 ) {
420 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
421 self.reset(temporary_store);
422 self.gas_status.adjust_computation_on_out_of_gas();
423 temporary_store.ensure_active_inputs_mutated();
424 temporary_store.collect_rebate(self);
425 if execution_result.is_ok() {
426 *execution_result = Err(err);
427 }
428 }
429 }
430
431 fn handle_storage_and_rebate_v2<T>(
432 &mut self,
433 temporary_store: &mut TemporaryStore<'_>,
434 execution_result: &mut Result<T, ExecutionError>,
435 ) {
436 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
437 self.reset(temporary_store);
441 temporary_store.ensure_active_inputs_mutated();
442 temporary_store.collect_storage_and_rebate(self);
443 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
444 self.reset(temporary_store);
447 self.gas_status.adjust_computation_on_out_of_gas();
448 temporary_store.ensure_active_inputs_mutated();
449 temporary_store.collect_rebate(self);
450 if execution_result.is_ok() {
451 *execution_result = Err(err);
452 }
453 } else if execution_result.is_ok() {
454 *execution_result = Err(err);
455 }
456 }
457 }
458 }
459}