sui_adapter_v0/
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::dont_charge_budget_on_storage_oog;
15 use sui_types::{
16 base_types::{ObjectID, ObjectRef},
17 digests::TransactionDigest,
18 error::ExecutionError,
19 gas_model::tables::GasStatus,
20 is_system_package,
21 object::Data,
22 storage::{DeleteKindWithOldVersion, WriteKind},
23 };
24 use tracing::trace;
25
26 #[derive(Debug)]
35 pub struct GasCharger {
36 tx_digest: TransactionDigest,
37 gas_model_version: u64,
38 gas_coins: Vec<ObjectRef>,
39 smashed_gas_coin: Option<ObjectID>,
42 gas_status: SuiGasStatus,
43 }
44
45 impl GasCharger {
46 pub fn new(
47 tx_digest: TransactionDigest,
48 gas_coins: Vec<ObjectRef>,
49 gas_status: SuiGasStatus,
50 protocol_config: &ProtocolConfig,
51 ) -> Self {
52 let gas_model_version = protocol_config.gas_model_version();
53 Self {
54 tx_digest,
55 gas_model_version,
56 gas_coins,
57 smashed_gas_coin: None,
58 gas_status,
59 }
60 }
61
62 pub fn new_unmetered(
63 tx_digest: TransactionDigest,
64 protocol_config: &ProtocolConfig,
65 ) -> Self {
66 Self {
67 tx_digest,
68 gas_model_version: 6, gas_coins: vec![],
70 smashed_gas_coin: None,
71 gas_status: SuiGasStatus::new_unmetered(protocol_config),
72 }
73 }
74
75 pub(crate) fn gas_coins(&self) -> &[ObjectRef] {
78 &self.gas_coins
79 }
80
81 pub fn gas_coin(&self) -> Option<ObjectID> {
84 self.smashed_gas_coin
85 }
86
87 pub fn gas_budget(&self) -> u64 {
88 self.gas_status.gas_budget()
89 }
90
91 pub fn unmetered_storage_rebate(&self) -> u64 {
92 self.gas_status.unmetered_storage_rebate()
93 }
94
95 pub fn no_charges(&self) -> bool {
96 self.gas_status.gas_used() == 0
97 && self.gas_status.storage_rebate() == 0
98 && self.gas_status.storage_gas_units() == 0
99 }
100
101 pub fn is_unmetered(&self) -> bool {
102 self.gas_status.is_unmetered()
103 }
104
105 pub fn move_gas_status(&self) -> &GasStatus {
106 self.gas_status.move_gas_status()
107 }
108
109 pub fn move_gas_status_mut(&mut self) -> &mut GasStatus {
110 self.gas_status.move_gas_status_mut()
111 }
112
113 pub fn into_gas_status(self) -> SuiGasStatus {
114 self.gas_status
115 }
116
117 pub fn summary(&self) -> GasCostSummary {
118 self.gas_status.summary()
119 }
120
121 pub fn smash_gas(&mut self, temporary_store: &mut TemporaryStore<'_>) {
129 let gas_coin_count = self.gas_coins.len();
130 if gas_coin_count == 0 || (gas_coin_count == 1 && self.gas_coins[0].0 == ObjectID::ZERO)
131 {
132 return; }
134 let gas_coin_id = self.gas_coins[0].0;
137 self.smashed_gas_coin = Some(gas_coin_id);
138 if gas_coin_count == 1 {
139 return;
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(|| {
175 panic!(
176 "Invariant violation: gas coin not found in store in txn {}",
177 self.tx_digest
178 )
179 })
180 .clone();
181 for (id, version, _digest) in &self.gas_coins[1..] {
183 debug_assert_ne!(*id, primary_gas_object.id());
184 temporary_store.delete_object(id, DeleteKindWithOldVersion::Normal(*version));
185 }
186 primary_gas_object
187 .data
188 .try_as_move_mut()
189 .unwrap_or_else(|| {
191 panic!(
192 "Invariant violation: invalid coin object in txn {}",
193 self.tx_digest
194 )
195 })
196 .set_coin_value_unsafe(new_balance);
197 temporary_store.write_object(primary_gas_object, WriteKind::Mutate);
198 }
199
200 pub fn track_storage_mutation(
205 &mut self,
206 object_id: ObjectID,
207 new_size: usize,
208 storage_rebate: u64,
209 ) -> u64 {
210 self.gas_status
211 .track_storage_mutation(object_id, new_size, storage_rebate)
212 .expect("storage gas overflow")
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_input_objects(
224 &mut self,
225 temporary_store: &TemporaryStore<'_>,
226 ) -> Result<(), ExecutionError> {
227 let objects = temporary_store.objects();
228 let _object_count = objects.len();
230 let total_size = temporary_store
232 .objects()
233 .iter()
234 .filter(|(id, _)| !is_system_package(**id))
236 .map(|(_, obj)| obj.object_size_for_gas_metering())
237 .sum();
238 self.gas_status.charge_storage_read(total_size)
239 }
240
241 pub fn reset(&mut self, temporary_store: &mut TemporaryStore<'_>) {
244 temporary_store.drop_writes();
245 self.gas_status.reset_storage_cost_and_rebate();
246 self.smash_gas(temporary_store);
247 }
248
249 pub fn charge_gas<T>(
260 &mut self,
261 temporary_store: &mut TemporaryStore<'_>,
262 execution_result: &mut Result<T, ExecutionError>,
263 ) -> GasCostSummary {
264 debug_assert!(self.gas_status.storage_rebate() == 0);
267 debug_assert!(self.gas_status.storage_gas_units() == 0);
268
269 if self.smashed_gas_coin.is_some() {
270 if let Err(err) = self.gas_status.bucketize_computation(None) {
272 if execution_result.is_ok() {
273 *execution_result = Err(err);
274 }
275 }
276
277 if execution_result.is_err() {
279 self.reset(temporary_store);
280 }
281 }
282
283 temporary_store.ensure_gas_and_input_mutated(self);
285 temporary_store.collect_storage_and_rebate(self);
286
287 if self.smashed_gas_coin.is_some() {
288 #[skip_checked_arithmetic]
289 trace!(target: "replay_gas_info", "Gas smashing has occurred for this transaction");
290 }
291
292 if let Some(gas_object_id) = self.smashed_gas_coin {
295 if dont_charge_budget_on_storage_oog(self.gas_model_version) {
296 self.handle_storage_and_rebate_v2(temporary_store, execution_result)
297 } else {
298 self.handle_storage_and_rebate_v1(temporary_store, execution_result)
299 }
300
301 let cost_summary = self.gas_status.summary();
302 let gas_used = cost_summary.net_gas_usage();
303
304 let mut gas_object = temporary_store.read_object(&gas_object_id).unwrap().clone();
305 deduct_gas(&mut gas_object, gas_used);
306 #[skip_checked_arithmetic]
307 trace!(gas_used, gas_obj_id =? gas_object.id(), gas_obj_ver =? gas_object.version(), "Updated gas object");
308
309 temporary_store.write_object(gas_object, WriteKind::Mutate);
310 cost_summary
311 } else {
312 GasCostSummary::default()
313 }
314 }
315
316 fn handle_storage_and_rebate_v1<T>(
317 &mut self,
318 temporary_store: &mut TemporaryStore<'_>,
319 execution_result: &mut Result<T, ExecutionError>,
320 ) {
321 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
322 self.reset(temporary_store);
323 self.gas_status.adjust_computation_on_out_of_gas();
324 temporary_store.ensure_gas_and_input_mutated(self);
325 temporary_store.collect_rebate(self);
326 if execution_result.is_ok() {
327 *execution_result = Err(err);
328 }
329 }
330 }
331
332 fn handle_storage_and_rebate_v2<T>(
333 &mut self,
334 temporary_store: &mut TemporaryStore<'_>,
335 execution_result: &mut Result<T, ExecutionError>,
336 ) {
337 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
338 self.reset(temporary_store);
341 temporary_store.ensure_gas_and_input_mutated(self);
342 temporary_store.collect_storage_and_rebate(self);
343 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
344 self.reset(temporary_store);
347 self.gas_status.adjust_computation_on_out_of_gas();
348 temporary_store.ensure_gas_and_input_mutated(self);
349 temporary_store.collect_rebate(self);
350 if execution_result.is_ok() {
351 *execution_result = Err(err);
352 }
353 } else if execution_result.is_ok() {
354 *execution_result = Err(err);
355 }
356 }
357 }
358 }
359}