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            available_address_balance_gas: u64,
112        ) -> UserInputResult {
113            match self {
114                Self::V2(status) => {
115                    status.check_gas_balance(gas_objs, gas_budget, available_address_balance_gas)
116                }
117            }
118        }
119
120        pub fn check_gas_objects(&self, gas_objs: &[&ObjectReadResult]) -> UserInputResult {
121            match self {
122                Self::V2(status) => status.check_gas_objects(gas_objs),
123            }
124        }
125
126        pub fn gas_price(&self) -> u64 {
127            match self {
128                Self::V2(status) => status.gas_price(),
129            }
130        }
131    }
132
133    /// Summary of the charges in a transaction.
134    /// Storage is charged independently of computation.
135    /// There are 3 parts to the storage charges:
136    /// `storage_cost`: it is the charge of storage at the time the transaction is executed.
137    ///                 The cost of storage is the number of bytes of the objects being mutated
138    ///                 multiplied by a variable storage cost per byte
139    /// `storage_rebate`: this is the amount a user gets back when manipulating an object.
140    ///                   The `storage_rebate` is the `storage_cost` for an object minus fees.
141    /// `non_refundable_storage_fee`: not all the value of the object storage cost is
142    ///                               given back to user and there is a small fraction that
143    ///                               is kept by the system. This value tracks that charge.
144    ///
145    /// When looking at a gas cost summary the amount charged to the user is
146    /// `computation_cost + storage_cost - storage_rebate`
147    /// and that is the amount that is deducted from the gas coins.
148    /// `non_refundable_storage_fee` is collected from the objects being mutated/deleted
149    /// and it is tracked by the system in storage funds.
150    ///
151    /// Objects deleted, including the older versions of objects mutated, have the storage field
152    /// on the objects added up to a pool of "potential rebate". This rebate then is reduced
153    /// by the "nonrefundable rate" such that:
154    /// `potential_rebate(storage cost of deleted/mutated objects) =
155    /// storage_rebate + non_refundable_storage_fee`
156
157    #[serde_as]
158    #[derive(Eq, PartialEq, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
159    #[serde(rename_all = "camelCase")]
160    pub struct GasCostSummary {
161        /// Cost of computation/execution
162        #[schemars(with = "BigInt<u64>")]
163        #[serde_as(as = "Readable<BigInt<u64>, _>")]
164        pub computation_cost: u64,
165        /// Storage cost, it's the sum of all storage cost for all objects created or mutated.
166        #[schemars(with = "BigInt<u64>")]
167        #[serde_as(as = "Readable<BigInt<u64>, _>")]
168        pub storage_cost: u64,
169        /// The amount of storage cost refunded to the user for all objects deleted or mutated in the
170        /// transaction.
171        #[schemars(with = "BigInt<u64>")]
172        #[serde_as(as = "Readable<BigInt<u64>, _>")]
173        pub storage_rebate: u64,
174        /// The fee for the rebate. The portion of the storage rebate kept by the system.
175        #[schemars(with = "BigInt<u64>")]
176        #[serde_as(as = "Readable<BigInt<u64>, _>")]
177        pub non_refundable_storage_fee: u64,
178    }
179
180    impl GasCostSummary {
181        pub fn new(
182            computation_cost: u64,
183            storage_cost: u64,
184            storage_rebate: u64,
185            non_refundable_storage_fee: u64,
186        ) -> GasCostSummary {
187            GasCostSummary {
188                computation_cost,
189                storage_cost,
190                storage_rebate,
191                non_refundable_storage_fee,
192            }
193        }
194
195        pub fn gas_used(&self) -> u64 {
196            self.computation_cost + self.storage_cost
197        }
198
199        /// Portion of the storage rebate that gets passed on to the transaction sender. The remainder
200        /// will be burned, then re-minted + added to the storage fund at the next epoch change
201        pub fn sender_rebate(&self, storage_rebate_rate: u64) -> u64 {
202            // we round storage rebate such that `>= x.5` goes to x+1 (rounds up) and
203            // `< x.5` goes to x (truncates). We replicate `f32/64::round()`
204            const BASIS_POINTS: u128 = 10000;
205            (((self.storage_rebate as u128 * storage_rebate_rate as u128)
206            + (BASIS_POINTS / 2)) // integer rounding adds half of the BASIS_POINTS (denominator)
207            / BASIS_POINTS) as u64
208        }
209
210        /// Get net gas usage, positive number means used gas; negative number means refund.
211        pub fn net_gas_usage(&self) -> i64 {
212            self.gas_used() as i64 - self.storage_rebate as i64
213        }
214
215        pub fn new_from_txn_effects<'a>(
216            transactions: impl Iterator<Item = &'a TransactionEffects>,
217        ) -> GasCostSummary {
218            let (storage_costs, computation_costs, storage_rebates, non_refundable_storage_fee): (
219                Vec<u64>,
220                Vec<u64>,
221                Vec<u64>,
222                Vec<u64>,
223            ) = transactions
224                .map(|e| {
225                    (
226                        e.gas_cost_summary().storage_cost,
227                        e.gas_cost_summary().computation_cost,
228                        e.gas_cost_summary().storage_rebate,
229                        e.gas_cost_summary().non_refundable_storage_fee,
230                    )
231                })
232                .multiunzip();
233
234            GasCostSummary {
235                storage_cost: storage_costs.iter().sum(),
236                computation_cost: computation_costs.iter().sum(),
237                storage_rebate: storage_rebates.iter().sum(),
238                non_refundable_storage_fee: non_refundable_storage_fee.iter().sum(),
239            }
240        }
241    }
242
243    impl std::fmt::Display for GasCostSummary {
244        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245            write!(
246                f,
247                "computation_cost: {}, storage_cost: {},  storage_rebate: {}, non_refundable_storage_fee: {}",
248                self.computation_cost,
249                self.storage_cost,
250                self.storage_rebate,
251                self.non_refundable_storage_fee,
252            )
253        }
254    }
255
256    impl std::ops::AddAssign<&Self> for GasCostSummary {
257        fn add_assign(&mut self, other: &Self) {
258            self.computation_cost += other.computation_cost;
259            self.storage_cost += other.storage_cost;
260            self.storage_rebate += other.storage_rebate;
261            self.non_refundable_storage_fee += other.non_refundable_storage_fee;
262        }
263    }
264
265    impl std::ops::AddAssign<Self> for GasCostSummary {
266        fn add_assign(&mut self, other: Self) {
267            self.add_assign(&other)
268        }
269    }
270
271    //
272    // Helper functions to deal with gas coins operations.
273    //
274
275    pub fn deduct_gas(gas_object: &mut Object, charge_or_rebate: i64) {
276        // The object must be a gas coin as we have checked in transaction handle phase.
277        let gas_coin = gas_object.data.try_as_move_mut().unwrap();
278        let balance = gas_coin.get_coin_value_unsafe();
279        let new_balance = if charge_or_rebate < 0 {
280            balance + (-charge_or_rebate as u64)
281        } else {
282            assert!(balance >= charge_or_rebate as u64);
283            balance - charge_or_rebate as u64
284        };
285        gas_coin.set_coin_value_unsafe(new_balance)
286    }
287
288    pub fn get_gas_balance(gas_object: &Object) -> UserInputResult<u64> {
289        if let Some(move_obj) = gas_object.data.try_as_move() {
290            if !move_obj.type_().is_gas_coin() {
291                return Err(UserInputError::InvalidGasObject {
292                    object_id: gas_object.id(),
293                });
294            }
295            Ok(move_obj.get_coin_value_unsafe())
296        } else {
297            Err(UserInputError::InvalidGasObject {
298                object_id: gas_object.id(),
299            })
300        }
301    }
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct GasUsageReport {
306    pub cost_summary: GasCostSummary,
307    pub gas_used: u64,
308    pub gas_budget: u64,
309    pub gas_price: u64,
310    pub reference_gas_price: u64,
311    pub storage_gas_price: u64,
312    pub rebate_rate: u64,
313    pub per_object_storage: Vec<(ObjectID, PerObjectStorage)>,
314}