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