iota_types/
gas.rs

1// Copyright (c) 2021, Facebook, Inc. and its affiliates
2// Copyright (c) Mysten Labs, Inc.
3// Modifications Copyright (c) 2024 IOTA Stiftung
4// SPDX-License-Identifier: Apache-2.0
5
6pub use checked::*;
7
8#[iota_macros::with_checked_arithmetic]
9pub mod checked {
10
11    use enum_dispatch::enum_dispatch;
12    use iota_protocol_config::ProtocolConfig;
13    pub use iota_sdk_types::gas::GasCostSummary;
14
15    use crate::{
16        ObjectID,
17        error::{ExecutionError, IotaResult, UserInputError, UserInputResult},
18        gas_model::{gas_v1::IotaGasStatus as IotaGasStatusV1, tables::GasStatus},
19        object::{MoveObjectExt, Object},
20        transaction::ObjectReadResult,
21    };
22
23    #[enum_dispatch]
24    pub trait IotaGasStatusAPI {
25        fn is_unmetered(&self) -> bool;
26        fn move_gas_status(&self) -> &GasStatus;
27        fn move_gas_status_mut(&mut self) -> &mut GasStatus;
28        fn bucketize_computation(&mut self) -> Result<(), ExecutionError>;
29        fn summary(&self) -> GasCostSummary;
30        fn gas_budget(&self) -> u64;
31        fn gas_price(&self) -> u64;
32        fn reference_gas_price(&self) -> u64;
33        fn storage_gas_units(&self) -> u64;
34        fn storage_rebate(&self) -> u64;
35        fn unmetered_storage_rebate(&self) -> u64;
36        fn gas_used(&self) -> u64;
37        fn reset_storage_cost_and_rebate(&mut self);
38        fn charge_storage_read(&mut self, size: usize) -> Result<(), ExecutionError>;
39        fn charge_publish_package(&mut self, size: usize) -> Result<(), ExecutionError>;
40        fn track_storage_mutation(
41            &mut self,
42            object_id: ObjectID,
43            new_size: usize,
44            storage_rebate: u64,
45        ) -> u64;
46        fn charge_storage_and_rebate(&mut self) -> Result<(), ExecutionError>;
47        fn adjust_computation_on_out_of_gas(&mut self);
48    }
49
50    /// Version aware enum for gas status.
51    #[enum_dispatch(IotaGasStatusAPI)]
52    #[derive(Debug)]
53    pub enum IotaGasStatus {
54        V1(IotaGasStatusV1),
55    }
56
57    impl IotaGasStatus {
58        pub fn new(
59            gas_budget: u64,
60            gas_price: u64,
61            reference_gas_price: u64,
62            config: &ProtocolConfig,
63        ) -> IotaResult<Self> {
64            Self::check_gas_preconditions(gas_price, reference_gas_price, config)?;
65
66            Ok(Self::V1(IotaGasStatusV1::new_with_budget(
67                gas_budget,
68                gas_price,
69                reference_gas_price,
70                config,
71            )))
72        }
73
74        pub fn new_unmetered() -> Self {
75            // Always return V1 as unmetered gas status is identical from V1 to V2.
76            // This is only used for system transactions which do not pay gas.
77            Self::V1(IotaGasStatusV1::new_unmetered())
78        }
79
80        // This is the only public API on IotaGasStatus, all other gas related
81        // operations should go through `GasCharger`
82        pub fn check_gas_balance(
83            &self,
84            gas_objs: &[&ObjectReadResult],
85            gas_budget: u64,
86        ) -> UserInputResult {
87            match self {
88                Self::V1(status) => status.check_gas_balance(gas_objs, gas_budget),
89            }
90        }
91
92        fn check_gas_preconditions(
93            gas_price: u64,
94            reference_gas_price: u64,
95            config: &ProtocolConfig,
96        ) -> IotaResult<()> {
97            // Common checks. We may pull them into version specific status as needed, but
98            // they are unlikely to change.
99
100            // The gas price must be greater than or equal to the reference gas price.
101            if gas_price < reference_gas_price {
102                return Err(UserInputError::GasPriceUnderRGP {
103                    gas_price,
104                    reference_gas_price,
105                }
106                .into());
107            }
108            if gas_price > config.max_gas_price() {
109                return Err(UserInputError::GasPriceTooHigh {
110                    max_gas_price: config.max_gas_price(),
111                }
112                .into());
113            }
114
115            Ok(())
116        }
117    }
118
119    // Helper functions to deal with gas coins operations.
120
121    pub fn deduct_gas(gas_object: &mut Object, charge_or_rebate: i64) {
122        // The object must be a gas coin as we have checked in transaction handle phase.
123        let gas_coin = gas_object.data.as_struct_mut_opt().unwrap();
124        let balance = gas_coin.get_coin_value_unchecked();
125        let new_balance = if charge_or_rebate < 0 {
126            balance + (-charge_or_rebate as u64)
127        } else {
128            assert!(balance >= charge_or_rebate as u64);
129            balance - charge_or_rebate as u64
130        };
131        gas_coin.set_coin_value_unchecked(new_balance)
132    }
133
134    pub fn get_gas_balance(gas_object: &Object) -> UserInputResult<u64> {
135        if let Some(move_obj) = gas_object.data.as_struct_opt() {
136            if !move_obj.struct_tag().is_gas_coin() {
137                return Err(UserInputError::InvalidGasObject {
138                    object_id: gas_object.id(),
139                });
140            }
141            Ok(move_obj.get_coin_value_unchecked())
142        } else {
143            Err(UserInputError::InvalidGasObject {
144                object_id: gas_object.id(),
145            })
146        }
147    }
148}