Skip to content

Commit

Permalink
Merge pull request #507 from crytic/feature/storage-slots
Browse files Browse the repository at this point in the history
Implement support for calculating storage layout
  • Loading branch information
montyly authored Jul 12, 2020
2 parents 9ed1cb1 + 1b82101 commit a23a137
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 12 deletions.
4 changes: 2 additions & 2 deletions slither/core/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __init__(self):
self._structures: Dict[str, "Structure"] = {}
self._events: Dict[str, "Event"] = {}
self._variables: Dict[str, "StateVariable"] = {}
self._variables_ordered: List["StateVariable"] = [] # contain also shadowed variables
self._variables_ordered: List["StateVariable"] = []
self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "Function"] = {}
self._linearizedBaseContracts = List[int]
Expand Down Expand Up @@ -255,7 +255,7 @@ def state_variables(self) -> List["StateVariable"]:
@property
def state_variables_ordered(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables by order of declaration. Contains also shadowed variables
list(StateVariable): List of the state variables by order of declaration.
"""
return list(self._variables_ordered)

Expand Down
40 changes: 39 additions & 1 deletion slither/core/slither_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import logging
import json
import re
import math
from collections import defaultdict
from typing import Optional, Dict, List, Set, Union
from typing import Optional, Dict, List, Set, Union, Tuple

from crytic_compile import CryticCompile

Expand Down Expand Up @@ -56,6 +57,8 @@ def __init__(self):
self._contract_name_collisions = defaultdict(list)
self._contract_with_missing_inheritance = set()

self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}

###################################################################################
###################################################################################
# region Source code
Expand Down Expand Up @@ -363,3 +366,38 @@ def contracts_with_missing_inheritance(self) -> Set:
return self._contract_with_missing_inheritance

# endregion
###################################################################################
###################################################################################
# region Storage Layouts
###################################################################################
###################################################################################

def compute_storage_layout(self):
for contract in self.contracts_derived:
self._storage_layouts[contract.name] = {}

slot = 0
offset = 0
for var in contract.state_variables_ordered:
if var.is_constant:
continue

size, new_slot = var.type.storage_size

if new_slot:
if offset > 0:
slot += 1
offset = 0
elif size + offset > 32:
slot += 1
offset = 0

self._storage_layouts[contract.name][var.canonical_name] = (slot, offset)
if new_slot:
slot += math.ceil(size / 32)
else:
offset += size

def storage_layout_of(self, contract, var) -> Tuple[int, int]:
return self._storage_layouts[contract.name][var.canonical_name]
# endregion
9 changes: 8 additions & 1 deletion slither/core/solidity_types/array_type.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Optional, Tuple

from slither.core.expressions import Literal
from slither.core.expressions.expression import Expression
Expand Down Expand Up @@ -37,6 +37,13 @@ def length(self) -> Optional[Expression]:
def lenght_value(self) -> Optional[Literal]:
return self._length_value

@property
def storage_size(self) -> Tuple[int, bool]:
if self._length_value:
elem_size, _ = self._type.storage_size
return elem_size * int(self._length_value.value), True
return 32, True

def __str__(self):
if self._length:
return str(self._type) + "[{}]".format(str(self._length_value))
Expand Down
9 changes: 8 additions & 1 deletion slither/core/solidity_types/elementary_type.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import itertools
from typing import Optional
from typing import Optional, Tuple

from slither.core.solidity_types.type import Type

Expand Down Expand Up @@ -172,6 +172,13 @@ def size(self) -> Optional[int]:
return int(t[len("bytes") :])
return None

@property
def storage_size(self) -> Tuple[int, bool]:
if self._type == 'string' or self._type == 'bytes':
return 32, True

return int(self.size / 8), False

def __str__(self):
return self._type

Expand Down
6 changes: 5 additions & 1 deletion slither/core/solidity_types/function_type.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import List, Tuple

from slither.core.solidity_types.type import Type
from slither.core.variables.function_type_variable import FunctionTypeVariable
Expand Down Expand Up @@ -26,6 +26,10 @@ def return_values(self) -> List[FunctionTypeVariable]:
def return_type(self) -> List[Type]:
return [x.type for x in self.return_values]

@property
def storage_size(self) -> Tuple[int, bool]:
return 24, False

def __str__(self):
# Use x.type
# x.name may be empty
Expand Down
6 changes: 6 additions & 0 deletions slither/core/solidity_types/mapping_type.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Tuple

from slither.core.solidity_types.type import Type


Expand All @@ -17,6 +19,10 @@ def type_from(self) -> Type:
def type_to(self) -> Type:
return self._to

@property
def storage_size(self) -> Tuple[int, bool]:
return 32, True

def __str__(self):
return "mapping({} => {})".format(str(self._from), str(self._to))

Expand Down
14 changes: 12 additions & 2 deletions slither/core/solidity_types/type.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import abc
from typing import Tuple

from slither.core.source_mapping.source_mapping import SourceMapping

class Type(SourceMapping,metaclass=abc.ABCMeta):
@property
@abc.abstractmethod
def storage_size(self) -> Tuple[int, bool]:
"""
Computes and returns storage layout related metadata
class Type(SourceMapping):
pass
:return: (int, bool) - the number of bytes this type will require, and whether it must start in
a new slot regardless of whether the current slot can still fit it
"""
35 changes: 34 additions & 1 deletion slither/core/solidity_types/user_defined_type.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Union, TYPE_CHECKING
from typing import Union, TYPE_CHECKING, Tuple
import math

from slither.core.solidity_types.type import Type

Expand All @@ -22,6 +23,38 @@ def __init__(self, t):
def type(self) -> Union["Contract", "Enum", "Structure"]:
return self._type

@property
def storage_size(self) -> Tuple[int, bool]:
from slither.core.declarations.structure import Structure
from slither.core.declarations.enum import Enum
from slither.core.declarations.contract import Contract

if isinstance(self._type, Contract):
return 20, False
elif isinstance(self._type, Enum):
return int(math.ceil(math.log2(len(self._type.values)) / 8)), False
elif isinstance(self._type, Structure):
# todo there's some duplicate logic here and slither_core, can we refactor this?
slot = 0
offset = 0
for elem in self._type.elems_ordered:
size, new_slot = elem.type.storage_size
if new_slot:
if offset > 0:
slot += 1
offset = 0
elif size + offset > 32:
slot += 1
offset = 0

if new_slot:
slot += math.ceil(size / 32)
else:
offset += size
if offset > 0:
slot += 1
return slot * 32, True

def __str__(self):
from slither.core.declarations.structure import Structure
from slither.core.declarations.enum import Enum
Expand Down
5 changes: 3 additions & 2 deletions slither/printers/summary/variable_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ def output(self, _filename):

for contract in self.slither.contracts_derived:
txt += '\n{}:\n'.format(contract.name)
table = MyPrettyTable(['Name', 'Type'])
table = MyPrettyTable(['Name', 'Type', 'Slot', 'Offset'])
for variable in contract.state_variables_ordered:
if not variable.is_constant:
table.add_row([variable.canonical_name, str(variable.type)])
slot, offset = self.slither.storage_layout_of(contract, variable)
table.add_row([variable.canonical_name, str(variable.type), slot, offset])

all_tables.append((contract.name, table))
txt += str(table) + '\n'
Expand Down
3 changes: 2 additions & 1 deletion slither/solc_parsing/declarations/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,8 @@ def parse_structs(self):
def parse_state_variables(self):
for father in self._contract.inheritance_reverse:
self._contract.variables_as_dict.update(father.variables_as_dict)
self._contract.add_variables_ordered(father.state_variables_ordered)
self._contract.add_variables_ordered(
[var for var in father.state_variables_ordered if var not in self._contract.state_variables_ordered])

for varNotParsed in self._variablesNotParsed:
var = StateVariable()
Expand Down
1 change: 1 addition & 0 deletions slither/solc_parsing/slitherSolc.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ def analyze_contracts(self):
self._convert_to_slithir()

compute_dependency(self._core)
self._core.compute_storage_layout()

def _analyze_all_enums(self, contracts_to_be_analyzed: List[ContractSolc]):
while contracts_to_be_analyzed:
Expand Down

0 comments on commit a23a137

Please sign in to comment.