@@ -94,10 +94,13 @@ def __init__(self, contracts: list[Contract], max_depth: int, rpc_info: RpcInfo
9494 self ._slot_info : dict [str , SlotInfo ] = {}
9595 self ._target_variables : list [tuple [Contract , StateVariable ]] = []
9696 self ._constant_storage_slots : list [tuple [Contract , StateVariable ]] = []
97+ self ._immutable_variables : list [tuple [Contract , StateVariable ]] = []
98+ self ._constant_variables : list [tuple [Contract , StateVariable ]] = []
9799 self .rpc_info : RpcInfo | None = rpc_info
98100 self .storage_address : str | None = None
99101 self .table : MyPrettyTable | None = None
100102 self .unstructured : bool = False
103+ self .include_immutable : bool = False
101104
102105 @property
103106 def contracts (self ) -> list [Contract ]:
@@ -130,9 +133,19 @@ def target_variables(self) -> list[tuple[Contract, StateVariable]]:
130133
131134 @property
132135 def constant_slots (self ) -> list [tuple [Contract , StateVariable ]]:
133- """Constant bytes32 variables and their associated contract."""
136+ """Constant bytes32 variables (unstructured storage slots) and their associated contract."""
134137 return self ._constant_storage_slots
135138
139+ @property
140+ def immutable_variables (self ) -> list [tuple [Contract , StateVariable ]]:
141+ """Immutable variables and their associated contract."""
142+ return self ._immutable_variables
143+
144+ @property
145+ def constant_variables (self ) -> list [tuple [Contract , StateVariable ]]:
146+ """Constant variables and their associated contract."""
147+ return self ._constant_variables
148+
136149 @property
137150 def slot_info (self ) -> dict [str , SlotInfo ]:
138151 """Contains the location, type, size, offset, and value of contract slots."""
@@ -154,6 +167,8 @@ def get_storage_layout(self) -> None:
154167 tmp [var .name ].elems = elems
155168 if self .unstructured :
156169 tmp .update (self .get_unstructured_layout ())
170+ if self .include_immutable :
171+ tmp .update (self .get_immutable_constant_layout ())
157172 self ._slot_info = tmp
158173
159174 def get_unstructured_layout (self ) -> dict [str , SlotInfo ]:
@@ -194,6 +209,146 @@ def get_unstructured_layout(self) -> dict[str, SlotInfo]:
194209 continue
195210 return tmp
196211
212+ def get_immutable_constant_layout (self ) -> dict [str , SlotInfo ]:
213+ """
214+ Retrieves the layout for immutable and constant variables.
215+ These variables don't have storage slots - their values are either:
216+ - Embedded in bytecode (immutable)
217+ - Computed at compile time (constant)
218+
219+ For public variables, values can be retrieved via RPC by calling the getter.
220+ For private/internal, we extract from the expression if possible.
221+ """
222+ tmp : dict [str , SlotInfo ] = {}
223+
224+ # Process immutable variables
225+ for contract , var in self .immutable_variables :
226+ var_name = var .name
227+ type_ = var .type
228+ type_string = str (type_ ) if type_ else "unknown"
229+ byte_size , _ = type_ .storage_size if type_ else (32 , 0 )
230+ size = byte_size * 8
231+
232+ value = self ._get_immutable_value (var )
233+
234+ tmp [var_name ] = SlotInfo (
235+ name = var_name ,
236+ type_string = f"{ type_string } (immutable)" ,
237+ slot = - 1 , # No storage slot
238+ size = size ,
239+ offset = 0 ,
240+ value = value ,
241+ )
242+ self .log += f"\n Immutable: { var_name } \n Type: { type_string } \n Value: { value } \n "
243+ logger .info (self .log )
244+ self .log = ""
245+
246+ # Process constant variables
247+ for contract , var in self .constant_variables :
248+ var_name = var .name
249+ type_ = var .type
250+ type_string = str (type_ ) if type_ else "unknown"
251+ byte_size , _ = type_ .storage_size if type_ else (32 , 0 )
252+ size = byte_size * 8
253+
254+ value = self ._get_constant_value (var )
255+
256+ tmp [var_name ] = SlotInfo (
257+ name = var_name ,
258+ type_string = f"{ type_string } (constant)" ,
259+ slot = - 1 , # No storage slot
260+ size = size ,
261+ offset = 0 ,
262+ value = value ,
263+ )
264+ self .log += f"\n Constant: { var_name } \n Type: { type_string } \n Value: { value } \n "
265+ logger .info (self .log )
266+ self .log = ""
267+
268+ return tmp
269+
270+ def _get_immutable_value (self , var : StateVariable ) -> int | bool | str | ChecksumAddress | None :
271+ """
272+ Retrieves the value of an immutable variable.
273+ For public immutables, calls the getter via RPC.
274+ For private/internal, returns None (would require bytecode analysis).
275+ """
276+ # Private/internal immutables cannot be retrieved without bytecode analysis
277+ if var .visibility != "public" :
278+ logger .debug (
279+ f"Cannot retrieve value for { var .visibility } immutable { var .name } "
280+ "(would require bytecode analysis)"
281+ )
282+ return None
283+
284+ # Try to get value via RPC for public variables
285+ if self .rpc_info :
286+ try :
287+ # Call the getter function
288+ func_signature = f"{ var .name } ()"
289+ selector = keccak (func_signature .encode ())[:4 ]
290+ result = self .rpc_info .web3 .eth .call (
291+ {"to" : self .checksum_address , "data" : "0x" + selector .hex ()},
292+ self .rpc_info .block ,
293+ )
294+ if result :
295+ type_ = var .type
296+ if isinstance (type_ , ElementaryType ):
297+ return self .convert_value_to_type (result , type_ .size , 0 , type_ .name )
298+ except (ValueError , TypeError ) as e :
299+ logger .warning (f"Could not retrieve immutable { var .name } via RPC: { e } " )
300+
301+ return None
302+
303+ def _get_constant_value (self , var : StateVariable ) -> int | bool | str | ChecksumAddress | None :
304+ """
305+ Retrieves the value of a constant variable from its expression.
306+ """
307+ if var .expression is None :
308+ return None
309+
310+ try :
311+ exp = var .expression
312+ type_ = var .type
313+
314+ # Try constant folding for complex expressions
315+ if isinstance (
316+ exp ,
317+ (
318+ BinaryOperation ,
319+ UnaryOperation ,
320+ Identifier ,
321+ TupleExpression ,
322+ TypeConversion ,
323+ CallExpression ,
324+ ),
325+ ):
326+ type_str = str (type_ ) if type_ else "uint256"
327+ exp = ConstantFolding (exp , type_str ).result ()
328+
329+ if isinstance (exp , Literal ):
330+ value = exp .value
331+ if isinstance (type_ , ElementaryType ):
332+ type_name = type_ .name
333+ # Handle different types
334+ if "int" in type_name :
335+ return int (value ) if isinstance (value , str ) else value
336+ if type_name == "bool" :
337+ return value .lower () == "true" if isinstance (value , str ) else bool (value )
338+ if type_name == "address" :
339+ return to_checksum_address (value ) if value else None
340+ if "bytes" in type_name :
341+ return value if isinstance (value , str ) else hex (value )
342+ return str (value )
343+ return str (value )
344+ except NotConstant :
345+ logger .debug (f"Could not fold constant { var .name } : expression is not constant" )
346+ except (ValueError , TypeError , AttributeError ) as e :
347+ logger .debug (f"Could not fold constant { var .name } : { e } " )
348+
349+ # Return raw expression as string if we can't fold it
350+ return str (var .expression ) if var .expression else None
351+
197352 def get_storage_slot (
198353 self ,
199354 target_variable : StateVariable ,
@@ -373,6 +528,10 @@ def get_slot_values(self, slot_info: SlotInfo) -> None:
373528 Fetches the slot value of `SlotInfo` object
374529 :param slot_info:
375530 """
531+ # Skip immutable/constant variables (slot=-1) - their values are already set
532+ if slot_info .slot == - 1 :
533+ return
534+
376535 assert self .rpc_info is not None
377536 hex_bytes = get_storage_data (
378537 self .rpc_info .web3 ,
@@ -396,12 +555,15 @@ def get_all_storage_variables(self, func: Callable = lambda x: x) -> None:
396555 if func (var ):
397556 if var .is_stored :
398557 self ._target_variables .append ((contract , var ))
399- elif (
400- self .unstructured
401- and var .is_constant
402- and var .type == ElementaryType ("bytes32" )
403- ):
404- self ._constant_storage_slots .append ((contract , var ))
558+ elif var .is_immutable and self .include_immutable :
559+ self ._immutable_variables .append ((contract , var ))
560+ elif var .is_constant :
561+ if self .include_immutable :
562+ # Capture all constants for display
563+ self ._constant_variables .append ((contract , var ))
564+ if self .unstructured and var .type == ElementaryType ("bytes32" ):
565+ # Also add bytes32 constants to unstructured slots
566+ self ._constant_storage_slots .append ((contract , var ))
405567 if self .unstructured :
406568 hardcoded_slot = self .find_hardcoded_slot_in_fallback (contract )
407569 if hardcoded_slot is not None :
0 commit comments