Merge branch 'dev' into dev-naming

pull/870/head
Josselin Feist 2 years ago
commit 0b230da0e7
  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. 49
      .github/workflows/features.yml
  8. 14
      .github/workflows/linter.yml
  9. 28
      .github/workflows/parser.yml
  10. 35
      .github/workflows/pip-audit.yml
  11. 14
      .github/workflows/pylint.yml
  12. 50
      .github/workflows/read_storage.yml
  13. 35
      CONTRIBUTING.md
  14. 11
      Dockerfile
  15. 179
      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/README.md
  26. 2
      plugin_example/setup.py
  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. 27
      setup.py
  32. 112
      slither/__main__.py
  33. 116
      slither/analyses/data_dependency/data_dependency.py
  34. 3
      slither/analyses/write/are_variables_written.py
  35. 38
      slither/core/cfg/node.py
  36. 2
      slither/core/children/child_node.py
  37. 62
      slither/core/compilation_unit.py
  38. 4
      slither/core/context/context.py
  39. 124
      slither/core/declarations/contract.py
  40. 82
      slither/core/declarations/custom_error.py
  41. 12
      slither/core/declarations/custom_error_contract.py
  42. 14
      slither/core/declarations/custom_error_top_level.py
  43. 11
      slither/core/declarations/enum.py
  44. 9
      slither/core/declarations/enum_top_level.py
  45. 96
      slither/core/declarations/function.py
  46. 10
      slither/core/declarations/function_contract.py
  47. 21
      slither/core/declarations/function_top_level.py
  48. 30
      slither/core/declarations/import_directive.py
  49. 8
      slither/core/declarations/pragma_directive.py
  50. 41
      slither/core/declarations/solidity_import_placeholder.py
  51. 46
      slither/core/declarations/solidity_variables.py
  52. 6
      slither/core/declarations/structure.py
  53. 10
      slither/core/declarations/structure_top_level.py
  54. 14
      slither/core/expressions/assignment_operation.py
  55. 20
      slither/core/expressions/binary_operation.py
  56. 4
      slither/core/expressions/expression.py
  57. 2
      slither/core/expressions/expression_typed.py
  58. 6
      slither/core/expressions/unary_operation.py
  59. 0
      slither/core/scope/__init__.py
  60. 121
      slither/core/scope/scope.py
  61. 36
      slither/core/slither_core.py
  62. 2
      slither/core/solidity_types/__init__.py
  63. 12
      slither/core/solidity_types/array_type.py
  64. 22
      slither/core/solidity_types/elementary_type.py
  65. 10
      slither/core/solidity_types/function_type.py
  66. 2
      slither/core/solidity_types/mapping_type.py
  67. 41
      slither/core/solidity_types/type_alias.py
  68. 3
      slither/core/solidity_types/type_information.py
  69. 4
      slither/core/source_mapping/source_mapping.py
  70. 2
      slither/core/variables/local_variable.py
  71. 2
      slither/core/variables/state_variable.py
  72. 4
      slither/core/variables/top_level_variable.py
  73. 34
      slither/core/variables/variable.py
  74. 91
      slither/detectors/abstract_detector.py
  75. 12
      slither/detectors/all_detectors.py
  76. 4
      slither/detectors/assembly/shift_parameter_mixup.py
  77. 2
      slither/detectors/attributes/constant_pragma.py
  78. 34
      slither/detectors/attributes/incorrect_solc.py
  79. 2
      slither/detectors/attributes/unimplemented_interface.py
  80. 2
      slither/detectors/compiler_bugs/reused_base_constructor.py
  81. 0
      slither/detectors/erc/erc20/__init__.py
  82. 95
      slither/detectors/erc/erc20/arbitrary_send_erc20.py
  83. 45
      slither/detectors/erc/erc20/arbitrary_send_erc20_no_permit.py
  84. 53
      slither/detectors/erc/erc20/arbitrary_send_erc20_permit.py
  85. 0
      slither/detectors/erc/erc20/incorrect_erc20_interface.py
  86. 2
      slither/detectors/erc/unindexed_event_parameters.py
  87. 138
      slither/detectors/functions/arbitrary_send.py
  88. 144
      slither/detectors/functions/arbitrary_send_eth.py
  89. 3
      slither/detectors/functions/dead_code.py
  90. 81
      slither/detectors/functions/protected_variable.py
  91. 14
      slither/detectors/naming_convention/naming_convention.py
  92. 18
      slither/detectors/operations/bad_prng.py
  93. 2
      slither/detectors/operations/missing_zero_address_validation.py
  94. 11
      slither/detectors/reentrancy/reentrancy.py
  95. 23
      slither/detectors/shadowing/abstract.py
  96. 30
      slither/detectors/shadowing/common.py
  97. 8
      slither/detectors/shadowing/state.py
  98. 11
      slither/detectors/slither/name_reused.py
  99. 4
      slither/detectors/source/rtlo.py
  100. 2
      slither/detectors/statements/assert_state_change.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:v3
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

