Merge branch 'dev' into dev-improve-reentrancy

pull/1351/head
Josselin Feist 2 years ago
commit 200c5ce875
  1. 1
      .github/workflows/ci.yml
  2. 2
      .github/workflows/features.yml
  3. 1
      .gitignore
  4. 4
      CONTRIBUTING.md
  5. 8
      Dockerfile
  6. 156
      README.md
  7. 9
      plugin_example/slither_my_plugin/__init__.py
  8. 6
      scripts/ci_test_cli.sh
  9. 15
      scripts/ci_test_path_filtering.sh
  10. 2
      scripts/ci_test_truffle.sh
  11. 10
      setup.py
  12. 108
      slither/__main__.py
  13. 7
      slither/core/declarations/contract.py
  14. 5
      slither/core/declarations/function.py
  15. 14
      slither/core/declarations/structure.py
  16. 6
      slither/core/expressions/literal.py
  17. 4
      slither/core/expressions/tuple_expression.py
  18. 2
      slither/core/solidity_types/array_type.py
  19. 8
      slither/core/source_mapping/source_mapping.py
  20. 2
      slither/detectors/abstract_detector.py
  21. 1
      slither/detectors/all_detectors.py
  22. 24
      slither/detectors/attributes/incorrect_solc.py
  23. 2
      slither/detectors/erc/erc20/arbitrary_send_erc20_no_permit.py
  24. 2
      slither/detectors/erc/erc20/arbitrary_send_erc20_permit.py
  25. 72
      slither/detectors/functions/permit_domain_signature_collision.py
  26. 2
      slither/detectors/statements/divide_before_multiply.py
  27. 29
      slither/detectors/statements/unprotected_upgradeable.py
  28. 70
      slither/detectors/variables/possible_const_state_variables.py
  29. 2
      slither/printers/summary/function_ids.py
  30. 12
      slither/slither.py
  31. 2
      slither/slithir/convert.py
  32. 6
      slither/slithir/operations/call.py
  33. 2
      slither/slithir/operations/event_call.py
  34. 8
      slither/slithir/tmp_operations/tmp_call.py
  35. 12
      slither/slithir/tmp_operations/tmp_new_elementary_type.py
  36. 3
      slither/slithir/variables/constant.py
  37. 2
      slither/solc_parsing/declarations/modifier.py
  38. 3
      slither/solc_parsing/slither_compilation_unit_solc.py
  39. 13
      slither/solc_parsing/yul/parse_yul.py
  40. 4
      slither/tools/demo/__main__.py
  41. 3
      slither/tools/doctor/README.md
  42. 0
      slither/tools/doctor/__init__.py
  43. 37
      slither/tools/doctor/__main__.py
  44. 18
      slither/tools/doctor/checks/__init__.py
  45. 59
      slither/tools/doctor/checks/platform.py
  46. 59
      slither/tools/doctor/checks/versions.py
  47. 28
      slither/tools/doctor/utils.py
  48. 9
      slither/tools/erc_conformance/__main__.py
  49. 15
      slither/tools/erc_conformance/erc/ercs.py
  50. 4
      slither/tools/flattening/__main__.py
  51. 10
      slither/tools/flattening/flattening.py
  52. 4
      slither/tools/kspec_coverage/__main__.py
  53. 4
      slither/tools/kspec_coverage/kspec_coverage.py
  54. 13
      slither/tools/mutator/__main__.py
  55. 7
      slither/tools/possible_paths/__main__.py
  56. 45
      slither/tools/possible_paths/possible_paths.py
  57. 19
      slither/tools/properties/__main__.py
  58. 8
      slither/tools/properties/platforms/truffle.py
  59. 2
      slither/tools/properties/utils.py
  60. 11
      slither/tools/read_storage/README.md
  61. 15
      slither/tools/read_storage/__main__.py
  62. 19
      slither/tools/read_storage/read_storage.py
  63. 7
      slither/tools/read_storage/utils/utils.py
  64. 4
      slither/tools/similarity/__main__.py
  65. 2
      slither/tools/similarity/encode.py
  66. 3
      slither/tools/similarity/info.py
  67. 3
      slither/tools/similarity/plot.py
  68. 3
      slither/tools/similarity/train.py
  69. 4
      slither/tools/slither_format/__main__.py
  70. 18
      slither/tools/slither_format/slither_format.py
  71. 77
      slither/tools/upgradeability/__main__.py
  72. 20
      slither/tools/upgradeability/utils/command_line.py
  73. 9
      slither/utils/arithmetic.py
  74. 2
      slither/utils/colors.py
  75. 59
      slither/utils/command_line.py
  76. 8
      slither/utils/function.py
  77. 23
      slither/utils/integer_conversion.py
  78. 4
      slither/utils/output.py
  79. 8
      slither/utils/output_capture.py
  80. 55
      slither/utils/type.py
  81. 14
      slither/visitors/expression/constants_folding.py
  82. BIN
      tests/ast-parsing/compile/library_event-0.8.16.sol-0.8.16-compact.zip
  83. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.0-compact.zip
  84. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.0-legacy.zip
  85. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.1-compact.zip
  86. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.1-legacy.zip
  87. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.2-compact.zip
  88. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.2-legacy.zip
  89. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.3-compact.zip
  90. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.3-legacy.zip
  91. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.4-compact.zip
  92. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.4-legacy.zip
  93. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.5-compact.zip
  94. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.5-legacy.zip
  95. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.6-compact.zip
  96. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.7.6-legacy.zip
  97. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.0-compact.zip
  98. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.1-compact.zip
  99. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.10-compact.zip
  100. BIN
      tests/ast-parsing/compile/modifier-0.7.0.sol-0.8.11-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

1
.gitignore vendored

@ -47,6 +47,7 @@ coverage.xml
*.cover *.cover
.hypothesis/ .hypothesis/
.vscode/ .vscode/
storage_layout.json
# Translations # Translations
*.mo *.mo
*.pot *.pot

