Merge branch 'dev' into dev-baseline-tests

pull/858/head
Josselin Feist 2 years ago
commit 4214b932ad
  1. 48
      .github/ISSUE_TEMPLATE/bug_report.yml
  2. 17
      .github/ISSUE_TEMPLATE/feature_request.yml
  3. 42
      .github/workflows/IR.yml
  4. 14
      .github/workflows/black.yml
  5. 76
      .github/workflows/ci.yml
  6. 20
      .github/workflows/detectors.yml
  7. 22
      .github/workflows/features.yml
  8. 14
      .github/workflows/linter.yml
  9. 28
      .github/workflows/parser.yml
  10. 36
      .github/workflows/pip-audit.yml
  11. 14
      .github/workflows/pylint.yml
  12. 50
      .github/workflows/read_storage.yml
  13. 27
      CONTRIBUTING.md
  14. 11
      Dockerfile
  15. 39
      README.md
  16. 4
      examples/scripts/convert_to_ir.py
  17. 72
      examples/scripts/data_dependency.py
  18. 4
      examples/scripts/export_dominator_tree_to_dot.py
  19. 4
      examples/scripts/export_to_dot.py
  20. 2
      examples/scripts/functions_called.py
  21. 2
      examples/scripts/functions_writing.py
  22. 6
      examples/scripts/slithIR.py
  23. 21
      examples/scripts/taint_mapping.py
  24. 6
      examples/scripts/variable_in_condition.py
  25. 2
      plugin_example/setup.py
  26. 3
      pyproject.toml
  27. 13
      scripts/ci_test_dapp.sh
  28. 2
      scripts/ci_test_kspec.sh
  29. 4
      scripts/ci_test_simil.sh
  30. 2
      scripts/ci_test_truffle.sh
  31. 22
      setup.py
  32. 52
      slither/__main__.py
  33. 88
      slither/analyses/data_dependency/data_dependency.py
  34. 6
      slither/core/cfg/node.py
  35. 2
      slither/core/children/child_node.py
  36. 2
      slither/core/compilation_unit.py
  37. 60
      slither/core/declarations/contract.py
  38. 19
      slither/core/declarations/custom_error.py
  39. 11
      slither/core/declarations/enum.py
  40. 32
      slither/core/declarations/function.py
  41. 6
      slither/core/declarations/import_directive.py
  42. 5
      slither/core/declarations/solidity_variables.py
  43. 4
      slither/core/expressions/assignment_operation.py
  44. 20
      slither/core/expressions/binary_operation.py
  45. 2
      slither/core/expressions/expression.py
  46. 2
      slither/core/expressions/expression_typed.py
  47. 6
      slither/core/expressions/unary_operation.py
  48. 21
      slither/core/scope/scope.py
  49. 28
      slither/core/slither_core.py
  50. 2
      slither/core/solidity_types/__init__.py
  51. 10
      slither/core/solidity_types/array_type.py
  52. 20
      slither/core/solidity_types/elementary_type.py
  53. 10
      slither/core/solidity_types/function_type.py
  54. 2
      slither/core/solidity_types/mapping_type.py
  55. 41
      slither/core/solidity_types/type_alias.py
  56. 3
      slither/core/solidity_types/type_information.py
  57. 2
      slither/core/source_mapping/source_mapping.py
  58. 2
      slither/core/variables/local_variable.py
  59. 2
      slither/core/variables/state_variable.py
  60. 4
      slither/core/variables/top_level_variable.py
  61. 20
      slither/core/variables/variable.py
  62. 2
      slither/detectors/abstract_detector.py
  63. 7
      slither/detectors/all_detectors.py
  64. 4
      slither/detectors/assembly/shift_parameter_mixup.py
  65. 2
      slither/detectors/attributes/constant_pragma.py
  66. 2
      slither/detectors/attributes/incorrect_solc.py
  67. 2
      slither/detectors/attributes/unimplemented_interface.py
  68. 0
      slither/detectors/erc/erc20/__init__.py
  69. 95
      slither/detectors/erc/erc20/arbitrary_send_erc20.py
  70. 45
      slither/detectors/erc/erc20/arbitrary_send_erc20_no_permit.py
  71. 53
      slither/detectors/erc/erc20/arbitrary_send_erc20_permit.py
  72. 0
      slither/detectors/erc/erc20/incorrect_erc20_interface.py
  73. 144
      slither/detectors/functions/arbitrary_send.py
  74. 144
      slither/detectors/functions/arbitrary_send_eth.py
  75. 3
      slither/detectors/functions/dead_code.py
  76. 81
      slither/detectors/functions/protected_variable.py
  77. 14
      slither/detectors/naming_convention/naming_convention.py
  78. 11
      slither/detectors/reentrancy/reentrancy.py
  79. 4
      slither/detectors/shadowing/abstract.py
  80. 30
      slither/detectors/shadowing/common.py
  81. 8
      slither/detectors/shadowing/state.py
  82. 4
      slither/detectors/source/rtlo.py
  83. 8
      slither/detectors/statements/calls_in_loop.py
  84. 8
      slither/detectors/statements/costly_operations_in_loop.py
  85. 8
      slither/detectors/statements/delegatecall_in_loop.py
  86. 8
      slither/detectors/statements/msg_value_in_loop.py
  87. 12
      slither/detectors/statements/too_many_digits.py
  88. 2
      slither/detectors/statements/type_based_tautology.py
  89. 83
      slither/detectors/statements/unprotected_upgradeable.py
  90. 4
      slither/detectors/variables/similar_variables.py
  91. 6
      slither/detectors/variables/uninitialized_local_variables.py
  92. 2
      slither/detectors/variables/uninitialized_storage_variables.py
  93. 2
      slither/formatters/attributes/constant_pragma.py
  94. 3
      slither/formatters/attributes/incorrect_solc.py
  95. 6
      slither/formatters/naming_convention/naming_convention.py
  96. 22
      slither/printers/abstract_printer.py
  97. 1
      slither/printers/all_printers.py
  98. 2
      slither/printers/functions/authorization.py
  99. 8
      slither/printers/functions/cfg.py
  100. 25
      slither/printers/guidance/echidna.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,48 @@
---
body:
-
attributes:
value: |
Please check the issues tab to avoid duplicates.
Thanks for taking the time to fill out this bug report!
type: markdown
-
attributes:
label: "Describe the issue:"
id: what-happened
type: textarea
validations:
required: true
-
attributes:
description: "It can be a github repo, etherscan link, or code snippet."
label: "Code example to reproduce the issue:"
placeholder: "`contract A {}`\n"
id: reproduce
type: textarea
validations:
required: true
-
attributes:
description: |
What version of slither are you running?
Run `slither --version`
label: "Version:"
id: version
type: textarea
validations:
required: true
-
attributes:
description: |
Please copy and paste any relevant log output. This
will be automatically formatted into code, so no need for backticks.
render: shell
label: "Relevant log output:"
id: logs
type: textarea
description: "File a bug report"
labels:
- bug-candidate
name: "Bug Report"
title: "[Bug-Candidate]: "

@ -0,0 +1,17 @@
---
name: Feature request
description: Suggest a feature
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Please check the issues tab to avoid duplicates.
Thanks for providing feedback on Slither!
- type: textarea
attributes:
label: Describe the desired feature
description: Explain what the feature solves/ improves.
validations:
required: true

@ -0,0 +1,42 @@
---
name: IR 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: IR tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
solc-select install all
solc-select use 0.8.11
- name: Test with pytest
run: |
pytest tests/test_ssa_generation.py

@ -11,7 +11,7 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Black
uses: docker://github/super-linter:v4
uses: github/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems
@ -46,3 +43,4 @@ jobs:
# Run only black
VALIDATE_PYTHON_BLACK: true
PYTHON_BLACK_CONFIG_FILE: pyproject.toml
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -1,9 +1,9 @@
---
name: CI
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
shell: bash
on:
push:
@ -13,46 +13,66 @@ on:
pull_request:
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
tests:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["cli",
"dapp",
"data_dependency",
"embark",
# "embark",
"erc",
"etherlime",
# "etherlime",
# "etherscan"
"find_paths",
"flat",
"kspec",
"printers",
# "prop"
"simil",
"slither_config",
"truffle",
"upgradability",
# "prop",
"flat"]
"upgradability"]
exclude:
# Requires nix
- os: windows-2022
type: dapp
# Requires nvm
- os: windows-2022
type: truffle
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.6
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Install dependencies
run: |
python setup.py install
# Used by ci_test.sh
pip install deepdiff
- uses: actions/checkout@v1
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
solc-select install all
solc-select use 0.5.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install solc-select
solc-select install all
solc-select use 0.5.1
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v16
- name: Run Tests
env:
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: |
bash "scripts/ci_test_${TEST_TYPE}.sh"
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v10
with:
name: dapp
- name: Run Tests
env:
PYTHONUTF8: 1
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: |
bash "scripts/ci_test_${TEST_TYPE}.sh"

@ -11,32 +11,32 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
name: Detectors tests
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest
pip install ".[dev]"
pip install solc-select
solc-select install all
solc-select use 0.7.3
- name: Test with pytest
run: |
pytest tests/test_detectors.py

@ -11,29 +11,30 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
name: Features tests
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest
pip install ".[dev]"
pip install solc-select
solc-select install all
solc-select use 0.8.0
@ -44,4 +45,5 @@ jobs:
- name: Test with pytest
run: |
pytest tests/test_features.py
pytest tests/test_constant_folding_unary.py
pytest tests/slithir/test_ternary_expressions.py

@ -11,7 +11,7 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Lint everything else
uses: docker://github/super-linter:v4
uses: github/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems
@ -58,3 +55,4 @@ jobs:
VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false
SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -11,37 +11,35 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
name: Parser tests
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest
git clone https://github.com/crytic/solc-select.git
cd solc-select
git checkout 119dd05f58341811cb02b546f25269a7e8a10875
python setup.py install
pip install ".[dev]"
- name: Install solc
run: |
solc-select install all
solc-select use 0.8.0
cd ..
- name: Test with pytest
run: |
pytest tests/test_ast_parsing.py

@ -1,3 +1,4 @@
---
name: pip-audit
on:
@ -10,18 +11,25 @@ on:
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
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install Slither
run: |
python -m venv /tmp/pip-audit-env
source /tmp/pip-audit-env/bin/activate
python -m pip install --upgrade pip
python -m pip install .
- name: Run pip-audit
uses: trailofbits/gh-action-pip-audit@v0.0.4
with:
virtual-environment: /tmp/pip-audit-env

@ -11,7 +11,7 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Pylint
uses: docker://github/super-linter:v4
uses: github/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems
@ -47,3 +44,4 @@ jobs:
VALIDATE_PYTHON: true
VALIDATE_PYTHON_PYLINT: true
PYTHON_PYLINT_CONFIG_FILE: pyproject.toml
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -0,0 +1,50 @@
---
name: Test slither-read-storage
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: Test slither-read-storage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install ganache
run: npm install --global ganache
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install python dependencies
run: |
pip install ".[dev]"
pip install web3
solc-select install 0.8.1
solc-select install 0.8.10
solc-select use 0.8.1
- name: Run slither-read-storage
run: |
pytest tests/test_read_storage.py
- name: Run storage layout tests
run: |
pytest tests/test_storage_layout.py