@ -0,0 +1,49 @@
---
name: Features tests
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
jobs:
build:
name: Features tests
runs-on: ${{ 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.0
cd tests/test_node_modules/
npm install hardhat
cd ../..
- 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:v3
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

@ -0,0 +1,35 @@
---
name: pip-audit
on:
push:
branches: [ dev, master ]
pull_request:
branches: [ dev, master ]
schedule: [ cron: "0 7 * * 2" ]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@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:v3
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.8.2` black `20.8b1`.
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`
@ -56,3 +73,11 @@ To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/d
- Run `pytest ./tests/test_ast_parsing.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`
### Synchronization with crytic-compile
By default, `slither` follows either the latest version of crytic-compile in pip, or `crytic-compile@master` (look for dependencies in [`setup.py`](./setup.py). If crytic-compile development comes with breaking changes, the process to update `slither` is:
- Update `slither/setup.py` to point to the related crytic-compile's branch
- Create a PR in `slither` and ensure it passes the CI
- Once the development branch is merged in `crytic-compile@master`, ensure `slither` follows the `master` branch
The `slither`'s PR can either be merged while using a crytic-compile non-`master` branch, or kept open until the breaking changes are available in `crytic-compile@master`.

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

@ -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]}')

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

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

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

@ -1,22 +1,36 @@
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as f:
long_description = f.read()
setup(
name="slither-analyzer",
description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.8.0",
version="0.8.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.0",
# "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=open("README.md").read(),
long_description=long_description,
entry_points={
"console_scripts": [
"slither = slither.__main__:main",
@ -29,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",
]
},
)

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

@ -13,8 +13,10 @@ from slither.core.declarations import (
SolidityVariableComposed,
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,
@ -37,7 +39,16 @@ if TYPE_CHECKING:
###################################################################################
def is_dependent(variable, source, context, only_unprotected=False):
Variable_types = Union[Variable, SolidityVariable]
Context_types = Union[Contract, Function]
def is_dependent(
variable: Variable_types,
source: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> bool:
"""
Args:
variable (Variable)
@ -52,17 +63,22 @@ def is_dependent(variable, source, context, only_unprotected=False):
return False
if variable == source:
return True
context = context.context
context_dict = context.context
if only_unprotected:
return (
variable in context[KEY_NON_SSA_UNPROTECTED]
and source in context[KEY_NON_SSA_UNPROTECTED][variable]
variable in context_dict[KEY_NON_SSA_UNPROTECTED]
and source in context_dict[KEY_NON_SSA_UNPROTECTED][variable]
)
return variable in context[KEY_NON_SSA] and source in context[KEY_NON_SSA][variable]
return variable in context_dict[KEY_NON_SSA] and source in context_dict[KEY_NON_SSA][variable]
def is_dependent_ssa(variable, source, context, only_unprotected=False):
def is_dependent_ssa(
variable: Variable_types,
source: Variable_types,
context: Context_types,
only_unprotected: bool = False,
) -> bool:
"""
Args:
variable (Variable)
@ -73,17 +89,17 @@ def is_dependent_ssa(variable, source, context, only_unprotected=False):
bool
"""
assert isinstance(context, (Contract, Function))
context = context.context
context_dict = context.context
if isinstance(variable, Constant):
return False
if variable == source:
return True
if only_unprotected:
return (
variable in context[KEY_SSA_UNPROTECTED]
and source in context[KEY_SSA_UNPROTECTED][variable]
variable in context_dict[KEY_SSA_UNPROTECTED]
and source in context_dict[KEY_SSA_UNPROTECTED][variable]
)
return variable in context[KEY_SSA] and source in context[KEY_SSA][variable]
return variable in context_dict[KEY_SSA] and source in context_dict[KEY_SSA][variable]
GENERIC_TAINT = {
@ -94,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
@ -116,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
@ -139,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]:
"""
@ -159,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.
@ -176,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]:
"""
@ -196,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.
@ -238,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
@ -261,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()
@ -269,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] = dict()
contract.context[KEY_SSA_UNPROTECTED] = dict()
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)
@ -291,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]
@ -302,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()
@ -325,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:
@ -350,12 +382,12 @@ 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
function.context[KEY_SSA] = dict()
function.context[KEY_SSA_UNPROTECTED] = dict()
function.context[KEY_SSA] = {}
function.context[KEY_SSA_UNPROTECTED] = {}
is_protected = function.is_protected()
for node in function.nodes:
@ -375,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,
(
@ -398,14 +430,18 @@ def convert_variable_to_non_ssa(v):
Structure,
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 = dict()
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:

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

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

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

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

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

@ -11,7 +11,7 @@ from slither.core.cfg.scope import Scope
from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.declarations.function import Function, FunctionType
from slither.core.declarations.function import Function, FunctionType, FunctionLanguage
from slither.utils.erc import (
ERC20_signatures,
ERC165_signatures,
@ -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
@ -38,6 +42,8 @@ if TYPE_CHECKING:
from slither.core.variables.variable import Variable
from slither.core.variables.state_variable import StateVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.scope.scope import FileScope
LOGGER = logging.getLogger("Contract")
@ -48,7 +54,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Contract class
"""
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
super().__init__()
self._name: Optional[str] = None
@ -68,11 +74,13 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "FunctionContract"] = {}
self._linearizedBaseContracts: List[int] = []
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
@ -90,6 +98,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._all_functions_called: Optional[List["InternalCallType"]] = None
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
self.file_scope: "FileScope" = scope
###################################################################################
###################################################################################
@ -138,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
###################################################################################
###################################################################################
@ -239,9 +256,41 @@ 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
###################################################################################
###################################################################################
# region Custom Errors
###################################################################################
###################################################################################
@property
def custom_errors(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the contract's custom errors
"""
return list(self._custom_errors.values())
@property
def custom_errors_inherited(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the inherited custom errors
"""
return [s for s in self.custom_errors if s.contract != self]
@property
def custom_errors_declared(self) -> List["CustomErrorContract"]:
"""
list(CustomErrorContract): List of the custom errors declared within the contract (not inherited)
"""
return [s for s in self.custom_errors if s.contract == self]
@property
def custom_errors_as_dict(self) -> Dict[str, "CustomErrorContract"]:
return self._custom_errors
# endregion
###################################################################################
###################################################################################
@ -506,7 +555,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def available_elements_from_inheritances(
self,
elements: Dict[str, "Function"],
getter_available: Callable[["Contract"], List["Function"]],
getter_available: Callable[["Contract"], List["FunctionContract"]],
) -> Dict[str, "Function"]:
"""
@ -517,14 +566,16 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
# keep track of the contracts visited
# to prevent an ovveride due to multiple inheritance of the same contract
# A is B, C, D is C, --> the second C was already seen
inherited_elements: Dict[str, "Function"] = {}
inherited_elements: Dict[str, "FunctionContract"] = {}
accessible_elements = {}
contracts_visited = []
for father in self.inheritance_reverse:
functions: Dict[str, "Function"] = {
functions: Dict[str, "FunctionContract"] = {
v.full_name: v
for v in getter_available(father)
if v.contract not in contracts_visited
and v.function_language
!= FunctionLanguage.Yul # Yul functions are not propagated in the inheritance
}
contracts_visited.append(father)
inherited_elements.update(functions)
@ -862,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()]
@ -936,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:
"""
@ -1050,14 +1144,16 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._is_upgradeable = False
if self.is_upgradeable_proxy:
return False
initializable = self.compilation_unit.get_contract_from_name("Initializable")
initializable = self.file_scope.get_contract_from_name("Initializable")
if initializable:
if initializable in self.inheritance:
self._is_upgradeable = True
else:
for c in self.inheritance + [self]:
for contract in self.inheritance + [self]:
# This might lead to false positive
lower_name = c.name.lower()
# Not sure why pylint is having a trouble here
# pylint: disable=no-member
lower_name = contract.name.lower()
if "upgradeable" in lower_name or "upgradable" in lower_name:
self._is_upgradeable = True
break
@ -1188,7 +1284,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
)
# Function uses to create node for state variable declaration statements
node = Node(NodeType.OTHER_ENTRYPOINT, counter, scope)
node = Node(NodeType.OTHER_ENTRYPOINT, counter, scope, func.file_scope)
node.set_offset(variable.source_mapping, self.compilation_unit)
node.set_function(func)
func.add_node(node)
@ -1219,7 +1315,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
from slither.slithir.variables import StateIRVariable
all_ssa_state_variables_instances = dict()
all_ssa_state_variables_instances = {}
for contract in self.inheritance:
for v in contract.state_variables_declared:
@ -1237,8 +1333,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
func.generate_slithir_ssa(all_ssa_state_variables_instances)
def fix_phi(self):
last_state_variables_instances = dict()
initial_state_variables_instances = dict()
last_state_variables_instances = {}
initial_state_variables_instances = {}
for v in self._initial_state_variables:
last_state_variables_instances[v.canonical_name] = []
initial_state_variables_instances[v.canonical_name] = v

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

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

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

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

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

@ -44,6 +44,7 @@ if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.slithir.operations import Operation
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
@ -104,14 +105,20 @@ def _filter_state_variables_written(expressions: List["Expression"]):
return ret
class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
class FunctionLanguage(Enum):
Solidity = 0
Yul = 1
Vyper = 2
class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-public-methods
"""
Function class
"""
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__()
self._scope: List[str] = []
self._internal_scope: List[str] = []
self._name: Optional[str] = None
self._view: bool = False
self._pure: bool = False
@ -207,6 +214,11 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
# Assume we are analyzing Solidty by default
self.function_language: FunctionLanguage = FunctionLanguage.Solidity
self._id: Optional[str] = None
###################################################################################
###################################################################################
# region General properties
@ -235,18 +247,18 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
self._name = new_name
@property
def scope(self) -> List[str]:
def internal_scope(self) -> List[str]:
"""
Return a list of name representing the scope of the function
This is used to model nested functions declared in YUL
:return:
"""
return self._scope
return self._internal_scope
@scope.setter
def scope(self, new_scope: List[str]):
self._scope = new_scope
@internal_scope.setter
def internal_scope(self, new_scope: List[str]):
self._internal_scope = new_scope
@property
def full_name(self) -> str:
@ -256,7 +268,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
"""
if self._full_name is None:
name, parameters, _ = self.signature
full_name = ".".join(self._scope + [name]) + "(" + ",".join(parameters) + ")"
full_name = ".".join(self._internal_scope + [name]) + "(" + ",".join(parameters) + ")"
self._full_name = full_name
return self._full_name
@ -325,6 +337,26 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
return self.compilation_unit.solc_version >= "0.8.0"
@property
def id(self) -> Optional[str]:
"""
Return the ID of the funciton. For Solidity with compact-AST the ID is the reference ID
For other, the ID is None
:return:
:rtype:
"""
return self._id
@id.setter
def id(self, new_id: str):
self._id = new_id
@property
@abstractmethod
def file_scope(self) -> "FileScope":
pass
# endregion
###################################################################################
###################################################################################
@ -506,7 +538,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
self._nodes = nodes
@property
def entry_point(self) -> "Node":
def entry_point(self) -> Optional["Node"]:
"""
Node: Entry point of the function
"""
@ -850,7 +882,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
from slither.slithir.variables import Constant
if self._return_values is None:
return_values = list()
return_values = []
returns = [n for n in self.nodes if n.type == NodeType.RETURN]
[ # pylint: disable=expression-not-assigned
return_values.extend(ir.values)
@ -871,7 +903,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
from slither.slithir.variables import Constant
if self._return_values_ssa is None:
return_values_ssa = list()
return_values_ssa = []
returns = [n for n in self.nodes if n.type == NodeType.RETURN]
[ # pylint: disable=expression-not-assigned
return_values_ssa.extend(ir.values)
@ -1272,9 +1304,9 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
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")
@ -1286,20 +1318,18 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
"""
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")
@ -1324,22 +1354,22 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
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
@ -1543,7 +1573,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
) -> "Node":
from slither.core.cfg.node import Node
node = Node(node_type, self._counter_nodes, scope)
node = Node(node_type, self._counter_nodes, scope, self.file_scope)
node.set_offset(src, self.compilation_unit)
self._counter_nodes += 1
node.set_function(self)
@ -1567,16 +1597,16 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
from slither.core.cfg.node import NodeType
if not self.is_implemented:
return dict()
return {}
if self._entry_point is None:
return dict()
return {}
# node, values
to_explore: List[Tuple["Node", Dict]] = [(self._entry_point, dict())]
to_explore: List[Tuple["Node", Dict]] = [(self._entry_point, {})]
# node -> values
explored: Dict = dict()
explored: Dict = {}
# name -> instances
ret: Dict = dict()
ret: Dict = {}
while to_explore:
node, values = to_explore[0]
@ -1694,6 +1724,6 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
###################################################################################
def __str__(self):
return self._name
return self.name
# endregion

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

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

