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",
"dapp",
"data_dependency",
"path_filtering",
# "embark",
"erc",
# "etherlime",

@ -45,7 +45,7 @@ jobs:
- name: Test with pytest
run: |
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/test_functions_ids.py
pytest tests/test_function.py

1
.gitignore vendored

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

@ -68,8 +68,8 @@ To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/d
### Parser tests
- 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 --generate`. This will generate the json artifacts in `tests/expected_json`. Add the generated files 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/ast-parsing/expected_json`. Add the generated files to git.
- 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`

@ -26,6 +26,14 @@ RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y --no-install-recommends python3-pip \
&& 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
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
--- | --- | --- | --- | ---
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
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
4 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
5 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | 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
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
8 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
9 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
10 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
11 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
12 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | 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
14 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
15 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | 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
17 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
18 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | 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
20 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
21 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
22 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
23 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
24 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
25 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
26 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | 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
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
29 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
30 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
31 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
32 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
33 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
34 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
35 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
36 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
37 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
38 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
39 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
40 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
41 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
42 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
43 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
44 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
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
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
47 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
48 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
49 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
50 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
51 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
52 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
53 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | 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
55 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
56 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
57 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
58 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
59 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
60 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
61 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
62 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
63 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | 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
65 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
66 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
67 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
68 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
69 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | 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
71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | 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
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
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)
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)
2 | `arbitrary-send-erc20` | [transferFrom uses arbitrary `from`](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20) | 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 | `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 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
6 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
7 | `protected-vars` | [Detected unprotected variables](https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables) | 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 | `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 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
11 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
12 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
13 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
14 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High
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 | `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 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
18 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | 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 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
21 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
22 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
23 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
24 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
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 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
27 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
28 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
29 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
30 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
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 | `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 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
34 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
35 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | 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 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
38 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | 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 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | 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 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
43 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
44 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
45 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
46 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
47 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
48 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
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 | `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 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
52 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
53 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
54 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
55 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
56 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
57 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
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 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
60 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
61 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
62 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
63 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | 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 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
66 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
67 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | 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 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
70 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
71 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
72 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
73 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
74 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
75 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
76 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
77 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
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
- 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.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_printers = []
plugin_printers: List[Type[AbstractPrinter]] = []
return plugin_detectors, plugin_printers

@ -4,17 +4,17 @@
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"
exit 1
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"
exit 1
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"
exit 1
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
truffle unbox metacoin
if ! slither .; then
if ! slither . --no-fail-pedantic; then
echo "Truffle test failed"
exit 1
fi