@ -20,13 +20,28 @@ Some pull request guidelines:
- Fill out the pull request description with a summary of what your patch does, key changes that have been made, and any further points of discussion, if applicable.
- Title your pull request with a brief description of what it's changing. "Fixes #123" is a good comment to add to the description, but makes for an unclear title on its own.
## Directory Structure
Below is a rough outline of slither's design:
```text
.
├── analyses # Provides additional info such as data dependency
├── core # Ties everything together
├── detectors # Rules that define and identify issues
├── slither.py # Main entry point
├── slithir # Contains the semantics of slither's intermediate representation
├── solc_parsing # Responsible for parsing the solc AST
├── tools # Miscellaneous tools built on top of slither
├── visitors # Parses expressions and converts to slithir
└── ...
```
A code walkthrough is available [here](https://www.youtube.com/watch?v=EUl3UlYSluU).
## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).
To run the unit tests, you need
- `deepdiff` installed (`pip install deepdiff`).
- `pycov` installed (`pip install pytest-cov`).
- [`solc-select`](https://github.com/crytic/solc-select) installed.
To run the unit tests, you need to clone this repository and run `pip install ".[dev]"`.
### Linters
@ -37,7 +52,8 @@ 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.12.2` black `21.10b0`.
We use pylint `2.13.4`, black `22.3.0`.
### Detectors tests
For each new detector, at least one regression tests must be present.
@ -45,6 +61,7 @@ For each new detector, at least one regression tests must be present.
- Create a test in `tests`
- Update `ALL_TEST` in `tests/test_detectors.py`
- Run `python ./tests/test_detectors.py --generate`. This will generate the json artifacts in `tests/expected_json`. Add the generated files to git.
- If updating an existing detector, identify the respective json artifacts and then delete them, or run `python ./tests/test_detectors.py --overwrite` instead.
- Run `pytest ./tests/test_detectors.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`

@ -1,4 +1,4 @@
FROM ubuntu:bionic
FROM ubuntu:focal
LABEL name=slither
LABEL src="https://github.com/trailofbits/slither"
@ -6,11 +6,12 @@ LABEL creator=trailofbits
LABEL dockerfile_maintenance=trailofbits
LABEL desc="Static Analyzer for Solidity"
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y git python3 python3-setuptools wget software-properties-common
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get upgrade -yq \
&& apt-get install -yq gcc git python3 python3-dev python3-setuptools wget software-properties-common
RUN wget https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux \
RUN wget -q https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux \
&& chmod +x solc-static-linux \
&& mv solc-static-linux /usr/bin/solc

@ -13,6 +13,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s
- [Tools](#tools)
- [How to Install](#how-to-install)
- [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications)
## Features
@ -40,9 +41,12 @@ Run Slither on a single file:
slither tests/uninitialized.sol
```
For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
### Integration
- For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action).
- To generate a Markdown report, use `slither [target] --checklist`.
- To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`)
Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc.
Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc. For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
### Detectors
@ -51,7 +55,7 @@ Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | ---
1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High
2 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#shift-parameter-mixup) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High
4 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
5 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
6 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
@ -121,10 +125,12 @@ Num | Detector | What it Detects | Impact | Confidence
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
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
75 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
76 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
77 | `arbitrary-send-erc20` | [Detect when `msg.sender` is not used as `from` in transferFrom](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20)
78 | `arbitrary-send-erc20-permit` | [Detect when `msg.sender` is not used as `from` in transferFrom in conjuction with permit](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit)
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
@ -143,6 +149,7 @@ For more information, see
- `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
- `function-summary`: [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary)
- `vars-and-auth`: [Print the state variables written and the authorization of the functions](https://github.com/crytic/slither/wiki/Printer-documentation#variables-written-and-authorization)
- `when-not-paused`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
To run a printer, use `--print` and a comma-separated list of printers.
@ -155,6 +162,7 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d
- `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 patch generation](https://github.com/crytic/slither/wiki/Slither-format)
- `slither-read-storage`: [Read storage values from contracts](./slither/tools/read_storage/README.md)
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
@ -162,7 +170,7 @@ See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documen
## How to install
Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
Slither requires Python 3.8+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
### Using Pip
@ -181,7 +189,7 @@ We recommend using a Python virtual environment, as detailed in the [Developer I
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
@ -205,11 +213,23 @@ Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
## FAQ
How do I exclude mocks or tests?
- View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
How do I fix "unknown file" or compilation issues?
- Because slither requires the solc AST, it must have all dependencies available.
If a contract has dependencies, `slither contract.sol` will fail.
Instead, use `slither .` in the parent directory of `contracts/` (you should see `contracts/` when you run `ls`).
If you have a `node_modules/` folder, it must be in the same directory as `contracts/`. To verify that this issue is related to slither,
run the compilation command for the framework you are using e.g `npx hardhat compile`. That must work successfully;
otherwise, slither's compilation engine, crytic-compile, cannot generate the AST.
## License
Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms.
## Publications
### Trail of Bits publication
@ -225,5 +245,8 @@ Title | Usage | Authors | Venue
[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)
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
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/).
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/).

@ -21,9 +21,9 @@ nodes = test.nodes
for node in nodes:
if node.expression:
print("Expression:\n\t{}".format(node.expression))
print(f"Expression:\n\t{node.expression}")
irs = convert_expression(node.expression, node)
print("IR expressions:")
for ir in irs:
print("\t{}".format(ir))
print(f"\t{ir}")
print()

@ -19,21 +19,13 @@ contract = contracts[0]
destination = contract.get_state_variable_from_name("destination")
source = contract.get_state_variable_from_name("source")
print(
"{} is dependent of {}: {}".format(
source, destination, is_dependent(source, destination, contract)
)
)
print(f"{source} is dependent of {destination}: {is_dependent(source, destination, contract)}")
assert not is_dependent(source, destination, contract)
print(
"{} is dependent of {}: {}".format(
destination, source, is_dependent(destination, source, contract)
)
)
print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
assert is_dependent(destination, source, contract)
print("{} is tainted {}".format(source, is_tainted(source, contract)))
print(f"{source} is tainted {is_tainted(source, contract)}")
assert not is_tainted(source, contract)
print("{} is tainted {}".format(destination, is_tainted(destination, contract)))
print(f"{destination} is tainted {is_tainted(destination, contract)}")
assert is_tainted(destination, contract)
contracts = slither.get_contract_from_name("Reference")
@ -45,32 +37,20 @@ source = contract.get_state_variable_from_name("source")
assert source
print("Reference contract")
print(
"{} is dependent of {}: {}".format(
source, destination, is_dependent(source, destination, contract)
)
)
print(f"{source} is dependent of {destination}: {is_dependent(source, destination, contract)}")
assert not is_dependent(source, destination, contract)
print(
"{} is dependent of {}: {}".format(
destination, source, is_dependent(destination, source, contract)
)
)
print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
assert is_dependent(destination, source, contract)
print("{} is tainted {}".format(source, is_tainted(source, contract)))
print(f"{source} is tainted {is_tainted(source, contract)}")
assert not is_tainted(source, contract)
print("{} is tainted {}".format(destination, is_tainted(destination, contract)))
print(f"{destination} is tainted {is_tainted(destination, contract)}")
assert is_tainted(destination, contract)
destination_indirect_1 = contract.get_state_variable_from_name("destination_indirect_1")
print(
"{} is tainted {}".format(destination_indirect_1, is_tainted(destination_indirect_1, contract))
)
print(f"{destination_indirect_1} is tainted {is_tainted(destination_indirect_1, contract)}")
assert is_tainted(destination_indirect_1, contract)
destination_indirect_2 = contract.get_state_variable_from_name("destination_indirect_2")
print(
"{} is tainted {}".format(destination_indirect_2, is_tainted(destination_indirect_2, contract))
)
print(f"{destination_indirect_2} is tainted {is_tainted(destination_indirect_2, contract)}")
assert is_tainted(destination_indirect_2, contract)
print("SolidityVar contract")
@ -83,13 +63,9 @@ assert addr_1
addr_2 = contract.get_state_variable_from_name("addr_2")
assert addr_2
msgsender = SolidityVariableComposed("msg.sender")
print(
"{} is dependent of {}: {}".format(addr_1, msgsender, is_dependent(addr_1, msgsender, contract))
)
print(f"{addr_1} is dependent of {msgsender}: {is_dependent(addr_1, msgsender, contract)}")
assert is_dependent(addr_1, msgsender, contract)
print(
"{} is dependent of {}: {}".format(addr_2, msgsender, is_dependent(addr_2, msgsender, contract))
)
print(f"{addr_2} is dependent of {msgsender}: {is_dependent(addr_2, msgsender, contract)}")
assert not is_dependent(addr_2, msgsender, contract)
@ -102,11 +78,7 @@ assert destination
source = contract.get_state_variable_from_name("source")
assert source
print(
"{} is dependent of {}: {}".format(
destination, source, is_dependent(destination, source, contract)
)
)
print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
assert is_dependent(destination, source, contract)
print("Base Derived contract")
@ -117,16 +89,10 @@ contract_derived = slither.get_contract_from_name("Derived")[0]
destination = contract.get_state_variable_from_name("destination")
source = contract.get_state_variable_from_name("source")
print(
"{} is dependent of {}: {} (base)".format(
destination, source, is_dependent(destination, source, contract)
)
)
print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
assert not is_dependent(destination, source, contract)
print(
"{} is dependent of {}: {} (derived)".format(
destination, source, is_dependent(destination, source, contract_derived)
)
f"{destination} is dependent of {source}: {is_dependent(destination, source, contract_derived)}"
)
assert is_dependent(destination, source, contract_derived)
@ -147,12 +113,10 @@ user_input = f.parameters[0]
f2 = contract.get_function_from_signature("f2(uint256,uint256)")
print(
"{} is dependent of {}: {} (base)".format(
var_dependant, user_input, is_dependent(var_dependant, user_input, contract)
)
f"{var_dependant} is dependent of {user_input}: {is_dependent(var_dependant, user_input, contract)} (base)"
)
assert is_dependent(var_dependant, user_input, contract)
print("{} is tainted: {}".format(var_tainted, is_tainted(var_tainted, contract)))
print(f"{var_tainted} is tainted: {is_tainted(var_tainted, contract)}")
assert is_tainted(var_tainted, contract)
print("{} is tainted: {}".format(var_not_tainted, is_tainted(var_not_tainted, contract)))
print(f"{var_not_tainted} is tainted: {is_tainted(var_not_tainted, contract)}")
assert not is_tainted(var_not_tainted, contract)

@ -11,6 +11,6 @@ slither = Slither(sys.argv[1])
for contract in slither.contracts:
for function in list(contract.functions) + list(contract.modifiers):
filename = "{}-{}-{}_dom.dot".format(sys.argv[1], contract.name, function.full_name)
print("Export {}".format(filename))
filename = f"{sys.argv[1]}-{contract.name}-{function.full_name}_dom.dot"
print(f"Export {filename}")
function.dominator_tree_to_dot(filename)

@ -11,6 +11,6 @@ slither = Slither(sys.argv[1])
for contract in slither.contracts:
for function in contract.functions + contract.modifiers:
filename = "{}-{}-{}.dot".format(sys.argv[1], contract.name, function.full_name)
print("Export {}".format(filename))
filename = f"{sys.argv[1]}-{contract.name}-{function.full_name}.dot"
print(f"Export {filename}")
function.slithir_cfg_to_dot(filename)

@ -22,4 +22,4 @@ all_calls = entry_point.all_internal_calls()
all_calls_formated = [f.canonical_name for f in all_calls]
# Print the result
print("From entry_point the functions reached are {}".format(all_calls_formated))
print(f"From entry_point the functions reached are {all_calls_formated}")

@ -19,4 +19,4 @@ var_a = contract.get_state_variable_from_name("a")
functions_writing_a = contract.get_functions_writing_to_variable(var_a)
# Print the result
print('The function writing "a" are {}'.format([f.name for f in functions_writing_a]))
print(f'The function writing "a" are {[f.name for f in functions_writing_a]}')

@ -17,7 +17,7 @@ for contract in slither.contracts:
# Dont explore inherited functions
if function.contract_declarer == contract:
print("Function: {}".format(function.name))
print(f"Function: {function.name}")
# Iterate over the nodes of the function
for node in function.nodes:
@ -26,7 +26,7 @@ for contract in slither.contracts:
# And the SlithIR operations
if node.expression:
print("\tSolidity expression: {}".format(node.expression))
print(f"\tSolidity expression: {node.expression}")
print("\tSlithIR:")
for ir in node.irs:
print("\t\t\t{}".format(ir))
print(f"\t\t\t{ir}")

@ -10,6 +10,9 @@ from slither.slithir.variables.temporary import TemporaryVariable
def visit_node(node, visited):
if node is None:
return
if node in visited:
return
@ -26,9 +29,9 @@ def visit_node(node, visited):
else:
read = ir.read
print(ir)
print("Refs {}".format(refs))
print("Read {}".format([str(x) for x in ir.read]))
print("Before {}".format([str(x) for x in taints]))
print(f"Refs {refs}")
print(f"Read {[str(x) for x in ir.read]}")
print(f"Before {[str(x) for x in taints]}")
if any(var_read in taints for var_read in read):
taints += [ir.lvalue]
lvalue = ir.lvalue
@ -36,7 +39,7 @@ def visit_node(node, visited):
taints += [refs[lvalue]]
lvalue = refs[lvalue]
print("After {}".format([str(x) for x in taints]))
print(f"After {[str(x) for x in taints]}")
print()
taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))]
@ -52,7 +55,7 @@ def check_call(func, taints):
for ir in node.irs:
if isinstance(ir, HighLevelCall):
if ir.destination in taints:
print("Call to tainted address found in {}".format(function.name))
print(f"Call to tainted address found in {function.name}")
if __name__ == "__main__":
@ -74,16 +77,14 @@ if __name__ == "__main__":
prev_taints = slither.context[KEY]
for contract in slither.contracts:
for function in contract.functions:
print("Function {}".format(function.name))
print(f"Function {function.name}")
slither.context[KEY] = list(set(slither.context[KEY] + function.parameters))
visit_node(function.entry_point, [])
print("All variables tainted : {}".format([str(v) for v in slither.context[KEY]]))
print(f"All variables tainted : {[str(v) for v in slither.context[KEY]]}")
for function in contract.functions:
check_call(function, slither.context[KEY])
print(
"All state variables tainted : {}".format(
[str(v) for v in prev_taints if isinstance(v, StateVariable)]
)
f"All state variables tainted : {[str(v) for v in prev_taints if isinstance(v, StateVariable)]}"
)

@ -25,8 +25,4 @@ function_using_a_as_condition = [
]
# Print the result
print(
'The function using "a" in condition are {}'.format(
[f.name for f in function_using_a_as_condition]
)
)
print(f'The function using "a" in condition are {[f.name for f in function_using_a_as_condition]}')

@ -7,7 +7,7 @@ setup(
author="Trail of Bits",
version="0.0",
packages=find_packages(),
python_requires=">=3.6",
python_requires=">=3.8",
install_requires=["slither-analyzer==0.1"],
entry_points={
"slither_analyzer.plugin": "slither my-plugin=slither_my_plugin:make_plugin",

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

@ -8,23 +8,20 @@ cd test_dapp || exit 255
git config --global user.email "ci@trailofbits.com"
git config --global user.name "CI User"
curl https://nixos.org/nix/install | sh
# shellcheck disable=SC1090,SC1091
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
nix-env -iA nixpkgs.cachix
cachix use dapp
which nix-env || exit 255
git clone --recursive https://github.com/dapphub/dapptools "$HOME/.dapp/dapptools"
nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign
dapp init
slither .
slither . --detect external-function
if [ $? -eq 21 ]
# TODO: make more elaborate test
if [ $? -eq 4 ]
then
exit 0
fi
echo "Truffle test failed"
echo "Dapp test failed"
exit 255

@ -7,7 +7,7 @@ slither-check-kspec "$DIR_TESTS/safeAdd/safeAdd.sol" "$DIR_TESTS/safeAdd/spec.md
DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 1 failed"
echo "slither-check-kspec 1 failed"
cat test_1.txt
echo ""
cat "$DIR_TESTS/test_1.txt"

@ -2,8 +2,8 @@
### Install requisites
pip3.6 install pybind11
pip3.6 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip
pip3.8 install pybind11
pip3.8 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip
### Test slither-simil

@ -15,7 +15,7 @@ npm install -g truffle
truffle unbox metacoin
slither .
if [ $? -eq 9 ]
if [ $? -eq 6 ]
then
exit 0
fi

@ -8,16 +8,27 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.8.2",
version="0.8.3",
packages=find_packages(),
python_requires=">=3.6",
python_requires=">=3.8",
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
"crytic-compile>=0.2.2",
# "crytic-compile",
# "crytic-compile>=0.2.3",
"crytic-compile",
],
# dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
extras_require={
"dev": [
"black==22.3.0",
"pylint==2.13.4",
"pytest",
"pytest-cov",
"deepdiff",
"numpy",
"solc-select>=v1.0.0b1",
]
},
dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0",
long_description=long_description,
entry_points={
@ -32,6 +43,7 @@ setup(
"slither-check-kspec = slither.tools.kspec_coverage.__main__:main",
"slither-prop = slither.tools.properties.__main__:main",
"slither-mutate = slither.tools.mutator.__main__:main",
"slither-read-storage = slither.tools.read_storage.__main__:main",
]
},
)

@ -64,6 +64,9 @@ def process_single(target, args, detector_classes, printer_classes):
ast = "--ast-compact-json"
if args.legacy_ast:
ast = "--ast-json"
if args.checklist:
args.show_ignored_findings = True
slither = Slither(target, ast_format=ast, **vars(args))
return _process(slither, detector_classes, printer_classes)
@ -205,7 +208,7 @@ def choose_detectors(args, all_detector_classes):
if detector in detectors:
detectors_to_run.append(detectors[detector])
else:
raise Exception("Error: {} is not a detector".format(detector))
raise Exception(f"Error: {detector} is not a detector")
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
@ -296,6 +299,9 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_detector = parser.add_argument_group("Detectors")
group_printer = parser.add_argument_group("Printers")
group_checklist = parser.add_argument_group(
"Checklist (consider using https://github.com/crytic/slither-action)"
)
group_misc = parser.add_argument_group("Additional options")
group_detector.add_argument(
@ -309,7 +315,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, "
help="Comma-separated list of contract information printers, "
f"available printers: {', '.join(d.ARGUMENT for d in printer_classes)}",
action="store",
dest="printers_to_run",
@ -389,6 +395,28 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["show_ignored_findings"],
)
group_checklist.add_argument(
"--checklist",
help="Generate a markdown page with the detector results",
action="store_true",
default=False,
)
group_checklist.add_argument(
"--checklist-limit",
help="Limite the number of results per detector in the markdown file",
action="store",
default="",
)
group_checklist.add_argument(
"--markdown-root",
type=check_and_sanitize_markdown_root,
help="URL for markdown generation",
action="store",
default="",
)
group_misc.add_argument(
"--json",
help='Export the results as a JSON file ("--json -" to export to stdout)',
@ -426,14 +454,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["zip_type"],
)
group_misc.add_argument(
"--markdown-root",
type=check_and_sanitize_markdown_root,
help="URL for markdown generation",
action="store",
default="",
)
group_misc.add_argument(
"--disable-color",
help="Disable output colorization",
@ -462,7 +482,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
help="Provide a config file (default: slither.config.json)",
action="store",
dest="config_file",
default="slither.config.json",
default=None,
)
group_misc.add_argument(
@ -484,12 +504,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False)
group_misc.add_argument(
"--checklist", help=argparse.SUPPRESS, action="store_true", default=False
)
group_misc.add_argument("--checklist-limit", help=argparse.SUPPRESS, action="store", default="")
parser.add_argument(
"--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False
)
@ -645,7 +659,7 @@ def main_impl(all_detector_classes, all_printer_classes):
cp.enable()
# Set colorization option
set_colorization_enabled(not args.disable_color)
set_colorization_enabled(False if args.disable_color else sys.stdout.isatty())
# Define some variables for potential JSON output
json_results = {}
@ -773,7 +787,7 @@ def main_impl(all_detector_classes, all_printer_classes):
if args.checklist:
output_results_to_markdown(results_detectors, args.checklist_limit)
# Dont print the number of result for printers
# Don't print the number of result for printers
if number_contracts == 0:
logger.warning(red("No contract was analyzed"))
if printer_classes:

@ -14,8 +14,9 @@ from slither.core.declarations import (
Structure,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.core.variables.variable import Variable
from slither.slithir.operations import Index, OperationWithLValue, InternalCall
from slither.slithir.operations import Index, OperationWithLValue, InternalCall, Operation
from slither.slithir.variables import (
Constant,
LocalIRVariable,
@ -38,10 +39,14 @@ if TYPE_CHECKING:
###################################################################################
Variable_types = Union[Variable, SolidityVariable]
Context_types = Union[Contract, Function]
def is_dependent(
variable: Variable,
source: Variable,
context: Union[Contract, Function],
variable: Variable_types,
source: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> bool:
"""
@ -69,9 +74,9 @@ def is_dependent(
def is_dependent_ssa(
variable: Variable,
source: Variable,
context: Union[Contract, Function],
variable: Variable_types,
source: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> bool:
"""
@ -105,7 +110,12 @@ GENERIC_TAINT = {
}
def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=False):
def is_tainted(
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
) -> bool:
"""
Args:
variable
@ -127,7 +137,12 @@ def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=F
)
def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_taint=False):
def is_tainted_ssa(
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
):
"""
Args:
variable
@ -150,8 +165,8 @@ def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_tai
def get_dependencies(
variable: Variable,
context: Union[Contract, Function],
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> Set[Variable]:
"""
@ -170,7 +185,7 @@ def get_dependencies(
def get_all_dependencies(
context: Union[Contract, Function], only_unprotected: bool = False
context: Context_types, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
@ -187,8 +202,8 @@ def get_all_dependencies(
def get_dependencies_ssa(
variable: Variable,
context: Union[Contract, Function],
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> Set[Variable]:
"""
@ -207,7 +222,7 @@ def get_dependencies_ssa(
def get_all_dependencies_ssa(
context: Union[Contract, Function], only_unprotected: bool = False
context: Context_types, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
@ -249,19 +264,19 @@ KEY_INPUT_SSA = "DATA_DEPENDENCY_INPUT_SSA"
###################################################################################
def pprint_dependency(context):
def pprint_dependency(caller_context: Context_types) -> None:
print("#### SSA ####")
context = context.context
context = caller_context.context
for k, values in context[KEY_SSA].items():
print("{} ({}):".format(k, id(k)))
print(f"{k} ({id(k)}):")
for v in values:
print("\t- {}".format(v))
print(f"\t- {v}")
print("#### NON SSA ####")
for k, values in context[KEY_NON_SSA].items():
print("{} ({}):".format(k, hex(id(k))))
print(f"{k} ({hex(id(k))}):")
for v in values:
print("\t- {} ({})".format(v, hex(id(v))))
print(f"\t- {v} ({hex(id(v))})")
# endregion
@ -272,7 +287,7 @@ def pprint_dependency(context):
###################################################################################
def compute_dependency(compilation_unit: "SlitherCompilationUnit"):
def compute_dependency(compilation_unit: "SlitherCompilationUnit") -> None:
compilation_unit.context[KEY_INPUT] = set()
compilation_unit.context[KEY_INPUT_SSA] = set()
@ -280,14 +295,16 @@ def compute_dependency(compilation_unit: "SlitherCompilationUnit"):
compute_dependency_contract(contract, compilation_unit)
def compute_dependency_contract(contract, compilation_unit: "SlitherCompilationUnit"):
def compute_dependency_contract(
contract: Contract, compilation_unit: "SlitherCompilationUnit"
) -> None:
if KEY_SSA in contract.context:
return
contract.context[KEY_SSA] = {}
contract.context[KEY_SSA_UNPROTECTED] = {}
for function in contract.functions + contract.modifiers:
for function in contract.functions + list(contract.modifiers):
compute_dependency_function(function)
propagate_function(contract, function, KEY_SSA, KEY_NON_SSA)
@ -302,7 +319,9 @@ def compute_dependency_contract(contract, compilation_unit: "SlitherCompilationU
propagate_contract(contract, KEY_SSA_UNPROTECTED, KEY_NON_SSA_UNPROTECTED)
def propagate_function(contract, function, context_key, context_key_non_ssa):
def propagate_function(
contract: Contract, function: Function, context_key: str, context_key_non_ssa: str
) -> None:
transitive_close_dependencies(function, context_key, context_key_non_ssa)
# Propage data dependency
data_depencencies = function.context[context_key]
@ -313,7 +332,9 @@ def propagate_function(contract, function, context_key, context_key_non_ssa):
contract.context[context_key][key].union(values)
def transitive_close_dependencies(context, context_key, context_key_non_ssa):
def transitive_close_dependencies(
context: Context_types, context_key: str, context_key_non_ssa: str
) -> None:
# transitive closure
changed = True
keys = context.context[context_key].keys()
@ -336,11 +357,11 @@ def transitive_close_dependencies(context, context_key, context_key_non_ssa):
context.context[context_key_non_ssa] = convert_to_non_ssa(context.context[context_key])
def propagate_contract(contract, context_key, context_key_non_ssa):
def propagate_contract(contract: Contract, context_key: str, context_key_non_ssa: str) -> None:
transitive_close_dependencies(contract, context_key, context_key_non_ssa)
def add_dependency(lvalue, function, ir, is_protected):
def add_dependency(lvalue: Variable, function: Function, ir: Operation, is_protected: bool) -> None:
if not lvalue in function.context[KEY_SSA]:
function.context[KEY_SSA][lvalue] = set()
if not is_protected:
@ -361,7 +382,7 @@ def add_dependency(lvalue, function, ir, is_protected):
]
def compute_dependency_function(function):
def compute_dependency_function(function: Function) -> None:
if KEY_SSA in function.context:
return
@ -386,7 +407,7 @@ def compute_dependency_function(function):
)
def convert_variable_to_non_ssa(v):
def convert_variable_to_non_ssa(v: Variable_types) -> Variable_types:
if isinstance(
v,
(
@ -410,14 +431,17 @@ def convert_variable_to_non_ssa(v):
Function,
Type,
SolidityImportPlaceHolder,
TopLevelVariable,
),
)
return v
def convert_to_non_ssa(data_depencies):
def convert_to_non_ssa(
data_depencies: Dict[Variable_types, Set[Variable_types]]
) -> Dict[Variable_types, Set[Variable_types]]:
# Need to create new set() as its changed during iteration
ret = {}
ret: Dict[Variable_types, Set[Variable_types]] = {}
for (k, values) in data_depencies.items():
var = convert_variable_to_non_ssa(k)
if not var in ret:

@ -39,12 +39,11 @@ from slither.slithir.variables import (
TupleVariable,
)
from slither.all_exceptions import SlitherException
from slither.core.declarations import Contract
from slither.core.declarations import Contract, Function
from slither.core.expressions.expression import Expression
if TYPE_CHECKING:
from slither.core.declarations import Function
from slither.slithir.variables.variable import SlithIRVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.utils.type_helpers import (
@ -140,7 +139,7 @@ class NodeType(Enum):
return "END_LOOP"
if self == NodeType.OTHER_ENTRYPOINT:
return "OTHER_ENTRYPOINT"
return "Unknown type {}".format(hex(self.value))
return f"Unknown type {hex(self.value)}"
# endregion
@ -917,6 +916,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
)
elif isinstance(ir, LibraryCall):
assert isinstance(ir.destination, Contract)
assert isinstance(ir.function, Function)
self._high_level_calls.append((ir.destination, ir.function))
self._library_calls.append((ir.destination, ir.function))

@ -28,4 +28,4 @@ class ChildNode:
@property
def compilation_unit(self) -> "SlitherCompilationUnit":
return self.contract.compilation_unit
return self.node.compilation_unit

@ -251,7 +251,7 @@ class SlitherCompilationUnit(Context):
slot = 0
offset = 0
for var in contract.state_variables_ordered:
if var.is_constant:
if var.is_constant or var.is_immutable:
continue
size, new_slot = var.type.storage_size

@ -20,6 +20,10 @@ from slither.utils.erc import (
ERC1820_signatures,
ERC777_signatures,
ERC1155_signatures,
ERC2612_signatures,
ERC1363_signatures,
ERC4524_signatures,
ERC4626_signatures,
)
from slither.utils.tests_pattern import is_test_contract
@ -73,9 +77,10 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._custom_errors: Dict[str, "CustomErrorContract"] = {}
# The only str is "*"
self._using_for: Dict[Union[str, Type], List[str]] = {}
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
@ -142,6 +147,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_interface(self, is_interface: bool):
self._is_interface = is_interface
@property
def is_library(self) -> bool:
return self._is_library
@is_library.setter
def is_library(self, is_library: bool):
self._is_library = is_library
# endregion
###################################################################################
###################################################################################
@ -243,7 +256,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def using_for(self) -> Dict[Union[str, Type], List[str]]:
def using_for(self) -> Dict[Union[str, Type], List[Type]]:
return self._using_for
# endregion
@ -900,6 +913,9 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
("ERC223", self.is_erc223),
("ERC721", self.is_erc721),
("ERC777", self.is_erc777),
("ERC2612", self.is_erc2612),
("ERC1363", self.is_erc1363),
("ERC4626", self.is_erc4626),
]
return [erc for erc, is_erc in all_erc if is_erc()]
@ -974,6 +990,46 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
full_names = self.functions_signatures
return all(s in full_names for s in ERC1155_signatures)
def is_erc4626(self) -> bool:
"""
Check if the contract is an erc4626
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc4626
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC4626_signatures)
def is_erc2612(self) -> bool:
"""
Check if the contract is an erc2612
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc2612
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC2612_signatures)
def is_erc1363(self) -> bool:
"""
Check if the contract is an erc1363
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc1363
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC1363_signatures)
def is_erc4524(self) -> bool:
"""
Check if the contract is an erc4524
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc4524
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC4524_signatures)
@property
def is_token(self) -> bool:
"""

@ -56,13 +56,24 @@ class CustomError(SourceMapping):
Contract and converted into address
:return: the solidity signature
"""
# Ideally this should be an assert
# But due to a logic limitation in the solc parsing (find_variable)
# We need to raise an error if the custom error sig was not yet built
# (set_solidity_sig was not called before find_variable)
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) + ")"
raise ValueError("Custom Error not yet built")
return self._solidity_signature
def set_solidity_sig(self) -> None:
"""
Function to be called once all the parameters have been set
Returns:
"""
parameters = [self._convert_type_for_solidity_signature(x.type) for x in self.parameters]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
# endregion
###################################################################################
###################################################################################

@ -9,6 +9,9 @@ class Enum(SourceMapping):
self._name = name
self._canonical_name = canonical_name
self._values = values
self._min = 0
# The max value of an Enum is the index of the last element
self._max = len(values) - 1
@property
def canonical_name(self) -> str:
@ -22,5 +25,13 @@ class Enum(SourceMapping):
def values(self) -> List[str]:
return self._values
@property
def min(self) -> int:
return self._min
@property
def max(self) -> int:
return self._max
def __str__(self):
return self.name

@ -538,7 +538,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._nodes = nodes
@property
def entry_point(self) -> "Node":
def entry_point(self) -> Optional["Node"]:
"""
Node: Entry point of the function
"""
@ -1304,9 +1304,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
with open(filename, "w", encoding="utf8") as f:
f.write("digraph{\n")
for node in self.nodes:
f.write('{}[label="{}"];\n'.format(node.node_id, str(node)))
f.write(f'{node.node_id}[label="{str(node)}"];\n')
for son in node.sons:
f.write("{}->{};\n".format(node.node_id, son.node_id))
f.write(f"{node.node_id}->{son.node_id};\n")
f.write("}\n")
@ -1318,20 +1318,18 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
def description(node):
desc = "{}\n".format(node)
desc += "id: {}".format(node.node_id)
desc = f"{node}\n"
desc += f"id: {node.node_id}"
if node.dominance_frontier:
desc += "\ndominance frontier: {}".format(
[n.node_id for n in node.dominance_frontier]
)
desc += f"\ndominance frontier: {[n.node_id for n in node.dominance_frontier]}"
return desc
with open(filename, "w", encoding="utf8") as f:
f.write("digraph{\n")
for node in self.nodes:
f.write('{}[label="{}"];\n'.format(node.node_id, description(node)))
f.write(f'{node.node_id}[label="{description(node)}"];\n')
if node.immediate_dominator:
f.write("{}->{};\n".format(node.immediate_dominator.node_id, node.node_id))
f.write(f"{node.immediate_dominator.node_id}->{node.node_id};\n")
f.write("}\n")
@ -1356,22 +1354,22 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
content = ""
content += "digraph{\n"
for node in self.nodes:
label = "Node Type: {} {}\n".format(str(node.type), node.node_id)
label = f"Node Type: {str(node.type)} {node.node_id}\n"
if node.expression and not skip_expressions:
label += "\nEXPRESSION:\n{}\n".format(node.expression)
label += f"\nEXPRESSION:\n{node.expression}\n"
if node.irs and not skip_expressions:
label += "\nIRs:\n" + "\n".join([str(ir) for ir in node.irs])
content += '{}[label="{}"];\n'.format(node.node_id, label)
content += f'{node.node_id}[label="{label}"];\n'
if node.type in [NodeType.IF, NodeType.IFLOOP]:
true_node = node.son_true
if true_node:
content += '{}->{}[label="True"];\n'.format(node.node_id, true_node.node_id)
content += f'{node.node_id}->{true_node.node_id}[label="True"];\n'
false_node = node.son_false
if false_node:
content += '{}->{}[label="False"];\n'.format(node.node_id, false_node.node_id)
content += f'{node.node_id}->{false_node.node_id}[label="False"];\n'
else:
for son in node.sons:
content += "{}->{};\n".format(node.node_id, son.node_id)
content += f"{node.node_id}->{son.node_id};\n"
content += "}\n"
return content
@ -1726,6 +1724,6 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
def __str__(self):
return self._name
return self.name
# endregion

@ -1,5 +1,5 @@
from pathlib import Path
from typing import Optional, TYPE_CHECKING
from typing import Optional, TYPE_CHECKING, Dict
from slither.core.source_mapping.source_mapping import SourceMapping
@ -13,6 +13,8 @@ class Import(SourceMapping):
self._filename: Path = filename
self._alias: Optional[str] = None
self.scope: "FileScope" = scope
# Map local name -> original name
self.renaming: Dict[str, str] = {}
@property
def filename(self) -> str:
@ -22,7 +24,7 @@ class Import(SourceMapping):
:return:
:rtype:
"""
return str(self._filename)
return self._filename.as_posix()
@property
def filename_path(self) -> Path:

@ -71,6 +71,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"abi.encodeWithSelector()": ["bytes"],
"abi.encodeWithSignature()": ["bytes"],
"bytes.concat()": ["bytes"],
"string.concat()": ["string"],
# abi.decode returns an a list arbitrary types
"abi.decode()": [],
"type(address)": [],
@ -92,7 +93,7 @@ def solidity_function_signature(name):
Returns:
str
"""
return name + " returns({})".format(",".join(SOLIDITY_FUNCTIONS[name]))
return name + f" returns({','.join(SOLIDITY_FUNCTIONS[name])})"
class SolidityVariable(Context):
@ -103,7 +104,7 @@ class SolidityVariable(Context):
# dev function, will be removed once the code is stable
def _check_name(self, name: str): # pylint: disable=no-self-use
assert name in SOLIDITY_VARIABLES or name.endswith("_slot") or name.endswith("_offset")
assert name in SOLIDITY_VARIABLES or name.endswith(("_slot", "_offset"))
@property
def state_variable(self):

@ -50,7 +50,7 @@ class AssignmentOperationType(Enum):
if operation_type == "%=":
return AssignmentOperationType.ASSIGN_MODULO
raise SlitherCoreError("get_type: Unknown operation type {})".format(operation_type))
raise SlitherCoreError(f"get_type: Unknown operation type {operation_type})")
def __str__(self) -> str:
if self == AssignmentOperationType.ASSIGN:
@ -75,7 +75,7 @@ class AssignmentOperationType(Enum):
return "/="
if self == AssignmentOperationType.ASSIGN_MODULO:
return "%="
raise SlitherCoreError("str: Unknown operation type {})".format(self))
raise SlitherCoreError(f"str: Unknown operation type {self})")
class AssignmentOperation(ExpressionTyped):

@ -40,8 +40,11 @@ class BinaryOperationType(Enum):
GREATER_SIGNED = 22
RIGHT_SHIFT_ARITHMETIC = 23
# pylint: disable=too-many-branches
@staticmethod
def get_type(operation_type: "BinaryOperation"): # pylint: disable=too-many-branches
def get_type(
operation_type: "BinaryOperation",
) -> "BinaryOperationType":
if operation_type == "**":
return BinaryOperationType.POWER
if operation_type == "*":
@ -91,9 +94,9 @@ class BinaryOperationType(Enum):
if operation_type == ">>'":
return BinaryOperationType.RIGHT_SHIFT_ARITHMETIC
raise SlitherCoreError("get_type: Unknown operation type {})".format(operation_type))
raise SlitherCoreError(f"get_type: Unknown operation type {operation_type})")
def __str__(self): # pylint: disable=too-many-branches
def __str__(self) -> str: # pylint: disable=too-many-branches
if self == BinaryOperationType.POWER:
return "**"
if self == BinaryOperationType.MULTIPLICATION:
@ -142,11 +145,16 @@ class BinaryOperationType(Enum):
return ">'"
if self == BinaryOperationType.RIGHT_SHIFT_ARITHMETIC:
return ">>'"
raise SlitherCoreError("str: Unknown operation type {})".format(self))
raise SlitherCoreError(f"str: Unknown operation type {self})")
class BinaryOperation(ExpressionTyped):
def __init__(self, left_expression, right_expression, expression_type):
def __init__(
self,
left_expression: Expression,
right_expression: Expression,
expression_type: BinaryOperationType,
) -> None:
assert isinstance(left_expression, Expression)
assert isinstance(right_expression, Expression)
super().__init__()
@ -169,5 +177,5 @@ class BinaryOperation(ExpressionTyped):
def type(self) -> BinaryOperationType:
return self._type
def __str__(self):
def __str__(self) -> str:
return str(self.expression_left) + " " + str(self.type) + " " + str(self.expression_right)

@ -2,7 +2,7 @@ from slither.core.source_mapping.source_mapping import SourceMapping
class Expression(SourceMapping):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._is_lvalue = False

@ -7,7 +7,7 @@ if TYPE_CHECKING:
class ExpressionTyped(Expression): # pylint: disable=too-few-public-methods
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._type: Optional["Type"] = None

@ -41,7 +41,7 @@ class UnaryOperationType(Enum):
return UnaryOperationType.PLUSPLUS_POST
if operation_type == "--":
return UnaryOperationType.MINUSMINUS_POST
raise SlitherCoreError("get_type: Unknown operation type {}".format(operation_type))
raise SlitherCoreError(f"get_type: Unknown operation type {operation_type}")
def __str__(self):
if self == UnaryOperationType.BANG:
@ -62,7 +62,7 @@ class UnaryOperationType(Enum):
]:
return "--"
raise SlitherCoreError("str: Unknown operation type {}".format(self))
raise SlitherCoreError(f"str: Unknown operation type {self}")
@staticmethod
def is_prefix(operation_type):
@ -82,7 +82,7 @@ class UnaryOperationType(Enum):
]:
return False
raise SlitherCoreError("is_prefix: Unknown operation type {}".format(operation_type))
raise SlitherCoreError(f"is_prefix: Unknown operation type {operation_type}")
class UnaryOperation(ExpressionTyped):

@ -6,6 +6,8 @@ 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.core.solidity_types import TypeAlias
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.variables import Constant
@ -36,6 +38,16 @@ class FileScope:
self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set()
self.structures: Dict[str, StructureTopLevel] = {}
self.variables: Dict[str, TopLevelVariable] = {}
# Renamed created by import
# import A as B
# local name -> original name (A -> B)
self.renaming: Dict[str, str] = {}
# User defined types
# Name -> type alias
self.user_defined_types: Dict[str, TypeAlias] = {}
def add_accesible_scopes(self) -> bool:
"""
@ -69,6 +81,15 @@ class FileScope:
if not _dict_contain(new_scope.structures, self.structures):
self.structures.update(new_scope.structures)
learn_something = True
if not _dict_contain(new_scope.variables, self.variables):
self.variables.update(new_scope.variables)
learn_something = True
if not _dict_contain(new_scope.renaming, self.renaming):
self.renaming.update(new_scope.renaming)
learn_something = True
if not _dict_contain(new_scope.user_defined_types, self.user_defined_types):
self.user_defined_types.update(new_scope.user_defined_types)
learn_something = True
return learn_something

@ -4,6 +4,8 @@
import json
import logging
import os
import pathlib
import posixpath
import re
from typing import Optional, Dict, List, Set, Union
@ -155,7 +157,7 @@ class SlitherCore(Context):
for compilation_unit in self._compilation_units:
for c in compilation_unit.contracts:
for f in c.functions:
f.cfg_to_dot(os.path.join(d, "{}.{}.dot".format(c.name, f.name)))
f.cfg_to_dot(os.path.join(d, f"{c.name}.{f.name}.dot"))
# endregion
###################################################################################
@ -172,7 +174,7 @@ class SlitherCore(Context):
return False
mapping_elements_with_lines = (
(
os.path.normpath(elem["source_mapping"]["filename_absolute"]),
posixpath.normpath(elem["source_mapping"]["filename_absolute"]),
elem["source_mapping"]["lines"],
)
for elem in r["elements"]
@ -217,8 +219,12 @@ class SlitherCore(Context):
for elem in r["elements"]
if "source_mapping" in elem
]
source_mapping_elements = map(
lambda x: os.path.normpath(x) if x else x, source_mapping_elements
# Use POSIX-style paths so that filter_paths works across different
# OSes. Convert to a list so elements don't get consumed and are lost
# while evaluating the first pattern
source_mapping_elements = list(
map(lambda x: pathlib.Path(x).resolve().as_posix() if x else x, source_mapping_elements)
)
matching = False
@ -239,14 +245,14 @@ class SlitherCore(Context):
if r["elements"] and matching:
return False
if r["elements"] and self._exclude_dependencies:
return not all(element["source_mapping"]["is_dependency"] for element in r["elements"])
if self._show_ignored_findings:
return True
if r["id"] in self._previous_results_ids:
return False
if self.has_ignore_comment(r):
return False
if r["id"] in self._previous_results_ids:
return False
if r["elements"] and self._exclude_dependencies:
return not all(element["source_mapping"]["is_dependency"] for element in r["elements"])
# Conserve previous result filtering. This is conserved for compatibility, but is meant to be removed
return not r["description"] in [pr["description"] for pr in self._previous_results]
@ -261,9 +267,7 @@ class SlitherCore(Context):
if "id" in r:
self._previous_results_ids.add(r["id"])
except json.decoder.JSONDecodeError:
logger.error(
red("Impossible to decode {}. Consider removing the file".format(filename))
)
logger.error(red(f"Impossible to decode {filename}. Consider removing the file"))
def write_results_to_hide(self):
if not self._results_to_hide:
@ -332,6 +336,6 @@ class SlitherCore(Context):
@property
def show_ignore_findings(self) -> bool:
return self.show_ignore_findings
return self._show_ignored_findings
# endregion

@ -3,4 +3,6 @@ from .elementary_type import ElementaryType
from .function_type import FunctionType
from .mapping_type import MappingType
from .user_defined_type import UserDefinedType
from .type import Type
from .type_information import TypeInformation
from .type_alias import TypeAlias, TypeAliasTopLevel, TypeAliasContract

@ -34,9 +34,17 @@ class ArrayType(Type):
return self._length
@property
def lenght_value(self) -> Optional[Literal]:
def length_value(self) -> Optional[Literal]:
return self._length_value
@property
def is_fixed_array(self) -> bool:
return bool(self.length)
@property
def is_dynamic_array(self) -> bool:
return not self.is_fixed_array
@property
def storage_size(self) -> Tuple[int, bool]:
if self._length_value:

@ -43,8 +43,8 @@ Int = [
"int256",
]
Max_Int = {k: 2 ** (8 * i - 1) - 1 if i > 0 else 2 ** 255 - 1 for i, k in enumerate(Int)}
Min_Int = {k: -(2 ** (8 * i - 1)) if i > 0 else -(2 ** 255) for i, k in enumerate(Int)}
Max_Int = {k: 2 ** (8 * i - 1) - 1 if i > 0 else 2**255 - 1 for i, k in enumerate(Int)}
Min_Int = {k: -(2 ** (8 * i - 1)) if i > 0 else -(2**255) for i, k in enumerate(Int)}
Uint = [
"uint",
@ -82,7 +82,7 @@ Uint = [
"uint256",
]
Max_Uint = {k: 2 ** (8 * i) - 1 if i > 0 else 2 ** 256 - 1 for i, k in enumerate(Uint)}
Max_Uint = {k: 2 ** (8 * i) - 1 if i > 0 else 2**256 - 1 for i, k in enumerate(Uint)}
Min_Uint = {k: 0 for k in Uint}
@ -127,10 +127,10 @@ Max_Byte = {k: 2 ** (8 * (i + 1)) - 1 for i, k in enumerate(Byte[2:])}
Max_Byte["bytes"] = None
Max_Byte["string"] = None
Max_Byte["byte"] = 255
Min_Byte = {k: 1 << (4 + 8 * i) for i, k in enumerate(Byte[2:])}
Min_Byte = {k: 0 for k in Byte}
Min_Byte["bytes"] = 0x0
Min_Byte["string"] = None
Min_Byte["byte"] = 0x10
Min_Byte["string"] = 0x0
Min_Byte["byte"] = 0x0
MaxValues = dict(dict(Max_Int, **Max_Uint), **Max_Byte)
MinValues = dict(dict(Min_Int, **Min_Uint), **Min_Byte)
@ -140,8 +140,8 @@ M = list(range(8, 257, 8))
N = list(range(0, 81))
MN = list(itertools.product(M, N))
Fixed = ["fixed{}x{}".format(m, n) for (m, n) in MN] + ["fixed"]
Ufixed = ["ufixed{}x{}".format(m, n) for (m, n) in MN] + ["ufixed"]
Fixed = [f"fixed{m}x{n}" for (m, n) in MN] + ["fixed"]
Ufixed = [f"ufixed{m}x{n}" for (m, n) in MN] + ["ufixed"]
ElementaryTypeName = ["address", "bool", "string", "var"] + Int + Uint + Byte + Fixed + Ufixed
@ -188,8 +188,8 @@ class ElementaryType(Type):
return int(8)
if t == "address":
return int(160)
if t.startswith("bytes"):
return int(t[len("bytes") :])
if t.startswith("bytes") and t != "bytes":
return int(t[len("bytes") :]) * 8
return None
@property

@ -38,8 +38,8 @@ class FunctionType(Type):
params = ",".join([str(x.type) for x in self._params])
return_values = ",".join([str(x.type) for x in self._return_values])
if return_values:
return "function({}) returns({})".format(params, return_values)
return "function({})".format(params)
return f"function({params}) returns({return_values})"
return f"function({params})"
@property
def parameters_signature(self) -> str:
@ -49,7 +49,7 @@ class FunctionType(Type):
# Use x.type
# x.name may be empty
params = ",".join([str(x.type) for x in self._params])
return "({})".format(params)
return f"({params})"
@property
def signature(self) -> str:
@ -61,8 +61,8 @@ class FunctionType(Type):
params = ",".join([str(x.type) for x in self._params])
return_values = ",".join([str(x.type) for x in self._return_values])
if return_values:
return "({}) returns({})".format(params, return_values)
return "({})".format(params)
return f"({params}) returns({return_values})"
return f"({params})"
def __eq__(self, other):
if not isinstance(other, FunctionType):

@ -24,7 +24,7 @@ class MappingType(Type):
return 32, True
def __str__(self):
return "mapping({} => {})".format(str(self._from), str(self._to))
return f"mapping({str(self._from)} => {str(self._to)})"
def __eq__(self, other):
if not isinstance(other, MappingType):

@ -0,0 +1,41 @@
from typing import TYPE_CHECKING, Tuple
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.top_level import TopLevel
from slither.core.solidity_types import Type
if TYPE_CHECKING:
from slither.core.declarations import Contract
from slither.core.scope.scope import FileScope
class TypeAlias(Type):
def __init__(self, underlying_type: Type, name: str):
super().__init__()
self.name = name
self.underlying_type = underlying_type
@property
def storage_size(self) -> Tuple[int, bool]:
return self.underlying_type.storage_size
def __hash__(self):
return hash(str(self))
class TypeAliasTopLevel(TypeAlias, TopLevel):
def __init__(self, underlying_type: Type, name: str, scope: "FileScope"):
super().__init__(underlying_type, name)
self.file_scope: "FileScope" = scope
def __str__(self):
return self.name
class TypeAliasContract(TypeAlias, ChildContract):
def __init__(self, underlying_type: Type, name: str, contract: "Contract"):
super().__init__(underlying_type, name)
self._contract: "Contract" = contract
def __str__(self):
return self.contract.name + "." + self.name

@ -13,8 +13,9 @@ class TypeInformation(Type):
def __init__(self, c):
# pylint: disable=import-outside-toplevel
from slither.core.declarations.contract import Contract
from slither.core.declarations.enum import Enum
assert isinstance(c, (Contract, ElementaryType))
assert isinstance(c, (Contract, ElementaryType, Enum))
super().__init__()
self._type = c

@ -128,7 +128,7 @@ class SourceMapping(Context):
if not lines:
lines = ""
elif len(lines) == 1:
lines = "#{}{}".format(line_descr, lines[0])
lines = f"#{line_descr}{lines[0]}"
else:
lines = f"#{line_descr}{lines[0]}-{line_descr}{lines[-1]}"
return lines

@ -58,4 +58,4 @@ class LocalVariable(ChildFunction, Variable):
@property
def canonical_name(self) -> str:
return "{}.{}".format(self.function.canonical_name, self.name)
return f"{self.function.canonical_name}.{self.name}"

@ -58,7 +58,7 @@ class StateVariable(ChildContract, Variable):
@property
def canonical_name(self) -> str:
return "{}.{}".format(self.contract.name, self.name)
return f"{self.contract.name}.{self.name}"
@property
def full_name(self) -> str:

@ -5,12 +5,14 @@ from slither.core.variables.variable import Variable
if TYPE_CHECKING:
from slither.core.cfg.node import Node
from slither.core.scope.scope import FileScope
class TopLevelVariable(TopLevel, Variable):
def __init__(self):
def __init__(self, scope: "FileScope"):
super().__init__()
self._node_initialization: Optional["Node"] = None
self.file_scope = scope
# endregion
###################################################################################

@ -10,7 +10,7 @@ from slither.core.solidity_types.elementary_type import ElementaryType
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
# pylint: disable=too-many-instance-attributes
class Variable(SourceMapping):
def __init__(self):
super().__init__()
@ -21,6 +21,8 @@ class Variable(SourceMapping):
self._visibility: Optional[str] = None
self._is_constant = False
self._is_immutable: bool = False
self._is_reentrant: bool = True
self._write_protection: Optional[List[str]] = None
@property
def is_scalar(self) -> bool:
@ -90,6 +92,22 @@ class Variable(SourceMapping):
def is_constant(self, is_cst: bool):
self._is_constant = is_cst
@property
def is_reentrant(self) -> bool:
return self._is_reentrant
@is_reentrant.setter
def is_reentrant(self, is_reentrant: bool):
self._is_reentrant = is_reentrant
@property
def write_protection(self) -> Optional[List[str]]:
return self._write_protection
@write_protection.setter
def write_protection(self, write_protection: List[str]):
self._write_protection = write_protection
@property
def visibility(self) -> Optional[str]:
"""

@ -208,6 +208,8 @@ class AbstractDetector(metaclass=abc.ABCMeta):
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"))
results = sorted(results, key=lambda x: x["id"])
return results
@property

@ -6,7 +6,9 @@ from .variables.uninitialized_local_variables import UninitializedLocalVars
from .attributes.constant_pragma import ConstantPragma
from .attributes.incorrect_solc import IncorrectSolc
from .attributes.locked_ether import LockedEther
from .functions.arbitrary_send import ArbitrarySend
from .functions.arbitrary_send_eth import ArbitrarySendEth
from .erc.erc20.arbitrary_send_erc20_no_permit import ArbitrarySendErc20NoPermit
from .erc.erc20.arbitrary_send_erc20_permit import ArbitrarySendErc20Permit
from .functions.suicidal import Suicidal
# from .functions.complex_function import ComplexFunction
@ -34,7 +36,7 @@ from .shadowing.builtin_symbols import BuiltinSymbolShadowing
from .operations.block_timestamp import Timestamp
from .statements.calls_in_loop import MultipleCallsInLoop
from .statements.incorrect_strict_equality import IncorrectStrictEquality
from .erc.incorrect_erc20_interface import IncorrectERC20InterfaceDetection
from .erc.erc20.incorrect_erc20_interface import IncorrectERC20InterfaceDetection
from .erc.incorrect_erc721_interface import IncorrectERC721InterfaceDetection
from .erc.unindexed_event_parameters import UnindexedERC20EventParameters
from .statements.deprecated_calls import DeprecatedStandards
@ -81,3 +83,4 @@ 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
from .functions.protected_variable import ProtectedVariables

@ -13,7 +13,9 @@ class ShiftParameterMixup(AbstractDetector):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#shift-parameter-mixup"
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly"
)
WIKI_TITLE = "Incorrect shift in assembly."
WIKI_DESCRIPTION = "Detect if the values in a shift operation are reversed"

@ -29,7 +29,7 @@ class ConstantPragma(AbstractDetector):
versions = sorted(list(set(versions)))
if len(versions) > 1:
info = ["Different versions of Solidity is used:\n"]
info = ["Different versions of Solidity are used:\n"]
info += [f"\t- Version used: {[str(v) for v in versions]}\n"]
for p in pragma:

@ -14,7 +14,7 @@ from slither.formatters.attributes.incorrect_solc import custom_format
# 4: version number
# pylint: disable=anomalous-backslash-in-string
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
PATTERN = re.compile(r"(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
class IncorrectSolc(AbstractDetector):

@ -87,7 +87,7 @@ contract Something {
if not intended_interface_is_subset_parent:
# Should not be a subset of an earlier determined intended_interface or derive from it
intended_interface_is_subset_intended = False
for intended_interface in intended_interfaces:
for intended_interface in list(intended_interfaces):
sigs_intended_interface = {
f.full_name for f in intended_interface.functions_entry_points
}

@ -0,0 +1,95 @@
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations.solidity_variables import SolidityVariable
from slither.slithir.operations import HighLevelCall, LibraryCall
from slither.core.declarations import Contract, Function, SolidityVariableComposed
from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.compilation_unit import SlitherCompilationUnit
class ArbitrarySendErc20:
"""Detects instances where ERC20 can be sent from an arbitrary from address."""
def __init__(self, compilation_unit: SlitherCompilationUnit):
self._compilation_unit = compilation_unit
self._no_permit_results: List[Node] = []
self._permit_results: List[Node] = []
@property
def compilation_unit(self) -> SlitherCompilationUnit:
return self._compilation_unit
@property
def no_permit_results(self) -> List[Node]:
return self._no_permit_results
@property
def permit_results(self) -> List[Node]:
return self._permit_results
def _detect_arbitrary_from(self, contract: Contract):
for f in contract.functions:
all_high_level_calls = [
f_called[1].solidity_signature
for f_called in f.high_level_calls
if isinstance(f_called[1], Function)
]
all_library_calls = [f_called[1].solidity_signature for f_called in f.library_calls]
if (
"transferFrom(address,address,uint256)" in all_high_level_calls
or "safeTransferFrom(address,address,address,uint256)" in all_library_calls
):
if (
"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"
in all_high_level_calls
):
ArbitrarySendErc20._arbitrary_from(f.nodes, self._permit_results)
else:
ArbitrarySendErc20._arbitrary_from(f.nodes, self._no_permit_results)
@staticmethod
def _arbitrary_from(nodes: List[Node], results: List[Node]):
"""Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter."""
for node in nodes:
for ir in node.irs:
if (
isinstance(ir, HighLevelCall)
and isinstance(ir.function, Function)
and ir.function.solidity_signature == "transferFrom(address,address,uint256)"
and not (
is_dependent(
ir.arguments[0],
SolidityVariableComposed("msg.sender"),
node.function.contract,
)
or is_dependent(
ir.arguments[0],
SolidityVariable("this"),
node.function.contract,
)
)
):
results.append(ir.node)
elif (
isinstance(ir, LibraryCall)
and ir.function.solidity_signature
== "safeTransferFrom(address,address,address,uint256)"
and not (
is_dependent(
ir.arguments[1],
SolidityVariableComposed("msg.sender"),
node.function.contract,
)
or is_dependent(
ir.arguments[1],
SolidityVariable("this"),
node.function.contract,
)
)
):
results.append(ir.node)
def detect(self):
"""Detect transfers that use arbitrary `from` parameter."""
for c in self.compilation_unit.contracts_derived:
self._detect_arbitrary_from(c)

@ -0,0 +1,45 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output
from .arbitrary_send_erc20 import ArbitrarySendErc20
class ArbitrarySendErc20NoPermit(AbstractDetector):
"""
Detect when `msg.sender` is not used as `from` in transferFrom
"""
ARGUMENT = "arbitrary-send-erc20"
HELP = "transferFrom uses arbitrary `from`"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20"
WIKI_TITLE = "Arbitrary `from` in transferFrom"
WIKI_DESCRIPTION = "Detect when `msg.sender` is not used as `from` in transferFrom."
WIKI_EXPLOIT_SCENARIO = """
```solidity
function a(address from, address to, uint256 amount) public {
erc20.transferFrom(from, to, am);
}
```
Alice approves this contract to spend her ERC20 tokens. Bob can call `a` and specify Alice's address as the `from` parameter in `transferFrom`, allowing him to transfer Alice's tokens to himself."""
WIKI_RECOMMENDATION = """
Use `msg.sender` as `from` in transferFrom.
"""
def _detect(self) -> List[Output]:
""""""
results: List[Output] = []
arbitrary_sends = ArbitrarySendErc20(self.compilation_unit)
arbitrary_sends.detect()
for node in arbitrary_sends.no_permit_results:
func = node.function
info = [func, " uses arbitrary from in transferFrom: ", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,53 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output
from .arbitrary_send_erc20 import ArbitrarySendErc20
class ArbitrarySendErc20Permit(AbstractDetector):
"""
Detect when `msg.sender` is not used as `from` in transferFrom along with the use of permit.
"""
ARGUMENT = "arbitrary-send-erc20-permit"
HELP = "transferFrom uses arbitrary from with permit"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit"
WIKI_TITLE = "Arbitrary `from` in transferFrom used with permit"
WIKI_DESCRIPTION = (
"Detect when `msg.sender` is not used as `from` in transferFrom and permit is used."
)
WIKI_EXPLOIT_SCENARIO = """
```solidity
function bad(address from, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) public {
erc20.permit(from, address(this), value, deadline, v, r, s);
erc20.transferFrom(from, to, value);
}
```
If an ERC20 token does not implement permit and has a fallback function e.g. WETH, transferFrom allows an attacker to transfer all tokens approved for this contract."""
WIKI_RECOMMENDATION = """
Ensure that the underlying ERC20 token correctly implements a permit function.
"""
def _detect(self) -> List[Output]:
""""""
results: List[Output] = []
arbitrary_sends = ArbitrarySendErc20(self.compilation_unit)
arbitrary_sends.detect()
for node in arbitrary_sends.permit_results:
func = node.function
info = [
func,
" uses arbitrary from in transferFrom in combination with permit: ",
node,
"\n",
]
res = self.generate_result(info)
results.append(res)
return results

@ -1,144 +0,0 @@
"""
Module detecting send to arbitrary address
To avoid FP, it does not report:
- If msg.sender is used as index (withdraw situation)
- If the function is protected
- If the value sent is msg.value (repay situation)
- If there is a call to transferFrom
TODO: dont report if the value is tainted by msg.value
"""
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,
SolidityVariableComposed,
)
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import (
HighLevelCall,
Index,
LowLevelCall,
Send,
SolidityCall,
Transfer,
)
# pylint: disable=too-many-nested-blocks,too-many-branches
from slither.utils.output import Output
def arbitrary_send(func: Function):
if func.is_protected():
return []
ret: List[Node] = []
for node in func.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall):
if ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"):
return False
if isinstance(ir, Index):
if ir.variable_right == SolidityVariableComposed("msg.sender"):
return False
if is_dependent(
ir.variable_right,
SolidityVariableComposed("msg.sender"),
func.contract,
):
return False
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)):
if isinstance(ir, (HighLevelCall)):
if isinstance(ir.function, Function):
if ir.function.full_name == "transferFrom(address,address,uint256)":
return False
if ir.call_value is None:
continue
if ir.call_value == SolidityVariableComposed("msg.value"):
continue
if is_dependent(
ir.call_value,
SolidityVariableComposed("msg.value"),
func.contract,
):
continue
if is_tainted(ir.destination, func.contract):
ret.append(node)
return ret
def detect_arbitrary_send(contract: Contract):
"""
Detect arbitrary send
Args:
contract (Contract)
Returns:
list((Function), (list (Node)))
"""
ret = []
for f in [f for f in contract.functions if f.contract_declarer == contract]:
nodes = arbitrary_send(f)
if nodes:
ret.append((f, nodes))
return ret
class ArbitrarySend(AbstractDetector):
ARGUMENT = "arbitrary-send"
HELP = "Functions that send Ether to arbitrary destinations"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations"
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{
address destination;
function setDestination(){
destination = msg.sender;
}
function withdraw() public{
destination.transfer(this.balance);
}
}
```
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) -> List[Output]:
""""""
results = []
for c in self.contracts:
arbitrary_send_result = detect_arbitrary_send(c)
for (func, nodes) in arbitrary_send_result:
info = [func, " sends eth to arbitrary user\n"]
info += ["\tDangerous calls:\n"]
# sort the nodes to get deterministic results
nodes.sort(key=lambda x: x.node_id)
for node in nodes:
info += ["\t- ", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,144 @@
"""
Module detecting send to arbitrary address
To avoid FP, it does not report:
- If msg.sender is used as index (withdraw situation)
- If the function is protected
- If the value sent is msg.value (repay situation)
- If there is a call to transferFrom
TODO: dont report if the value is tainted by msg.value
"""
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,
SolidityVariableComposed,
)
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import (
HighLevelCall,
Index,
LowLevelCall,
Send,
SolidityCall,
Transfer,
)
# pylint: disable=too-many-nested-blocks,too-many-branches
from slither.utils.output import Output
def arbitrary_send(func: Function):
if func.is_protected():
return []
ret: List[Node] = []
for node in func.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall):
if ir.function == SolidityFunction("ecrecover(bytes32,uint8,bytes32,bytes32)"):
return False
if isinstance(ir, Index):
if ir.variable_right == SolidityVariableComposed("msg.sender"):
return False
if is_dependent(
ir.variable_right,
SolidityVariableComposed("msg.sender"),
func.contract,
):
return False
if isinstance(ir, (HighLevelCall, LowLevelCall, Transfer, Send)):
if isinstance(ir, (HighLevelCall)):
if isinstance(ir.function, Function):
if ir.function.full_name == "transferFrom(address,address,uint256)":
return False
if ir.call_value is None:
continue
if ir.call_value == SolidityVariableComposed("msg.value"):
continue
if is_dependent(
ir.call_value,
SolidityVariableComposed("msg.value"),
func.contract,
):
continue
if is_tainted(ir.destination, func.contract):
ret.append(node)
return ret
def detect_arbitrary_send(contract: Contract):
"""
Detect arbitrary send
Args:
contract (Contract)
Returns:
list((Function), (list (Node)))
"""
ret = []
for f in [f for f in contract.functions if f.contract_declarer == contract]:
nodes = arbitrary_send(f)
if nodes:
ret.append((f, nodes))
return ret
class ArbitrarySendEth(AbstractDetector):
ARGUMENT = "arbitrary-send-eth"
HELP = "Functions that send Ether to arbitrary destinations"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations"
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 ArbitrarySendEth{
address destination;
function setDestination(){
destination = msg.sender;
}
function withdraw() public{
destination.transfer(this.balance);
}
}
```
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) -> List[Output]:
""""""
results = []
for c in self.contracts:
arbitrary_send_result = detect_arbitrary_send(c)
for (func, nodes) in arbitrary_send_result:
info = [func, " sends eth to arbitrary user\n"]
info += ["\tDangerous calls:\n"]
# sort the nodes to get deterministic results
nodes.sort(key=lambda x: x.node_id)
for node in nodes:
info += ["\t- ", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -68,6 +68,9 @@ contract Contract{
function.contract_declarer.is_from_dependency()
):
continue
# Continue if the functon is not implemented because it means the contract is abstract
if not function.is_implemented:
continue
info = [function, " is never used and should be removed\n"]
res = self.generate_result(info)
results.append(res)

@ -0,0 +1,81 @@
"""
Module detecting suicidal contract
A suicidal contract is an unprotected function that calls selfdestruct
"""
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Function, Contract
from slither.utils.output import Output
class ProtectedVariables(AbstractDetector):
ARGUMENT = "protected-vars"
HELP = "Detected unprotected variables"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables"
WIKI_TITLE = "Protected Variables"
WIKI_DESCRIPTION = "Detect unprotected variable that are marked protected"
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Buggy{
/// @custom:security write-protection="onlyOwner()"
address owner;
function set_protected() public onlyOwner(){
owner = msg.sender;
}
function set_not_protected() public{
owner = msg.sender;
}
}
```
`owner` must be always written by function using `onlyOwner` (`write-protection="onlyOwner()"`), however anyone can call `set_not_protected`.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Add access controls to the vulnerable function"
def _analyze_function(self, function: Function, contract: Contract) -> List[Output]:
results = []
for state_variable_written in function.state_variables_written:
if state_variable_written.write_protection:
for function_sig in state_variable_written.write_protection:
function_protection = contract.get_function_from_signature(function_sig)
if not function_protection:
function_protection = contract.get_modifier_from_signature(function_sig)
if not function_protection:
self.logger.error(f"{function_sig} not found")
continue
if function_protection not in function.all_internal_calls():
info = [
function,
" should have ",
function_protection,
" to protect ",
state_variable_written,
"\n",
]
res = self.generate_result(info)
results.append(res)
return results
def _detect(self):
"""Detect the suicidal functions"""
results = []
for contract in self.compilation_unit.contracts_derived:
for function in contract.functions_entry_points:
results += self._analyze_function(function, contract)
return results