@ -1,16 +1,40 @@
from typing import Optional
from pathlib import Path
from typing import Optional, TYPE_CHECKING, Dict
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class Import(SourceMapping):
def __init__(self, filename: str):
def __init__(self, filename: Path, scope: "FileScope"):
super().__init__()
self._filename = filename
self._filename: Path = filename
self._alias: Optional[str] = None
self.scope: "FileScope" = scope
# Map local name -> original name
self.renaming: Dict[str, str] = {}
@property
def filename(self) -> str:
"""
Return the absolute filename
:return:
:rtype:
"""
return self._filename.as_posix()
@property
def filename_path(self) -> Path:
"""
Return the absolute filename
:return:
:rtype:
"""
return self._filename
@property

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

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

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

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

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

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

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

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

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

@ -4,6 +4,8 @@
import json
import logging
import os
import pathlib
import posixpath
import re
from typing import Optional, Dict, List, Set, Union
@ -110,9 +112,7 @@ class SlitherCore(Context):
"""
contracts = []
for compilation_unit in self._compilation_units:
contract = compilation_unit.get_contract_from_name(contract_name)
if contract:
contracts.append(contract)
contracts += compilation_unit.get_contract_from_name(contract_name)
return contracts
###################################################################################
@ -123,7 +123,7 @@ class SlitherCore(Context):
@property
def source_code(self) -> Dict[str, str]:
""" {filename: source_code (str)}: source code """
"""{filename: source_code (str)}: source code"""
return self._raw_source_code
@property
@ -157,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
###################################################################################
@ -174,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"]
@ -219,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
@ -241,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]
@ -256,16 +260,14 @@ class SlitherCore(Context):
filename = self._previous_results_filename
try:
if os.path.isfile(filename):
with open(filename) as f:
with open(filename, encoding="utf8") as f:
self._previous_results = json.load(f)
if self._previous_results:
for r in self._previous_results:
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:
@ -334,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:
@ -46,7 +54,7 @@ class ArrayType(Type):
def __str__(self):
if self._length:
return str(self._type) + "[{}]".format(str(self._length_value))
return str(self._type) + f"[{str(self._length_value)}]"
return str(self._type) + "[]"
def __eq__(self, other):

@ -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,13 +188,13 @@ 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
def storage_size(self) -> Tuple[int, bool]:
if self._type == "string" or self._type == "bytes":
if self._type in ["string", "bytes"]:
return 32, True
if self.size is None:
return 32, True

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

@ -8,7 +8,7 @@ if TYPE_CHECKING:
class SourceMapping(Context):
def __init__(self):
def __init__(self) -> None:
super().__init__()
# TODO create a namedtuple for the source mapping rather than a dict
self._source_mapping: Optional[Dict] = None
@ -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__()
@ -20,6 +20,9 @@ class Variable(SourceMapping):
self._initialized: Optional[bool] = None
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:
@ -89,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]:
"""
@ -106,6 +125,19 @@ class Variable(SourceMapping):
assert isinstance(t, (Type, list)) or t is None
self._type = t
@property
def is_immutable(self) -> bool:
"""
Return true of the variable is immutable
:return:
"""
return self._is_immutable
@is_immutable.setter
def is_immutable(self, immutablility: bool):
self._is_immutable = immutablility
@property
def function_name(self):
"""

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

@ -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
@ -79,6 +81,6 @@ from .statements.unary import IncorrectUnaryExpressionDetection
from .operations.missing_zero_address_validation import MissingZeroAddressValidation
from .functions.dead_code import DeadCode
from .statements.write_after_write import WriteAfterWrite
#
#
from .statements.msg_value_in_loop import MsgValueInLoop
from .statements.delegatecall_in_loop import DelegatecallInLoop
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):
@ -43,6 +43,7 @@ Deploy with any of the following Solidity versions:
- 0.5.16 - 0.5.17
- 0.6.11 - 0.6.12
- 0.7.5 - 0.7.6
- 0.8.4 - 0.8.7
Use a simple pragma version that allows any of these versions.
Consider using the latest version of Solidity for testing."""
# endregion wiki_recommendation
@ -51,9 +52,7 @@ Consider using the latest version of Solidity for testing."""
OLD_VERSION_TXT = "allows old versions"
LESS_THAN_TXT = "uses lesser than"
TOO_RECENT_VERSION_TXT = (
"necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6"
)
TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7"
BUGGY_VERSION_TXT = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
)
@ -66,6 +65,10 @@ Consider using the latest version of Solidity for testing."""
"0.6.12",
"0.7.5",
"0.7.6",
"0.8.4",
"0.8.5",
"0.8.6",
"0.8.7",
]
# Indicates the versions that should not be used.
@ -80,6 +83,8 @@ Consider using the latest version of Solidity for testing."""
"^0.5.14",
"0.6.9",
"^0.6.9",
"0.8.8",
"^0.8.8",
]
def _check_version(self, version):
@ -87,6 +92,8 @@ Consider using the latest version of Solidity for testing."""
if op and op not in [">", ">=", "^"]:
return self.LESS_THAN_TXT
version_number = ".".join(version[2:])
if version_number in self.BUGGY_VERSIONS:
return self.BUGGY_VERSION_TXT
if version_number not in self.ALLOWED_VERSIONS:
if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split("."))):
return self.TOO_RECENT_VERSION_TXT
@ -144,11 +151,20 @@ Consider using the latest version of Solidity for testing."""
results.append(json)
if self.compilation_unit.solc_version not in self.ALLOWED_VERSIONS:
info = [
"solc-",
self.compilation_unit.solc_version,
" is not recommended for deployment\n",
]
if self.compilation_unit.solc_version in self.BUGGY_VERSIONS:
info = [
"solc-",
self.compilation_unit.solc_version,
" ",
self.BUGGY_VERSION_TXT,
]
else:
info = [
"solc-",
self.compilation_unit.solc_version,
" is not recommended for deployment\n",
]
json = self.generate_result(info)

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

@ -77,7 +77,7 @@ The constructor of `A` is called multiple times in `D` and `E`:
:param contract: The contract to detect explicit calls to a base constructor with arguments to.
:return: Dictionary of function:list(tuple): { constructor : [(invoking_contract, called_by_constructor]}
"""
results = dict()
results = {}
# Create a set to track all completed contracts
processed_contracts = set()

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

