From 85faa6ddaec39bd2ca7b04994b345be0fd2b481e Mon Sep 17 00:00:00 2001 From: alpharush <0xalpharush@protonmail.com> Date: Mon, 6 Nov 2023 13:40:37 -0600 Subject: [PATCH] WIP --- slither/detectors/_cli.py | 0 slither/printers/_cli.py | 0 slither/tools/flat/__init__.py | 0 slither/tools/flat/__main__.py | 53 +++ slither/tools/flat/_cli.py | 83 +++++ slither/tools/flat/export/__init__.py | 0 slither/tools/flat/export/export.py | 57 ++++ slither/tools/flat/flattening.py | 471 ++++++++++++++++++++++++++ slither/tools/read_storage/_cli.py | 167 +++++++++ 9 files changed, 831 insertions(+) create mode 100644 slither/detectors/_cli.py create mode 100644 slither/printers/_cli.py create mode 100644 slither/tools/flat/__init__.py create mode 100644 slither/tools/flat/__main__.py create mode 100644 slither/tools/flat/_cli.py create mode 100644 slither/tools/flat/export/__init__.py create mode 100644 slither/tools/flat/export/export.py create mode 100644 slither/tools/flat/flattening.py create mode 100644 slither/tools/read_storage/_cli.py diff --git a/slither/detectors/_cli.py b/slither/detectors/_cli.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/printers/_cli.py b/slither/printers/_cli.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/tools/flat/__init__.py b/slither/tools/flat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/tools/flat/__main__.py b/slither/tools/flat/__main__.py new file mode 100644 index 0000000000..e446a2d40a --- /dev/null +++ b/slither/tools/flat/__main__.py @@ -0,0 +1,53 @@ +import argparse +import logging +import sys + +from crytic_compile import cryticparser + +from slither import Slither +from slither.tools.flat.flattening import ( + Flattening, + Strategy, + STRATEGIES_NAMES, + DEFAULT_EXPORT_PATH, +) + +logging.basicConfig() +logger = logging.getLogger("Slither") +logger.setLevel(logging.INFO) + + +def main() -> None: + args = parse_args() + + slither = Slither(args.filename, **vars(args)) + + for compilation_unit in slither.compilation_units: + + flat = Flattening( + compilation_unit, + external_to_public=args.convert_external, + remove_assert=args.remove_assert, + convert_library_to_internal=args.convert_library_to_internal, + private_to_internal=args.convert_private, + export_path=args.dir, + pragma_solidity=args.pragma_solidity, + ) + + try: + strategy = Strategy[args.strategy] + except KeyError: + to_log = f"{args.strategy} is not a valid strategy, use: {STRATEGIES_NAMES} (default MostDerived)" + logger.error(to_log) + return + flat.export( + strategy=strategy, + target=args.contract, + json=args.json, + zip=args.zip, + zip_type=args.zip_type, + ) + + +if __name__ == "__main__": + main() diff --git a/slither/tools/flat/_cli.py b/slither/tools/flat/_cli.py new file mode 100644 index 0000000000..7f95b828ce --- /dev/null +++ b/slither/tools/flat/_cli.py @@ -0,0 +1,83 @@ +from argparse import ArgumentParser +from crytic_compile.utils.zip import ZIP_TYPES_ACCEPTED + +from slither.tools.flat.flattening import Strategy, STRATEGIES_NAMES, DEFAULT_EXPORT_PATH + + +def init_parser(sub_parser: ArgumentParser) -> None: + """Parse the underlying arguments for the program. + Returns: + The arguments for the program. + """ + parser = sub_parser.add_parser( + name="flat", + help="Contracts flattening. See https://github.com/crytic/slither/wiki/Contract-Flattening", + ) + + # parser.add_argument("filename", help="The filename of the contract or project to analyze.") # TODO remove? + + parser.add_argument("--contract", help="Flatten one contract.", default=None) + + parser.add_argument( + "--strategy", + help=f"Flatenning strategy: {STRATEGIES_NAMES} (default: MostDerived).", + default=Strategy.MostDerived.name, # pylint: disable=no-member + ) + + group_export = parser.add_argument_group("Export options") + + group_export.add_argument( + "--dir", + help=f"Export directory (default: {DEFAULT_EXPORT_PATH}).", + default=None, + ) + + group_export.add_argument( + "--json", + help='Export the results as a JSON file ("--json -" to export to stdout)', + action="store", + default=None, + ) + + parser.add_argument( + "--zip", + help="Export all the files to a zip file", + action="store", + default=None, + ) + + parser.add_argument( + "--zip-type", + help=f"Zip compression type. One of {','.join(ZIP_TYPES_ACCEPTED.keys())}. Default lzma", + action="store", + default=None, + ) + + group_patching = parser.add_argument_group("Patching options") + + group_patching.add_argument( + "--convert-external", help="Convert external to public.", action="store_true" + ) + + group_patching.add_argument( + "--convert-private", + help="Convert private variables to internal.", + action="store_true", + ) + + group_patching.add_argument( + "--convert-library-to-internal", + help="Convert external or public functions to internal in library.", + action="store_true", + ) + + group_patching.add_argument( + "--remove-assert", help="Remove call to assert().", action="store_true" + ) + + group_patching.add_argument( + "--pragma-solidity", + help="Set the solidity pragma with a given version.", + action="store", + default=None, + ) diff --git a/slither/tools/flat/export/__init__.py b/slither/tools/flat/export/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/slither/tools/flat/export/export.py b/slither/tools/flat/export/export.py new file mode 100644 index 0000000000..8b8ce73559 --- /dev/null +++ b/slither/tools/flat/export/export.py @@ -0,0 +1,57 @@ +import json +import logging + +# https://docs.python.org/3/library/zipfile.html#zipfile-objects +import zipfile +from collections import namedtuple +from typing import List + +ZIP_TYPES_ACCEPTED = { + "lzma": zipfile.ZIP_LZMA, + "stored": zipfile.ZIP_STORED, + "deflated": zipfile.ZIP_DEFLATED, + "bzip2": zipfile.ZIP_BZIP2, +} + +Export = namedtuple("Export", ["filename", "content"]) + +logger = logging.getLogger("Slither-flat") + + +def save_to_zip(files: List[Export], zip_filename: str, zip_type: str = "lzma"): + """ + Save projects to a zip + """ + logger.info(f"Export {zip_filename}") + with zipfile.ZipFile( + zip_filename, + "w", + compression=ZIP_TYPES_ACCEPTED.get(zip_type, zipfile.ZIP_LZMA), + ) as file_desc: + for f in files: + file_desc.writestr(str(f.filename), f.content) + + +def save_to_disk(files: List[Export]): + """ + Save projects to a zip + """ + for file in files: + with open(file.filename, "w", encoding="utf8") as f: + logger.info(f"Export {file.filename}") + f.write(file.content) + + +def export_as_json(files: List[Export], filename: str): + """ + Save projects to a zip + """ + + files_as_dict = {str(f.filename): f.content for f in files} + + if filename == "-": + print(json.dumps(files_as_dict)) + else: + logger.info(f"Export {filename}") + with open(filename, "w", encoding="utf8") as f: + json.dump(files_as_dict, f) diff --git a/slither/tools/flat/flattening.py b/slither/tools/flat/flattening.py new file mode 100644 index 0000000000..2c27be9907 --- /dev/null +++ b/slither/tools/flat/flattening.py @@ -0,0 +1,471 @@ +import logging +import re +import uuid +from collections import namedtuple +from enum import Enum as PythonEnum +from pathlib import Path +from typing import List, Set, Dict, Optional, Sequence + +from slither.core.compilation_unit import SlitherCompilationUnit +from slither.core.declarations import SolidityFunction, EnumContract, StructureContract +from slither.core.declarations.contract import Contract +from slither.core.declarations.function_top_level import FunctionTopLevel +from slither.core.declarations.top_level import TopLevel +from slither.core.declarations.solidity_variables import SolidityCustomRevert +from slither.core.solidity_types import MappingType, ArrayType +from slither.core.solidity_types.type import Type +from slither.core.solidity_types.user_defined_type import UserDefinedType +from slither.exceptions import SlitherException +from slither.slithir.operations import NewContract, TypeConversion, SolidityCall, InternalCall +from slither.tools.flat.export.export import ( + Export, + export_as_json, + save_to_zip, + save_to_disk, +) + +logger = logging.getLogger("Slither-flat") +logger.setLevel(logging.INFO) + +# index: where to start +# patch_type: +# - public_to_external: public to external (external-to-public) +# - calldata_to_memory: calldata to memory (external-to-public) +# - line_removal: remove the line (remove-assert) +Patch = namedtuple("PatchExternal", ["index", "patch_type"]) + + +class Strategy(PythonEnum): + MostDerived = 0 + OneFile = 1 + LocalImport = 2 + + +STRATEGIES_NAMES = ",".join([i.name for i in Strategy]) + +DEFAULT_EXPORT_PATH = Path("crytic-export/flattening") + + +class Flattening: + # pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-locals,too-few-public-methods + def __init__( + self, + compilation_unit: SlitherCompilationUnit, + external_to_public=False, + remove_assert=False, + convert_library_to_internal=False, + private_to_internal=False, + export_path: Optional[str] = None, + pragma_solidity: Optional[str] = None, + ): + self._source_codes: Dict[Contract, str] = {} + self._source_codes_top_level: Dict[TopLevel, str] = {} + self._compilation_unit: SlitherCompilationUnit = compilation_unit + self._external_to_public = external_to_public + self._remove_assert = remove_assert + self._use_abi_encoder_v2 = False + self._convert_library_to_internal = convert_library_to_internal + self._private_to_internal = private_to_internal + self._pragma_solidity = pragma_solidity + + self._export_path: Path = DEFAULT_EXPORT_PATH if export_path is None else Path(export_path) + + self._check_abi_encoder_v2() + + for contract in compilation_unit.contracts: + self._get_source_code(contract) + + self._get_source_code_top_level(compilation_unit.structures_top_level) + self._get_source_code_top_level(compilation_unit.enums_top_level) + self._get_source_code_top_level(compilation_unit.custom_errors) + self._get_source_code_top_level(compilation_unit.variables_top_level) + self._get_source_code_top_level(compilation_unit.functions_top_level) + + def _get_source_code_top_level(self, elems: Sequence[TopLevel]) -> None: + for elem in elems: + self._source_codes_top_level[elem] = elem.source_mapping.content + + def _check_abi_encoder_v2(self): + """ + Check if ABIEncoderV2 is required + Set _use_abi_encorder_v2 + :return: + """ + for p in self._compilation_unit.pragma_directives: + if "ABIEncoderV2" in str(p.directive): + self._use_abi_encoder_v2 = True + return + + def _get_source_code( + self, contract: Contract + ): # pylint: disable=too-many-branches,too-many-statements + """ + Save the source code of the contract in self._source_codes + Patch the source code + :param contract: + :return: + """ + src_mapping = contract.source_mapping + content = self._compilation_unit.core.source_code[src_mapping.filename.absolute] + start = src_mapping.start + end = src_mapping.start + src_mapping.length + + to_patch = [] + # interface must use external + if self._external_to_public and not contract.is_interface: + for f in contract.functions_declared: + # fallback must be external + if f.is_fallback or f.is_constructor_variables: + continue + if f.visibility == "external": + attributes_start = ( + f.parameters_src().source_mapping.start + + f.parameters_src().source_mapping.length + ) + attributes_end = f.returns_src().source_mapping.start + attributes = content[attributes_start:attributes_end] + regex = re.search(r"((\sexternal)\s+)|(\sexternal)$|(\)external)$", attributes) + if regex: + to_patch.append( + Patch( + attributes_start + regex.span()[0] + 1, + "public_to_external", + ) + ) + else: + raise SlitherException(f"External keyword not found {f.name} {attributes}") + + for var in f.parameters: + if var.location == "calldata": + calldata_start = var.source_mapping.start + calldata_end = calldata_start + var.source_mapping.length + calldata_idx = content[calldata_start:calldata_end].find(" calldata ") + to_patch.append( + Patch( + calldata_start + calldata_idx + 1, + "calldata_to_memory", + ) + ) + + if self._convert_library_to_internal and contract.is_library: + for f in contract.functions_declared: + visibility = "" + if f.visibility in ["external", "public"]: + visibility = f.visibility + attributes_start = ( + f.parameters_src().source_mapping["start"] + + f.parameters_src().source_mapping["length"] + ) + attributes_end = f.returns_src().source_mapping["start"] + attributes = content[attributes_start:attributes_end] + regex = ( + re.search(r"((\sexternal)\s+)|(\sexternal)$|(\)external)$", attributes) + if visibility == "external" + else re.search(r"((\spublic)\s+)|(\spublic)$|(\)public)$", attributes) + ) + if regex: + to_patch.append( + Patch( + attributes_start + regex.span()[0] + 1, + "external_to_internal" + if visibility == "external" + else "public_to_internal", + ) + ) + else: + raise SlitherException( + f"{visibility} keyword not found {f.name} {attributes}" + ) + + if self._private_to_internal: + for variable in contract.state_variables_declared: + if variable.visibility == "private": + attributes_start = variable.source_mapping.start + attributes_end = attributes_start + variable.source_mapping.length + attributes = content[attributes_start:attributes_end] + regex = re.search(r" private ", attributes) + if regex: + to_patch.append( + Patch( + attributes_start + regex.span()[0] + 1, + "private_to_internal", + ) + ) + else: + raise SlitherException( + f"private keyword not found {variable.name} {attributes}" + ) + + if self._remove_assert: + for function in contract.functions_and_modifiers_declared: + for node in function.nodes: + for ir in node.irs: + if isinstance(ir, SolidityCall) and ir.function == SolidityFunction( + "assert(bool)" + ): + to_patch.append(Patch(node.source_mapping.start, "line_removal")) + logger.info( + f"Code commented: {node.expression} ({node.source_mapping})" + ) + + to_patch.sort(key=lambda x: x.index, reverse=True) + + content = content[start:end] + for patch in to_patch: + patch_type = patch.patch_type + index = patch.index + index = index - start + if patch_type == "public_to_external": + content = content[:index] + "public" + content[index + len("external") :] + elif patch_type == "external_to_internal": + content = content[:index] + "internal" + content[index + len("external") :] + elif patch_type == "public_to_internal": + content = content[:index] + "internal" + content[index + len("public") :] + elif patch_type == "private_to_internal": + content = content[:index] + "internal" + content[index + len("private") :] + elif patch_type == "calldata_to_memory": + content = content[:index] + "memory" + content[index + len("calldata") :] + else: + assert patch_type == "line_removal" + content = content[:index] + " // " + content[index:] + + self._source_codes[contract] = content + + def _pragmas(self) -> str: + """ + Return the required pragmas + :return: + """ + ret = "" + if self._pragma_solidity: + ret += f"pragma solidity {self._pragma_solidity};\n" + else: + # TODO support multiple compiler version + ret += f"pragma solidity {list(self._compilation_unit.crytic_compile.compilation_units.values())[0].compiler_version.version};\n" + + if self._use_abi_encoder_v2: + ret += "pragma experimental ABIEncoderV2;\n" + return ret + + def _export_from_type( + self, + t: Type, + contract: Contract, + exported: Set[str], + list_contract: Set[Contract], + list_top_level: Set[TopLevel], + ): + if isinstance(t, UserDefinedType): + t_type = t.type + if isinstance(t_type, TopLevel): + list_top_level.add(t_type) + elif isinstance(t_type, (EnumContract, StructureContract)): + if t_type.contract != contract and t_type.contract not in exported: + self._export_list_used_contracts( + t_type.contract, exported, list_contract, list_top_level + ) + else: + assert isinstance(t.type, Contract) + if t.type != contract and t.type not in exported: + self._export_list_used_contracts( + t.type, exported, list_contract, list_top_level + ) + elif isinstance(t, MappingType): + self._export_from_type(t.type_from, contract, exported, list_contract, list_top_level) + self._export_from_type(t.type_to, contract, exported, list_contract, list_top_level) + elif isinstance(t, ArrayType): + self._export_from_type(t.type, contract, exported, list_contract, list_top_level) + + def _export_list_used_contracts( # pylint: disable=too-many-branches + self, + contract: Contract, + exported: Set[str], + list_contract: Set[Contract], + list_top_level: Set[TopLevel], + ): + # TODO: investigate why this happen + if not isinstance(contract, Contract): + return + if contract.name in exported: + return + exported.add(contract.name) + for inherited in contract.inheritance: + self._export_list_used_contracts(inherited, exported, list_contract, list_top_level) + + # Find all the external contracts called + externals = contract.all_library_calls + contract.all_high_level_calls + # externals is a list of (contract, function) + # We also filter call to itself to avoid infilite loop + externals = list({e[0] for e in externals if e[0] != contract}) + + for inherited in externals: + self._export_list_used_contracts(inherited, exported, list_contract, list_top_level) + + for list_libs in contract.using_for.values(): + for lib_candidate_type in list_libs: + if isinstance(lib_candidate_type, UserDefinedType): + lib_candidate = lib_candidate_type.type + if isinstance(lib_candidate, Contract): + self._export_list_used_contracts( + lib_candidate, exported, list_contract, list_top_level + ) + + # Find all the external contracts use as a base type + local_vars = [] + for f in contract.functions_declared: + local_vars += f.variables + + for v in contract.variables + local_vars: + self._export_from_type(v.type, contract, exported, list_contract, list_top_level) + + for s in contract.structures: + for elem in s.elems.values(): + self._export_from_type(elem.type, contract, exported, list_contract, list_top_level) + + # Find all convert and "new" operation that can lead to use an external contract + for f in contract.functions_declared: + for ir in f.slithir_operations: + if isinstance(ir, NewContract): + if ir.contract_created != contract and not ir.contract_created in exported: + self._export_list_used_contracts( + ir.contract_created, exported, list_contract, list_top_level + ) + if isinstance(ir, TypeConversion): + self._export_from_type( + ir.type, contract, exported, list_contract, list_top_level + ) + + for read in ir.read: + if isinstance(read, TopLevel): + list_top_level.add(read) + if isinstance(ir, InternalCall) and isinstance(ir.function, FunctionTopLevel): + list_top_level.add(ir.function) + if ( + isinstance(ir, SolidityCall) + and isinstance(ir.function, SolidityCustomRevert) + and isinstance(ir.function.custom_error, TopLevel) + ): + list_top_level.add(ir.function.custom_error) + + list_contract.add(contract) + + def _export_contract_with_inheritance(self, contract) -> Export: + list_contracts: Set[Contract] = set() # will contain contract itself + list_top_level: Set[TopLevel] = set() + self._export_list_used_contracts(contract, set(), list_contracts, list_top_level) + path = Path(self._export_path, f"{contract.name}_{uuid.uuid4()}.sol") + + content = "" + content += self._pragmas() + + for listed_top_level in list_top_level: + content += self._source_codes_top_level[listed_top_level] + content += "\n" + + for listed_contract in list_contracts: + content += self._source_codes[listed_contract] + content += "\n" + + return Export(filename=path, content=content) + + def _export_most_derived(self) -> List[Export]: + ret: List[Export] = [] + for contract in self._compilation_unit.contracts_derived: + ret.append(self._export_contract_with_inheritance(contract)) + return ret + + def _export_all(self) -> List[Export]: + path = Path(self._export_path, "export.sol") + + content = "" + content += self._pragmas() + + for top_level_content in self._source_codes_top_level.values(): + content += "\n" + content += top_level_content + content += "\n" + + contract_seen = set() + contract_to_explore = list(self._compilation_unit.contracts) + + # We only need the inheritance order here, as solc can compile + # a contract that use another contract type (ex: state variable) that he has not seen yet + while contract_to_explore: + next_to_explore = contract_to_explore.pop(0) + + if not next_to_explore.inheritance or all( + (father in contract_seen for father in next_to_explore.inheritance) + ): + content += "\n" + content += self._source_codes[next_to_explore] + content += "\n" + contract_seen.add(next_to_explore) + else: + contract_to_explore.append(next_to_explore) + + return [Export(filename=path, content=content)] + + def _export_with_import(self) -> List[Export]: + exports: List[Export] = [] + for contract in self._compilation_unit.contracts: + list_contracts: Set[Contract] = set() # will contain contract itself + list_top_level: Set[TopLevel] = set() + self._export_list_used_contracts(contract, set(), list_contracts, list_top_level) + + if list_top_level: + logger.info( + "Top level objects are not yet supported with the local import flattening" + ) + for elem in list_top_level: + logger.info(f"Missing {elem} for {contract.name}") + + path = Path(self._export_path, f"{contract.name}.sol") + + content = "" + content += self._pragmas() + for used_contract in list_contracts: + if used_contract != contract: + content += f"import './{used_contract.name}.sol';\n" + content += "\n" + content += self._source_codes[contract] + content += "\n" + exports.append(Export(filename=path, content=content)) + return exports + + def export( # pylint: disable=too-many-arguments,too-few-public-methods + self, + strategy: Strategy, + target: Optional[str] = None, + json: Optional[str] = None, + zip: Optional[str] = None, # pylint: disable=redefined-builtin + zip_type: Optional[str] = None, + ): + + if not self._export_path.exists(): + self._export_path.mkdir(parents=True) + + exports: List[Export] = [] + if target is None: + if strategy == Strategy.MostDerived: + exports = self._export_most_derived() + elif strategy == Strategy.OneFile: + exports = self._export_all() + elif strategy == Strategy.LocalImport: + exports = self._export_with_import() + else: + contracts = self._compilation_unit.get_contract_from_name(target) + if len(contracts) == 0: + logger.error(f"{target} not found") + return + exports = [] + for contract in contracts: + exports.append(self._export_contract_with_inheritance(contract)) + + if json: + export_as_json(exports, json) + + elif zip: + save_to_zip(exports, zip, zip_type) + + else: + save_to_disk(exports) diff --git a/slither/tools/read_storage/_cli.py b/slither/tools/read_storage/_cli.py new file mode 100644 index 0000000000..ede99bcb5e --- /dev/null +++ b/slither/tools/read_storage/_cli.py @@ -0,0 +1,167 @@ +""" +Tool to read on-chain storage from EVM +""" +import json +import argparse +from argparse import ArgumentParser + +from crytic_compile import cryticparser + +from slither import Slither +from slither.tools.read_storage.read_storage import SlitherReadStorage, RpcInfo + + +def init_parser(sub_parser: ArgumentParser) -> None: + """Parse the underlying arguments for the program. + Returns: + The arguments for the program. + """ + parser = sub_parser.add_parser( + name="read-storage", + help="Read a variable's value from storage for a deployed contract", + usage=( + "\nTo retrieve a single variable's value:\n" + + "\tslither-read-storage $TARGET address --variable-name $NAME\n" + + "To retrieve a contract's storage layout:\n" + + "\tslither-read-storage $TARGET address --contract-name $NAME --json storage_layout.json\n" + + "To retrieve a contract's storage layout and values:\n" + + "\tslither-read-storage $TARGET address --contract-name $NAME --json storage_layout.json --value\n" + + "TARGET can be a contract address or project directory" + ), + ) + + parser.add_argument( + "contract_source", + help="The deployed contract address if verified on etherscan. Prepend project directory for unverified contracts.", + nargs="+", + ) + + parser.add_argument( + "--variable-name", + help="The name of the variable whose value will be returned.", + default=None, + ) + + parser.add_argument("--rpc-url", help="An endpoint for web3 requests.") + + parser.add_argument( + "--key", + help="The key/ index whose value will be returned from a mapping or array.", + default=None, + ) + + parser.add_argument( + "--deep-key", + help="The key/ index whose value will be returned from a deep mapping or multidimensional array.", + default=None, + ) + + parser.add_argument( + "--struct-var", + help="The name of the variable whose value will be returned from a struct.", + default=None, + ) + + parser.add_argument( + "--storage-address", + help="The address of the storage contract (if a proxy pattern is used).", + default=None, + ) + + parser.add_argument( + "--contract-name", + help="The name of the logic contract.", + default=None, + ) + + parser.add_argument( + "--json", + action="store", + help="Save the result in a JSON file.", + ) + + parser.add_argument( + "--value", + action="store_true", + 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( + "--block", + help="The block number to read storage from. Requires an archive node to be provided as the RPC url.", + default="latest", + ) + + parser.add_argument( + "--unstructured", + action="store_true", + help="Include unstructured storage slots", + ) + + # args = parser.parse_args() + + # if len(args.contract_source) == 2: + # # Source code is file.sol or project directory + # source_code, target = args.contract_source + # slither = Slither(source_code, **vars(args)) + # else: + # # Source code is published and retrieved via etherscan + # target = args.contract_source[0] + # slither = Slither(target, **vars(args)) + + # if args.contract_name: + # contracts = slither.get_contract_from_name(args.contract_name) + # else: + # contracts = slither.contracts + + # rpc_info = None + # if args.rpc_url: + # valid = ["latest", "earliest", "pending", "safe", "finalized"] + # block = args.block if args.block in valid else int(args.block) + # rpc_info = RpcInfo(args.rpc_url, block) + + # srs = SlitherReadStorage(contracts, args.max_depth, rpc_info) + # srs.unstructured = bool(args.unstructured) + # # Remove target prefix e.g. rinkeby:0x0 -> 0x0. + # address = target[target.find(":") + 1 :] + # # Default to implementation address unless a storage address is given. + # if not args.storage_address: + # args.storage_address = address + # srs.storage_address = args.storage_address + + # if args.variable_name: + # # Use a lambda func to only return variables that have same name as target. + # # x is a tuple (`Contract`, `StateVariable`). + # srs.get_all_storage_variables(lambda x: bool(x[1].name == args.variable_name)) + # 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. + # if args.value: + # assert args.rpc_url + # srs.walk_slot_info(srs.get_slot_values) + + # if args.table: + # srs.walk_slot_info(srs.convert_slot_info_to_rows) + # print(srs.table) + + # if args.json: + # with open(args.json, "w", encoding="utf-8") as file: + # slot_infos_json = srs.to_json() + # json.dump(slot_infos_json, file, indent=4)