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 either::Either;
13 use sui_protocol_config::ProtocolConfig;
14 use sui_types::deny_list_v2::CONFIG_SETTING_DYNAMIC_FIELD_SIZE_FOR_GAS;
15 use sui_types::gas::{GasCostSummary, SuiGasStatus, deduct_gas};
16 use sui_types::gas_model::gas_predicates::{
17 charge_upgrades, dont_charge_budget_on_storage_oog,
18 };
19 use sui_types::{
20 accumulator_event::AccumulatorEvent,
21 base_types::{ObjectID, ObjectRef, SuiAddress},
22 digests::TransactionDigest,
23 error::ExecutionError,
24 gas_model::tables::GasStatus,
25 is_system_package,
26 object::Data,
27 };
28 use tracing::trace;
29
30 #[derive(Debug)]
39 pub struct GasCharger {
40 tx_digest: TransactionDigest,
41 gas_model_version: u64,
42 payment_method: PaymentMethod,
43 smashed_gas_coin: Option<ObjectID>,
46 gas_status: SuiGasStatus,
48 }
49
50 #[derive(Debug)]
51 pub enum PaymentMethod {
52 Unmetered,
53 Coins(Vec<ObjectRef>),
54 AddressBalance(SuiAddress),
55 }
56
57 impl PaymentMethod {
58 pub fn is_unmetered(&self) -> bool {
59 matches!(self, PaymentMethod::Unmetered)
60 }
61 pub fn is_address_balance(&self) -> bool {
62 matches!(self, PaymentMethod::AddressBalance(_))
63 }
64 pub fn is_coins(&self) -> bool {
65 matches!(self, PaymentMethod::Coins(_))
66 }
67 }
68
69 impl GasCharger {
70 pub fn new(
71 tx_digest: TransactionDigest,
72 payment_method: PaymentMethod,
73 gas_status: SuiGasStatus,
74 protocol_config: &ProtocolConfig,
75 ) -> Self {
76 let gas_model_version = protocol_config.gas_model_version();
77 Self {
78 tx_digest,
79 gas_model_version,
80 payment_method,
81 smashed_gas_coin: None,
82 gas_status,
83 }
84 }
85
86 pub fn new_unmetered(tx_digest: TransactionDigest) -> Self {
87 Self {
88 tx_digest,
89 gas_model_version: 6, payment_method: PaymentMethod::Unmetered,
91 smashed_gas_coin: None,
92 gas_status: SuiGasStatus::new_unmetered(),
93 }
94 }
95
96 pub(crate) fn gas_coins(&self) -> impl Iterator<Item = &'_ ObjectRef> {
99 match &self.payment_method {
100 PaymentMethod::Coins(gas_coins) => Either::Left(gas_coins.iter()),
101 PaymentMethod::AddressBalance(_) | PaymentMethod::Unmetered => {
102 Either::Right(std::iter::empty())
103 }
104 }
105 }
106
107 pub fn gas_coin(&self) -> Option<ObjectID> {
110 self.smashed_gas_coin
111 }
112
113 pub fn gas_budget(&self) -> u64 {
114 self.gas_status.gas_budget()
115 }
116
117 pub fn unmetered_storage_rebate(&self) -> u64 {
118 self.gas_status.unmetered_storage_rebate()
119 }
120
121 pub fn no_charges(&self) -> bool {
122 self.gas_status.gas_used() == 0
123 && self.gas_status.storage_rebate() == 0
124 && self.gas_status.storage_gas_units() == 0
125 }
126
127 pub fn is_unmetered(&self) -> bool {
128 self.gas_status.is_unmetered()
129 }
130
131 pub fn move_gas_status(&self) -> &GasStatus {
132 self.gas_status.move_gas_status()
133 }
134
135 pub fn move_gas_status_mut(&mut self) -> &mut GasStatus {
136 self.gas_status.move_gas_status_mut()
137 }
138
139 pub fn into_gas_status(self) -> SuiGasStatus {
140 self.gas_status
141 }
142
143 pub fn summary(&self) -> GasCostSummary {
144 self.gas_status.summary()
145 }
146
147 pub fn smash_gas(&mut self, temporary_store: &mut TemporaryStore<'_>) {
155 if let PaymentMethod::Coins(gas_coins) = &mut self.payment_method {
156 let gas_coin_count = gas_coins.len();
157 if gas_coin_count == 0 || (gas_coin_count == 1 && gas_coins[0].0 == ObjectID::ZERO)
158 {
159 return; }
161 let gas_coin_id = gas_coins[0].0;
164 self.smashed_gas_coin = Some(gas_coin_id);
165 if gas_coin_count == 1 {
166 return;
167 }
168
169 let new_balance = gas_coins
171 .iter()
172 .map(|obj_ref| {
173 let obj = temporary_store.objects().get(&obj_ref.0).unwrap();
174 let Data::Move(move_obj) = &obj.data else {
175 return Err(ExecutionError::invariant_violation(
176 "Provided non-gas coin object as input for gas!",
177 ));
178 };
179 if !move_obj.type_().is_gas_coin() {
180 return Err(ExecutionError::invariant_violation(
181 "Provided non-gas coin object as input for gas!",
182 ));
183 }
184 Ok(move_obj.get_coin_value_unsafe())
185 })
186 .collect::<Result<Vec<u64>, ExecutionError>>()
187 .unwrap_or_else(|_| {
190 panic!(
191 "Invariant violation: non-gas coin object as input for gas in txn {}",
192 self.tx_digest
193 )
194 })
195 .iter()
196 .sum();
197 let mut primary_gas_object = temporary_store
198 .objects()
199 .get(&gas_coin_id)
200 .unwrap_or_else(|| {
202 panic!(
203 "Invariant violation: gas coin not found in store in txn {}",
204 self.tx_digest
205 )
206 })
207 .clone();
208 for (id, _version, _digest) in &gas_coins[1..] {
210 debug_assert_ne!(*id, primary_gas_object.id());
211 temporary_store.delete_input_object(id);
212 }
213 primary_gas_object
214 .data
215 .try_as_move_mut()
216 .unwrap_or_else(|| {
218 panic!(
219 "Invariant violation: invalid coin object in txn {}",
220 self.tx_digest
221 )
222 })
223 .set_coin_value_unsafe(new_balance);
224 temporary_store.mutate_input_object(primary_gas_object);
225 }
226 }
227
228 pub fn track_storage_mutation(
233 &mut self,
234 object_id: ObjectID,
235 new_size: usize,
236 storage_rebate: u64,
237 ) -> u64 {
238 self.gas_status
239 .track_storage_mutation(object_id, new_size, storage_rebate)
240 }
241
242 pub fn reset_storage_cost_and_rebate(&mut self) {
243 self.gas_status.reset_storage_cost_and_rebate();
244 }
245
246 pub fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
247 self.gas_status.charge_publish_package(size)
248 }
249
250 pub fn charge_upgrade_package(&mut self, size: usize) -> Result<(), ExecutionError> {
251 if charge_upgrades(self.gas_model_version) {
252 self.gas_status.charge_publish_package(size)
253 } else {
254 Ok(())
255 }
256 }
257
258 pub fn charge_input_objects(
259 &mut self,
260 temporary_store: &TemporaryStore<'_>,
261 ) -> Result<(), ExecutionError> {
262 let objects = temporary_store.objects();
263 let _object_count = objects.len();
265 let total_size = temporary_store
267 .objects()
268 .iter()
269 .filter(|(id, _)| !is_system_package(**id))
271 .map(|(_, obj)| obj.object_size_for_gas_metering())
272 .sum();
273 self.gas_status.charge_storage_read(total_size)
274 }
275
276 pub fn charge_coin_transfers(
277 &mut self,
278 protocol_config: &ProtocolConfig,
279 num_non_gas_coin_owners: u64,
280 ) -> Result<(), ExecutionError> {
281 let bytes_read_per_owner = CONFIG_SETTING_DYNAMIC_FIELD_SIZE_FOR_GAS;
285 let cost_per_byte =
288 protocol_config.dynamic_field_borrow_child_object_type_cost_per_byte() as usize;
289 let cost_per_owner = bytes_read_per_owner * cost_per_byte;
290 let owner_cost = cost_per_owner * (num_non_gas_coin_owners as usize);
291 self.gas_status.charge_storage_read(owner_cost)
292 }
293
294 pub fn reset(&mut self, temporary_store: &mut TemporaryStore<'_>) {
297 temporary_store.drop_writes();
298 self.gas_status.reset_storage_cost_and_rebate();
299 self.smash_gas(temporary_store);
300 }
301
302 pub fn charge_gas<T>(
313 &mut self,
314 temporary_store: &mut TemporaryStore<'_>,
315 execution_result: &mut Result<T, ExecutionError>,
316 ) -> GasCostSummary {
317 debug_assert!(self.gas_status.storage_rebate() == 0);
320 debug_assert!(self.gas_status.storage_gas_units() == 0);
321
322 if self.smashed_gas_coin.is_some() || self.payment_method.is_address_balance() {
323 let is_move_abort = execution_result
325 .as_ref()
326 .err()
327 .map(|err| {
328 matches!(
329 err.kind(),
330 sui_types::execution_status::ExecutionFailureStatus::MoveAbort(_, _)
331 )
332 })
333 .unwrap_or(false);
334 if let Err(err) = self.gas_status.bucketize_computation(Some(is_move_abort))
336 && execution_result.is_ok()
337 {
338 *execution_result = Err(err);
339 }
340
341 if execution_result.is_err() {
343 self.reset(temporary_store);
344 }
345 }
346
347 temporary_store.ensure_active_inputs_mutated();
349 temporary_store.collect_storage_and_rebate(self);
350
351 if self.smashed_gas_coin.is_some() {
352 #[skip_checked_arithmetic]
353 trace!(target: "replay_gas_info", "Gas smashing has occurred for this transaction");
354 }
355
356 if self.payment_method.is_unmetered() {
357 return GasCostSummary::default();
358 }
359
360 if execution_result
361 .as_ref()
362 .err()
363 .map(|err| {
364 matches!(
365 err.kind(),
366 sui_types::execution_status::ExecutionFailureStatus::InsufficientFundsForWithdraw
367 )
368 })
369 .unwrap_or(false)
370 && self.payment_method.is_address_balance() {
371 return GasCostSummary::default();
374 }
375
376 self.compute_storage_and_rebate(temporary_store, execution_result);
377 let cost_summary = self.gas_status.summary();
378 let net_change = cost_summary.net_gas_usage();
379
380 match self.payment_method {
381 PaymentMethod::AddressBalance(payer_address) => {
382 if net_change != 0 {
383 let balance_type = sui_types::balance::Balance::type_tag(
384 sui_types::gas_coin::GAS::type_tag(),
385 );
386 let accumulator_event = AccumulatorEvent::from_balance_change(
387 payer_address,
388 balance_type,
389 net_change,
390 )
391 .expect("Failed to create accumulator event for gas balance");
392
393 temporary_store.add_accumulator_event(accumulator_event);
394 }
395
396 cost_summary
397 }
398 PaymentMethod::Coins(_) => {
399 let gas_object_id = self.smashed_gas_coin.unwrap();
400
401 let mut gas_object =
402 temporary_store.read_object(&gas_object_id).unwrap().clone();
403 deduct_gas(&mut gas_object, net_change);
404 #[skip_checked_arithmetic]
405 trace!(net_change, gas_obj_id =? gas_object.id(), gas_obj_ver =? gas_object.version(), "Updated gas object");
406
407 temporary_store.mutate_input_object(gas_object);
408 cost_summary
409 }
410 PaymentMethod::Unmetered => unreachable!(),
411 }
412 }
413
414 fn compute_storage_and_rebate<T>(
426 &mut self,
427 temporary_store: &mut TemporaryStore<'_>,
428 execution_result: &mut Result<T, ExecutionError>,
429 ) {
430 if dont_charge_budget_on_storage_oog(self.gas_model_version) {
431 self.handle_storage_and_rebate_v2(temporary_store, execution_result)
432 } else {
433 self.handle_storage_and_rebate_v1(temporary_store, execution_result)
434 }
435 }
436
437 fn handle_storage_and_rebate_v1<T>(
438 &mut self,
439 temporary_store: &mut TemporaryStore<'_>,
440 execution_result: &mut Result<T, ExecutionError>,
441 ) {
442 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
443 self.reset(temporary_store);
444 self.gas_status.adjust_computation_on_out_of_gas();
445 temporary_store.ensure_active_inputs_mutated();
446 temporary_store.collect_rebate(self);
447 if execution_result.is_ok() {
448 *execution_result = Err(err);
449 }
450 }
451 }
452
453 fn handle_storage_and_rebate_v2<T>(
454 &mut self,
455 temporary_store: &mut TemporaryStore<'_>,
456 execution_result: &mut Result<T, ExecutionError>,
457 ) {
458 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
459 self.reset(temporary_store);
463 temporary_store.ensure_active_inputs_mutated();
464 temporary_store.collect_storage_and_rebate(self);
465 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
466 self.reset(temporary_store);
469 self.gas_status.adjust_computation_on_out_of_gas();
470 temporary_store.ensure_active_inputs_mutated();
471 temporary_store.collect_rebate(self);
472 if execution_result.is_ok() {
473 *execution_result = Err(err);
474 }
475 } else if execution_result.is_ok() {
476 *execution_result = Err(err);
477 }
478 }
479 }
480 }
481}