11// Copyright 2019-2022 ChainSafe Systems
22// SPDX-License-Identifier: Apache-2.0, MIT
33
4+ use std:: fmt:: { Debug , Display } ;
5+ use std:: ops:: { Add , AddAssign , Mul , Sub , SubAssign } ;
6+
47pub use self :: charge:: GasCharge ;
58pub ( crate ) use self :: outputs:: GasOutputs ;
69pub use self :: price_list:: { price_list_by_network_version, PriceList , WasmGasPrices } ;
@@ -12,88 +15,190 @@ mod price_list;
1215
1316pub const MILLIGAS_PRECISION : i64 = 1000 ;
1417
15- // Type aliases to disambiguate units in interfaces.
16- pub type Gas = i64 ;
17- pub type Milligas = i64 ;
18+ /// A typesafe representation of gas (internally stored as milligas).
19+ ///
20+ /// - All math operations are _saturating_ and never overflow.
21+ /// - Enforces correct units by making it impossible to, e.g., get gas squared (by multiplying gas
22+ /// by gas).
23+ /// - Makes it harder to confuse gas and milligas.
24+ #[ derive( Hash , Eq , PartialEq , Ord , PartialOrd , Copy , Clone , Default ) ]
25+ pub struct Gas ( i64 /* milligas */ ) ;
26+
27+ impl Debug for Gas {
28+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
29+ if self . 0 == 0 {
30+ f. debug_tuple ( "Gas" ) . field ( & 0 as & dyn Debug ) . finish ( )
31+ } else {
32+ let integral = self . 0 / MILLIGAS_PRECISION ;
33+ let fractional = self . 0 % MILLIGAS_PRECISION ;
34+ f. debug_tuple ( "Gas" )
35+ . field ( & format_args ! ( "{integral}.{fractional:03}" ) )
36+ . finish ( )
37+ }
38+ }
39+ }
40+
41+ impl Display for Gas {
42+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
43+ if self . 0 == 0 {
44+ f. write_str ( "0" )
45+ } else {
46+ let integral = self . 0 / MILLIGAS_PRECISION ;
47+ let fractional = self . 0 % MILLIGAS_PRECISION ;
48+ write ! ( f, "{integral}.{fractional:03}" )
49+ }
50+ }
51+ }
52+
53+ impl Gas {
54+ /// Construct a `Gas` from milligas.
55+ #[ inline]
56+ pub fn from_milligas ( milligas : i64 ) -> Gas {
57+ Gas ( milligas)
58+ }
59+
60+ /// Construct a `Gas` from gas, scaling up. If this exceeds the width of an i64, it saturates at
61+ /// `i64::MAX` milligas.
62+ #[ inline]
63+ pub fn new ( gas : i64 ) -> Gas {
64+ Gas ( gas. saturating_mul ( MILLIGAS_PRECISION ) )
65+ }
66+
67+ #[ inline]
68+ pub fn is_saturated ( & self ) -> bool {
69+ self . 0 == i64:: MAX
70+ }
71+
72+ /// Returns the gas value as an integer, rounding the fractional part up.
73+ #[ inline]
74+ pub fn round_up ( & self ) -> i64 {
75+ milligas_to_gas ( self . 0 , true )
76+ }
77+
78+ /// Returns the gas value as an integer, truncating the fractional part.
79+ #[ inline]
80+ pub fn round_down ( & self ) -> i64 {
81+ milligas_to_gas ( self . 0 , false )
82+ }
83+
84+ /// Returns the gas value as milligas, without loss of precision.
85+ #[ inline]
86+ pub fn as_milligas ( & self ) -> i64 {
87+ self . 0
88+ }
89+ }
90+
91+ impl num_traits:: Zero for Gas {
92+ fn zero ( ) -> Self {
93+ Gas ( 0 )
94+ }
95+
96+ fn is_zero ( & self ) -> bool {
97+ self . 0 == 0
98+ }
99+ }
100+
101+ impl Add for Gas {
102+ type Output = Gas ;
103+
104+ #[ inline]
105+ fn add ( self , rhs : Self ) -> Self :: Output {
106+ Self ( self . 0 . saturating_add ( rhs. 0 ) )
107+ }
108+ }
109+
110+ impl AddAssign for Gas {
111+ #[ inline]
112+ fn add_assign ( & mut self , rhs : Self ) {
113+ self . 0 = self . 0 . saturating_add ( rhs. 0 )
114+ }
115+ }
116+
117+ impl SubAssign for Gas {
118+ #[ inline]
119+ fn sub_assign ( & mut self , rhs : Self ) {
120+ self . 0 = self . 0 . saturating_sub ( rhs. 0 )
121+ }
122+ }
123+
124+ impl Sub for Gas {
125+ type Output = Gas ;
126+
127+ #[ inline]
128+ fn sub ( self , rhs : Self ) -> Self :: Output {
129+ Self ( self . 0 . saturating_sub ( rhs. 0 ) )
130+ }
131+ }
132+
133+ impl Mul < i64 > for Gas {
134+ type Output = Gas ;
135+
136+ #[ inline]
137+ fn mul ( self , rhs : i64 ) -> Self :: Output {
138+ Self ( self . 0 . saturating_mul ( rhs) )
139+ }
140+ }
141+
142+ impl Mul < i32 > for Gas {
143+ type Output = Gas ;
144+
145+ #[ inline]
146+ fn mul ( self , rhs : i32 ) -> Self :: Output {
147+ Self ( self . 0 . saturating_mul ( rhs. into ( ) ) )
148+ }
149+ }
18150
19151pub struct GasTracker {
20- milligas_limit : i64 ,
21- milligas_used : i64 ,
152+ gas_limit : Gas ,
153+ gas_used : Gas ,
22154}
23155
24156impl GasTracker {
25157 /// Gas limit and gas used are provided in protocol units (i.e. full units).
26158 /// They are converted to milligas for internal canonical accounting.
27159 pub fn new ( gas_limit : Gas , gas_used : Gas ) -> Self {
28160 Self {
29- milligas_limit : gas_to_milligas ( gas_limit) ,
30- milligas_used : gas_to_milligas ( gas_used) ,
161+ gas_limit,
162+ gas_used,
31163 }
32164 }
33165
34166 /// Safely consumes gas and returns an out of gas error if there is not sufficient
35167 /// enough gas remaining for charge.
36- pub fn charge_milligas ( & mut self , name : & str , to_use : Milligas ) -> Result < ( ) > {
37- match self . milligas_used . checked_add ( to_use) {
38- None => {
39- log:: trace!( "gas overflow: {}" , name) ;
40- self . milligas_used = self . milligas_limit ;
41- Err ( ExecutionError :: OutOfGas )
42- }
43- Some ( used) => {
44- log:: trace!( "charged {} gas: {}" , to_use, name) ;
45- if used > self . milligas_limit {
46- log:: trace!( "out of gas: {}" , name) ;
47- self . milligas_used = self . milligas_limit ;
48- Err ( ExecutionError :: OutOfGas )
49- } else {
50- self . milligas_used = used;
51- Ok ( ( ) )
52- }
53- }
168+ pub fn charge_gas ( & mut self , name : & str , to_use : Gas ) -> Result < ( ) > {
169+ log:: trace!( "charging gas: {} {}" , name, to_use) ;
170+ // The gas type uses saturating math.
171+ self . gas_used += to_use;
172+ if self . gas_used > self . gas_limit {
173+ log:: trace!( "gas limit reached" ) ;
174+ self . gas_used = self . gas_limit ;
175+ Err ( ExecutionError :: OutOfGas )
176+ } else {
177+ Ok ( ( ) )
54178 }
55179 }
56180
57181 /// Applies the specified gas charge, where quantities are supplied in milligas.
58182 pub fn apply_charge ( & mut self , charge : GasCharge ) -> Result < ( ) > {
59- self . charge_milligas ( charge. name , charge. total ( ) )
183+ self . charge_gas ( charge. name , charge. total ( ) )
60184 }
61185
62- /// Getter for gas available .
186+ /// Getter for the maximum gas usable by this message .
63187 pub fn gas_limit ( & self ) -> Gas {
64- milligas_to_gas ( self . milligas_limit , false )
65- }
66-
67- /// Getter for milligas available.
68- pub fn milligas_limit ( & self ) -> Milligas {
69- self . milligas_limit
188+ self . gas_limit
70189 }
71190
72191 /// Getter for gas used.
73192 pub fn gas_used ( & self ) -> Gas {
74- milligas_to_gas ( self . milligas_used , true )
75- }
76-
77- /// Getter for milligas used.
78- pub fn milligas_used ( & self ) -> Milligas {
79- self . milligas_used
193+ self . gas_used
80194 }
81195
196+ /// Getter for gas available.
82197 pub fn gas_available ( & self ) -> Gas {
83- milligas_to_gas ( self . milligas_available ( ) , false )
84- }
85-
86- pub fn milligas_available ( & self ) -> Milligas {
87- self . milligas_limit . saturating_sub ( self . milligas_used )
198+ self . gas_limit - self . gas_used
88199 }
89200}
90201
91- /// Converts the specified gas into equivalent fractional gas units
92- #[ inline]
93- pub ( crate ) fn gas_to_milligas ( gas : i64 ) -> i64 {
94- gas. saturating_mul ( MILLIGAS_PRECISION )
95- }
96-
97202/// Converts the specified fractional gas units into gas units
98203#[ inline]
99204pub ( crate ) fn milligas_to_gas ( milligas : i64 , round_up : bool ) -> i64 {
@@ -108,18 +213,20 @@ pub(crate) fn milligas_to_gas(milligas: i64, round_up: bool) -> i64 {
108213
109214#[ cfg( test) ]
110215mod tests {
216+ use num_traits:: Zero ;
217+
111218 use super :: * ;
112219
113220 #[ test]
114221 #[ allow( clippy:: identity_op) ]
115222 fn basic_gas_tracker ( ) -> Result < ( ) > {
116- let mut t = GasTracker :: new ( 20 , 10 ) ;
117- t. apply_charge ( GasCharge :: new ( "" , 5 * MILLIGAS_PRECISION , 0 ) ) ?;
118- assert_eq ! ( t. gas_used( ) , 15 ) ;
119- t. apply_charge ( GasCharge :: new ( "" , 5 * MILLIGAS_PRECISION , 0 ) ) ?;
120- assert_eq ! ( t. gas_used( ) , 20 ) ;
223+ let mut t = GasTracker :: new ( Gas :: new ( 20 ) , Gas :: new ( 10 ) ) ;
224+ t. apply_charge ( GasCharge :: new ( "" , Gas :: new ( 5 ) , Gas :: zero ( ) ) ) ?;
225+ assert_eq ! ( t. gas_used( ) , Gas :: new ( 15 ) ) ;
226+ t. apply_charge ( GasCharge :: new ( "" , Gas :: new ( 5 ) , Gas :: zero ( ) ) ) ?;
227+ assert_eq ! ( t. gas_used( ) , Gas :: new ( 20 ) ) ;
121228 assert ! ( t
122- . apply_charge( GasCharge :: new( "" , 1 * MILLIGAS_PRECISION , 0 ) )
229+ . apply_charge( GasCharge :: new( "" , Gas :: new ( 1 ) , Gas :: zero ( ) ) )
123230 . is_err( ) ) ;
124231 Ok ( ( ) )
125232 }
0 commit comments