@ -8,14 +8,14 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.8.3",
version="0.9.0",
packages=find_packages(),
python_requires=">=3.8",
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
# "crytic-compile>=0.2.3",
"crytic-compile",
"pycryptodome>=3.4.6",
"crytic-compile>=0.2.4",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
],
extras_require={
"dev": [
@ -28,7 +28,6 @@ setup(
"solc-select>=v1.0.0b1",
]
},
dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0",
long_description=long_description,
long_description_content_type="text/markdown",
@ -45,6 +44,7 @@ setup(
"slither-prop = slither.tools.properties.__main__:main",
"slither-mutate = slither.tools.mutator.__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 sys
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 crytic_compile import cryticparser
from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export
from crytic_compile.platform.etherscan import SUPPORTED_NETWORK
from crytic_compile import compile_all, is_supported
@ -55,10 +55,10 @@ logger = logging.getLogger("Slither")
def process_single(
target: str,
target: Union[str, CryticCompile],
args: argparse.Namespace,
detector_classes: List[AbstractDetector],
printer_classes: List[AbstractPrinter],
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
"""
The core high-level code for running Slither static analysis.
@ -80,8 +80,8 @@ def process_single(
def process_all(
target: str,
args: argparse.Namespace,
detector_classes: List[AbstractDetector],
printer_classes: List[AbstractPrinter],
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[List[Slither], List[Dict], List[Dict], int]:
compilations = compile_all(target, **vars(args))
slither_instances = []
@ -109,8 +109,8 @@ def process_all(
def _process(
slither: Slither,
detector_classes: List[AbstractDetector],
printer_classes: List[AbstractPrinter],
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
for detector_cls in detector_classes:
slither.register_detector(detector_cls)
@ -137,13 +137,14 @@ def _process(
return slither, results_detectors, results_printers, analyzed_contracts_count
# TODO: delete me?
def process_from_asts(
filenames: List[str],
args: argparse.Namespace,
detector_classes: List[AbstractDetector],
printer_classes: List[AbstractPrinter],
):
all_contracts = []
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
all_contracts: List[str] = []
for filename in filenames:
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 = [d for d in detectors if inspect.isclass(d) and issubclass(d, AbstractDetector)]
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)]
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_ = [getattr(all_printers, name) for name in dir(all_printers)]
printers = [p for p in printers_ if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
# Handle plugins!
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
def choose_detectors(
args: argparse.Namespace, all_detector_classes: List[AbstractDetector]
) -> List[AbstractDetector]:
args: argparse.Namespace, all_detector_classes: List[Type[AbstractDetector]]
) -> List[Type[AbstractDetector]]:
# If detectors are specified, run only these ones
detectors_to_run = []
@ -245,8 +248,8 @@ def choose_detectors(
def choose_printers(
args: argparse.Namespace, all_printer_classes: List[AbstractPrinter]
) -> List[AbstractPrinter]:
args: argparse.Namespace, all_printer_classes: List[Type[AbstractPrinter]]
) -> List[Type[AbstractPrinter]]:
printers_to_run = []
# 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:
return args.filter_paths.split(",")
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 += "\ntarget can be:\n"
usage += "\t- file.sol // a Solidity file\n"
usage += "\t- project_directory // a project directory. See https://github.com/crytic/crytic-compile/#crytic-compile for the supported platforms\n"
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"
parser = argparse.ArgumentParser(
@ -397,11 +403,19 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_detector.add_argument(
"--fail-pedantic",
help="Fail if any finding is detected",
help="Return the number of findings in the exit code",
action="store_true",
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(
"--fail-low",
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
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()
detector_types_json = output_detectors_json(detectors)
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
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()
output_printers(printers)
parser.exit()
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()
assert isinstance(values, str)
output_to_markdown(detectors, printers, values)
parser.exit()
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()
assert isinstance(values, str)
output_wiki(detectors, values)
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
sys.setrecursionlimit(1500)
@ -681,8 +713,9 @@ def main():
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
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_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.setLevel(logging.INFO)
results_detectors = []
results_printers = []
results_detectors: List[Dict] = []
results_printers: List[Dict] = []
try:
filename = args.filename
@ -798,6 +831,7 @@ def main_impl(
if "compilations" in args.json_types:
compilation_results = []
for slither_instance in slither_instances:
assert slither_instance.crytic_compile
compilation_results.append(
generate_standard_export(slither_instance.crytic_compile)
)
@ -848,7 +882,7 @@ def main_impl(
except Exception: # pylint: disable=broad-except
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(output_error)
@ -871,7 +905,7 @@ def main_impl(
if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type)
if args.perf:
if args.perf and cp:
cp.disable()
stats = pstats.Stats(cp).sort_stats("cumtime")
stats.print_stats()

@ -322,6 +322,13 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
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
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
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
"""
if self._solidity_signature is None:

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

@ -1,7 +1,9 @@
from typing import Optional, Union, TYPE_CHECKING
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.integer_conversion import convert_string_to_int
if TYPE_CHECKING:
from slither.core.solidity_types.type import Type
@ -29,6 +31,10 @@ class Literal(Expression):
def __str__(self):
if 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
return str(self._value)

@ -4,7 +4,7 @@ from slither.core.expressions.expression import 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)
super().__init__()
self._expressions = expressions
@ -13,6 +13,6 @@ class TupleExpression(Expression):
def expressions(self) -> List[Expression]:
return self._expressions
def __str__(self):
def __str__(self) -> str:
expressions_str = [str(e) for e in self.expressions]
return "(" + ",".join(expressions_str) + ")"

@ -53,7 +53,7 @@ class ArrayType(Type):
def storage_size(self) -> Tuple[int, bool]:
if self._length_value:
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
def __str__(self):

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

@ -189,7 +189,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if results and self.slither.triage_mode:
while True:
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":
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.delegatecall_in_loop import DelegatecallInLoop
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.6.11 - 0.6.12
- 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.
Consider using the latest version of Solidity for testing."""
# endregion wiki_recommendation
@ -52,24 +59,13 @@ Consider using the latest version of Solidity for testing."""
OLD_VERSION_TXT = "allows old versions"
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 = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
)
# Indicates the allowed versions. Must be formatted in increasing order.
ALLOWED_VERSIONS = [
"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",
]
ALLOWED_VERSIONS = ["0.5.16", "0.5.17", "0.6.11", "0.6.12", "0.7.5", "0.7.6", "0.8.16"]
# Indicates the versions that should not be used.
BUGGY_VERSIONS = [

@ -14,7 +14,7 @@ class ArbitrarySendErc20NoPermit(AbstractDetector):
IMPACT = 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_DESCRIPTION = "Detect when `msg.sender` is not used as `from` in transferFrom."

@ -14,7 +14,7 @@ class ArbitrarySendErc20Permit(AbstractDetector):
IMPACT = DetectorClassification.HIGH
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_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)
for node in nodes:
info += ["\t-", node, "\n"]
info += ["\t- ", node, "\n"]
res = self.generate_result(info)
results.append(res)

@ -65,21 +65,22 @@ class UnprotectedUpgradeable(AbstractDetector):
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Buggy is Initializable{
address payable owner;
function initialize() external initializer{
require(owner == address(0));
owner = msg.sender;
}
function kill() external{
require(msg.sender == owner);
selfdestruct(owner);
}
```solidity
contract Buggy is Initializable{
address payable owner;
function initialize() external initializer{
require(owner == address(0));
owner = msg.sender;
}
function kill() external{
require(msg.sender == 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
WIKI_RECOMMENDATION = (

@ -1,15 +1,40 @@
"""
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.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.utils.output import Output
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.variables.state_variable import StateVariable
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):
"""
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_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
valid_solidity_function = [
SolidityFunction("keccak256()"),
@ -46,13 +67,7 @@ class ConstCandidateStateVars(AbstractDetector):
SolidityFunction("mulmod(uint256,uint256,uint256)"),
]
@staticmethod
def _is_constant_var(v):
if isinstance(v, StateVariable):
return v.is_constant
return False
def _constant_initial_expression(self, v):
def _constant_initial_expression(self, v: Variable) -> bool:
if not v.expression:
return True
@ -60,34 +75,39 @@ class ConstCandidateStateVars(AbstractDetector):
values = export.result()
if not values:
return True
if all(
(val in self.valid_solidity_function or self._is_constant_var(val) for val in values)
):
if all((val in self.valid_solidity_function or _is_constant_var(val) for val in values)):
return True
return False
def _detect(self):
def _detect(self) -> List[Output]:
"""Detect state variables that could be const"""
results = []
all_variables = [c.state_variables for c in self.compilation_unit.contracts]
all_variables = {item for sublist in all_variables for item in sublist}
all_non_constant_elementary_variables = {
v for v in all_variables if self._valid_candidate(v)
all_variables_l = [c.state_variables for c in self.compilation_unit.contracts]
all_variables: Set[StateVariable] = {
item for sublist in all_variables_l for item in sublist
}
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_non_constant_elementary_variables = {v for v in all_variables if _valid_candidate(v)}
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 = [
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}
constable_variables = [
constable_variables: List[Variable] = [
v
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
constable_variables = sorted(constable_variables, key=lambda x: x.canonical_name)
@ -101,5 +121,5 @@ class ConstCandidateStateVars(AbstractDetector):
return results
@staticmethod
def _format(compilation_unit: SlitherCompilationUnit, result):
def _format(compilation_unit: SlitherCompilationUnit, result: Dict) -> None:
custom_format(compilation_unit, result)

@ -9,7 +9,7 @@ from slither.utils.myprettytable import MyPrettyTable
class FunctionIds(AbstractPrinter):
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"

@ -1,5 +1,5 @@
import logging
from typing import Union, List, ValuesView
from typing import Union, List, ValuesView, Type, Dict
from crytic_compile import CryticCompile, InvalidCompilation
@ -19,7 +19,9 @@ logger_detector = logging.getLogger("Detectors")
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:
raise Exception(
@ -178,7 +180,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
def detectors_optimization(self):
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`.
"""
@ -188,7 +190,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
instance = detector_class(compilation_unit, self, logger_detector)
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`.
"""
@ -197,7 +199,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
instance = printer_class(self, logger_printer)
self._printers.append(instance)
def run_detectors(self):
def run_detectors(self) -> List[Dict]:
"""
: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()}
# ...
# 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.set_expression(ins.expression)
eventcall.call_id = ins.call_id

@ -1,8 +1,10 @@
from typing import Optional, List
from slither.slithir.operations.operation import Operation
class Call(Operation):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._arguments = []
@ -14,7 +16,7 @@ class Call(Operation):
def arguments(self, 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
:return: bool

@ -17,4 +17,4 @@ class EventCall(Call):
def __str__(self):
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):
return self._callid
@property
def read(self):
return [self.called]
@call_id.setter
def call_id(self, c):
self._callid = c
@property
def read(self):
return [self.called]
@property
def called(self):
return self._called

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

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

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

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

@ -675,7 +675,7 @@ def parse_yul_variable_declaration(
the assignment
"""
if not ast["value"]:
if "value" not in ast or not ast["value"]:
return None
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]:
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:
kind = "bool" if value in ["true", "false"] else "uint256"

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

@ -1,7 +1,10 @@
import logging
from typing import Dict, List, Optional, Set
from slither.core.declarations import Contract
from slither.slithir.operations import EventCall
from slither.utils import output
from slither.utils.erc import ERC, ERC_EVENT
from slither.utils.type import (
export_nested_types_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
def _check_signature(erc_function, contract, ret):
def _check_signature(erc_function: ERC, contract: Contract, ret: Dict) -> None:
name = erc_function.name
parameters = erc_function.parameters
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)
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
parameters = erc_event.parameters
indexes = erc_event.indexes
@ -180,7 +183,13 @@ def _check_events(erc_event, contract, ret):
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:
explored = set()

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

@ -4,7 +4,7 @@ import uuid
from collections import namedtuple
from enum import Enum as PythonEnum
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.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.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:
src_mapping = elem.source_mapping
content = self._compilation_unit.core.source_code[src_mapping["filename_absolute"]]
start = src_mapping["start"]
end = src_mapping["start"] + src_mapping["length"]
content = self._compilation_unit.core.source_code[src_mapping.filename.absolute]
start = src_mapping.start
end = src_mapping.start + src_mapping.length
self._source_codes_top_level[elem] = content[start:end]
def _check_abi_encoder_v2(self):

@ -16,7 +16,7 @@ logger.handlers[0].setFormatter(formatter)
logger.propagate = False
def parse_args():
def parse_args() -> argparse.Namespace:
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
@ -56,7 +56,7 @@ def parse_args():
return parser.parse_args()
def main():
def main() -> None:
# ------------------------------
# Usage: slither-kspec-coverage contract kspec
# 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 import Slither
def kspec_coverage(args):
def kspec_coverage(args: argparse.Namespace) -> None:
contract = args.contract
kspec = args.kspec

@ -2,6 +2,7 @@ import argparse
import inspect
import logging
import sys
from typing import Type, List, Any
from crytic_compile import cryticparser
@ -22,7 +23,7 @@ logger.setLevel(logging.INFO)
###################################################################################
def parse_args():
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Experimental smart contract mutator. Based on https://arxiv.org/abs/2006.11597",
usage="slither-mutate target",
@ -48,14 +49,16 @@ def parse_args():
return parser.parse_args()
def _get_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)]
def _get_mutators() -> List[Type[AbstractMutator]]:
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)]
return detectors
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()
output_mutators(checks)
parser.exit()