@ -89,16 +89,12 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
if func.is_constructor:
continue
if not self.is_mixed_case(func.name):
if (
func.visibility
in [
"internal",
"private",
]
and self.is_mixed_case_with_underscore(func.name)
):
if func.visibility in [
"internal",
"private",
] and self.is_mixed_case_with_underscore(func.name):
continue
if func.name.startswith("echidna_") or func.name.startswith("crytic_"):
if func.name.startswith(("echidna_", "crytic_")):
continue
info = ["Function ", func, " is not in mixedCase\n"]

@ -283,11 +283,12 @@ class Reentrancy(AbstractDetector):
def detect_reentrancy(self, contract):
for function in contract.functions_and_modifiers_declared:
if function.is_implemented:
if self.KEY in function.context:
continue
self._explore(function.entry_point, [])
function.context[self.KEY] = True
if not function.is_constructor:
if function.is_implemented:
if self.KEY in function.context:
continue
self._explore(function.entry_point, [])
function.context[self.KEY] = True
def _detect(self):
""""""

@ -8,6 +8,7 @@ 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
from .common import is_upgradable_gap_variable
def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
@ -19,6 +20,9 @@ def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
var: StateVariable
for var in contract.state_variables_declared:
if is_upgradable_gap_variable(contract, var):
continue
shadow: List[StateVariable] = [v for v in variables_fathers if v.name == var.name]
if shadow:
ret.append([var] + shadow)