@ -68,8 +68,8 @@ To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/d
### Parser tests ### Parser tests
- Create a test in `tests/ast-parsing` - Create a test in `tests/ast-parsing`
- Run `python ./tests/test_ast_parsing.py --compile`. This will compile the artifact in `tests/compile`. Add the compiled artifact to git. - Run `python ./tests/test_ast_parsing.py --compile`. This will compile the artifact in `tests/ast-parsing/compile`. Add the compiled artifact to git.
- Run `python ./tests/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/expected_json`. Add the generated files to git. - Run `python ./tests/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/ast-parsing/expected_json`. Add the generated files to git.
- Run `pytest ./tests/test_ast_parsing.py` and check that everything worked. - Run `pytest ./tests/test_ast_parsing.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html` To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`

@ -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

@ -54,83 +54,85 @@ Use [solc-select](https://github.com/crytic/solc-select) if your contracts requi
Num | Detector | What it Detects | Impact | Confidence Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | --- --- | --- | --- | --- | ---
1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High 1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High
2 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High 2 | `arbitrary-send-erc20` | [transferFrom uses arbitrary `from`](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High 3 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High
4 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High 4 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High
5 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High 5 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
6 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High 6 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
7 | `rtlo` | [Right-To-Left-Override control character is used](https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character) | High | High 7 | `protected-vars` | [Detected unprotected variables](https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables) | High | High
8 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High 8 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
9 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High 9 | `rtlo` | [Right-To-Left-Override control character is used](https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character) | High | High
10 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High 10 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
11 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High 11 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
12 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High 12 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
13 | `arbitrary-send` | [Functions that send Ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium 13 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
14 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium 14 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High
15 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium 15 | `arbitrary-send-erc20-permit` | [transferFrom uses arbitrary from with permit](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit) | High | Medium
16 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium 16 | `arbitrary-send-eth` | [Functions that send Ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
17 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium 17 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
18 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium 18 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
19 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium 19 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
20 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium 20 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
21 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium 21 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
22 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High 22 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
23 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High 23 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
24 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High 24 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
25 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High 25 | `domain-separator-collision` | [Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()](https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision) | Medium | High
26 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High 26 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
27 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High 27 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
28 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High 28 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
29 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High 29 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
30 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High 30 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
31 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium 31 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
32 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium 32 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
33 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium 33 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
34 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium 34 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
35 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium 35 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
36 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium 36 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
37 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium 37 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
38 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium 38 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
39 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium 39 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
40 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium 40 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
41 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium 41 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
42 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High 42 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
43 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High 43 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
44 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High 44 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
45 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High 45 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
46 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High 46 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
47 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High 47 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
48 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium 48 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
49 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium 49 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
50 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium 50 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
51 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium 51 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
52 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium 52 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
53 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium 53 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
54 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium 54 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
55 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium 55 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
56 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High 56 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
57 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High 57 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
58 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High 58 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
59 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High 59 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
60 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High 60 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
61 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High 61 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
62 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High 62 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
63 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High 63 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
64 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High 64 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
65 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High 65 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
66 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High 66 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
67 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High 67 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
68 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High 68 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
69 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High 69 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
70 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium 70 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium 71 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium 72 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium 73 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium 74 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
75 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High 75 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
76 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High 76 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
77 | `arbitrary-send-erc20` | [Detect when `msg.sender` is not used as `from` in transferFrom](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20) 77 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
78 | `arbitrary-send-erc20-permit` | [Detect when `msg.sender` is not used as `from` in transferFrom in conjuction with permit](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit) 78 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
79 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
80 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
For more information, see For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector - The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector

@ -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

@ -8,14 +8,14 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.", description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither", url="https://github.com/crytic/slither",
author="Trail of Bits", author="Trail of Bits",
version="0.8.3", version="0.9.0",
packages=find_packages(), packages=find_packages(),
python_requires=">=3.8", python_requires=">=3.8",
install_requires=[ install_requires=[
"prettytable>=0.7.2", "prettytable>=0.7.2",
"pysha3>=1.0.2", "pycryptodome>=3.4.6",
# "crytic-compile>=0.2.3", "crytic-compile>=0.2.4",
"crytic-compile", # "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
], ],
extras_require={ extras_require={
"dev": [ "dev": [
@ -28,7 +28,6 @@ setup(
"solc-select>=v1.0.0b1", "solc-select>=v1.0.0b1",
] ]
}, },
dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0", license="AGPL-3.0",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
@ -45,6 +44,7 @@ setup(
"slither-prop = slither.tools.properties.__main__:main", "slither-prop = slither.tools.properties.__main__:main",
"slither-mutate = slither.tools.mutator.__main__:main", "slither-mutate = slither.tools.mutator.__main__:main",
"slither-read-storage = slither.tools.read_storage.__main__:main", "slither-read-storage = slither.tools.read_storage.__main__:main",
"slither-doctor = slither.tools.doctor.__main__:main",
] ]
}, },
) )

@ -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,19 +276,22 @@ 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"
usage += "\t- file.sol // a Solidity file\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" usage += "\t- project_directory // a project directory. See https://github.com/crytic/crytic-compile/#crytic-compile for the supported platforms\n"
usage += "\t- 0x.. // a contract on mainet\n" usage += "\t- 0x.. // a contract on mainnet\n"
usage += f"\t- NETWORK:0x.. // a contract on a different network. Supported networks: {','.join(x[:-1] for x in SUPPORTED_NETWORK)}\n" usage += f"\t- NETWORK:0x.. // a contract on a different network. Supported networks: {','.join(x[:-1] for x in SUPPORTED_NETWORK)}\n"
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@ -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()

@ -322,6 +322,13 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
""" """
return list(self._variables.values()) return list(self._variables.values())
@property
def state_variables_entry_points(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables that are public.
"""
return [var for var in self._variables.values() if var.visibility == "public"]
@property @property
def state_variables_ordered(self) -> List["StateVariable"]: def state_variables_ordered(self) -> List["StateVariable"]:
""" """

@ -960,6 +960,11 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
""" """
Return a signature following the Solidity Standard Return a signature following the Solidity Standard
Contract and converted into address Contract and converted into address
It might still keep internal types (ex: structure name) for internal functions.
The reason is that internal functions allows recursive structure definition, which
can't be converted following the Solidity stand ard
:return: the solidity signature :return: the solidity signature
""" """
if self._solidity_signature is None: if self._solidity_signature is None:

@ -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)

@ -189,7 +189,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if results and self.slither.triage_mode: if results and self.slither.triage_mode:
while True: while True:
indexes = input( indexes = input(
f'Results to hide during next runs: "0,1,...,{len(results)}" or "All" (enter to not hide results): ' f'Results to hide during next runs: "0,1,...,{len(results)}" or "All" (enter to not hide results):\n'
) )
if indexes == "All": if indexes == "All":
self.slither.save_results_to_hide(results) self.slither.save_results_to_hide(results)

@ -84,3 +84,4 @@ from .statements.write_after_write import WriteAfterWrite
from .statements.msg_value_in_loop import MsgValueInLoop from .statements.msg_value_in_loop import MsgValueInLoop
from .statements.delegatecall_in_loop import DelegatecallInLoop from .statements.delegatecall_in_loop import DelegatecallInLoop
from .functions.protected_variable import ProtectedVariables from .functions.protected_variable import ProtectedVariables
from .functions.permit_domain_signature_collision import DomainSeparatorCollision

@ -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
@ -52,24 +59,13 @@ Consider using the latest version of Solidity for testing."""
OLD_VERSION_TXT = "allows old versions" OLD_VERSION_TXT = "allows old versions"
LESS_THAN_TXT = "uses lesser than" LESS_THAN_TXT = "uses lesser than"
TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7" TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.16"
BUGGY_VERSION_TXT = ( BUGGY_VERSION_TXT = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)" "is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
) )
# 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 = [

@ -14,7 +14,7 @@ class ArbitrarySendErc20NoPermit(AbstractDetector):
IMPACT = DetectorClassification.HIGH IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20" WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom"
WIKI_TITLE = "Arbitrary `from` in transferFrom" WIKI_TITLE = "Arbitrary `from` in transferFrom"
WIKI_DESCRIPTION = "Detect when `msg.sender` is not used as `from` in transferFrom." WIKI_DESCRIPTION = "Detect when `msg.sender` is not used as `from` in transferFrom."

@ -14,7 +14,7 @@ class ArbitrarySendErc20Permit(AbstractDetector):
IMPACT = DetectorClassification.HIGH IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit" WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom-used-with-permit"
WIKI_TITLE = "Arbitrary `from` in transferFrom used with permit" WIKI_TITLE = "Arbitrary `from` in transferFrom used with permit"
WIKI_DESCRIPTION = ( WIKI_DESCRIPTION = (

@ -0,0 +1,72 @@
"""
Module detecting EIP-2612 domain separator collision
"""
from typing import Union, List
from slither.core.declarations import Function
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.variables.state_variable import StateVariable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.function import get_function_id
class DomainSeparatorCollision(AbstractDetector):
"""
Domain separator collision
"""
ARGUMENT = "domain-separator-collision"
HELP = "Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision"
)
WIKI_TITLE = "Domain separator collision"
WIKI_DESCRIPTION = "An ERC20 token has a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR(), causing unanticipated behavior for contracts using `permit` functionality."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Contract{
function some_collisions() external() {}
}
```
`some_collision` clashes with EIP-2612's DOMAIN_SEPARATOR() and will interfere with contract's using `permit`."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Remove or rename the function that collides with DOMAIN_SEPARATOR()."
def _detect(self):
domain_sig = get_function_id("DOMAIN_SEPARATOR()")
for contract in self.compilation_unit.contracts_derived:
if contract.is_erc20():
funcs_and_vars: List[Union[Function, StateVariable]] = contract.functions_entry_points + contract.state_variables_entry_points # type: ignore
for func_or_var in funcs_and_vars:
# External/ public function names should not collide with DOMAIN_SEPARATOR()
hash_collision = (
func_or_var.solidity_signature != "DOMAIN_SEPARATOR()"
and get_function_id(func_or_var.solidity_signature) == domain_sig
)
# DOMAIN_SEPARATOR() should return bytes32
incorrect_return_type = func_or_var.solidity_signature == "DOMAIN_SEPARATOR()"
if incorrect_return_type:
if isinstance(func_or_var, Function):
incorrect_return_type = (
not func_or_var.return_type
or func_or_var.return_type[0] != ElementaryType("bytes32")
)
else:
assert isinstance(func_or_var, StateVariable)
incorrect_return_type = func_or_var.type != ElementaryType("bytes32")
if hash_collision or incorrect_return_type:
info = [
"The function signature of ",
func_or_var,
" collides with DOMAIN_SEPARATOR and should be renamed or removed.\n",
]
res = self.generate_result(info)
return [res]
return []

