Skip to content

Commit 9e89bbb

Browse files
authored
Merge pull request #2588 from crytic/dev-support-transient
Improve transient storage support
2 parents 8abf917 + ef03cb7 commit 9e89bbb

File tree

11 files changed

+152
-101
lines changed

11 files changed

+152
-101
lines changed

slither/core/compilation_unit.py

+40-20
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit
7373
# Memoize
7474
self._all_state_variables: Optional[Set[StateVariable]] = None
7575

76-
self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
76+
self._persistent_storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
77+
self._transient_storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
7778

7879
self._contract_with_missing_inheritance: Set[Contract] = set()
7980

@@ -297,33 +298,52 @@ def get_scope(self, filename_str: str) -> FileScope:
297298

298299
def compute_storage_layout(self) -> None:
299300
assert self.is_solidity
301+
300302
for contract in self.contracts_derived:
301-
self._storage_layouts[contract.name] = {}
302-
303-
slot = 0
304-
offset = 0
305-
for var in contract.stored_state_variables_ordered:
306-
assert var.type
307-
size, new_slot = var.type.storage_size
308-
309-
if new_slot:
310-
if offset > 0:
311-
slot += 1
312-
offset = 0
313-
elif size + offset > 32:
303+
self._compute_storage_layout(contract.name, contract.storage_variables_ordered, False)
304+
self._compute_storage_layout(contract.name, contract.transient_variables_ordered, True)
305+
306+
def _compute_storage_layout(
307+
self, contract_name: str, state_variables_ordered: List[StateVariable], is_transient: bool
308+
):
309+
if is_transient:
310+
self._transient_storage_layouts[contract_name] = {}
311+
else:
312+
self._persistent_storage_layouts[contract_name] = {}
313+
314+
slot = 0
315+
offset = 0
316+
for var in state_variables_ordered:
317+
assert var.type
318+
size, new_slot = var.type.storage_size
319+
320+
if new_slot:
321+
if offset > 0:
314322
slot += 1
315323
offset = 0
324+
elif size + offset > 32:
325+
slot += 1
326+
offset = 0
316327