@ -0,0 +1,30 @@
from slither.core.declarations import Contract
from slither.core.variables.state_variable import StateVariable
from slither.core.solidity_types import ArrayType, ElementaryType
def is_upgradable_gap_variable(contract: Contract, variable: StateVariable) -> bool:
"""Helper function that returns true if 'variable' is a gap variable used
for upgradable contracts. More specifically, the function returns true if:
- variable is named "__gap"
- it is a uint256 array declared at the end of the contract
- it has private visibility"""
# Return early on if the variable name is != gap to avoid iterating over all the state variables
if variable.name != "__gap":
return False
declared_variable_ordered = [
v for v in contract.state_variables_ordered if v in contract.state_variables_declared
]
if not declared_variable_ordered:
return False
variable_type = variable.type
return (
declared_variable_ordered[-1] is variable
and isinstance(variable_type, ArrayType)
and variable_type.type == ElementaryType("uint256")
and variable.visibility == "private"
)

@ -3,9 +3,11 @@ Module detecting shadowing of state variables
"""
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
from .common import is_upgradable_gap_variable
def detect_shadowing(contract):
def detect_shadowing(contract: Contract):
ret = []
variables_fathers = []
for father in contract.inheritance:
@ -13,6 +15,10 @@ def detect_shadowing(contract):
variables_fathers += father.state_variables_declared
for var in contract.state_variables_declared:
# Ignore __gap variables for updatable contracts
if is_upgradable_gap_variable(contract, var):
continue
shadow = [v for v in variables_fathers if v.name == var.name]
if shadow:
ret.append([var] + shadow)

@ -1,7 +1,7 @@
import re
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
# pylint: disable=bidirectional-unicode
class RightToLeftOverride(AbstractDetector):
"""
Detect the usage of a Right-To-Left-Override (U+202E) character
@ -88,6 +88,6 @@ contract Token
results.append(res)
# Advance the start index for the next iteration
start_index = result_index + 1
start_index = idx + 1
return results