@ -189,7 +189,7 @@ In general, it's usually a good idea to re-arrange arithmetic to perform multipl
nodes.sort(key=lambda x: x.node_id) nodes.sort(key=lambda x: x.node_id)
for node in nodes: for node in nodes:
info += ["\t-", node, "\n"] info += ["\t- ", node, "\n"]
res = self.generate_result(info) res = self.generate_result(info)
results.append(res) results.append(res)

@ -65,21 +65,22 @@ class UnprotectedUpgradeable(AbstractDetector):
# region wiki_exploit_scenario # region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """ WIKI_EXPLOIT_SCENARIO = """
```solidity ```solidity
contract Buggy is Initializable{ contract Buggy is Initializable{
address payable owner; address payable owner;
function initialize() external initializer{ function initialize() external initializer{
require(owner == address(0)); require(owner == address(0));
owner = msg.sender; owner = msg.sender;
} }
function kill() external{ function kill() external{
require(msg.sender == owner); require(msg.sender == owner);
selfdestruct(owner); selfdestruct(owner);
}
} }
``` }
Buggy is an upgradeable contract. Anyone can call initialize on the logic contract, and destruct the contract.""" ```
Buggy is an upgradeable contract. Anyone can call initialize on the logic contract, and destruct the contract.
"""
# endregion wiki_exploit_scenario # endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = ( WIKI_RECOMMENDATION = (

@ -1,15 +1,40 @@
""" """
Module detecting state variables that could be declared as constant Module detecting state variables that could be declared as constant
""" """
from typing import Set, List, Dict
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.variables.variable import Variable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output
from slither.visitors.expression.export_values import ExportValues from slither.visitors.expression.export_values import ExportValues
from slither.core.declarations import Contract, Function
from slither.core.declarations.solidity_variables import SolidityFunction from slither.core.declarations.solidity_variables import SolidityFunction
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
from slither.formatters.variables.possible_const_state_variables import custom_format from slither.formatters.variables.possible_const_state_variables import custom_format
def _is_valid_type(v: StateVariable) -> bool:
t = v.type
if isinstance(t, ElementaryType):
return True
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
return True
return False
def _valid_candidate(v: StateVariable) -> bool:
return _is_valid_type(v) and not (v.is_constant or v.is_immutable)
def _is_constant_var(v: Variable) -> bool:
if isinstance(v, StateVariable):
return v.is_constant
return False
class ConstCandidateStateVars(AbstractDetector): class ConstCandidateStateVars(AbstractDetector):
""" """
State variables that could be declared as constant detector. State variables that could be declared as constant detector.
@ -29,10 +54,6 @@ class ConstCandidateStateVars(AbstractDetector):
WIKI_DESCRIPTION = "Constant state variables should be declared constant to save gas." WIKI_DESCRIPTION = "Constant state variables should be declared constant to save gas."
WIKI_RECOMMENDATION = "Add the `constant` attributes to state variables that never change." WIKI_RECOMMENDATION = "Add the `constant` attributes to state variables that never change."
@staticmethod
def _valid_candidate(v):
return isinstance(v.type, ElementaryType) and not (v.is_constant or v.is_immutable)
# https://solidity.readthedocs.io/en/v0.5.2/contracts.html#constant-state-variables # https://solidity.readthedocs.io/en/v0.5.2/contracts.html#constant-state-variables
valid_solidity_function = [ valid_solidity_function = [
SolidityFunction("keccak256()"), SolidityFunction("keccak256()"),
@ -46,13 +67,7 @@ class ConstCandidateStateVars(AbstractDetector):
SolidityFunction("mulmod(uint256,uint256,uint256)"), SolidityFunction("mulmod(uint256,uint256,uint256)"),
] ]
@staticmethod def _constant_initial_expression(self, v: Variable) -> bool:
def _is_constant_var(v):
if isinstance(v, StateVariable):
return v.is_constant
return False
def _constant_initial_expression(self, v):
if not v.expression: if not v.expression:
return True return True
@ -60,34 +75,39 @@ class ConstCandidateStateVars(AbstractDetector):
values = export.result() values = export.result()
if not values: if not values:
return True return True
if all( if all((val in self.valid_solidity_function or _is_constant_var(val) for val in values)):
(val in self.valid_solidity_function or self._is_constant_var(val) for val in values)
):
return True return True
return False return False
def _detect(self): def _detect(self) -> List[Output]:
"""Detect state variables that could be const""" """Detect state variables that could be const"""
results = [] results = []
all_variables = [c.state_variables for c in self.compilation_unit.contracts] all_variables_l = [c.state_variables for c in self.compilation_unit.contracts]
all_variables = {item for sublist in all_variables for item in sublist} all_variables: Set[StateVariable] = {
all_non_constant_elementary_variables = { item for sublist in all_variables_l for item in sublist
v for v in all_variables if self._valid_candidate(v)
} }
all_non_constant_elementary_variables = {v for v in all_variables if _valid_candidate(v)}
all_functions = [c.all_functions_called for c in self.compilation_unit.contracts]
all_functions = list({item for sublist in all_functions for item in sublist}) all_functions_nested = [c.all_functions_called for c in self.compilation_unit.contracts]
all_functions = list(
{
item1
for sublist in all_functions_nested
for item1 in sublist
if isinstance(item1, Function)
}
)
all_variables_written = [ all_variables_written = [
f.state_variables_written for f in all_functions if not f.is_constructor_variables f.state_variables_written for f in all_functions if not f.is_constructor_variables
] ]
all_variables_written = {item for sublist in all_variables_written for item in sublist} all_variables_written = {item for sublist in all_variables_written for item in sublist}
constable_variables = [ constable_variables: List[Variable] = [
v v
for v in all_non_constant_elementary_variables for v in all_non_constant_elementary_variables
if (not v in all_variables_written) and self._constant_initial_expression(v) if (v not in all_variables_written) and self._constant_initial_expression(v)
] ]
# Order for deterministic results # Order for deterministic results
constable_variables = sorted(constable_variables, key=lambda x: x.canonical_name) constable_variables = sorted(constable_variables, key=lambda x: x.canonical_name)
@ -101,5 +121,5 @@ class ConstCandidateStateVars(AbstractDetector):
return results return results
@staticmethod @staticmethod
def _format(compilation_unit: SlitherCompilationUnit, result): def _format(compilation_unit: SlitherCompilationUnit, result: Dict) -> None:
custom_format(compilation_unit, result) custom_format(compilation_unit, result)

@ -9,7 +9,7 @@ from slither.utils.myprettytable import MyPrettyTable
class FunctionIds(AbstractPrinter): class FunctionIds(AbstractPrinter):
ARGUMENT = "function-id" ARGUMENT = "function-id"
HELP = "Print the keccack256 signature of the functions" HELP = "Print the keccak256 signature of the functions"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#function-id" WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#function-id"

@ -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.
""" """

@ -827,7 +827,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
# lib L { event E()} # lib L { event E()}
# ... # ...
# emit L.E(); # emit L.E();
if str(ins.ori.variable_right) in ins.ori.variable_left.events_as_dict: if str(ins.ori.variable_right) in [f.name for f in ins.ori.variable_left.events]:
eventcall = EventCall(ins.ori.variable_right) eventcall = EventCall(ins.ori.variable_right)
eventcall.set_expression(ins.expression) eventcall.set_expression(ins.expression)
eventcall.call_id = ins.call_id eventcall.call_id = ins.call_id

@ -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,7 +16,7 @@ class Call(Operation):
def arguments(self, v): def arguments(self, v):
self._arguments = v self._arguments = v
def can_reenter(self, _callstack=None) -> bool: # 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

@ -17,4 +17,4 @@ class EventCall(Call):
def __str__(self): def __str__(self):
args = [str(a) for a in self.arguments] args = [str(a) for a in self.arguments]
return f"Emit {self.name}({'.'.join(args)})" return f"Emit {self.name}({','.join(args)})"

@ -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

@ -257,7 +257,6 @@ class SlitherCompilationUnitSolc:
scope.accessible_scopes.append(get_imported_scope) scope.accessible_scopes.append(get_imported_scope)
elif top_level_data[self.get_key()] == "StructDefinition": elif top_level_data[self.get_key()] == "StructDefinition":
scope = self.compilation_unit.get_scope(filename)
st = StructureTopLevel(self.compilation_unit, scope) st = StructureTopLevel(self.compilation_unit, scope)
st.set_offset(top_level_data["src"], self._compilation_unit) st.set_offset(top_level_data["src"], self._compilation_unit)
st_parser = StructureTopLevelSolc(st, top_level_data, self) st_parser = StructureTopLevelSolc(st, top_level_data, self)
@ -279,7 +278,6 @@ class SlitherCompilationUnitSolc:
self._variables_top_level_parser.append(var_parser) self._variables_top_level_parser.append(var_parser)
scope.variables[var.name] = var scope.variables[var.name] = var
elif top_level_data[self.get_key()] == "FunctionDefinition": elif top_level_data[self.get_key()] == "FunctionDefinition":
scope = self.compilation_unit.get_scope(filename)
func = FunctionTopLevel(self._compilation_unit, scope) func = FunctionTopLevel(self._compilation_unit, scope)
scope.functions.add(func) scope.functions.add(func)
func.set_offset(top_level_data["src"], self._compilation_unit) func.set_offset(top_level_data["src"], self._compilation_unit)
@ -290,7 +288,6 @@ class SlitherCompilationUnitSolc:
self.add_function_or_modifier_parser(func_parser) self.add_function_or_modifier_parser(func_parser)
elif top_level_data[self.get_key()] == "ErrorDefinition": elif top_level_data[self.get_key()] == "ErrorDefinition":
scope = self.compilation_unit.get_scope(filename)
custom_error = CustomErrorTopLevel(self._compilation_unit, scope) custom_error = CustomErrorTopLevel(self._compilation_unit, scope)
custom_error.set_offset(top_level_data["src"], self._compilation_unit) custom_error.set_offset(top_level_data["src"], self._compilation_unit)

@ -675,7 +675,7 @@ def parse_yul_variable_declaration(
the assignment the assignment
""" """
if not ast["value"]: if "value" not in ast or not ast["value"]:
return None return None
return _parse_yul_assignment_common(root, node, ast, "variables") return _parse_yul_assignment_common(root, node, ast, "variables")
@ -807,7 +807,16 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[
def parse_yul_literal(_root: YulScope, _node: YulNode, ast: Dict) -> Optional[Expression]: def parse_yul_literal(_root: YulScope, _node: YulNode, ast: Dict) -> Optional[Expression]:
kind = ast["kind"] kind = ast["kind"]
value = ast["value"]
if kind == "string":
# Solc 0.8.0 use value, 0.8.16 use hexValue - not sure when this changed was made
if "value" in ast:
value = ast["value"]
else:
value = ast["hexValue"]
else:
# number/bool
value = ast["value"]
if not kind: if not kind:
kind = "bool" if value in ["true", "false"] else "uint256" kind = "bool" if value in ["true", "false"] else "uint256"

@ -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

@ -0,0 +1,3 @@
# Slither doctor
Slither doctor is a tool designed to troubleshoot running Slither on a project.

@ -0,0 +1,37 @@
import argparse
from crytic_compile import cryticparser
from slither.tools.doctor.utils import report_section
from slither.tools.doctor.checks import ALL_CHECKS
def parse_args() -> argparse.Namespace:
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
"""
parser = argparse.ArgumentParser(
description="Troubleshoot running Slither on your project",
usage="slither-doctor project",
)
parser.add_argument("project", help="The codebase to be tested.")
# Add default arguments from crytic-compile
cryticparser.init(parser)
return parser.parse_args()
def main():
args = parse_args()
kwargs = vars(args)
for check in ALL_CHECKS:
with report_section(check.title):
check.function(**kwargs)
if __name__ == "__main__":
main()

@ -0,0 +1,18 @@
from typing import Callable, List
from dataclasses import dataclass
from slither.tools.doctor.checks.platform import compile_project, detect_platform
from slither.tools.doctor.checks.versions import show_versions
@dataclass
class Check:
title: str
function: Callable[..., None]
ALL_CHECKS: List[Check] = [
Check("Software versions", show_versions),
Check("Project platform", detect_platform),
Check("Project compilation", compile_project),
]

@ -0,0 +1,59 @@
import logging
from pathlib import Path
from crytic_compile import crytic_compile
from slither.tools.doctor.utils import snip_section
from slither.utils.colors import red, yellow, green
def detect_platform(project: str, **kwargs) -> None:
path = Path(project)
if path.is_file():
print(
yellow(
f"{project!r} is a file. Using it as target will manually compile your code with solc and _not_ use a compilation framework. Is that what you meant to do?"
)
)
return
print(f"Trying to detect project type for {project!r}")
supported_platforms = crytic_compile.get_platforms()
skip_platforms = {"solc", "solc-json", "archive", "standard", "etherscan"}
detected_platforms = {
platform.NAME: platform.is_supported(project, **kwargs)
for platform in supported_platforms
if platform.NAME.lower() not in skip_platforms
}
platform_qty = len([platform for platform, state in detected_platforms.items() if state])
print("Is this project using...")
for platform, state in detected_platforms.items():
print(f" => {platform + '?':<15}{state and green('Yes') or red('No')}")
print()
if platform_qty == 0:
print(red("No platform was detected! This doesn't sound right."))
print(
yellow(
"Are you trying to analyze a folder with standalone solidity files, without using a compilation framework? If that's the case, then this is okay."
)
)
elif platform_qty > 1:
print(red("More than one platform was detected! This doesn't sound right."))
print(
red("Please use `--compile-force-framework` in Slither to force the correct framework.")
)
else:
print(green("A single platform was detected."), yellow("Is it the one you expected?"))
def compile_project(project: str, **kwargs):
print("Invoking crytic-compile on the project, please wait...")
try:
crytic_compile.CryticCompile(project, **kwargs)
except Exception as e: # pylint: disable=broad-except
with snip_section("Project compilation failed :( The following error was generated:"):
logging.exception(e)

@ -0,0 +1,59 @@
from importlib import metadata
import json
from typing import Optional
import urllib
from packaging.version import parse, LegacyVersion, Version
from slither.utils.colors import yellow, green
def get_installed_version(name: str) -> Optional[LegacyVersion | Version]:
try:
return parse(metadata.version(name))
except metadata.PackageNotFoundError:
return None
def get_github_version(name: str) -> Optional[LegacyVersion | Version]:
try:
with urllib.request.urlopen(
f"https://api.github.com/repos/crytic/{name}/releases/latest"
) as response:
text = response.read()
data = json.loads(text)
return parse(data["tag_name"])
except: # pylint: disable=bare-except
return None
def show_versions(**_kwargs) -> None:
versions = {
"Slither": (get_installed_version("slither-analyzer"), get_github_version("slither")),
"crytic-compile": (
get_installed_version("crytic-compile"),
get_github_version("crytic-compile"),
),
"solc-select": (get_installed_version("solc-select"), get_github_version("solc-select")),
}
outdated = {
name
for name, (installed, latest) in versions.items()
if not installed or not latest or latest > installed
}
for name, (installed, latest) in versions.items():
color = yellow if name in outdated else green
print(f"{name + ':':<16}{color(installed or 'N/A'):<16} (latest is {latest or 'Unknown'})")
if len(outdated) > 0:
print()
print(
yellow(
f"Please update {', '.join(outdated)} to the latest release before creating a bug report."
)
)
else:
print()
print(green("Your tools are up to date."))

@ -0,0 +1,28 @@
from contextlib import contextmanager
import logging
from typing import Optional
from slither.utils.colors import bold, yellow, red
@contextmanager
def snip_section(message: Optional[str]) -> None:
if message:
print(red(message), end="\n\n")
print(yellow("---- snip 8< ----"))
yield
print(yellow("---- >8 snip ----"))
@contextmanager
def report_section(title: str) -> None:
print(bold(f"## {title}"), end="\n\n")
try:
yield
except Exception as e: # pylint: disable=broad-except
with snip_section(
"slither-doctor failed unexpectedly! Please report this on the Slither GitHub issue tracker, and include the output below:"
):
logging.exception(e)
finally:
print(end="\n\n")

@ -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))

