sui_types/
gas.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5pub use checked::*;
6use serde::{Deserialize, Serialize};
7
8use crate::{base_types::ObjectID, gas_model::gas_v2::PerObjectStorage};
9
10#[sui_macros::with_checked_arithmetic]
11pub mod checked {
12
13    use crate::gas::GasUsageReport;
14    use crate::gas_model::gas_predicates::check_for_gas_price_too_high;
15    use crate::{
16        ObjectID,
17        effects::{TransactionEffects, TransactionEffectsAPI},
18        error::{ExecutionError, SuiResult, UserInputError, UserInputResult},
19        gas_model::{gas_v2::SuiGasStatus as SuiGasStatusV2, tables::GasStatus},
20        object::Object,
21        sui_serde::{BigInt, Readable},
22        transaction::ObjectReadResult,
23    };
24    use enum_dispatch::enum_dispatch;
25    use itertools::MultiUnzip;
26    use schemars::JsonSchema;
27    use serde::{Deserialize, Serialize};
28    use serde_with::serde_as;
29    use sui_protocol_config::ProtocolConfig;
30
31    #[enum_dispatch]
32    pub trait SuiGasStatusAPI {
33        fn is_unmetered(&self) -> bool;
34        fn move_gas_status(&self) -> &GasStatus;
35        fn move_gas_status_mut(&mut self) -> &mut GasStatus;
36        fn bucketize_computation(&mut self, aborted: Option<bool>) -> Result<(), ExecutionError>;
37        fn summary(&self) -> GasCostSummary;
38        fn gas_budget(&self) -> u64;
39        fn gas_price(&self) -> u64;
40        fn reference_gas_price(&self) -> u64;
41        fn storage_gas_units(&self) -> u64;
42        fn storage_rebate(&self) -> u64;
43        fn unmetered_storage_rebate(&self) -> u64;
44        fn gas_used(&self) -> u64;
45        fn reset_storage_cost_and_rebate(&mut self);
46        fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError>;
47        fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError>;
48        fn track_storage_mutation(
49            &mut self,
50            object_id: ObjectID,
51            new_size: usize,
52            storage_rebate: u64,
53        ) -> u64;
54        fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError>;
55        fn adjust_computation_on_out_of_gas(&mut self);
56        fn gas_usage_report(&self) -> GasUsageReport;
57    }
58
59    /// Version aware enum for gas status.
60    #[enum_dispatch(SuiGasStatusAPI)]
61    #[derive(Debug)]
62    pub enum SuiGasStatus {
63        // V1 does not exists any longer as it was a pre mainnet version.
64        // So we start the enum from V2
65        V2(SuiGasStatusV2),
66    }
67
68    impl SuiGasStatus {
69        pub fn new(
70            gas_budget: u64,
71            gas_price: u64,
72            reference_gas_price: u64,
73            config: &ProtocolConfig,
74        ) -> SuiResult<Self> {
75            // Common checks. We may pull them into version specific status as needed, but they
76            // are unlikely to change.
77
78            // gas price must be bigger or equal to reference gas price
79            if gas_price < reference_gas_price {
80                return Err(UserInputError::GasPriceUnderRGP {
81                    gas_price,
82                    reference_gas_price,
83                }
84                .into());
85            }
86            if check_for_gas_price_too_high(config.gas_model_version())
87                && gas_price >= config.max_gas_price()
88            {
89                return Err(UserInputError::GasPriceTooHigh {
90                    max_gas_price: config.max_gas_price(),
91                }
92                .into());
93            }
94
95            Ok(Self::V2(SuiGasStatusV2::new_with_budget(
96                gas_budget,
97                gas_price,
98                reference_gas_price,
99                config,
100            )))
101        }
102
103        pub fn new_unmetered() -> Self {
104            Self::V2(SuiGasStatusV2::new_unmetered())
105        }
106
107        pub fn check_gas_balance(
108            &self,
109            gas_objs: &[&ObjectReadResult],
110            gas_budget: u64,
111        ) -> UserInputResult {
112            match self {
113                Self::V2(status) => status.check_gas_balance(gas_objs, gas_budget),
114            }
115        }
116
117        pub fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult {
118            match self {
119                Self::V2(status) => status.check_gas_objects(gas_objs),
120            }
121        }
122
123        pub fn check_gas_data(
124            &self,
125            gas_objs: &[&ObjectReadResult],
126            gas_budget: u64,
127        ) -> UserInputResult {
128            match self {
129                Self::V2(status) => status.check_gas_data(gas_objs, gas_budget),
130            }
131        }
132
133        pub fn gas_price(&self) -> u64 {
134            match self {
135                Self::V2(status) => status.gas_price(),
136            }
137        }
138    }
139
140    /// Summary of the charges in a transaction.
141    /// Storage is charged independently of computation.
142    /// There are 3 parts to the storage charges:
143    /// `storage_cost`: it is the charge of storage at the time the transaction is executed.
144    ///                 The cost of storage is the number of bytes of the objects being mutated
145    ///                 multiplied by a variable storage cost per byte
146    /// `storage_rebate`: this is the amount a user gets back when manipulating an object.
147    ///                   The `storage_rebate` is the `storage_cost` for an object minus fees.
148    /// `non_refundable_storage_fee`: not all the value of the object storage cost is
149    ///                               given back to user and there is a small fraction that
150    ///                               is kept by the system. This value tracks that charge.
151    ///
152    /// When looking at a gas cost summary the amount charged to the user is
153    /// `computation_cost + storage_cost - storage_rebate`
154    /// and that is the amount that is deducted from the gas coins.
155    /// `non_refundable_storage_fee` is collected from the objects being mutated/deleted
156    /// and it is tracked by the system in storage funds.
157    ///
158    /// Objects deleted, including the older versions of objects mutated, have the storage field
159    /// on the objects added up to a pool of "potential rebate". This rebate then is reduced
160    /// by the "nonrefundable rate" such that:
161    /// `potential_rebate(storage cost of deleted/mutated objects) =
162    /// storage_rebate + non_refundable_storage_fee`
163
164    #[serde_as]
165    #[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
166    #[serde(rename_all = "camelCase")]
167    pub struct GasCostSummary {
168        /// Cost of computation/execution
169        #[schemars(with = "BigInt<u64>")]
170        #[serde_as(as = "Readable<BigInt<u64>, _>")]
171        pub computation_cost: u64,
172        /// Storage cost, it's the sum of all storage cost for all objects created or mutated.
173        #[schemars(with = "BigInt<u64>")]
174        #[serde_as(as = "Readable<BigInt<u64>, _>")]
175        pub storage_cost: u64,
176        /// The amount of storage cost refunded to the user for all objects deleted or mutated in the
177        /// transaction.
178        #[schemars(with = "BigInt<u64>")]
179        #[serde_as(as = "Readable<BigInt<u64>, _>")]
180        pub storage_rebate: u64,
181        /// The fee for the rebate. The portion of the storage rebate kept by the system.
182        #[schemars(with = "BigInt<u64>")]
183        #[serde_as(as = "Readable<BigInt<u64>, _>")]
184        pub non_refundable_storage_fee: u64,
185    }
186
187    impl GasCostSummary {
188        pub fn new(
189            computation_cost: u64,
190            storage_cost: u64,
191            storage_rebate: u64,
192            non_refundable_storage_fee: u64,
193        ) -> GasCostSummary {
194            GasCostSummary {
195                computation_cost,
196                storage_cost,
197                storage_rebate,
198                non_refundable_storage_fee,
199            }
200        }
201
202        pub fn gas_used(&self) -> u64 {
203            self.computation_cost + self.storage_cost
204        }
205
206        /// Portion of the storage rebate that gets passed on to the transaction sender. The remainder
207        /// will be burned, then re-minted + added to the storage fund at the next epoch change
208        pub fn sender_rebate(&self, storage_rebate_rate: u64) -> u64 {
209            // we round storage rebate such that `>= x.5` goes to x+1 (rounds up) and
210            // `< x.5` goes to x (truncates). We replicate `f32/64::round()`
211            const BASIS_POINTS: u128 = 10000;
212            (((self.storage_rebate as u128 * storage_rebate_rate as u128)
213            + (BASIS_POINTS / 2)) // integer rounding adds half of the BASIS_POINTS (denominator)
214            / BASIS_POINTS) as u64
215        }
216
217        /// Get net gas usage, positive number means used gas; negative number means refund.
218        pub fn net_gas_usage(&self) -> i64 {
219            self.gas_used() as i64 - self.storage_rebate as i64
220        }
221
222        pub fn new_from_txn_effects<'a>(
223            transactions: impl Iterator<Item = &'a TransactionEffects>,
224        ) -> GasCostSummary {
225            let (storage_costs, computation_costs, storage_rebates, non_refundable_storage_fee): (
226                Vec<u64>,
227                Vec<u64>,
228                Vec<u64>,
229                Vec<u64>,
230            ) = transactions
231                .map(|e| {
232                    (
233                        e.gas_cost_summary().storage_cost,
234                        e.gas_cost_summary().computation_cost,
235                        e.gas_cost_summary().storage_rebate,
236                        e.gas_cost_summary().non_refundable_storage_fee,
237                    )
238                })
239                .multiunzip();
240
241            GasCostSummary {
242                storage_cost: storage_costs.iter().sum(),
243                computation_cost: computation_costs.iter().sum(),
244                storage_rebate: storage_rebates.iter().sum(),
245                non_refundable_storage_fee: non_refundable_storage_fee.iter().sum(),
246            }
247        }
248    }
249
250    impl std::fmt::Display for GasCostSummary {
251        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
252            write!(
253                f,
254                "computation_cost: {}, storage_cost: {},  storage_rebate: {}, non_refundable_storage_fee: {}",
255                self.computation_cost,
256                self.storage_cost,
257                self.storage_rebate,
258                self.non_refundable_storage_fee,
259            )
260        }
261    }
262
263    impl std::ops::AddAssign<&Self> for GasCostSummary {
264        fn add_assign(&mut self, other: &Self) {
265            self.computation_cost += other.computation_cost;
266            self.storage_cost += other.storage_cost;
267            self.storage_rebate += other.storage_rebate;
268            self.non_refundable_storage_fee += other.non_refundable_storage_fee;
269        }
270    }
271
272    impl std::ops::AddAssign<Self> for GasCostSummary {
273        fn add_assign(&mut self, other: Self) {
274            self.add_assign(&other)
275        }
276    }
277
278    //
279    // Helper functions to deal with gas coins operations.
280    //
281
282    pub fn deduct_gas(gas_object: &mut Object, charge_or_rebate: i64) {
283        // The object must be a gas coin as we have checked in transaction handle phase.
284        let gas_coin = gas_object.data.try_as_move_mut().unwrap();
285        let balance = gas_coin.get_coin_value_unsafe();
286        let new_balance = if charge_or_rebate < 0 {
287            balance + (-charge_or_rebate as u64)
288        } else {
289            assert!(balance >= charge_or_rebate as u64);
290            balance - charge_or_rebate as u64
291        };
292        gas_coin.set_coin_value_unsafe(new_balance)
293    }
294
295    pub fn get_gas_balance(gas_object: &Object) -> UserInputResult<u64> {
296        if let Some(move_obj) = gas_object.data.try_as_move() {
297            if !move_obj.type_().is_gas_coin() {
298                return Err(UserInputError::InvalidGasObject {
299                    object_id: gas_object.id(),
300                });
301            }
302            Ok(move_obj.get_coin_value_unsafe())
303        } else {
304            Err(UserInputError::InvalidGasObject {
305                object_id: gas_object.id(),
306            })
307        }
308    }
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct GasUsageReport {
313    pub cost_summary: GasCostSummary,
314    pub gas_used: u64,
315    pub gas_budget: u64,
316    pub gas_price: u64,
317    pub reference_gas_price: u64,
318    pub storage_gas_price: u64,
319    pub rebate_rate: u64,
320    pub per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
321}