@ -1,4 +1,4 @@
from typing import List
from typing import List, Optional
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
@ -22,7 +22,11 @@ def detect_call_in_loop(contract: Contract) -> List[Node]:
return ret
def call_in_loop(node: Node, in_loop_counter: int, visited: List[Node], ret: List[Node]) -> None:
def call_in_loop(
node: Optional[Node], in_loop_counter: int, visited: List[Node], ret: List[Node]
) -> None:
if node is None:
return
if node in visited:
return
# shared visited

@ -1,4 +1,4 @@
from typing import List
from typing import List, Optional
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
@ -17,8 +17,12 @@ def detect_costly_operations_in_loop(contract: Contract) -> List[Node]:
def costly_operations_in_loop(
node: Node, in_loop_counter: int, visited: List[Node], ret: List[Node]
node: Optional[Node], in_loop_counter: int, visited: List[Node], ret: List[Node]
) -> None:
if node is None:
return
if node in visited:
return
# shared visited

@ -1,4 +1,4 @@
from typing import List
from typing import List, Optional
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import LowLevelCall, InternalCall
@ -15,8 +15,12 @@ def detect_delegatecall_in_loop(contract: Contract) -> List[Node]:
def delegatecall_in_loop(
node: Node, in_loop_counter: int, visited: List[Node], results: List[Node]
node: Optional[Node], in_loop_counter: int, visited: List[Node], results: List[Node]
) -> None:
if node is None:
return
if node in visited:
return
# shared visited