@ -4,7 +4,7 @@ import uuid
from collections import namedtuple from collections import namedtuple
from enum import Enum as PythonEnum from enum import Enum as PythonEnum
from pathlib import Path from pathlib import Path
from typing import List, Set, Dict, Optional from typing import List, Set, Dict, Optional, Sequence
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import SolidityFunction, EnumContract, StructureContract from slither.core.declarations import SolidityFunction, EnumContract, StructureContract
@ -78,12 +78,12 @@ class Flattening:
self._get_source_code_top_level(compilation_unit.variables_top_level) self._get_source_code_top_level(compilation_unit.variables_top_level)
self._get_source_code_top_level(compilation_unit.functions_top_level) self._get_source_code_top_level(compilation_unit.functions_top_level)
def _get_source_code_top_level(self, elems: List[TopLevel]) -> None: def _get_source_code_top_level(self, elems: Sequence[TopLevel]) -> None:
for elem in elems: for elem in elems:
src_mapping = elem.source_mapping src_mapping = elem.source_mapping
content = self._compilation_unit.core.source_code[src_mapping["filename_absolute"]] content = self._compilation_unit.core.source_code[src_mapping.filename.absolute]
start = src_mapping["start"] start = src_mapping.start
end = src_mapping["start"] + src_mapping["length"] end = src_mapping.start + src_mapping.length
self._source_codes_top_level[elem] = content[start:end] self._source_codes_top_level[elem] = content[start:end]
def _check_abi_encoder_v2(self): def _check_abi_encoder_v2(self):