317-
self._storage_layouts[contract.name][var.canonical_name] = (
328+
if is_transient:
329+
self._transient_storage_layouts[contract_name][var.canonical_name] = (
318330
slot,
319331
offset,
320332
)
321-
if new_slot:
322-
slot += math.ceil(size / 32)
323-
else:
324-
offset += size
333+
else:
334+
self._persistent_storage_layouts[contract_name][var.canonical_name] = (
335+
slot,
336+
offset,
337+
)
338+
339+
if new_slot:
340+
slot += math.ceil(size / 32)
341+
else:
342+
offset += size
325343

326344
def storage_layout_of(self, contract: Contract, var: StateVariable) -> Tuple[int, int]:
327-
return self._storage_layouts[contract.name][var.canonical_name]
345+
if var.is_stored:
346+
return self._persistent_storage_layouts[contract.name][var.canonical_name]
347+
return self._transient_storage_layouts[contract.name][var.canonical_name]
328348

329349
# endregion

slither/core/declarations/contract.py

+17-29
Original file line numberDiff line numberDiff line change
@@ -440,55 +440,43 @@ def variables_as_dict(self) -> Dict[str, "StateVariable"]:
440440
def state_variables(self) -> List["StateVariable"]:
441441
"""
442442
Returns all the accessible variables (do not include private variable from inherited contract).
443-
Use state_variables_ordered for all the variables following the storage order
443+
Use stored_state_variables_ordered for all the storage variables following the storage order
444+
Use transient_state_variables_ordered for all the transient variables following the storage order
444445
445446
list(StateVariable): List of the state variables.
446447
"""
447448
return list(self._variables.values())
448449

449450
@property
450-
def stored_state_variables(self) -> List["StateVariable"]:
451+
def state_variables_entry_points(self) -> List["StateVariable"]:
451452
"""
452-
Returns state variables with storage locations, excluding private variables from inherited contracts.
453-
Use stored_state_variables_ordered to access variables with storage locations in their declaration order.
454-
455-
This implementation filters out state variables if they are constant or immutable. It will be
456-
updated to accommodate any new non-storage keywords that might replace 'constant' and 'immutable' in the future.
457-
458-
Returns:
459-
List[StateVariable]: A list of state variables with storage locations.
453+
list(StateVariable): List of the state variables that are public.
460454
"""
461-
return [variable for variable in self.state_variables if variable.is_stored]
455+
return [var for var in self._variables.values() if var.visibility == "public"]
462456

463457
@property
464-
def stored_state_variables_ordered(self) -> List["StateVariable"]:
458+
def state_variables_ordered(self) -> List["StateVariable"]:
465459
"""
466-
list(StateVariable): List of the state variables with storage locations by order of declaration.
467-
468-
This implementation filters out state variables if they are constant or immutable. It will be
469-
updated to accommodate any new non-storage keywords that might replace 'constant' and 'immutable' in the future.
470-
471-
Returns:
472-
List[StateVariable]: A list of state variables with storage locations ordered by declaration.
460+
list(StateVariable): List of the state variables by order of declaration.
473461
"""
474-
return [variable for variable in self.state_variables_ordered if variable.is_stored]
462+
return self._variables_ordered
463+
464+
def add_state_variables_ordered(self, new_vars: List["StateVariable"]) -> None:
465+
self._variables_ordered += new_vars
475466

476467
@property
477-
def state_variables_entry_points(self) -> List["StateVariable"]:
468+
def storage_variables_ordered(self) -> List["StateVariable"]:
478469
"""
479-
list(StateVariable): List of the state variables that are public.
470+
list(StateVariable): List of the state variables in storage location by order of declaration.
480471
"""
481-
return [var for var in self._variables.values() if var.visibility == "public"]
472+
return [v for v in self._variables_ordered if v.is_stored]
482473

483474
@property
484-
def state_variables_ordered(self) -> List["StateVariable"]:
475+
def transient_variables_ordered(self) -> List["StateVariable"]:
485476
"""
486-
list(StateVariable): List of the state variables by order of declaration.
477+
list(StateVariable): List of the state variables in transient location by order of declaration.
487478
"""
488-
return list(self._variables_ordered)
489-
490-
def add_variables_ordered(self, new_vars: List["StateVariable"]) -> None:
491-
self._variables_ordered += new_vars
479+
return [v for v in self._variables_ordered if v.is_transient]
492480

493481
@property
494482
def state_variables_inherited(self) -> List["StateVariable"]:

slither/core/variables/state_variable.py

+16
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ def location(self) -> Optional[str]:
3535
"""
3636
return self._location
3737

38+
@property
39+
def is_stored(self) -> bool:
40+
"""
41+
Checks if the state variable is stored, based on it not being constant or immutable or transient.
42+
"""
43+
return (
44+
not self._is_constant and not self._is_immutable and not self._location == "transient"
45+
)
46+
47+
@property
48+
def is_transient(self) -> bool:
49+
"""
50+
Checks if the state variable is transient. A transient variable can not be constant or immutable.
51+
"""
52+
return self._location == "transient"
53+
3854
# endregion
3955
###################################################################################
4056
###################################################################################

slither/core/variables/variable.py

-7
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,6 @@ def is_constant(self) -> bool:
9393
def is_constant(self, is_cst: bool) -> None:
9494
self._is_constant = is_cst
9595

96-
@property
97-
def is_stored(self) -> bool:
98-
"""
99-
Checks if a variable is stored, based on it not being constant or immutable. Future updates may adjust for new non-storage keywords.
100-
"""
101-
return not self._is_constant and not self._is_immutable
102-
10396
@property
10497
def is_reentrant(self) -> bool:
10598
return self._is_reentrant

slither/detectors/variables/unchanged_state_variables.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def detect(self) -> None:
9292
variables = []
9393
functions = []
9494

95-
variables.append(c.stored_state_variables)
95+
variables.append(c.storage_variables_ordered)
9696
functions.append(c.all_functions_called)
9797

9898
valid_candidates: Set[StateVariable] = {

slither/printers/summary/variable_order.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,17 @@ def output(self, _filename: str) -> Output:
2727

2828
for contract in self.slither.contracts_derived:
2929
txt += f"\n{contract.name}:\n"
30-
table = MyPrettyTable(["Name", "Type", "Slot", "Offset"])
31-
for variable in contract.stored_state_variables_ordered:
30+
table = MyPrettyTable(["Name", "Type", "Slot", "Offset", "State"])
31+
for variable in contract.storage_variables_ordered:
3232
slot, offset = contract.compilation_unit.storage_layout_of(contract, variable)
33-
table.add_row([variable.canonical_name, str(variable.type), slot, offset])
33+
table.add_row(
34+
[variable.canonical_name, str(variable.type), slot, offset, "Storage"]
35+
)
36+
for variable in contract.transient_variables_ordered:
37+
slot, offset = contract.compilation_unit.storage_layout_of(contract, variable)
38+
table.add_row(
39+
[variable.canonical_name, str(variable.type), slot, offset, "Transient"]
40+
)
3441

3542
all_tables.append((contract.name, table))
3643
txt += str(table) + "\n"

slither/solc_parsing/declarations/contract.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ def parse_state_variables(self) -> None:
350350
if v.visibility != "private"
351351
}
352352
)
353-
self._contract.add_variables_ordered(
353+
self._contract.add_state_variables_ordered(
354354
[
355355
var
356356
for var in father.state_variables_ordered
@@ -370,7 +370,7 @@ def parse_state_variables(self) -> None:
370370
if var_parser.reference_id is not None:
371371
self._contract.state_variables_by_ref_id[var_parser.reference_id] = var
372372
self._contract.variables_as_dict[var.name] = var
373-
self._contract.add_variables_ordered([var])
373+
self._contract.add_state_variables_ordered([var])
374374

375375
def _parse_modifier(self, modifier_data: Dict) -> None:
376376
modif = Modifier(self._contract.compilation_unit)

slither/tools/upgradeability/checks/variable_initialization.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class VariableWithInit(AbstractCheck):
4343

4444
def _check(self) -> List[Output]:
4545
results = []
46-
for s in self.contract.stored_state_variables_ordered:
46+
for s in self.contract.storage_variables_ordered:
4747
if s.initialized:
4848
info: CHECK_INFO = [s, " is a state variable with an initial value.\n"]
4949
json = self.generate_result(info)

slither/tools/upgradeability/checks/variables_order.py

+60-33
Original file line numberDiff line numberDiff line change
@@ -115,29 +115,43 @@ def _contract2(self) -> Contract:
115115
def _check(self) -> List[Output]:
116116
contract1 = self._contract1()
117117
contract2 = self._contract2()
118-
order1 = contract1.stored_state_variables_ordered
119-
order2 = contract2.stored_state_variables_ordered
120118

121119
results: List[Output] = []
122-
for idx, _ in enumerate(order1):
123-
if len(order2) <= idx:
124-
# Handle by MissingVariable
125-
return results
126-
127-
variable1 = order1[idx]
128-
variable2 = order2[idx]
129-
if (variable1.name != variable2.name) or (variable1.type != variable2.type):
130-
info: CHECK_INFO = [
131-
"Different variables between ",
132-
contract1,
133-
" and ",
134-
contract2,
135-
"\n",
136-
]
137-
info += ["\t ", variable1, "\n"]
138-
info += ["\t ", variable2, "\n"]
139-
json = self.generate_result(info)
140-
results.append(json)
120+
121+
def _check_internal(
122+
contract1: Contract, contract2: Contract, results: List[Output], is_transient: bool
123+
):
124+
if is_transient:
125+
order1 = contract1.transient_variables_ordered
126+
order2 = contract2.transient_variables_ordered
127+
else:
128+
order1 = contract1.storage_variables_ordered
129+
order2 = contract2.storage_variables_ordered
130+
131+
for idx, _ in enumerate(order1):
132+
if len(order2) <= idx:
133+
# Handle by MissingVariable
134+
return
135+
136+
variable1 = order1[idx]
137+
variable2 = order2[idx]
138+
if (variable1.name != variable2.name) or (variable1.type != variable2.type):
139+
info: CHECK_INFO = [
140+
"Different variables between ",
141+
contract1,
142+
" and ",
143+
contract2,
144+
"\n",
145+
]
146+
info += ["\t ", variable1, "\n"]
147+
info += ["\t ", variable2, "\n"]
148+
json = self.generate_result(info)
149+
results.append(json)
150+
151+
# Checking state variables with storage location
152+
_check_internal(contract1, contract2, results, False)
153+
# Checking state variables with transient location
154+
_check_internal(contract1, contract2, results, True)
141155

142156
return results
143157

@@ -236,22 +250,35 @@ def _contract2(self) -> Contract:
236250
def _check(self) -> List[Output]:
237251
contract1 = self._contract1()
238252
contract2 = self._contract2()
239-
order1 = contract1.stored_state_variables_ordered
240-
order2 = contract2.stored_state_variables_ordered
241253

242-
results = []
254+
results: List[Output] = []
243255

244-
if len(order2) <= len(order1):
245-
return []
256+
def _check_internal(
257+
contract1: Contract, contract2: Contract, results: List[Output], is_transient: bool
258+
):
259+
if is_transient:
260+
order1 = contract1.transient_variables_ordered
261+
order2 = contract2.transient_variables_ordered
262+
else:
263+
order1 = contract1.storage_variables_ordered
264+
order2 = contract2.storage_variables_ordered
246265

247-
idx = len(order1)
266+
if len(order2) <= len(order1):
267+
return
248268

249-
while idx < len(order2):
250-
variable2 = order2[idx]
251-
info: CHECK_INFO = ["Extra variables in ", contract2, ": ", variable2, "\n"]
252-
json = self.generate_result(info)
253-
results.append(json)
254-
idx = idx + 1
269+
idx = len(order1)
270+
271+
while idx < len(order2):
272+
variable2 = order2[idx]
273+
info: CHECK_INFO = ["Extra variables in ", contract2, ": ", variable2, "\n"]
274+
json = self.generate_result(info)
275+
results.append(json)
276+
idx = idx + 1
277+
278+
# Checking state variables with storage location
279+
_check_internal(contract1, contract2, results, False)
280+
# Checking state variables with transient location
281+
_check_internal(contract1, contract2, results, True)
255282

256283
return results
257284

slither/utils/upgradeability.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ def compare(
8080
tainted-contracts: list[TaintedExternalContract]
8181
"""
8282

83-
order_vars1 = v1.stored_state_variables_ordered
84-
order_vars2 = v2.stored_state_variables_ordered
83+
order_vars1 = v1.storage_variables_ordered + v1.transient_variables_ordered
84+
order_vars2 = v2.storage_variables_ordered + v2.transient_variables_ordered
8585
func_sigs1 = [function.solidity_signature for function in v1.functions]
8686
func_sigs2 = [function.solidity_signature for function in v2.functions]
8787

@@ -306,8 +306,8 @@ def get_missing_vars(v1: Contract, v2: Contract) -> List[StateVariable]:
306306
List of StateVariables from v1 missing in v2
307307
"""
308308
results = []
309-
order_vars1 = v1.stored_state_variables_ordered
310-
order_vars2 = v2.stored_state_variables_ordered
309+
order_vars1 = v1.storage_variables_ordered + v1.transient_variables_ordered
310+
order_vars2 = v2.storage_variables_ordered + v2.transient_variables_ordered
311311
if len(order_vars2) < len(order_vars1):
312312
for variable in order_vars1:
313313
if variable.name not in [v.name for v in order_vars2]:

slither/vyper_parsing/declarations/contract.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ def parse_state_variables(self) -> None:
470470

471471
assert var.name
472472
self._contract.variables_as_dict[var.name] = var
473-
self._contract.add_variables_ordered([var])
473+
self._contract.add_state_variables_ordered([var])
474474
# Interfaces can refer to constants
475475
self._contract.file_scope.variables[var.name] = var
476476

0 commit comments

Comments
 (0)