near_vm_logic/
gas_counter.rs

1use crate::{HostError, VMLogicError};
2#[cfg(feature = "protocol_feature_evm")]
3use near_primitives_core::runtime::fees::EvmGas;
4use near_primitives_core::runtime::fees::Fee;
5use near_primitives_core::{
6    config::{ActionCosts, ExtCosts, ExtCostsConfig},
7    profile::ProfileData,
8    types::Gas,
9};
10use std::fmt;
11
12#[cfg(feature = "costs_counting")]
13thread_local! {
14    pub static EXT_COSTS_COUNTER: std::cell::RefCell<std::collections::HashMap<ExtCosts, u64>> =
15        Default::default();
16
17    #[cfg(feature = "protocol_feature_evm")]
18    pub static EVM_GAS_COUNTER: std::cell::RefCell<EvmGas> = Default::default();
19}
20
21#[cfg(all(feature = "costs_counting", feature = "protocol_feature_evm"))]
22pub fn reset_evm_gas_counter() -> u64 {
23    let mut ret = 0;
24    EVM_GAS_COUNTER.with(|f| {
25        ret = *f.borrow();
26        *f.borrow_mut() = 0;
27    });
28    ret
29}
30
31type Result<T> = ::std::result::Result<T, VMLogicError>;
32
33/// Gas counter (a part of VMlogic)
34pub struct GasCounter {
35    /// The amount of gas that was irreversibly used for contract execution.
36    burnt_gas: Gas,
37    /// `burnt_gas` + gas that was attached to the promises.
38    used_gas: Gas,
39    /// Gas limit for execution
40    max_gas_burnt: Gas,
41    prepaid_gas: Gas,
42    is_view: bool,
43    ext_costs_config: ExtCostsConfig,
44    /// Where to store profile data, if needed.
45    #[allow(dead_code)]
46    profile: Option<ProfileData>,
47}
48
49impl fmt::Debug for GasCounter {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.debug_tuple("").finish()
52    }
53}
54
55impl GasCounter {
56    pub fn new(
57        ext_costs_config: ExtCostsConfig,
58        max_gas_burnt: Gas,
59        prepaid_gas: Gas,
60        is_view: bool,
61        profile: Option<ProfileData>,
62    ) -> Self {
63        Self {
64            ext_costs_config,
65            burnt_gas: 0,
66            used_gas: 0,
67            max_gas_burnt,
68            prepaid_gas,
69            is_view,
70            profile,
71        }
72    }
73
74    fn deduct_gas(&mut self, burn_gas: Gas, use_gas: Gas) -> Result<()> {
75        assert!(burn_gas <= use_gas);
76        let new_burnt_gas =
77            self.burnt_gas.checked_add(burn_gas).ok_or(HostError::IntegerOverflow)?;
78        let new_used_gas = self.used_gas.checked_add(use_gas).ok_or(HostError::IntegerOverflow)?;
79        if new_burnt_gas <= self.max_gas_burnt && (self.is_view || new_used_gas <= self.prepaid_gas)
80        {
81            self.burnt_gas = new_burnt_gas;
82            self.used_gas = new_used_gas;
83            Ok(())
84        } else {
85            use std::cmp::min;
86            let res = if new_burnt_gas > self.max_gas_burnt {
87                Err(HostError::GasLimitExceeded.into())
88            } else if new_used_gas > self.prepaid_gas {
89                Err(HostError::GasExceeded.into())
90            } else {
91                unreachable!()
92            };
93
94            let max_burnt_gas = min(self.max_gas_burnt, self.prepaid_gas);
95            self.burnt_gas = min(new_burnt_gas, max_burnt_gas);
96            self.used_gas = min(new_used_gas, self.prepaid_gas);
97
98            res
99        }
100    }
101
102    #[cfg(all(feature = "costs_counting", feature = "protocol_feature_evm"))]
103    #[inline]
104    pub fn inc_evm_gas_counter(&mut self, value: EvmGas) {
105        EVM_GAS_COUNTER.with(|f| {
106            *f.borrow_mut() += value;
107        })
108    }
109
110    #[cfg(all(not(feature = "costs_counting"), feature = "protocol_feature_evm"))]
111    #[inline]
112    pub fn inc_evm_gas_counter(&mut self, _value: EvmGas) {}
113
114    #[cfg(feature = "costs_counting")]
115    #[inline]
116    fn inc_ext_costs_counter(&mut self, cost: ExtCosts, value: u64) {
117        EXT_COSTS_COUNTER.with(|f| {
118            *f.borrow_mut().entry(cost).or_default() += value;
119        });
120    }
121
122    #[cfg(not(feature = "costs_counting"))]
123    #[inline]
124    fn inc_ext_costs_counter(&mut self, _cost: ExtCosts, _value: u64) {}
125
126    #[cfg(feature = "costs_counting")]
127    #[inline]
128    fn update_profile_host(&mut self, cost: ExtCosts, value: u64) {
129        match &self.profile {
130            Some(profile) => profile.add_ext_cost(cost, value),
131            None => {}
132        };
133    }
134
135    #[cfg(not(feature = "costs_counting"))]
136    #[inline]
137    fn update_profile_host(&mut self, _cost: ExtCosts, _value: u64) {}
138
139    #[cfg(feature = "costs_counting")]
140    #[inline]
141    fn update_profile_action(&mut self, action: ActionCosts, value: u64) {
142        match &self.profile {
143            Some(profile) => profile.add_action_cost(action, value),
144            None => {}
145        };
146    }
147
148    #[cfg(not(feature = "costs_counting"))]
149    #[inline]
150    fn update_profile_action(&mut self, _action: ActionCosts, _value: u64) {}
151
152    pub fn pay_wasm_gas(&mut self, value: u64) -> Result<()> {
153        self.deduct_gas(value, value)
154    }
155
156    pub fn pay_evm_gas(&mut self, value: u64) -> Result<()> {
157        self.deduct_gas(value, value)
158    }
159
160    /// A helper function to pay per byte gas
161    pub fn pay_per_byte(&mut self, cost: ExtCosts, num_bytes: u64) -> Result<()> {
162        let use_gas = num_bytes
163            .checked_mul(cost.value(&self.ext_costs_config))
164            .ok_or(HostError::IntegerOverflow)?;
165
166        self.inc_ext_costs_counter(cost, num_bytes);
167        self.update_profile_host(cost, use_gas);
168        self.deduct_gas(use_gas, use_gas)
169    }
170
171    /// A helper function to pay base cost gas
172    pub fn pay_base(&mut self, cost: ExtCosts) -> Result<()> {
173        let base_fee = cost.value(&self.ext_costs_config);
174        self.inc_ext_costs_counter(cost, 1);
175        self.update_profile_host(cost, base_fee);
176        self.deduct_gas(base_fee, base_fee)
177    }
178
179    /// A helper function to pay per byte gas fee for batching an action.
180    /// # Args:
181    /// * `per_byte_fee`: the fee per byte;
182    /// * `num_bytes`: the number of bytes;
183    /// * `sir`: whether the receiver_id is same as the current account ID;
184    /// * `action`: what kind of action is charged for;
185    pub fn pay_action_per_byte(
186        &mut self,
187        per_byte_fee: &Fee,
188        num_bytes: u64,
189        sir: bool,
190        action: ActionCosts,
191    ) -> Result<()> {
192        let burn_gas =
193            num_bytes.checked_mul(per_byte_fee.send_fee(sir)).ok_or(HostError::IntegerOverflow)?;
194        let use_gas = burn_gas
195            .checked_add(
196                num_bytes.checked_mul(per_byte_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?,
197            )
198            .ok_or(HostError::IntegerOverflow)?;
199        self.update_profile_action(action, burn_gas);
200        self.deduct_gas(burn_gas, use_gas)
201    }
202
203    /// A helper function to pay base cost gas fee for batching an action.
204    /// # Args:
205    /// * `base_fee`: base fee for the action;
206    /// * `sir`: whether the receiver_id is same as the current account ID;
207    /// * `action`: what kind of action is charged for;
208    pub fn pay_action_base(
209        &mut self,
210        base_fee: &Fee,
211        sir: bool,
212        action: ActionCosts,
213    ) -> Result<()> {
214        let burn_gas = base_fee.send_fee(sir);
215        let use_gas =
216            burn_gas.checked_add(base_fee.exec_fee()).ok_or(HostError::IntegerOverflow)?;
217        self.update_profile_action(action, burn_gas);
218        self.deduct_gas(burn_gas, use_gas)
219    }
220
221    /// A helper function to pay base cost gas fee for batching an action.
222    /// # Args:
223    /// * `burn_gas`: amount of gas to burn;
224    /// * `use_gas`: amount of gas to reserve;
225    /// * `action`: what kind of action is charged for;
226    pub fn pay_action_accumulated(
227        &mut self,
228        burn_gas: Gas,
229        use_gas: Gas,
230        action: ActionCosts,
231    ) -> Result<()> {
232        self.update_profile_action(action, burn_gas);
233        self.deduct_gas(burn_gas, use_gas)
234    }
235
236    pub fn prepay_gas(&mut self, use_gas: Gas) -> Result<()> {
237        self.deduct_gas(0, use_gas)
238    }
239
240    pub fn burnt_gas(&self) -> Gas {
241        self.burnt_gas
242    }
243    pub fn used_gas(&self) -> Gas {
244        self.used_gas
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use near_primitives_core::config::ExtCostsConfig;
252
253    #[test]
254    fn test_deduct_gas() {
255        let mut counter = GasCounter::new(ExtCostsConfig::default(), 10, 10, false, None);
256        counter.deduct_gas(5, 10).expect("deduct_gas should work");
257        assert_eq!(counter.burnt_gas(), 5);
258        assert_eq!(counter.used_gas(), 10);
259    }
260
261    #[test]
262    #[should_panic]
263    fn test_prepaid_gas_min() {
264        let mut counter = GasCounter::new(ExtCostsConfig::default(), 100, 10, false, None);
265        counter.deduct_gas(10, 5).unwrap();
266    }
267}