@ -1,4 +1,4 @@
from typing import List
from typing import List, Optional
from slither.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import InternalCall
@ -15,8 +15,12 @@ def detect_msg_value_in_loop(contract: Contract) -> List[Node]:
def msg_value_in_loop(
node: Node, in_loop_counter: int, visited: List[Node], results: List[Node]
node: Optional[Node], in_loop_counter: int, visited: List[Node], results: List[Node]
) -> None:
if node is None:
return
if node in visited:
return
# shared visited

@ -2,9 +2,19 @@
Module detecting numbers with too many digits.
"""
import re
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.variables import Constant
_HEX_ADDRESS_REGEXP = re.compile("(0[xX])?[0-9a-fA-F]{40}")
def is_hex_address(value) -> bool:
"""
Checks if the given string of text type is an address in hexadecimal encoded form.
"""
return _HEX_ADDRESS_REGEXP.fullmatch(value) is not None
class TooManyDigits(AbstractDetector):
"""
@ -58,7 +68,7 @@ Use:
if isinstance(read, Constant):
# read.value can return an int or a str. Convert it to str
value_as_str = read.original_value
if "00000" in value_as_str:
if "00000" in value_as_str and not is_hex_address(value_as_str):
# Info to be printed
ret.append(node)
return ret

@ -11,7 +11,7 @@ from slither.core.solidity_types.elementary_type import Int, Uint
def typeRange(t):
bits = int(t.split("int")[1])
if t in Uint:
return 0, (2 ** bits) - 1
return 0, (2**bits) - 1
if t in Int:
v = (2 ** (bits - 1)) - 1
return -v, v