@ -5,6 +5,7 @@ from argparse import ArgumentParser, Namespace
from crytic_compile import cryticparser
from slither import Slither
from slither.core.declarations import FunctionContract
from slither.utils.colors import red
from slither.tools.possible_paths.possible_paths import (
find_target_paths,
@ -58,7 +59,11 @@ def main() -> None:
# Print out all target functions.
print("Target functions:")
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")
# 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):
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.
: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
def resolve_functions(slither, functions):
def resolve_functions(
slither: Slither, functions: List[Union[str, Tuple[str, str]]]
) -> List[Function]:
"""
Resolves the provided function descriptors.
: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.
"""
# Create the resolved list.
resolved = []
resolved: List[Function] = []
# Verify that the provided argument is a list.
if not isinstance(functions, list):
@ -72,24 +81,31 @@ def resolve_functions(slither, functions):
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
: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 [function] + [
# TODO implement me
if not isinstance(function, FunctionContract):
return []
ret: List[Function] = [function]
ret += [
f
for c in function.contract.inheritance
for f in c.functions_and_modifiers_declared
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 []
# Create our results list
results = set()
results: Set[Tuple[Function, ...]] = set()
# Add our current function to the path.
current_path = [target_function] + current_path
@ -106,9 +122,12 @@ def __find_target_paths(slither, target_function, current_path=None):
continue
# 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 += function.internal_calls
called_functions = set(called_functions)
called_functions_list = [
f for (_, f) in function.high_level_calls if isinstance(f, Function)
]
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 all_target_functions.intersection(called_functions):
@ -123,14 +142,16 @@ def __find_target_paths(slither, target_function, current_path=None):
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.
: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.
"""
# Create our results list
results = set()
results: Set[Tuple[Function, ...]] = set()
# Loop for each target function
for target_function in target_functions:

@ -1,6 +1,7 @@
import argparse
import logging
import sys
from typing import Any
from crytic_compile import cryticparser
@ -26,7 +27,7 @@ logger.handlers[0].setFormatter(formatter)
logger.propagate = False
def _all_scenarios():
def _all_scenarios() -> str:
txt = "\n"
txt += "#################### ERC20 ####################\n"
for k, value in ERC20_PROPERTIES.items():
@ -35,29 +36,33 @@ def _all_scenarios():
return txt
def _all_properties():
def _all_properties() -> MyPrettyTable:
table = MyPrettyTable(["Num", "Description", "Scenario"])
idx = 0
for scenario, value in ERC20_PROPERTIES.items():
for prop in value.properties:
table.add_row([idx, prop.description, scenario])
table.add_row([str(idx), prop.description, scenario])
idx = idx + 1
return table
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())
parser.exit()
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())
parser.exit()
def parse_args():
def parse_args() -> argparse.Namespace:
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
@ -120,7 +125,7 @@ def parse_args():
return parser.parse_args()
def main():
def main() -> None:
args = parse_args()
# Perform slither analysis on the given filename

@ -15,7 +15,7 @@ PATTERN_TRUFFLE_MIGRATION = re.compile("^[0-9]*_")
logger = logging.getLogger("Slither")
def _extract_caller(p: PropertyCaller):
def _extract_caller(p: PropertyCaller) -> List[str]:
if p == PropertyCaller.OWNER:
return ["owner"]
if p == PropertyCaller.SENDER:
@ -28,7 +28,7 @@ def _extract_caller(p: PropertyCaller):
return ["user"]
def _helpers():
def _helpers() -> str:
"""
Generate two functions:
- 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,
addresses: Addresses,
assert_message: str = "",
):
) -> Path:
"""
Generate unit tests files
:param test_contract:
@ -134,7 +134,7 @@ def generate_unit_test( # pylint: disable=too-many-arguments,too-many-branches
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
:param test_contract:

@ -12,7 +12,7 @@ def write_file(
content: str,
allow_overwrite: bool = True,
discard_if_exist: bool = False,
):
) -> None:
"""
Write the content into output_dir/filename
: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.
--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.
--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.
--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
@ -28,19 +29,19 @@ optional arguments:
Retrieve the storage slots of a local contract:
```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:
```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`:
```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`:
@ -79,7 +80,7 @@ slither-read-storage 0xa2327a938Febf5FEC13baCFb16Ae10EcBc4cbDCF --variable-name
Take Avalanche, for instance:
```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

@ -21,9 +21,9 @@ def parse_args() -> argparse.Namespace:
"\nTo retrieve a single variable's value:\n"
+ "\tslither-read-storage $TARGET address --variable-name $NAME\n"
+ "To retrieve a contract's storage layout:\n"
+ "\tslither-read-storage $TARGET address --contract-name $NAME --layout\n"
+ "\tslither-read-storage $TARGET address --contract-name $NAME --json storage_layout.json\n"
+ "To retrieve a contract's storage layout and values:\n"
+ "\tslither-read-storage $TARGET address --contract-name $NAME --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"
),
)
@ -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(
"--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)
return parser.parse_args()
@ -122,6 +128,11 @@ def main() -> None:
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:
# Remove target prefix e.g. rinkeby:0x0 -> 0x0.
address = target[target.find(":") + 1 :]

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

@ -55,16 +55,19 @@ def coerce_type(
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.
Args:
web3: Web3 instance provider.
checksum_address (ChecksumAddress): The address to query.
slot (bytes): The slot to retrieve data from.
block (optional int|str): The block number to retrieve data from
Returns:
(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)
) # pad to 32 bytes

@ -17,7 +17,7 @@ logger = logging.getLogger("Slither-simil")
modes = ["info", "test", "train", "plot"]
def parse_args():
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
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()
default_log = logging.INFO

@ -74,7 +74,7 @@ def parse_target(target):
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 = {}
if infile.endswith(".npz"):
r = load_cache(infile, nsamples=nsamples)

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

@ -1,3 +1,4 @@
import argparse
import logging
import random
import sys
@ -23,7 +24,7 @@ except ImportError:
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:
logger.error(

@ -1,3 +1,4 @@
import argparse
import logging
import os
import sys
@ -10,7 +11,7 @@ from slither.tools.similarity.model import train_unsupervised
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:
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.
:return: Returns the arguments for the program.
@ -90,7 +90,7 @@ def parse_args():
return parser.parse_args()
def main():
def main() -> None:
# ------------------------------
# Usage: python3 -m slither_format filename
# Example: python3 -m slither_format contract.sol

@ -1,5 +1,9 @@
import logging
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.attributes.incorrect_solc import IncorrectSolc
from slither.detectors.attributes.constant_pragma import ConstantPragma
@ -13,7 +17,7 @@ from slither.utils.colors import yellow
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("Slither.Format")
all_detectors = {
all_detectors: Dict[str, Type[AbstractDetector]] = {
"unused-state": UnusedStateVars,
"solc-version": IncorrectSolc,
"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:
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
cls_detectors_to_run = []
cls_detectors_to_run: List[Type[AbstractDetector]] = []
exclude = detectors_to_exclude.split(",")
if detectors_to_run == "all":
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))
number_of_patches = 0
for file in patches:
@ -130,7 +136,7 @@ def print_patches(number_of_slither_results, patches):
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('"Number of Slither results":' + '"' + str(number_of_slither_results) + '",')
print('"Number of patchlets":' + '"' + str(len(patches)) + '"', ",")

@ -3,10 +3,13 @@ import inspect
import json
import logging
import sys
from typing import List, Any, Type, Dict, Tuple, Union, Sequence, Optional
from crytic_compile import cryticparser
from slither import Slither
from slither.core.declarations import Contract
from slither.exceptions import SlitherException
from slither.utils.colors import red
from slither.utils.output import output_to_json
@ -24,7 +27,7 @@ logger: logging.Logger = logging.getLogger("Slither")
logger.setLevel(logging.INFO)
def parse_args():
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Slither Upgradeability Checks. For usage information see https://github.com/crytic/slither/wiki/Upgradeability-Checks.",
usage="slither-check-upgradeability contract.sol ContractName",
@ -93,21 +96,27 @@ def parse_args():
###################################################################################
def _get_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)]
def _get_checks() -> List[Type[AbstractCheck]]:
detectors_ = [getattr(all_checks, name) for name in dir(all_checks)]
detectors: List[Type[AbstractCheck]] = [
c for c in detectors_ if inspect.isclass(c) and issubclass(c, AbstractCheck)
]
return detectors
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()
output_detectors(checks)
parser.exit()
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()
detector_types_json = output_detectors_json(checks)
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
def __call__(
self, parser, args, values, option_string=None
): # pylint: disable=signature-differs
self,
parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> None: # pylint: disable=signature-differs
checks = _get_checks()
assert isinstance(values, str)
output_to_markdown(checks, values)
parser.exit()
class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(
self, parser, args, values, option_string=None
): # pylint: disable=signature-differs
self,
parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> Any: # pylint: disable=signature-differs
checks = _get_checks()
assert isinstance(values, str)
output_wiki(checks, values)
parser.exit()
def _run_checks(detectors):
results = [d.check() for d in detectors]
results = [r for r in results if r]
results = [item for sublist in results for item in sublist] # flatten
def _run_checks(detectors: List[AbstractCheck]) -> List[Dict]:
results_ = [d.check() for d in detectors]
results_ = [r for r in results_ if r]
results = [item for sublist in results_ for item in sublist] # flatten
return results
def _checks_on_contract(detectors, contract):
detectors = [
def _checks_on_contract(
detectors: List[Type[AbstractCheck]], contract: Contract
) -> Tuple[List[Dict], int]:
detectors_ = [
d(logger, contract)
for d in detectors
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):
detectors = [
def _checks_on_contract_update(
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
]
return _run_checks(detectors), len(detectors)
return _run_checks(detectors_), len(detectors_)
def _checks_on_contract_and_proxy(detectors, contract, proxy):
detectors = [d(logger, contract, proxy=proxy) for d in detectors if d.REQUIRE_PROXY]
return _run_checks(detectors), len(detectors)
def _checks_on_contract_and_proxy(
detectors: List[Type[AbstractCheck]], contract: Contract, proxy: Contract
) -> 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
@ -168,8 +193,8 @@ def _checks_on_contract_and_proxy(detectors, contract, proxy):
###################################################################################
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
def main():
json_results = {
def main() -> None:
json_results: Dict = {
"proxy-present": False,
"contract_v2-present": False,
"detectors": [],
@ -254,7 +279,7 @@ def main():
number_detectors_run += number_detectors
# 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
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
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
detectors_list = sorted(
detector_classes, key=lambda element: (element.IMPACT, element.ARGUMENT)
@ -31,7 +33,7 @@ def output_wiki(detector_classes, filter_wiki):
print(recommendation)
def output_detectors(detector_classes):
def output_detectors(detector_classes: List[Type[AbstractCheck]]) -> None:
detectors_list = []
for detector in detector_classes:
argument = detector.ARGUMENT
@ -48,7 +50,7 @@ def output_detectors(detector_classes):
for (argument, help_info, impact, proxy, v2) in detectors_list:
table.add_row(
[
idx,
str(idx),
argument,
help_info,
classification_txt[impact],
@ -60,8 +62,8 @@ def output_detectors(detector_classes):
print(table)
def output_to_markdown(detector_classes, _filter_wiki):
def extract_help(cls):
def output_to_markdown(detector_classes: List[Type[AbstractCheck]], _filter_wiki: str) -> None:
def extract_help(cls: AbstractCheck) -> str:
if cls.WIKI == "":
return cls.HELP
return f"[{cls.HELP}]({cls.WIKI})"
@ -85,7 +87,9 @@ def output_to_markdown(detector_classes, _filter_wiki):
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 = []
for detector in detector_classes:
argument = detector.ARGUMENT
@ -110,7 +114,7 @@ def output_detectors_json(detector_classes):
# Sort by impact, confidence, and name
detectors_list = sorted(detectors_list, key=lambda element: (element[2], element[0]))
idx = 1
table = []
table: List[Dict[str, Union[str, int]]] = []
for (
argument,
help_info,

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

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

@ -1,13 +1,17 @@
import argparse
import json
import os
import re
import logging
from collections import defaultdict
from typing import Dict, List, Type, Union
from crytic_compile.cryticparser.defaults import (
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.myprettytable import MyPrettyTable
@ -34,7 +38,7 @@ defaults_flag_in_config = {
"exclude_low": False,
"exclude_medium": False,
"exclude_high": False,
"fail_pedantic": False,
"fail_pedantic": True,
"fail_low": False,
"fail_medium": 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
if args.config_file is None:
# 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..."))
def output_to_markdown(detector_classes, printer_classes, filter_wiki):
def extract_help(cls):
def output_to_markdown(
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 == "":
return cls.HELP
return f"[{cls.HELP}]({cls.WIKI})"
@ -127,7 +135,7 @@ def output_to_markdown(detector_classes, printer_classes, filter_wiki):
idx = idx + 1
def get_level(l):
def get_level(l: str) -> int:
tab = l.count("\t") + 1
if l.replace("\t", "").startswith(" -"):
tab = tab + 1
@ -136,7 +144,7 @@ def get_level(l):
return tab
def convert_result_to_markdown(txt):
def convert_result_to_markdown(txt: str) -> str:
# -1 to remove the last \n
lines = txt[0:-1].split("\n")
ret = []
@ -154,16 +162,21 @@ def convert_result_to_markdown(txt):
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)
info = defaultdict(dict)
for results in all_results:
checks[results["check"]].append(results)
info[results["check"]] = {"impact": results["impact"], "confidence": results["confidence"]}
info: Dict = defaultdict(dict)
for results_ in all_results:
checks[results_["check"]].append(results_)
info[results_["check"]] = {
"impact": results_["impact"],
"confidence": results_["confidence"],
}
print("Summary")
for check in checks:
print(f" - [{check}](#{check}) ({len(checks[check])} results) ({info[check]['impact']})")
for check_ in checks:
print(
f" - [{check_}](#{check_}) ({len(checks[check_])} results) ({info[check_]['impact']})"
)
counter = 0
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})**")
def output_wiki(detector_classes, filter_wiki):
detectors_list = []
def output_wiki(detector_classes: List[Type[AbstractDetector]], filter_wiki: str) -> None:
# Sort by impact, confidence, and name
detectors_list = sorted(
@ -223,7 +235,7 @@ def output_wiki(detector_classes, filter_wiki):
print(recommendation)
def output_detectors(detector_classes):
def output_detectors(detector_classes: List[Type[AbstractDetector]]) -> None:
detectors_list = []
for detector in detector_classes:
argument = detector.ARGUMENT
@ -242,12 +254,15 @@ def output_detectors(detector_classes):
)
idx = 1
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
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 = []
for detector in detector_classes:
argument = detector.ARGUMENT
@ -307,7 +322,7 @@ def output_detectors_json(detector_classes): # pylint: disable=too-many-locals
return table
def output_printers(printer_classes):
def output_printers(printer_classes: List[Type[AbstractPrinter]]) -> None:
printers_list = []
for printer in printer_classes:
argument = printer.ARGUMENT
@ -319,12 +334,12 @@ def output_printers(printer_classes):
printers_list = sorted(printers_list, key=lambda element: (element[0]))
idx = 1
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
print(table)
def output_printers_json(printer_classes):
def output_printers_json(printer_classes: List[Type[AbstractPrinter]]) -> List[Dict]:
printers_list = []
for printer in printer_classes:
argument = printer.ARGUMENT

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

@ -1,28 +1,35 @@
from decimal import Decimal
from fractions import Fraction
from typing import Union
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):
return val
return Fraction(val)
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:
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
# Would not be able to compile it
# 10**77 is the largest exponent that fits
# See https://github.com/ethereum/solidity/blob/9e61f92bd4d19b430cb8cb26f1c7cf79f1dff380/libsolidity/ast/Types.cpp#L1281-L1290
if expo > 77:
if base != Decimal(0):
if base != Fraction(0):
raise SlitherError(
f"{base}e{expo} is too large to fit in any Solidity integer size"
)
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 zipfile
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 pkg_resources import require
@ -129,7 +129,7 @@ def _output_result_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:
"""

@ -28,7 +28,7 @@ class StandardOutputCapture:
original_logger_handlers = None
@staticmethod
def enable(block_original=True):
def enable(block_original: bool = True) -> None:
"""
Redirects stdout and stderr to a capturable StringIO.
: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)]
@staticmethod
def disable():
def disable() -> None:
"""
Disables redirection of stdout/stderr, if previously enabled.
:return: None
@ -78,7 +78,7 @@ class StandardOutputCapture:
StandardOutputCapture.original_logger_handlers = None
@staticmethod
def get_stdout_output():
def get_stdout_output() -> str:
"""
Obtains the output from the currently set stdout
:return: Returns stdout output as a string
@ -87,7 +87,7 @@ class StandardOutputCapture:
return sys.stdout.read()
@staticmethod
def get_stderr_output():
def get_stderr_output() -> str:
"""
Obtains the output from the currently set stderr
:return: Returns stderr output as a string

@ -1,12 +1,14 @@
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.type import Type
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):
# 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
@ -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
# While having an array of a struct of two uint leads to a (uint, uint)[] signature
if isinstance(types, ArrayType):
underlying_type = convert_type_for_solidity_signature(types.type)
underlying_type_str = _convert_type_for_solidity_signature_to_string(underlying_type)
underlying_type = convert_type_for_solidity_signature(types.type, seen)
underlying_type_str = _convert_type_for_solidity_signature_to_string(
underlying_type, seen
)
return underlying_type_str + "[]"
return str(types)
@ -26,9 +30,9 @@ def _convert_type_for_solidity_signature_to_string(types: Union[Type, List[Type]
ret = "("
for underlying_type in types:
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:
ret += "," + _convert_type_for_solidity_signature_to_string(underlying_type)
ret += "," + _convert_type_for_solidity_signature_to_string(underlying_type, seen)
first_item = False
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:
types = convert_type_for_solidity_signature(t)
return _convert_type_for_solidity_signature_to_string(types)
seen: Set[Type] = set()
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
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):
underlying_type = t.type
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):
# We can't have recursive types for structure, so recursion is ok here
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 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)
:param variable:
:return: list(Type)
"""
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):
ret.extend(underlying_type)
else:
@ -87,7 +113,7 @@ def _export_nested_types_from_variable(current_type: Type, ret: List[Type]) -> N
next_type = current_type.type
else:
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]:
@ -97,7 +123,8 @@ def export_nested_types_from_variable(variable: Variable) -> List[Type]:
:return: 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

@ -1,5 +1,5 @@
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
@ -72,15 +72,15 @@ class ConstantFolding(ExpressionVisitor):
cf = ConstantFolding(expr, self._type)
expr = cf.result()
assert isinstance(expr, Literal)
set_val(expression, int(expr.value))
set_val(expression, -convert_string_to_fraction(expr.value))
else:
raise NotConstant
def _post_literal(self, expression):
if expression.value.isdigit():
set_val(expression, int(expression.value))
else:
raise NotConstant
try:
set_val(expression, convert_string_to_fraction(expression.value))
except ValueError as e:
raise NotConstant from e
def _post_assignement_operation(self, expression):
raise NotConstant
@ -115,7 +115,7 @@ class ConstantFolding(ExpressionVisitor):
cf = ConstantFolding(expression.expressions[0], self._type)
expr = cf.result()
assert isinstance(expr, Literal)
set_val(expression, int(expr.value))
set_val(expression, convert_string_to_fraction(expr.value))
return
raise NotConstant

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

Loading…
Cancel
Save