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,184 @@ 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+ #[ inline]
55+ pub fn from_milligas ( milligas : i64 ) -> Gas {
56+ Gas ( milligas)
57+ }
58+
59+ #[ inline]
60+ pub fn new ( gas : i64 ) -> Gas {
61+ Gas ( gas. saturating_mul ( MILLIGAS_PRECISION ) )
62+ }
63+
64+ #[ inline]
65+ pub fn is_saturated ( & self ) -> bool {
66+ self . 0 == i64:: MAX
67+ }
68+
69+ #[ inline]
70+ pub fn round_up ( & self ) -> i64 {
71+ milligas_to_gas ( self . 0 , true )
72+ }
73+
74+ #[ inline]
75+ pub fn round_down ( & self ) -> i64 {
76+ milligas_to_gas ( self . 0 , false )
77+ }
78+
79+ #[ inline]
80+ pub fn as_milligas ( & self ) -> i64 {
81+ self . 0
82+ }
83+ }
84+
85+ impl num_traits:: Zero for Gas {
86+ fn zero ( ) -> Self {
87+ return Gas :: default ( ) ;
88+ }
89+
90+ fn is_zero ( & self ) -> bool {
91+ self . 0 == 0
92+ }
93+ }
94+
95+ impl Add for Gas {
96+ type Output = Gas ;
97+
98+ #[ inline]
99+ fn add ( self , rhs : Self ) -> Self :: Output {
100+ Self ( self . 0 . saturating_add ( rhs. 0 ) )
101+ }
102+ }
103+
104+ impl AddAssign for Gas {
105+ #[ inline]
106+ fn add_assign ( & mut self , rhs : Self ) {
107+ self . 0 = self . 0 . saturating_add ( rhs. 0 )
108+ }
109+ }
110+
111+ impl SubAssign for Gas {
112+ #[ inline]
113+ fn sub_assign ( & mut self , rhs : Self ) {
114+ self . 0 = self . 0 . saturating_sub ( rhs. 0 )
115+ }
116+ }
117+
118+ impl Sub for Gas {
119+ type Output = Gas ;
120+
121+ #[ inline]
122+ fn sub ( self , rhs : Self ) -> Self :: Output {
123+ Self ( self . 0 . saturating_sub ( rhs. 0 ) )
124+ }
125+ }
126+
127+ impl Mul < i64 > for Gas {
128+ type Output = Gas ;
129+
130+ #[ inline]
131+ fn mul ( self , rhs : i64 ) -> Self :: Output {
132+ Self ( self . 0 . saturating_mul ( rhs) )
133+ }
134+ }
135+
136+ impl Mul < i32 > for Gas {
137+ type Output = Gas ;
138+
139+ #[ inline]
140+ fn mul ( self , rhs : i32 ) -> Self :: Output {
141+ Self ( self . 0 . saturating_mul ( rhs. into ( ) ) )
142+ }
143+ }
18144
19145pub struct GasTracker {
20- milligas_limit : i64 ,
21- milligas_used : i64 ,
146+ gas_limit : Gas ,
147+ gas_used : Gas ,
22148}
23149
24150impl GasTracker {
25151 /// Gas limit and gas used are provided in protocol units (i.e. full units).
26152 /// They are converted to milligas for internal canonical accounting.
27153 pub fn new ( gas_limit : Gas , gas_used : Gas ) -> Self {
28154 Self {
29- milligas_limit : gas_to_milligas ( gas_limit) ,
30- milligas_used : gas_to_milligas ( gas_used) ,
155+ gas_limit,
156+ gas_used,
31157 }
32158 }
33159
34160 /// Safely consumes gas and returns an out of gas error if there is not sufficient
35161 /// 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- }
162+ pub fn charge_gas ( & mut self , name : & str , to_use : Gas ) -> Result < ( ) > {
163+ log:: trace!( "charging gas: {} {}" , name, to_use) ;
164+ // The gas type uses saturating math.
165+ self . gas_used += to_use;
166+ if self . gas_used > self . gas_limit {
167+ log:: trace!( "gas limit reached" ) ;
168+ self . gas_used = self . gas_limit ;
169+ Err ( ExecutionError :: OutOfGas )
170+ } else {
171+ Ok ( ( ) )
54172 }
55173 }
56174
57175 /// Applies the specified gas charge, where quantities are supplied in milligas.
58176 pub fn apply_charge ( & mut self , charge : GasCharge ) -> Result < ( ) > {
59- self . charge_milligas ( charge. name , charge. total ( ) )
177+ self . charge_gas ( charge. name , charge. total ( ) )
60178 }
61179
62- /// Getter for gas available .
180+ /// Getter for the maximum gas usable by this message .
63181 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
182+ self . gas_limit
70183 }
71184
72185 /// Getter for gas used.
73186 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
187+ self . gas_used
80188 }
81189
190+ /// Getter for gas available.
82191 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 )
192+ self . gas_limit - self . gas_used
88193 }
89194}
90195
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-
97196/// Converts the specified fractional gas units into gas units
98197#[ inline]
99198pub ( crate ) fn milligas_to_gas ( milligas : i64 , round_up : bool ) -> i64 {
@@ -108,18 +207,20 @@ pub(crate) fn milligas_to_gas(milligas: i64, round_up: bool) -> i64 {
108207
109208#[ cfg( test) ]
110209mod tests {
210+ use num_traits:: Zero ;
211+
111212 use super :: * ;
112213
113214 #[ test]
114215 #[ allow( clippy:: identity_op) ]
115216 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 ) ;
217+ let mut t = GasTracker :: new ( Gas :: new ( 20 ) , Gas :: new ( 10 ) ) ;
218+ t. apply_charge ( GasCharge :: new ( "" , Gas :: new ( 5 ) , Gas :: zero ( ) ) ) ?;
219+ assert_eq ! ( t. gas_used( ) , Gas :: new ( 15 ) ) ;
220+ t. apply_charge ( GasCharge :: new ( "" , Gas :: new ( 5 ) , Gas :: zero ( ) ) ) ?;
221+ assert_eq ! ( t. gas_used( ) , Gas :: new ( 20 ) ) ;
121222 assert ! ( t
122- . apply_charge( GasCharge :: new( "" , 1 * MILLIGAS_PRECISION , 0 ) )
223+ . apply_charge( GasCharge :: new( "" , Gas :: new ( 1 ) , Gas :: zero ( ) ) )
123224 . is_err( ) ) ;
124225 Ok ( ( ) )
125226 }
0 commit comments