@ -1,11 +1,12 @@
from typing import List
from slither.core.declarations import SolidityFunction, Function
from slither.core.declarations.contract import Contract
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import LowLevelCall, SolidityCall
def _can_be_destroyed(contract) -> List[Function]:
def _can_be_destroyed(contract: Contract) -> List[Function]:
targets = []
for f in contract.functions_entry_points:
for ir in f.all_slithir_operations():
@ -21,6 +22,26 @@ def _can_be_destroyed(contract) -> List[Function]:
return targets
def _has_initializer_modifier(functions: List[Function]) -> bool:
for f in functions:
for m in f.modifiers:
if m.name == "initializer":
return True
return False
def _whitelisted_modifiers(f: Function) -> bool:
# The onlyProxy modifier prevents calling the implementation contract (must be delegatecall)
# https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/3dec82093ea4a490d63aab3e925fed4f692909e8/contracts/proxy/utils/UUPSUpgradeable.sol#L38-L42
return "onlyProxy" not in [modifier.name for modifier in f.modifiers]
def _initialize_functions(contract: Contract) -> List[Function]:
return list(
filter(_whitelisted_modifiers, [f for f in contract.functions if f.name == "initialize"])
)
class UnprotectedUpgradeable(AbstractDetector):
ARGUMENT = "unprotected-upgrade"
@ -61,34 +82,36 @@ class UnprotectedUpgradeable(AbstractDetector):
for contract in self.compilation_unit.contracts_derived:
if contract.is_upgradeable:
functions_that_can_destroy = _can_be_destroyed(contract)
if functions_that_can_destroy:
initiliaze_functions = [f for f in contract.functions if f.name == "initialize"]
vars_init_ = [
init.all_state_variables_written() for init in initiliaze_functions
]
vars_init = [item for sublist in vars_init_ for item in sublist]
vars_init_in_constructors_ = [
f.all_state_variables_written() for f in contract.constructors
]
vars_init_in_constructors = [
item for sublist in vars_init_in_constructors_ for item in sublist
]
if vars_init and (set(vars_init) - set(vars_init_in_constructors)):
info = (
[
contract,
" is an upgradeable contract that does not protect its initiliaze functions: ",
]
+ initiliaze_functions
+ [
". Anyone can delete the contract with: ",
]
+ functions_that_can_destroy
)
res = self.generate_result(info)
results.append(res)
if not _has_initializer_modifier(contract.constructors):
functions_that_can_destroy = _can_be_destroyed(contract)
if functions_that_can_destroy:
initialize_functions = _initialize_functions(contract)
vars_init_ = [
init.all_state_variables_written() for init in initialize_functions
]
vars_init = [item for sublist in vars_init_ for item in sublist]
vars_init_in_constructors_ = [
f.all_state_variables_written() for f in contract.constructors
]
vars_init_in_constructors = [
item for sublist in vars_init_in_constructors_ for item in sublist
]
if vars_init and (set(vars_init) - set(vars_init_in_constructors)):
info = (
[
contract,
" is an upgradeable contract that does not protect its initialize functions: ",
]
+ initialize_functions
+ [
". Anyone can delete the contract with: ",
]
+ functions_that_can_destroy
)
res = self.generate_result(info)
results.append(res)
return results