@ -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,9 +18,10 @@ 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.
--block BLOCK_NUMBER Block number to retrieve storage from (requires archive rpc node)
``` ```
### Examples ### Examples
@ -28,19 +29,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 +80,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"
), ),
) )
@ -98,6 +98,12 @@ def parse_args() -> argparse.Namespace:
parser.add_argument("--max-depth", help="Max depth to search in data structure.", default=20) parser.add_argument("--max-depth", help="Max depth to search in data structure.", default=20)
parser.add_argument(
"--block",
help="The block number to read storage from. Requires an archive node to be provided as the RPC url.",
default="latest",
)
cryticparser.init(parser) cryticparser.init(parser)
return parser.parse_args() return parser.parse_args()
@ -122,6 +128,11 @@ def main() -> None:
srs = SlitherReadStorage(contracts, args.max_depth) srs = SlitherReadStorage(contracts, args.max_depth)
try:
srs.block = int(args.block)
except ValueError:
srs.block = str(args.block or "latest")
if args.rpc_url: if args.rpc_url:
# Remove target prefix e.g. rinkeby:0x0 -> 0x0. # Remove target prefix e.g. rinkeby:0x0 -> 0x0.
address = target[target.find(":") + 1 :] address = target[target.find(":") + 1 :]

@ -53,15 +53,16 @@ class SlitherReadStorageException(Exception):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class SlitherReadStorage: class SlitherReadStorage:
def __init__(self, contracts: List[Contract], max_depth: int) -> None: def __init__(self, contracts: List[Contract], max_depth: int) -> None:
self._checksum_address: Optional[ChecksumAddress] = None
self._contracts: List[Contract] = contracts self._contracts: List[Contract] = contracts
self._max_depth: int = max_depth
self._log: str = "" self._log: str = ""
self._max_depth: int = max_depth
self._slot_info: Dict[str, SlotInfo] = {} self._slot_info: Dict[str, SlotInfo] = {}
self._target_variables: List[Tuple[Contract, StateVariable]] = [] self._target_variables: List[Tuple[Contract, StateVariable]] = []
self._web3: Optional[Web3] = None self._web3: Optional[Web3] = None
self._checksum_address: Optional[ChecksumAddress] = None self.block: Union[str, int] = "latest"
self.storage_address: Optional[str] = None
self.rpc: Optional[str] = None self.rpc: Optional[str] = None
self.storage_address: Optional[str] = None
self.table: Optional[MyPrettyTable] = None self.table: Optional[MyPrettyTable] = None
@property @property
@ -231,7 +232,10 @@ class SlitherReadStorage:
:param slot_info: :param slot_info:
""" """
hex_bytes = get_storage_data( hex_bytes = get_storage_data(
self.web3, self.checksum_address, int.to_bytes(slot_info.slot, 32, byteorder="big") self.web3,
self.checksum_address,
int.to_bytes(slot_info.slot, 32, byteorder="big"),
self.block,
) )
slot_info.value = self.convert_value_to_type( slot_info.value = self.convert_value_to_type(
hex_bytes, slot_info.size, slot_info.offset, slot_info.type_string hex_bytes, slot_info.size, slot_info.offset, slot_info.type_string
@ -249,7 +253,7 @@ class SlitherReadStorage:
func, func,
[ [
(contract, var) (contract, var)
for var in contract.variables for var in contract.state_variables_ordered
if not var.is_constant and not var.is_immutable if not var.is_constant and not var.is_immutable
], ],
) )
@ -609,7 +613,10 @@ class SlitherReadStorage:
# Convert from hexadecimal to decimal. # Convert from hexadecimal to decimal.
val = int( val = int(
get_storage_data( get_storage_data(
self.web3, self.checksum_address, int.to_bytes(slot, 32, byteorder="big") self.web3,
self.checksum_address,
int.to_bytes(slot, 32, byteorder="big"),
self.block,
).hex(), ).hex(),
16, 16,
) )

