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(tx_digest: TransactionDigest) -> Self {
63 Self {
64 tx_digest,
65 gas_model_version: 6, gas_coins: vec![],
67 smashed_gas_coin: None,
68 gas_status: SuiGasStatus::new_unmetered(),
69 }
70 }
71
72 pub(crate) fn gas_coins(&self) -> &[ObjectRef] {
75 &self.gas_coins
76 }
77
78 pub fn gas_coin(&self) -> Option<ObjectID> {
81 self.smashed_gas_coin
82 }
83
84 pub fn gas_budget(&self) -> u64 {
85 self.gas_status.gas_budget()
86 }
87
88 pub fn unmetered_storage_rebate(&self) -> u64 {
89 self.gas_status.unmetered_storage_rebate()
90 }
91
92 pub fn no_charges(&self) -> bool {
93 self.gas_status.gas_used() == 0
94 && self.gas_status.storage_rebate() == 0
95 && self.gas_status.storage_gas_units() == 0
96 }
97
98 pub fn is_unmetered(&self) -> bool {
99 self.gas_status.is_unmetered()
100 }
101
102 pub fn move_gas_status(&self) -> &GasStatus {
103 self.gas_status.move_gas_status()
104 }
105
106 pub fn move_gas_status_mut(&mut self) -> &mut GasStatus {
107 self.gas_status.move_gas_status_mut()
108 }
109
110 pub fn into_gas_status(self) -> SuiGasStatus {
111 self.gas_status
112 }
113
114 pub fn summary(&self) -> GasCostSummary {
115 self.gas_status.summary()
116 }
117
118 pub fn smash_gas(&mut self, temporary_store: &mut TemporaryStore<'_>) {
126 let gas_coin_count = self.gas_coins.len();
127 if gas_coin_count == 0 || (gas_coin_count == 1 && self.gas_coins[0].0 == ObjectID::ZERO)
128 {
129 return; }
131 let gas_coin_id = self.gas_coins[0].0;
134 self.smashed_gas_coin = Some(gas_coin_id);
135 if gas_coin_count == 1 {
136 return;
137 }
138 let new_balance = self
140 .gas_coins
141 .iter()
142 .map(|obj_ref| {
143 let obj = temporary_store.objects().get(&obj_ref.0).unwrap();
144 let Data::Move(move_obj) = &obj.data else {
145 return Err(ExecutionError::invariant_violation(
146 "Provided non-gas coin object as input for gas!",
147 ));
148 };
149 if !move_obj.type_().is_gas_coin() {
150 return Err(ExecutionError::invariant_violation(
151 "Provided non-gas coin object as input for gas!",
152 ));
153 }
154 Ok(move_obj.get_coin_value_unsafe())
155 })
156 .collect::<Result<Vec<u64>, ExecutionError>>()
157 .unwrap_or_else(|_| {
160 panic!(
161 "Invariant violation: non-gas coin object as input for gas in txn {}",
162 self.tx_digest
163 )
164 })
165 .iter()
166 .sum();
167 let mut primary_gas_object = temporary_store
168 .objects()
169 .get(&gas_coin_id)
170 .unwrap_or_else(|| {
172 panic!(
173 "Invariant violation: gas coin not found in store in txn {}",
174 self.tx_digest
175 )
176 })
177 .clone();
178 for (id, version, _digest) in &self.gas_coins[1..] {
180 debug_assert_ne!(*id, primary_gas_object.id());
181 temporary_store.delete_object(id, DeleteKindWithOldVersion::Normal(*version));
182 }
183 primary_gas_object
184 .data
185 .try_as_move_mut()
186 .unwrap_or_else(|| {
188 panic!(
189 "Invariant violation: invalid coin object in txn {}",
190 self.tx_digest
191 )
192 })
193 .set_coin_value_unsafe(new_balance);
194 temporary_store.write_object(primary_gas_object, WriteKind::Mutate);
195 }
196
197 pub fn track_storage_mutation(
202 &mut self,
203 object_id: ObjectID,
204 new_size: usize,
205 storage_rebate: u64,
206 ) -> u64 {
207 self.gas_status
208 .track_storage_mutation(object_id, new_size, storage_rebate)
209 }
210
211 pub fn reset_storage_cost_and_rebate(&mut self) {
212 self.gas_status.reset_storage_cost_and_rebate();
213 }
214
215 pub fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError> {
216 self.gas_status.charge_publish_package(size)
217 }
218
219 pub fn charge_input_objects(
220 &mut self,
221 temporary_store: &TemporaryStore<'_>,
222 ) -> Result<(), ExecutionError> {
223 let objects = temporary_store.objects();
224 let _object_count = objects.len();
226 let total_size = temporary_store
228 .objects()
229 .iter()
230 .filter(|(id, _)| !is_system_package(**id))
232 .map(|(_, obj)| obj.object_size_for_gas_metering())
233 .sum();
234 self.gas_status.charge_storage_read(total_size)
235 }
236
237 pub fn reset(&mut self, temporary_store: &mut TemporaryStore<'_>) {
240 temporary_store.drop_writes();
241 self.gas_status.reset_storage_cost_and_rebate();
242 self.smash_gas(temporary_store);
243 }
244
245 pub fn charge_gas<T>(
256 &mut self,
257 temporary_store: &mut TemporaryStore<'_>,
258 execution_result: &mut Result<T, ExecutionError>,
259 ) -> GasCostSummary {
260 debug_assert!(self.gas_status.storage_rebate() == 0);
263 debug_assert!(self.gas_status.storage_gas_units() == 0);
264
265 if self.smashed_gas_coin.is_some() {
266 if let Err(err) = self.gas_status.bucketize_computation(None) {
268 if execution_result.is_ok() {
269 *execution_result = Err(err);
270 }
271 }
272
273 if execution_result.is_err() {
275 self.reset(temporary_store);
276 }
277 }
278
279 temporary_store.ensure_gas_and_input_mutated(self);
281 temporary_store.collect_storage_and_rebate(self);
282
283 if self.smashed_gas_coin.is_some() {
284 #[skip_checked_arithmetic]
285 trace!(target: "replay_gas_info", "Gas smashing has occurred for this transaction");
286 }
287
288 if let Some(gas_object_id) = self.smashed_gas_coin {
291 if dont_charge_budget_on_storage_oog(self.gas_model_version) {
292 self.handle_storage_and_rebate_v2(temporary_store, execution_result)
293 } else {
294 self.handle_storage_and_rebate_v1(temporary_store, execution_result)
295 }
296
297 let cost_summary = self.gas_status.summary();
298 let gas_used = cost_summary.net_gas_usage();
299
300 let mut gas_object = temporary_store.read_object(&gas_object_id).unwrap().clone();
301 deduct_gas(&mut gas_object, gas_used);
302 #[skip_checked_arithmetic]
303 trace!(gas_used, gas_obj_id =? gas_object.id(), gas_obj_ver =? gas_object.version(), "Updated gas object");
304
305 temporary_store.write_object(gas_object, WriteKind::Mutate);
306 cost_summary
307 } else {
308 GasCostSummary::default()
309 }
310 }
311
312 fn handle_storage_and_rebate_v1<T>(
313 &mut self,
314 temporary_store: &mut TemporaryStore<'_>,
315 execution_result: &mut Result<T, ExecutionError>,
316 ) {
317 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
318 self.reset(temporary_store);
319 self.gas_status.adjust_computation_on_out_of_gas();
320 temporary_store.ensure_gas_and_input_mutated(self);
321 temporary_store.collect_rebate(self);
322 if execution_result.is_ok() {
323 *execution_result = Err(err);
324 }
325 }
326 }
327
328 fn handle_storage_and_rebate_v2<T>(
329 &mut self,
330 temporary_store: &mut TemporaryStore<'_>,
331 execution_result: &mut Result<T, ExecutionError>,
332 ) {
333 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
334 self.reset(temporary_store);
337 temporary_store.ensure_gas_and_input_mutated(self);
338 temporary_store.collect_storage_and_rebate(self);
339 if let Err(err) = self.gas_status.charge_storage_and_rebate() {
340 self.reset(temporary_store);
343 self.gas_status.adjust_computation_on_out_of_gas();
344 temporary_store.ensure_gas_and_input_mutated(self);
345 temporary_store.collect_rebate(self);
346 if execution_result.is_ok() {
347 *execution_result = Err(err);
348 }
349 } else if execution_result.is_ok() {
350 *execution_result = Err(err);
351 }
352 }
353 }
354 }
355}