@ -17,7 +17,9 @@ class SimilarVarsDetection(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-are-too-similar"
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar"
)
WIKI_TITLE = "Variable names too similar"
WIKI_DESCRIPTION = "Detect variables with names that are too similar."

@ -88,7 +88,11 @@ Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is
for contract in self.compilation_unit.contracts:
for function in contract.functions:
if function.is_implemented and function.contract_declarer == contract:
if (
function.is_implemented
and function.contract_declarer == contract
and function.entry_point
):
if function.contains_assembly:
continue
# dont consider storage variable, as they are detected by another detector

@ -96,7 +96,7 @@ Bob calls `func`. As a result, `owner` is overridden to `0`.
for contract in self.compilation_unit.contracts:
for function in contract.functions:
if function.is_implemented:
if function.is_implemented and function.entry_point:
uninitialized_storage_variables = [
v for v in function.local_variables if v.is_storage and v.uninitialized
]

@ -13,7 +13,7 @@ REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"]
# 2: version number
# 3: version number
# 4: version number
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
PATTERN = re.compile(r"(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
def custom_format(slither, result):

@ -13,8 +13,7 @@ REPLACEMENT_VERSIONS = ["^0.4.25", "^0.5.3"]
# 3: version number
# 4: version number
# pylint: disable=anomalous-backslash-in-string
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
PATTERN = re.compile(r"(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
def custom_format(slither, result):

@ -300,10 +300,10 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
# group 2: beginning of the to type
# nested mapping are within the group 1
# RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)'
RE_MAPPING_FROM = b"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = b"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING_FROM = rb"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = rb"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING = (
b"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + b"\)"
rb"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + rb"\)"
)

@ -1,6 +1,13 @@
import abc
from logging import Logger
from typing import TYPE_CHECKING, Union, List, Optional, Dict
from slither.utils import output
from slither.utils.output import SupportedOutput
if TYPE_CHECKING:
from slither import Slither
class IncorrectPrinterInitialization(Exception):
@ -13,7 +20,7 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
WIKI = ""
def __init__(self, slither, logger):
def __init__(self, slither: "Slither", logger: Logger) -> None:
self.slither = slither
self.contracts = slither.contracts
self.filename = slither.filename
@ -34,11 +41,15 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
f"WIKI is not initialized {self.__class__.__name__}"
)
def info(self, info):
def info(self, info: str) -> None:
if self.logger:
self.logger.info(info)
def generate_output(self, info, additional_fields=None):
def generate_output(
self,
info: Union[str, List[Union[str, SupportedOutput]]],
additional_fields: Optional[Dict] = None,
) -> output.Output:
if additional_fields is None:
additional_fields = {}
printer_output = output.Output(info, additional_fields)
@ -47,6 +58,5 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
return printer_output
@abc.abstractmethod
def output(self, filename):
"""TODO Documentation"""
return
def output(self, filename: str) -> output.Output:
pass

@ -17,3 +17,4 @@ from .summary.require_calls import RequireOrAssert
from .summary.constructor_calls import ConstructorPrinter
from .guidance.echidna import Echidna
from .summary.evm import PrinterEVM
from .summary.when_not_paused import PrinterWhenNotPaused

@ -43,7 +43,7 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):
for contract in self.contracts:
if contract.is_top_level:
continue
txt += "\nContract %s\n" % contract.name
txt += f"\nContract {contract.name}\n"
table = MyPrettyTable(
["Function", "State variables written", "Conditions on msg.sender"]
)

@ -22,12 +22,10 @@ class CFG(AbstractPrinter):
continue
for function in contract.functions + contract.modifiers:
if filename:
new_filename = "{}-{}-{}.dot".format(
filename, contract.name, function.full_name
)
new_filename = f"{filename}-{contract.name}-{function.full_name}.dot"
else:
new_filename = "{}-{}.dot".format(contract.name, function.full_name)
info += "Export {}\n".format(new_filename)
new_filename = f"{contract.name}-{function.full_name}.dot"
info += f"Export {new_filename}\n"
content = function.slithir_cfg_to_dot_str()
with open(new_filename, "w", encoding="utf8") as f:
f.write(content)

@ -296,6 +296,24 @@ def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _with_fallback(slither: SlitherCore) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for function in contract.functions_entry_points:
if function.is_fallback:
ret.add(contract.name)
return ret
def _with_receive(slither: SlitherCore) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for function in contract.functions_entry_points:
if function.is_receive:
ret.add(contract.name)
return ret
def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]:
"""
Detect the functions with external calls
@ -376,6 +394,10 @@ class Echidna(AbstractPrinter):
use_balance = _use_balance(self.slither)
with_fallback = list(_with_fallback(self.slither))
with_receive = list(_with_receive(self.slither))
d = {
"payable": payable,
"timestamp": timestamp,
@ -391,6 +413,9 @@ class Echidna(AbstractPrinter):
"have_external_calls": external_calls,
"call_a_parameter": call_parameters,
"use_balance": use_balance,
"solc_versions": [unit.solc_version for unit in self.slither.compilation_units],
"with_fallback": with_fallback,
"with_receive": with_receive,
}
self.info(json.dumps(d, indent=4))

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

Loading…
Cancel
Save