@ -55,16 +55,19 @@ def coerce_type(
return value.hex() return value.hex()
def get_storage_data(web3, checksum_address: ChecksumAddress, slot: bytes) -> bytes: def get_storage_data(
web3, checksum_address: ChecksumAddress, slot: bytes, block: Union[int, str]
) -> bytes:
""" """
Retrieves the storage data from the blockchain at target address and slot. Retrieves the storage data from the blockchain at target address and slot.
Args: Args:
web3: Web3 instance provider. web3: Web3 instance provider.
checksum_address (ChecksumAddress): The address to query. checksum_address (ChecksumAddress): The address to query.
slot (bytes): The slot to retrieve data from. slot (bytes): The slot to retrieve data from.
block (optional int|str): The block number to retrieve data from
Returns: Returns:
(HexBytes): The slot's storage data. (HexBytes): The slot's storage data.
""" """
return bytes(web3.eth.get_storage_at(checksum_address, slot)).rjust( return bytes(web3.eth.get_storage_at(checksum_address, slot, block)).rjust(
32, bytes(1) 32, bytes(1)
) # pad to 32 bytes ) # pad to 32 bytes

@ -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":

@ -10,6 +10,7 @@ class Colors: # pylint: disable=too-few-public-methods
YELLOW = "\033[93m" YELLOW = "\033[93m"
BLUE = "\033[94m" BLUE = "\033[94m"
MAGENTA = "\033[95m" MAGENTA = "\033[95m"
BOLD = "\033[1m"
END = "\033[0m" END = "\033[0m"
@ -83,6 +84,7 @@ yellow = partial(colorize, Colors.YELLOW)
red = partial(colorize, Colors.RED) red = partial(colorize, Colors.RED)
blue = partial(colorize, Colors.BLUE) blue = partial(colorize, Colors.BLUE)
magenta = partial(colorize, Colors.MAGENTA) magenta = partial(colorize, Colors.MAGENTA)
bold = partial(colorize, Colors.BOLD)
# We enable colorization by default if the output is a tty # We enable colorization by default if the output is a tty
set_colorization_enabled(sys.stdout.isatty()) set_colorization_enabled(sys.stdout.isatty())

@ -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,4 +1,4 @@
import sha3 from Crypto.Hash import keccak
def get_function_id(sig: str) -> int: def get_function_id(sig: str) -> int:
@ -9,6 +9,6 @@ def get_function_id(sig: str) -> int:
Return: Return:
(int) (int)
""" """
s = sha3.keccak_256() digest = keccak.new(digest_bits=256)
s.update(sig.encode("utf-8")) digest.update(sig.encode("utf-8"))
return int("0x" + s.hexdigest()[:8], 16) return int("0x" + digest.hexdigest()[:8], 16)

@ -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,12 +1,14 @@
import math import math
from typing import List, Union from typing import List, Union, Set
from slither.core.solidity_types import ArrayType, MappingType, ElementaryType, UserDefinedType from slither.core.solidity_types import ArrayType, MappingType, ElementaryType, UserDefinedType
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
from slither.core.variables.variable import Variable from slither.core.variables.variable import Variable
def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type]]) -> str: def _convert_type_for_solidity_signature_to_string(
types: Union[Type, List[Type]], seen: Set[Type]
) -> str:
if isinstance(types, Type): if isinstance(types, Type):
# Array might be struct, so we need to go again through the conversion here # Array might be struct, so we need to go again through the conversion here
# We could have this logic in convert_type_for_solidity_signature # We could have this logic in convert_type_for_solidity_signature
@ -15,8 +17,10 @@ def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type]
# Which is currently not supported. This comes down to (uint, uint)[] not being possible in Solidity # Which is currently not supported. This comes down to (uint, uint)[] not being possible in Solidity
# While having an array of a struct of two uint leads to a (uint, uint)[] signature # While having an array of a struct of two uint leads to a (uint, uint)[] signature
if isinstance(types, ArrayType): if isinstance(types, ArrayType):
underlying_type = convert_type_for_solidity_signature(types.type) underlying_type = convert_type_for_solidity_signature(types.type, seen)
underlying_type_str = _convert_type_for_solidity_signature_to_string(underlying_type) underlying_type_str = _convert_type_for_solidity_signature_to_string(
underlying_type, seen
)
return underlying_type_str + "[]" return underlying_type_str + "[]"
return str(types) return str(types)
@ -26,9 +30,9 @@ def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type]
ret = "(" ret = "("
for underlying_type in types: for underlying_type in types:
if first_item: if first_item:
ret += _convert_type_for_solidity_signature_to_string(underlying_type) ret += _convert_type_for_solidity_signature_to_string(underlying_type, seen)
else: else:
ret += "," + _convert_type_for_solidity_signature_to_string(underlying_type) ret += "," + _convert_type_for_solidity_signature_to_string(underlying_type, seen)
first_item = False first_item = False
ret += ")" ret += ")"
@ -36,14 +40,33 @@ def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type]
def convert_type_for_solidity_signature_to_string(t: Type) -> str: def convert_type_for_solidity_signature_to_string(t: Type) -> str:
types = convert_type_for_solidity_signature(t) seen: Set[Type] = set()
return _convert_type_for_solidity_signature_to_string(types) types = convert_type_for_solidity_signature(t, seen)
return _convert_type_for_solidity_signature_to_string(types, seen)
def convert_type_for_solidity_signature(t: Type) -> Union[Type, List[Type]]: def convert_type_for_solidity_signature(t: Type, seen: Set[Type]) -> Union[Type, List[Type]]:
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract, Enum, Structure from slither.core.declarations import Contract, Enum, Structure
# Solidity allows recursive type for structure definition if its not used in public /external
# When this happens we can reach an infinite loop. If we detect a loop, we just stop converting the underlying type
# This is ok, because it wont happen for public/external function
#
# contract A{
#
# struct St{
# St[] a;
# uint b;
# }
#
# function f(St memory s) internal{}
#
# }
if t in seen:
return t
seen.add(t)
if isinstance(t, UserDefinedType): if isinstance(t, UserDefinedType):
underlying_type = t.type underlying_type = t.type
if isinstance(underlying_type, Contract): if isinstance(underlying_type, Contract):
@ -61,21 +84,24 @@ def convert_type_for_solidity_signature(t: Type) -> Union[Type, List[Type]]:
if isinstance(underlying_type, Structure): if isinstance(underlying_type, Structure):
# We can't have recursive types for structure, so recursion is ok here # We can't have recursive types for structure, so recursion is ok here
types = [ types = [
convert_type_for_solidity_signature(x.type) for x in underlying_type.elems_ordered convert_type_for_solidity_signature(x.type, seen)
for x in underlying_type.elems_ordered
] ]
return types return types
return t return t
def _export_nested_types_from_variable(current_type: Type, ret: List[Type]) -> None: def _export_nested_types_from_variable(
current_type: Type, ret: List[Type], seen: Set[Type]
) -> None:
""" """
Export the list of nested types (mapping/array) Export the list of nested types (mapping/array)
:param variable: :param variable:
:return: list(Type) :return: list(Type)
""" """
if isinstance(current_type, MappingType): if isinstance(current_type, MappingType):
underlying_type = convert_type_for_solidity_signature(current_type.type_from) underlying_type = convert_type_for_solidity_signature(current_type.type_from, seen)
if isinstance(underlying_type, list): if isinstance(underlying_type, list):
ret.extend(underlying_type) ret.extend(underlying_type)
else: else:
@ -87,7 +113,7 @@ def _export_nested_types_from_variable(current_type: Type, ret: List[Type]) -> N
next_type = current_type.type next_type = current_type.type
else: else:
return return
_export_nested_types_from_variable(next_type, ret) _export_nested_types_from_variable(next_type, ret, seen)
def export_nested_types_from_variable(variable: Variable) -> List[Type]: def export_nested_types_from_variable(variable: Variable) -> List[Type]:
@ -97,7 +123,8 @@ def export_nested_types_from_variable(variable: Variable) -> List[Type]:
:return: list(Type) :return: list(Type)
""" """
l: List[Type] = [] l: List[Type] = []
_export_nested_types_from_variable(variable.type, l) seen: Set[Type] = set()
_export_nested_types_from_variable(variable.type, l, seen)
return l return l

@ -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