diff --git a/setup.py b/setup.py index ef9d81f203..920fd917e1 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ "eth-abi>=4.0.0", "eth-typing>=3.0.0", "eth-utils>=2.1.0", + "shtab>=1.7.1", ], extras_require={ "lint": [ diff --git a/slither/__main__.py b/slither/__main__.py index 633ad68cf3..c9dec71b06 100644 --- a/slither/__main__.py +++ b/slither/__main__.py @@ -18,6 +18,7 @@ from crytic_compile.platform.standard import generate_standard_export from crytic_compile.platform.etherscan import SUPPORTED_NETWORK from crytic_compile import compile_all, is_supported +import shtab from slither.detectors import all_detectors from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification @@ -293,7 +294,7 @@ def parse_filter_paths(args: argparse.Namespace, filter_path: bool) -> List[str] def parse_args( detector_classes: List[Type[AbstractDetector]], printer_classes: List[Type[AbstractPrinter]] ) -> argparse.Namespace: - usage = "slither target [flag]\n" + usage = "slither target [options]\n" usage += "\ntarget can be:\n" usage += "\t- file.sol // a Solidity file\n" usage += "\t- project_directory // a project directory. See https://github.com/crytic/crytic-compile/#crytic-compile for the supported platforms\n" @@ -305,7 +306,9 @@ def parse_args( usage=usage, ) - parser.add_argument("filename", help=argparse.SUPPRESS) + shtab.add_argument_to(parser) + + parser.add_argument("filename", metavar="target", help="File or project target, see above") cryticparser.init(parser) @@ -497,28 +500,28 @@ def parse_args( help='Export the results as a JSON file ("--json -" to export to stdout)', action="store", default=defaults_flag_in_config["json"], - ) + ).complete = shtab.FILE group_misc.add_argument( "--sarif", help='Export the results as a SARIF JSON file ("--sarif -" to export to stdout)', action="store", default=defaults_flag_in_config["sarif"], - ) + ).complete = shtab.FILE group_misc.add_argument( "--sarif-input", help="Sarif input (beta)", action="store", default=defaults_flag_in_config["sarif_input"], - ) + ).complete = shtab.FILE group_misc.add_argument( "--sarif-triage", help="Sarif triage (beta)", action="store", default=defaults_flag_in_config["sarif_triage"], - ) + ).complete = shtab.FILE group_misc.add_argument( "--json-types", @@ -534,13 +537,14 @@ def parse_args( help="Export the results as a zipped JSON file", action="store", default=defaults_flag_in_config["zip"], - ) + ).complete = shtab.FILE group_misc.add_argument( "--zip-type", help=f'Zip compression type. One of {",".join(ZIP_TYPES_ACCEPTED.keys())}. Default lzma', action="store", default=defaults_flag_in_config["zip_type"], + choices=list(ZIP_TYPES_ACCEPTED.keys()), ) group_misc.add_argument( @@ -572,7 +576,7 @@ def parse_args( action="store", dest="config_file", default=None, - ) + ).complete = shtab.FILE group_misc.add_argument( "--change-line-prefix", diff --git a/slither/tools/doctor/__main__.py b/slither/tools/doctor/__main__.py index f401781a77..a388e91046 100644 --- a/slither/tools/doctor/__main__.py +++ b/slither/tools/doctor/__main__.py @@ -3,6 +3,7 @@ import sys from crytic_compile import cryticparser +import shtab from slither.tools.doctor.utils import report_section from slither.tools.doctor.checks import ALL_CHECKS @@ -18,6 +19,8 @@ def parse_args() -> argparse.Namespace: usage="slither-doctor project", ) + shtab.add_argument_to(parser) + parser.add_argument("project", help="The codebase to be tested.") # Add default arguments from crytic-compile @@ -27,12 +30,12 @@ def parse_args() -> argparse.Namespace: def main() -> None: - # log on stdout to keep output in order - logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True) - args = parse_args() kwargs = vars(args) + # log on stdout to keep output in order + logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, force=True) + for check in ALL_CHECKS: with report_section(check.title): check.function(**kwargs) diff --git a/slither/tools/documentation/__main__.py b/slither/tools/documentation/__main__.py index 0244dd6c67..8ed6c628f9 100644 --- a/slither/tools/documentation/__main__.py +++ b/slither/tools/documentation/__main__.py @@ -3,6 +3,7 @@ import uuid from typing import Optional, Dict, List from crytic_compile import cryticparser +import shtab from slither import Slither from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.declarations import Function @@ -26,6 +27,8 @@ def parse_args() -> argparse.Namespace: usage="slither-documentation filename", ) + shtab.add_argument_to(parser) + parser.add_argument("project", help="The target directory/Solidity file.") parser.add_argument( diff --git a/slither/tools/erc_conformance/__main__.py b/slither/tools/erc_conformance/__main__.py index 1c9224eacb..ea2958dd3f 100644 --- a/slither/tools/erc_conformance/__main__.py +++ b/slither/tools/erc_conformance/__main__.py @@ -4,6 +4,7 @@ from typing import Any, Dict, List, Callable from crytic_compile import cryticparser +import shtab from slither import Slither from slither.core.declarations import Contract @@ -42,6 +43,8 @@ def parse_args() -> argparse.Namespace: usage="slither-check-erc project contractName", ) + shtab.add_argument_to(parser) + parser.add_argument("project", help="The codebase to be tested.") parser.add_argument( @@ -53,7 +56,8 @@ def parse_args() -> argparse.Namespace: "--erc", help=f"ERC to be tested, available {','.join(ERCS.keys())} (default ERC20)", action="store", - default="erc20", + default="ERC20", + choices=list(ERCS.keys()), ) parser.add_argument( @@ -61,7 +65,7 @@ def parse_args() -> argparse.Namespace: help='Export the results as a JSON file ("--json -" to export to stdout)', action="store", default=False, - ) + ).complete = shtab.FILE # Add default arguments from crytic-compile cryticparser.init(parser) diff --git a/slither/tools/flattening/__main__.py b/slither/tools/flattening/__main__.py index bf9856fe84..9ec73ec2a2 100644 --- a/slither/tools/flattening/__main__.py +++ b/slither/tools/flattening/__main__.py @@ -4,6 +4,7 @@ from crytic_compile import cryticparser from crytic_compile.utils.zip import ZIP_TYPES_ACCEPTED +import shtab from slither import Slither from slither.tools.flattening.flattening import ( @@ -28,6 +29,8 @@ def parse_args() -> argparse.Namespace: usage="slither-flat filename", ) + shtab.add_argument_to(parser) + parser.add_argument("filename", help="The filename of the contract or project to analyze.") parser.add_argument("--contract", help="Flatten one contract.", default=None) @@ -44,27 +47,28 @@ def parse_args() -> argparse.Namespace: "--dir", help=f"Export directory (default: {DEFAULT_EXPORT_PATH}).", default=None, - ) + ).complete = shtab.DIRECTORY group_export.add_argument( "--json", help='Export the results as a JSON file ("--json -" to export to stdout)', action="store", default=None, - ) + ).complete = shtab.FILE parser.add_argument( "--zip", help="Export all the files to a zip file", action="store", default=None, - ) + ).complete = shtab.FILE parser.add_argument( "--zip-type", help=f"Zip compression type. One of {','.join(ZIP_TYPES_ACCEPTED.keys())}. Default lzma", action="store", default=None, + choices=list(ZIP_TYPES_ACCEPTED.keys()), ) group_patching = parser.add_argument_group("Patching options") diff --git a/slither/tools/interface/__main__.py b/slither/tools/interface/__main__.py index 0705f0373a..0d7d09ba59 100644 --- a/slither/tools/interface/__main__.py +++ b/slither/tools/interface/__main__.py @@ -3,6 +3,7 @@ from pathlib import Path from crytic_compile import cryticparser +import shtab from slither import Slither from slither.utils.code_generation import generate_interface @@ -22,6 +23,8 @@ def parse_args() -> argparse.Namespace: usage=("slither-interface "), ) + shtab.add_argument_to(parser) + parser.add_argument( "contract_source", help="The name of the contract (case sensitive) followed by the deployed contract address if verified on etherscan or project directory/filename for local contracts.", diff --git a/slither/tools/kspec_coverage/__main__.py b/slither/tools/kspec_coverage/__main__.py index 19933e0feb..96b4ee5ec5 100644 --- a/slither/tools/kspec_coverage/__main__.py +++ b/slither/tools/kspec_coverage/__main__.py @@ -2,6 +2,7 @@ import logging import argparse from crytic_compile import cryticparser +import shtab from slither.tools.kspec_coverage.kspec_coverage import kspec_coverage logging.basicConfig() @@ -26,6 +27,8 @@ def parse_args() -> argparse.Namespace: usage="slither-kspec-coverage contract.sol kspec.md", ) + shtab.add_argument_to(parser) + parser.add_argument( "contract", help="The filename of the contract or truffle directory to analyze." ) @@ -45,7 +48,7 @@ def parse_args() -> argparse.Namespace: help='Export the results as a JSON file ("--json -" to export to stdout)', action="store", default=False, - ) + ).complete = shtab.FILE cryticparser.init(parser) diff --git a/slither/tools/mutator/__main__.py b/slither/tools/mutator/__main__.py index dea11676a6..df51b8a7bb 100644 --- a/slither/tools/mutator/__main__.py +++ b/slither/tools/mutator/__main__.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Type, List, Any, Optional, Union from crytic_compile import cryticparser +import shtab from slither import Slither from slither.tools.mutator.utils.testing_generated_mutant import run_test_cmd from slither.tools.mutator.mutators import all_mutators @@ -41,6 +42,8 @@ def parse_args() -> argparse.Namespace: usage="slither-mutate --test-cmd ", ) + shtab.add_argument_to(parser) + parser.add_argument("codebase", help="Codebase to analyze (.sol file, project directory, ...)") parser.add_argument( @@ -66,7 +69,7 @@ def parse_args() -> argparse.Namespace: # output directory argument parser.add_argument( "--output-dir", help="Name of output directory (by default 'mutation_campaign')" - ) + ).complete = shtab.DIRECTORY # to print just all the mutants parser.add_argument( diff --git a/slither/tools/possible_paths/__main__.py b/slither/tools/possible_paths/__main__.py index b993d266a1..3949924945 100644 --- a/slither/tools/possible_paths/__main__.py +++ b/slither/tools/possible_paths/__main__.py @@ -4,6 +4,7 @@ from argparse import ArgumentParser, Namespace from crytic_compile import cryticparser +import shtab from slither import Slither from slither.core.declarations import FunctionContract from slither.utils.colors import red @@ -27,6 +28,8 @@ def parse_args() -> Namespace: usage="possible_paths.py filename [contract.function targets]", ) + shtab.add_argument_to(parser) + parser.add_argument( "filename", help="The filename of the contract or truffle directory to analyze." ) diff --git a/slither/tools/properties/__main__.py b/slither/tools/properties/__main__.py index b5e5c911a3..12c210413c 100644 --- a/slither/tools/properties/__main__.py +++ b/slither/tools/properties/__main__.py @@ -4,6 +4,7 @@ from typing import Any from crytic_compile import cryticparser +import shtab from slither import Slither from slither.tools.properties.properties.erc20 import generate_erc20, ERC20_PROPERTIES @@ -73,6 +74,8 @@ def parse_args() -> argparse.Namespace: formatter_class=argparse.RawDescriptionHelpFormatter, ) + shtab.add_argument_to(parser) + parser.add_argument( "filename", help="The filename of the contract or project directory to analyze." ) diff --git a/slither/tools/read_storage/__main__.py b/slither/tools/read_storage/__main__.py index 3baa5d351a..5001ab792b 100644 --- a/slither/tools/read_storage/__main__.py +++ b/slither/tools/read_storage/__main__.py @@ -5,6 +5,7 @@ import argparse from crytic_compile import cryticparser +import shtab from slither import Slither from slither.exceptions import SlitherError @@ -29,6 +30,8 @@ def parse_args() -> argparse.Namespace: ), ) + shtab.add_argument_to(parser) + parser.add_argument( "contract_source", help="The deployed contract address if verified on etherscan. Prepend project directory for unverified contracts.", @@ -77,7 +80,7 @@ def parse_args() -> argparse.Namespace: "--json", action="store", help="Save the result in a JSON file.", - ) + ).complete = shtab.FILE parser.add_argument( "--value", diff --git a/slither/tools/similarity/__main__.py b/slither/tools/similarity/__main__.py index 86673fccd4..6d088966d0 100755 --- a/slither/tools/similarity/__main__.py +++ b/slither/tools/similarity/__main__.py @@ -5,6 +5,7 @@ import sys from crytic_compile import cryticparser +import shtab from slither.tools.similarity.info import info from slither.tools.similarity.test import test @@ -22,11 +23,15 @@ def parse_args() -> argparse.Namespace: description="Code similarity detection tool. For usage, see https://github.com/crytic/slither/wiki/Code-Similarity-detector" ) - parser.add_argument("mode", help="|".join(modes)) + shtab.add_argument_to(parser) + + parser.add_argument("mode", help="|".join(modes), choices=modes) parser.add_argument("model", help="model.bin") - parser.add_argument("--filename", action="store", dest="filename", help="contract.sol") + parser.add_argument( + "--filename", action="store", dest="filename", help="contract.sol" + ).complete = shtab.FILE parser.add_argument("--fname", action="store", dest="fname", help="Target function") @@ -51,7 +56,7 @@ def parse_args() -> argparse.Namespace: parser.add_argument( "--input", action="store", dest="input", help="File or directory used as input" - ) + ).complete = shtab.FILE parser.add_argument( "--version", diff --git a/slither/tools/slither_format/__main__.py b/slither/tools/slither_format/__main__.py index 85c0a3917f..83c9add011 100644 --- a/slither/tools/slither_format/__main__.py +++ b/slither/tools/slither_format/__main__.py @@ -2,6 +2,7 @@ import argparse import logging from crytic_compile import cryticparser +import shtab from slither import Slither from slither.utils.command_line import read_config_file from slither.tools.slither_format.slither_format import slither_format @@ -30,6 +31,8 @@ def parse_args() -> argparse.Namespace: """ parser = argparse.ArgumentParser(description="slither_format", usage="slither_format filename") + shtab.add_argument_to(parser) + parser.add_argument( "filename", help="The filename of the contract or truffle directory to analyze." ) @@ -60,7 +63,7 @@ def parse_args() -> argparse.Namespace: action="store", dest="config_file", default="slither.config.json", - ) + ).complete = shtab.FILE group_detector = parser.add_argument_group("Detectors") group_detector.add_argument( @@ -74,8 +77,8 @@ def parse_args() -> argparse.Namespace: group_detector.add_argument( "--exclude", - help="Comma-separated list of detectors to exclude," - "available detectors: {', '.join(d for d in available_detectors)}", + help="Comma-separated list of detectors to exclude, " + f"available detectors: {', '.join(d for d in available_detectors)}", action="store", dest="detectors_to_exclude", default="all", diff --git a/slither/tools/upgradeability/__main__.py b/slither/tools/upgradeability/__main__.py index 56b838b9c1..e54eb25c21 100644 --- a/slither/tools/upgradeability/__main__.py +++ b/slither/tools/upgradeability/__main__.py @@ -6,6 +6,7 @@ from typing import List, Any, Type, Dict, Tuple, Union, Sequence, Optional from crytic_compile import cryticparser +import shtab from slither import Slither @@ -36,6 +37,8 @@ def parse_args(check_classes: List[Type[AbstractCheck]]) -> argparse.Namespace: usage="slither-check-upgradeability contract.sol ContractName", ) + shtab.add_argument_to(parser) + group_checks = parser.add_argument_group("Checks") parser.add_argument("contract.sol", help="Codebase to analyze") @@ -47,14 +50,14 @@ def parse_args(check_classes: List[Type[AbstractCheck]]) -> argparse.Namespace: parser.add_argument("--new-contract-name", help="New contract name (if changed)") parser.add_argument( "--new-contract-filename", help="New implementation filename (if different)" - ) + ).complete = shtab.FILE parser.add_argument( "--json", help='Export the results as a JSON file ("--json -" to export to stdout)', action="store", default=False, - ) + ).complete = shtab.FILE group_checks.add_argument( "--detect",