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 SUI_ACCUMULATOR_ROOT_OBJECT_ID,
20 accumulator_event::AccumulatorEvent,
21 accumulator_root::AccumulatorObjId,
22 base_types::{ObjectID, ObjectRef, SuiAddress},
23 digests::TransactionDigest,
24 error::ExecutionError,
25 gas_model::tables::GasStatus,
26 is_system_package,
27 object::Data,
28 };
29 use tracing::trace;
30
31 #[derive(Debug)]
40 pub struct GasCharger {
41 tx_digest: TransactionDigest,
42 gas_model_version: u64,
43 gas_coins: Vec<ObjectRef>,
44 smashed_gas_coin: Option<ObjectID>,
47 gas_status: SuiGasStatus,
48 address_balance_gas_payer: Option<SuiAddress>,
50 }
51
52 impl GasCharger {
53 pub fn new(
54 tx_digest: TransactionDigest,
55 gas_coins: Vec<ObjectRef>,
56 gas_status: SuiGasStatus,
57 protocol_config: &ProtocolConfig,
58 address_balance_gas_payer: Option<SuiAddress>,
59 ) -> Self {
60 let gas_model_version = protocol_config.gas_model_version();
61 Self {
62 tx_digest,
63 gas_model_version,
64 gas_coins,
65 smashed_gas_coin: None,
66 gas_status,
67 address_balance_gas_payer,
68 }
69 }
70
71 pub fn new_unmetered(tx_digest: TransactionDigest) -> Self {
72 Self {
73 tx_digest,
74 gas_model_version: 6, gas_coins: vec![],
76 smashed_gas_coin: None,
77 gas_status: SuiGasStatus::new_unmetered(),
78 address_balance_gas_payer: None,
79 }
80 }
81
82 pub(crate) fn gas_coins(&self) -> &[ObjectRef] {
85 &self.gas_coins
86 }
87
88 pub fn gas_coin(&self) -> Option<ObjectID> {
91 self.smashed_gas_coin
92 }
93
94 pub fn gas_budget(&self) -> u64 {
95 self.gas_status.gas_budget()
96 }
97
98 pub fn unmetered_storage_rebate(&self) -> u64 {
99 self.gas_status.unmetered_storage_rebate()
100 }
101
102 pub fn no_charges(&self) -> bool {
103 self.gas_status.gas_used() == 0
104 && self.gas_status.storage_rebate() == 0
105 && self.gas_status.storage_gas_units() == 0
106 }
107
108 pub fn is_unmetered(&self) -> bool {
109 self.gas_status.is_unmetered()
110 }
111
112 pub fn move_gas_status(&self) -> &GasStatus {
113 self.gas_status.move_gas_status()
114 }
115
116 pub fn move_gas_status_mut(&mut self) -> &mut GasStatus {
117 self.gas_status.move_gas_status_mut()
118 }
119
120 pub fn into_gas_status(self) -> SuiGasStatus {
121 self.gas_status
122 }
123
124 pub fn summary(&self) -> GasCostSummary {
125 self.gas_status.summary()
126 }
127
128 pub fn smash_gas(&mut self, temporary_store: &mut TemporaryStore<'_>) {
136 let gas_coin_count = self.gas_coins.len();
137 if gas_coin_count == 0 || (gas_coin_count == 1 && self.gas_coins[0].0 == ObjectID::ZERO)
138 {
139 return; }
141 let gas_coin_id = self.gas_coins[0].0;
144 self.smashed_gas_coin = Some(gas_coin_id);
145 if gas_coin_count == 1 {
146 return;
147 }
148
149 let new_balance = self
151 .gas_coins
152 .iter()
153 .map(|obj_ref| {
154 let obj = temporary_store.objects().get(&obj_ref.0).unwrap();
155 let Data::Move(move_obj) = &obj.data else {
156 return Err(ExecutionError::invariant_violation(
157 "Provided non-gas coin object as input for gas!",
158 ));
159 };
160 if !move_obj.type_().is_gas_coin() {
161 return Err(ExecutionError::invariant_violation(
162 "Provided non-gas coin object as input for gas!",
163 ));
164 }
165 Ok(move_obj.get_coin_value_unsafe())
166 })
167 .collect::<Result<Vec<u64>, ExecutionError>>()
168 .unwrap_or_else(|_| {
171 panic!(
172 "Invariant violation: non-gas coin object as input for gas in txn {}",
173 self.tx_digest
174 )
175 })
176 .iter()
177 .sum();
178 let mut primary_gas_object = temporary_store
179 .objects()
180 .get(&gas_coin_id)
181 .unwrap_or_else(|| {
183 panic!(
184 "Invariant violation: gas coin not found in store in txn {}",
185 self.tx_digest
186 )
187 })
188 .clone();
189 for (id, _version, _digest) in &self.gas_coins[1..] {
191 debug_assert_ne!(*id, primary_gas_object.id());
192 temporary_store.delete_input_object(id);
193 }
194 primary_gas_object
195 .data
196 .try_as_move_mut()
197 .unwrap_or_else(|| {
199 panic!(
200 "Invariant violation: invalid coin object in txn {}",
201 self.tx_digest
202 )
203 })
204 .set_coin_value_unsafe(new_balance);
205 temporary_store.mutate_input_object(primary_gas_object);
206 }
207
208 pub fn track_storage_mutation(
213 &mut self,
214 object_id: ObjectID,
215 new_size: usize,
216 storage_rebate: u64,
217 ) -> u64 {
218 self.gas_status
219 .track_storage_mutation(object_id, new_size, storage_rebate)
220 }
221
222 pub fn reset_storage_cost_and_rebate(&mut self) {
223 self.gas_status.reset_storage_cost_and_rebate();
224 }
225
226 pub fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
227 self.gas_status.charge_publish_package(size)
228 }
229
230 pub fn charge_upgrade_package(&mut self, size: usize) -> Result<(), ExecutionError> {
231 if charge_upgrades(self.gas_model_version) {
232 self.gas_status.charge_publish_package(size)
233 } else {
234 Ok(())
235 }
236 }
237
238 pub fn charge_input_objects(
239 &mut self,
240 temporary_store: &TemporaryStore<'_>,
241 ) -> Result<(), ExecutionError> {
242 let objects = temporary_store.objects();
243 let _object_count = objects.len();
245 let total_size = temporary_store
247 .objects()
248 .iter()
249 .filter(|(id, _)| !is_system_package(**id))
251 .map(|(_, obj)| obj.object_size_for_gas_metering())
252 .sum();
253 self.gas_status.charge_storage_read(total_size)
254 }
255
256 pub fn charge_coin_transfers(
257 &mut self,
258 protocol_config: &ProtocolConfig,
259 num_non_gas_coin_owners: u64,
260 ) -> Result<(), ExecutionError> {
261 let bytes_read_per_owner = CONFIG_SETTING_DYNAMIC_FIELD_SIZE_FOR_GAS;
265 let cost_per_byte =
268 protocol_config.dynamic_field_borrow_child_object_type_cost_per_byte() as usize;
269 let cost_per_owner = bytes_read_per_owner * cost_per_byte;
270 let owner_cost = cost_per_owner * (num_non_gas_coin_owners as usize);
271 self.gas_status.charge_storage_read(owner_cost)
272 }
273
274 pub fn reset(&mut self, temporary_store: &mut TemporaryStore<'_>) {
277 temporary_store.drop_writes();
278 self.gas_status.reset_storage_cost_and_rebate();
279 self.smash_gas(temporary_store);
280 }
281
282 pub fn charge_gas<T>(
293 &mut self,
294 temporary_store: &mut TemporaryStore<'_>,
295 execution_result: &mut Result<T, ExecutionError>,
296 ) -> GasCostSummary {
297 debug_assert!(self.gas_status.storage_rebate() == 0);
300 debug_assert!(self.gas_status.storage_gas_units() == 0);
301
302 if self.smashed_gas_coin.is_some() || self.address_balance_gas_payer.is_some() {
303 let is_move_abort = execution_result
305 .as_ref()
306 .err()
307 .map(|err| {
308 matches!(
309 err.kind(),
310 sui_types::execution_status::ExecutionFailureStatus::MoveAbort(_, _)
311 )
312 })
313 .unwrap_or(false);
314 if let Err(err) = self.gas_status.bucketize_computation(Some(is_move_abort))
316 && execution_result.is_ok()
317 {
318 *execution_result = Err(err);
319 }
320
321 if execution_result.is_err() {
323 self.reset(temporary_store);
324 }
325 }
326
327 temporary_store.ensure_active_inputs_mutated();
329 temporary_store.collect_storage_and_rebate(self);
330
331 if self.smashed_gas_coin.is_some() {
332 #[skip_checked_arithmetic]
333 trace!(target: "replay_gas_info", "Gas smashing has occurred for this transaction");
334 }
335
336 if let Some(payer_address) = self.address_balance_gas_payer {
339 let is_insufficient_balance_error = execution_result
340 .as_ref()
341 .err()
342 .map(|err| matches!(err.kind(), sui_types::execution_status::ExecutionFailureStatus::InsufficientBalanceForWithdraw))
343 .unwrap_or(false);
344
345 if is_insufficient_balance_error {
348 GasCostSummary::default()
349 } else {
350 let cost_summary = self.gas_status.summary();
351 let net_change = cost_summary.net_gas_usage();
352
353 if net_change != 0 {
354 let accumulator_event = AccumulatorEvent::from_balance_change(
355 AccumulatorObjId::new_unchecked(SUI_ACCUMULATOR_ROOT_OBJECT_ID),
356 payer_address,
357 net_change,
358 );
359
360 temporary_store.add_accumulator_event(accumulator_event);
361 }
362
363 cost_summary
364 }
365 } else if let Some(gas_object_id) = self.smashed_gas_coin {
366 if dont_charge_budget_on_storage_oog(self.gas_model_version) {
367 self.handle_storage_and_rebate_v2(temporary_store, execution_result)
368 } else {
369 self.handle_storage_and_rebate_v1(temporary_store, execution_result)
370 }
371
372 let cost_summary = self.gas_status.summary();
373 let gas_used = cost_summary.net_gas_usage();
374
375 let mut gas_object = temporary_store.read_object(&gas_object_id).unwrap().clone();
376 deduct_gas(&mut gas_object, gas_used);
377 #[skip_checked_arithmetic]
378 trace!(gas_used, gas_obj_id =? gas_object.id(), gas_obj_ver =? gas_object.version(), "Updated gas object");
379
380 temporary_store.mutate_input_object(gas_object);
381 cost_summary
382 } else {
383 GasCostSummary::default()
384 }
385 }
386
387 fn handle_storage_and_rebate_v1<T>(
388 &mut self,
389 temporary_store: &mut TemporaryStore<'_>,
390 execution_result: &mut Result<T, ExecutionError>,
391 ) {
392 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
393 self.reset(temporary_store);
394 self.gas_status.adjust_computation_on_out_of_gas();
395 temporary_store.ensure_active_inputs_mutated();
396 temporary_store.collect_rebate(self);
397 if execution_result.is_ok() {
398 *execution_result = Err(err);
399 }
400 }
401 }
402
403 fn handle_storage_and_rebate_v2<T>(
404 &mut self,
405 temporary_store: &mut TemporaryStore<'_>,
406 execution_result: &mut Result<T, ExecutionError>,
407 ) {
408 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
409 self.reset(temporary_store);
412 temporary_store.ensure_active_inputs_mutated();
413 temporary_store.collect_storage_and_rebate(self);
414 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
415 self.reset(temporary_store);
418 self.gas_status.adjust_computation_on_out_of_gas();
419 temporary_store.ensure_active_inputs_mutated();
420 temporary_store.collect_rebate(self);
421 if execution_result.is_ok() {
422 *execution_result = Err(err);
423 }
424 } else if execution_result.is_ok() {
425 *execution_result = Err(err);
426 }
427 }
428 }
429 }
430}