Merge branch 'dev' into dev-fix-usingfor

pull/1378/head
Josselin Feist 2 years ago
commit aa68cacdce
  1. 1
      .github/workflows/ci.yml
  2. 2
      .github/workflows/features.yml
  3. 8
      Dockerfile
  4. 9
      plugin_example/slither_my_plugin/__init__.py
  5. 6
      scripts/ci_test_cli.sh
  6. 15
      scripts/ci_test_path_filtering.sh
  7. 2
      scripts/ci_test_truffle.sh
  8. 106
      slither/__main__.py
  9. 14
      slither/core/declarations/structure.py
  10. 6
      slither/core/expressions/literal.py
  11. 4
      slither/core/expressions/tuple_expression.py
  12. 2
      slither/core/solidity_types/array_type.py
  13. 8
      slither/core/source_mapping/source_mapping.py
  14. 22
      slither/detectors/attributes/incorrect_solc.py
  15. 12
      slither/slither.py
  16. 8
      slither/slithir/operations/call.py
  17. 8
      slither/slithir/tmp_operations/tmp_call.py
  18. 12
      slither/slithir/tmp_operations/tmp_new_elementary_type.py
  19. 3
      slither/slithir/variables/constant.py
  20. 2
      slither/solc_parsing/declarations/modifier.py
  21. 4
      slither/tools/demo/__main__.py
  22. 9
      slither/tools/erc_conformance/__main__.py
  23. 15
      slither/tools/erc_conformance/erc/ercs.py
  24. 4
      slither/tools/flattening/__main__.py
  25. 4
      slither/tools/kspec_coverage/__main__.py
  26. 4
      slither/tools/kspec_coverage/kspec_coverage.py
  27. 13
      slither/tools/mutator/__main__.py
  28. 7
      slither/tools/possible_paths/__main__.py
  29. 45
      slither/tools/possible_paths/possible_paths.py
  30. 19
      slither/tools/properties/__main__.py
  31. 8
      slither/tools/properties/platforms/truffle.py
  32. 2
      slither/tools/properties/utils.py
  33. 10
      slither/tools/read_storage/README.md
  34. 4
      slither/tools/read_storage/__main__.py
  35. 4
      slither/tools/similarity/__main__.py
  36. 2
      slither/tools/similarity/encode.py
  37. 3
      slither/tools/similarity/info.py
  38. 3
      slither/tools/similarity/plot.py
  39. 3
      slither/tools/similarity/train.py
  40. 4
      slither/tools/slither_format/__main__.py
  41. 18
      slither/tools/slither_format/slither_format.py
  42. 77
      slither/tools/upgradeability/__main__.py
  43. 20
      slither/tools/upgradeability/utils/command_line.py
  44. 9
      slither/utils/arithmetic.py
  45. 59
      slither/utils/command_line.py
  46. 23
      slither/utils/integer_conversion.py
  47. 4
      slither/utils/output.py
  48. 8
      slither/utils/output_capture.py
  49. 14
      slither/visitors/expression/constants_folding.py
  50. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.0-compact.zip
  51. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.0-legacy.zip
  52. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.1-compact.zip
  53. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.1-legacy.zip
  54. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.2-compact.zip
  55. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.2-legacy.zip
  56. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.3-compact.zip
  57. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.3-legacy.zip
  58. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.4-compact.zip
  59. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.4-legacy.zip
  60. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.5-compact.zip
  61. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.5-legacy.zip
  62. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.6-compact.zip
  63. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.6-legacy.zip
  64. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.0-compact.zip
  65. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.1-compact.zip
  66. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.10-compact.zip
  67. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.11-compact.zip
  68. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.12-compact.zip
  69. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.13-compact.zip
  70. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.14-compact.zip
  71. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.15-compact.zip
  72. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.2-compact.zip
  73. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.3-compact.zip
  74. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.4-compact.zip
  75. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.5-compact.zip
  76. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.6-compact.zip
  77. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.7-compact.zip
  78. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.8-compact.zip
  79. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.9-compact.zip
  80. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.0-compact.zip
  81. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.0-legacy.zip
  82. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.1-compact.zip
  83. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.1-legacy.zip
  84. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.10-compact.zip
  85. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.10-legacy.zip
  86. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.11-compact.zip
  87. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.11-legacy.zip
  88. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.12-compact.zip
  89. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.12-legacy.zip
  90. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.13-compact.zip
  91. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.13-legacy.zip
  92. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.14-compact.zip
  93. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.14-legacy.zip
  94. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.15-compact.zip
  95. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.15-legacy.zip
  96. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.16-compact.zip
  97. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.16-legacy.zip
  98. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.17-compact.zip
  99. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.17-legacy.zip
  100. BIN
      tests/ast-parsing/compile/modifier-all.sol-0.4.18-compact.zip
  101. Some files were not shown because too many files have changed in this diff Show More

