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