Merge pull request #1311 from crytic/dev-fix-read-storage

Multiple improvements to slither-read-storage
pull/1343/head
Feist Josselin 2 years ago committed by GitHub
commit ce9dbf650d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitignore
  2. 6
      slither/core/solidity_types/elementary_type.py
  3. 43
      slither/tools/read_storage/__main__.py
  4. 365
      slither/tools/read_storage/read_storage.py
  5. 6
      slither/tools/read_storage/utils/__init__.py
  6. 66
      slither/tools/read_storage/utils/utils.py
  7. 259
      tests/storage-layout/TEST_storage_layout.json
  8. 16
      tests/test_read_storage.py

3
.gitignore vendored

@ -107,3 +107,6 @@ ENV/
# Test results # Test results
test_artifacts/ test_artifacts/
# crytic export
crytic-export/

@ -1,5 +1,5 @@
import itertools import itertools
from typing import Optional, Tuple from typing import Tuple
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
@ -176,7 +176,7 @@ class ElementaryType(Type):
return self.type return self.type
@property @property
def size(self) -> Optional[int]: def size(self) -> int:
""" """
Return the size in bits Return the size in bits
Return None if the size is not known Return None if the size is not known
@ -194,7 +194,7 @@ class ElementaryType(Type):
return int(160) return int(160)
if t.startswith("bytes") and t != "bytes": if t.startswith("bytes") and t != "bytes":
return int(t[len("bytes") :]) * 8 return int(t[len("bytes") :]) * 8
return None raise SlitherException(f"{t} does not have a size")
@property @property
def storage_size(self) -> Tuple[int, bool]: def storage_size(self) -> Tuple[int, bool]:

@ -23,7 +23,7 @@ def parse_args() -> argparse.Namespace:
+ "To retrieve a contract's storage layout:\n" + "To retrieve a contract's storage layout:\n"
+ "\tslither-read-storage $TARGET address --contract-name $NAME --layout\n" + "\tslither-read-storage $TARGET address --contract-name $NAME --layout\n"
+ "To retrieve a contract's storage layout and values:\n" + "To retrieve a contract's storage layout and values:\n"
+ "\tslither-read-storage $TARGET address --contract-name $NAME --layout --values\n" + "\tslither-read-storage $TARGET address --contract-name $NAME --layout --value\n"
+ "TARGET can be a contract address or project directory" + "TARGET can be a contract address or project directory"
), ),
) )
@ -73,9 +73,9 @@ def parse_args() -> argparse.Namespace:
) )
parser.add_argument( parser.add_argument(
"--layout", "--json",
action="store_true", action="store",
help="Toggle used to write a JSON file with the entire storage layout.", help="Save the result in a JSON file.",
) )
parser.add_argument( parser.add_argument(
@ -84,6 +84,18 @@ def parse_args() -> argparse.Namespace:
help="Toggle used to include values in output.", help="Toggle used to include values in output.",
) )
parser.add_argument(
"--table",
action="store_true",
help="Print table view of storage layout",
)
parser.add_argument(
"--silent",
action="store_true",
help="Silence log outputs",
)
parser.add_argument("--max-depth", help="Max depth to search in data structure.", default=20) parser.add_argument("--max-depth", help="Max depth to search in data structure.", default=20)
cryticparser.init(parser) cryticparser.init(parser)
@ -120,25 +132,28 @@ def main() -> None:
srs.rpc = args.rpc_url srs.rpc = args.rpc_url
if args.layout: if args.variable_name:
srs.get_all_storage_variables()
srs.get_storage_layout()
else:
assert args.variable_name
# Use a lambda func to only return variables that have same name as target. # Use a lambda func to only return variables that have same name as target.
# x is a tuple (`Contract`, `StateVariable`). # x is a tuple (`Contract`, `StateVariable`).
srs.get_all_storage_variables(lambda x: bool(x[1].name == args.variable_name)) srs.get_all_storage_variables(lambda x: bool(x[1].name == args.variable_name))
srs.get_target_variables(**vars(args)) srs.get_target_variables(**vars(args))
else:
srs.get_all_storage_variables()
srs.get_storage_layout()
# To retrieve slot values an rpc url is required. # To retrieve slot values an rpc url is required.
if args.value: if args.value:
assert args.rpc_url assert args.rpc_url
srs.get_slot_values() srs.walk_slot_info(srs.get_slot_values)
if args.table:
srs.walk_slot_info(srs.convert_slot_info_to_rows)
print(srs.table)
# Only write file if storage layout is used. if args.json:
if len(srs.slot_info) > 1: with open(args.json, "w", encoding="utf-8") as file:
with open("storage_layout.json", "w", encoding="utf-8") as file: slot_infos_json = srs.to_json()
json.dump(srs.slot_info, file, indent=4) json.dump(slot_infos_json, file, indent=4)
if __name__ == "__main__": if __name__ == "__main__":

