sui_adapter_v2/
gas_charger.rs1pub 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::gas::{deduct_gas, GasCostSummary, SuiGasStatus};
14 use sui_types::gas_model::gas_predicates::{
15 charge_upgrades, dont_charge_budget_on_storage_oog,
16 };
17 use sui_types::{
18 base_types::{ObjectID, ObjectRef},
19 digests::TransactionDigest,
20 error::ExecutionError,
21 gas_model::tables::GasStatus,
22 is_system_package,
23 object::Data,
24 };
25 use tracing::trace;
26
27 #[derive(Debug)]
36 pub struct GasCharger {
37 tx_digest: TransactionDigest,
38 gas_model_version: u64,
39 gas_coins: Vec<ObjectRef>,
40 smashed_gas_coin: Option<ObjectID>,
43 gas_status: SuiGasStatus,
44 }
45
46 impl GasCharger {
47 pub fn new(
48 tx_digest: TransactionDigest,
49 gas_coins: Vec<ObjectRef>,
50 gas_status: SuiGasStatus,
51 protocol_config: &ProtocolConfig,
52 ) -> Self {
53 let gas_model_version = protocol_config.gas_model_version();
54 Self {
55 tx_digest,
56 gas_model_version,
57 gas_coins,
58 smashed_gas_coin: None,
59 gas_status,
60 }
61 }
62
63 pub fn new_unmetered(tx_digest: TransactionDigest) -> Self {
64 Self {
65 tx_digest,
66 gas_model_version: 6, gas_coins: vec![],
68 smashed_gas_coin: None,
69 gas_status: SuiGasStatus::new_unmetered(),
70 }
71 }
72
73 pub(crate) fn gas_coins(&self) -> &[ObjectRef] {
76 &self.gas_coins
77 }
78
79 pub fn gas_coin(&self) -> Option<ObjectID> {
82 self.smashed_gas_coin
83 }
84
85 pub fn gas_budget(&self) -> u64 {
86 self.gas_status.gas_budget()
87 }
88
89 pub fn unmetered_storage_rebate(&self) -> u64 {
90 self.gas_status.unmetered_storage_rebate()
91 }
92
93 pub fn no_charges(&self) -> bool {
94 self.gas_status.gas_used() == 0
95 && self.gas_status.storage_rebate() == 0
96 && self.gas_status.storage_gas_units() == 0
97 }
98
99 pub fn is_unmetered(&self) -> bool {
100 self.gas_status.is_unmetered()
101 }
102
103 pub fn move_gas_status(&self) -> &GasStatus {
104 self.gas_status.move_gas_status()
105 }
106
107 pub fn move_gas_status_mut(&mut self) -> &mut GasStatus {
108 self.gas_status.move_gas_status_mut()
109 }
110
111 pub fn into_gas_status(self) -> SuiGasStatus {
112 self.gas_status
113 }
114
115 pub fn summary(&self) -> GasCostSummary {
116 self.gas_status.summary()
117 }
118
119 pub fn smash_gas(&mut self, temporary_store: &mut TemporaryStore<'_>) {
127 let gas_coin_count = self.gas_coins.len();
128 if gas_coin_count == 0 || (gas_coin_count == 1 && self.gas_coins[0].0 == ObjectID::ZERO)
129 {
130 return; }
132 let gas_coin_id = self.gas_coins[0].0;
135 self.smashed_gas_coin = Some(gas_coin_id);
136 if gas_coin_count == 1 {
137 return;
138 }
139
140 let new_balance = self
142 .gas_coins
143 .iter()
144 .map(|obj_ref| {
145 let obj = temporary_store.objects().get(&obj_ref.0).unwrap();
146 let Data::Move(move_obj) = &obj.data else {
147 return Err(ExecutionError::invariant_violation(
148 "Provided non-gas coin object as input for gas!",
149 ));
150 };
151 if !move_obj.type_().is_gas_coin() {
152 return Err(ExecutionError::invariant_violation(
153 "Provided non-gas coin object as input for gas!",
154 ));
155 }
156 Ok(move_obj.get_coin_value_unsafe())
157 })
158 .collect::<Result<Vec<u64>, ExecutionError>>()
159 .unwrap_or_else(|_| {
162 panic!(
163 "Invariant violation: non-gas coin object as input for gas in txn {}",
164 self.tx_digest
165 )
166 })
167 .iter()
168 .sum();
169 let mut primary_gas_object = temporary_store
170 .objects()
171 .get(&gas_coin_id)
172 .unwrap_or_else(|| {
174 panic!(
175 "Invariant violation: gas coin not found in store in txn {}",
176 self.tx_digest
177 )
178 })
179 .clone();
180 for (id, _version, _digest) in &self.gas_coins[1..] {
182 debug_assert_ne!(*id, primary_gas_object.id());
183 temporary_store.delete_input_object(id);
184 }
185 primary_gas_object
186 .data
187 .try_as_move_mut()
188 .unwrap_or_else(|| {
190 panic!(
191 "Invariant violation: invalid coin object in txn {}",
192 self.tx_digest
193 )
194 })
195 .set_coin_value_unsafe(new_balance);
196 temporary_store.mutate_input_object(primary_gas_object);
197 }
198
199 pub fn track_storage_mutation(
204 &mut self,
205 object_id: ObjectID,
206 new_size: usize,
207 storage_rebate: u64,
208 ) -> u64 {
209 self.gas_status
210 .track_storage_mutation(object_id, new_size, storage_rebate)
211 }
212
213 pub fn reset_storage_cost_and_rebate(&mut self) {
214 self.gas_status.reset_storage_cost_and_rebate();
215 }
216
217 pub fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
218 self.gas_status.charge_publish_package(size)
219 }
220
221 pub fn charge_upgrade_package(&mut self, size: usize) -> Result<(), ExecutionError> {
222 if charge_upgrades(self.gas_model_version) {
223 self.gas_status.charge_publish_package(size)
224 } else {
225 Ok(())
226 }
227 }
228
229 pub fn charge_input_objects(
230 &mut self,
231 temporary_store: &TemporaryStore<'_>,
232 ) -> Result<(), ExecutionError> {
233 let objects = temporary_store.objects();
234 let _object_count = objects.len();
236 let total_size = temporary_store
238 .objects()
239 .iter()
240 .filter(|(id, _)| !is_system_package(**id))
242 .map(|(_, obj)| obj.object_size_for_gas_metering())
243 .sum();
244 self.gas_status.charge_storage_read(total_size)
245 }
246
247 pub fn reset(&mut self, temporary_store: &mut TemporaryStore<'_>) {
250 temporary_store.drop_writes();
251 self.gas_status.reset_storage_cost_and_rebate();
252 self.smash_gas(temporary_store);
253 }
254
255 pub fn charge_gas<T>(
266 &mut self,
267 temporary_store: &mut TemporaryStore<'_>,
268 execution_result: &mut Result<T, ExecutionError>,
269 ) -> GasCostSummary {
270 debug_assert!(self.gas_status.storage_rebate() == 0);
273 debug_assert!(self.gas_status.storage_gas_units() == 0);
274
275 if self.smashed_gas_coin.is_some() {
276 if let Err(err) = self.gas_status.bucketize_computation(None) {
278 if execution_result.is_ok() {
279 *execution_result = Err(err);
280 }
281 }
282
283 if execution_result.is_err() {
285 self.reset(temporary_store);
286 }
287 }
288
289 temporary_store.ensure_active_inputs_mutated();
291 temporary_store.collect_storage_and_rebate(self);
292
293 if self.smashed_gas_coin.is_some() {
294 #[skip_checked_arithmetic]
295 trace!(target: "replay_gas_info", "Gas smashing has occurred for this transaction");
296 }
297
298 if let Some(gas_object_id) = self.smashed_gas_coin {
301 if dont_charge_budget_on_storage_oog(self.gas_model_version) {
302 self.handle_storage_and_rebate_v2(temporary_store, execution_result)
303 } else {
304 self.handle_storage_and_rebate_v1(temporary_store, execution_result)
305 }
306
307 let cost_summary = self.gas_status.summary();
308 let gas_used = cost_summary.net_gas_usage();
309
310 let mut gas_object = temporary_store.read_object(&gas_object_id).unwrap().clone();
311 deduct_gas(&mut gas_object, gas_used);
312 #[skip_checked_arithmetic]
313 trace!(gas_used, gas_obj_id =? gas_object.id(), gas_obj_ver =? gas_object.version(), "Updated gas object");
314
315 temporary_store.mutate_input_object(gas_object);
316 cost_summary
317 } else {
318 GasCostSummary::default()
319 }
320 }
321
322 fn handle_storage_and_rebate_v1<T>(
323 &mut self,
324 temporary_store: &mut TemporaryStore<'_>,
325 execution_result: &mut Result<T, ExecutionError>,
326 ) {
327 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
328 self.reset(temporary_store);
329 self.gas_status.adjust_computation_on_out_of_gas();
330 temporary_store.ensure_active_inputs_mutated();
331 temporary_store.collect_rebate(self);
332 if execution_result.is_ok() {
333 *execution_result = Err(err);
334 }
335 }
336 }
337
338 fn handle_storage_and_rebate_v2<T>(
339 &mut self,
340 temporary_store: &mut TemporaryStore<'_>,
341 execution_result: &mut Result<T, ExecutionError>,
342 ) {
343 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
344 self.reset(temporary_store);
347 temporary_store.ensure_active_inputs_mutated();
348 temporary_store.collect_storage_and_rebate(self);
349 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
350 self.reset(temporary_store);
353 self.gas_status.adjust_computation_on_out_of_gas();
354 temporary_store.ensure_active_inputs_mutated();
355 temporary_store.collect_rebate(self);
356 if execution_result.is_ok() {
357 *execution_result = Err(err);
358 }
359 } else if execution_result.is_ok() {
360 *execution_result = Err(err);
361 }
362 }
363 }
364 }
365}