Merge branch 'dev' into dev-baseline-tests

pull/858/head
Feist Josselin 3 years ago committed by GitHub
commit 35e4b263ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/black.yml
  2. 4
      .github/workflows/ci.yml
  3. 47
      .github/workflows/features.yml
  4. 2
      .github/workflows/linter.yml
  5. 27
      .github/workflows/pip-audit.yml
  6. 2
      .github/workflows/pylint.yml
  7. 10
      CONTRIBUTING.md
  8. 152
      README.md
  9. 2
      plugin_example/README.md
  10. 3
      pyproject.toml
  11. 9
      setup.py
  12. 62
      slither/__main__.py
  13. 42
      slither/analyses/data_dependency/data_dependency.py
  14. 3
      slither/analyses/write/are_variables_written.py
  15. 32
      slither/core/cfg/node.py
  16. 60
      slither/core/compilation_unit.py
  17. 4
      slither/core/context/context.py
  18. 78
      slither/core/declarations/contract.py
  19. 71
      slither/core/declarations/custom_error.py
  20. 12
      slither/core/declarations/custom_error_contract.py
  21. 14
      slither/core/declarations/custom_error_top_level.py
  22. 9
      slither/core/declarations/enum_top_level.py
  23. 73
      slither/core/declarations/function.py
  24. 10
      slither/core/declarations/function_contract.py
  25. 21
      slither/core/declarations/function_top_level.py
  26. 28
      slither/core/declarations/import_directive.py
  27. 8
      slither/core/declarations/pragma_directive.py
  28. 41
      slither/core/declarations/solidity_import_placeholder.py
  29. 41
      slither/core/declarations/solidity_variables.py
  30. 6
      slither/core/declarations/structure.py
  31. 10
      slither/core/declarations/structure_top_level.py
  32. 10
      slither/core/expressions/assignment_operation.py
  33. 2
      slither/core/expressions/expression.py
  34. 0
      slither/core/scope/__init__.py
  35. 100
      slither/core/scope/scope.py
  36. 8
      slither/core/slither_core.py
  37. 2
      slither/core/solidity_types/array_type.py
  38. 2
      slither/core/solidity_types/elementary_type.py
  39. 2
      slither/core/source_mapping/source_mapping.py
  40. 14
      slither/core/variables/variable.py
  41. 89
      slither/detectors/abstract_detector.py
  42. 5
      slither/detectors/all_detectors.py
  43. 3
      slither/detectors/assembly/shift_parameter_mixup.py
  44. 5
      slither/detectors/attributes/const_functions_asm.py
  45. 5
      slither/detectors/attributes/const_functions_state.py
  46. 38
      slither/detectors/attributes/incorrect_solc.py
  47. 3
      slither/detectors/attributes/locked_ether.py
  48. 3
      slither/detectors/attributes/unimplemented_interface.py
  49. 3
      slither/detectors/compiler_bugs/array_by_reference.py
  50. 3
      slither/detectors/compiler_bugs/enum_conversion.py
  51. 3
      slither/detectors/compiler_bugs/multiple_constructor_schemes.py
  52. 6
      slither/detectors/compiler_bugs/public_mapping_nested.py
  53. 6
      slither/detectors/compiler_bugs/reused_base_constructor.py
  54. 7
      slither/detectors/compiler_bugs/storage_ABIEncoderV2_array.py
  55. 7
      slither/detectors/compiler_bugs/storage_signed_integer_array.py
  56. 4
      slither/detectors/compiler_bugs/uninitialized_function_ptr_in_constructor.py
  57. 3
      slither/detectors/erc/incorrect_erc20_interface.py
  58. 3
      slither/detectors/erc/incorrect_erc721_interface.py
  59. 5
      slither/detectors/erc/unindexed_event_parameters.py
  60. 19
      slither/detectors/functions/arbitrary_send.py
  61. 3
      slither/detectors/functions/dead_code.py
  62. 3
      slither/detectors/functions/modifier.py
  63. 3
      slither/detectors/functions/suicidal.py
  64. 3
      slither/detectors/functions/unimplemented.py
  65. 3
      slither/detectors/naming_convention/naming_convention.py
  66. 21
      slither/detectors/operations/bad_prng.py
  67. 3
      slither/detectors/operations/missing_events_access_control.py
  68. 3
      slither/detectors/operations/missing_events_arithmetic.py
  69. 5
      slither/detectors/operations/missing_zero_address_validation.py
  70. 3
      slither/detectors/operations/unchecked_low_level_return_values.py
  71. 3
      slither/detectors/operations/unchecked_send_return_value.py
  72. 3
      slither/detectors/operations/unchecked_transfer.py
  73. 3
      slither/detectors/operations/unused_return_values.py
  74. 3
      slither/detectors/operations/void_constructor.py
  75. 6
      slither/detectors/reentrancy/reentrancy_benign.py
  76. 6
      slither/detectors/reentrancy/reentrancy_eth.py
  77. 6
      slither/detectors/reentrancy/reentrancy_events.py
  78. 6
      slither/detectors/reentrancy/reentrancy_no_gas.py
  79. 6
      slither/detectors/reentrancy/reentrancy_read_before_write.py
  80. 8
      slither/detectors/reentrancy/token.py
  81. 22
      slither/detectors/shadowing/abstract.py
  82. 3
      slither/detectors/shadowing/builtin_symbols.py
  83. 3
      slither/detectors/shadowing/local.py
  84. 3
      slither/detectors/shadowing/state.py
  85. 18
      slither/detectors/slither/name_reused.py
  86. 4
      slither/detectors/source/rtlo.py
  87. 5
      slither/detectors/statements/array_length_assignment.py
  88. 6
      slither/detectors/statements/assert_state_change.py
  89. 3
      slither/detectors/statements/boolean_constant_equality.py
  90. 3
      slither/detectors/statements/boolean_constant_misuse.py
  91. 79
      slither/detectors/statements/calls_in_loop.py
  92. 3
      slither/detectors/statements/controlled_delegatecall.py
  93. 86
      slither/detectors/statements/costly_operations_in_loop.py
  94. 97
      slither/detectors/statements/delegatecall_in_loop.py
  95. 3
      slither/detectors/statements/deprecated_calls.py
  96. 3
      slither/detectors/statements/divide_before_multiply.py
  97. 16
      slither/detectors/statements/incorrect_strict_equality.py
  98. 3
      slither/detectors/statements/mapping_deletion.py
  99. 89
      slither/detectors/statements/msg_value_in_loop.py
  100. 3
      slither/detectors/statements/redundant_statements.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -36,7 +36,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Black
uses: docker://github/super-linter:v3
uses: docker://github/super-linter:v4
if: always()
env:
# run linter on everything to catch preexisting problems

@ -32,7 +32,7 @@ jobs:
"slither_config",
"truffle",
"upgradability",
"prop",
# "prop",
"flat"]
steps:
- uses: actions/checkout@v1
@ -55,4 +55,4 @@ jobs:
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: |
bash scripts/ci_test_${TEST_TYPE}.sh
bash "scripts/ci_test_${TEST_TYPE}.sh"

@ -0,0 +1,47 @@
---
name: Features tests
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
jobs:
build:
name: Features tests
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
with:
python-version: 3.6
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest
pip install solc-select
solc-select install all
solc-select use 0.8.0
cd tests/test_node_modules/
npm install hardhat
cd ../..
- name: Test with pytest
run: |
pytest tests/test_features.py

@ -36,7 +36,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Lint everything else
uses: docker://github/super-linter:v3
uses: docker://github/super-linter:v4
if: always()
env:
# run linter on everything to catch preexisting problems

@ -0,0 +1,27 @@
name: pip-audit
on:
push:
branches: [ dev, master ]
pull_request:
branches: [ dev, master ]
schedule: [ cron: "0 7 * * 2" ]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python 3.10
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: Install pip-audit
run: |
python -m pip install --upgrade pip
python -m pip install pip-audit
- name: Run pip-audit
run: |
python -m pip install .
pip-audit --desc -v

@ -36,7 +36,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Pylint
uses: docker://github/super-linter:v3
uses: docker://github/super-linter:v4
if: always()
env:
# run linter on everything to catch preexisting problems