@ -1,27 +1,14 @@
import sys
import logging import logging
import sys
from math import floor from math import floor
from typing import Callable, Optional, Tuple, Union, List, Dict, Any
from typing import Callable, Optional, Tuple, Union, List, Dict
try:
from typing import TypedDict
except ImportError:
# < Python 3.8
from typing_extensions import TypedDict
try: try:
from web3 import Web3 from web3 import Web3
from eth_typing.evm import ChecksumAddress from eth_typing.evm import ChecksumAddress
from eth_abi import decode_single, encode_abi from eth_abi import decode_single, encode_abi
from eth_utils import keccak from eth_utils import keccak
from hexbytes import HexBytes
from .utils import ( from .utils import (
is_elementary,
is_array,
is_mapping,
is_struct,
is_user_defined_type,
get_offset_value, get_offset_value,
get_storage_data, get_storage_data,
coerce_type, coerce_type,
@ -31,25 +18,32 @@ except ImportError:
print("$ pip3 install web3 --user\n") print("$ pip3 install web3 --user\n")
sys.exit(-1) sys.exit(-1)
import dataclasses
from slither.utils.myprettytable import MyPrettyTable
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
from slither.core.solidity_types import ArrayType from slither.core.solidity_types import ArrayType, ElementaryType, UserDefinedType, MappingType
from slither.core.declarations import Contract, StructureContract from slither.core.declarations import Contract, Structure
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
from slither.core.variables.structure_variable import StructureVariable from slither.core.variables.structure_variable import StructureVariable
logging.basicConfig() logging.basicConfig()
logger = logging.getLogger("Slither-read-storage") logger = logging.getLogger("Slither-read-storage")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
Elem = Dict[str, "SlotInfo"]
NestedElem = Dict[str, Elem]
class SlotInfo(TypedDict): @dataclasses.dataclass
class SlotInfo:
name: str
type_string: str type_string: str
slot: int slot: int
size: int size: int
offset: int offset: int
value: Optional[Union[int, bool, str, ChecksumAddress, hex]] value: Optional[Union[int, bool, str, ChecksumAddress]] = None
elems: Optional[TypedDict] # same types as SlotInfo # For structure and array, str->SlotInfo
elems: Union[Elem, NestedElem] = dataclasses.field(default_factory=lambda: {}) # type: ignore[assignment]
class SlitherReadStorageException(Exception): class SlitherReadStorageException(Exception):
@ -58,16 +52,17 @@ class SlitherReadStorageException(Exception):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class SlitherReadStorage: class SlitherReadStorage:
def __init__(self, contracts, max_depth): def __init__(self, contracts: List[Contract], max_depth: int) -> None:
self._contracts: List[Contract] = contracts self._contracts: List[Contract] = contracts
self._max_depth: int = max_depth self._max_depth: int = max_depth
self._log: str = "" self._log: str = ""
self._slot_info: SlotInfo = {} self._slot_info: Dict[str, SlotInfo] = {}
self._target_variables = [] self._target_variables: List[Tuple[Contract, StateVariable]] = []
self._web3: Optional[Web3] = None self._web3: Optional[Web3] = None
self._checksum_address: Optional[ChecksumAddress] = None self._checksum_address: Optional[ChecksumAddress] = None
self.storage_address: Optional[str] = None self.storage_address: Optional[str] = None
self.rpc: Optional[str] = None self.rpc: Optional[str] = None
self.table: Optional[MyPrettyTable] = None
@property @property
def contracts(self) -> List[Contract]: def contracts(self) -> List[Contract]:
@ -82,7 +77,7 @@ class SlitherReadStorage:
return self._log return self._log
@log.setter @log.setter
def log(self, log) -> str: def log(self, log: str) -> None:
self._log = log self._log = log
@property @property
@ -93,6 +88,8 @@ class SlitherReadStorage:
@property @property
def checksum_address(self) -> ChecksumAddress: def checksum_address(self) -> ChecksumAddress:
if not self.storage_address:
raise ValueError
if not self._checksum_address: if not self._checksum_address:
self._checksum_address = self.web3.toChecksumAddress(self.storage_address) self._checksum_address = self.web3.toChecksumAddress(self.storage_address)
return self._checksum_address return self._checksum_address
@ -103,50 +100,52 @@ class SlitherReadStorage:
return self._target_variables return self._target_variables
@property @property
def slot_info(self) -> SlotInfo: def slot_info(self) -> Dict[str, SlotInfo]:
"""Contains the location, type, size, offset, and value of contract slots.""" """Contains the location, type, size, offset, and value of contract slots."""
return self._slot_info return self._slot_info
def get_storage_layout(self) -> None: def get_storage_layout(self) -> None:
"""Retrieves the storage layout of entire contract.""" """Retrieves the storage layout of entire contract."""
tmp = {} tmp: Dict[str, SlotInfo] = {}
for contract, var in self.target_variables: for contract, var in self.target_variables:
type_ = var.type type_ = var.type
info = self.get_storage_slot(var, contract) info = self.get_storage_slot(var, contract)
if info:
tmp[var.name] = info tmp[var.name] = info
if isinstance(type_, UserDefinedType) and isinstance(type_.type, Structure):
tmp[var.name].elems = self._all_struct_slots(var, type_.type, contract)
if is_user_defined_type(type_) and is_struct(type_.type): elif isinstance(type_, ArrayType):
tmp[var.name]["elems"] = self._all_struct_slots(var, contract) elems = self._all_array_slots(var, contract, type_, info.slot)
continue tmp[var.name].elems = elems
if is_array(type_):
elems = self._all_array_slots(var, contract, type_, info["slot"])
tmp[var.name]["elems"] = elems
self._slot_info = tmp self._slot_info = tmp
# TODO: remove this pylint exception (montyly)
# pylint: disable=too-many-locals
def get_storage_slot( def get_storage_slot(
self, self,
target_variable: StateVariable, target_variable: StateVariable,
contract: Contract, contract: Contract,
**kwargs, **kwargs: Any,
) -> Union[SlotInfo, None]: ) -> Union[SlotInfo, None]:
"""Finds the storage slot of a variable in a given contract. """Finds the storage slot of a variable in a given contract.
Args: Args:
target_variable (`StateVariable`): The variable to retrieve the slot for. target_variable (`StateVariable`): The variable to retrieve the slot for.
contracts (`Contract`): The contract that contains the given state variable. contracts (`Contract`): The contract that contains the given state variable.
**kwargs: **kwargs:
key (str): Key of a mapping or index position if an array. key (int): Key of a mapping or index position if an array.
deep_key (str): Key of a mapping embedded within another mapping or secondary index if array. deep_key (int): Key of a mapping embedded within another mapping or
secondary index if array.
struct_var (str): Structure variable name. struct_var (str): Structure variable name.
Returns: Returns:
(`SlotInfo`) | None : A dictionary of the slot information. (`SlotInfo`) | None : A dictionary of the slot information.
""" """
key = kwargs.get("key", None) key: Optional[int] = kwargs.get("key", None)
deep_key = kwargs.get("deep_key", None) deep_key: Optional[int] = kwargs.get("deep_key", None)
struct_var = kwargs.get("struct_var", None) struct_var: Optional[str] = kwargs.get("struct_var", None)
info = "" info: str
var_log_name = target_variable.name var_log_name = target_variable.name
try: try:
int_slot, size, offset, type_to = self.get_variable_info(contract, target_variable) int_slot, size, offset, type_to = self.get_variable_info(contract, target_variable)
@ -159,24 +158,33 @@ class SlitherReadStorage:
slot = int.to_bytes(int_slot, 32, byteorder="big") slot = int.to_bytes(int_slot, 32, byteorder="big")
if is_elementary(target_variable.type): target_variable_type = target_variable.type
type_to = target_variable.type.name
if isinstance(target_variable_type, ElementaryType):
type_to = target_variable_type.name
elif is_array(target_variable.type) and key: elif isinstance(target_variable_type, ArrayType) and key is not None:
var_log_name = f"{var_log_name}[{key}]"
info, type_to, slot, size, offset = self._find_array_slot( info, type_to, slot, size, offset = self._find_array_slot(
target_variable, slot, key, deep_key=deep_key, struct_var=struct_var target_variable_type,
slot,
key,
deep_key=deep_key,
struct_var=struct_var,
) )
self.log += info self.log += info
elif is_user_defined_type(target_variable.type) and struct_var: elif isinstance(target_variable_type, UserDefinedType) and struct_var is not None:
var_log_name = struct_var var_log_name = f"{var_log_name}.{struct_var}"
elems = target_variable.type.type.elems_ordered target_variable_type_type = target_variable_type.type
assert isinstance(target_variable_type_type, Structure)
elems = target_variable_type_type.elems_ordered
info, type_to, slot, size, offset = self._find_struct_var_slot(elems, slot, struct_var) info, type_to, slot, size, offset = self._find_struct_var_slot(elems, slot, struct_var)
self.log += info self.log += info
elif is_mapping(target_variable.type) and key: elif isinstance(target_variable_type, MappingType) and key:
info, type_to, slot, size, offset = self._find_mapping_slot( info, type_to, slot, size, offset = self._find_mapping_slot(
target_variable, slot, key, struct_var=struct_var, deep_key=deep_key target_variable_type, slot, key, struct_var=struct_var, deep_key=deep_key
) )
self.log += info self.log += info
@ -184,12 +192,14 @@ class SlitherReadStorage:
self.log += f"\nName: {var_log_name}\nType: {type_to}\nSlot: {int_slot}\n" self.log += f"\nName: {var_log_name}\nType: {type_to}\nSlot: {int_slot}\n"
logger.info(self.log) logger.info(self.log)
self.log = "" self.log = ""
return {
"type_string": type_to, return SlotInfo(
"slot": int_slot, name=var_log_name,
"size": size, type_string=type_to,
"offset": offset, slot=int_slot,
} size=size,
offset=offset,
)
def get_target_variables(self, **kwargs) -> None: def get_target_variables(self, **kwargs) -> None:
""" """
@ -201,28 +211,32 @@ class SlitherReadStorage:
struct_var (str): Structure variable name. struct_var (str): Structure variable name.
""" """
for contract, var in self.target_variables: for contract, var in self.target_variables:
self._slot_info[f"{contract.name}.{var.name}"] = self.get_storage_slot( slot_info = self.get_storage_slot(var, contract, **kwargs)
var, contract, **kwargs if slot_info:
) self._slot_info[f"{contract.name}.{var.name}"] = slot_info
def get_slot_values(self) -> SlotInfo: def walk_slot_info(self, func: Callable) -> None:
""" stack = list(self.slot_info.values())
Fetches the slot values and inserts them in slot info dictionary.
Returns:
(`SlotInfo`): The dictionary of slot info.
"""
stack = list(self.slot_info.items())
while stack: while stack:
_, v = stack.pop() slot_info = stack.pop()
if isinstance(v, dict): if isinstance(slot_info, dict): # NestedElem
stack.extend(v.items()) stack.extend(slot_info.values())
if "slot" in v: elif slot_info.elems:
hex_bytes = get_storage_data(self.web3, self.checksum_address, v["slot"]) stack.extend(list(slot_info.elems.values()))
v["value"] = self.convert_value_to_type( if isinstance(slot_info, SlotInfo):
hex_bytes, v["size"], v["offset"], v["type_string"] func(slot_info)
def get_slot_values(self, slot_info: SlotInfo) -> None:
"""Fetches the slot value of `SlotInfo` object
:param slot_info:
"""
hex_bytes = get_storage_data(
self.web3, self.checksum_address, int.to_bytes(slot_info.slot, 32, byteorder="big")
)
slot_info.value = self.convert_value_to_type(
hex_bytes, slot_info.size, slot_info.offset, slot_info.type_string
) )
logger.info(f"\nValue: {v['value']}\n") logger.info(f"\nValue: {slot_info.value}\n")
return self.slot_info
def get_all_storage_variables(self, func: Callable = None) -> None: def get_all_storage_variables(self, func: Callable = None) -> None:
"""Fetches all storage variables from a list of contracts. """Fetches all storage variables from a list of contracts.
@ -241,14 +255,29 @@ class SlitherReadStorage:
) )
) )
def convert_slot_info_to_rows(self, slot_info: SlotInfo) -> None:
"""Convert and append slot info to table. Create table if it
does not yet exist
:param slot_info:
"""
field_names = [
field.name for field in dataclasses.fields(SlotInfo) if field.name != "elems"
]
if not self.table:
self.table = MyPrettyTable(field_names)
self.table.add_row([getattr(slot_info, field) for field in field_names])
def to_json(self) -> Dict:
return {key: dataclasses.asdict(value) for key, value in self.slot_info.items()}
@staticmethod @staticmethod
def _find_struct_var_slot( def _find_struct_var_slot(
elems: List[StructureVariable], slot: bytes, struct_var: str elems: List[StructureVariable], slot_as_bytes: bytes, struct_var: str
) -> Tuple[str, str, bytes, int, int]: ) -> Tuple[str, str, bytes, int, int]:
"""Finds the slot of a structure variable. """Finds the slot of a structure variable.
Args: Args:
elems (List[StructureVariable]): Ordered list of structure variables. elems (List[StructureVariable]): Ordered list of structure variables.
slot (bytes): The slot of the struct to begin searching at. slot_as_bytes (bytes): The slot of the struct to begin searching at.
struct_var (str): The target structure variable. struct_var (str): The target structure variable.
Returns: Returns:
info (str): Info about the target variable to log. info (str): Info about the target variable to log.
@ -257,31 +286,36 @@ class SlitherReadStorage:
size (int): The size (in bits) of the target variable. size (int): The size (in bits) of the target variable.
offset (int): The size of other variables that share the same slot. offset (int): The size of other variables that share the same slot.
""" """
slot = int.from_bytes(slot, "big") slot = int.from_bytes(slot_as_bytes, "big")
offset = 0 offset = 0
type_to = ""
for var in elems: for var in elems:
size = var.type.size var_type = var.type
if isinstance(var_type, ElementaryType):
size = var_type.size
if offset >= 256: if offset >= 256:
slot += 1 slot += 1
offset = 0 offset = 0
if struct_var == var.name: if struct_var == var.name:
type_to = var.type.name type_to = var_type.name
break # found struct var break # found struct var
offset += size offset += size
else:
logger.info(f"{type(var_type)} is current not implemented in _find_struct_var_slot")
slot = int.to_bytes(slot, 32, byteorder="big") slot_as_bytes = int.to_bytes(slot, 32, byteorder="big")
info = f"\nStruct Variable: {struct_var}" info = f"\nStruct Variable: {struct_var}"
return info, type_to, slot, size, offset return info, type_to, slot_as_bytes, size, offset
# pylint: disable=too-many-branches # pylint: disable=too-many-branches,too-many-statements
@staticmethod @staticmethod
def _find_array_slot( def _find_array_slot(
target_variable: StateVariable, target_variable_type: ArrayType,
slot: bytes, slot: bytes,
key: int, key: int,
deep_key: int = None, deep_key: int = None,
struct_var: str = None, struct_var: str = None,
) -> Tuple[str, str, bytes]: ) -> Tuple[str, str, bytes, int, int]:
"""Finds the slot of array's index. """Finds the slot of array's index.
Args: Args:
target_variable (`StateVariable`): The array that contains the target variable. target_variable (`StateVariable`): The array that contains the target variable.
@ -293,31 +327,36 @@ class SlitherReadStorage:
info (str): Info about the target variable to log. info (str): Info about the target variable to log.
type_to (str): The type of the target variable. type_to (str): The type of the target variable.
slot (bytes): The storage location of the target variable. slot (bytes): The storage location of the target variable.
size (int): The size
offset (int): The offset
""" """
info = f"\nKey: {key}" info = f"\nKey: {key}"
offset = 0 offset = 0
size = 256 size = 256
if is_array( target_variable_type_type = target_variable_type.type
target_variable.type.type
if isinstance(
target_variable_type_type, ArrayType
): # multidimensional array uint[i][], , uint[][i], or uint[][] ): # multidimensional array uint[i][], , uint[][i], or uint[][]
size = target_variable.type.type.type.size assert isinstance(target_variable_type_type.type, ElementaryType)
type_to = target_variable.type.type.type.name size = target_variable_type_type.type.size
type_to = target_variable_type_type.type.name
if target_variable.type.is_fixed_array: # uint[][i] if target_variable_type.is_fixed_array: # uint[][i]
slot_int = int.from_bytes(slot, "big") + int(key) slot_int = int.from_bytes(slot, "big") + int(key)
else: else:
slot = keccak(slot) slot = keccak(slot)
key = int(key) key = int(key)
if target_variable.type.type.is_fixed_array: # arr[i][] if target_variable_type_type.is_fixed_array: # arr[i][]
key *= int(str(target_variable.type.type.length)) key *= int(str(target_variable_type_type.length))
slot_int = int.from_bytes(slot, "big") + key slot_int = int.from_bytes(slot, "big") + key
if not deep_key: if not deep_key:
return info, type_to, int.to_bytes(slot_int, 32, "big"), size, offset return info, type_to, int.to_bytes(slot_int, 32, "big"), size, offset
info += f"\nDeep Key: {deep_key}" info += f"\nDeep Key: {deep_key}"
if target_variable.type.type.is_dynamic_array: # uint[][] if target_variable_type_type.is_dynamic_array: # uint[][]
# keccak256(keccak256(slot) + index) + floor(j / floor(256 / size)) # keccak256(keccak256(slot) + index) + floor(j / floor(256 / size))
slot = keccak(int.to_bytes(slot_int, 32, "big")) slot = keccak(int.to_bytes(slot_int, 32, "big"))
slot_int = int.from_bytes(slot, "big") slot_int = int.from_bytes(slot, "big")
@ -325,13 +364,15 @@ class SlitherReadStorage:
# keccak256(slot) + index + floor(j / floor(256 / size)) # keccak256(slot) + index + floor(j / floor(256 / size))
slot_int += floor(int(deep_key) / floor(256 / size)) # uint[i][] slot_int += floor(int(deep_key) / floor(256 / size)) # uint[i][]
elif target_variable.type.is_fixed_array: elif target_variable_type.is_fixed_array:
slot_int = int.from_bytes(slot, "big") + int(key) slot_int = int.from_bytes(slot, "big") + int(key)
if is_user_defined_type(target_variable.type.type): # struct[i] if isinstance(target_variable_type_type, UserDefinedType) and isinstance(
type_to = target_variable.type.type.type.name target_variable_type_type.type, Structure
): # struct[i]
type_to = target_variable_type_type.type.name
if not struct_var: if not struct_var:
return info, type_to, int.to_bytes(slot_int, 32, "big"), size, offset return info, type_to, int.to_bytes(slot_int, 32, "big"), size, offset
elems = target_variable.type.type.type.elems_ordered elems = target_variable_type_type.type.elems_ordered
slot = int.to_bytes(slot_int, 32, byteorder="big") slot = int.to_bytes(slot_int, 32, byteorder="big")
info_tmp, type_to, slot, size, offset = SlitherReadStorage._find_struct_var_slot( info_tmp, type_to, slot, size, offset = SlitherReadStorage._find_struct_var_slot(
elems, slot, struct_var elems, slot, struct_var
@ -339,16 +380,19 @@ class SlitherReadStorage:
info += info_tmp info += info_tmp
else: else:
type_to = target_variable.type.type.name assert isinstance(target_variable_type_type, ElementaryType)
size = target_variable.type.type.size # bits type_to = target_variable_type_type.name
size = target_variable_type_type.size # bits
elif is_user_defined_type(target_variable.type.type): # struct[] elif isinstance(target_variable_type_type, UserDefinedType) and isinstance(
target_variable_type_type.type, Structure
): # struct[]
slot = keccak(slot) slot = keccak(slot)
slot_int = int.from_bytes(slot, "big") + int(key) slot_int = int.from_bytes(slot, "big") + int(key)
type_to = target_variable.type.type.type.name type_to = target_variable_type_type.type.name
if not struct_var: if not struct_var:
return info, type_to, int.to_bytes(slot_int, 32, "big"), size, offset return info, type_to, int.to_bytes(slot_int, 32, "big"), size, offset
elems = target_variable.type.type.type.elems_ordered elems = target_variable_type_type.type.elems_ordered
slot = int.to_bytes(slot_int, 32, byteorder="big") slot = int.to_bytes(slot_int, 32, byteorder="big")
info_tmp, type_to, slot, size, offset = SlitherReadStorage._find_struct_var_slot( info_tmp, type_to, slot, size, offset = SlitherReadStorage._find_struct_var_slot(
elems, slot, struct_var elems, slot, struct_var
@ -356,10 +400,12 @@ class SlitherReadStorage:
info += info_tmp info += info_tmp
else: else:
assert isinstance(target_variable_type_type, ElementaryType)
slot = keccak(slot) slot = keccak(slot)
slot_int = int.from_bytes(slot, "big") + int(key) slot_int = int.from_bytes(slot, "big") + int(key)
type_to = target_variable.type.type.name type_to = target_variable_type_type.name
size = target_variable.type.type.size # bits size = target_variable_type_type.size # bits
slot = int.to_bytes(slot_int, 32, byteorder="big") slot = int.to_bytes(slot_int, 32, byteorder="big")
@ -367,7 +413,7 @@ class SlitherReadStorage:
@staticmethod @staticmethod
def _find_mapping_slot( def _find_mapping_slot(
target_variable: StateVariable, target_variable_type: MappingType,
slot: bytes, slot: bytes,
key: Union[int, str], key: Union[int, str],
deep_key: Union[int, str] = None, deep_key: Union[int, str] = None,
@ -381,7 +427,7 @@ class SlitherReadStorage:
struct_var (str, optional): Structure variable name. struct_var (str, optional): Structure variable name.
:returns: :returns:
log (str): Info about the target variable to log. log (str): Info about the target variable to log.
type_to (bytes): The type of the target variable. type_to (str): The type of the target variable.
slot (bytes): The storage location of the target variable. slot (bytes): The storage location of the target variable.
size (int): The size (in bits) of the target variable. size (int): The size (in bits) of the target variable.
offset (int): The size of other variables that share the same slot. offset (int): The size of other variables that share the same slot.
@ -393,27 +439,30 @@ class SlitherReadStorage:
info += f"\nKey: {key}" info += f"\nKey: {key}"
if deep_key: if deep_key:
info += f"\nDeep Key: {deep_key}" info += f"\nDeep Key: {deep_key}"
assert isinstance(target_variable_type.type_from, ElementaryType)
key_type = target_variable.type.type_from.name key_type = target_variable_type.type_from.name
assert key assert key
if "int" in key_type: # without this eth_utils encoding fails if "int" in key_type: # without this eth_utils encoding fails
key = int(key) key = int(key)
key = coerce_type(key_type, key) key = coerce_type(key_type, key)
slot = keccak(encode_abi([key_type, "uint256"], [key, decode_single("uint256", slot)])) slot = keccak(encode_abi([key_type, "uint256"], [key, decode_single("uint256", slot)]))
if is_user_defined_type(target_variable.type.type_to) and is_struct( if isinstance(target_variable_type.type_to, UserDefinedType) and isinstance(
target_variable.type.type_to.type target_variable_type.type_to.type, Structure
): # mapping(elem => struct) ): # mapping(elem => struct)
assert struct_var assert struct_var
elems = target_variable.type.type_to.type.elems_ordered elems = target_variable_type.type_to.type.elems_ordered
info_tmp, type_to, slot, size, offset = SlitherReadStorage._find_struct_var_slot( info_tmp, type_to, slot, size, offset = SlitherReadStorage._find_struct_var_slot(
elems, slot, struct_var elems, slot, struct_var
) )
info += info_tmp info += info_tmp
elif is_mapping(target_variable.type.type_to): # mapping(elem => mapping(elem => ???)) elif isinstance(
target_variable_type.type_to, MappingType
): # mapping(elem => mapping(elem => ???))
assert deep_key assert deep_key
key_type = target_variable.type.type_to.type_from.name assert isinstance(target_variable_type.type_to.type_from, ElementaryType)
key_type = target_variable_type.type_to.type_from.name
if "int" in key_type: # without this eth_utils encoding fails if "int" in key_type: # without this eth_utils encoding fails
deep_key = int(deep_key) deep_key = int(deep_key)
@ -421,16 +470,20 @@ class SlitherReadStorage:
slot = keccak(encode_abi([key_type, "bytes32"], [deep_key, slot])) slot = keccak(encode_abi([key_type, "bytes32"], [deep_key, slot]))
# mapping(elem => mapping(elem => elem)) # mapping(elem => mapping(elem => elem))
type_to = target_variable.type.type_to.type_to.type target_variable_type_type_to_type_to = target_variable_type.type_to.type_to
byte_size, _ = target_variable.type.type_to.type_to.storage_size assert isinstance(
target_variable_type_type_to_type_to, (UserDefinedType, ElementaryType)
)
type_to = str(target_variable_type_type_to_type_to.type)
byte_size, _ = target_variable_type_type_to_type_to.storage_size
size = byte_size * 8 # bits size = byte_size * 8 # bits
offset = 0 offset = 0
if is_user_defined_type(target_variable.type.type_to.type_to) and is_struct( if isinstance(target_variable_type_type_to_type_to, UserDefinedType) and isinstance(
target_variable.type.type_to.type_to.type target_variable_type_type_to_type_to.type, Structure
): # mapping(elem => mapping(elem => struct)) ): # mapping(elem => mapping(elem => struct))
assert struct_var assert struct_var
elems = target_variable.type.type_to.type_to.type.elems_ordered elems = target_variable_type_type_to_type_to.type.elems_ordered
# If map struct, will be bytes32(uint256(keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))))) + structFieldDepth); # If map struct, will be bytes32(uint256(keccak256(abi.encode(key1, keccak256(abi.encode(key0, uint(slot)))))) + structFieldDepth);
info_tmp, type_to, slot, size, offset = SlitherReadStorage._find_struct_var_slot( info_tmp, type_to, slot, size, offset = SlitherReadStorage._find_struct_var_slot(
elems, slot, struct_var elems, slot, struct_var
@ -439,11 +492,17 @@ class SlitherReadStorage:
# TODO: suppory mapping with dynamic arrays # TODO: suppory mapping with dynamic arrays
else: # mapping(elem => elem) # mapping(elem => elem)
type_to = target_variable.type.type_to.name # the value's elementary type elif isinstance(target_variable_type.type_to, ElementaryType):
byte_size, _ = target_variable.type.type_to.storage_size type_to = target_variable_type.type_to.name # the value's elementary type
byte_size, _ = target_variable_type.type_to.storage_size
size = byte_size * 8 # bits size = byte_size * 8 # bits
else:
raise NotImplementedError(
f"{target_variable_type} => {target_variable_type.type_to} not implemented"
)
return info, type_to, slot, size, offset return info, type_to, slot, size, offset
@staticmethod @staticmethod
@ -451,6 +510,7 @@ class SlitherReadStorage:
contract: Contract, target_variable: StateVariable contract: Contract, target_variable: StateVariable
) -> Tuple[int, int, int, str]: ) -> Tuple[int, int, int, str]:
"""Return slot, size, offset, and type.""" """Return slot, size, offset, and type."""
assert isinstance(target_variable.type, Type)
type_to = str(target_variable.type) type_to = str(target_variable.type)
byte_size, _ = target_variable.type.storage_size byte_size, _ = target_variable.type.storage_size
size = byte_size * 8 # bits size = byte_size * 8 # bits
@ -464,8 +524,8 @@ class SlitherReadStorage:
@staticmethod @staticmethod
def convert_value_to_type( def convert_value_to_type(
hex_bytes: HexBytes, size: int, offset: int, type_to: str hex_bytes: bytes, size: int, offset: int, type_to: str
) -> Union[int, bool, str, ChecksumAddress, hex]: ) -> Union[int, bool, str, ChecksumAddress]:
"""Convert slot data to type representation.""" """Convert slot data to type representation."""
# Account for storage packing # Account for storage packing
offset_hex_bytes = get_offset_value(hex_bytes, offset, size) offset_hex_bytes = get_offset_value(hex_bytes, offset, size)
@ -477,14 +537,11 @@ class SlitherReadStorage:
return value return value
def _all_struct_slots( def _all_struct_slots(
self, var: Union[StructureVariable, StructureContract], contract: Contract, key=None self, var: StateVariable, st: Structure, contract: Contract, key: Optional[int] = None
) -> Dict[str, SlotInfo]: ) -> Elem:
"""Retrieves all members of a struct.""" """Retrieves all members of a struct."""
if isinstance(var.type.type, StructureContract): struct_elems = st.elems_ordered
struct_elems = var.type.type.elems_ordered data: Elem = {}
else:
struct_elems = var.type.type.type.elems_ordered
data = {}
for elem in struct_elems: for elem in struct_elems:
info = self.get_storage_slot( info = self.get_storage_slot(
var, var,
@ -492,34 +549,41 @@ class SlitherReadStorage:
key=key, key=key,
struct_var=elem.name, struct_var=elem.name,
) )
if info:
data[elem.name] = info data[elem.name] = info
return data return data
# pylint: disable=too-many-nested-blocks
def _all_array_slots( def _all_array_slots(
self, var: ArrayType, contract: Contract, type_: Type, slot: int self, var: StateVariable, contract: Contract, type_: ArrayType, slot: int
) -> Dict[int, SlotInfo]: ) -> Union[Elem, NestedElem]:
"""Retrieves all members of an array.""" """Retrieves all members of an array."""
array_length = self._get_array_length(type_, slot) array_length = self._get_array_length(type_, slot)
elems = {} target_variable_type = type_.type
if is_user_defined_type(type_.type): if isinstance(target_variable_type, UserDefinedType) and isinstance(
target_variable_type.type, Structure
):
nested_elems: NestedElem = {}
for i in range(min(array_length, self.max_depth)): for i in range(min(array_length, self.max_depth)):
elems[i] = self._all_struct_slots(var, contract, key=str(i)) nested_elems[str(i)] = self._all_struct_slots(
continue var, target_variable_type.type, contract, key=i
)
return nested_elems
else: elems: Elem = {}
for i in range(min(array_length, self.max_depth)): for i in range(min(array_length, self.max_depth)):
info = self.get_storage_slot( info = self.get_storage_slot(
var, var,
contract, contract,
key=str(i), key=str(i),
) )
elems[i] = info if info:
elems[str(i)] = info
if is_array(type_.type): # multidimensional array if isinstance(target_variable_type, ArrayType): # multidimensional array
array_length = self._get_array_length(type_.type, info["slot"]) array_length = self._get_array_length(target_variable_type, info.slot)
elems[i]["elems"] = {}
for j in range(min(array_length, self.max_depth)): for j in range(min(array_length, self.max_depth)):
info = self.get_storage_slot( info = self.get_storage_slot(
var, var,
@ -527,15 +591,15 @@ class SlitherReadStorage:
key=str(i), key=str(i),
deep_key=str(j), deep_key=str(j),
) )
if info:
elems[i]["elems"][j] = info elems[str(i)].elems[str(j)] = info
return elems return elems
def _get_array_length(self, type_: Type, slot: int = None) -> int: def _get_array_length(self, type_: Type, slot: int) -> int:
"""Gets the length of dynamic and fixed arrays. """Gets the length of dynamic and fixed arrays.
Args: Args:
type_ (`Type`): The array type. type_ (`AbstractType`): The array type.
slot (int, optional): Slot a dynamic array's length is stored at. slot (int): Slot a dynamic array's length is stored at.
Returns: Returns:
(int): The length of the array. (int): The length of the array.
""" """
@ -543,8 +607,13 @@ class SlitherReadStorage:
if self.rpc: if self.rpc:
# The length of dynamic arrays is stored at the starting slot. # The length of dynamic arrays is stored at the starting slot.
# Convert from hexadecimal to decimal. # Convert from hexadecimal to decimal.
val = int(get_storage_data(self.web3, self.checksum_address, slot).hex(), 16) val = int(
if is_array(type_): get_storage_data(
self.web3, self.checksum_address, int.to_bytes(slot, 32, byteorder="big")
).hex(),
16,
)
if isinstance(type_, ArrayType):
if type_.is_fixed_array: if type_.is_fixed_array:
val = int(str(type_.length)) val = int(str(type_.length))

@ -1,10 +1,4 @@
from .utils import ( from .utils import (
is_elementary,
is_array,
is_enum,
is_mapping,
is_struct,
is_user_defined_type,
get_offset_value, get_offset_value,
get_storage_data, get_storage_data,
coerce_type, coerce_type,

@ -1,44 +1,10 @@
from typing import Union from typing import Union
from hexbytes import HexBytes
from eth_typing.evm import ChecksumAddress from eth_typing.evm import ChecksumAddress
from eth_utils import to_int, to_text, to_checksum_address from eth_utils import to_int, to_text, to_checksum_address
from slither.core.declarations import Structure, Enum
from slither.core.solidity_types import ArrayType, MappingType, UserDefinedType, ElementaryType
from slither.core.variables.state_variable import StateVariable
def is_elementary(variable: StateVariable) -> bool:
"""Returns whether variable is an elementary type."""
return isinstance(variable, ElementaryType)
def is_array(variable: StateVariable) -> bool:
"""Returns whether variable is an array."""
return isinstance(variable, ArrayType)
def is_mapping(variable: StateVariable) -> bool:
"""Returns whether variable is a mapping."""
return isinstance(variable, MappingType)
def is_struct(variable: StateVariable) -> bool: def get_offset_value(hex_bytes: bytes, offset: int, size: int) -> bytes:
"""Returns whether variable is a struct."""
return isinstance(variable, Structure)
def is_enum(variable: StateVariable) -> bool:
"""Returns whether variable is an enum."""
return isinstance(variable, Enum)
def is_user_defined_type(variable: StateVariable) -> bool:
"""Returns whether variable is a struct."""
return isinstance(variable, UserDefinedType)
def get_offset_value(hex_bytes: HexBytes, offset: int, size: int) -> bytes:
""" """
Trims slot data to only contain the target variable's. Trims slot data to only contain the target variable's.
Args: Args:
@ -58,7 +24,9 @@ def get_offset_value(hex_bytes: HexBytes, offset: int, size: int) -> bytes:
return value return value
def coerce_type(solidity_type: str, value: bytes) -> Union[int, bool, str, ChecksumAddress, hex]: def coerce_type(
solidity_type: str, value: Union[int, str, bytes]
) -> Union[int, bool, str, ChecksumAddress]:
""" """
Converts input to the indicated type. Converts input to the indicated type.
Args: Args:
@ -68,24 +36,26 @@ def coerce_type(solidity_type: str, value: bytes) -> Union[int, bool, str, Check
(Union[int, bool, str, ChecksumAddress, hex]): The type representation of the value. (Union[int, bool, str, ChecksumAddress, hex]): The type representation of the value.
""" """
if "int" in solidity_type: if "int" in solidity_type:
converted_value = to_int(value) return to_int(value)
elif "bool" in solidity_type: if "bool" in solidity_type:
converted_value = bool(to_int(value)) return bool(to_int(value))
elif "string" in solidity_type: if "string" in solidity_type and isinstance(value, bytes):
# length * 2 is stored in lower end bits # length * 2 is stored in lower end bits
# TODO handle bytes and strings greater than 32 bytes # TODO handle bytes and strings greater than 32 bytes
length = int(int.from_bytes(value[-2:], "big") / 2) length = int(int.from_bytes(value[-2:], "big") / 2)
converted_value = to_text(value[:length]) return to_text(value[:length])
elif "address" in solidity_type: if "address" in solidity_type:
converted_value = to_checksum_address(value) if not isinstance(value, (str, bytes)):
else: raise TypeError
converted_value = value.hex() return to_checksum_address(value)
return converted_value if not isinstance(value, bytes):
raise TypeError
return value.hex()
def get_storage_data(web3, checksum_address: ChecksumAddress, slot: bytes) -> HexBytes: def get_storage_data(web3, checksum_address: ChecksumAddress, slot: bytes) -> bytes:
""" """
Retrieves the storage data from the blockchain at target address and slot. Retrieves the storage data from the blockchain at target address and slot.
Args: Args:

@ -1,469 +1,576 @@
{ {
"packedUint": { "packedUint": {
"name": "packedUint",
"type_string": "uint248", "type_string": "uint248",
"slot": 0, "slot": 0,
"size": 248, "size": 248,
"offset": 0, "offset": 0,
"value": 1 "value": 1,
"elems": {}
}, },
"packedBool": { "packedBool": {
"name": "packedBool",
"type_string": "bool", "type_string": "bool",
"slot": 0, "slot": 0,
"size": 8, "size": 8,
"offset": 248, "offset": 248,
"value": true "value": true,
"elems": {}
}, },
"_packedStruct": { "_packedStruct": {
"name": "_packedStruct",
"type_string": "StorageLayout.PackedStruct", "type_string": "StorageLayout.PackedStruct",
"slot": 1, "slot": 1,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": "0000000000000000000000000000000000000000000000000000000000000101",
"elems": { "elems": {
"b": { "b": {
"name": "_packedStruct.b",
"type_string": "bool", "type_string": "bool",
"slot": 1, "slot": 1,
"size": 8, "size": 8,
"offset": 0, "offset": 0,
"value": true "value": true,
"elems": {}
}, },
"a": { "a": {
"name": "_packedStruct.a",
"type_string": "uint248", "type_string": "uint248",
"slot": 1, "slot": 1,
"size": 248, "size": 248,
"offset": 8, "offset": 8,
"value": 1 "value": 1,
"elems": {}
}
} }
},
"value": "0000000000000000000000000000000000000000000000000000000000000101"
}, },
"mappingPackedStruct": { "mappingPackedStruct": {
"name": "mappingPackedStruct",
"type_string": "mapping(uint256 => StorageLayout.PackedStruct)", "type_string": "mapping(uint256 => StorageLayout.PackedStruct)",
"slot": 2, "slot": 2,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 0 "value": 0,
"elems": {}
}, },
"deepMappingPackedStruct": { "deepMappingPackedStruct": {
"name": "deepMappingPackedStruct",
"type_string": "mapping(address => mapping(uint256 => StorageLayout.PackedStruct))", "type_string": "mapping(address => mapping(uint256 => StorageLayout.PackedStruct))",
"slot": 3, "slot": 3,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 0 "value": 0,
"elems": {}
}, },
"deepMappingElementaryTypes": { "deepMappingElementaryTypes": {
"name": "deepMappingElementaryTypes",
"type_string": "mapping(address => mapping(uint256 => bool))", "type_string": "mapping(address => mapping(uint256 => bool))",
"slot": 4, "slot": 4,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 0 "value": 0,
"elems": {}
}, },
"mappingDynamicArrayOfStructs": { "mappingDynamicArrayOfStructs": {
"name": "mappingDynamicArrayOfStructs",
"type_string": "mapping(address => StorageLayout.PackedStruct[])", "type_string": "mapping(address => StorageLayout.PackedStruct[])",
"slot": 5, "slot": 5,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 0 "value": 0,
"elems": {}
}, },
"_address": { "_address": {
"name": "_address",
"type_string": "address", "type_string": "address",
"slot": 6, "slot": 6,
"size": 160, "size": 160,
"offset": 0, "offset": 0,
"value": "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6" "value": "0xae17D2dD99e07CA3bF2571CCAcEAA9e2Aefc2Dc6",
"elems": {}
}, },
"_string": { "_string": {
"name": "_string",
"type_string": "string", "type_string": "string",
"slot": 7, "slot": 7,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": "slither-read-storage" "value": "slither-read-storage",
"elems": {}
}, },
"packedUint8": { "packedUint8": {
"name": "packedUint8",
"type_string": "uint8", "type_string": "uint8",
"slot": 8, "slot": 8,
"size": 8, "size": 8,
"offset": 0, "offset": 0,
"value": 8 "value": 8,
"elems": {}
}, },
"packedBytes": { "packedBytes": {
"name": "packedBytes",
"type_string": "bytes8", "type_string": "bytes8",
"slot": 8, "slot": 8,
"size": 64, "size": 64,
"offset": 8, "offset": 8,
"value": "6161616161616161" "value": "6161616161616161",
"elems": {}
}, },
"_enumA": { "_enumA": {
"name": "_enumA",
"type_string": "StorageLayout.Enum", "type_string": "StorageLayout.Enum",
"slot": 8, "slot": 8,
"size": 8, "size": 8,
"offset": 72, "offset": 72,
"value": "00" "value": "00",
"elems": {}
}, },
"_enumB": { "_enumB": {
"name": "_enumB",
"type_string": "StorageLayout.Enum", "type_string": "StorageLayout.Enum",
"slot": 8, "slot": 8,
"size": 8, "size": 8,
"offset": 80, "offset": 80,
"value": "01" "value": "01",
"elems": {}
}, },
"_enumC": { "_enumC": {
"name": "_enumC",
"type_string": "StorageLayout.Enum", "type_string": "StorageLayout.Enum",
"slot": 8, "slot": 8,
"size": 8, "size": 8,
"offset": 88, "offset": 88,
"value": "02" "value": "02",
"elems": {}
}, },
"fixedArray": { "fixedArray": {
"name": "fixedArray",
"type_string": "uint256[3]", "type_string": "uint256[3]",
"slot": 9, "slot": 9,
"size": 768, "size": 768,
"offset": 0, "offset": 0,
"value": 1,
"elems": { "elems": {
"0": { "0": {
"name": "fixedArray[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 9, "slot": 9,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 1 "value": 1,
"elems": {}
}, },
"1": { "1": {
"name": "fixedArray[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 10, "slot": 10,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 2 "value": 2,
"elems": {}
}, },
"2": { "2": {
"name": "fixedArray[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 11, "slot": 11,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 3 "value": 3,
"elems": {}
}
} }
},
"value": 1
}, },
"dynamicArrayOfFixedArrays": { "dynamicArrayOfFixedArrays": {
"name": "dynamicArrayOfFixedArrays",
"type_string": "uint256[3][]", "type_string": "uint256[3][]",
"slot": 12, "slot": 12,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 2,
"elems": { "elems": {
"0": { "0": {
"name": "dynamicArrayOfFixedArrays[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 101051993584849178915136821395265346177868384823507754984078593667947067386055, "slot": 101051993584849178915136821395265346177868384823507754984078593667947067386055,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 1,
"elems": { "elems": {
"0": { "0": {
"name": "dynamicArrayOfFixedArrays[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 101051993584849178915136821395265346177868384823507754984078593667947067386055, "slot": 101051993584849178915136821395265346177868384823507754984078593667947067386055,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 1 "value": 1,
"elems": {}
}, },
"1": { "1": {
"name": "dynamicArrayOfFixedArrays[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 101051993584849178915136821395265346177868384823507754984078593667947067386056, "slot": 101051993584849178915136821395265346177868384823507754984078593667947067386056,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 2 "value": 2,
"elems": {}
}, },
"2": { "2": {
"name": "dynamicArrayOfFixedArrays[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 101051993584849178915136821395265346177868384823507754984078593667947067386057, "slot": 101051993584849178915136821395265346177868384823507754984078593667947067386057,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 3 "value": 3,
"elems": {}
}
} }
},
"value": 1
}, },
"1": { "1": {
"name": "dynamicArrayOfFixedArrays[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 101051993584849178915136821395265346177868384823507754984078593667947067386058, "slot": 101051993584849178915136821395265346177868384823507754984078593667947067386058,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 4,
"elems": { "elems": {
"0": { "0": {
"name": "dynamicArrayOfFixedArrays[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 101051993584849178915136821395265346177868384823507754984078593667947067386058, "slot": 101051993584849178915136821395265346177868384823507754984078593667947067386058,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 4 "value": 4,
"elems": {}
}, },
"1": { "1": {
"name": "dynamicArrayOfFixedArrays[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 101051993584849178915136821395265346177868384823507754984078593667947067386059, "slot": 101051993584849178915136821395265346177868384823507754984078593667947067386059,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 5 "value": 5,
"elems": {}
}, },
"2": { "2": {
"name": "dynamicArrayOfFixedArrays[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 101051993584849178915136821395265346177868384823507754984078593667947067386060, "slot": 101051993584849178915136821395265346177868384823507754984078593667947067386060,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 6 "value": 6,
"elems": {}
}
}
} }
},
"value": 4
} }
},
"value": 2
}, },
"fixedArrayofDynamicArrays": { "fixedArrayofDynamicArrays": {
"name": "fixedArrayofDynamicArrays",
"type_string": "uint256[][3]", "type_string": "uint256[][3]",
"slot": 13, "slot": 13,
"size": 768, "size": 768,
"offset": 0, "offset": 0,
"value": 1,
"elems": { "elems": {
"0": { "0": {
"name": "fixedArrayofDynamicArrays[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 13, "slot": 13,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 1,
"elems": { "elems": {
"0": { "0": {
"name": "fixedArrayofDynamicArrays[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 97569884605916225051403212656556507955018248777258318895762758024193532305077, "slot": 97569884605916225051403212656556507955018248777258318895762758024193532305077,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 7 "value": 7,
"elems": {}
}
} }
},
"value": 1
}, },
"1": { "1": {
"name": "fixedArrayofDynamicArrays[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 14, "slot": 14,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 2,
"elems": { "elems": {
"0": { "0": {
"name": "fixedArrayofDynamicArrays[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 84800337471693920904250232874319843718400766719524250287777680170677855896573, "slot": 84800337471693920904250232874319843718400766719524250287777680170677855896573,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 8 "value": 8,
"elems": {}
}, },
"1": { "1": {
"name": "fixedArrayofDynamicArrays[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 84800337471693920904250232874319843718400766719524250287777680170677855896574, "slot": 84800337471693920904250232874319843718400766719524250287777680170677855896574,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 9 "value": 9,
"elems": {}
}
} }
},
"value": 2
}, },
"2": { "2": {
"name": "fixedArrayofDynamicArrays[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 15, "slot": 15,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 3,
"elems": { "elems": {
"0": { "0": {
"name": "fixedArrayofDynamicArrays[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 63806209331542711802848847270949280092855778197726125910674179583545433573378, "slot": 63806209331542711802848847270949280092855778197726125910674179583545433573378,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 10 "value": 10,
"elems": {}
}, },
"1": { "1": {
"name": "fixedArrayofDynamicArrays[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 63806209331542711802848847270949280092855778197726125910674179583545433573379, "slot": 63806209331542711802848847270949280092855778197726125910674179583545433573379,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 11 "value": 11,
"elems": {}
}, },
"2": { "2": {
"name": "fixedArrayofDynamicArrays[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 63806209331542711802848847270949280092855778197726125910674179583545433573380, "slot": 63806209331542711802848847270949280092855778197726125910674179583545433573380,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 12 "value": 12,
"elems": {}
}
}
} }
},
"value": 3
} }
},
"value": 1
}, },
"multidimensionalArray": { "multidimensionalArray": {
"name": "multidimensionalArray",
"type_string": "uint256[][]", "type_string": "uint256[][]",
"slot": 16, "slot": 16,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 3,
"elems": { "elems": {
"0": { "0": {
"name": "multidimensionalArray[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 12396694973890998440467380340983585058878106250672390494374587083972727727730, "slot": 12396694973890998440467380340983585058878106250672390494374587083972727727730,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 1,
"elems": { "elems": {
"0": { "0": {
"name": "multidimensionalArray[0]",
"type_string": "uint256", "type_string": "uint256",
"slot": 93856215500098298973000561543003607329881518401177956003908346942307446808932, "slot": 93856215500098298973000561543003607329881518401177956003908346942307446808932,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 13 "value": 13,
"elems": {}
}
} }
},
"value": 1
}, },
"1": { "1": {
"name": "multidimensionalArray[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 12396694973890998440467380340983585058878106250672390494374587083972727727731, "slot": 12396694973890998440467380340983585058878106250672390494374587083972727727731,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 2,
"elems": { "elems": {
"0": { "0": {
"name": "multidimensionalArray[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 48332168562525185806884758054388614910060623018875025120987491603435926351511, "slot": 48332168562525185806884758054388614910060623018875025120987491603435926351511,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 14 "value": 14,
"elems": {}
}, },
"1": { "1": {
"name": "multidimensionalArray[1]",
"type_string": "uint256", "type_string": "uint256",
"slot": 48332168562525185806884758054388614910060623018875025120987491603435926351512, "slot": 48332168562525185806884758054388614910060623018875025120987491603435926351512,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 15 "value": 15,
"elems": {}
}
} }
},
"value": 2
}, },
"2": { "2": {
"name": "multidimensionalArray[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 12396694973890998440467380340983585058878106250672390494374587083972727727732, "slot": 12396694973890998440467380340983585058878106250672390494374587083972727727732,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 3,
"elems": { "elems": {
"0": { "0": {
"name": "multidimensionalArray[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 69037578548663760355678879060995014288537668748590083357305779656188235687653, "slot": 69037578548663760355678879060995014288537668748590083357305779656188235687653,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 16 "value": 16,
"elems": {}
}, },
"1": { "1": {
"name": "multidimensionalArray[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 69037578548663760355678879060995014288537668748590083357305779656188235687654, "slot": 69037578548663760355678879060995014288537668748590083357305779656188235687654,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 17 "value": 17,
"elems": {}
}, },
"2": { "2": {
"name": "multidimensionalArray[2]",
"type_string": "uint256", "type_string": "uint256",
"slot": 69037578548663760355678879060995014288537668748590083357305779656188235687655, "slot": 69037578548663760355678879060995014288537668748590083357305779656188235687655,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": 18 "value": 18,
"elems": {}
}
}
} }
},
"value": 3
} }
},
"value": 3
}, },
"dynamicArrayOfStructs": { "dynamicArrayOfStructs": {
"name": "dynamicArrayOfStructs",
"type_string": "StorageLayout.PackedStruct[]", "type_string": "StorageLayout.PackedStruct[]",
"slot": 17, "slot": 17,
"size": 256, "size": 256,
"offset": 0, "offset": 0,
"value": "0000000000000000000000000000000000000000000000000000000000000002",
"elems": { "elems": {
"0": { "0": {
"b": { "b": {
"name": "dynamicArrayOfStructs[0]",
"type_string": "bool", "type_string": "bool",
"slot": 22581645139872629890233439717971975110198959689450188087151966948260709403752, "slot": 22581645139872629890233439717971975110198959689450188087151966948260709403752,
"size": 8, "size": 8,
"offset": 0, "offset": 0,
"value": true "value": true,
"elems": {}
}, },
"a": { "a": {
"name": "dynamicArrayOfStructs[0]",
"type_string": "uint248", "type_string": "uint248",
"slot": 22581645139872629890233439717971975110198959689450188087151966948260709403752, "slot": 22581645139872629890233439717971975110198959689450188087151966948260709403752,
"size": 248, "size": 248,
"offset": 8, "offset": 8,
"value": 1 "value": 1,
"elems": {}
} }
}, },
"1": { "1": {
"b": { "b": {
"name": "dynamicArrayOfStructs[1]",
"type_string": "bool", "type_string": "bool",
"slot": 22581645139872629890233439717971975110198959689450188087151966948260709403753, "slot": 22581645139872629890233439717971975110198959689450188087151966948260709403753,
"size": 8, "size": 8,
"offset": 0, "offset": 0,
"value": false "value": false,
"elems": {}
}, },
"a": { "a": {
"name": "dynamicArrayOfStructs[1]",
"type_string": "uint248", "type_string": "uint248",
"slot": 22581645139872629890233439717971975110198959689450188087151966948260709403753, "slot": 22581645139872629890233439717971975110198959689450188087151966948260709403753,
"size": 248, "size": 248,
"offset": 8, "offset": 8,
"value": 10 "value": 10,
"elems": {}
}
} }
} }
},
"value": "0000000000000000000000000000000000000000000000000000000000000002"
}, },
"fixedArrayOfStructs": { "fixedArrayOfStructs": {
"name": "fixedArrayOfStructs",
"type_string": "StorageLayout.PackedStruct[3]", "type_string": "StorageLayout.PackedStruct[3]",
"slot": 18, "slot": 18,
"size": 768, "size": 768,
"offset": 0, "offset": 0,
"value": "0000000000000000000000000000000000000000000000000000000000000101",
"elems": { "elems": {
"0": { "0": {
"b": { "b": {
"name": "fixedArrayOfStructs[0]",
"type_string": "bool", "type_string": "bool",
"slot": 18, "slot": 18,
"size": 8, "size": 8,
"offset": 0, "offset": 0,
"value": true "value": true,
"elems": {}
}, },
"a": { "a": {
"name": "fixedArrayOfStructs[0]",
"type_string": "uint248", "type_string": "uint248",
"slot": 18, "slot": 18,
"size": 248, "size": 248,
"offset": 8, "offset": 8,
"value": 1 "value": 1,
"elems": {}
} }
}, },
"1": { "1": {
"b": { "b": {
"name": "fixedArrayOfStructs[1]",
"type_string": "bool", "type_string": "bool",
"slot": 19, "slot": 19,
"size": 8, "size": 8,
"offset": 0, "offset": 0,
"value": false "value": false,
"elems": {}
}, },
"a": { "a": {
"name": "fixedArrayOfStructs[1]",
"type_string": "uint248", "type_string": "uint248",
"slot": 19, "slot": 19,
"size": 248, "size": 248,
"offset": 8, "offset": 8,
"value": 10 "value": 10,
"elems": {}
} }
}, },
"2": { "2": {
"b": { "b": {
"name": "fixedArrayOfStructs[2]",
"type_string": "bool", "type_string": "bool",
"slot": 20, "slot": 20,
"size": 8, "size": 8,
"offset": 0, "offset": 0,
"value": false "value": false,
"elems": {}
}, },
"a": { "a": {
"name": "fixedArrayOfStructs[2]",
"type_string": "uint248", "type_string": "uint248",
"slot": 20, "slot": 20,
"size": 248, "size": 248,
"offset": 8, "offset": 8,
"value": 0 "value": 0,
"elems": {}
}
} }
} }
},
"value": "0000000000000000000000000000000000000000000000000000000000000101"
} }
} }

@ -14,6 +14,7 @@ from slither.tools.read_storage import SlitherReadStorage
try: try:
from web3 import Web3 from web3 import Web3
from web3.contract import Contract
except ImportError: except ImportError:
print("ERROR: in order to use slither-read-storage, you need to install web3") print("ERROR: in order to use slither-read-storage, you need to install web3")
print("$ pip3 install web3 --user\n") print("$ pip3 install web3 --user\n")
@ -67,14 +68,14 @@ def fixture_ganache() -> Generator[GanacheInstance, None, None]:
p.wait() p.wait()
def get_source_file(file_path): def get_source_file(file_path) -> str:
with open(file_path, "r", encoding="utf8") as f: with open(file_path, "r", encoding="utf8") as f:
source = f.read() source = f.read()
return source return source
def deploy_contract(w3, ganache, contract_bin, contract_abi): def deploy_contract(w3, ganache, contract_bin, contract_abi) -> Contract:
"""Deploy contract to the local ganache network""" """Deploy contract to the local ganache network"""
signed_txn = w3.eth.account.sign_transaction( signed_txn = w3.eth.account.sign_transaction(
dict( dict(
@ -96,7 +97,7 @@ def deploy_contract(w3, ganache, contract_bin, contract_abi):
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
@pytest.mark.usefixtures("web3", "ganache") @pytest.mark.usefixtures("web3", "ganache")
def test_read_storage(web3, ganache): def test_read_storage(web3, ganache) -> None:
assert web3.isConnected() assert web3.isConnected()
bin_path = os.path.join(STORAGE_TEST_ROOT, "StorageLayout.bin") bin_path = os.path.join(STORAGE_TEST_ROOT, "StorageLayout.bin")
abi_path = os.path.join(STORAGE_TEST_ROOT, "StorageLayout.abi") abi_path = os.path.join(STORAGE_TEST_ROOT, "StorageLayout.abi")
@ -114,9 +115,10 @@ def test_read_storage(web3, ganache):
srs.storage_address = address srs.storage_address = address
srs.get_all_storage_variables() srs.get_all_storage_variables()
srs.get_storage_layout() srs.get_storage_layout()
srs.get_slot_values() srs.walk_slot_info(srs.get_slot_values)
with open("storage_layout.json", "w", encoding="utf-8") as file: with open("storage_layout.json", "w", encoding="utf-8") as file:
json.dump(srs.slot_info, file, indent=4) slot_infos_json = srs.to_json()
json.dump(slot_infos_json, file, indent=4)
expected_file = os.path.join(STORAGE_TEST_ROOT, "TEST_storage_layout.json") expected_file = os.path.join(STORAGE_TEST_ROOT, "TEST_storage_layout.json")
actual_file = os.path.join(SLITHER_ROOT, "storage_layout.json") actual_file = os.path.join(SLITHER_ROOT, "storage_layout.json")
@ -132,8 +134,8 @@ def test_read_storage(web3, ganache):
path_list = re.findall(r"\['(.*?)'\]", change.path()) path_list = re.findall(r"\['(.*?)'\]", change.path())
path = "_".join(path_list) path = "_".join(path_list)
with open(f"{path}_expected.txt", "w", encoding="utf8") as f: with open(f"{path}_expected.txt", "w", encoding="utf8") as f:
f.write(change.t1) f.write(str(change.t1))
with open(f"{path}_actual.txt", "w", encoding="utf8") as f: with open(f"{path}_actual.txt", "w", encoding="utf8") as f:
f.write(change.t2) f.write(str(change.t2))
assert not diff assert not diff

Loading…
Cancel
Save