@ -25,6 +25,7 @@ jobs:
type: ["cli", type: ["cli",
"dapp", "dapp",
"data_dependency", "data_dependency",
"path_filtering",
# "embark", # "embark",
"erc", "erc",
# "etherlime", # "etherlime",

@ -45,7 +45,7 @@ jobs:
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest tests/test_features.py pytest tests/test_features.py
pytest tests/test_constant_folding_unary.py pytest tests/test_constant_folding.py
pytest tests/slithir/test_ternary_expressions.py pytest tests/slithir/test_ternary_expressions.py
pytest tests/test_functions_ids.py pytest tests/test_functions_ids.py
pytest tests/test_function.py pytest tests/test_function.py

@ -26,6 +26,14 @@ RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y --no-install-recommends python3-pip \ && apt-get install -y --no-install-recommends python3-pip \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# improve compatibility with amd64 solc in non-amd64 environments (e.g. Docker Desktop on M1 Mac)
ENV QEMU_LD_PREFIX=/usr/x86_64-linux-gnu
RUN if [ ! "$(uname -m)" = "x86_64" ]; then \
export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get install -y --no-install-recommends libc6-amd64-cross \
&& rm -rf /var/lib/apt/lists/*; fi
RUN useradd -m slither RUN useradd -m slither
USER slither USER slither

@ -1,8 +1,13 @@
from typing import Tuple, List, Type
from slither_my_plugin.detectors.example import Example from slither_my_plugin.detectors.example import Example
from slither.detectors.abstract_detector import AbstractDetector
from slither.printers.abstract_printer import AbstractPrinter
def make_plugin(): def make_plugin() -> Tuple[List[Type[AbstractDetector]], List[Type[AbstractPrinter]]]:
plugin_detectors = [Example] plugin_detectors = [Example]
plugin_printers = [] plugin_printers: List[Type[AbstractPrinter]] = []
return plugin_detectors, plugin_printers return plugin_detectors, plugin_printers

@ -4,17 +4,17 @@
solc-select use 0.7.0 solc-select use 0.7.0
if ! slither "tests/config/test.sol" --solc-ast; then if ! slither "tests/config/test.sol" --solc-ast --no-fail-pedantic; then
echo "--solc-ast failed" echo "--solc-ast failed"
exit 1 exit 1
fi fi
if ! slither "tests/config/test.sol" --solc-disable-warnings; then if ! slither "tests/config/test.sol" --solc-disable-warnings --no-fail-pedantic; then
echo "--solc-disable-warnings failed" echo "--solc-disable-warnings failed"
exit 1 exit 1
fi fi
if ! slither "tests/config/test.sol" --disable-color; then if ! slither "tests/config/test.sol" --disable-color --no-fail-pedantic; then
echo "--disable-color failed" echo "--disable-color failed"
exit 1 exit 1
fi fi

@ -0,0 +1,15 @@
#!/usr/bin/env bash
### Test path filtering across POSIX and Windows
solc-select use 0.8.0
slither "tests/test_path_filtering/test_path_filtering.sol" --config "tests/test_path_filtering/slither.config.json" > "output.txt" 2>&1
if ! grep -q "0 result(s) found" "output.txt"
then
echo "Path filtering across POSIX and Windows failed"
rm output.txt
exit 5
else
rm output.txt
fi

@ -14,7 +14,7 @@ nvm use --lts
npm install -g truffle npm install -g truffle
truffle unbox metacoin truffle unbox metacoin
if ! slither .; then if ! slither . --no-fail-pedantic; then
echo "Truffle test failed" echo "Truffle test failed"
exit 1 exit 1
fi fi

@ -10,11 +10,11 @@ import os
import pstats import pstats
import sys import sys
import traceback import traceback
from typing import Tuple, Optional, List, Dict from typing import Tuple, Optional, List, Dict, Type, Union, Any, Sequence
from pkg_resources import iter_entry_points, require from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export from crytic_compile.platform.standard import generate_standard_export
from crytic_compile.platform.etherscan import SUPPORTED_NETWORK from crytic_compile.platform.etherscan import SUPPORTED_NETWORK
from crytic_compile import compile_all, is_supported from crytic_compile import compile_all, is_supported
@ -55,10 +55,10 @@ logger = logging.getLogger("Slither")
def process_single( def process_single(
target: str, target: Union[str, CryticCompile],
args: argparse.Namespace, args: argparse.Namespace,
detector_classes: List[AbstractDetector], detector_classes: List[Type[AbstractDetector]],
printer_classes: List[AbstractPrinter], printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]: ) -> Tuple[Slither, List[Dict], List[Dict], int]:
""" """
The core high-level code for running Slither static analysis. The core high-level code for running Slither static analysis.
@ -80,8 +80,8 @@ def process_single(
def process_all( def process_all(
target: str, target: str,
args: argparse.Namespace, args: argparse.Namespace,
detector_classes: List[AbstractDetector], detector_classes: List[Type[AbstractDetector]],
printer_classes: List[AbstractPrinter], printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[List[Slither], List[Dict], List[Dict], int]: ) -> Tuple[List[Slither], List[Dict], List[Dict], int]:
compilations = compile_all(target, **vars(args)) compilations = compile_all(target, **vars(args))
slither_instances = [] slither_instances = []
@ -109,8 +109,8 @@ def process_all(
def _process( def _process(
slither: Slither, slither: Slither,
detector_classes: List[AbstractDetector], detector_classes: List[Type[AbstractDetector]],
printer_classes: List[AbstractPrinter], printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]: ) -> Tuple[Slither, List[Dict], List[Dict], int]:
for detector_cls in detector_classes: for detector_cls in detector_classes:
slither.register_detector(detector_cls) slither.register_detector(detector_cls)
@ -137,13 +137,14 @@ def _process(
return slither, results_detectors, results_printers, analyzed_contracts_count return slither, results_detectors, results_printers, analyzed_contracts_count
# TODO: delete me?
def process_from_asts( def process_from_asts(
filenames: List[str], filenames: List[str],
args: argparse.Namespace, args: argparse.Namespace,
detector_classes: List[AbstractDetector], detector_classes: List[Type[AbstractDetector]],
printer_classes: List[AbstractPrinter], printer_classes: List[Type[AbstractPrinter]],
): ) -> Tuple[Slither, List[Dict], List[Dict], int]:
all_contracts = [] all_contracts: List[str] = []
for filename in filenames: for filename in filenames:
with open(filename, encoding="utf8") as file_open: with open(filename, encoding="utf8") as file_open:
@ -162,13 +163,15 @@ def process_from_asts(
################################################################################### ###################################################################################
def get_detectors_and_printers(): def get_detectors_and_printers() -> Tuple[
List[Type[AbstractDetector]], List[Type[AbstractPrinter]]
]:
detectors = [getattr(all_detectors, name) for name in dir(all_detectors)] detectors_ = [getattr(all_detectors, name) for name in dir(all_detectors)]
detectors = [d for d in detectors if inspect.isclass(d) and issubclass(d, AbstractDetector)] detectors = [d for d in detectors_ if inspect.isclass(d) and issubclass(d, AbstractDetector)]
printers = [getattr(all_printers, name) for name in dir(all_printers)] printers_ = [getattr(all_printers, name) for name in dir(all_printers)]
printers = [p for p in printers if inspect.isclass(p) and issubclass(p, AbstractPrinter)] printers = [p for p in printers_ if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
# Handle plugins! # Handle plugins!
for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None): for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None):
@ -194,8 +197,8 @@ def get_detectors_and_printers():
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def choose_detectors( def choose_detectors(
args: argparse.Namespace, all_detector_classes: List[AbstractDetector] args: argparse.Namespace, all_detector_classes: List[Type[AbstractDetector]]
) -> List[AbstractDetector]: ) -> List[Type[AbstractDetector]]:
# If detectors are specified, run only these ones # If detectors are specified, run only these ones
detectors_to_run = [] detectors_to_run = []
@ -245,8 +248,8 @@ def choose_detectors(
def choose_printers( def choose_printers(
args: argparse.Namespace, all_printer_classes: List[AbstractPrinter] args: argparse.Namespace, all_printer_classes: List[Type[AbstractPrinter]]
) -> List[AbstractPrinter]: ) -> List[Type[AbstractPrinter]]:
printers_to_run = [] printers_to_run = []
# disable default printer # disable default printer
@ -273,13 +276,16 @@ def choose_printers(
################################################################################### ###################################################################################
def parse_filter_paths(args): def parse_filter_paths(args: argparse.Namespace) -> List[str]:
if args.filter_paths: if args.filter_paths:
return args.filter_paths.split(",") return args.filter_paths.split(",")
return [] return []
def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-statements # pylint: disable=too-many-statements
def parse_args(
detector_classes: List[Type[AbstractDetector]], printer_classes: List[Type[AbstractPrinter]]
) -> argparse.Namespace:
usage = "slither target [flag]\n" usage = "slither target [flag]\n"
usage += "\ntarget can be:\n" usage += "\ntarget can be:\n"
@ -397,11 +403,19 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_detector.add_argument( group_detector.add_argument(
"--fail-pedantic", "--fail-pedantic",
help="Fail if any finding is detected", help="Return the number of findings in the exit code",
action="store_true", action="store_true",
default=defaults_flag_in_config["fail_pedantic"], default=defaults_flag_in_config["fail_pedantic"],
) )
group_detector.add_argument(
"--no-fail-pedantic",
help="Do not return the number of findings in the exit code. Opposite of --fail-pedantic",
dest="fail_pedantic",
action="store_false",
required=False,
)
group_detector.add_argument( group_detector.add_argument(
"--fail-low", "--fail-low",
help="Fail if low or greater impact finding is detected", help="Fail if low or greater impact finding is detected",
@ -614,7 +628,9 @@ class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods
class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-methods class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
detectors, _ = get_detectors_and_printers() detectors, _ = get_detectors_and_printers()
detector_types_json = output_detectors_json(detectors) detector_types_json = output_detectors_json(detectors)
print(json.dumps(detector_types_json)) print(json.dumps(detector_types_json))
@ -622,22 +638,38 @@ class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-meth
class ListPrinters(argparse.Action): # pylint: disable=too-few-public-methods class ListPrinters(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
_, printers = get_detectors_and_printers() _, printers = get_detectors_and_printers()
output_printers(printers) output_printers(printers)
parser.exit() parser.exit()
class OutputMarkdown(argparse.Action): # pylint: disable=too-few-public-methods class OutputMarkdown(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, args, values, option_string=None): def __call__(
self,
parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> None:
detectors, printers = get_detectors_and_printers() detectors, printers = get_detectors_and_printers()
assert isinstance(values, str)
output_to_markdown(detectors, printers, values) output_to_markdown(detectors, printers, values)
parser.exit() parser.exit()
class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, args, values, option_string=None): def __call__(
self,
parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> None:
detectors, _ = get_detectors_and_printers() detectors, _ = get_detectors_and_printers()
assert isinstance(values, str)
output_wiki(detectors, values) output_wiki(detectors, values)
parser.exit() parser.exit()
@ -670,7 +702,7 @@ class FormatterCryticCompile(logging.Formatter):
################################################################################### ###################################################################################
def main(): def main() -> None:
# Codebase with complex domninators can lead to a lot of SSA recursive call # Codebase with complex domninators can lead to a lot of SSA recursive call
sys.setrecursionlimit(1500) sys.setrecursionlimit(1500)
@ -681,8 +713,9 @@ def main():
# pylint: disable=too-many-statements,too-many-branches,too-many-locals # pylint: disable=too-many-statements,too-many-branches,too-many-locals
def main_impl( def main_impl(
all_detector_classes: List[AbstractDetector], all_printer_classes: List[AbstractPrinter] all_detector_classes: List[Type[AbstractDetector]],
): all_printer_classes: List[Type[AbstractPrinter]],
) -> None:
""" """
:param all_detector_classes: A list of all detectors that can be included/excluded. :param all_detector_classes: A list of all detectors that can be included/excluded.
:param all_printer_classes: A list of all printers that can be included. :param all_printer_classes: A list of all printers that can be included.
@ -748,8 +781,8 @@ def main_impl(
crytic_compile_error.propagate = False crytic_compile_error.propagate = False
crytic_compile_error.setLevel(logging.INFO) crytic_compile_error.setLevel(logging.INFO)
results_detectors = [] results_detectors: List[Dict] = []
results_printers = [] results_printers: List[Dict] = []
try: try:
filename = args.filename filename = args.filename
@ -798,6 +831,7 @@ def main_impl(
if "compilations" in args.json_types: if "compilations" in args.json_types:
compilation_results = [] compilation_results = []
for slither_instance in slither_instances: for slither_instance in slither_instances:
assert slither_instance.crytic_compile
compilation_results.append( compilation_results.append(
generate_standard_export(slither_instance.crytic_compile) generate_standard_export(slither_instance.crytic_compile)
) )
@ -848,7 +882,7 @@ def main_impl(
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
output_error = traceback.format_exc() output_error = traceback.format_exc()
logging.error(traceback.print_exc()) traceback.print_exc()
logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation
logging.error(output_error) logging.error(output_error)
@ -871,7 +905,7 @@ def main_impl(
if outputting_zip: if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type) output_to_zip(args.zip, output_error, json_results, args.zip_type)
if args.perf: if args.perf and cp:
cp.disable() cp.disable()
stats = pstats.Stats(cp).sort_stats("cumtime") stats = pstats.Stats(cp).sort_stats("cumtime")
stats.print_stats() stats.print_stats()

@ -8,10 +8,10 @@ if TYPE_CHECKING:
class Structure(SourceMapping): class Structure(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"): def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__() super().__init__()
self._name: Optional[str] = None self._name: Optional[str] = None
self._canonical_name = None self._canonical_name: Optional[str] = None
self._elems: Dict[str, "StructureVariable"] = {} self._elems: Dict[str, "StructureVariable"] = {}
# Name of the elements in the order of declaration # Name of the elements in the order of declaration
self._elems_ordered: List[str] = [] self._elems_ordered: List[str] = []
@ -19,25 +19,27 @@ class Structure(SourceMapping):
@property @property
def canonical_name(self) -> str: def canonical_name(self) -> str:
assert self._canonical_name
return self._canonical_name return self._canonical_name
@canonical_name.setter @canonical_name.setter
def canonical_name(self, name: str): def canonical_name(self, name: str) -> None:
self._canonical_name = name self._canonical_name = name
@property @property
def name(self) -> str: def name(self) -> str:
assert self._name
return self._name return self._name
@name.setter @name.setter
def name(self, new_name: str): def name(self, new_name: str) -> None:
self._name = new_name self._name = new_name
@property @property
def elems(self) -> Dict[str, "StructureVariable"]: def elems(self) -> Dict[str, "StructureVariable"]:
return self._elems return self._elems
def add_elem_in_order(self, s: str): def add_elem_in_order(self, s: str) -> None:
self._elems_ordered.append(s) self._elems_ordered.append(s)
@property @property
@ -47,5 +49,5 @@ class Structure(SourceMapping):
ret.append(self._elems[e]) ret.append(self._elems[e])
return ret return ret
def __str__(self): def __str__(self) -> str:
return self.name return self.name

@ -1,7 +1,9 @@
from typing import Optional, Union, TYPE_CHECKING from typing import Optional, Union, TYPE_CHECKING
from slither.core.expressions.expression import Expression from slither.core.expressions.expression import Expression
from slither.core.solidity_types.elementary_type import Fixed, Int, Ufixed, Uint
from slither.utils.arithmetic import convert_subdenomination from slither.utils.arithmetic import convert_subdenomination
from slither.utils.integer_conversion import convert_string_to_int
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
@ -29,6 +31,10 @@ class Literal(Expression):
def __str__(self): def __str__(self):
if self.subdenomination: if self.subdenomination:
return str(convert_subdenomination(self._value, self.subdenomination)) return str(convert_subdenomination(self._value, self.subdenomination))
if self.type in Int + Uint + Fixed + Ufixed + ["address"]:
return str(convert_string_to_int(self._value))
# be sure to handle any character # be sure to handle any character
return str(self._value) return str(self._value)

@ -4,7 +4,7 @@ from slither.core.expressions.expression import Expression
class TupleExpression(Expression): class TupleExpression(Expression):
def __init__(self, expressions): def __init__(self, expressions: List[Expression]) -> None:
assert all(isinstance(x, Expression) for x in expressions if x) assert all(isinstance(x, Expression) for x in expressions if x)
super().__init__() super().__init__()
self._expressions = expressions self._expressions = expressions
@ -13,6 +13,6 @@ class TupleExpression(Expression):
def expressions(self) -> List[Expression]: def expressions(self) -> List[Expression]:
return self._expressions return self._expressions
def __str__(self): def __str__(self) -> str:
expressions_str = [str(e) for e in self.expressions] expressions_str = [str(e) for e in self.expressions]
return "(" + ",".join(expressions_str) + ")" return "(" + ",".join(expressions_str) + ")"

@ -53,7 +53,7 @@ class ArrayType(Type):
def storage_size(self) -> Tuple[int, bool]: def storage_size(self) -> Tuple[int, bool]:
if self._length_value: if self._length_value:
elem_size, _ = self._type.storage_size elem_size, _ = self._type.storage_size
return elem_size * int(self._length_value.value), True return elem_size * int(str(self._length_value)), True
return 32, True return 32, True
def __str__(self): def __str__(self):

@ -162,13 +162,15 @@ def _convert_source_mapping(
class SourceMapping(Context, metaclass=ABCMeta): class SourceMapping(Context, metaclass=ABCMeta):
def __init__(self): def __init__(self) -> None:
super().__init__() super().__init__()
# self._source_mapping: Optional[Dict] = None # self._source_mapping: Optional[Dict] = None
self.source_mapping: Source = Source() self.source_mapping: Source = Source()
self.references: List[Source] = [] self.references: List[Source] = []
def set_offset(self, offset: Union["Source", str], compilation_unit: "SlitherCompilationUnit"): def set_offset(
self, offset: Union["Source", str], compilation_unit: "SlitherCompilationUnit"
) -> None:
if isinstance(offset, Source): if isinstance(offset, Source):
self.source_mapping.start = offset.start self.source_mapping.start = offset.start
self.source_mapping.length = offset.length self.source_mapping.length = offset.length
@ -184,6 +186,6 @@ class SourceMapping(Context, metaclass=ABCMeta):
def add_reference_from_raw_source( def add_reference_from_raw_source(
self, offset: str, compilation_unit: "SlitherCompilationUnit" self, offset: str, compilation_unit: "SlitherCompilationUnit"
): ) -> None:
s = _convert_source_mapping(offset, compilation_unit) s = _convert_source_mapping(offset, compilation_unit)
self.references.append(s) self.references.append(s)

@ -43,7 +43,14 @@ Deploy with any of the following Solidity versions:
- 0.5.16 - 0.5.17 - 0.5.16 - 0.5.17
- 0.6.11 - 0.6.12 - 0.6.11 - 0.6.12
- 0.7.5 - 0.7.6 - 0.7.5 - 0.7.6
- 0.8.4 - 0.8.7 - 0.8.16
The recommendations take into account:
- Risks related to recent releases
- Risks of complex code generation changes
- Risks of new language features
- Risks of known bugs
Use a simple pragma version that allows any of these versions. Use a simple pragma version that allows any of these versions.
Consider using the latest version of Solidity for testing.""" Consider using the latest version of Solidity for testing."""
# endregion wiki_recommendation # endregion wiki_recommendation
@ -58,18 +65,7 @@ Consider using the latest version of Solidity for testing."""
) )
# Indicates the allowed versions. Must be formatted in increasing order. # Indicates the allowed versions. Must be formatted in increasing order.
ALLOWED_VERSIONS = [ ALLOWED_VERSIONS = ["0.5.16", "0.5.17", "0.6.11", "0.6.12", "0.7.5", "0.7.6", "0.8.16"]
"0.5.16",
"0.5.17",
"0.6.11",
"0.6.12",
"0.7.5",
"0.7.6",
"0.8.4",
"0.8.5",
"0.8.6",
"0.8.7",
]
# Indicates the versions that should not be used. # Indicates the versions that should not be used.
BUGGY_VERSIONS = [ BUGGY_VERSIONS = [

@ -1,5 +1,5 @@
import logging import logging
from typing import Union, List, ValuesView from typing import Union, List, ValuesView, Type, Dict
from crytic_compile import CryticCompile, InvalidCompilation from crytic_compile import CryticCompile, InvalidCompilation
@ -19,7 +19,9 @@ logger_detector = logging.getLogger("Detectors")
logger_printer = logging.getLogger("Printers") logger_printer = logging.getLogger("Printers")
def _check_common_things(thing_name, cls, base_cls, instances_list): def _check_common_things(
thing_name: str, cls: Type, base_cls: Type, instances_list: List[Type[AbstractDetector]]
) -> None:
if not issubclass(cls, base_cls) or cls is base_cls: if not issubclass(cls, base_cls) or cls is base_cls:
raise Exception( raise Exception(
@ -178,7 +180,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
def detectors_optimization(self): def detectors_optimization(self):
return [d for d in self.detectors if d.IMPACT == DetectorClassification.OPTIMIZATION] return [d for d in self.detectors if d.IMPACT == DetectorClassification.OPTIMIZATION]
def register_detector(self, detector_class): def register_detector(self, detector_class: Type[AbstractDetector]) -> None:
""" """
:param detector_class: Class inheriting from `AbstractDetector`. :param detector_class: Class inheriting from `AbstractDetector`.
""" """
@ -188,7 +190,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
instance = detector_class(compilation_unit, self, logger_detector) instance = detector_class(compilation_unit, self, logger_detector)
self._detectors.append(instance) self._detectors.append(instance)
def register_printer(self, printer_class): def register_printer(self, printer_class: Type[AbstractPrinter]) -> None:
""" """
:param printer_class: Class inheriting from `AbstractPrinter`. :param printer_class: Class inheriting from `AbstractPrinter`.
""" """
@ -197,7 +199,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
instance = printer_class(self, logger_printer) instance = printer_class(self, logger_printer)
self._printers.append(instance) self._printers.append(instance)
def run_detectors(self): def run_detectors(self) -> List[Dict]:
""" """
:return: List of registered detectors results. :return: List of registered detectors results.
""" """

@ -1,8 +1,10 @@
from typing import Optional, List
from slither.slithir.operations.operation import Operation from slither.slithir.operations.operation import Operation
class Call(Operation): class Call(Operation):
def __init__(self): def __init__(self) -> None:
super().__init__() super().__init__()
self._arguments = [] self._arguments = []
@ -14,14 +16,14 @@ class Call(Operation):
def arguments(self, v): def arguments(self, v):
self._arguments = v self._arguments = v
def can_reenter(self, _callstack=None): # pylint: disable=no-self-use def can_reenter(self, _callstack: Optional[List] = None) -> bool: # pylint: disable=no-self-use
""" """
Must be called after slithIR analysis pass Must be called after slithIR analysis pass
:return: bool :return: bool
""" """
return False return False
def can_send_eth(self): # pylint: disable=no-self-use def can_send_eth(self) -> bool: # pylint: disable=no-self-use
""" """
Must be called after slithIR analysis pass Must be called after slithIR analysis pass
:return: bool :return: bool

@ -63,14 +63,14 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
def call_id(self): def call_id(self):
return self._callid return self._callid
@property
def read(self):
return [self.called]
@call_id.setter @call_id.setter
def call_id(self, c): def call_id(self, c):
self._callid = c self._callid = c
@property
def read(self):
return [self.called]
@property @property
def called(self): def called(self):
return self._called return self._called

@ -1,21 +1,23 @@
from typing import List
from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.operations.lvalue import OperationWithLValue
from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.solidity_types.elementary_type import ElementaryType
class TmpNewElementaryType(OperationWithLValue): class TmpNewElementaryType(OperationWithLValue):
def __init__(self, new_type, lvalue): def __init__(self, new_type: ElementaryType, lvalue):
assert isinstance(new_type, ElementaryType) assert isinstance(new_type, ElementaryType)
super().__init__() super().__init__()
self._type = new_type self._type: ElementaryType = new_type
self._lvalue = lvalue self._lvalue = lvalue
@property @property
def read(self): def read(self) -> List:
return [] return []
@property @property
def type(self): def type(self) -> ElementaryType:
return self._type return self._type
def __str__(self): def __str__(self) -> str:
return f"{self.lvalue} = new {self._type}" return f"{self.lvalue} = new {self._type}"

@ -1,4 +1,3 @@
from decimal import Decimal
from functools import total_ordering from functools import total_ordering
from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint
@ -33,7 +32,7 @@ class Constant(SlithIRVariable):
else: else:
if val.isdigit(): if val.isdigit():
self._type = ElementaryType("uint256") self._type = ElementaryType("uint256")
self._val = int(Decimal(val)) self._val = convert_string_to_int(val)
else: else:
self._type = ElementaryType("string") self._type = ElementaryType("string")
self._val = val self._val = val

@ -62,7 +62,7 @@ class ModifierSolc(FunctionSolc):
self._content_was_analyzed = True self._content_was_analyzed = True
if self.is_compact_ast: if self.is_compact_ast:
body = self._functionNotParsed["body"] body = self._functionNotParsed.get("body", None)
if body and body[self.get_key()] == "Block": if body and body[self.get_key()] == "Block":
self._function.is_implemented = True self._function.is_implemented = True

@ -9,7 +9,7 @@ logging.getLogger("Slither").setLevel(logging.INFO)
logger = logging.getLogger("Slither-demo") logger = logging.getLogger("Slither-demo")
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -26,7 +26,7 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
args = parse_args() args = parse_args()
# Perform slither analysis on the given filename # Perform slither analysis on the given filename

@ -1,6 +1,7 @@
import argparse import argparse
import logging import logging
from collections import defaultdict from collections import defaultdict
from typing import Any, Dict, List
from crytic_compile import cryticparser from crytic_compile import cryticparser
from slither import Slither from slither import Slither
@ -26,7 +27,7 @@ logger.propagate = False
ADDITIONAL_CHECKS = {"ERC20": check_erc20, "ERC1155": check_erc1155} ADDITIONAL_CHECKS = {"ERC20": check_erc20, "ERC1155": check_erc1155}
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -63,20 +64,20 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def _log_error(err, args): def _log_error(err: Any, args: argparse.Namespace) -> None:
if args.json: if args.json:
output_to_json(args.json, str(err), {"upgradeability-check": []}) output_to_json(args.json, str(err), {"upgradeability-check": []})
logger.error(err) logger.error(err)
def main(): def main() -> None:
args = parse_args() args = parse_args()
# Perform slither analysis on the given filename # Perform slither analysis on the given filename
slither = Slither(args.project, **vars(args)) slither = Slither(args.project, **vars(args))
ret = defaultdict(list) ret: Dict[str, List] = defaultdict(list)
if args.erc.upper() in ERCS: if args.erc.upper() in ERCS:

@ -1,7 +1,10 @@
import logging import logging
from typing import Dict, List, Optional, Set
from slither.core.declarations import Contract
from slither.slithir.operations import EventCall from slither.slithir.operations import EventCall
from slither.utils import output from slither.utils import output
from slither.utils.erc import ERC, ERC_EVENT
from slither.utils.type import ( from slither.utils.type import (
export_nested_types_from_variable, export_nested_types_from_variable,
export_return_type_from_variable, export_return_type_from_variable,
@ -11,7 +14,7 @@ logger = logging.getLogger("Slither-conformance")
# pylint: disable=too-many-locals,too-many-branches,too-many-statements # pylint: disable=too-many-locals,too-many-branches,too-many-statements
def _check_signature(erc_function, contract, ret): def _check_signature(erc_function: ERC, contract: Contract, ret: Dict) -> None:
name = erc_function.name name = erc_function.name
parameters = erc_function.parameters parameters = erc_function.parameters
return_type = erc_function.return_type return_type = erc_function.return_type
@ -146,7 +149,7 @@ def _check_signature(erc_function, contract, ret):
ret["missing_event_emmited"].append(missing_event_emmited.data) ret["missing_event_emmited"].append(missing_event_emmited.data)
def _check_events(erc_event, contract, ret): def _check_events(erc_event: ERC_EVENT, contract: Contract, ret: Dict[str, List]) -> None:
name = erc_event.name name = erc_event.name
parameters = erc_event.parameters parameters = erc_event.parameters
indexes = erc_event.indexes indexes = erc_event.indexes
@ -180,7 +183,13 @@ def _check_events(erc_event, contract, ret):
ret["missing_event_index"].append(missing_event_index.data) ret["missing_event_index"].append(missing_event_index.data)
def generic_erc_checks(contract, erc_functions, erc_events, ret, explored=None): def generic_erc_checks(
contract: Contract,
erc_functions: List[ERC],
erc_events: List[ERC_EVENT],
ret: Dict[str, List],
explored: Optional[Set[Contract]] = None,
) -> None:
if explored is None: if explored is None:
explored = set() explored = set()

@ -18,7 +18,7 @@ logger = logging.getLogger("Slither")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -106,7 +106,7 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
args = parse_args() args = parse_args()
slither = Slither(args.filename, **vars(args)) slither = Slither(args.filename, **vars(args))

@ -16,7 +16,7 @@ logger.handlers[0].setFormatter(formatter)
logger.propagate = False logger.propagate = False
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -56,7 +56,7 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
# ------------------------------ # ------------------------------
# Usage: slither-kspec-coverage contract kspec # Usage: slither-kspec-coverage contract kspec
# Example: slither-kspec-coverage contract.sol kspec.md # Example: slither-kspec-coverage contract.sol kspec.md

@ -1,8 +1,10 @@
import argparse
from slither.tools.kspec_coverage.analysis import run_analysis from slither.tools.kspec_coverage.analysis import run_analysis
from slither import Slither from slither import Slither
def kspec_coverage(args): def kspec_coverage(args: argparse.Namespace) -> None:
contract = args.contract contract = args.contract
kspec = args.kspec kspec = args.kspec

@ -2,6 +2,7 @@ import argparse
import inspect import inspect
import logging import logging
import sys import sys
from typing import Type, List, Any
from crytic_compile import cryticparser from crytic_compile import cryticparser
@ -22,7 +23,7 @@ logger.setLevel(logging.INFO)
################################################################################### ###################################################################################
def parse_args(): def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Experimental smart contract mutator. Based on https://arxiv.org/abs/2006.11597", description="Experimental smart contract mutator. Based on https://arxiv.org/abs/2006.11597",
usage="slither-mutate target", usage="slither-mutate target",
@ -48,14 +49,16 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def _get_mutators(): def _get_mutators() -> List[Type[AbstractMutator]]:
detectors = [getattr(all_mutators, name) for name in dir(all_mutators)] detectors_ = [getattr(all_mutators, name) for name in dir(all_mutators)]
detectors = [c for c in detectors if inspect.isclass(c) and issubclass(c, AbstractMutator)] detectors = [c for c in detectors_ if inspect.isclass(c) and issubclass(c, AbstractMutator)]
return detectors return detectors
class ListMutators(argparse.Action): # pylint: disable=too-few-public-methods class ListMutators(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
checks = _get_mutators() checks = _get_mutators()
output_mutators(checks) output_mutators(checks)
parser.exit() parser.exit()

@ -5,6 +5,7 @@ from argparse import ArgumentParser, Namespace
from crytic_compile import cryticparser from crytic_compile import cryticparser
from slither import Slither from slither import Slither
from slither.core.declarations import FunctionContract
from slither.utils.colors import red from slither.utils.colors import red
from slither.tools.possible_paths.possible_paths import ( from slither.tools.possible_paths.possible_paths import (
find_target_paths, find_target_paths,
@ -58,7 +59,11 @@ def main() -> None:
# Print out all target functions. # Print out all target functions.
print("Target functions:") print("Target functions:")
for target in targets: for target in targets:
print(f"- {target.contract_declarer.name}.{target.full_name}") if isinstance(target, FunctionContract):
print(f"- {target.contract_declarer.name}.{target.full_name}")
else:
pass
# TODO implement me
print("\n") print("\n")
# Obtain all paths which reach the target functions. # Obtain all paths which reach the target functions.

@ -1,8 +1,15 @@
from typing import List, Tuple, Union, Optional, Set
from slither import Slither
from slither.core.declarations import Function, FunctionContract
from slither.core.slither_core import SlitherCore
class ResolveFunctionException(Exception): class ResolveFunctionException(Exception):
pass pass
def resolve_function(slither, contract_name, function_name): def resolve_function(slither: SlitherCore, contract_name: str, function_name: str) -> Function:
""" """
Resolves a function instance, given a contract name and function. Resolves a function instance, given a contract name and function.
:param contract_name: The name of the contract the function is declared in. :param contract_name: The name of the contract the function is declared in.
@ -32,7 +39,9 @@ def resolve_function(slither, contract_name, function_name):
return target_function return target_function
def resolve_functions(slither, functions): def resolve_functions(
slither: Slither, functions: List[Union[str, Tuple[str, str]]]
) -> List[Function]:
""" """
Resolves the provided function descriptors. Resolves the provided function descriptors.
:param functions: A list of tuples (contract_name, function_name) or str (of form "ContractName.FunctionName") :param functions: A list of tuples (contract_name, function_name) or str (of form "ContractName.FunctionName")
@ -40,7 +49,7 @@ def resolve_functions(slither, functions):
:return: Returns a list of resolved functions. :return: Returns a list of resolved functions.
""" """
# Create the resolved list. # Create the resolved list.
resolved = [] resolved: List[Function] = []
# Verify that the provided argument is a list. # Verify that the provided argument is a list.
if not isinstance(functions, list): if not isinstance(functions, list):
@ -72,24 +81,31 @@ def resolve_functions(slither, functions):
return resolved return resolved
def all_function_definitions(function): def all_function_definitions(function: Function) -> List[Function]:
""" """
Obtains a list of representing this function and any base definitions Obtains a list of representing this function and any base definitions
:param function: The function to obtain all definitions at and beneath. :param function: The function to obtain all definitions at and beneath.
:return: Returns a list composed of the provided function definition and any base definitions. :return: Returns a list composed of the provided function definition and any base definitions.
""" """
return [function] + [ # TODO implement me
if not isinstance(function, FunctionContract):
return []
ret: List[Function] = [function]
ret += [
f f
for c in function.contract.inheritance for c in function.contract.inheritance
for f in c.functions_and_modifiers_declared for f in c.functions_and_modifiers_declared
if f.full_name == function.full_name if f.full_name == function.full_name
] ]
return ret
def __find_target_paths(slither, target_function, current_path=None): def __find_target_paths(
slither: SlitherCore, target_function: Function, current_path: Optional[List[Function]] = None
) -> Set[Tuple[Function, ...]]:
current_path = current_path if current_path else [] current_path = current_path if current_path else []
# Create our results list # Create our results list
results = set() results: Set[Tuple[Function, ...]] = set()
# Add our current function to the path. # Add our current function to the path.
current_path = [target_function] + current_path current_path = [target_function] + current_path
@ -106,9 +122,12 @@ def __find_target_paths(slither, target_function, current_path=None):
continue continue
# Find all function calls in this function (except for low level) # Find all function calls in this function (except for low level)
called_functions = [f for (_, f) in function.high_level_calls + function.library_calls] called_functions_list = [
called_functions += function.internal_calls f for (_, f) in function.high_level_calls if isinstance(f, Function)
called_functions = set(called_functions) ]
called_functions_list += [f for (_, f) in function.library_calls]
called_functions_list += [f for f in function.internal_calls if isinstance(f, Function)]
called_functions = set(called_functions_list)
# If any of our target functions are reachable from this function, it's a result. # If any of our target functions are reachable from this function, it's a result.
if all_target_functions.intersection(called_functions): if all_target_functions.intersection(called_functions):
@ -123,14 +142,16 @@ def __find_target_paths(slither, target_function, current_path=None):
return results return results
def find_target_paths(slither, target_functions): def find_target_paths(
slither: SlitherCore, target_functions: List[Function]
) -> Set[Tuple[Function, ...]]:
""" """
Obtains all functions which can lead to any of the target functions being called. Obtains all functions which can lead to any of the target functions being called.
:param target_functions: The functions we are interested in reaching. :param target_functions: The functions we are interested in reaching.
:return: Returns a list of all functions which can reach any of the target_functions. :return: Returns a list of all functions which can reach any of the target_functions.
""" """
# Create our results list # Create our results list
results = set() results: Set[Tuple[Function, ...]] = set()
# Loop for each target function # Loop for each target function
for target_function in target_functions: for target_function in target_functions:

@ -1,6 +1,7 @@
import argparse import argparse
import logging import logging
import sys import sys
from typing import Any
from crytic_compile import cryticparser from crytic_compile import cryticparser
@ -26,7 +27,7 @@ logger.handlers[0].setFormatter(formatter)
logger.propagate = False logger.propagate = False
def _all_scenarios(): def _all_scenarios() -> str:
txt = "\n" txt = "\n"
txt += "#################### ERC20 ####################\n" txt += "#################### ERC20 ####################\n"
for k, value in ERC20_PROPERTIES.items(): for k, value in ERC20_PROPERTIES.items():
@ -35,29 +36,33 @@ def _all_scenarios():
return txt return txt
def _all_properties(): def _all_properties() -> MyPrettyTable:
table = MyPrettyTable(["Num", "Description", "Scenario"]) table = MyPrettyTable(["Num", "Description", "Scenario"])
idx = 0 idx = 0
for scenario, value in ERC20_PROPERTIES.items(): for scenario, value in ERC20_PROPERTIES.items():
for prop in value.properties: for prop in value.properties:
table.add_row([idx, prop.description, scenario]) table.add_row([str(idx), prop.description, scenario])
idx = idx + 1 idx = idx + 1
return table return table
class ListScenarios(argparse.Action): # pylint: disable=too-few-public-methods class ListScenarios(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
logger.info(_all_scenarios()) logger.info(_all_scenarios())
parser.exit() parser.exit()
class ListProperties(argparse.Action): # pylint: disable=too-few-public-methods class ListProperties(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
logger.info(_all_properties()) logger.info(_all_properties())
parser.exit() parser.exit()
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -120,7 +125,7 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
args = parse_args() args = parse_args()
# Perform slither analysis on the given filename # Perform slither analysis on the given filename

@ -15,7 +15,7 @@ PATTERN_TRUFFLE_MIGRATION = re.compile("^[0-9]*_")
logger = logging.getLogger("Slither") logger = logging.getLogger("Slither")
def _extract_caller(p: PropertyCaller): def _extract_caller(p: PropertyCaller) -> List[str]:
if p == PropertyCaller.OWNER: if p == PropertyCaller.OWNER:
return ["owner"] return ["owner"]
if p == PropertyCaller.SENDER: if p == PropertyCaller.SENDER:
@ -28,7 +28,7 @@ def _extract_caller(p: PropertyCaller):
return ["user"] return ["user"]
def _helpers(): def _helpers() -> str:
""" """
Generate two functions: Generate two functions:
- catchRevertThrowReturnFalse: check if the call revert/throw or return false - catchRevertThrowReturnFalse: check if the call revert/throw or return false
@ -75,7 +75,7 @@ def generate_unit_test( # pylint: disable=too-many-arguments,too-many-branches
output_dir: Path, output_dir: Path,
addresses: Addresses, addresses: Addresses,
assert_message: str = "", assert_message: str = "",
): ) -> Path:
""" """
Generate unit tests files Generate unit tests files
:param test_contract: :param test_contract:
@ -134,7 +134,7 @@ def generate_unit_test( # pylint: disable=too-many-arguments,too-many-branches
return output_dir return output_dir
def generate_migration(test_contract: str, output_dir: Path, owner_address: str): def generate_migration(test_contract: str, output_dir: Path, owner_address: str) -> None:
""" """
Generate migration file Generate migration file
:param test_contract: :param test_contract:

@ -12,7 +12,7 @@ def write_file(
content: str, content: str,
allow_overwrite: bool = True, allow_overwrite: bool = True,
discard_if_exist: bool = False, discard_if_exist: bool = False,
): ) -> None:
""" """
Write the content into output_dir/filename Write the content into output_dir/filename
:param output_dir: :param output_dir:

@ -18,7 +18,7 @@ optional arguments:
--struct-var STRUCT_VAR The name of the variable whose value will be returned from a struct. --struct-var STRUCT_VAR The name of the variable whose value will be returned from a struct.
--storage-address STORAGE_ADDRESS The address of the storage contract (if a proxy pattern is used). --storage-address STORAGE_ADDRESS The address of the storage contract (if a proxy pattern is used).
--contract-name CONTRACT_NAME The name of the logic contract. --contract-name CONTRACT_NAME The name of the logic contract.
--layout Toggle used to write a JSON file with the entire storage layout. --json FILE Write the entire storage layout in JSON format to the specified FILE
--value Toggle used to include values in output. --value Toggle used to include values in output.
--max-depth MAX_DEPTH Max depth to search in data structure. --max-depth MAX_DEPTH Max depth to search in data structure.
``` ```
@ -28,19 +28,19 @@ optional arguments:
Retrieve the storage slots of a local contract: Retrieve the storage slots of a local contract:
```shell ```shell
slither-read-storage file.sol 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --layout slither-read-storage file.sol 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --json storage_layout.json
``` ```
Retrieve the storage slots of a contract verified on an Etherscan-like platform: Retrieve the storage slots of a contract verified on an Etherscan-like platform:
```shell ```shell
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --layout slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --json storage_layout.json
``` ```
To retrieve the values as well, pass `--value` and `--rpc-url $RPC_URL`: To retrieve the values as well, pass `--value` and `--rpc-url $RPC_URL`:
```shell ```shell
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --layout --rpc-url $RPC_URL --value slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --json storage_layout.json --rpc-url $RPC_URL --value
``` ```
To view only the slot of the `slot0` structure variable, pass `--variable-name slot0`: To view only the slot of the `slot0` structure variable, pass `--variable-name slot0`:
@ -79,7 +79,7 @@ slither-read-storage 0xa2327a938Febf5FEC13baCFb16Ae10EcBc4cbDCF --variable-name
Take Avalanche, for instance: Take Avalanche, for instance:
```shell ```shell
slither-read-storage avax:0x0000000000000000000000000000000000000000 --layout --value --rpc-url $AVAX_RPC_URL slither-read-storage avax:0x0000000000000000000000000000000000000000 --json storage_layout.json --value --rpc-url $AVAX_RPC_URL
``` ```
## Limitations ## Limitations

@ -21,9 +21,9 @@ def parse_args() -> argparse.Namespace:
"\nTo retrieve a single variable's value:\n" "\nTo retrieve a single variable's value:\n"
+ "\tslither-read-storage $TARGET address --variable-name $NAME\n" + "\tslither-read-storage $TARGET address --variable-name $NAME\n"
+ "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 --json storage_layout.json\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 --value\n" + "\tslither-read-storage $TARGET address --contract-name $NAME --json storage_layout.json --value\n"
+ "TARGET can be a contract address or project directory" + "TARGET can be a contract address or project directory"
), ),
) )

@ -17,7 +17,7 @@ logger = logging.getLogger("Slither-simil")
modes = ["info", "test", "train", "plot"] modes = ["info", "test", "train", "plot"]
def parse_args(): def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Code similarity detection tool. For usage, see https://github.com/crytic/slither/wiki/Code-Similarity-detector" description="Code similarity detection tool. For usage, see https://github.com/crytic/slither/wiki/Code-Similarity-detector"
) )
@ -78,7 +78,7 @@ def parse_args():
################################################################################### ###################################################################################
def main(): def main() -> None:
args = parse_args() args = parse_args()
default_log = logging.INFO default_log = logging.INFO

@ -74,7 +74,7 @@ def parse_target(target):
return None return None
def load_and_encode(infile, vmodel, ext=None, nsamples=None, **kwargs): def load_and_encode(infile: str, vmodel, ext=None, nsamples=None, **kwargs):
r = {} r = {}
if infile.endswith(".npz"): if infile.endswith(".npz"):
r = load_cache(infile, nsamples=nsamples) r = load_cache(infile, nsamples=nsamples)

@ -1,3 +1,4 @@
import argparse
import logging import logging
import sys import sys
import os.path import os.path
@ -10,7 +11,7 @@ logging.basicConfig()
logger = logging.getLogger("Slither-simil") logger = logging.getLogger("Slither-simil")
def info(args): def info(args: argparse.Namespace) -> None:
try: try:

@ -1,3 +1,4 @@
import argparse
import logging import logging
import random import random
import sys import sys
@ -23,7 +24,7 @@ except ImportError:
logger = logging.getLogger("Slither-simil") logger = logging.getLogger("Slither-simil")
def plot(args): # pylint: disable=too-many-locals def plot(args: argparse.Namespace) -> None: # pylint: disable=too-many-locals
if decomposition is None or plt is None: if decomposition is None or plt is None:
logger.error( logger.error(

@ -1,3 +1,4 @@
import argparse
import logging import logging
import os import os
import sys import sys
@ -10,7 +11,7 @@ from slither.tools.similarity.model import train_unsupervised
logger = logging.getLogger("Slither-simil") logger = logging.getLogger("Slither-simil")
def train(args): # pylint: disable=too-many-locals def train(args: argparse.Namespace) -> None: # pylint: disable=too-many-locals
try: try:
last_data_train_filename = "last_data_train.txt" last_data_train_filename = "last_data_train.txt"

@ -23,7 +23,7 @@ available_detectors = [
] ]
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -90,7 +90,7 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
# ------------------------------ # ------------------------------
# Usage: python3 -m slither_format filename # Usage: python3 -m slither_format filename
# Example: python3 -m slither_format contract.sol # Example: python3 -m slither_format contract.sol

@ -1,5 +1,9 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Type, List, Dict
from slither import Slither
from slither.detectors.abstract_detector import AbstractDetector
from slither.detectors.variables.unused_state_variables import UnusedStateVars from slither.detectors.variables.unused_state_variables import UnusedStateVars
from slither.detectors.attributes.incorrect_solc import IncorrectSolc from slither.detectors.attributes.incorrect_solc import IncorrectSolc
from slither.detectors.attributes.constant_pragma import ConstantPragma from slither.detectors.attributes.constant_pragma import ConstantPragma
@ -13,7 +17,7 @@ from slither.utils.colors import yellow
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("Slither.Format") logger = logging.getLogger("Slither.Format")
all_detectors = { all_detectors: Dict[str, Type[AbstractDetector]] = {
"unused-state": UnusedStateVars, "unused-state": UnusedStateVars,
"solc-version": IncorrectSolc, "solc-version": IncorrectSolc,
"pragma": ConstantPragma, "pragma": ConstantPragma,
@ -25,7 +29,7 @@ all_detectors = {
} }
def slither_format(slither, **kwargs): # pylint: disable=too-many-locals def slither_format(slither: Slither, **kwargs: Dict) -> None: # pylint: disable=too-many-locals
"""' """'
Keyword Args: Keyword Args:
detectors_to_run (str): Comma-separated list of detectors, defaults to all detectors_to_run (str): Comma-separated list of detectors, defaults to all
@ -85,9 +89,11 @@ def slither_format(slither, **kwargs): # pylint: disable=too-many-locals
################################################################################### ###################################################################################
def choose_detectors(detectors_to_run, detectors_to_exclude): def choose_detectors(
detectors_to_run: str, detectors_to_exclude: str
) -> List[Type[AbstractDetector]]:
# If detectors are specified, run only these ones # If detectors are specified, run only these ones
cls_detectors_to_run = [] cls_detectors_to_run: List[Type[AbstractDetector]] = []
exclude = detectors_to_exclude.split(",") exclude = detectors_to_exclude.split(",")
if detectors_to_run == "all": if detectors_to_run == "all":
for key, detector in all_detectors.items(): for key, detector in all_detectors.items():
@ -114,7 +120,7 @@ def choose_detectors(detectors_to_run, detectors_to_exclude):
################################################################################### ###################################################################################
def print_patches(number_of_slither_results, patches): def print_patches(number_of_slither_results: int, patches: Dict) -> None:
logger.info("Number of Slither results: " + str(number_of_slither_results)) logger.info("Number of Slither results: " + str(number_of_slither_results))
number_of_patches = 0 number_of_patches = 0
for file in patches: for file in patches:
@ -130,7 +136,7 @@ def print_patches(number_of_slither_results, patches):
logger.info("Location end: " + str(patch["end"])) logger.info("Location end: " + str(patch["end"]))
def print_patches_json(number_of_slither_results, patches): def print_patches_json(number_of_slither_results: int, patches: Dict) -> None:
print("{", end="") print("{", end="")
print('"Number of Slither results":' + '"' + str(number_of_slither_results) + '",') print('"Number of Slither results":' + '"' + str(number_of_slither_results) + '",')
print('"Number of patchlets":' + '"' + str(len(patches)) + '"', ",") print('"Number of patchlets":' + '"' + str(len(patches)) + '"', ",")

@ -3,10 +3,13 @@ import inspect
import json import json
import logging import logging
import sys import sys
from typing import List, Any, Type, Dict, Tuple, Union, Sequence, Optional
from crytic_compile import cryticparser from crytic_compile import cryticparser
from slither import Slither from slither import Slither
from slither.core.declarations import Contract
from slither.exceptions import SlitherException from slither.exceptions import SlitherException
from slither.utils.colors import red from slither.utils.colors import red
from slither.utils.output import output_to_json from slither.utils.output import output_to_json
@ -24,7 +27,7 @@ logger: logging.Logger = logging.getLogger("Slither")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
def parse_args(): def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Slither Upgradeability Checks. For usage information see https://github.com/crytic/slither/wiki/Upgradeability-Checks.", description="Slither Upgradeability Checks. For usage information see https://github.com/crytic/slither/wiki/Upgradeability-Checks.",
usage="slither-check-upgradeability contract.sol ContractName", usage="slither-check-upgradeability contract.sol ContractName",
@ -93,21 +96,27 @@ def parse_args():
################################################################################### ###################################################################################
def _get_checks(): def _get_checks() -> List[Type[AbstractCheck]]:
detectors = [getattr(all_checks, name) for name in dir(all_checks)] detectors_ = [getattr(all_checks, name) for name in dir(all_checks)]
detectors = [c for c in detectors if inspect.isclass(c) and issubclass(c, AbstractCheck)] detectors: List[Type[AbstractCheck]] = [
c for c in detectors_ if inspect.isclass(c) and issubclass(c, AbstractCheck)
]
return detectors return detectors
class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
checks = _get_checks() checks = _get_checks()
output_detectors(checks) output_detectors(checks)
parser.exit() parser.exit()
class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-methods class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
checks = _get_checks() checks = _get_checks()
detector_types_json = output_detectors_json(checks) detector_types_json = output_detectors_json(checks)
print(json.dumps(detector_types_json)) print(json.dumps(detector_types_json))
@ -116,48 +125,64 @@ class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-meth
class OutputMarkdown(argparse.Action): # pylint: disable=too-few-public-methods class OutputMarkdown(argparse.Action): # pylint: disable=too-few-public-methods
def __call__( def __call__(
self, parser, args, values, option_string=None self,
): # pylint: disable=signature-differs parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> None: # pylint: disable=signature-differs
checks = _get_checks() checks = _get_checks()
assert isinstance(values, str)
output_to_markdown(checks, values) output_to_markdown(checks, values)
parser.exit() parser.exit()
class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods
def __call__( def __call__(
self, parser, args, values, option_string=None self,
): # pylint: disable=signature-differs parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> Any: # pylint: disable=signature-differs
checks = _get_checks() checks = _get_checks()
assert isinstance(values, str)
output_wiki(checks, values) output_wiki(checks, values)
parser.exit() parser.exit()
def _run_checks(detectors): def _run_checks(detectors: List[AbstractCheck]) -> List[Dict]:
results = [d.check() for d in detectors] results_ = [d.check() for d in detectors]
results = [r for r in results if r] results_ = [r for r in results_ if r]
results = [item for sublist in results for item in sublist] # flatten results = [item for sublist in results_ for item in sublist] # flatten
return results return results
def _checks_on_contract(detectors, contract): def _checks_on_contract(
detectors = [ detectors: List[Type[AbstractCheck]], contract: Contract
) -> Tuple[List[Dict], int]:
detectors_ = [
d(logger, contract) d(logger, contract)
for d in detectors for d in detectors
if (not d.REQUIRE_PROXY and not d.REQUIRE_CONTRACT_V2) if (not d.REQUIRE_PROXY and not d.REQUIRE_CONTRACT_V2)
] ]
return _run_checks(detectors), len(detectors) return _run_checks(detectors_), len(detectors_)
def _checks_on_contract_update(detectors, contract_v1, contract_v2): def _checks_on_contract_update(
detectors = [ detectors: List[Type[AbstractCheck]], contract_v1: Contract, contract_v2: Contract
) -> Tuple[List[Dict], int]:
detectors_ = [
d(logger, contract_v1, contract_v2=contract_v2) for d in detectors if d.REQUIRE_CONTRACT_V2 d(logger, contract_v1, contract_v2=contract_v2) for d in detectors if d.REQUIRE_CONTRACT_V2
] ]
return _run_checks(detectors), len(detectors) return _run_checks(detectors_), len(detectors_)
def _checks_on_contract_and_proxy(detectors, contract, proxy): def _checks_on_contract_and_proxy(
detectors = [d(logger, contract, proxy=proxy) for d in detectors if d.REQUIRE_PROXY] detectors: List[Type[AbstractCheck]], contract: Contract, proxy: Contract
return _run_checks(detectors), len(detectors) ) -> Tuple[List[Dict], int]:
detectors_ = [d(logger, contract, proxy=proxy) for d in detectors if d.REQUIRE_PROXY]
return _run_checks(detectors_), len(detectors_)
# endregion # endregion
@ -168,8 +193,8 @@ def _checks_on_contract_and_proxy(detectors, contract, proxy):
################################################################################### ###################################################################################
# pylint: disable=too-many-statements,too-many-branches,too-many-locals # pylint: disable=too-many-statements,too-many-branches,too-many-locals
def main(): def main() -> None:
json_results = { json_results: Dict = {
"proxy-present": False, "proxy-present": False,
"contract_v2-present": False, "contract_v2-present": False,
"detectors": [], "detectors": [],
@ -254,7 +279,7 @@ def main():
number_detectors_run += number_detectors number_detectors_run += number_detectors
# If there is a V2, we run the contract-only check on the V2 # If there is a V2, we run the contract-only check on the V2
detectors_results, _ = _checks_on_contract(detectors, v2_contract) detectors_results, number_detectors = _checks_on_contract(detectors, v2_contract)
json_results["detectors"] += detectors_results json_results["detectors"] += detectors_results
number_detectors_run += number_detectors number_detectors_run += number_detectors

@ -1,8 +1,10 @@
from slither.tools.upgradeability.checks.abstract_checks import classification_txt from typing import List, Union, Dict, Type
from slither.tools.upgradeability.checks.abstract_checks import classification_txt, AbstractCheck
from slither.utils.myprettytable import MyPrettyTable from slither.utils.myprettytable import MyPrettyTable
def output_wiki(detector_classes, filter_wiki): def output_wiki(detector_classes: List[Type[AbstractCheck]], filter_wiki: str) -> None:
# Sort by impact, confidence, and name # Sort by impact, confidence, and name
detectors_list = sorted( detectors_list = sorted(
detector_classes, key=lambda element: (element.IMPACT, element.ARGUMENT) detector_classes, key=lambda element: (element.IMPACT, element.ARGUMENT)
@ -31,7 +33,7 @@ def output_wiki(detector_classes, filter_wiki):
print(recommendation) print(recommendation)
def output_detectors(detector_classes): def output_detectors(detector_classes: List[Type[AbstractCheck]]) -> None:
detectors_list = [] detectors_list = []
for detector in detector_classes: for detector in detector_classes:
argument = detector.ARGUMENT argument = detector.ARGUMENT
@ -48,7 +50,7 @@ def output_detectors(detector_classes):
for (argument, help_info, impact, proxy, v2) in detectors_list: for (argument, help_info, impact, proxy, v2) in detectors_list:
table.add_row( table.add_row(
[ [
idx, str(idx),
argument, argument,
help_info, help_info,
classification_txt[impact], classification_txt[impact],
@ -60,8 +62,8 @@ def output_detectors(detector_classes):
print(table) print(table)
def output_to_markdown(detector_classes, _filter_wiki): def output_to_markdown(detector_classes: List[Type[AbstractCheck]], _filter_wiki: str) -> None:
def extract_help(cls): def extract_help(cls: AbstractCheck) -> str:
if cls.WIKI == "": if cls.WIKI == "":
return cls.HELP return cls.HELP
return f"[{cls.HELP}]({cls.WIKI})" return f"[{cls.HELP}]({cls.WIKI})"
@ -85,7 +87,9 @@ def output_to_markdown(detector_classes, _filter_wiki):
idx = idx + 1 idx = idx + 1
def output_detectors_json(detector_classes): def output_detectors_json(
detector_classes: List[Type[AbstractCheck]],
) -> List[Dict[str, Union[str, int]]]:
detectors_list = [] detectors_list = []
for detector in detector_classes: for detector in detector_classes:
argument = detector.ARGUMENT argument = detector.ARGUMENT
@ -110,7 +114,7 @@ def output_detectors_json(detector_classes):
# Sort by impact, confidence, and name # Sort by impact, confidence, and name
detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[0])) detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[0]))
idx = 1 idx = 1
table = [] table: List[Dict[str, Union[str, int]]] = []
for ( for (
argument, argument,
help_info, help_info,

@ -1,17 +1,12 @@
from decimal import Decimal
from slither.exceptions import SlitherException from slither.exceptions import SlitherException
from slither.utils.integer_conversion import convert_string_to_fraction
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def convert_subdenomination( def convert_subdenomination(
value: str, sub: str value: str, sub: str
) -> int: # pylint: disable=too-many-return-statements ) -> int: # pylint: disable=too-many-return-statements
# to allow 0.1 ether conversion decimal_value = convert_string_to_fraction(value)
if value[0:2] == "0x":
decimal_value = Decimal(int(value, 16))
else:
decimal_value = Decimal(value)
if sub == "wei": if sub == "wei":
return int(decimal_value) return int(decimal_value)
if sub == "gwei": if sub == "gwei":

@ -1,13 +1,17 @@
import argparse
import json import json
import os import os
import re import re
import logging import logging
from collections import defaultdict from collections import defaultdict
from typing import Dict, List, Type, Union
from crytic_compile.cryticparser.defaults import ( from crytic_compile.cryticparser.defaults import (
DEFAULTS_FLAG_IN_CONFIG as DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE, DEFAULTS_FLAG_IN_CONFIG as DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE,
) )
from slither.detectors.abstract_detector import classification_txt from slither.detectors.abstract_detector import classification_txt, AbstractDetector
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.colors import yellow, red from slither.utils.colors import yellow, red
from slither.utils.myprettytable import MyPrettyTable from slither.utils.myprettytable import MyPrettyTable
@ -34,7 +38,7 @@ defaults_flag_in_config = {
"exclude_low": False, "exclude_low": False,
"exclude_medium": False, "exclude_medium": False,
"exclude_high": False, "exclude_high": False,
"fail_pedantic": False, "fail_pedantic": True,
"fail_low": False, "fail_low": False,
"fail_medium": False, "fail_medium": False,
"fail_high": False, "fail_high": False,
@ -54,7 +58,7 @@ defaults_flag_in_config = {
} }
def read_config_file(args): def read_config_file(args: argparse.Namespace) -> None:
# No config file was provided as an argument # No config file was provided as an argument
if args.config_file is None: if args.config_file is None:
# Check wether the default config file is present # Check wether the default config file is present
@ -83,8 +87,12 @@ def read_config_file(args):
logger.error(yellow("Falling back to the default settings...")) logger.error(yellow("Falling back to the default settings..."))
def output_to_markdown(detector_classes, printer_classes, filter_wiki): def output_to_markdown(
def extract_help(cls): detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
filter_wiki: str,
) -> None:
def extract_help(cls: Union[Type[AbstractDetector], Type[AbstractPrinter]]) -> str:
if cls.WIKI == "": if cls.WIKI == "":
return cls.HELP return cls.HELP
return f"[{cls.HELP}]({cls.WIKI})" return f"[{cls.HELP}]({cls.WIKI})"
@ -127,7 +135,7 @@ def output_to_markdown(detector_classes, printer_classes, filter_wiki):
idx = idx + 1 idx = idx + 1
def get_level(l): def get_level(l: str) -> int:
tab = l.count("\t") + 1 tab = l.count("\t") + 1
if l.replace("\t", "").startswith(" -"): if l.replace("\t", "").startswith(" -"):
tab = tab + 1 tab = tab + 1
@ -136,7 +144,7 @@ def get_level(l):
return tab return tab
def convert_result_to_markdown(txt): def convert_result_to_markdown(txt: str) -> str:
# -1 to remove the last \n # -1 to remove the last \n
lines = txt[0:-1].split("\n") lines = txt[0:-1].split("\n")
ret = [] ret = []
@ -154,16 +162,21 @@ def convert_result_to_markdown(txt):
return "".join(ret) return "".join(ret)
def output_results_to_markdown(all_results, checklistlimit: str): def output_results_to_markdown(all_results: List[Dict], checklistlimit: str) -> None:
checks = defaultdict(list) checks = defaultdict(list)
info = defaultdict(dict) info: Dict = defaultdict(dict)
for results in all_results: for results_ in all_results:
checks[results["check"]].append(results) checks[results_["check"]].append(results_)
info[results["check"]] = {"impact": results["impact"], "confidence": results["confidence"]} info[results_["check"]] = {
"impact": results_["impact"],
"confidence": results_["confidence"],
}
print("Summary") print("Summary")
for check in checks: for check_ in checks:
print(f" - [{check}](#{check}) ({len(checks[check])} results) ({info[check]['impact']})") print(
f" - [{check_}](#{check_}) ({len(checks[check_])} results) ({info[check_]['impact']})"
)
counter = 0 counter = 0
for (check, results) in checks.items(): for (check, results) in checks.items():
@ -185,8 +198,7 @@ def output_results_to_markdown(all_results, checklistlimit: str):
print(f"**More results were found, check [{checklistlimit}]({checklistlimit})**") print(f"**More results were found, check [{checklistlimit}]({checklistlimit})**")
def output_wiki(detector_classes, filter_wiki): def output_wiki(detector_classes: List[Type[AbstractDetector]], filter_wiki: str) -> None:
detectors_list = []
# Sort by impact, confidence, and name # Sort by impact, confidence, and name
detectors_list = sorted( detectors_list = sorted(
@ -223,7 +235,7 @@ def output_wiki(detector_classes, filter_wiki):
print(recommendation) print(recommendation)
def output_detectors(detector_classes): def output_detectors(detector_classes: List[Type[AbstractDetector]]) -> None:
detectors_list = [] detectors_list = []
for detector in detector_classes: for detector in detector_classes:
argument = detector.ARGUMENT argument = detector.ARGUMENT
@ -242,12 +254,15 @@ def output_detectors(detector_classes):
) )
idx = 1 idx = 1
for (argument, help_info, impact, confidence) in detectors_list: for (argument, help_info, impact, confidence) in detectors_list:
table.add_row([idx, argument, help_info, classification_txt[impact], confidence]) table.add_row([str(idx), argument, help_info, classification_txt[impact], confidence])
idx = idx + 1 idx = idx + 1
print(table) print(table)
def output_detectors_json(detector_classes): # pylint: disable=too-many-locals # pylint: disable=too-many-locals
def output_detectors_json(
detector_classes: List[Type[AbstractDetector]],
) -> List[Dict]:
detectors_list = [] detectors_list = []
for detector in detector_classes: for detector in detector_classes:
argument = detector.ARGUMENT argument = detector.ARGUMENT
@ -307,7 +322,7 @@ def output_detectors_json(detector_classes): # pylint: disable=too-many-locals
return table return table
def output_printers(printer_classes): def output_printers(printer_classes: List[Type[AbstractPrinter]]) -> None:
printers_list = [] printers_list = []
for printer in printer_classes: for printer in printer_classes:
argument = printer.ARGUMENT argument = printer.ARGUMENT
@ -319,12 +334,12 @@ def output_printers(printer_classes):
printers_list = sorted(printers_list, key=lambda element: (element[0])) printers_list = sorted(printers_list, key=lambda element: (element[0]))
idx = 1 idx = 1
for (argument, help_info) in printers_list: for (argument, help_info) in printers_list:
table.add_row([idx, argument, help_info]) table.add_row([str(idx), argument, help_info])
idx = idx + 1 idx = idx + 1
print(table) print(table)
def output_printers_json(printer_classes): def output_printers_json(printer_classes: List[Type[AbstractPrinter]]) -> List[Dict]:
printers_list = [] printers_list = []
for printer in printer_classes: for printer in printer_classes:
argument = printer.ARGUMENT argument = printer.ARGUMENT

@ -1,28 +1,35 @@
from decimal import Decimal from fractions import Fraction
from typing import Union from typing import Union
from slither.exceptions import SlitherError from slither.exceptions import SlitherError
def convert_string_to_int(val: Union[str, int]) -> int: def convert_string_to_fraction(val: Union[str, int]) -> Fraction:
if isinstance(val, int): if isinstance(val, int):
return val return Fraction(val)
if val.startswith(("0x", "0X")): if val.startswith(("0x", "0X")):
return int(val, 16) return Fraction(int(val, 16))
# Fractions do not support underscore separators (on Python <3.11)
val = val.replace("_", "")
if "e" in val or "E" in val: if "e" in val or "E" in val:
base, expo = val.split("e") if "e" in val else val.split("E") base, expo = val.split("e") if "e" in val else val.split("E")
base, expo = Decimal(base), int(expo) base, expo = Fraction(base), int(expo)
# The resulting number must be < 2**256-1, otherwise solc # The resulting number must be < 2**256-1, otherwise solc
# Would not be able to compile it # Would not be able to compile it
# 10**77 is the largest exponent that fits # 10**77 is the largest exponent that fits
# See https://github.com/ethereum/solidity/blob/9e61f92bd4d19b430cb8cb26f1c7cf79f1dff380/libsolidity/ast/Types.cpp#L1281-L1290 # See https://github.com/ethereum/solidity/blob/9e61f92bd4d19b430cb8cb26f1c7cf79f1dff380/libsolidity/ast/Types.cpp#L1281-L1290
if expo > 77: if expo > 77:
if base != Decimal(0): if base != Fraction(0):
raise SlitherError( raise SlitherError(
f"{base}e{expo} is too large to fit in any Solidity integer size" f"{base}e{expo} is too large to fit in any Solidity integer size"
) )
return 0 return 0
return int(Decimal(base) * Decimal(10 ** expo)) return Fraction(base) * Fraction(10**expo)
return Fraction(val)
return int(Decimal(val))
def convert_string_to_int(val: Union[str, int]) -> int:
return int(convert_string_to_fraction(val))

@ -4,7 +4,7 @@ import json
import logging import logging
import zipfile import zipfile
from collections import OrderedDict from collections import OrderedDict
from typing import Optional, Dict, List, Union, Any, TYPE_CHECKING from typing import Optional, Dict, List, Union, Any, TYPE_CHECKING, Type
from zipfile import ZipFile from zipfile import ZipFile
from pkg_resources import require from pkg_resources import require
@ -129,7 +129,7 @@ def _output_result_to_sarif(
def output_to_sarif( def output_to_sarif(
filename: Optional[str], results: Dict, detectors_classes: List["AbstractDetector"] filename: Optional[str], results: Dict, detectors_classes: List[Type["AbstractDetector"]]
) -> None: ) -> None:
""" """

@ -28,7 +28,7 @@ class StandardOutputCapture:
original_logger_handlers = None original_logger_handlers = None
@staticmethod @staticmethod
def enable(block_original=True): def enable(block_original: bool = True) -> None:
""" """
Redirects stdout and stderr to a capturable StringIO. Redirects stdout and stderr to a capturable StringIO.
:param block_original: If True, blocks all output to the original stream. If False, duplicates output. :param block_original: If True, blocks all output to the original stream. If False, duplicates output.
@ -54,7 +54,7 @@ class StandardOutputCapture:
root_logger.handlers = [logging.StreamHandler(sys.stderr)] root_logger.handlers = [logging.StreamHandler(sys.stderr)]
@staticmethod @staticmethod
def disable(): def disable() -> None:
""" """
Disables redirection of stdout/stderr, if previously enabled. Disables redirection of stdout/stderr, if previously enabled.
:return: None :return: None
@ -78,7 +78,7 @@ class StandardOutputCapture:
StandardOutputCapture.original_logger_handlers = None StandardOutputCapture.original_logger_handlers = None
@staticmethod @staticmethod
def get_stdout_output(): def get_stdout_output() -> str:
""" """
Obtains the output from the currently set stdout Obtains the output from the currently set stdout
:return: Returns stdout output as a string :return: Returns stdout output as a string
@ -87,7 +87,7 @@ class StandardOutputCapture:
return sys.stdout.read() return sys.stdout.read()
@staticmethod @staticmethod
def get_stderr_output(): def get_stderr_output() -> str:
""" """
Obtains the output from the currently set stderr Obtains the output from the currently set stderr
:return: Returns stderr output as a string :return: Returns stderr output as a string

@ -1,5 +1,5 @@
from slither.core.expressions import BinaryOperationType, Literal, UnaryOperationType from slither.core.expressions import BinaryOperationType, Literal, UnaryOperationType
from slither.utils.integer_conversion import convert_string_to_int from slither.utils.integer_conversion import convert_string_to_fraction, convert_string_to_int
from slither.visitors.expression.expression import ExpressionVisitor from slither.visitors.expression.expression import ExpressionVisitor
@ -72,15 +72,15 @@ class ConstantFolding(ExpressionVisitor):
cf = ConstantFolding(expr, self._type) cf = ConstantFolding(expr, self._type)
expr = cf.result() expr = cf.result()
assert isinstance(expr, Literal) assert isinstance(expr, Literal)
set_val(expression, int(expr.value)) set_val(expression, -convert_string_to_fraction(expr.value))
else: else:
raise NotConstant raise NotConstant
def _post_literal(self, expression): def _post_literal(self, expression):
if expression.value.isdigit(): try:
set_val(expression, int(expression.value)) set_val(expression, convert_string_to_fraction(expression.value))
else: except ValueError as e:
raise NotConstant raise NotConstant from e
def _post_assignement_operation(self, expression): def _post_assignement_operation(self, expression):
raise NotConstant raise NotConstant
@ -115,7 +115,7 @@ class ConstantFolding(ExpressionVisitor):
cf = ConstantFolding(expression.expressions[0], self._type) cf = ConstantFolding(expression.expressions[0], self._type)
expr = cf.result() expr = cf.result()
assert isinstance(expr, Literal) assert isinstance(expr, Literal)
set_val(expression, int(expr.value)) set_val(expression, convert_string_to_fraction(expr.value))
return return
raise NotConstant raise NotConstant

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save