@ -16,7 +16,7 @@ class UnindexedERC20EventParameters(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters"
WIKI_TITLE = "Unindexed ERC20 event oarameters"
WIKI_TITLE = "Unindexed ERC20 event parameters"
WIKI_DESCRIPTION = "Detects whether events defined by the `ERC20` specification that should have some parameters as `indexed` are missing the `indexed` keyword."
# region wiki_exploit_scenario

@ -1,138 +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 slither.core.declarations import Function
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
def arbitrary_send(func):
if func.is_protected():
return []
ret = []
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):
"""
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):
""""""
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"]

@ -2,18 +2,24 @@
Module detecting bad PRNG due to the use of block.timestamp, now or blockhash (block.blockhash) as a source of randomness
"""
from typing import List, Tuple
from slither.analyses.data_dependency.data_dependency import is_dependent_ssa
from slither.core.cfg.node import Node
from slither.core.declarations import Function, Contract
from slither.core.declarations.solidity_variables import (
SolidityVariable,
SolidityFunction,
SolidityVariableComposed,
)
from slither.core.variables.variable import Variable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import BinaryType, Binary
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output, AllSupportedOutput
def collect_return_values_of_bad_PRNG_functions(f):
def collect_return_values_of_bad_PRNG_functions(f: Function) -> List:
"""
Return the return-values of calls to blockhash()
Args:
@ -33,7 +39,7 @@ def collect_return_values_of_bad_PRNG_functions(f):
return values_returned
def contains_bad_PRNG_sources(func, blockhash_ret_values):
def contains_bad_PRNG_sources(func: Function, blockhash_ret_values: List[Variable]) -> List[Node]:
"""
Check if any node in function has a modulus operator and the first operand is dependent on block.timestamp, now or blockhash()
Returns:
@ -57,7 +63,7 @@ def contains_bad_PRNG_sources(func, blockhash_ret_values):
return list(ret)
def detect_bad_PRNG(contract):
def detect_bad_PRNG(contract: Contract) -> List[Tuple[Function, List[Node]]]:
"""
Args:
contract (Contract)
@ -67,7 +73,7 @@ def detect_bad_PRNG(contract):
blockhash_ret_values = []
for f in contract.functions:
blockhash_ret_values += collect_return_values_of_bad_PRNG_functions(f)
ret = []
ret: List[Tuple[Function, List[Node]]] = []
for f in contract.functions:
bad_prng_nodes = contains_bad_PRNG_sources(f, blockhash_ret_values)
if bad_prng_nodes:
@ -110,7 +116,7 @@ As a result, Eve wins the game."""
"Do not use `block.timestamp`, `now` or `blockhash` as a source of randomness"
)
def _detect(self):
def _detect(self) -> List[Output]:
"""Detect bad PRNG due to the use of block.timestamp, now or blockhash (block.blockhash) as a source of randomness"""
results = []
for c in self.compilation_unit.contracts_derived:
@ -118,7 +124,7 @@ As a result, Eve wins the game."""
for func, nodes in values:
for node in nodes:
info = [func, ' uses a weak PRNG: "', node, '" \n']
info: List[AllSupportedOutput] = [func, ' uses a weak PRNG: "', node, '" \n']
res = self.generate_result(info)
results.append(res)

@ -40,7 +40,7 @@ contract C {
}
}
```
Bob calls `updateOwner` without specifying the `newOwner`, soBob loses ownership of the contract.
Bob calls `updateOwner` without specifying the `newOwner`, so Bob loses ownership of the contract.
"""
# endregion wiki_exploit_scenario

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

