From f3485c5f625754feaa0f51239b7c86c979ccbc9a Mon Sep 17 00:00:00 2001 From: samczsun Date: Mon, 15 Jun 2020 20:13:27 -0400 Subject: [PATCH 1/3] Implement support for calculating storage layout --- slither/core/slither_core.py | 38 +++++++++++++++++++ slither/core/solidity_types/array_type.py | 7 ++++ .../core/solidity_types/elementary_type.py | 7 ++++ slither/core/solidity_types/function_type.py | 4 ++ slither/core/solidity_types/mapping_type.py | 4 ++ slither/core/solidity_types/type.py | 14 ++++++- .../core/solidity_types/user_defined_type.py | 33 ++++++++++++++++ slither/printers/summary/variable_order.py | 5 ++- slither/solc_parsing/declarations/contract.py | 3 +- slither/solc_parsing/slitherSolc.py | 1 + 10 files changed, 111 insertions(+), 5 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index cb4a5fd049..c42503ca9c 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -5,6 +5,7 @@ import logging import json import re +import math from collections import defaultdict from typing import Optional, Dict, List, Set, Union @@ -56,6 +57,8 @@ def __init__(self): self._contract_name_collisions = defaultdict(list) self._contract_with_missing_inheritance = set() + self._storage_layouts = {} + ################################################################################### ################################################################################### # region Source code @@ -345,3 +348,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): + return self._storage_layouts[contract.name][var.canonical_name] + # endregion diff --git a/slither/core/solidity_types/array_type.py b/slither/core/solidity_types/array_type.py index 7022539a7f..aba611b8cb 100644 --- a/slither/core/solidity_types/array_type.py +++ b/slither/core/solidity_types/array_type.py @@ -37,6 +37,13 @@ def length(self) -> Optional[Expression]: def lenght_value(self) -> Optional[Literal]: return self._length_value + @property + def storage_size(self): + 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)) diff --git a/slither/core/solidity_types/elementary_type.py b/slither/core/solidity_types/elementary_type.py index 474f2a4524..16c25e2f0d 100644 --- a/slither/core/solidity_types/elementary_type.py +++ b/slither/core/solidity_types/elementary_type.py @@ -172,6 +172,13 @@ def size(self) -> Optional[int]: return int(t[len("bytes") :]) return None + @property + def storage_size(self): + if self._type == 'string' or self._type == 'bytes': + return 32, True + + return int(self.size / 8), False + def __str__(self): return self._type diff --git a/slither/core/solidity_types/function_type.py b/slither/core/solidity_types/function_type.py index e1d20d68a2..a666a35216 100644 --- a/slither/core/solidity_types/function_type.py +++ b/slither/core/solidity_types/function_type.py @@ -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): + return 24, False + def __str__(self): # Use x.type # x.name may be empty diff --git a/slither/core/solidity_types/mapping_type.py b/slither/core/solidity_types/mapping_type.py index 7c4b8c2bb8..815f2dd783 100644 --- a/slither/core/solidity_types/mapping_type.py +++ b/slither/core/solidity_types/mapping_type.py @@ -17,6 +17,10 @@ def type_from(self) -> Type: def type_to(self) -> Type: return self._to + @property + def storage_size(self): + return 32, True + def __str__(self): return "mapping({} => {})".format(str(self._from), str(self._to)) diff --git a/slither/core/solidity_types/type.py b/slither/core/solidity_types/type.py index e06c7bf0da..8042fcc7b7 100644 --- a/slither/core/solidity_types/type.py +++ b/slither/core/solidity_types/type.py @@ -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 + """ diff --git a/slither/core/solidity_types/user_defined_type.py b/slither/core/solidity_types/user_defined_type.py index eeb38b670b..dc3c1dd8cf 100644 --- a/slither/core/solidity_types/user_defined_type.py +++ b/slither/core/solidity_types/user_defined_type.py @@ -1,4 +1,5 @@ from typing import Union, TYPE_CHECKING +import math from slither.core.solidity_types.type import Type @@ -22,6 +23,38 @@ def __init__(self, t): def type(self) -> Union["Contract", "Enum", "Structure"]: return self._type + @property + def storage_size(self): + 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 diff --git a/slither/printers/summary/variable_order.py b/slither/printers/summary/variable_order.py index 93c04fe497..d00fd7f56b 100644 --- a/slither/printers/summary/variable_order.py +++ b/slither/printers/summary/variable_order.py @@ -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' diff --git a/slither/solc_parsing/declarations/contract.py b/slither/solc_parsing/declarations/contract.py index f7d001fe8b..d348cdccc1 100644 --- a/slither/solc_parsing/declarations/contract.py +++ b/slither/solc_parsing/declarations/contract.py @@ -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() diff --git a/slither/solc_parsing/slitherSolc.py b/slither/solc_parsing/slitherSolc.py index d5de1b56ad..e6f5d0df4f 100644 --- a/slither/solc_parsing/slitherSolc.py +++ b/slither/solc_parsing/slitherSolc.py @@ -344,6 +344,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: From 075db34267c6a20c8e16e98948a6b56a843e0ffe Mon Sep 17 00:00:00 2001 From: samczsun Date: Tue, 23 Jun 2020 18:16:36 -0400 Subject: [PATCH 2/3] typings --- slither/core/slither_core.py | 6 +++--- slither/core/solidity_types/array_type.py | 4 ++-- slither/core/solidity_types/elementary_type.py | 4 ++-- slither/core/solidity_types/function_type.py | 4 ++-- slither/core/solidity_types/mapping_type.py | 4 +++- slither/core/solidity_types/user_defined_type.py | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/slither/core/slither_core.py b/slither/core/slither_core.py index c42503ca9c..a67c68bc67 100644 --- a/slither/core/slither_core.py +++ b/slither/core/slither_core.py @@ -7,7 +7,7 @@ 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 @@ -57,7 +57,7 @@ def __init__(self): self._contract_name_collisions = defaultdict(list) self._contract_with_missing_inheritance = set() - self._storage_layouts = {} + self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {} ################################################################################### ################################################################################### @@ -380,6 +380,6 @@ def compute_storage_layout(self): else: offset += size - def storage_layout_of(self, contract, var): + def storage_layout_of(self, contract, var) -> Tuple[int, int]: return self._storage_layouts[contract.name][var.canonical_name] # endregion diff --git a/slither/core/solidity_types/array_type.py b/slither/core/solidity_types/array_type.py index aba611b8cb..966d1b537b 100644 --- a/slither/core/solidity_types/array_type.py +++ b/slither/core/solidity_types/array_type.py @@ -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 @@ -38,7 +38,7 @@ def lenght_value(self) -> Optional[Literal]: return self._length_value @property - def storage_size(self): + 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 diff --git a/slither/core/solidity_types/elementary_type.py b/slither/core/solidity_types/elementary_type.py index 16c25e2f0d..6f1d7a8b5a 100644 --- a/slither/core/solidity_types/elementary_type.py +++ b/slither/core/solidity_types/elementary_type.py @@ -1,5 +1,5 @@ import itertools -from typing import Optional +from typing import Optional, Tuple from slither.core.solidity_types.type import Type @@ -173,7 +173,7 @@ def size(self) -> Optional[int]: return None @property - def storage_size(self): + def storage_size(self) -> Tuple[int, bool]: if self._type == 'string' or self._type == 'bytes': return 32, True diff --git a/slither/core/solidity_types/function_type.py b/slither/core/solidity_types/function_type.py index a666a35216..6a96bf368f 100644 --- a/slither/core/solidity_types/function_type.py +++ b/slither/core/solidity_types/function_type.py @@ -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 @@ -27,7 +27,7 @@ def return_type(self) -> List[Type]: return [x.type for x in self.return_values] @property - def storage_size(self): + def storage_size(self) -> Tuple[int, bool]: return 24, False def __str__(self): diff --git a/slither/core/solidity_types/mapping_type.py b/slither/core/solidity_types/mapping_type.py index 815f2dd783..e2f8ebe302 100644 --- a/slither/core/solidity_types/mapping_type.py +++ b/slither/core/solidity_types/mapping_type.py @@ -1,3 +1,5 @@ +from typing import Tuple + from slither.core.solidity_types.type import Type @@ -18,7 +20,7 @@ def type_to(self) -> Type: return self._to @property - def storage_size(self): + def storage_size(self) -> Tuple[int, bool]: return 32, True def __str__(self): diff --git a/slither/core/solidity_types/user_defined_type.py b/slither/core/solidity_types/user_defined_type.py index dc3c1dd8cf..4aed394e8d 100644 --- a/slither/core/solidity_types/user_defined_type.py +++ b/slither/core/solidity_types/user_defined_type.py @@ -1,4 +1,4 @@ -from typing import Union, TYPE_CHECKING +from typing import Union, TYPE_CHECKING, Tuple import math from slither.core.solidity_types.type import Type @@ -24,7 +24,7 @@ def type(self) -> Union["Contract", "Enum", "Structure"]: return self._type @property - def storage_size(self): + 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 From 1b8210159ef2f8b74525df6af82ba5686f4133c8 Mon Sep 17 00:00:00 2001 From: Josselin Date: Sun, 12 Jul 2020 11:19:17 +0200 Subject: [PATCH 3/3] Update documentation of state_variables_ordered (breaking change) --- slither/core/declarations/contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/slither/core/declarations/contract.py b/slither/core/declarations/contract.py index 992e2d8531..18ed5ac103 100644 --- a/slither/core/declarations/contract.py +++ b/slither/core/declarations/contract.py @@ -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] @@ -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)