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
33pub struct GasCounter {
35 burnt_gas: Gas,
37 used_gas: Gas,
39 max_gas_burnt: Gas,
41 prepaid_gas: Gas,
42 is_view: bool,
43 ext_costs_config: ExtCostsConfig,
44 #[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 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 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 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 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 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}