13
13
TupleExpression ,
14
14
TypeConversion ,
15
15
CallExpression ,
16
+ MemberAccess ,
16
17
)
18
+ from slither .core .expressions .elementary_type_name_expression import ElementaryTypeNameExpression
17
19
from slither .core .variables import Variable
18
20
from slither .utils .integer_conversion import convert_string_to_fraction , convert_string_to_int
19
21
from slither .visitors .expression .expression import ExpressionVisitor
@@ -27,7 +29,13 @@ class NotConstant(Exception):
27
29
KEY = "ConstantFolding"
28
30
29
31
CONSTANT_TYPES_OPERATIONS = Union [
30
- Literal , BinaryOperation , UnaryOperation , Identifier , TupleExpression , TypeConversion
32
+ Literal ,
33
+ BinaryOperation ,
34
+ UnaryOperation ,
35
+ Identifier ,
36
+ TupleExpression ,
37
+ TypeConversion ,
38
+ MemberAccess ,
31
39
]
32
40
33
41
@@ -69,6 +77,9 @@ def result(self) -> "Literal":
69
77
# pylint: disable=import-outside-toplevel
70
78
def _post_identifier (self , expression : Identifier ) -> None :
71
79
from slither .core .declarations .solidity_variables import SolidityFunction
80
+ from slither .core .declarations .enum import Enum
81
+ from slither .core .solidity_types .type_alias import TypeAlias
82
+ from slither .core .declarations .contract import Contract
72
83
73
84
if isinstance (expression .value , Variable ):
74
85
if expression .value .is_constant :
@@ -77,7 +88,14 @@ def _post_identifier(self, expression: Identifier) -> None:
77
88
# Everything outside of literal
78
89
if isinstance (
79
90
expr ,
80
- (BinaryOperation , UnaryOperation , Identifier , TupleExpression , TypeConversion ),
91
+ (
92
+ BinaryOperation ,
93
+ UnaryOperation ,
94
+ Identifier ,
95
+ TupleExpression ,
96
+ TypeConversion ,
97
+ MemberAccess ,
98
+ ),
81
99
):
82
100
cf = ConstantFolding (expr , self ._type )
83
101
expr = cf .result ()
@@ -88,20 +106,41 @@ def _post_identifier(self, expression: Identifier) -> None:
88
106
elif isinstance (expression .value , SolidityFunction ):
89
107
set_val (expression , expression .value )
90
108
else :
91
- raise NotConstant
109
+ # Enum: We don't want to raise an error for a direct access to an Enum as they can be converted to a constant value
110
+ # We can't handle it here because we don't have the field accessed so we do it in _post_member_access
111
+ # TypeAlias: Support when a .wrap() is done with a constant
112
+ # Contract: Support when a constatn is use from a different contract
113
+ if not isinstance (expression .value , (Enum , TypeAlias , Contract )):
114
+ raise NotConstant
92
115
93
116
# pylint: disable=too-many-branches,too-many-statements
94
117
def _post_binary_operation (self , expression : BinaryOperation ) -> None :
95
118
expression_left = expression .expression_left
96
119
expression_right = expression .expression_right
97
120
if not isinstance (
98
121
expression_left ,
99
- (Literal , BinaryOperation , UnaryOperation , Identifier , TupleExpression , TypeConversion ),
122
+ (
123
+ Literal ,
124
+ BinaryOperation ,
125
+ UnaryOperation ,
126
+ Identifier ,
127
+ TupleExpression ,
128
+ TypeConversion ,
129
+ MemberAccess ,
130
+ ),
100
131
):
101
132
raise NotConstant
102
133
if not isinstance (
103
134
expression_right ,
104
- (Literal , BinaryOperation , UnaryOperation , Identifier , TupleExpression , TypeConversion ),
135
+ (
136
+ Literal ,
137
+ BinaryOperation ,
138
+ UnaryOperation ,
139
+ Identifier ,
140
+ TupleExpression ,
141
+ TypeConversion ,
142
+ MemberAccess ,
143
+ ),
105
144
):
106
145
raise NotConstant
107
146
left = get_val (expression_left )
@@ -205,6 +244,34 @@ def _post_assignement_operation(self, expression: expressions.AssignmentOperatio
205
244
raise NotConstant
206
245
207
246
def _post_call_expression (self , expression : expressions .CallExpression ) -> None :
247
+ from slither .core .declarations .solidity_variables import SolidityFunction
248
+ from slither .core .declarations .enum import Enum
249
+ from slither .core .solidity_types import TypeAlias
250
+
251
+ # pylint: disable=too-many-boolean-expressions
252
+ if (
253
+ isinstance (expression .called , Identifier )
254
+ and expression .called .value == SolidityFunction ("type()" )
255
+ and len (expression .arguments ) == 1
256
+ and (
257
+ isinstance (expression .arguments [0 ], ElementaryTypeNameExpression )
258
+ or isinstance (expression .arguments [0 ], Identifier )
259
+ and isinstance (expression .arguments [0 ].value , Enum )
260
+ )
261
+ ):
262
+ # Returning early to support type(ElemType).max/min or type(MyEnum).max/min
263
+ return
264
+ if (
265
+ isinstance (expression .called .expression , Identifier )
266
+ and isinstance (expression .called .expression .value , TypeAlias )
267
+ and isinstance (expression .called , MemberAccess )
268
+ and expression .called .member_name == "wrap"
269
+ and len (expression .arguments ) == 1
270
+ ):
271
+ # Handle constants in .wrap of user defined type
272
+ set_val (expression , get_val (expression .arguments [0 ]))
273
+ return
274
+
208
275
called = get_val (expression .called )
209
276
args = [get_val (arg ) for arg in expression .arguments ]
210
277
if called .name == "keccak256(bytes)" :
@@ -220,12 +287,104 @@ def _post_conditional_expression(self, expression: expressions.ConditionalExpres
220
287
def _post_elementary_type_name_expression (
221
288
self , expression : expressions .ElementaryTypeNameExpression
222
289
) -> None :
223
- raise NotConstant
290
+ # We don't have to raise an exception to support type(uint112).max or similar
291
+ pass
224
292
225
293
def _post_index_access (self , expression : expressions .IndexAccess ) -> None :
226
294
raise NotConstant
227
295
296
+ # pylint: disable=too-many-locals
228
297
def _post_member_access (self , expression : expressions .MemberAccess ) -> None :
298
+ from slither .core .declarations import (
299
+ SolidityFunction ,
300
+ Contract ,
301
+ EnumContract ,
302
+ EnumTopLevel ,
303
+ Enum ,
304
+ )
305
+ from slither .core .solidity_types import UserDefinedType , TypeAlias
306
+
307
+ # pylint: disable=too-many-nested-blocks
308
+ if isinstance (expression .expression , CallExpression ) and expression .member_name in [
309
+ "min" ,
310
+ "max" ,
311
+ ]:
312
+ if isinstance (expression .expression .called , Identifier ):
313
+ if expression .expression .called .value == SolidityFunction ("type()" ):
314
+ assert len (expression .expression .arguments ) == 1
315
+ type_expression_found = expression .expression .arguments [0 ]
316
+ type_found : Union [ElementaryType , UserDefinedType ]
317
+ if isinstance (type_expression_found , ElementaryTypeNameExpression ):
318
+ type_expression_found_type = type_expression_found .type
319
+ assert isinstance (type_expression_found_type , ElementaryType )
320
+ type_found = type_expression_found_type
321
+ value = (
322
+ type_found .max if expression .member_name == "max" else type_found .min
323
+ )
324
+ set_val (expression , value )
325
+ return
326
+ # type(enum).max/min
327
+ # Case when enum is in another contract e.g. type(C.E).max
328
+ if isinstance (type_expression_found , MemberAccess ):
329
+ contract = type_expression_found .expression .value
330
+ assert isinstance (contract , Contract )
331
+ for enum in contract .enums :
332
+ if enum .name == type_expression_found .member_name :
333
+ type_found_in_expression = enum
334
+ type_found = UserDefinedType (enum )
335
+ break
336
+ else :
337
+ assert isinstance (type_expression_found , Identifier )
338
+ type_found_in_expression = type_expression_found .value
339
+ assert isinstance (type_found_in_expression , (EnumContract , EnumTopLevel ))
340
+ type_found = UserDefinedType (type_found_in_expression )
341
+ value = (
342
+ type_found_in_expression .max
343
+ if expression .member_name == "max"
344
+ else type_found_in_expression .min
345
+ )
346
+ set_val (expression , value )
347
+ return
348
+ elif isinstance (expression .expression , Identifier ) and isinstance (
349
+ expression .expression .value , Enum
350
+ ):
351
+ # Handle direct access to enum field
352
+ set_val (expression , expression .expression .value .values .index (expression .member_name ))
353
+ return
354
+ elif isinstance (expression .expression , Identifier ) and isinstance (
355
+ expression .expression .value , TypeAlias
356
+ ):
357
+ # User defined type .wrap call handled in _post_call_expression
358
+ return
359
+ elif (
360
+ isinstance (expression .expression .value , Contract )
361
+ and expression .member_name in expression .expression .value .variables_as_dict
362
+ and expression .expression .value .variables_as_dict [expression .member_name ].is_constant
363
+ ):
364
+ # Handles when a constant is accessed on another contract
365
+ variables = expression .expression .value .variables_as_dict
366
+ if isinstance (variables [expression .member_name ].expression , MemberAccess ):
367
+ self ._post_member_access (variables [expression .member_name ].expression )
368
+ set_val (expression , get_val (variables [expression .member_name ].expression ))
369
+ return
370
+
371
+ # If the variable is a Literal we convert its value to int
372
+ if isinstance (variables [expression .member_name ].expression , Literal ):
373
+ value = convert_string_to_int (
374
+ variables [expression .member_name ].expression .converted_value
375
+ )
376
+ # If the variable is a UnaryOperation we need convert its value to int
377
+ # and replacing possible spaces
378
+ elif isinstance (variables [expression .member_name ].expression , UnaryOperation ):
379
+ value = convert_string_to_int (
380
+ str (variables [expression .member_name ].expression ).replace (" " , "" )
381
+ )
382
+ else :
383
+ value = variables [expression .member_name ].expression
384
+
385
+ set_val (expression , value )
386
+ return
387
+
229
388
raise NotConstant
230
389
231
390
def _post_new_array (self , expression : expressions .NewArray ) -> None :
@@ -272,6 +431,7 @@ def _post_type_conversion(self, expression: expressions.TypeConversion) -> None:
272
431
TupleExpression ,
273
432
TypeConversion ,
274
433
CallExpression ,
434
+ MemberAccess ,
275
435
),
276
436
):
277
437
raise NotConstant
0 commit comments