@ -2,19 +2,28 @@
Module detecting shadowing variables on abstract contract
Recursively check the called functions
"""
from typing import List
from slither.core.declarations import Contract
from slither.core.variables.state_variable import StateVariable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output, AllSupportedOutput
from .common import is_upgradable_gap_variable
def detect_shadowing(contract):
ret = []
def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
ret: List[List[StateVariable]] = []
variables_fathers = []
for father in contract.inheritance:
if all(not f.is_implemented for f in father.functions + father.modifiers):
if all(not f.is_implemented for f in father.functions + list(father.modifiers)):
variables_fathers += father.state_variables_declared
var: StateVariable
for var in contract.state_variables_declared:
shadow = [v for v in variables_fathers if v.name == var.name]
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)
return ret
@ -51,7 +60,7 @@ contract DerivedContract is BaseContract{
WIKI_RECOMMENDATION = "Remove the state variable shadowing."
def _detect(self):
def _detect(self) -> List[Output]:
"""Detect shadowing
Recursively visit the calls
@ -59,14 +68,14 @@ contract DerivedContract is BaseContract{
list: {'vuln', 'filename,'contract','func', 'shadow'}
"""
results = []
results: List[Output] = []
for contract in self.contracts:
shadowing = detect_shadowing(contract)
if shadowing:
for all_variables in shadowing:
shadow = all_variables[0]
variables = all_variables[1:]
info = [shadow, " shadows:\n"]
info: List[AllSupportedOutput] = [shadow, " shadows:\n"]
for var in variables:
info += ["\t- ", var, "\n"]

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

@ -53,7 +53,16 @@ As a result, the second contract cannot be analyzed.
def _detect(self): # pylint: disable=too-many-locals,too-many-branches
results = []
compilation_unit = self.compilation_unit
names_reused = compilation_unit.contract_name_collisions
all_contracts = compilation_unit.contracts
all_contracts_name = [c.name for c in all_contracts]
contracts_name_reused = {
contract for contract in all_contracts_name if all_contracts_name.count(contract) > 1
}
names_reused = {
name: compilation_unit.get_contract_from_name(name) for name in contracts_name_reused
}
# First show the contracts that we know are missing
incorrectly_constructed = [

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

@ -48,7 +48,7 @@ class AssertStateChange(AbstractDetector):
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change"
WIKI_TITLE = "Assert state shange"
WIKI_TITLE = "Assert state change"
WIKI_DESCRIPTION = """Incorrect use of `assert()`. See Solidity best [practices](https://solidity.readthedocs.io/en/latest/control-structures.html#id4)."""
# region wiki_exploit_scenario

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

Loading…
Cancel
Save