@ -37,7 +37,7 @@ To run them locally in the root dir of the repository:
- `pylint slither tests --rcfile pyproject.toml`
- `black . --config pyproject.toml`
We use pylint `2.8.2` black `20.8b1`.
We use pylint `2.12.2` black `21.10b0`.
### Detectors tests
For each new detector, at least one regression tests must be present.
@ -56,3 +56,11 @@ To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/d
- 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`
### Synchronization with crytic-compile
By default, `slither` follows either the latest version of crytic-compile in pip, or `crytic-compile@master` (look for dependencies in [`setup.py`](./setup.py). If crytic-compile development comes with breaking changes, the process to update `slither` is:
- Update `slither/setup.py` to point to the related crytic-compile's branch
- Create a PR in `slither` and ensure it passes the CI
- Once the development branch is merged in `crytic-compile@master`, ensure `slither` follows the `master` branch
The `slither`'s PR can either be merged while using a crytic-compile non-`master` branch, or kept open until the breaking changes are available in `crytic-compile@master`.

@ -64,70 +64,72 @@ Num | Detector | What it Detects | Impact | Confidence
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 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
17 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
18 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
19 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
20 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
21 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
22 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
23 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
24 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
25 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
26 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
27 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
28 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
29 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
30 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
31 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
32 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
33 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
34 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
35 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
36 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
37 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
38 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
39 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
40 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
41 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
42 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
43 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
44 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
45 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
46 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
47 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
48 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
49 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
50 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
51 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
52 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
53 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
54 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
55 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
56 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
57 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
58 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
59 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state-variables) | Informational | High
60 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
61 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
62 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
63 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
64 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
65 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
66 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
67 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High
68 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
69 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
70 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
71 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-are-too-similar) | Informational | Medium
72 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
73 | `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
74 | `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
See the [Detectors Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for more information.
By default, all the detectors are run.
Check out [Crytic](https://crytic.io/) to get access to additional Slither's detectors and GitHub integration.
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-are-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
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
- The [Detection Selection](https://github.com/crytic/slither/wiki/Usage#detector-selection) to run only selected detectors. By default, all the detectors are run.
- The [Triage Mode](https://github.com/crytic/slither/wiki/Usage#triage-mode) to filter individual results
## Printers
@ -152,7 +154,7 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d
- `slither-prop`: [Automatic unit test and property generation](https://github.com/crytic/slither/wiki/Property-generation)
- `slither-flat`: [Flatten a codebase](https://github.com/crytic/slither/wiki/Contract-Flattening)
- `slither-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance)
- `slither-format`: [Automatic patches generation](https://github.com/crytic/slither/wiki/Slither-format)
- `slither-format`: [Automatic patch generation](https://github.com/crytic/slither/wiki/Slither-format)
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
@ -175,7 +177,7 @@ git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
```
We recommend using an Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
We recommend using a Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
### Using Docker
@ -199,7 +201,7 @@ Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (
* The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses.
* The [API documentation](https://github.com/trailofbits/slither/wiki/API-examples) describes the methods and objects available for custom analyses.
* The [API documentation](https://github.com/crytic/slither/wiki/Python-API) describes the methods and objects available for custom analyses.
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
@ -214,12 +216,14 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
- [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
### External publications
- [ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method), Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen - CTCIS 19
- [MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf), William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh - ISSRE 2019
- [ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf), Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma - SANER 20
- [Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf), Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan - IJMLC 20
- [Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf), Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury - TOSEM 20
- [Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf), Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig - ASE 20
- [Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144), Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu - IEEE Open J. Comput. Soc. 1 (2020)
Title | Usage | Authors | Venue
--- | --- | --- | ---
[ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method) | AST-based analysis built on top of Slither | Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen | CTCIS 19
[MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf) | Leverage data dependency through Slither | William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh | ISSRE 2019
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

@ -1,6 +1,6 @@
# Slither, Plugin Example
This repo contains an example of plugin for Slither.
This repository contains an example of plugin for Slither.
See the [detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector).

@ -18,5 +18,6 @@ logging-fstring-interpolation,
logging-not-lazy,
duplicate-code,
import-error,
unsubscriptable-object
unsubscriptable-object,
consider-using-f-string
"""

@ -1,22 +1,25 @@
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as f:
long_description = f.read()
setup(
name="slither-analyzer",
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.0",
version="0.8.2",
packages=find_packages(),
python_requires=">=3.6",
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
"crytic-compile>=0.2.0",
"crytic-compile>=0.2.2",
# "crytic-compile",
],
# dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0",
long_description=open("README.md").read(),
long_description=long_description,
entry_points={
"console_scripts": [
"slither = slither.__main__:main",

@ -16,6 +16,7 @@ from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser
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
from slither.detectors import all_detectors
@ -23,9 +24,9 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi
from slither.printers import all_printers
from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither
from slither.utils.output import output_to_json, output_to_zip, ZIP_TYPES_ACCEPTED
from slither.utils.output import output_to_json, output_to_zip, output_to_sarif, ZIP_TYPES_ACCEPTED
from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, blue, set_colorization_enabled
from slither.utils.colors import red, set_colorization_enabled
from slither.utils.command_line import (
output_detectors,
output_results_to_markdown,
@ -38,6 +39,7 @@ from slither.utils.command_line import (
read_config_file,
JSON_OUTPUT_TYPES,
DEFAULT_JSON_OUTPUT_TYPES,
check_and_sanitize_markdown_root,
)
from slither.exceptions import SlitherException
@ -171,13 +173,11 @@ def get_detectors_and_printers():
detector = None
if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
raise Exception(
"Error when loading plugin %s, %r is not a detector" % (entry_point, detector)
f"Error when loading plugin {entry_point}, {detector} is not a detector"
)
printer = None
if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
raise Exception(
"Error when loading plugin %s, %r is not a printer" % (entry_point, printer)
)
raise Exception(f"Error when loading plugin {entry_point}, {printer} is not a printer")
# We convert those to lists in case someone returns a tuple
detectors += list(plugin_detectors)
@ -251,7 +251,7 @@ def choose_printers(args, all_printer_classes):
if printer in printers:
printers_to_run.append(printers[printer])
else:
raise Exception("Error: {} is not a printer".format(printer))
raise Exception(f"Error: {printer} is not a printer")
return printers_to_run
@ -270,12 +270,20 @@ def parse_filter_paths(args):
def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-statements
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 += f"\t- NETWORK:0x.. // a contract on a different network. Supported networks: {','.join(x[:-1] for x in SUPPORTED_NETWORK)}\n"
parser = argparse.ArgumentParser(
description="Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage",
usage="slither.py contract.sol [flag]",
description="For usage information, see https://github.com/crytic/slither/wiki/Usage",
usage=usage,
)
parser.add_argument("filename", help="contract.sol")
parser.add_argument("filename", help=argparse.SUPPRESS)
cryticparser.init(parser)
@ -293,7 +301,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_detector.add_argument(
"--detect",
help="Comma-separated list of detectors, defaults to all, "
"available detectors: {}".format(", ".join(d.ARGUMENT for d in detector_classes)),
f"available detectors: {', '.join(d.ARGUMENT for d in detector_classes)}",
action="store",
dest="detectors_to_run",
default=defaults_flag_in_config["detectors_to_run"],
@ -302,7 +310,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_printer.add_argument(
"--print",
help="Comma-separated list fo contract information printers, "
"available printers: {}".format(", ".join(d.ARGUMENT for d in printer_classes)),
f"available printers: {', '.join(d.ARGUMENT for d in printer_classes)}",
action="store",
dest="printers_to_run",
default=defaults_flag_in_config["printers_to_run"],
@ -388,6 +396,13 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["json"],
)
group_misc.add_argument(
"--sarif",
help='Export the results as a SARIF JSON file ("--sarif -" to export to stdout)',
action="store",
default=defaults_flag_in_config["sarif"],
)
group_misc.add_argument(
"--json-types",
help="Comma-separated list of result types to output to JSON, defaults to "
@ -413,6 +428,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_misc.add_argument(
"--markdown-root",
type=check_and_sanitize_markdown_root,
help="URL for markdown generation",
action="store",
default="",
@ -636,15 +652,17 @@ def main_impl(all_detector_classes, all_printer_classes):
output_error = None
outputting_json = args.json is not None
outputting_json_stdout = args.json == "-"
outputting_sarif = args.sarif is not None
outputting_sarif_stdout = args.sarif == "-"
outputting_zip = args.zip is not None
if args.zip_type not in ZIP_TYPES_ACCEPTED.keys():
if args.zip_type not in ZIP_TYPES_ACCEPTED:
to_log = f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}'
logger.error(to_log)
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output.
if outputting_json:
StandardOutputCapture.enable(outputting_json_stdout)
if outputting_json or output_to_sarif:
StandardOutputCapture.enable(outputting_json_stdout or outputting_sarif_stdout)
printer_classes = choose_printers(args, all_printer_classes)
detector_classes = choose_detectors(args, all_detector_classes)
@ -723,7 +741,7 @@ def main_impl(all_detector_classes, all_printer_classes):
) = process_all(filename, args, detector_classes, printer_classes)
# Determine if we are outputting JSON
if outputting_json or outputting_zip:
if outputting_json or outputting_zip or output_to_sarif:
# Add our compilation information to JSON
if "compilations" in args.json_types:
compilation_results = []
@ -768,12 +786,6 @@ def main_impl(all_detector_classes, all_printer_classes):
len(detector_classes),
len(results_detectors),
)
logger.info(
blue(
"Use https://crytic.io/ to get access to additional detectors and Github integration"
)
)
if args.ignore_return_value:
return
@ -800,6 +812,12 @@ def main_impl(all_detector_classes, all_printer_classes):
StandardOutputCapture.disable()
output_to_json(None if outputting_json_stdout else args.json, output_error, json_results)
if outputting_sarif:
StandardOutputCapture.disable()
output_to_sarif(
None if outputting_sarif_stdout else args.sarif, json_results, detector_classes
)
if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type)

@ -13,6 +13,7 @@ from slither.core.declarations import (
SolidityVariableComposed,
Structure,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.variables.variable import Variable
from slither.slithir.operations import Index, OperationWithLValue, InternalCall
from slither.slithir.variables import (
@ -37,7 +38,12 @@ if TYPE_CHECKING:
###################################################################################
def is_dependent(variable, source, context, only_unprotected=False):
def is_dependent(
variable: Variable,
source: Variable,
context: Union[Contract, Function],
only_unprotected: bool = False,
) -> bool:
"""
Args:
variable (Variable)
@ -52,17 +58,22 @@ def is_dependent(variable, source, context, only_unprotected=False):
return False
if variable == source:
return True
context = context.context
context_dict = context.context
if only_unprotected:
return (
variable in context[KEY_NON_SSA_UNPROTECTED]
and source in context[KEY_NON_SSA_UNPROTECTED][variable]
variable in context_dict[KEY_NON_SSA_UNPROTECTED]
and source in context_dict[KEY_NON_SSA_UNPROTECTED][variable]
)
return variable in context[KEY_NON_SSA] and source in context[KEY_NON_SSA][variable]
return variable in context_dict[KEY_NON_SSA] and source in context_dict[KEY_NON_SSA][variable]
def is_dependent_ssa(variable, source, context, only_unprotected=False):
def is_dependent_ssa(
variable: Variable,
source: Variable,
context: Union[Contract, Function],
only_unprotected: bool = False,
) -> bool:
"""
Args:
variable (Variable)
@ -73,17 +84,17 @@ def is_dependent_ssa(variable, source, context, only_unprotected=False):
bool
"""
assert isinstance(context, (Contract, Function))
context = context.context
context_dict = context.context
if isinstance(variable, Constant):
return False
if variable == source:
return True
if only_unprotected:
return (
variable in context[KEY_SSA_UNPROTECTED]
and source in context[KEY_SSA_UNPROTECTED][variable]
variable in context_dict[KEY_SSA_UNPROTECTED]
and source in context_dict[KEY_SSA_UNPROTECTED][variable]
)
return variable in context[KEY_SSA] and source in context[KEY_SSA][variable]
return variable in context_dict[KEY_SSA] and source in context_dict[KEY_SSA][variable]
GENERIC_TAINT = {
@ -273,8 +284,8 @@ def compute_dependency_contract(contract, compilation_unit: "SlitherCompilationU
if KEY_SSA in contract.context:
return
contract.context[KEY_SSA] = dict()
contract.context[KEY_SSA_UNPROTECTED] = dict()
contract.context[KEY_SSA] = {}
contract.context[KEY_SSA_UNPROTECTED] = {}
for function in contract.functions + contract.modifiers:
compute_dependency_function(function)
@ -354,8 +365,8 @@ def compute_dependency_function(function):
if KEY_SSA in function.context:
return
function.context[KEY_SSA] = dict()
function.context[KEY_SSA_UNPROTECTED] = dict()
function.context[KEY_SSA] = {}
function.context[KEY_SSA_UNPROTECTED] = {}
is_protected = function.is_protected()
for node in function.nodes:
@ -398,6 +409,7 @@ def convert_variable_to_non_ssa(v):
Structure,
Function,
Type,
SolidityImportPlaceHolder,
),
)
return v
@ -405,7 +417,7 @@ def convert_variable_to_non_ssa(v):
def convert_to_non_ssa(data_depencies):
# Need to create new set() as its changed during iteration
ret = dict()
ret = {}
for (k, values) in data_depencies.items():
var = convert_variable_to_non_ssa(k)
if not var in ret:

@ -13,7 +13,6 @@ from slither.slithir.operations import (
OperationWithLValue,
SolidityCall,
Length,
Balance,
)
from slither.slithir.variables import ReferenceVariable, TemporaryVariable
@ -65,7 +64,7 @@ def _visit(
continue
if isinstance(ir, (Index, Member)):
refs[ir.lvalue] = ir.variable_left
if isinstance(ir, (Length, Balance)):
if isinstance(ir, Length):
refs[ir.lvalue] = ir.value
if ir.lvalue and not isinstance(ir.lvalue, (TemporaryVariable, ReferenceVariable)):

@ -16,7 +16,6 @@ from slither.core.variables.variable import Variable
from slither.core.solidity_types import ElementaryType
from slither.slithir.convert import convert_expression
from slither.slithir.operations import (
Balance,
HighLevelCall,
Index,
InternalCall,
@ -55,6 +54,7 @@ if TYPE_CHECKING:
LowLevelCallType,
)
from slither.core.cfg.scope import Scope
from slither.core.scope.scope import FileScope
# pylint: disable=too-many-lines,too-many-branches,too-many-instance-attributes
@ -153,7 +153,13 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
def __init__(self, node_type: NodeType, node_id: int, scope: Union["Scope", "Function"]):
def __init__(
self,
node_type: NodeType,
node_id: int,
scope: Union["Scope", "Function"],
file_scope: "FileScope",
):
super().__init__()
self._node_type = node_type
@ -194,6 +200,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._external_calls_as_expressions: List[Expression] = []
self._internal_calls_as_expressions: List[Expression] = []
self._irs: List[Operation] = []
self._all_slithir_operations: Optional[List[Operation]] = None
self._irs_ssa: List[Operation] = []
self._state_vars_written: List[StateVariable] = []
@ -221,7 +228,8 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._asm_source_code: Optional[Union[str, Dict]] = None
self.scope = scope
self.scope: Union["Scope", "Function"] = scope
self.file_scope: "FileScope" = file_scope
###################################################################################
###################################################################################
@ -714,11 +722,13 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._find_read_write_call()
def all_slithir_operations(self) -> List[Operation]:
irs = self.irs
for ir in irs:
if isinstance(ir, InternalCall):
irs += ir.function.all_slithir_operations()
return irs
if self._all_slithir_operations is None:
irs = list(self.irs)
for ir in self.irs:
if isinstance(ir, InternalCall):
irs += ir.function.all_slithir_operations()
self._all_slithir_operations = irs
return self._all_slithir_operations
@staticmethod
def _is_non_slithir_var(var: Variable):
@ -875,7 +885,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._vars_read.append(origin)
if isinstance(ir, OperationWithLValue):
if isinstance(ir, (Index, Member, Length, Balance)):
if isinstance(ir, (Index, Member, Length)):
continue # Don't consider Member and Index operations -> ReferenceVariable
var = ir.lvalue
if isinstance(var, ReferenceVariable):
@ -903,7 +913,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
except AttributeError as error:
# pylint: disable=raise-missing-from
raise SlitherException(
f"Function not found on {ir}. Please try compiling with a recent Solidity version. {error}"
f"Function not found on IR: {ir}.\nNode: {self} ({self.source_mapping_str})\nFunction: {self.function}\nPlease try compiling with a recent Solidity version. {error}"
)
elif isinstance(ir, LibraryCall):
assert isinstance(ir.destination, Contract)
@ -959,7 +969,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._ssa_vars_read.append(origin)
if isinstance(ir, OperationWithLValue):
if isinstance(ir, (Index, Member, Length, Balance)):
if isinstance(ir, (Index, Member, Length)):
continue # Don't consider Member and Index operations -> ReferenceVariable
var = ir.lvalue
if isinstance(var, ReferenceVariable):

@ -1,9 +1,9 @@
import math
from collections import defaultdict
from typing import Optional, Dict, List, Set, Union, TYPE_CHECKING, Tuple
from crytic_compile import CompilationUnit, CryticCompile
from crytic_compile.compiler.compiler import CompilerVersion
from crytic_compile.utils.naming import Filename
from slither.core.context.context import Context
from slither.core.declarations import (
@ -13,9 +13,11 @@ from slither.core.declarations import (
Function,
Modifier,
)
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.scope.scope import FileScope
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.operations import InternalCall
@ -24,6 +26,7 @@ from slither.slithir.variables import Constant
if TYPE_CHECKING:
from slither.core.slither_core import SlitherCore
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherCompilationUnit(Context):
def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit):
@ -33,13 +36,14 @@ class SlitherCompilationUnit(Context):
self._crytic_compile_compilation_unit = crytic_compilation_unit
# Top level object
self._contracts: Dict[str, Contract] = {}
self.contracts: List[Contract] = []
self._structures_top_level: List[StructureTopLevel] = []
self._enums_top_level: List[EnumTopLevel] = []
self._variables_top_level: List[TopLevelVariable] = []
self._functions_top_level: List[FunctionTopLevel] = []
self._pragma_directives: List[Pragma] = []
self._import_directives: List[Import] = []
self._custom_errors: List[CustomError] = []
self._all_functions: Set[Function] = set()
self._all_modifiers: Set[Modifier] = set()
@ -49,7 +53,6 @@ class SlitherCompilationUnit(Context):
self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
self._contract_name_collisions = defaultdict(list)
self._contract_with_missing_inheritance = set()
self._source_units: Dict[int, str] = {}
@ -58,6 +61,8 @@ class SlitherCompilationUnit(Context):
self.counter_slithir_temporary = 0
self.counter_slithir_reference = 0
self.scopes: Dict[Filename, FileScope] = {}
@property
def core(self) -> "SlitherCore":
return self._core
@ -98,12 +103,12 @@ class SlitherCompilationUnit(Context):
@property
def pragma_directives(self) -> List[Pragma]:
""" list(core.declarations.Pragma): Pragma directives."""
"""list(core.declarations.Pragma): Pragma directives."""
return self._pragma_directives
@property
def import_directives(self) -> List[Import]:
""" list(core.declarations.Import): Import directives"""
"""list(core.declarations.Import): Import directives"""
return self._import_directives
# endregion
@ -113,32 +118,22 @@ class SlitherCompilationUnit(Context):
###################################################################################
###################################################################################
@property
def contracts(self) -> List[Contract]:
"""list(Contract): List of contracts."""
return list(self._contracts.values())
@property
def contracts_derived(self) -> List[Contract]:
"""list(Contract): List of contracts that are derived and not inherited."""
inheritances = [x.inheritance for x in self.contracts]
inheritance = [item for sublist in inheritances for item in sublist]
return [c for c in self._contracts.values() if c not in inheritance and not c.is_top_level]
@property
def contracts_as_dict(self) -> Dict[str, Contract]:
"""list(dict(str: Contract): List of contracts as dict: name -> Contract."""
return self._contracts
return [c for c in self.contracts if c not in inheritance and not c.is_top_level]
def get_contract_from_name(self, contract_name: Union[str, Constant]) -> Optional[Contract]:
def get_contract_from_name(self, contract_name: Union[str, Constant]) -> List[Contract]:
"""
Return a contract from a name
Return a list of contract from a name
Args:
contract_name (str): name of the contract
Returns:
Contract
List[Contract]
"""
return next((c for c in self.contracts if c.name == contract_name), None)
return [c for c in self.contracts if c.name == contract_name]
# endregion
###################################################################################
@ -210,6 +205,10 @@ class SlitherCompilationUnit(Context):
def functions_top_level(self) -> List[FunctionTopLevel]:
return self._functions_top_level
@property
def custom_errors(self) -> List[CustomError]:
return self._custom_errors
# endregion
###################################################################################
###################################################################################
@ -217,14 +216,27 @@ class SlitherCompilationUnit(Context):
###################################################################################
###################################################################################
@property
def contract_name_collisions(self) -> Dict:
return self._contract_name_collisions
@property
def contracts_with_missing_inheritance(self) -> Set:
return self._contract_with_missing_inheritance
# endregion
###################################################################################
###################################################################################
# region Scope
###################################################################################
###################################################################################
def get_scope(self, filename_str: str) -> FileScope:
filename = self._crytic_compile_compilation_unit.crytic_compile.filename_lookup(
filename_str
)
if filename not in self.scopes:
self.scopes[filename] = FileScope(filename)
return self.scopes[filename]
# endregion
###################################################################################
###################################################################################

@ -3,9 +3,9 @@ from typing import Dict
class Context: # pylint: disable=too-few-public-methods
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._context = {"MEMBERS": defaultdict(None)}
self._context: Dict = {"MEMBERS": defaultdict(None)}
@property
def context(self) -> Dict:

@ -11,7 +11,7 @@ from slither.core.cfg.scope import Scope
from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.declarations.function import Function, FunctionType
from slither.core.declarations.function import Function, FunctionType, FunctionLanguage
from slither.utils.erc import (
ERC20_signatures,
ERC165_signatures,
@ -19,6 +19,7 @@ from slither.utils.erc import (
ERC721_signatures,
ERC1820_signatures,
ERC777_signatures,
ERC1155_signatures,
)
from slither.utils.tests_pattern import is_test_contract
@ -37,6 +38,8 @@ if TYPE_CHECKING:
from slither.core.variables.variable import Variable
from slither.core.variables.state_variable import StateVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.scope.scope import FileScope
LOGGER = logging.getLogger("Contract")
@ -47,7 +50,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Contract class
"""
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
super().__init__()
self._name: Optional[str] = None
@ -66,7 +69,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._variables_ordered: List["StateVariable"] = []
self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "FunctionContract"] = {}
self._linearizedBaseContracts = List[int]
self._linearizedBaseContracts: List[int] = []
self._custom_errors: Dict[str, "CustomErrorContract"] = {}
# The only str is "*"
self._using_for: Dict[Union[str, Type], List[str]] = {}
@ -89,6 +93,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._all_functions_called: Optional[List["InternalCallType"]] = None
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
self.file_scope: "FileScope" = scope
###################################################################################
###################################################################################
@ -241,6 +246,38 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def using_for(self) -> Dict[Union[str, Type], List[str]]:
return self._using_for
# endregion
###################################################################################
###################################################################################
# region Custom Errors
###################################################################################
###################################################################################
@property
def custom_errors(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the contract's custom errors
"""
return list(self._custom_errors.values())
@property
def custom_errors_inherited(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the inherited custom errors
"""
return [s for s in self.custom_errors if s.contract != self]
@property
def custom_errors_declared(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the custom errors declared within the contract (not inherited)
"""
return [s for s in self.custom_errors if s.contract == self]
@property
def custom_errors_as_dict(self) -> Dict[str, "CustomErrorContract"]:
return self._custom_errors
# endregion
###################################################################################
###################################################################################
@ -505,7 +542,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def available_elements_from_inheritances(
self,
elements: Dict[str, "Function"],
getter_available: Callable[["Contract"], List["Function"]],
getter_available: Callable[["Contract"], List["FunctionContract"]],
) -> Dict[str, "Function"]:
"""
@ -516,14 +553,16 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
# keep track of the contracts visited
# to prevent an ovveride due to multiple inheritance of the same contract
# A is B, C, D is C, --> the second C was already seen
inherited_elements: Dict[str, "Function"] = {}
inherited_elements: Dict[str, "FunctionContract"] = {}
accessible_elements = {}
contracts_visited = []
for father in self.inheritance_reverse:
functions: Dict[str, "Function"] = {
functions: Dict[str, "FunctionContract"] = {
v.full_name: v
for v in getter_available(father)
if v.contract not in contracts_visited
and v.function_language
!= FunctionLanguage.Yul # Yul functions are not propagated in the inheritance
}
contracts_visited.append(father)
inherited_elements.update(functions)
@ -925,6 +964,16 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
full_names = self.functions_signatures
return all(s in full_names for s in ERC777_signatures)
def is_erc1155(self) -> bool:
"""
Check if the contract is an erc1155
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc1155
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC1155_signatures)
@property
def is_token(self) -> bool:
"""
@ -937,6 +986,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
or self.is_erc165()
or self.is_erc223()
or self.is_erc777()
or self.is_erc1155()
)
def is_possible_erc20(self) -> bool:
@ -1038,14 +1088,16 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._is_upgradeable = False
if self.is_upgradeable_proxy:
return False
initializable = self.compilation_unit.get_contract_from_name("Initializable")
initializable = self.file_scope.get_contract_from_name("Initializable")
if initializable:
if initializable in self.inheritance:
self._is_upgradeable = True
else:
for c in self.inheritance + [self]:
for contract in self.inheritance + [self]:
# This might lead to false positive
lower_name = c.name.lower()
# Not sure why pylint is having a trouble here
# pylint: disable=no-member
lower_name = contract.name.lower()
if "upgradeable" in lower_name or "upgradable" in lower_name:
self._is_upgradeable = True
break
@ -1176,7 +1228,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
)
# Function uses to create node for state variable declaration statements
node = Node(NodeType.OTHER_ENTRYPOINT, counter, scope)
node = Node(NodeType.OTHER_ENTRYPOINT, counter, scope, func.file_scope)
node.set_offset(variable.source_mapping, self.compilation_unit)
node.set_function(func)
func.add_node(node)
@ -1207,7 +1259,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
from slither.slithir.variables import StateIRVariable
all_ssa_state_variables_instances = dict()
all_ssa_state_variables_instances = {}
for contract in self.inheritance:
for v in contract.state_variables_declared:
@ -1225,8 +1277,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
func.generate_slithir_ssa(all_ssa_state_variables_instances)
def fix_phi(self):
last_state_variables_instances = dict()
initial_state_variables_instances = dict()
last_state_variables_instances = {}
initial_state_variables_instances = {}
for v in self._initial_state_variables:
last_state_variables_instances[v.canonical_name] = []
initial_state_variables_instances[v.canonical_name] = v

@ -0,0 +1,71 @@
from typing import List, TYPE_CHECKING, Optional, Type, Union
from slither.core.solidity_types import UserDefinedType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
class CustomError(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__()
self._name: str = ""
self._parameters: List[LocalVariable] = []
self._compilation_unit = compilation_unit
self._solidity_signature: Optional[str] = None
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, new_name: str) -> None:
self._name = new_name
@property
def parameters(self) -> List[LocalVariable]:
return self._parameters
def add_parameters(self, p: "LocalVariable"):
self._parameters.append(p)
@property
def compilation_unit(self) -> "SlitherCompilationUnit":
return self._compilation_unit
# region Signature
###################################################################################
###################################################################################
@staticmethod
def _convert_type_for_solidity_signature(t: Optional[Union[Type, List[Type]]]):
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
return "address"
return str(t)
@property
def solidity_signature(self) -> str:
"""
Return a signature following the Solidity Standard
Contract and converted into address
:return: the solidity signature
"""
if self._solidity_signature is None:
parameters = [
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature
# endregion
###################################################################################
###################################################################################
def __str__(self):
return "revert " + self.solidity_signature

@ -0,0 +1,12 @@
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.custom_error import CustomError
class CustomErrorContract(CustomError, ChildContract):
def is_declared_by(self, contract):
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract

@ -0,0 +1,14 @@
from typing import TYPE_CHECKING
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
class CustomErrorTopLevel(CustomError, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
super().__init__(compilation_unit)
self.file_scope: "FileScope" = scope

@ -1,6 +1,13 @@
from typing import TYPE_CHECKING, List
from slither.core.declarations import Enum
from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class EnumTopLevel(Enum, TopLevel):
pass
def __init__(self, name: str, canonical_name: str, values: List[str], scope: "FileScope"):
super().__init__(name, canonical_name, values)
self.file_scope: "FileScope" = scope

@ -44,6 +44,7 @@ if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.slithir.operations import Operation
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
@ -104,14 +105,20 @@ def _filter_state_variables_written(expressions: List["Expression"]):
return ret
class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
class FunctionLanguage(Enum):
Solidity = 0
Yul = 1
Vyper = 2
class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-public-methods
"""
Function class
"""
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__()
self._scope: List[str] = []
self._internal_scope: List[str] = []
self._name: Optional[str] = None
self._view: bool = False
self._pure: bool = False
@ -207,6 +214,11 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
# Assume we are analyzing Solidty by default
self.function_language: FunctionLanguage = FunctionLanguage.Solidity
self._id: Optional[str] = None
###################################################################################
###################################################################################
# region General properties
@ -235,18 +247,18 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
self._name = new_name
@property
def scope(self) -> List[str]:
def internal_scope(self) -> List[str]:
"""
Return a list of name representing the scope of the function
This is used to model nested functions declared in YUL
:return:
"""
return self._scope
return self._internal_scope
@scope.setter
def scope(self, new_scope: List[str]):
self._scope = new_scope
@internal_scope.setter
def internal_scope(self, new_scope: List[str]):
self._internal_scope = new_scope
@property
def full_name(self) -> str:
@ -256,7 +268,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
"""
if self._full_name is None:
name, parameters, _ = self.signature
full_name = ".".join(self._scope + [name]) + "(" + ",".join(parameters) + ")"
full_name = ".".join(self._internal_scope + [name]) + "(" + ",".join(parameters) + ")"
self._full_name = full_name
return self._full_name
@ -325,6 +337,26 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
return self.compilation_unit.solc_version >= "0.8.0"
@property
def id(self) -> Optional[str]:
"""
Return the ID of the funciton. For Solidity with compact-AST the ID is the reference ID
For other, the ID is None
:return:
:rtype:
"""
return self._id
@id.setter
def id(self, new_id: str):
self._id = new_id
@property
@abstractmethod
def file_scope(self) -> "FileScope":
pass
# endregion
###################################################################################
###################################################################################
@ -850,7 +882,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
from slither.slithir.variables import Constant
if self._return_values is None:
return_values = list()
return_values = []
returns = [n for n in self.nodes if n.type == NodeType.RETURN]
[ # pylint: disable=expression-not-assigned
return_values.extend(ir.values)
@ -871,7 +903,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
from slither.slithir.variables import Constant
if self._return_values_ssa is None:
return_values_ssa = list()
return_values_ssa = []
returns = [n for n in self.nodes if n.type == NodeType.RETURN]
[ # pylint: disable=expression-not-assigned
return_values_ssa.extend(ir.values)
@ -1405,7 +1437,11 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
"""
Determine if the function is protected using a check on msg.sender
Only detects if msg.sender is directly used in a condition
Consider onlyOwner as a safe modifier.
If the owner functionality is incorrectly implemented, this will lead to incorrectly
classify the function as protected
Otherwise only detects if msg.sender is directly used in a condition
For example, it wont work for:
address a = msg.sender
require(a == owner)
@ -1417,6 +1453,9 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
if self.is_constructor:
self._is_protected = True
return True
if "onlyOwner" in [m.name for m in self.modifiers]:
self._is_protected = True
return True
conditional_vars = self.all_conditional_solidity_variables_read(include_loop=False)
args_vars = self.all_solidity_variables_used_as_args()
self._is_protected = (
@ -1536,7 +1575,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
) -> "Node":
from slither.core.cfg.node import Node
node = Node(node_type, self._counter_nodes, scope)
node = Node(node_type, self._counter_nodes, scope, self.file_scope)
node.set_offset(src, self.compilation_unit)
self._counter_nodes += 1
node.set_function(self)
@ -1560,16 +1599,16 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
from slither.core.cfg.node import NodeType
if not self.is_implemented:
return dict()
return {}
if self._entry_point is None:
return dict()
return {}
# node, values
to_explore: List[Tuple["Node", Dict]] = [(self._entry_point, dict())]
to_explore: List[Tuple["Node", Dict]] = [(self._entry_point, {})]
# node -> values
explored: Dict = dict()
explored: Dict = {}
# name -> instances
ret: Dict = dict()
ret: Dict = {}
while to_explore:
node, values = to_explore[0]

@ -5,16 +5,16 @@ from typing import TYPE_CHECKING, List, Tuple
from slither.core.children.child_contract import ChildContract
from slither.core.children.child_inheritance import ChildInheritance
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.declarations import Function
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
if TYPE_CHECKING:
from slither.core.declarations import Contract
from slither.core.scope.scope import FileScope
class FunctionContract(Function, ChildContract, ChildInheritance, SourceMapping):
class FunctionContract(Function, ChildContract, ChildInheritance):
@property
def canonical_name(self) -> str:
"""
@ -24,7 +24,7 @@ class FunctionContract(Function, ChildContract, ChildInheritance, SourceMapping)
if self._canonical_name is None:
name, parameters, _ = self.signature
self._canonical_name = (
".".join([self.contract_declarer.name] + self._scope + [name])
".".join([self.contract_declarer.name] + self._internal_scope + [name])
+ "("
+ ",".join(parameters)
+ ")"
@ -39,6 +39,10 @@ class FunctionContract(Function, ChildContract, ChildInheritance, SourceMapping)
"""
return self.contract_declarer == contract
@property
def file_scope(self) -> "FileScope":
return self.contract.file_scope
# endregion
###################################################################################
###################################################################################

@ -1,14 +1,25 @@
"""
Function module
"""
from typing import List, Tuple
from typing import List, Tuple, TYPE_CHECKING
from slither.core.declarations import Function
from slither.core.declarations.top_level import TopLevel
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
class FunctionTopLevel(Function, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
super().__init__(compilation_unit)
self._scope: "FileScope" = scope
@property
def file_scope(self) -> "FileScope":
return self._scope
class FunctionTopLevel(Function, TopLevel, SourceMapping):
@property
def canonical_name(self) -> str:
"""
@ -17,7 +28,9 @@ class FunctionTopLevel(Function, TopLevel, SourceMapping):
"""
if self._canonical_name is None:
name, parameters, _ = self.signature
self._canonical_name = ".".join(self._scope + [name]) + "(" + ",".join(parameters) + ")"
self._canonical_name = (
".".join(self._internal_scope + [name]) + "(" + ",".join(parameters) + ")"
)
return self._canonical_name
# endregion

@ -1,16 +1,38 @@
from typing import Optional
from pathlib import Path
from typing import Optional, TYPE_CHECKING
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class Import(SourceMapping):
def __init__(self, filename: str):
def __init__(self, filename: Path, scope: "FileScope"):
super().__init__()
self._filename = filename
self._filename: Path = filename
self._alias: Optional[str] = None
self.scope: "FileScope" = scope
@property
def filename(self) -> str:
"""
Return the absolute filename
:return:
:rtype:
"""
return str(self._filename)
@property
def filename_path(self) -> Path:
"""
Return the absolute filename
:return:
:rtype:
"""
return self._filename
@property

@ -1,12 +1,16 @@
from typing import List
from typing import List, TYPE_CHECKING
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class Pragma(SourceMapping):
def __init__(self, directive: List[str]):
def __init__(self, directive: List[str], scope: "FileScope"):
super().__init__()
self._directive = directive
self.scope: "FileScope" = scope
@property
def directive(self) -> List[str]:

@ -0,0 +1,41 @@
"""
Special variable to model import with renaming
"""
from slither.core.declarations import Import
from slither.core.solidity_types import ElementaryType
from slither.core.variables.variable import Variable
class SolidityImportPlaceHolder(Variable):
"""
Placeholder for import on top level objects
See the example at https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
In the long term we should remove this and better integrate import aliases
"""
def __init__(self, import_directive: Import):
super().__init__()
assert import_directive.alias is not None
self._import_directive = import_directive
self._name = import_directive.alias
self._type = ElementaryType("string")
self._initialized = True
self._visibility = "private"
self._is_constant = True
@property
def type(self) -> ElementaryType:
return ElementaryType("string")
def __eq__(self, other):
return (
self.__class__ == other.__class__
and self._import_directive.filename == self._import_directive.filename
)
@property
def import_directive(self) -> Import:
return self._import_directive
def __hash__(self):
return hash(str(self.import_directive))

@ -2,11 +2,12 @@
from typing import List, Dict, Union, TYPE_CHECKING
from slither.core.context.context import Context
from slither.core.declarations.custom_error import CustomError
from slither.core.solidity_types import ElementaryType, TypeInformation
from slither.exceptions import SlitherException
if TYPE_CHECKING:
from slither.core.declarations import Import
pass
SOLIDITY_VARIABLES = {
"now": "uint256",
@ -19,6 +20,7 @@ SOLIDITY_VARIABLES = {
}
SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint",
"block.coinbase": "address",
"block.difficulty": "uint256",
"block.gaslimit": "uint256",
@ -42,6 +44,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"require(bool,string)": [],
"revert()": [],
"revert(string)": [],
"revert ": [],
"addmod(uint256,uint256,uint256)": ["uint256"],
"mulmod(uint256,uint256,uint256)": ["uint256"],
"keccak256()": ["bytes32"],
@ -67,10 +70,15 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"abi.encodePacked()": ["bytes"],
"abi.encodeWithSelector()": ["bytes"],
"abi.encodeWithSignature()": ["bytes"],
"bytes.concat()": ["bytes"],
# abi.decode returns an a list arbitrary types
"abi.decode()": [],
"type(address)": [],
"type()": [], # 0.6.8 changed type(address) to type()
# The following are conversion from address.something
"balance(address)": ["uint256"],
"code(address)": ["bytes"],
"codehash(address)": ["bytes32"],
}
@ -186,35 +194,18 @@ class SolidityFunction:
return hash(self.name)
class SolidityImportPlaceHolder(SolidityVariable):
"""
Placeholder for import on top level objects
See the example at https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
In the long term we should remove this and better integrate import aliases
"""
def __init__(self, import_directive: "Import"):
assert import_directive.alias is not None
super().__init__(import_directive.alias)
self._import_directive = import_directive
def _check_name(self, name: str):
return True
@property
def type(self) -> ElementaryType:
return ElementaryType("string")
class SolidityCustomRevert(SolidityFunction):
def __init__(self, custom_error: CustomError): # pylint: disable=super-init-not-called
self._name = "revert " + custom_error.solidity_signature
self._custom_error = custom_error
self._return_type: List[Union[TypeInformation, ElementaryType]] = []
def __eq__(self, other):
return (
self.__class__ == other.__class__
and self.name == other.name
and self._import_directive.filename == self._import_directive.filename
and self._custom_error == other._custom_error
)
@property
def import_directive(self) -> "Import":
return self._import_directive
def __hash__(self):
return hash(str(self.import_directive))
return hash(hash(self.name) + hash(self._custom_error))

@ -4,16 +4,18 @@ from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.variables.structure_variable import StructureVariable
from slither.core.compilation_unit import SlitherCompilationUnit
class Structure(SourceMapping):
def __init__(self):
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__()
self._name = None
self._canonical_name = None
self._elems: Dict[str, "StructureVariable"] = dict()
self._elems: Dict[str, "StructureVariable"] = {}
# Name of the elements in the order of declaration
self._elems_ordered: List[str] = []
self.compilation_unit = compilation_unit
@property
def canonical_name(self) -> str:

@ -1,6 +1,14 @@
from typing import TYPE_CHECKING
from slither.core.declarations import Structure
from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
from slither.core.compilation_unit import SlitherCompilationUnit
class StructureTopLevel(Structure, TopLevel):
pass
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
super().__init__(compilation_unit)
self.file_scope: "FileScope" = scope

@ -26,7 +26,7 @@ class AssignmentOperationType(Enum):
ASSIGN_MODULO = 10 # %=
@staticmethod
def get_type(operation_type: "AssignmentOperationType"):
def get_type(operation_type: str) -> "AssignmentOperationType":
if operation_type == "=":
return AssignmentOperationType.ASSIGN
if operation_type == "|=":
@ -52,7 +52,7 @@ class AssignmentOperationType(Enum):
raise SlitherCoreError("get_type: Unknown operation type {})".format(operation_type))
def __str__(self):
def __str__(self) -> str:
if self == AssignmentOperationType.ASSIGN:
return "="
if self == AssignmentOperationType.ASSIGN_OR:
@ -91,7 +91,7 @@ class AssignmentOperation(ExpressionTyped):
super().__init__()
left_expression.set_lvalue()
self._expressions = [left_expression, right_expression]
self._type: Optional["Type"] = expression_type
self._type: Optional["AssignmentOperationType"] = expression_type
self._expression_return_type: Optional["Type"] = expression_return_type
@property
@ -111,8 +111,8 @@ class AssignmentOperation(ExpressionTyped):
return self._expressions[1]
@property
def type(self) -> Optional["Type"]:
def type(self) -> Optional["AssignmentOperationType"]:
return self._type
def __str__(self):
def __str__(self) -> str:
return str(self.expression_left) + " " + str(self.type) + " " + str(self.expression_right)

@ -10,5 +10,5 @@ class Expression(SourceMapping):
def is_lvalue(self) -> bool:
return self._is_lvalue
def set_lvalue(self):
def set_lvalue(self) -> None:
self._is_lvalue = True

@ -0,0 +1,100 @@
from typing import List, Any, Dict, Optional, Union, Set
from crytic_compile.utils.naming import Filename
from slither.core.declarations import Contract, Import, Pragma
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.slithir.variables import Constant
def _dict_contain(d1: Dict, d2: Dict) -> bool:
"""
Return true if d1 is included in d2
"""
d2_keys = d2.keys()
return all(item in d2_keys for item in d1.keys())
# pylint: disable=too-many-instance-attributes
class FileScope:
def __init__(self, filename: Filename):
self.filename = filename
self.accessible_scopes: List[FileScope] = []
self.contracts: Dict[str, Contract] = {}
# Custom error are a list instead of a dict
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {}
# Functions is a list instead of a dict
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
self.functions: Set[FunctionTopLevel] = set()
self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set()
self.structures: Dict[str, StructureTopLevel] = {}
def add_accesible_scopes(self) -> bool:
"""
Add information from accessible scopes. Return true if new information was obtained
:return:
:rtype:
"""
learn_something = False
for new_scope in self.accessible_scopes:
if not _dict_contain(new_scope.contracts, self.contracts):
self.contracts.update(new_scope.contracts)
learn_something = True
if not new_scope.custom_errors.issubset(self.custom_errors):
self.custom_errors |= new_scope.custom_errors
learn_something = True
if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums)
learn_something = True
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
if not new_scope.imports.issubset(self.imports):
self.imports |= new_scope.imports
learn_something = True
if not new_scope.pragmas.issubset(self.pragmas):
self.pragmas |= new_scope.pragmas
learn_something = True
if not _dict_contain(new_scope.structures, self.structures):
self.structures.update(new_scope.structures)
learn_something = True
return learn_something
def get_contract_from_name(self, name: Union[str, Constant]) -> Optional[Contract]:
if isinstance(name, Constant):
return self.contracts.get(name.name, None)
return self.contracts.get(name, None)
# region Built in definitions
###################################################################################
###################################################################################
def __eq__(self, other: Any) -> bool:
if isinstance(other, str):
return other == self.filename
return NotImplemented
def __neq__(self, other: Any) -> bool:
if isinstance(other, str):
return other != self.filename
return NotImplemented
def __str__(self) -> str:
return str(self.filename.relative)
def __hash__(self) -> int:
return hash(self.filename.relative)
# endregion

@ -110,9 +110,7 @@ class SlitherCore(Context):
"""
contracts = []
for compilation_unit in self._compilation_units:
contract = compilation_unit.get_contract_from_name(contract_name)
if contract:
contracts.append(contract)
contracts += compilation_unit.get_contract_from_name(contract_name)
return contracts
###################################################################################
@ -123,7 +121,7 @@ class SlitherCore(Context):
@property
def source_code(self) -> Dict[str, str]:
""" {filename: source_code (str)}: source code """
"""{filename: source_code (str)}: source code"""
return self._raw_source_code
@property
@ -256,7 +254,7 @@ class SlitherCore(Context):
filename = self._previous_results_filename
try:
if os.path.isfile(filename):
with open(filename) as f:
with open(filename, encoding="utf8") as f:
self._previous_results = json.load(f)
if self._previous_results:
for r in self._previous_results:

@ -46,7 +46,7 @@ class ArrayType(Type):
def __str__(self):
if self._length:
return str(self._type) + "[{}]".format(str(self._length_value))
return str(self._type) + f"[{str(self._length_value)}]"
return str(self._type) + "[]"
def __eq__(self, other):

@ -194,7 +194,7 @@ class ElementaryType(Type):
@property
def storage_size(self) -> Tuple[int, bool]:
if self._type == "string" or self._type == "bytes":
if self._type in ["string", "bytes"]:
return 32, True
if self.size is None:
return 32, True

@ -8,7 +8,7 @@ if TYPE_CHECKING:
class SourceMapping(Context):
def __init__(self):
def __init__(self) -> None:
super().__init__()
# TODO create a namedtuple for the source mapping rather than a dict
self._source_mapping: Optional[Dict] = None

@ -20,6 +20,7 @@ class Variable(SourceMapping):
self._initialized: Optional[bool] = None
self._visibility: Optional[str] = None
self._is_constant = False
self._is_immutable: bool = False
@property
def is_scalar(self) -> bool:
@ -106,6 +107,19 @@ class Variable(SourceMapping):
assert isinstance(t, (Type, list)) or t is None
self._type = t
@property
def is_immutable(self) -> bool:
"""
Return true of the variable is immutable
:return:
"""
return self._is_immutable
@is_immutable.setter
def is_immutable(self, immutablility: bool):
self._is_immutable = immutablility
@property
def function_name(self):
"""

@ -1,6 +1,7 @@
import abc
import re
from typing import Optional, List, TYPE_CHECKING
from logging import Logger
from typing import Optional, List, TYPE_CHECKING, Dict, Union, Callable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract
@ -8,7 +9,7 @@ from slither.utils.colors import green, yellow, red
from slither.formatters.exceptions import FormatImpossible
from slither.formatters.utils.patches import apply_patch, create_diff
from slither.utils.comparable_enum import ComparableEnum
from slither.utils.output import Output
from slither.utils.output import Output, SupportedOutput
if TYPE_CHECKING:
from slither import Slither
@ -25,8 +26,10 @@ class DetectorClassification(ComparableEnum):
INFORMATIONAL = 3
OPTIMIZATION = 4
UNIMPLEMENTED = 999
classification_colors = {
classification_colors: Dict[DetectorClassification, Callable[[str], str]] = {
DetectorClassification.INFORMATIONAL: green,
DetectorClassification.OPTIMIZATION: green,
DetectorClassification.LOW: green,
@ -46,8 +49,8 @@ classification_txt = {
class AbstractDetector(metaclass=abc.ABCMeta):
ARGUMENT = "" # run the detector with slither.py --ARGUMENT
HELP = "" # help information
IMPACT: Optional[DetectorClassification] = None
CONFIDENCE: Optional[DetectorClassification] = None
IMPACT: DetectorClassification = DetectorClassification.UNIMPLEMENTED
CONFIDENCE: DetectorClassification = DetectorClassification.UNIMPLEMENTED
WIKI = ""
@ -58,7 +61,9 @@ class AbstractDetector(metaclass=abc.ABCMeta):
STANDARD_JSON = True
def __init__(self, compilation_unit: SlitherCompilationUnit, slither, logger):
def __init__(
self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger
):
self.compilation_unit: SlitherCompilationUnit = compilation_unit
self.contracts: List[Contract] = compilation_unit.contracts
self.slither: "Slither" = slither
@ -67,27 +72,27 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if not self.HELP:
raise IncorrectDetectorInitialization(
"HELP is not initialized {}".format(self.__class__.__name__)
f"HELP is not initialized {self.__class__.__name__}"
)
if not self.ARGUMENT:
raise IncorrectDetectorInitialization(
"ARGUMENT is not initialized {}".format(self.__class__.__name__)
f"ARGUMENT is not initialized {self.__class__.__name__}"
)
if not self.WIKI:
raise IncorrectDetectorInitialization(
"WIKI is not initialized {}".format(self.__class__.__name__)
f"WIKI is not initialized {self.__class__.__name__}"
)
if not self.WIKI_TITLE:
raise IncorrectDetectorInitialization(
"WIKI_TITLE is not initialized {}".format(self.__class__.__name__)
f"WIKI_TITLE is not initialized {self.__class__.__name__}"
)
if not self.WIKI_DESCRIPTION:
raise IncorrectDetectorInitialization(
"WIKI_DESCRIPTION is not initialized {}".format(self.__class__.__name__)
f"WIKI_DESCRIPTION is not initialized {self.__class__.__name__}"
)
if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [
@ -95,17 +100,17 @@ class AbstractDetector(metaclass=abc.ABCMeta):
DetectorClassification.OPTIMIZATION,
]:
raise IncorrectDetectorInitialization(
"WIKI_EXPLOIT_SCENARIO is not initialized {}".format(self.__class__.__name__)
f"WIKI_EXPLOIT_SCENARIO is not initialized {self.__class__.__name__}"
)
if not self.WIKI_RECOMMENDATION:
raise IncorrectDetectorInitialization(
"WIKI_RECOMMENDATION is not initialized {}".format(self.__class__.__name__)
f"WIKI_RECOMMENDATION is not initialized {self.__class__.__name__}"
)
if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
raise IncorrectDetectorInitialization(
"ARGUMENT has illegal character {}".format(self.__class__.__name__)
f"ARGUMENT has illegal character {self.__class__.__name__}"
)
if self.IMPACT not in [
@ -116,7 +121,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
DetectorClassification.OPTIMIZATION,
]:
raise IncorrectDetectorInitialization(
"IMPACT is not initialized {}".format(self.__class__.__name__)
f"IMPACT is not initialized {self.__class__.__name__}"
)
if self.CONFIDENCE not in [
@ -127,42 +132,35 @@ class AbstractDetector(metaclass=abc.ABCMeta):
DetectorClassification.OPTIMIZATION,
]:
raise IncorrectDetectorInitialization(
"CONFIDENCE is not initialized {}".format(self.__class__.__name__)
f"CONFIDENCE is not initialized {self.__class__.__name__}"
)
def _log(self, info):
def _log(self, info: str) -> None:
if self.logger:
self.logger.info(self.color(info))
@abc.abstractmethod
def _detect(self):
def _detect(self) -> List[Output]:
"""TODO Documentation"""
return []
# pylint: disable=too-many-branches
def detect(self):
results = []
def detect(self) -> List[Dict]:
results: List[Dict] = []
# only keep valid result, and remove dupplicate
# Keep only dictionaries
for r in [r.data for r in self._detect()]:
for r in [output.data for output in self._detect()]:
if self.compilation_unit.core.valid_result(r) and r not in results:
results.append(r)
if results:
if self.logger:
info = "\n"
for idx, result in enumerate(results):
if self.slither.triage_mode:
info += "{}: ".format(idx)
info += result["description"]
info += "Reference: {}".format(self.WIKI)
self._log(info)
if results and self.logger:
self._log_result(results)
if self.compilation_unit.core.generate_patches:
for result in results:
try:
self._format(self.compilation_unit, result)
if not "patches" in result:
continue
result["patches_diff"] = dict()
result["patches_diff"] = {}
for file in result["patches"]:
original_txt = self.compilation_unit.core.source_code[file].encode("utf8")
patched_txt = original_txt
@ -191,9 +189,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if results and self.slither.triage_mode:
while True:
indexes = input(
'Results to hide during next runs: "0,1,...,{}" or "All" (enter to not hide results): '.format(
len(results)
)
f'Results to hide during next runs: "0,1,...,{len(results)}" or "All" (enter to not hide results): '
)
if indexes == "All":
self.slither.save_results_to_hide(results)
@ -205,20 +201,24 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if indexes.endswith("]"):
indexes = indexes[:-1]
try:
indexes = [int(i) for i in indexes.split(",")]
indexes_converted = [int(i) for i in indexes.split(",")]
self.slither.save_results_to_hide(
[r for (idx, r) in enumerate(results) if idx in indexes]
[r for (idx, r) in enumerate(results) if idx in indexes_converted]
)
return [r for (idx, r) in enumerate(results) if idx not in indexes]
return [r for (idx, r) in enumerate(results) if idx not in indexes_converted]
except ValueError:
self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3"))
return results
@property
def color(self):
def color(self) -> Callable[[str], str]:
return classification_colors[self.IMPACT]
def generate_result(self, info, additional_fields=None):
def generate_result(
self,
info: Union[str, List[Union[str, SupportedOutput]]],
additional_fields: Optional[Dict] = None,
) -> Output:
output = Output(
info,
additional_fields,
@ -233,6 +233,15 @@ class AbstractDetector(metaclass=abc.ABCMeta):
return output
@staticmethod
def _format(_compilation_unit: SlitherCompilationUnit, _result):
def _format(_compilation_unit: SlitherCompilationUnit, _result: Dict) -> None:
"""Implement format"""
return
def _log_result(self, results: List[Dict]) -> None:
info = "\n"
for idx, result in enumerate(results):
if self.slither.triage_mode:
info += f"{idx}: "
info += result["description"]
info += f"Reference: {self.WIKI}"
self._log(info)

@ -79,6 +79,5 @@ from .statements.unary import IncorrectUnaryExpressionDetection
from .operations.missing_zero_address_validation import MissingZeroAddressValidation
from .functions.dead_code import DeadCode
from .statements.write_after_write import WriteAfterWrite
#
#
from .statements.msg_value_in_loop import MsgValueInLoop
from .statements.delegatecall_in_loop import DelegatecallInLoop

@ -17,6 +17,8 @@ class ShiftParameterMixup(AbstractDetector):
WIKI_TITLE = "Incorrect shift in assembly."
WIKI_DESCRIPTION = "Detect if the values in a shift operation are reversed"
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
@ -28,6 +30,7 @@ contract C {
}
```
The shift statement will right-shift the constant 8 by `a` bits"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Swap the order of parameters."

@ -19,6 +19,8 @@ class ConstantFunctionsAsm(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code"
WIKI_TITLE = "Constant functions using assembly code"
# region wiki_description
WIKI_DESCRIPTION = """
Functions declared as `constant`/`pure`/`view` using assembly code.
@ -26,7 +28,9 @@ Functions declared as `constant`/`pure`/`view` using assembly code.
Starting from Solidity 0.5, a call to a `constant`/`pure`/`view` function uses the `STATICCALL` opcode, which reverts in case of state modification.
As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts)."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Constant{
@ -39,6 +43,7 @@ contract Constant{
```
`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract that interacts with `Constant` in Solidity 0.5.0.
All the calls to `get` revert, breaking Bob's smart contract execution."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Ensure the attributes of contracts compiled prior to Solidity 0.5.0 are correct."

@ -19,6 +19,8 @@ class ConstantFunctionsState(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state"
WIKI_TITLE = "Constant functions changing the state"
# region wiki_description
WIKI_DESCRIPTION = """
Functions declared as `constant`/`pure`/`view` change the state.
@ -26,7 +28,9 @@ Functions declared as `constant`/`pure`/`view` change the state.
Starting from Solidity 0.5, a call to a `constant`/`pure`/`view` function uses the `STATICCALL` opcode, which reverts in case of state modification.
As a result, a call to an [incorrectly labeled function may trap a contract compiled with Solidity 0.5](https://solidity.readthedocs.io/en/develop/050-breaking-changes.html#interoperability-with-older-contracts)."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Constant{
@ -39,6 +43,7 @@ contract Constant{
```
`Constant` was deployed with Solidity 0.4.25. Bob writes a smart contract that interacts with `Constant` in Solidity 0.5.0.
All the calls to `get` revert, breaking Bob's smart contract execution."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Ensure that attributes of contracts compiled prior to Solidity 0.5.0 are correct."

@ -30,24 +30,29 @@ class IncorrectSolc(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity"
WIKI_TITLE = "Incorrect versions of Solidity"
# region wiki_description
WIKI_DESCRIPTION = """
`solc` frequently releases new compiler versions. Using an old version prevents access to new Solidity security checks.
We also recommend avoiding complex `pragma` statement."""
# endregion wiki_description
# region wiki_recommendation
WIKI_RECOMMENDATION = """
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
Use a simple pragma version that allows any of these versions.
Consider using the latest version of Solidity for testing."""
# endregion wiki_recommendation
COMPLEX_PRAGMA_TXT = "is too complex"
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"
)
TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7"
BUGGY_VERSION_TXT = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
)
@ -60,6 +65,10 @@ Consider using the latest version of Solidity for testing."""
"0.6.12",
"0.7.5",
"0.7.6",
"0.8.4",
"0.8.5",
"0.8.6",
"0.8.7",
]
# Indicates the versions that should not be used.
@ -74,6 +83,8 @@ Consider using the latest version of Solidity for testing."""
"^0.5.14",
"0.6.9",
"^0.6.9",
"0.8.8",
"^0.8.8",
]
def _check_version(self, version):
@ -81,6 +92,8 @@ Consider using the latest version of Solidity for testing."""
if op and op not in [">", ">=", "^"]:
return self.LESS_THAN_TXT
version_number = ".".join(version[2:])
if version_number in self.BUGGY_VERSIONS:
return self.BUGGY_VERSION_TXT
if version_number not in self.ALLOWED_VERSIONS:
if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split("."))):
return self.TOO_RECENT_VERSION_TXT
@ -138,11 +151,20 @@ Consider using the latest version of Solidity for testing."""
results.append(json)
if self.compilation_unit.solc_version not in self.ALLOWED_VERSIONS:
info = [
"solc-",
self.compilation_unit.solc_version,
" is not recommended for deployment\n",
]
if self.compilation_unit.solc_version in self.BUGGY_VERSIONS:
info = [
"solc-",
self.compilation_unit.solc_version,
" ",
self.BUGGY_VERSION_TXT,
]
else:
info = [
"solc-",
self.compilation_unit.solc_version,
" is not recommended for deployment\n",
]
json = self.generate_result(info)

@ -25,6 +25,8 @@ class LockedEther(AbstractDetector): # pylint: disable=too-many-nested-blocks
WIKI_TITLE = "Contracts that lock Ether"
WIKI_DESCRIPTION = "Contract with a `payable` function, but without a withdrawal capacity."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
pragma solidity 0.4.24;
@ -34,6 +36,7 @@ contract Locked{
}
```
Every Ether sent to `Locked` will be lost."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Remove the payable attribute or add a withdraw function."

@ -21,6 +21,8 @@ class MissingInheritance(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance"
WIKI_TITLE = "Missing inheritance"
WIKI_DESCRIPTION = "Detect missing inheritance."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
interface ISomething {
@ -35,6 +37,7 @@ contract Something {
```
`Something` should inherit from `ISomething`.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Inherit from the missing interface or contract."

@ -26,6 +26,8 @@ class ArrayByReference(AbstractDetector):
WIKI_DESCRIPTION = (
"Detect arrays passed to a function that expects reference to a storage array"
)
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Memory {
@ -48,6 +50,7 @@ contract Memory {
Bob calls `f()`. Bob assumes that at the end of the call `x[0]` is 2, but it is 1.
As a result, Bob's usage of the contract is incorrect."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Ensure the correct usage of `memory` and `storage` in the function parameters. Make all the locations explicit."

@ -48,6 +48,8 @@ class EnumConversion(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion"
WIKI_TITLE = "Dangerous enum conversion"
WIKI_DESCRIPTION = "Detect out-of-range `enum` conversion (`solc` < `0.4.5`)."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
pragma solidity 0.4.2;
@ -61,6 +63,7 @@ class EnumConversion(AbstractDetector):
}
```
Attackers can trigger unexpected behaviour by calling `bug(1)`."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Use a recent compiler version. If `solc` <`0.4.5` is required, check the `enum` conversion range."

@ -20,6 +20,8 @@ class MultipleConstructorSchemes(AbstractDetector):
WIKI_DESCRIPTION = (
"Detect multiple constructor definitions in the same contract (using new and old schemes)."
)
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
@ -37,6 +39,7 @@ contract A {
}
```
In Solidity [0.4.22](https://github.com/ethereum/solidity/releases/tag/v0.4.23), a contract with both constructor schemes will compile. The first constructor will take precedence over the second, which may be unintended."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Only declare one constructor, preferably using the new scheme `constructor(...)` instead of `function <contractName>(...)`."

@ -72,9 +72,9 @@ class PublicMappingNested(AbstractDetector):
"""
results = []
for p in self.compilation_unit.pragma_directives:
if "0.5.0" in p.version and not "<0.5.0" in p.version:
return []
if self.compilation_unit.solc_version >= "0.5.0":
return []
if self.compilation_unit.solc_version and self.compilation_unit.solc_version.startswith(
"0.5."
):

@ -34,6 +34,8 @@ class ReusedBaseConstructor(AbstractDetector):
WIKI_TITLE = "Reused base constructors"
WIKI_DESCRIPTION = "Detects if the same base constructor is called with arguments from two different locations in the same inheritance hierarchy."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
pragma solidity ^0.4.0;
@ -65,6 +67,8 @@ The constructor of `A` is called multiple times in `D` and `E`:
- `D` inherits from `B` and `C`, both of which construct `A`.
- `E` only inherits from `B`, but `B` and `E` construct `A`.
."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Remove the duplicate constructor call."
def _detect_explicitly_called_base_constructors(self, contract):
@ -73,7 +77,7 @@ The constructor of `A` is called multiple times in `D` and `E`:
:param contract: The contract to detect explicit calls to a base constructor with arguments to.
:return: Dictionary of function:list(tuple): { constructor : [(invoking_contract, called_by_constructor]}
"""
results = dict()
results = {}
# Create a set to track all completed contracts
processed_contracts = set()

@ -43,7 +43,6 @@ vulnerable_solc_versions = [
"0.5.7",
"0.5.8",
"0.5.9",
"0.5.10",
]
@ -61,7 +60,9 @@ class ABIEncoderV2Array(AbstractDetector):
"https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array"
)
WIKI_TITLE = "Storage ABIEncoderV2 Array"
WIKI_DESCRIPTION = """`solc` versions `0.4.7`-`0.5.10` contain a [compiler bug](https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs.) leading to incorrect ABI encoder usage."""
WIKI_DESCRIPTION = """`solc` versions `0.4.7`-`0.5.9` contain a [compiler bug](https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs) leading to incorrect ABI encoder usage."""
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
@ -75,6 +76,8 @@ contract A {
```
`abi.encode(bad_arr)` in a call to `bad()` will incorrectly encode the array as `[[1, 2], [2, 3], [3, 4]]` and lead to unintended behavior.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Use a compiler >= `0.5.10`."
@staticmethod

@ -59,8 +59,13 @@ class StorageSignedIntegerArray(AbstractDetector):
"https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array"
)
WIKI_TITLE = "Storage Signed Integer Array"
# region wiki_description
WIKI_DESCRIPTION = """`solc` versions `0.4.7`-`0.5.10` contain [a compiler bug](https://blog.ethereum.org/2019/06/25/solidity-storage-array-bugs)
leading to incorrect values in signed integer arrays."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
@ -75,6 +80,8 @@ contract A {
`bad0()` uses a (storage-allocated) signed integer array state variable to store the ether balances of three accounts.
`-1` is supposed to indicate uninitialized values but the Solidity bug makes these as `1`, which could be exploited by the accounts.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Use a compiler version >= `0.5.10`."
@staticmethod

@ -89,6 +89,8 @@ class UninitializedFunctionPtrsConstructor(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors"
WIKI_TITLE = "Uninitialized function pointers in constructors"
WIKI_DESCRIPTION = "solc versions `0.4.5`-`0.4.26` and `0.5.0`-`0.5.8` contain a compiler bug leading to unexpected behavior when calling uninitialized function pointers in constructors."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract bad0 {
@ -102,6 +104,8 @@ contract bad0 {
}
```
The call to `a(10)` will lead to unexpected behavior because function pointer `a` is not initialized in the constructor."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Initialize function pointers before calling. Avoid function pointers if possible."
)

@ -19,6 +19,8 @@ class IncorrectERC20InterfaceDetection(AbstractDetector):
WIKI_TITLE = "Incorrect erc20 interface"
WIKI_DESCRIPTION = "Incorrect return values for `ERC20` functions. A contract compiled with Solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Token{
@ -27,6 +29,7 @@ contract Token{
}
```
`Token.transfer` does not return a boolean. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC20` interface implementation. Alice's contract is unable to interact with Bob's contract."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Set the appropriate return values and types for the defined `ERC20` functions."

@ -20,6 +20,8 @@ class IncorrectERC721InterfaceDetection(AbstractDetector):
WIKI_TITLE = "Incorrect erc721 interface"
WIKI_DESCRIPTION = "Incorrect return values for `ERC721` functions. A contract compiled with solidity > 0.4.22 interacting with these functions will fail to execute them, as the return value is missing."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Token{
@ -28,6 +30,7 @@ contract Token{
}
```
`Token.ownerOf` does not return an address like `ERC721` expects. Bob deploys the token. Alice creates a contract that interacts with it but assumes a correct `ERC721` interface implementation. Alice's contract is unable to interact with Bob's contract."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Set the appropriate return values and vtypes for the defined `ERC721` functions."

@ -16,8 +16,10 @@ class UnindexedERC20EventParameters(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters"
WIKI_TITLE = "Unindexed ERC20 event oarameters"
WIKI_TITLE = "Unindexed ERC20 event parameters"
WIKI_DESCRIPTION = "Detects whether events defined by the `ERC20` specification that should have some parameters as `indexed` are missing the `indexed` keyword."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract ERC20Bad {
@ -30,6 +32,7 @@ contract ERC20Bad {
```
`Transfer` and `Approval` events should have the 'indexed' keyword on their two first parameters, as defined by the `ERC20` specification.
Failure to include these keywords will exclude the parameter data in the transaction/block's bloom filter, so external tooling searching for these parameters may overlook them and fail to index logs from this token contract."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Add the `indexed` keyword to event parameters that should include it, according to the `ERC20` specification."

@ -9,7 +9,10 @@
TODO: dont report if the value is tainted by msg.value
"""
from slither.core.declarations import Function
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations import Function, Contract
from slither.analyses.data_dependency.data_dependency import is_tainted, is_dependent
from slither.core.declarations.solidity_variables import (
SolidityFunction,
@ -27,11 +30,14 @@ from slither.slithir.operations import (
# pylint: disable=too-many-nested-blocks,too-many-branches
def arbitrary_send(func):
from slither.utils.output import Output
def arbitrary_send(func: Function):
if func.is_protected():
return []
ret = []
ret: List[Node] = []
for node in func.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall):
@ -68,7 +74,7 @@ def arbitrary_send(func):
return ret
def detect_arbitrary_send(contract):
def detect_arbitrary_send(contract: Contract):
"""
Detect arbitrary send
Args:
@ -94,6 +100,8 @@ class ArbitrarySend(AbstractDetector):
WIKI_TITLE = "Functions that send Ether to arbitrary destinations"
WIKI_DESCRIPTION = "Unprotected call to a function sending Ether to an arbitrary address."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract ArbitrarySend{
@ -108,10 +116,11 @@ contract ArbitrarySend{
}
```
Bob calls `setDestination` and `withdraw`. As a result he withdraws the contract's balance."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Ensure that an arbitrary user cannot withdraw unauthorized funds."
def _detect(self):
def _detect(self) -> List[Output]:
""""""
results = []

@ -21,6 +21,8 @@ class DeadCode(AbstractDetector):
WIKI_TITLE = "Dead-code"
WIKI_DESCRIPTION = "Functions that are not sued."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Contract{
@ -28,6 +30,7 @@ contract Contract{
}
```
`dead_code` is not used in the contract, and make the code's review more difficult."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Remove unused functions."

@ -42,6 +42,8 @@ class ModifierDefaultDetection(AbstractDetector):
WIKI_TITLE = "Incorrect modifier"
WIKI_DESCRIPTION = "If a modifier does not execute `_` or revert, the execution of the function will return the default value, which can be misleading for the caller."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
modidfier myModif(){
@ -54,6 +56,7 @@ class ModifierDefaultDetection(AbstractDetector):
}
```
If the condition in `myModif` is false, the execution of `get()` will return 0."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "All the paths in a modifier must execute `_` or revert."

@ -21,6 +21,8 @@ class Suicidal(AbstractDetector):
WIKI_TITLE = "Suicidal"
WIKI_DESCRIPTION = "Unprotected call to a function executing `selfdestruct`/`suicide`."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Suicidal{
@ -30,6 +32,7 @@ contract Suicidal{
}
```
Bob calls `kill` and destructs the contract."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Protect access to all sensitive functions."

@ -28,6 +28,8 @@ class UnimplementedFunctionDetection(AbstractDetector):
WIKI_TITLE = "Unimplemented functions"
WIKI_DESCRIPTION = "Detect functions that are not implemented on derived-most contracts."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
interface BaseInterface {
@ -48,6 +50,7 @@ contract DerivedContract is BaseInterface, BaseInterface2 {
`DerivedContract` does not implement `BaseInterface.f2` or `BaseInterface2.f3`.
As a result, the contract will not properly compile.
All unimplemented functions must be implemented on a contract that is meant to be used."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Implement all unimplemented functions in any contract you intend to use directly (not simply inherit from)."

@ -22,11 +22,14 @@ class NamingConvention(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions"
WIKI_TITLE = "Conformance to Solidity naming conventions"
# region wiki_description
WIKI_DESCRIPTION = """
Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions) that should be followed.
#### Rule exceptions
- Allow constant variable name/symbol/decimals to be lowercase (`ERC20`).
- Allow `_` at the beginning of the `mixed_case` match for private variables and unused parameters."""
# endregion wiki_description
WIKI_RECOMMENDATION = "Follow the Solidity [naming convention](https://solidity.readthedocs.io/en/v0.4.25/style-guide.html#naming-conventions)."

@ -2,18 +2,24 @@
Module detecting bad PRNG due to the use of block.timestamp, now or blockhash (block.blockhash) as a source of randomness
"""
from typing import List, Tuple
from slither.analyses.data_dependency.data_dependency import is_dependent_ssa
from slither.core.cfg.node import Node
from slither.core.declarations import Function, Contract
from slither.core.declarations.solidity_variables import (
SolidityVariable,
SolidityFunction,
SolidityVariableComposed,
)
from slither.core.variables.variable import Variable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import BinaryType, Binary
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output, AllSupportedOutput
def collect_return_values_of_bad_PRNG_functions(f):
def collect_return_values_of_bad_PRNG_functions(f: Function) -> List:
"""
Return the return-values of calls to blockhash()
Args:
@ -33,7 +39,7 @@ def collect_return_values_of_bad_PRNG_functions(f):
return values_returned
def contains_bad_PRNG_sources(func, blockhash_ret_values):
def contains_bad_PRNG_sources(func: Function, blockhash_ret_values: List[Variable]) -> List[Node]:
"""
Check if any node in function has a modulus operator and the first operand is dependent on block.timestamp, now or blockhash()
Returns:
@ -57,7 +63,7 @@ def contains_bad_PRNG_sources(func, blockhash_ret_values):
return list(ret)
def detect_bad_PRNG(contract):
def detect_bad_PRNG(contract: Contract) -> List[Tuple[Function, List[Node]]]:
"""
Args:
contract (Contract)
@ -67,7 +73,7 @@ def detect_bad_PRNG(contract):
blockhash_ret_values = []
for f in contract.functions:
blockhash_ret_values += collect_return_values_of_bad_PRNG_functions(f)
ret = []
ret: List[Tuple[Function, List[Node]]] = []
for f in contract.functions:
bad_prng_nodes = contains_bad_PRNG_sources(f, blockhash_ret_values)
if bad_prng_nodes:
@ -89,6 +95,8 @@ class BadPRNG(AbstractDetector):
WIKI_TITLE = "Weak PRNG"
WIKI_DESCRIPTION = "Weak PRNG due to a modulo on `block.timestamp`, `now` or `blockhash`. These can be influenced by miners to some extent so they should be avoided."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Game {
@ -102,12 +110,13 @@ contract Game {
```
Eve is a miner. Eve calls `guessing` and re-orders the block containing the transaction.
As a result, Eve wins the game."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Do not use `block.timestamp`, `now` or `blockhash` as a source of randomness"
)
def _detect(self):
def _detect(self) -> List[Output]:
"""Detect bad PRNG due to the use of block.timestamp, now or blockhash (block.blockhash) as a source of randomness"""
results = []
for c in self.compilation_unit.contracts_derived:
@ -115,7 +124,7 @@ As a result, Eve wins the game."""
for func, nodes in values:
for node in nodes:
info = [func, ' uses a weak PRNG: "', node, '" \n']
info: List[AllSupportedOutput] = [func, ' uses a weak PRNG: "', node, '" \n']
res = self.generate_result(info)
results.append(res)

@ -22,6 +22,8 @@ class MissingEventsAccessControl(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control"
WIKI_TITLE = "Missing events access control"
WIKI_DESCRIPTION = "Detect missing events for critical access control parameters"
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
@ -38,6 +40,7 @@ contract C {
```
`updateOwner()` has no event, so it is difficult to track off-chain owner changes.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Emit an event for critical parameter changes."

@ -22,6 +22,8 @@ class MissingEventsArithmetic(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic"
WIKI_TITLE = "Missing events arithmetic"
WIKI_DESCRIPTION = "Detect missing events for critical arithmetic parameters."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
@ -42,6 +44,7 @@ contract C {
```
`updateOwner()` has no event, so it is difficult to track off-chain changes in the buy price.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Emit an event for critical parameter changes."

@ -24,6 +24,8 @@ class MissingZeroAddressValidation(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation"
WIKI_TITLE = "Missing zero address validation"
WIKI_DESCRIPTION = "Detect missing zero address validation."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
@ -38,8 +40,9 @@ contract C {
}
}
```
Bob calls `updateOwner` without specifying the `newOwner`, soBob loses ownership of the contract.
Bob calls `updateOwner` without specifying the `newOwner`, so Bob loses ownership of the contract.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Check that the address is not zero."

@ -20,6 +20,8 @@ class UncheckedLowLevel(UnusedReturnValues):
WIKI_TITLE = "Unchecked low-level calls"
WIKI_DESCRIPTION = "The return value of a low-level call is not checked."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MyConc{
@ -31,6 +33,7 @@ contract MyConc{
The return value of the low-level call is not checked, so if the call fails, the Ether will be locked in the contract.
If the low level is used to prevent blocking operations, consider logging failed calls.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Ensure that the return value of a low-level call is checked or logged."

@ -21,6 +21,8 @@ class UncheckedSend(UnusedReturnValues):
WIKI_TITLE = "Unchecked Send"
WIKI_DESCRIPTION = "The return value of a `send` is not checked."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MyConc{
@ -32,6 +34,7 @@ contract MyConc{
The return value of `send` is not checked, so if the send fails, the Ether will be locked in the contract.
If `send` is used to prevent blocking operations, consider logging the failed `send`.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Ensure that the return value of `send` is checked or logged."

@ -22,6 +22,8 @@ class UncheckedTransfer(UnusedReturnValues):
WIKI_TITLE = "Unchecked transfer"
WIKI_DESCRIPTION = "The return value of an external transfer/transferFrom call is not checked"
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Token {
@ -37,6 +39,7 @@ contract MyBank{
}
```
Several tokens do not revert in case of failure and return false. If one of these tokens is used in `MyBank`, `deposit` will not revert if the transfer fails, and an attacker can call `deposit` for free.."""
# endregion wiki_exploit_scenariox
WIKI_RECOMMENDATION = (
"Use `SafeERC20`, or ensure that the transfer/transferFrom return value is checked."

@ -24,6 +24,8 @@ class UnusedReturnValues(AbstractDetector):
WIKI_DESCRIPTION = (
"The return value of an external call is not stored in a local or state variable."
)
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MyConc{
@ -34,6 +36,7 @@ contract MyConc{
}
```
`MyConc` calls `add` of `SafeMath`, but does not store the result in `a`. As a result, the computation has no effect."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Ensure that all the return values of the function calls are used."

@ -14,6 +14,8 @@ class VoidConstructor(AbstractDetector):
WIKI_TITLE = "Void constructor"
WIKI_DESCRIPTION = "Detect the call to a constructor that is not implemented"
WIKI_RECOMMENDATION = "Remove the constructor call."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A{}
@ -22,6 +24,7 @@ contract B is A{
}
```
When reading `B`'s constructor definition, we might assume that `A()` initiates the contract, but no code is executed."""
# endregion wiki_exploit_scenario
def _detect(self):
""""""

@ -25,9 +25,14 @@ class ReentrancyBenign(Reentrancy):
)
WIKI_TITLE = "Reentrancy vulnerabilities"
# region wiki_description
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentrancy-no-eth`)."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
function callme(){
@ -39,6 +44,7 @@ Only report reentrancy that acts as a double call (see `reentrancy-eth`, `reentr
```
`callme` contains a reentrancy. The reentrancy is benign because it's exploitation would have the same effect as two consecutive calls."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."

@ -25,9 +25,14 @@ class ReentrancyEth(Reentrancy):
)
WIKI_TITLE = "Reentrancy vulnerabilities"
# region wiki_description
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)"""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
function withdrawBalance(){
@ -41,6 +46,7 @@ Do not report reentrancies that don't involve Ether (see `reentrancy-no-eth`)"""
```
Bob uses the re-entrancy bug to call `withdrawBalance` two times, and withdraw more than its initial deposit to the contract."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions pattern`](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."

@ -24,9 +24,14 @@ class ReentrancyEvent(Reentrancy):
)
WIKI_TITLE = "Reentrancy vulnerabilities"
# region wiki_description
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Only report reentrancies leading to out-of-order events."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
function bug(Called d){
@ -37,6 +42,7 @@ Only report reentrancies leading to out-of-order events."""
```
If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, which might lead to issues for third parties."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."

@ -28,9 +28,14 @@ class ReentrancyNoGas(Reentrancy):
)
WIKI_TITLE = "Reentrancy vulnerabilities"
# region wiki_description
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Only report reentrancy that is based on `transfer` or `send`."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
function callme(){
@ -40,6 +45,7 @@ Only report reentrancy that is based on `transfer` or `send`."""
```
`send` and `transfer` do not protect from reentrancies in case of gas price changes."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."

@ -24,10 +24,14 @@ class ReentrancyReadBeforeWritten(Reentrancy):
)
WIKI_TITLE = "Reentrancy vulnerabilities"
# region wiki_description
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Do not report reentrancies that involve Ether (see `reentrancy-eth`)."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
function bug(){
@ -39,6 +43,8 @@ Do not report reentrancies that involve Ether (see `reentrancy-eth`)."""
}
```
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
STANDARD_JSON = False

@ -46,9 +46,14 @@ class TokenReentrancy(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#token-reentrant"
WIKI_TITLE = "Token reentrant"
# region wiki_description
WIKI_DESCRIPTION = """
Tokens that allow arbitrary external call on transfer/transfer (such as ERC223/ERC777) can be exploited on third
party through a reentrancy."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MyToken{
@ -70,9 +75,12 @@ contract MyDefi{
`MyDefi` has a reentrancy, but its developers did not think transferFrom could be reentrancy.
`MyToken` is used in MyDefi. As a result an attacker can exploit the reentrancy."""
# endregion wiki_exploit_scenario
# region wiki_recommendation
WIKI_RECOMMENDATION = """Avoid to have external calls in `transfer`/`transferFrom`.
If you do, ensure your users are aware of the potential issues."""
# endregion wiki_recommendation
def _detect(self):
results = []

@ -2,19 +2,24 @@
Module detecting shadowing variables on abstract contract
Recursively check the called functions
"""
from typing import List
from slither.core.declarations import Contract
from slither.core.variables.state_variable import StateVariable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output, AllSupportedOutput
def detect_shadowing(contract):
ret = []
def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
ret: List[List[StateVariable]] = []
variables_fathers = []
for father in contract.inheritance:
if all(not f.is_implemented for f in father.functions + father.modifiers):
if all(not f.is_implemented for f in father.functions + list(father.modifiers)):
variables_fathers += father.state_variables_declared
var: StateVariable
for var in contract.state_variables_declared:
shadow = [v for v in variables_fathers if v.name == var.name]
shadow: List[StateVariable] = [v for v in variables_fathers if v.name == var.name]
if shadow:
ret.append([var] + shadow)
return ret
@ -34,6 +39,8 @@ class ShadowingAbstractDetection(AbstractDetector):
WIKI_TITLE = "State variable shadowing from abstract contracts"
WIKI_DESCRIPTION = "Detection of state variables shadowed from abstract contracts."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract BaseContract{
@ -45,10 +52,11 @@ contract DerivedContract is BaseContract{
}
```
`owner` of `BaseContract` is shadowed in `DerivedContract`."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Remove the state variable shadowing."
def _detect(self):
def _detect(self) -> List[Output]:
"""Detect shadowing
Recursively visit the calls
@ -56,14 +64,14 @@ contract DerivedContract is BaseContract{
list: {'vuln', 'filename,'contract','func', 'shadow'}
"""
results = []
results: List[Output] = []
for contract in self.contracts:
shadowing = detect_shadowing(contract)
if shadowing:
for all_variables in shadowing:
shadow = all_variables[0]
variables = all_variables[1:]
info = [shadow, " shadows:\n"]
info: List[AllSupportedOutput] = [shadow, " shadows:\n"]
for var in variables:
info += ["\t- ", var, "\n"]

@ -19,6 +19,8 @@ class BuiltinSymbolShadowing(AbstractDetector):
WIKI_TITLE = "Builtin Symbol Shadowing"
WIKI_DESCRIPTION = "Detection of shadowing built-in symbols using local variables, state variables, functions, modifiers, or events."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
pragma solidity ^0.4.24;
@ -36,6 +38,7 @@ contract Bug {
}
```
`now` is defined as a state variable, and shadows with the built-in symbol `now`. The function `assert` overshadows the built-in `assert` function. Any use of either of these built-in symbols may lead to unexpected results."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Rename the local variables, state variables, functions, modifiers, and events that shadow a builtin symbol."

@ -19,6 +19,8 @@ class LocalShadowing(AbstractDetector):
WIKI_TITLE = "Local variable shadowing"
WIKI_DESCRIPTION = "Detection of shadowing using local variables."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
pragma solidity ^0.4.24;
@ -39,6 +41,7 @@ contract Bug {
}
```
`sensitive_function.owner` shadows `Bug.owner`. As a result, the use of `owner` in `sensitive_function` might be incorrect."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Rename the local variables that shadow another component."

@ -33,6 +33,8 @@ class StateShadowing(AbstractDetector):
WIKI_TITLE = "State variable shadowing"
WIKI_DESCRIPTION = "Detection of state variables shadowed."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract BaseContract{
@ -58,6 +60,7 @@ contract DerivedContract is BaseContract{
}
```
`owner` of `BaseContract` is never assigned and the modifier `isOwner` does not work."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Remove the state variable shadowing."

@ -34,19 +34,35 @@ class NameReused(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused"
WIKI_TITLE = "Name reused"
# region wiki_description
WIKI_DESCRIPTION = """If a codebase has two contracts the similar names, the compilation artifacts
will not contain one of the contracts with the duplicate name."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
Bob's `truffle` codebase has two contracts named `ERC20`.
When `truffle compile` runs, only one of the two contracts will generate artifacts in `build/contracts`.
As a result, the second contract cannot be analyzed.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Rename the contract."
def _detect(self): # pylint: disable=too-many-locals,too-many-branches
results = []
compilation_unit = self.compilation_unit
names_reused = compilation_unit.contract_name_collisions
all_contracts = compilation_unit.contracts
all_contracts_name = [c.name for c in all_contracts]
contracts_name_reused = {
contract for contract in all_contracts_name if all_contracts_name.count(contract) > 1
}
names_reused = {
name: compilation_unit.get_contract_from_name(name) for name in contracts_name_reused
}
# First show the contracts that we know are missing
incorrectly_constructed = [

@ -15,6 +15,8 @@ class RightToLeftOverride(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character"
WIKI_TITLE = "Right-to-Left-Override character"
WIKI_DESCRIPTION = "An attacker can manipulate the logic of the contract by using a right-to-left-override character (`U+202E)`."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Token
@ -43,6 +45,8 @@ contract Token
`Token` uses the right-to-left-override character when calling `_withdraw`. As a result, the fee is incorrectly sent to `msg.sender`, and the token balance is sent to the owner.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Special control characters must not be allowed."
RTLO_CHARACTER_ENCODED = "\u202e".encode("utf-8")

@ -72,6 +72,8 @@ class ArrayLengthAssignment(AbstractDetector):
WIKI_TITLE = "Array Length Assignment"
WIKI_DESCRIPTION = """Detects the direct assignment of an array's length."""
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
@ -94,9 +96,12 @@ Contract storage/state-variables are indexed by a 256-bit integer.
The user can set the array length to `2**256-1` in order to index all storage slots.
In the example above, one could call the function `f` to set the array length, then call the function `g` to control any storage slot desired.
Note that storage slots here are indexed via a hash of the indexers; nonetheless, all storage will still be accessible and could be controlled by the attacker."""
# endregion wiki_exploit_scenario
# region wiki_recommendation
WIKI_RECOMMENDATION = """Do not allow array lengths to be set directly set; instead, opt to add values as needed.
Otherwise, thoroughly review the contract to ensure a user-controlled variable cannot reach an array length assignment."""
# endregion wiki_recommendation
def _detect(self):
"""

@ -48,8 +48,10 @@ class AssertStateChange(AbstractDetector):
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change"
WIKI_TITLE = "Assert state shange"
WIKI_TITLE = "Assert state change"
WIKI_DESCRIPTION = """Incorrect use of `assert()`. See Solidity best [practices](https://solidity.readthedocs.io/en/latest/control-structures.html#id4)."""
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
@ -63,6 +65,8 @@ contract A {
```
The assert in `bad()` increments the state variable `s_a` while checking for the condition.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """Use `require` for invariants modifying the state."""
def _detect(self):

@ -24,6 +24,8 @@ class BooleanEquality(AbstractDetector):
WIKI_TITLE = "Boolean equality"
WIKI_DESCRIPTION = """Detects the comparison to boolean constants."""
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
@ -37,6 +39,7 @@ contract A {
}
```
Boolean constants can be used directly and do not need to be compare to `true` or `false`."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """Remove the equality to the boolean constant."""

@ -32,6 +32,8 @@ class BooleanConstantMisuse(AbstractDetector):
WIKI_TITLE = "Misuse of a Boolean constant"
WIKI_DESCRIPTION = """Detects the misuse of a Boolean constant."""
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
@ -52,6 +54,7 @@ contract A {
```
Boolean constants in code have only a few legitimate uses.
Other uses (in complex expressions, as conditionals) indicate either an error or, most likely, the persistence of faulty code."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """Verify and simplify the condition."""

@ -1,14 +1,51 @@
from slither.core.cfg.node import NodeType
from typing import List
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
from slither.utils.output import Output
from slither.slithir.operations import (
HighLevelCall,
LibraryCall,
LowLevelCall,
Send,
Transfer,
InternalCall,
)
def detect_call_in_loop(contract: Contract) -> List[Node]:
ret: List[Node] = []
for f in contract.functions_entry_points:
if f.is_implemented:
call_in_loop(f.entry_point, 0, [], ret)
return ret
def call_in_loop(node: Node, in_loop_counter: int, visited: List[Node], ret: List[Node]) -> None:
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop_counter += 1
elif node.type == NodeType.ENDLOOP:
in_loop_counter -= 1
if in_loop_counter > 0:
for ir in node.all_slithir_operations():
if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)):
if isinstance(ir, LibraryCall):
continue
ret.append(ir.node)
if isinstance(ir, (InternalCall)):
call_in_loop(ir.function.entry_point, in_loop_counter, visited, ret)
for son in node.sons:
call_in_loop(son, in_loop_counter, visited, ret)
class MultipleCallsInLoop(AbstractDetector):
ARGUMENT = "calls-loop"
@ -20,6 +57,8 @@ class MultipleCallsInLoop(AbstractDetector):
WIKI_TITLE = "Calls inside a loop"
WIKI_DESCRIPTION = "Calls inside a loop might lead to a denial-of-service attack."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract CallsInLoop{
@ -39,45 +78,15 @@ contract CallsInLoop{
}
```
If one of the destinations has a fallback function that reverts, `bad` will always revert."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Favor [pull over push](https://github.com/ethereum/wiki/wiki/Safety#favor-pull-over-push-for-external-calls) strategy for external calls."
@staticmethod
def call_in_loop(node, in_loop, visited, ret):
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop = True
elif node.type == NodeType.ENDLOOP:
in_loop = False
if in_loop:
for ir in node.irs:
if isinstance(ir, (LowLevelCall, HighLevelCall, Send, Transfer)):
if isinstance(ir, LibraryCall):
continue
ret.append(node)
for son in node.sons:
MultipleCallsInLoop.call_in_loop(son, in_loop, visited, ret)
@staticmethod
def detect_call_in_loop(contract):
ret = []
for f in contract.functions + contract.modifiers:
if f.contract_declarer == contract and f.is_implemented:
MultipleCallsInLoop.call_in_loop(f.entry_point, False, [], ret)
return ret
def _detect(self):
def _detect(self) -> List[Output]:
""""""
results = []
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
values = self.detect_call_in_loop(c)
values = detect_call_in_loop(c)
for node in values:
func = node.function

@ -27,6 +27,8 @@ class ControlledDelegateCall(AbstractDetector):
WIKI_TITLE = "Controlled Delegatecall"
WIKI_DESCRIPTION = "`Delegatecall` or `callcode` to an address controlled by the user."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Delegatecall{
@ -36,6 +38,7 @@ contract Delegatecall{
}
```
Bob calls `delegate` and delegates the execution to his malicious contract. As a result, Bob withdraws the funds of the contract and destructs it."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Avoid using `delegatecall`. Use only trusted destinations."

@ -1,7 +1,45 @@
from slither.core.cfg.node import NodeType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.mapping_type import MappingType
from typing import List
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
from slither.utils.output import Output
from slither.slithir.operations import InternalCall, OperationWithLValue
from slither.core.variables.state_variable import StateVariable
def detect_costly_operations_in_loop(contract: Contract) -> List[Node]:
ret: List[Node] = []
for f in contract.functions_entry_points:
if f.is_implemented:
costly_operations_in_loop(f.entry_point, 0, [], ret)
return ret
def costly_operations_in_loop(
node: Node, in_loop_counter: int, visited: List[Node], ret: List[Node]
) -> None:
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop_counter += 1
elif node.type == NodeType.ENDLOOP:
in_loop_counter -= 1
if in_loop_counter > 0:
for ir in node.all_slithir_operations():
# Ignore Array/Mapping/Struct types for now
if isinstance(ir, OperationWithLValue) and isinstance(ir.lvalue, StateVariable):
ret.append(ir.node)
break
if isinstance(ir, (InternalCall)):
costly_operations_in_loop(ir.function.entry_point, in_loop_counter, visited, ret)
for son in node.sons:
costly_operations_in_loop(son, in_loop_counter, visited, ret)
class CostlyOperationsInLoop(AbstractDetector):
@ -20,6 +58,8 @@ class CostlyOperationsInLoop(AbstractDetector):
WIKI_DESCRIPTION = (
"Costly operations inside a loop might waste gas, so optimizations are justified."
)
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract CostlyOperationsInLoop{
@ -43,47 +83,15 @@ contract CostlyOperationsInLoop{
}
```
Incrementing `state_variable` in a loop incurs a lot of gas because of expensive `SSTOREs`, which might lead to an `out-of-gas`."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Use a local variable to hold the loop computation result."
@staticmethod
def costly_operations_in_loop(node, in_loop, visited, ret):
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop = True
elif node.type == NodeType.ENDLOOP:
in_loop = False
if in_loop:
sv_written = node.state_variables_written
for sv in sv_written:
# Ignore Array/Mapping/Struct types for now
if isinstance(sv.type, (ArrayType, MappingType)):
continue
ret.append(node)
break
for son in node.sons:
CostlyOperationsInLoop.costly_operations_in_loop(son, in_loop, visited, ret)
@staticmethod
def detect_costly_operations_in_loop(contract):
ret = []
for f in contract.functions + contract.modifiers:
if f.contract_declarer == contract and f.is_implemented:
CostlyOperationsInLoop.costly_operations_in_loop(f.entry_point, False, [], ret)
return ret
def _detect(self):
def _detect(self) -> List[Output]:
""""""
results = []
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
values = self.detect_costly_operations_in_loop(c)
values = detect_costly_operations_in_loop(c)
for node in values:
func = node.function
info = [func, " has costly operations inside a loop:\n"]

@ -0,0 +1,97 @@
from typing import List
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import LowLevelCall, InternalCall
from slither.core.declarations import Contract
from slither.utils.output import Output
def detect_delegatecall_in_loop(contract: Contract) -> List[Node]:
results: List[Node] = []
for f in contract.functions_entry_points:
if f.is_implemented and f.payable:
delegatecall_in_loop(f.entry_point, 0, [], results)
return results
def delegatecall_in_loop(
node: Node, in_loop_counter: int, visited: List[Node], results: List[Node]
) -> None:
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop_counter += 1
elif node.type == NodeType.ENDLOOP:
in_loop_counter -= 1
for ir in node.all_slithir_operations():
if (
in_loop_counter > 0
and isinstance(ir, (LowLevelCall))
and ir.function_name == "delegatecall"
):
results.append(ir.node)
if isinstance(ir, (InternalCall)):
delegatecall_in_loop(ir.function.entry_point, in_loop_counter, visited, results)
for son in node.sons:
delegatecall_in_loop(son, in_loop_counter, visited, results)
class DelegatecallInLoop(AbstractDetector):
"""
Detect the use of delegatecall inside a loop in a payable function
"""
ARGUMENT = "delegatecall-loop"
HELP = "Payable functions using `delegatecall` inside a loop"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop"
WIKI_TITLE = "Payable functions using `delegatecall` inside a loop"
WIKI_DESCRIPTION = "Detect the use of `delegatecall` inside a loop in a payable function."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract DelegatecallInLoop{
mapping (address => uint256) balances;
function bad(address[] memory receivers) public payable {
for (uint256 i = 0; i < receivers.length; i++) {
address(this).delegatecall(abi.encodeWithSignature("addBalance(address)", receivers[i]));
}
}
function addBalance(address a) public payable {
balances[a] += msg.value;
}
}
```
When calling `bad` the same `msg.value` amount will be accredited multiple times."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """
Carefully check that the function called by `delegatecall` is not payable/doesn't use `msg.value`.
"""
def _detect(self) -> List[Output]:
""""""
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
values = detect_delegatecall_in_loop(c)
for node in values:
func = node.function
info = [func, " has delegatecall inside a loop in a payable function: ", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -27,6 +27,8 @@ class DeprecatedStandards(AbstractDetector):
WIKI_TITLE = "Deprecated standards"
WIKI_DESCRIPTION = "Detect the usage of deprecated standards."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract ContractWithDeprecatedReferences {
@ -55,6 +57,7 @@ contract ContractWithDeprecatedReferences {
}
}
```"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Replace all uses of deprecated symbols."

@ -153,6 +153,8 @@ class DivideBeforeMultiply(AbstractDetector):
WIKI_TITLE = "Divide before multiply"
WIKI_DESCRIPTION = """Solidity integer division might truncate. As a result, performing multiplication before division can sometimes avoid loss of precision."""
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract A {
@ -164,6 +166,7 @@ contract A {
If `n` is greater than `oldSupply`, `coins` will be zero. For example, with `oldSupply = 5; n = 10, interest = 2`, coins will be zero.
If `(oldSupply * interest / n)` was used, `coins` would have been `1`.
In general, it's usually a good idea to re-arrange arithmetic to perform multiplication before division, unless the limit of a smaller type makes this dangerous."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """Consider ordering multiplication before division."""

@ -5,13 +5,14 @@
from slither.analyses.data_dependency.data_dependency import is_dependent_ssa
from slither.core.declarations import Function
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import (
Assignment,
Balance,
Binary,
BinaryType,
HighLevelCall,
SolidityCall,
)
from slither.core.solidity_types import MappingType, ElementaryType
@ -20,6 +21,7 @@ from slither.core.variables.state_variable import StateVariable
from slither.core.declarations.solidity_variables import (
SolidityVariable,
SolidityVariableComposed,
SolidityFunction,
)
@ -35,6 +37,8 @@ class IncorrectStrictEquality(AbstractDetector):
WIKI_TITLE = "Dangerous strict equalities"
WIKI_DESCRIPTION = "Use of strict equalities that can be easily manipulated by an attacker."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Crowdsale{
@ -44,6 +48,7 @@ contract Crowdsale{
```
`Crowdsale` relies on `fund_reached` to know when to stop the sale of tokens.
`Crowdsale` reaches 100 Ether. Bob sends 0.1 Ether. As a result, `fund_reached` is always false and the `crowdsale` never ends."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"""Don't use strict equality to determine if an account has enough Ether or tokens."""
@ -74,7 +79,9 @@ contract Crowdsale{
for func in functions:
for node in func.nodes:
for ir in node.irs_ssa:
if isinstance(ir, Balance):
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"balance(address)"
):
taints.append(ir.lvalue)
if isinstance(ir, HighLevelCall):
# print(ir.function.full_name)
@ -99,10 +106,13 @@ contract Crowdsale{
# Retrieve all tainted (node, function) pairs
def tainted_equality_nodes(self, funcs, taints):
results = dict()
results = {}
taints += self.sources_taint
for func in funcs:
# Disable the detector on top level function until we have good taint on those
if isinstance(func, FunctionTopLevel):
continue
for node in func.nodes:
for ir in node.irs_ssa:

@ -22,6 +22,8 @@ class MappingDeletionDetection(AbstractDetector):
WIKI_TITLE = "Deletion on mapping containing a structure"
WIKI_DESCRIPTION = "A deletion in a structure containing a mapping will not delete the mapping (see the [Solidity documentation](https://solidity.readthedocs.io/en/latest/types.html##delete)). The remaining data may be used to compromise the contract."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
struct BalancesStruct{
@ -36,6 +38,7 @@ class MappingDeletionDetection(AbstractDetector):
```
`remove` deletes an item of `stackBalance`.
The mapping `balances` is never deleted, so `remove` does not work as intended."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Use a lock mechanism instead of a deletion to disable structure containing a mapping."

@ -0,0 +1,89 @@
from typing import List
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import InternalCall
from slither.core.declarations import SolidityVariableComposed, Contract
from slither.utils.output import Output
def detect_msg_value_in_loop(contract: Contract) -> List[Node]:
results: List[Node] = []
for f in contract.functions_entry_points:
if f.is_implemented and f.payable:
msg_value_in_loop(f.entry_point, 0, [], results)
return results
def msg_value_in_loop(
node: Node, in_loop_counter: int, visited: List[Node], results: List[Node]
) -> None:
if node in visited:
return
# shared visited
visited.append(node)
if node.type == NodeType.STARTLOOP:
in_loop_counter += 1
elif node.type == NodeType.ENDLOOP:
in_loop_counter -= 1
for ir in node.all_slithir_operations():
if in_loop_counter > 0 and SolidityVariableComposed("msg.value") in ir.read:
results.append(ir.node)
if isinstance(ir, (InternalCall)):
msg_value_in_loop(ir.function.entry_point, in_loop_counter, visited, results)
for son in node.sons:
msg_value_in_loop(son, in_loop_counter, visited, results)
class MsgValueInLoop(AbstractDetector):
"""
Detect the use of msg.value inside a loop
"""
ARGUMENT = "msg-value-loop"
HELP = "msg.value inside a loop"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop"
WIKI_TITLE = "`msg.value` inside a loop"
WIKI_DESCRIPTION = "Detect the use of `msg.value` inside a loop."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract MsgValueInLoop{
mapping (address => uint256) balances;
function bad(address[] memory receivers) public payable {
for (uint256 i=0; i < receivers.length; i++) {
balances[receivers[i]] += msg.value;
}
}
}
```
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """
Track msg.value through a local variable and decrease its amount on every iteration/usage.
"""
def _detect(self) -> List[Output]:
""""""
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
values = detect_msg_value_in_loop(c)
for node in values:
func = node.function
info = [func, " use msg.value in a loop: ", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -22,6 +22,8 @@ class RedundantStatements(AbstractDetector):
WIKI_TITLE = "Redundant Statements"
WIKI_DESCRIPTION = "Detect the usage of redundant statements that have no effect."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract RedundantStatementsContract {
@ -41,6 +43,7 @@ contract RedundantStatementsContract {
}
```
Each commented line references types/identifiers, but performs no action with them, so no code will be generated for such statements and they can be removed."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Remove redundant statements if they congest code but offer no value."

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

Loading…
Cancel
Save