namada_gas/
lib.rs

1//! Gas accounting module to track the gas usage in a block for transactions and
2//! validity predicates triggered by transactions.
3
4#![doc(html_favicon_url = "https://dev.namada.net/master/favicon.png")]
5#![doc(html_logo_url = "https://dev.namada.net/master/rustdoc-logo.png")]
6#![deny(rustdoc::broken_intra_doc_links)]
7#![deny(rustdoc::private_intra_doc_links)]
8#![warn(
9    missing_docs,
10    rust_2018_idioms,
11    clippy::cast_sign_loss,
12    clippy::cast_possible_truncation,
13    clippy::cast_possible_wrap,
14    clippy::cast_lossless,
15    clippy::arithmetic_side_effects
16)]
17
18pub mod event;
19pub mod storage;
20
21use std::fmt::Display;
22use std::num::ParseIntError;
23use std::str::FromStr;
24
25use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
26use namada_core::hints;
27use namada_macros::BorshDeserializer;
28#[cfg(feature = "migrations")]
29use namada_migrations::*;
30use serde::{Deserialize, Serialize};
31use thiserror::Error;
32
33/// Choose the gas mmeter used for WASM instructions
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub enum GasMeterKind {
36    /// Gas accounting using a host env function. Suitable for unstructed code.
37    HostFn,
38    /// Global mutable variable accounted inside WASM. This should only be used
39    /// for trusted WASM code as a malicious code might modify the gas meter
40    MutGlobal,
41}
42
43#[allow(missing_docs)]
44#[derive(Error, Debug, Clone, PartialEq, Eq)]
45pub enum Error {
46    #[error("Transaction gas exceeded the limit of {0} gas units")]
47    TransactionGasExceededError(WholeGas),
48    #[error("Block gas limit exceeded")]
49    BlockGasExceeded,
50    #[error("Overflow during gas operations")]
51    GasOverflow,
52}
53
54#[allow(missing_docs)]
55#[derive(Error, Debug, Clone, PartialEq, Eq)]
56pub enum GasParseError {
57    #[error("Failed to parse gas: {0}")]
58    Parse(ParseIntError),
59    #[error("Gas overflowed")]
60    Overflow,
61}
62
63// RAW GAS COSTS
64// =============================================================================
65// The raw gas costs exctracted from the benchmarks.
66//
67const COMPILE_GAS_PER_BYTE_RAW: u64 = 1_664;
68const WASM_CODE_VALIDATION_GAS_PER_BYTE_RAW: u64 = 59;
69const WRAPPER_TX_VALIDATION_GAS_RAW: u64 = 1_526_700;
70// There's no benchmark to calculate the cost of storage occupation, so we
71// define it as the cost of storage latency (which is needed for any storage
72// operation and it's based on actual execution time), plus the same cost
73// multiplied by an arbitrary factor that represents the higher cost of storage
74// space as a resource. This way, the storage occupation cost is not completely
75// free-floating but it's tied to the other costs
76const STORAGE_OCCUPATION_GAS_PER_BYTE_RAW: u64 =
77    PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW * (1 + 1_000);
78// NOTE: this accounts for the latency of a physical drive access. For read
79// accesses we have no way to tell if data was in cache or in storage. Moreover,
80// the latency shouldn't really be accounted per single byte but rather per
81// storage blob but this would make it more tedious to compute gas in the
82// codebase. For these two reasons we just set an arbitrary value (based on
83// actual SSDs latency) per byte here
84const PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW: u64 = 20;
85// This is based on the global average bandwidth
86const NETWORK_TRANSMISSION_GAS_PER_BYTE_RAW: u64 = 848;
87
88// The cost of accessing data from memory (both read and write mode), per byte
89const MEMORY_ACCESS_GAS_PER_BYTE_RAW: u64 = 39;
90// The cost of accessing data from storage, per byte
91const STORAGE_ACCESS_GAS_PER_BYTE_RAW: u64 =
92    93 + PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW;
93// The cost of writing data to storage, per byte
94const STORAGE_WRITE_GAS_PER_BYTE_RAW: u64 = MEMORY_ACCESS_GAS_PER_BYTE_RAW
95    + 17_583
96    + STORAGE_OCCUPATION_GAS_PER_BYTE_RAW;
97// The cost of removing data from storage, per byte
98const STORAGE_DELETE_GAS_PER_BYTE_RAW: u64 = MEMORY_ACCESS_GAS_PER_BYTE_RAW
99    + 17_583
100    + PHYSICAL_STORAGE_LATENCY_PER_BYTE_RAW;
101// The cost of verifying a single signature of a transaction
102const VERIFY_TX_SIG_GAS_RAW: u64 = 435_190;
103// The cost for requesting one more page in wasm (64KiB)
104const WASM_MEMORY_PAGE_GAS_RAW: u64 =
105    MEMORY_ACCESS_GAS_PER_BYTE_RAW * 64 * 1_024;
106// The cost to validate an Ibc action
107const IBC_ACTION_VALIDATE_GAS_RAW: u64 = 290_935;
108// The cost to execute an Ibc action
109const IBC_ACTION_EXECUTE_GAS_RAW: u64 = 1_685_733;
110// The cost of masp sig verification
111const MASP_VERIFY_SIG_GAS_RAW: u64 = 1_908_750;
112// The fixed cost of spend note verification
113const MASP_FIXED_SPEND_GAS_RAW: u64 = 59_521_000;
114// The variable cost of spend note verification
115const MASP_VARIABLE_SPEND_GAS_RAW: u64 = 9_849_000;
116// The fixed cost of convert note verification
117const MASP_FIXED_CONVERT_GAS_RAW: u64 = 46_197_000;
118// The variable cost of convert note verification
119const MASP_VARIABLE_CONVERT_GAS_RAW: u64 = 10_245_000;
120// The fixed cost of output note verification
121const MASP_FIXED_OUTPUT_GAS_RAW: u64 = 53_439_000;
122// The variable cost of output note verification
123const MASP_VARIABLE_OUTPUT_GAS_RAW: u64 = 9_710_000;
124// The cost to process a masp spend note in the bundle
125const MASP_SPEND_CHECK_GAS_RAW: u64 = 405_070;
126// The cost to process a masp convert note in the bundle
127const MASP_CONVERT_CHECK_GAS_RAW: u64 = 188_590;
128// The cost to process a masp output note in the bundle
129const MASP_OUTPUT_CHECK_GAS_RAW: u64 = 204_430;
130// The cost to run the final masp check in the bundle
131const MASP_FINAL_CHECK_GAS_RAW: u64 = 43;
132// =============================================================================
133
134// A correction factor for non-WASM-opcodes costs. We can see that the
135// gas cost we get for wasm codes (txs and vps) is much greater than what we
136// would expect from the benchmarks. This is likely due to some imperfections in
137// the injection tool but, most importantly, to the fact that the code we end up
138// executing is an optimized version of the one we instrument. Therefore we
139// provide this factor to correct the costs of non-WASM gas based on the avarage
140// speedup we can observe. NOTE: we should really reduce the gas costs of WASM
141// opcodes instead of increasing the gas costs of non-WASM gas, but the former
142// would involve some complicated adjustments for host function calls so we
143// prefer to go with the latter.
144const GAS_COST_CORRECTION: u64 = 5;
145
146// ADJUSTED GAS COSTS
147// =============================================================================
148// The gas costs adjusted for the correction factor.
149//
150
151// The compilation cost is reduced by a factor to compensate for the (most
152// likely) presence of the cache
153const COMPILE_GAS_PER_BYTE: u64 =
154    COMPILE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION / 100;
155const WASM_CODE_VALIDATION_GAS_PER_BYTE: u64 =
156    WASM_CODE_VALIDATION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
157const WRAPPER_TX_VALIDATION_GAS: u64 =
158    WRAPPER_TX_VALIDATION_GAS_RAW * GAS_COST_CORRECTION;
159const STORAGE_OCCUPATION_GAS_PER_BYTE: u64 =
160    STORAGE_OCCUPATION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
161const NETWORK_TRANSMISSION_GAS_PER_BYTE: u64 =
162    NETWORK_TRANSMISSION_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
163/// The cost of accessing data from memory (both read and write mode), per byte
164pub const MEMORY_ACCESS_GAS_PER_BYTE: u64 =
165    MEMORY_ACCESS_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
166/// The cost of accessing data from storage, per byte
167pub const STORAGE_ACCESS_GAS_PER_BYTE: u64 =
168    STORAGE_ACCESS_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
169/// The cost of writing data to storage, per byte
170pub const STORAGE_WRITE_GAS_PER_BYTE: u64 =
171    STORAGE_WRITE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
172/// The cost of removing data from storage, per byte
173pub const STORAGE_DELETE_GAS_PER_BYTE: u64 =
174    STORAGE_DELETE_GAS_PER_BYTE_RAW * GAS_COST_CORRECTION;
175/// The cost of verifying a single signature of a transaction
176pub const VERIFY_TX_SIG_GAS: u64 = VERIFY_TX_SIG_GAS_RAW * GAS_COST_CORRECTION;
177/// The cost for requesting one more page in wasm (64KiB)
178#[allow(clippy::cast_possible_truncation)] // const in u32 range
179pub const WASM_MEMORY_PAGE_GAS: u32 =
180    (WASM_MEMORY_PAGE_GAS_RAW * GAS_COST_CORRECTION) as u32;
181/// The cost to validate an Ibc action
182pub const IBC_ACTION_VALIDATE_GAS: u64 =
183    IBC_ACTION_VALIDATE_GAS_RAW * GAS_COST_CORRECTION;
184/// The cost to execute an Ibc action
185pub const IBC_ACTION_EXECUTE_GAS: u64 =
186    IBC_ACTION_EXECUTE_GAS_RAW * GAS_COST_CORRECTION;
187/// The cost of masp sig verification
188pub const MASP_VERIFY_SIG_GAS: u64 =
189    MASP_VERIFY_SIG_GAS_RAW * GAS_COST_CORRECTION;
190/// The fixed cost of spend note verification
191pub const MASP_FIXED_SPEND_GAS: u64 =
192    MASP_FIXED_SPEND_GAS_RAW * GAS_COST_CORRECTION;
193/// The variable cost of spend note verification
194pub const MASP_VARIABLE_SPEND_GAS: u64 =
195    MASP_VARIABLE_SPEND_GAS_RAW * GAS_COST_CORRECTION;
196/// The fixed cost of convert note verification
197pub const MASP_FIXED_CONVERT_GAS: u64 =
198    MASP_FIXED_CONVERT_GAS_RAW * GAS_COST_CORRECTION;
199/// The variable cost of convert note verification
200pub const MASP_VARIABLE_CONVERT_GAS: u64 =
201    MASP_VARIABLE_CONVERT_GAS_RAW * GAS_COST_CORRECTION;
202/// The fixed cost of output note verification
203pub const MASP_FIXED_OUTPUT_GAS: u64 =
204    MASP_FIXED_OUTPUT_GAS_RAW * GAS_COST_CORRECTION;
205/// The variable cost of output note verification
206pub const MASP_VARIABLE_OUTPUT_GAS: u64 =
207    MASP_VARIABLE_OUTPUT_GAS_RAW * GAS_COST_CORRECTION;
208/// The cost to process a masp spend note in the bundle
209pub const MASP_SPEND_CHECK_GAS: u64 =
210    MASP_SPEND_CHECK_GAS_RAW * GAS_COST_CORRECTION;
211/// The cost to process a masp convert note in the bundle
212pub const MASP_CONVERT_CHECK_GAS: u64 =
213    MASP_CONVERT_CHECK_GAS_RAW * GAS_COST_CORRECTION;
214/// The cost to process a masp output note in the bundle
215pub const MASP_OUTPUT_CHECK_GAS: u64 =
216    MASP_OUTPUT_CHECK_GAS_RAW * GAS_COST_CORRECTION;
217/// The cost to run the final masp check in the bundle
218pub const MASP_FINAL_CHECK_GAS: u64 =
219    MASP_FINAL_CHECK_GAS_RAW * GAS_COST_CORRECTION;
220// =============================================================================
221
222/// Gas module result for functions that may fail
223pub type Result<T> = std::result::Result<T, Error>;
224
225/// Representation of tracking gas in sub-units.
226///
227/// This effectively decouples gas metering from fee payment, allowing higher
228/// resolution when accounting for gas while, at the same time, providing a
229/// contained gas value when paying fees.
230// This type should not implement the Copy trait to prevent charging gas more
231// than once
232#[derive(
233    Clone,
234    Debug,
235    Default,
236    PartialEq,
237    PartialOrd,
238    BorshDeserialize,
239    BorshDeserializer,
240    BorshSerialize,
241    BorshSchema,
242    Serialize,
243    Deserialize,
244)]
245#[must_use = "Gas must be accounted for by the gas meter"]
246pub struct Gas {
247    sub: u64,
248}
249
250impl Gas {
251    /// Initialize a new gas value from its sub units.
252    pub const fn new(sub_units: u64) -> Self {
253        Self { sub: sub_units }
254    }
255
256    /// Checked add of `Gas`. Returns `None` on overflow
257    pub fn checked_add(&self, rhs: Self) -> Option<Self> {
258        self.sub.checked_add(rhs.sub).map(|sub| Self { sub })
259    }
260
261    /// Checked sub of `Gas`. Returns `None` on underflow
262    pub fn checked_sub(&self, rhs: Self) -> Option<Self> {
263        self.sub.checked_sub(rhs.sub).map(|sub| Self { sub })
264    }
265
266    /// Checked div of `Gas`. Returns `None` if `rhs` is zero.
267    pub fn checked_div(&self, rhs: u64) -> Option<Self> {
268        self.sub.checked_div(rhs).map(|sub| Self { sub })
269    }
270
271    /// Converts the sub gas units to whole ones. If the sub units are not a
272    /// multiple of the scale than ceil the quotient
273    pub fn get_whole_gas_units(&self, scale: u64) -> WholeGas {
274        let quotient = self
275            .sub
276            .checked_div(scale)
277            .expect("Gas quotient should not overflow on checked division");
278        if self
279            .sub
280            .checked_rem(scale)
281            .expect("Gas quotient remainder should not overflow")
282            == 0
283        {
284            quotient.into()
285        } else {
286            quotient
287                .checked_add(1)
288                .expect("Cannot overflow as the quotient is scaled down u64")
289                .into()
290        }
291    }
292
293    /// Generates a `Gas` instance from a `WholeGas` amount
294    pub fn from_whole_units(whole: WholeGas, scale: u64) -> Option<Self> {
295        scale.checked_mul(whole.into()).map(|sub| Self { sub })
296    }
297}
298
299impl From<u64> for Gas {
300    fn from(sub: u64) -> Self {
301        Self { sub }
302    }
303}
304
305impl From<Gas> for u64 {
306    fn from(gas: Gas) -> Self {
307        gas.sub
308    }
309}
310
311/// Gas represented in whole units. Used for fee payment and to display
312/// information to the user.
313#[derive(
314    Debug,
315    Clone,
316    Copy,
317    PartialEq,
318    BorshSerialize,
319    BorshDeserialize,
320    BorshDeserializer,
321    BorshSchema,
322    Serialize,
323    Deserialize,
324    Eq,
325)]
326pub struct WholeGas(u64);
327
328impl From<u64> for WholeGas {
329    fn from(amount: u64) -> WholeGas {
330        Self(amount)
331    }
332}
333
334impl From<WholeGas> for u64 {
335    fn from(whole: WholeGas) -> u64 {
336        whole.0
337    }
338}
339
340impl FromStr for WholeGas {
341    type Err = GasParseError;
342
343    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
344        Ok(Self(s.parse().map_err(GasParseError::Parse)?))
345    }
346}
347
348impl Display for WholeGas {
349    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        write!(f, "{}", self.0)
351    }
352}
353
354/// Trait to share gas operations for transactions and validity predicates
355pub trait GasMetering {
356    /// Add gas cost. It will return error when the
357    /// consumed gas exceeds the provided transaction gas limit, but the state
358    /// will still be updated
359    fn consume(&mut self, gas: Gas) -> Result<()>;
360
361    /// Get the gas initially available to the gas meter
362    ///
363    /// This value will be equal to the gas limit minus some
364    /// gas that may have been consumed before the current
365    /// meter was initialized
366    fn get_initially_available_gas(&self) -> Gas;
367
368    /// Get the gas consumed thus far
369    fn get_consumed_gas(&self) -> Gas;
370
371    /// Get the gas limit
372    fn get_gas_limit(&self) -> Gas;
373
374    /// Get the protocol gas scale
375    fn get_gas_scale(&self) -> u64;
376
377    /// Get the amount of gas still available to the transaction
378    fn get_available_gas(&self) -> Gas {
379        self.get_gas_limit()
380            .checked_sub(self.get_consumed_gas())
381            .unwrap_or_default()
382    }
383
384    /// Add the compiling cost proportionate to the code length
385    fn add_compiling_gas(&mut self, bytes_len: u64) -> Result<()> {
386        self.consume(
387            bytes_len
388                .checked_mul(COMPILE_GAS_PER_BYTE)
389                .ok_or(Error::GasOverflow)?
390                .into(),
391        )
392    }
393
394    /// Add the gas for loading the wasm code from storage
395    fn add_wasm_load_from_storage_gas(&mut self, bytes_len: u64) -> Result<()> {
396        self.consume(
397            bytes_len
398                .checked_mul(STORAGE_ACCESS_GAS_PER_BYTE)
399                .ok_or(Error::GasOverflow)?
400                .into(),
401        )
402    }
403
404    /// Add the gas for validating untrusted wasm code
405    fn add_wasm_validation_gas(&mut self, bytes_len: u64) -> Result<()> {
406        self.consume(
407            bytes_len
408                .checked_mul(WASM_CODE_VALIDATION_GAS_PER_BYTE)
409                .ok_or(Error::GasOverflow)?
410                .into(),
411        )
412    }
413
414    /// Check if the meter ran out of gas. Starts with the initial gas.
415    fn check_limit(&self, gas: Gas) -> Result<()> {
416        self.get_initially_available_gas()
417            .checked_sub(gas)
418            .ok_or_else(|| {
419                Error::TransactionGasExceededError(
420                    self.get_gas_limit()
421                        .get_whole_gas_units(self.get_gas_scale()),
422                )
423            })
424            .and(Ok(()))
425    }
426}
427
428/// Gas metering in a transaction
429#[derive(Debug)]
430pub struct TxGasMeter {
431    /// Track gas overflow
432    gas_overflow: bool,
433    /// The protocol gas scale
434    gas_scale: u64,
435    /// The gas limit for a transaction
436    tx_gas_limit: Gas,
437    /// Gas consumption of the tx
438    transaction_gas: Gas,
439}
440
441/// Gas metering in a validity predicate
442#[derive(Debug)]
443pub struct VpGasMeter {
444    /// Track gas overflow
445    gas_overflow: bool,
446    /// The protocol gas scale
447    gas_scale: u64,
448    /// The transaction gas limit
449    tx_gas_limit: Gas,
450    /// The gas consumed by the transaction before the Vp
451    prev_meter_consumed_gas: Gas,
452    /// The current gas usage in the VP
453    current_gas: Gas,
454}
455
456impl GasMetering for TxGasMeter {
457    fn consume(&mut self, gas: Gas) -> Result<()> {
458        if self.gas_overflow {
459            hints::cold();
460            return Err(Error::GasOverflow);
461        }
462
463        self.transaction_gas =
464            self.transaction_gas.checked_add(gas).ok_or_else(|| {
465                hints::cold();
466                self.gas_overflow = true;
467                Error::GasOverflow
468            })?;
469
470        if self.transaction_gas > self.tx_gas_limit {
471            return Err(Error::TransactionGasExceededError(
472                self.tx_gas_limit.get_whole_gas_units(self.gas_scale),
473            ));
474        }
475
476        Ok(())
477    }
478
479    #[inline]
480    fn get_initially_available_gas(&self) -> Gas {
481        self.get_gas_limit()
482    }
483
484    fn get_consumed_gas(&self) -> Gas {
485        if !self.gas_overflow {
486            self.transaction_gas.clone()
487        } else {
488            hints::cold();
489            u64::MAX.into()
490        }
491    }
492
493    fn get_gas_limit(&self) -> Gas {
494        self.tx_gas_limit.clone()
495    }
496
497    fn get_gas_scale(&self) -> u64 {
498        self.gas_scale
499    }
500}
501
502impl TxGasMeter {
503    /// Return a placeholder [`TxGasMeter`].
504    ///
505    /// ## Safety
506    ///
507    /// This should only be used as an unitialized meter. Do
508    /// not perform gas metering with it.
509    pub const unsafe fn placeholder() -> Self {
510        Self {
511            gas_overflow: false,
512            gas_scale: 0u64,
513            tx_gas_limit: Gas::new(0u64),
514            transaction_gas: Gas::new(0u64),
515        }
516    }
517
518    /// Initialize a new Tx gas meter. Requires a gas limit for the specific
519    /// wrapper transaction and the protocol's gas scale
520    pub fn new(tx_gas_limit: impl Into<Gas>, gas_scale: u64) -> Self {
521        Self {
522            gas_overflow: false,
523            gas_scale,
524            tx_gas_limit: tx_gas_limit.into(),
525            transaction_gas: Gas::default(),
526        }
527    }
528
529    /// Add the gas required by a wrapper transaction which is comprised of:
530    ///  - cost of validating the wrapper tx
531    ///  - space that the transaction requires in the block
532    ///  - cost of downloading (as part of the block) the transaction bytes over
533    ///    the network
534    pub fn add_wrapper_gas(&mut self, tx_bytes: &[u8]) -> Result<()> {
535        self.consume(WRAPPER_TX_VALIDATION_GAS.into())?;
536
537        let bytes_len = tx_bytes.len() as u64;
538        self.consume(
539            bytes_len
540                .checked_mul(
541                    STORAGE_OCCUPATION_GAS_PER_BYTE
542                        + NETWORK_TRANSMISSION_GAS_PER_BYTE,
543                )
544                .ok_or(Error::GasOverflow)?
545                .into(),
546        )
547    }
548}
549
550impl GasMetering for VpGasMeter {
551    fn consume(&mut self, gas: Gas) -> Result<()> {
552        if self.gas_overflow {
553            hints::cold();
554            return Err(Error::GasOverflow);
555        }
556
557        self.current_gas =
558            self.current_gas.checked_add(gas).ok_or_else(|| {
559                hints::cold();
560                self.gas_overflow = true;
561                Error::GasOverflow
562            })?;
563
564        let current_total = self
565            .prev_meter_consumed_gas
566            .checked_add(self.current_gas.clone())
567            .ok_or(Error::GasOverflow)?;
568
569        if current_total > self.tx_gas_limit {
570            return Err(Error::TransactionGasExceededError(
571                self.tx_gas_limit.get_whole_gas_units(self.gas_scale),
572            ));
573        }
574
575        Ok(())
576    }
577
578    fn get_initially_available_gas(&self) -> Gas {
579        self.tx_gas_limit
580            .checked_sub(self.prev_meter_consumed_gas.clone())
581            .unwrap_or_default()
582    }
583
584    fn get_consumed_gas(&self) -> Gas {
585        self.prev_meter_consumed_gas
586            .checked_add(self.get_vp_consumed_gas())
587            .unwrap_or_else(|| u64::MAX.into())
588    }
589
590    fn get_gas_limit(&self) -> Gas {
591        self.tx_gas_limit.clone()
592    }
593
594    fn get_gas_scale(&self) -> u64 {
595        self.gas_scale
596    }
597}
598
599impl VpGasMeter {
600    /// Return a placeholder [`VpGasMeter`].
601    ///
602    /// ## Safety
603    ///
604    /// This should only be used as an unitialized meter. Do
605    /// not perform gas metering with it.
606    pub const unsafe fn placeholder() -> Self {
607        Self {
608            gas_overflow: false,
609            gas_scale: 0u64,
610            tx_gas_limit: Gas::new(0u64),
611            prev_meter_consumed_gas: Gas::new(0u64),
612            current_gas: Gas::new(0u64),
613        }
614    }
615
616    /// Initialize a new VP gas meter from the [`TxGasMeter`]
617    pub fn new_from_tx_meter(tx_gas_meter: &TxGasMeter) -> Self {
618        Self::new_from_meter(tx_gas_meter)
619    }
620
621    /// Initialize a new VP gas meter from the given generic gas meter
622    pub fn new_from_meter(gas_meter: &impl GasMetering) -> Self {
623        Self {
624            gas_overflow: false,
625            gas_scale: gas_meter.get_gas_scale(),
626            tx_gas_limit: gas_meter.get_gas_limit(),
627            prev_meter_consumed_gas: gas_meter.get_consumed_gas(),
628            current_gas: Gas::default(),
629        }
630    }
631
632    /// Get the gas consumed by the VP alone
633    pub fn get_vp_consumed_gas(&self) -> Gas {
634        self.current_gas.clone()
635    }
636}
637
638#[cfg(test)]
639mod tests {
640    use assert_matches::assert_matches;
641    use proptest::prelude::*;
642
643    use super::*;
644    const BLOCK_GAS_LIMIT: u64 = 10_000_000_000;
645    const TX_GAS_LIMIT: u64 = 1_000_000;
646    const GAS_SCALE: u64 = 1;
647
648    proptest! {
649        #[test]
650        fn test_vp_gas_meter_add(gas in 0..BLOCK_GAS_LIMIT) {
651            let tx_gas_meter = TxGasMeter {
652                gas_overflow: false,
653                gas_scale: GAS_SCALE,
654                tx_gas_limit: BLOCK_GAS_LIMIT.into(),
655                transaction_gas: Gas::default(),
656            };
657            let mut meter = VpGasMeter::new_from_tx_meter(&tx_gas_meter);
658            meter.consume(gas.into()).expect("cannot add the gas");
659        }
660
661    }
662
663    #[test]
664    fn test_vp_gas_overflow() {
665        let tx_gas_meter = TxGasMeter {
666            gas_overflow: false,
667            gas_scale: GAS_SCALE,
668            tx_gas_limit: BLOCK_GAS_LIMIT.into(),
669            transaction_gas: (TX_GAS_LIMIT - 1).into(),
670        };
671        let mut meter = VpGasMeter::new_from_tx_meter(&tx_gas_meter);
672        assert_matches!(
673            meter
674                .consume(u64::MAX.into())
675                .expect_err("unexpectedly succeeded"),
676            Error::GasOverflow
677        );
678    }
679
680    #[test]
681    fn test_vp_gas_limit() {
682        let tx_gas_meter = TxGasMeter {
683            gas_overflow: false,
684            gas_scale: GAS_SCALE,
685            tx_gas_limit: TX_GAS_LIMIT.into(),
686            transaction_gas: (TX_GAS_LIMIT - 1).into(),
687        };
688        let mut meter = VpGasMeter::new_from_tx_meter(&tx_gas_meter);
689        assert_matches!(
690            meter
691                .consume(TX_GAS_LIMIT.into())
692                .expect_err("unexpectedly succeeded"),
693            Error::TransactionGasExceededError(_)
694        );
695    }
696
697    #[test]
698    fn test_tx_gas_overflow() {
699        let mut meter = TxGasMeter::new(BLOCK_GAS_LIMIT, GAS_SCALE);
700        meter.consume(1.into()).expect("cannot add the gas");
701        assert_matches!(
702            meter
703                .consume(u64::MAX.into())
704                .expect_err("unexpectedly succeeded"),
705            Error::GasOverflow
706        );
707    }
708
709    #[test]
710    fn test_tx_gas_limit() {
711        let mut meter = TxGasMeter::new(TX_GAS_LIMIT, GAS_SCALE);
712        assert_matches!(
713            meter
714                .consume((TX_GAS_LIMIT + 1).into())
715                .expect_err("unexpectedly succeeded"),
716            Error::TransactionGasExceededError(_)
717        );
718    }
719}