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

@ -1,9 +1,9 @@
---
name: CI name: CI
defaults: defaults:
run: run:
# To load bashrc shell: bash
shell: bash -ieo pipefail {0}
on: on:
push: push:
@ -13,46 +13,66 @@ on:
pull_request: pull_request:
schedule: schedule:
# run CI every day even if no PRs/merges occur # run CI every day even if no PRs/merges occur
- cron: '0 12 * * *' - cron: '0 12 * * *'
jobs: jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false
matrix: matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["cli", type: ["cli",
"dapp",
"data_dependency", "data_dependency",
"embark", # "embark",
"erc", "erc",
"etherlime", # "etherlime",
# "etherscan"
"find_paths", "find_paths",
"flat",
"kspec", "kspec",
"printers", "printers",
# "prop"
"simil", "simil",
"slither_config", "slither_config",
"truffle", "truffle",
"upgradability", "upgradability"]
"prop", exclude:
"flat"] # Requires nix
- os: windows-2022
type: dapp
# Requires nvm
- os: windows-2022
type: truffle
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Set up Python 3.6 - name: Set up Python 3.8
uses: actions/setup-python@v1 uses: actions/setup-python@v3
with: with:
python-version: 3.6 python-version: 3.8
- name: Install dependencies - name: Install dependencies
run: | run: |
python setup.py install pip install ".[dev]"
# Used by ci_test.sh solc-select install all
pip install deepdiff solc-select use 0.5.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install solc-select - name: Set up nix
solc-select install all if: matrix.type == 'dapp'
solc-select use 0.5.1 uses: cachix/install-nix-action@v16
- name: Run Tests - name: Set up cachix
env: if: matrix.type == 'dapp'
TEST_TYPE: ${{ matrix.type }} uses: cachix/cachix-action@v10
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }} with:
run: | name: dapp
bash scripts/ci_test_${TEST_TYPE}.sh
- 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] branches: [master, dev]
schedule: schedule:
# run CI every day even if no PRs/merges occur # run CI every day even if no PRs/merges occur
- cron: '0 12 * * *' - cron: '0 12 * * *'
jobs: jobs:
build: build:
name: Detectors tests name: Detectors tests
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python 3.6 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.6 python-version: 3.8
- name: Install dependencies - name: Install dependencies
run: | run: |
python setup.py install pip install ".[dev]"
pip install deepdiff
pip install pytest
pip install solc-select
solc-select install all solc-select install all
solc-select use 0.7.3 solc-select use 0.7.3
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest tests/test_detectors.py 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] branches: [master, dev]
schedule: schedule:
# run CI every day even if no PRs/merges occur # run CI every day even if no PRs/merges occur
- cron: '0 12 * * *' - cron: '0 12 * * *'
jobs: jobs:
build: build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python 3.6 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.6 python-version: 3.8
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters mkdir -p .github/linters
cp pyproject.toml .github/linters cp pyproject.toml .github/linters
- name: Lint everything else - name: Lint everything else
uses: docker://github/super-linter:v3 uses: github/super-linter/slim@v4.9.2
if: always() if: always()
env: env:
# run linter on everything to catch preexisting problems # run linter on everything to catch preexisting problems
@ -58,3 +55,4 @@ jobs:
VALIDATE_JSCPD: false VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false VALIDATE_PYTHON_MYPY: false
SHELLCHECK_OPTS: "-e SC1090" SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -11,37 +11,35 @@ on:
branches: [master, dev] branches: [master, dev]
schedule: schedule:
# run CI every day even if no PRs/merges occur # run CI every day even if no PRs/merges occur
- cron: '0 12 * * *' - cron: '0 12 * * *'
jobs: jobs:
build: build:
name: Parser tests name: Parser tests
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python 3.6 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.6 python-version: 3.8
- name: Install dependencies - name: Install dependencies
run: | run: |
python setup.py install pip install ".[dev]"
pip install deepdiff
pip install pytest - name: Install solc
run: |
git clone https://github.com/crytic/solc-select.git
cd solc-select
git checkout 119dd05f58341811cb02b546f25269a7e8a10875
python setup.py install
solc-select install all solc-select install all
solc-select use 0.8.0 solc-select use 0.8.0
cd ..
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest tests/test_ast_parsing.py 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] branches: [master, dev]
schedule: schedule:
# run CI every day even if no PRs/merges occur # run CI every day even if no PRs/merges occur
- cron: '0 12 * * *' - cron: '0 12 * * *'
jobs: jobs:
build: build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python 3.6 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.6 python-version: 3.8
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters mkdir -p .github/linters
cp pyproject.toml .github/linters cp pyproject.toml .github/linters
- name: Pylint - name: Pylint
uses: docker://github/super-linter:v3 uses: github/super-linter/slim@v4.9.2
if: always() if: always()
env: env:
# run linter on everything to catch preexisting problems # run linter on everything to catch preexisting problems
@ -47,3 +44,4 @@ jobs:
VALIDATE_PYTHON: true VALIDATE_PYTHON: true
VALIDATE_PYTHON_PYLINT: true VALIDATE_PYTHON_PYLINT: true
PYTHON_PYLINT_CONFIG_FILE: pyproject.toml 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. - 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. - 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 ## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation). 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 To run the unit tests, you need to clone this repository and run `pip install ".[dev]"`.
- `deepdiff` installed (`pip install deepdiff`).
- `pycov` installed (`pip install pytest-cov`).
- [`solc-select`](https://github.com/crytic/solc-select) installed.
### Linters ### Linters
@ -37,7 +52,8 @@ To run them locally in the root dir of the repository:
- `pylint slither tests --rcfile pyproject.toml` - `pylint slither tests --rcfile pyproject.toml`
- `black . --config 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 ### Detectors tests
For each new detector, at least one regression tests must be present. 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` - Create a test in `tests`
- Update `ALL_TEST` in `tests/test_detectors.py` - 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. - 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. - 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` 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. - Run `pytest ./tests/test_ast_parsing.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html` To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`
### 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 name=slither
LABEL src="https://github.com/trailofbits/slither" LABEL src="https://github.com/trailofbits/slither"
@ -6,11 +6,12 @@ LABEL creator=trailofbits
LABEL dockerfile_maintenance=trailofbits LABEL dockerfile_maintenance=trailofbits
LABEL desc="Static Analyzer for Solidity" LABEL desc="Static Analyzer for Solidity"
RUN apt-get update \ RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get upgrade -y \ && apt-get update \
&& apt-get install -y git python3 python3-setuptools wget software-properties-common && 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 \ && chmod +x solc-static-linux \
&& mv solc-static-linux /usr/bin/solc && 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) - [Tools](#tools)
- [How to Install](#how-to-install) - [How to Install](#how-to-install)
- [Getting Help](#getting-help) - [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications) - [Publications](#publications)
## Features ## Features
@ -40,9 +41,12 @@ Run Slither on a single file:
slither tests/uninitialized.sol 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 ### 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 1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High
2 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High 2 | `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 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 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 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 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 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 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 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 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | 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 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium 18 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
19 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | 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 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High 20 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
21 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High 21 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
22 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High 22 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
23 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High 23 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
24 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High 24 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | 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 25 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | 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 26 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
27 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | 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 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | 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 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium 29 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
30 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium 30 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
31 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium 31 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
32 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | 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 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | 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 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium 34 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
35 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | 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 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium 36 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
37 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | 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 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium 38 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
39 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium 39 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
40 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High 40 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
41 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High 41 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
42 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High 42 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | 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 43 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | 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 44 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
45 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | 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 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium 46 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
47 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium 47 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
48 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium 48 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
49 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium 49 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
50 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium 50 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
51 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium 51 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | 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 52 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
53 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium 53 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
54 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High 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 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High 55 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
56 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High 56 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
57 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High 57 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
58 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High 58 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
59 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state-variables) | Informational | High 59 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
60 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | 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 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High 61 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | 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 62 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | 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 63 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
64 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | 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 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | 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 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High 66 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
67 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variables) | Informational | High 67 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | 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 68 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
69 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium 69 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
70 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium 70 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | 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 71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | 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 72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | 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 73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
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 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 For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector - The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
@ -141,6 +149,7 @@ For more information, see
- `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg) - `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) - `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) - `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. 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-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-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-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. 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 ## 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 ### Using Pip
@ -175,11 +185,11 @@ git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install 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 ### 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 ```bash
docker pull trailofbits/eth-security-toolbox 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 [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. * 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 ## 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. 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 ## Publications
### Trail of Bits publication ### 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 - [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
### External publications ### 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 Title | Usage | Authors | Venue
- [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 [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
- [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 [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
- [Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf), Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury - TOSEM 20 [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
- [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 [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
- [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) [Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/). If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

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

@ -19,21 +19,13 @@ contract = contracts[0]
destination = contract.get_state_variable_from_name("destination") destination = contract.get_state_variable_from_name("destination")
source = contract.get_state_variable_from_name("source") source = contract.get_state_variable_from_name("source")
print( print(f"{source} is dependent of {destination}: {is_dependent(source, destination, contract)}")
"{} is dependent of {}: {}".format(
source, destination, is_dependent(source, destination, contract)
)
)
assert not is_dependent(source, destination, contract) assert not is_dependent(source, destination, contract)
print( print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
"{} is dependent of {}: {}".format(
destination, source, is_dependent(destination, source, contract)
)
)
assert 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) 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) assert is_tainted(destination, contract)
contracts = slither.get_contract_from_name("Reference") contracts = slither.get_contract_from_name("Reference")
@ -45,32 +37,20 @@ source = contract.get_state_variable_from_name("source")
assert source assert source
print("Reference contract") print("Reference contract")
print( print(f"{source} is dependent of {destination}: {is_dependent(source, destination, contract)}")
"{} is dependent of {}: {}".format(
source, destination, is_dependent(source, destination, contract)
)
)
assert not is_dependent(source, destination, contract) assert not is_dependent(source, destination, contract)
print( print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
"{} is dependent of {}: {}".format(
destination, source, is_dependent(destination, source, contract)
)
)
assert 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) 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) assert is_tainted(destination, contract)
destination_indirect_1 = contract.get_state_variable_from_name("destination_indirect_1") destination_indirect_1 = contract.get_state_variable_from_name("destination_indirect_1")
print( print(f"{destination_indirect_1} is tainted {is_tainted(destination_indirect_1, contract)}")
"{} is tainted {}".format(destination_indirect_1, is_tainted(destination_indirect_1, contract))
)
assert 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") destination_indirect_2 = contract.get_state_variable_from_name("destination_indirect_2")
print( print(f"{destination_indirect_2} is tainted {is_tainted(destination_indirect_2, contract)}")
"{} is tainted {}".format(destination_indirect_2, is_tainted(destination_indirect_2, contract))
)
assert is_tainted(destination_indirect_2, contract) assert is_tainted(destination_indirect_2, contract)
print("SolidityVar contract") print("SolidityVar contract")
@ -83,13 +63,9 @@ assert addr_1
addr_2 = contract.get_state_variable_from_name("addr_2") addr_2 = contract.get_state_variable_from_name("addr_2")
assert addr_2 assert addr_2
msgsender = SolidityVariableComposed("msg.sender") msgsender = SolidityVariableComposed("msg.sender")
print( print(f"{addr_1} is dependent of {msgsender}: {is_dependent(addr_1, msgsender, contract)}")
"{} is dependent of {}: {}".format(addr_1, msgsender, is_dependent(addr_1, msgsender, contract))
)
assert is_dependent(addr_1, msgsender, contract) assert is_dependent(addr_1, msgsender, contract)
print( print(f"{addr_2} is dependent of {msgsender}: {is_dependent(addr_2, msgsender, contract)}")
"{} is dependent of {}: {}".format(addr_2, msgsender, is_dependent(addr_2, msgsender, contract))
)
assert not 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") source = contract.get_state_variable_from_name("source")
assert source assert source
print( print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
"{} is dependent of {}: {}".format(
destination, source, is_dependent(destination, source, contract)
)
)
assert is_dependent(destination, source, contract) assert is_dependent(destination, source, contract)
print("Base Derived 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") destination = contract.get_state_variable_from_name("destination")
source = contract.get_state_variable_from_name("source") source = contract.get_state_variable_from_name("source")
print( print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
"{} is dependent of {}: {} (base)".format(
destination, source, is_dependent(destination, source, contract)
)
)
assert not is_dependent(destination, source, contract) assert not is_dependent(destination, source, contract)
print( print(
"{} is dependent of {}: {} (derived)".format( f"{destination} is dependent of {source}: {is_dependent(destination, source, contract_derived)}"
destination, source, is_dependent(destination, source, contract_derived)
)
) )
assert 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)") f2 = contract.get_function_from_signature("f2(uint256,uint256)")
print( print(
"{} is dependent of {}: {} (base)".format( f"{var_dependant} is dependent of {user_input}: {is_dependent(var_dependant, user_input, contract)} (base)"
var_dependant, user_input, is_dependent(var_dependant, user_input, contract)
)
) )
assert is_dependent(var_dependant, user_input, contract) 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) 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) assert not is_tainted(var_not_tainted, contract)

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

@ -11,6 +11,6 @@ slither = Slither(sys.argv[1])
for contract in slither.contracts: for contract in slither.contracts:
for function in contract.functions + contract.modifiers: for function in contract.functions + contract.modifiers:
filename = "{}-{}-{}.dot".format(sys.argv[1], contract.name, function.full_name) filename = f"{sys.argv[1]}-{contract.name}-{function.full_name}.dot"
print("Export {}".format(filename)) print(f"Export {filename}")
function.slithir_cfg_to_dot(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] all_calls_formated = [f.canonical_name for f in all_calls]
# Print the result # 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) functions_writing_a = contract.get_functions_writing_to_variable(var_a)
# Print the result # 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 # Dont explore inherited functions
if function.contract_declarer == contract: if function.contract_declarer == contract:
print("Function: {}".format(function.name)) print(f"Function: {function.name}")
# Iterate over the nodes of the function # Iterate over the nodes of the function
for node in function.nodes: for node in function.nodes:
@ -26,7 +26,7 @@ for contract in slither.contracts:
# And the SlithIR operations # And the SlithIR operations
if node.expression: if node.expression:
print("\tSolidity expression: {}".format(node.expression)) print(f"\tSolidity expression: {node.expression}")
print("\tSlithIR:") print("\tSlithIR:")
for ir in node.irs: 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): def visit_node(node, visited):
if node is None:
return
if node in visited: if node in visited:
return return
@ -26,9 +29,9 @@ def visit_node(node, visited):
else: else:
read = ir.read read = ir.read
print(ir) print(ir)
print("Refs {}".format(refs)) print(f"Refs {refs}")
print("Read {}".format([str(x) for x in ir.read])) print(f"Read {[str(x) for x in ir.read]}")
print("Before {}".format([str(x) for x in taints])) print(f"Before {[str(x) for x in taints]}")
if any(var_read in taints for var_read in read): if any(var_read in taints for var_read in read):
taints += [ir.lvalue] taints += [ir.lvalue]
lvalue = ir.lvalue lvalue = ir.lvalue
@ -36,7 +39,7 @@ def visit_node(node, visited):
taints += [refs[lvalue]] taints += [refs[lvalue]]
lvalue = refs[lvalue] lvalue = refs[lvalue]
print("After {}".format([str(x) for x in taints])) print(f"After {[str(x) for x in taints]}")
print() print()
taints = [v for v in taints if not isinstance(v, (TemporaryVariable, ReferenceVariable))] 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: for ir in node.irs:
if isinstance(ir, HighLevelCall): if isinstance(ir, HighLevelCall):
if ir.destination in taints: 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__": if __name__ == "__main__":
@ -74,16 +77,14 @@ if __name__ == "__main__":
prev_taints = slither.context[KEY] prev_taints = slither.context[KEY]
for contract in slither.contracts: for contract in slither.contracts:
for function in contract.functions: 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)) slither.context[KEY] = list(set(slither.context[KEY] + function.parameters))
visit_node(function.entry_point, []) 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: for function in contract.functions:
check_call(function, slither.context[KEY]) check_call(function, slither.context[KEY])
print( print(
"All state variables tainted : {}".format( f"All state variables tainted : {[str(v) for v in prev_taints if isinstance(v, StateVariable)]}"
[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 result
print( print(f'The function using "a" in condition are {[f.name for f in function_using_a_as_condition]}')
'The function using "a" in condition are {}'.format(
[f.name for f in function_using_a_as_condition]
)
)

@ -1,6 +1,6 @@
# Slither, Plugin Example # 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). See the [detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector).

@ -7,7 +7,7 @@ setup(
author="Trail of Bits", author="Trail of Bits",
version="0.0", version="0.0",
packages=find_packages(), packages=find_packages(),
python_requires=">=3.6", python_requires=">=3.8",
install_requires=["slither-analyzer==0.1"], install_requires=["slither-analyzer==0.1"],
entry_points={ entry_points={
"slither_analyzer.plugin": "slither my-plugin=slither_my_plugin:make_plugin", "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.email "ci@trailofbits.com"
git config --global user.name "CI User" git config --global user.name "CI User"
curl https://nixos.org/nix/install | sh which nix-env || exit 255
# shellcheck disable=SC1090,SC1091
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
nix-env -iA nixpkgs.cachix
cachix use dapp
git clone --recursive https://github.com/dapphub/dapptools "$HOME/.dapp/dapptools" git clone --recursive https://github.com/dapphub/dapptools "$HOME/.dapp/dapptools"
nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign
dapp init dapp init
slither . slither . --detect external-function
if [ $? -eq 21 ] # TODO: make more elaborate test
if [ $? -eq 4 ]
then then
exit 0 exit 0
fi fi
echo "Truffle test failed" echo "Dapp test failed"
exit 255 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") DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt")
if [ "$DIFF" != "" ] if [ "$DIFF" != "" ]
then then
echo "slither-check-upgradeability 1 failed" echo "slither-check-kspec 1 failed"
cat test_1.txt cat test_1.txt
echo "" echo ""
cat "$DIR_TESTS/test_1.txt" cat "$DIR_TESTS/test_1.txt"

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

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

@ -1,22 +1,36 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as f:
long_description = f.read()
setup( setup(
name="slither-analyzer", name="slither-analyzer",
description="Slither is a Solidity static analysis framework written in Python 3.", description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither", url="https://github.com/crytic/slither",
author="Trail of Bits", author="Trail of Bits",
version="0.8.0", version="0.8.3",
packages=find_packages(), packages=find_packages(),
python_requires=">=3.6", python_requires=">=3.8",
install_requires=[ install_requires=[
"prettytable>=0.7.2", "prettytable>=0.7.2",
"pysha3>=1.0.2", "pysha3>=1.0.2",
"crytic-compile>=0.2.0", # "crytic-compile>=0.2.3",
# "crytic-compile", "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", license="AGPL-3.0",
long_description=open("README.md").read(), long_description=long_description,
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"slither = slither.__main__:main", "slither = slither.__main__:main",
@ -29,6 +43,7 @@ setup(
"slither-check-kspec = slither.tools.kspec_coverage.__main__:main", "slither-check-kspec = slither.tools.kspec_coverage.__main__:main",
"slither-prop = slither.tools.properties.__main__:main", "slither-prop = slither.tools.properties.__main__:main",
"slither-mutate = slither.tools.mutator.__main__:main", "slither-mutate = slither.tools.mutator.__main__:main",
"slither-read-storage = slither.tools.read_storage.__main__:main",
] ]
}, },
) )

@ -16,6 +16,7 @@ from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser from crytic_compile import cryticparser
from crytic_compile.platform.standard import generate_standard_export from crytic_compile.platform.standard import generate_standard_export
from crytic_compile.platform.etherscan import SUPPORTED_NETWORK
from crytic_compile import compile_all, is_supported from crytic_compile import compile_all, is_supported
from slither.detectors import all_detectors 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 import all_printers
from slither.printers.abstract_printer import AbstractPrinter from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither 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.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 ( from slither.utils.command_line import (
output_detectors, output_detectors,
output_results_to_markdown, output_results_to_markdown,
@ -38,6 +39,7 @@ from slither.utils.command_line import (
read_config_file, read_config_file,
JSON_OUTPUT_TYPES, JSON_OUTPUT_TYPES,
DEFAULT_JSON_OUTPUT_TYPES, DEFAULT_JSON_OUTPUT_TYPES,
check_and_sanitize_markdown_root,
) )
from slither.exceptions import SlitherException from slither.exceptions import SlitherException
@ -62,6 +64,9 @@ def process_single(target, args, detector_classes, printer_classes):
ast = "--ast-compact-json" ast = "--ast-compact-json"
if args.legacy_ast: if args.legacy_ast:
ast = "--ast-json" ast = "--ast-json"
if args.checklist:
args.show_ignored_findings = True
slither = Slither(target, ast_format=ast, **vars(args)) slither = Slither(target, ast_format=ast, **vars(args))
return _process(slither, detector_classes, printer_classes) return _process(slither, detector_classes, printer_classes)
@ -171,13 +176,11 @@ def get_detectors_and_printers():
detector = None detector = None
if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors): if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
raise Exception( 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 printer = None
if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers): if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
raise Exception( raise Exception(f"Error when loading plugin {entry_point}, {printer} is not a printer")
"Error when loading plugin %s, %r is not a printer" % (entry_point, printer)
)
# We convert those to lists in case someone returns a tuple # We convert those to lists in case someone returns a tuple
detectors += list(plugin_detectors) detectors += list(plugin_detectors)
@ -205,7 +208,7 @@ def choose_detectors(args, all_detector_classes):
if detector in detectors: if detector in detectors:
detectors_to_run.append(detectors[detector]) detectors_to_run.append(detectors[detector])
else: 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) detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run return detectors_to_run
@ -251,7 +254,7 @@ def choose_printers(args, all_printer_classes):
if printer in printers: if printer in printers:
printers_to_run.append(printers[printer]) printers_to_run.append(printers[printer])
else: else:
raise Exception("Error: {} is not a printer".format(printer)) raise Exception(f"Error: {printer} is not a printer")
return printers_to_run 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 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( parser = argparse.ArgumentParser(
description="Slither. For usage information, see https://github.com/crytic/slither/wiki/Usage", description="For usage information, see https://github.com/crytic/slither/wiki/Usage",
usage="slither.py contract.sol [flag]", usage=usage,
) )
parser.add_argument("filename", help="contract.sol") parser.add_argument("filename", help=argparse.SUPPRESS)
cryticparser.init(parser) 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_detector = parser.add_argument_group("Detectors")
group_printer = parser.add_argument_group("Printers") 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_misc = parser.add_argument_group("Additional options")
group_detector.add_argument( group_detector.add_argument(
"--detect", "--detect",
help="Comma-separated list of detectors, defaults to all, " 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", action="store",
dest="detectors_to_run", dest="detectors_to_run",
default=defaults_flag_in_config["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( group_printer.add_argument(
"--print", "--print",
help="Comma-separated list fo contract information printers, " help="Comma-separated list of contract information printers, "
"available printers: {}".format(", ".join(d.ARGUMENT for d in printer_classes)), f"available printers: {', '.join(d.ARGUMENT for d in printer_classes)}",
action="store", action="store",
dest="printers_to_run", dest="printers_to_run",
default=defaults_flag_in_config["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"], 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( group_misc.add_argument(
"--json", "--json",
help='Export the results as a JSON file ("--json -" to export to stdout)', 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"], 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( group_misc.add_argument(
"--json-types", "--json-types",
help="Comma-separated list of result types to output to JSON, defaults to " 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"], 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( group_misc.add_argument(
"--disable-color", "--disable-color",
help="Disable output colorization", 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)", help="Provide a config file (default: slither.config.json)",
action="store", action="store",
dest="config_file", dest="config_file",
default="slither.config.json", default=None,
) )
group_misc.add_argument( 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) 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( parser.add_argument(
"--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False "--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False
) )
@ -629,22 +659,24 @@ def main_impl(all_detector_classes, all_printer_classes):
cp.enable() cp.enable()
# Set colorization option # 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 # Define some variables for potential JSON output
json_results = {} json_results = {}
output_error = None output_error = None
outputting_json = args.json is not None outputting_json = args.json is not None
outputting_json_stdout = args.json == "-" outputting_json_stdout = args.json == "-"
outputting_sarif = args.sarif is not None
outputting_sarif_stdout = args.sarif == "-"
outputting_zip = args.zip is not None 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())}' to_log = f'Zip type not accepted, it must be one of {",".join(ZIP_TYPES_ACCEPTED.keys())}'
logger.error(to_log) logger.error(to_log)
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout # If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output. # output.
if outputting_json: if outputting_json or output_to_sarif:
StandardOutputCapture.enable(outputting_json_stdout) StandardOutputCapture.enable(outputting_json_stdout or outputting_sarif_stdout)
printer_classes = choose_printers(args, all_printer_classes) printer_classes = choose_printers(args, all_printer_classes)
detector_classes = choose_detectors(args, all_detector_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) ) = process_all(filename, args, detector_classes, printer_classes)
# Determine if we are outputting JSON # 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 # Add our compilation information to JSON
if "compilations" in args.json_types: if "compilations" in args.json_types:
compilation_results = [] compilation_results = []
@ -755,7 +787,7 @@ def main_impl(all_detector_classes, all_printer_classes):
if args.checklist: if args.checklist:
output_results_to_markdown(results_detectors, args.checklist_limit) 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: if number_contracts == 0:
logger.warning(red("No contract was analyzed")) logger.warning(red("No contract was analyzed"))
if printer_classes: if printer_classes:
@ -768,12 +800,6 @@ def main_impl(all_detector_classes, all_printer_classes):
len(detector_classes), len(detector_classes),
len(results_detectors), len(results_detectors),
) )
logger.info(
blue(
"Use https://crytic.io/ to get access to additional detectors and Github integration"
)
)
if args.ignore_return_value: if args.ignore_return_value:
return return
@ -800,6 +826,12 @@ def main_impl(all_detector_classes, all_printer_classes):
StandardOutputCapture.disable() StandardOutputCapture.disable()
output_to_json(None if outputting_json_stdout else args.json, output_error, json_results) 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: if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type) output_to_zip(args.zip, output_error, json_results, args.zip_type)

@ -13,8 +13,10 @@ from slither.core.declarations import (
SolidityVariableComposed, SolidityVariableComposed,
Structure, 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.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 ( from slither.slithir.variables import (
Constant, Constant,
LocalIRVariable, 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: Args:
variable (Variable) variable (Variable)
@ -52,17 +63,22 @@ def is_dependent(variable, source, context, only_unprotected=False):
return False return False
if variable == source: if variable == source:
return True return True
context = context.context context_dict = context.context
if only_unprotected: if only_unprotected:
return ( return (
variable in context[KEY_NON_SSA_UNPROTECTED] variable in context_dict[KEY_NON_SSA_UNPROTECTED]
and source in context[KEY_NON_SSA_UNPROTECTED][variable] 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: Args:
variable (Variable) variable (Variable)
@ -73,17 +89,17 @@ def is_dependent_ssa(variable, source, context, only_unprotected=False):
bool bool
""" """
assert isinstance(context, (Contract, Function)) assert isinstance(context, (Contract, Function))
context = context.context context_dict = context.context
if isinstance(variable, Constant): if isinstance(variable, Constant):
return False return False
if variable == source: if variable == source:
return True return True
if only_unprotected: if only_unprotected:
return ( return (
variable in context[KEY_SSA_UNPROTECTED] variable in context_dict[KEY_SSA_UNPROTECTED]
and source in context[KEY_SSA_UNPROTECTED][variable] 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 = { 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: Args:
variable 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: Args:
variable variable
@ -139,8 +165,8 @@ def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_tai
def get_dependencies( def get_dependencies(
variable: Variable, variable: Variable_types,
context: Union[Contract, Function], context: Context_types,
only_unprotected: bool = False, only_unprotected: bool = False,
) -> Set[Variable]: ) -> Set[Variable]:
""" """
@ -159,7 +185,7 @@ def get_dependencies(
def get_all_dependencies( def get_all_dependencies(
context: Union[Contract, Function], only_unprotected: bool = False context: Context_types, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]: ) -> Dict[Variable, Set[Variable]]:
""" """
Return the dictionary of dependencies. Return the dictionary of dependencies.
@ -176,8 +202,8 @@ def get_all_dependencies(
def get_dependencies_ssa( def get_dependencies_ssa(
variable: Variable, variable: Variable_types,
context: Union[Contract, Function], context: Context_types,
only_unprotected: bool = False, only_unprotected: bool = False,
) -> Set[Variable]: ) -> Set[Variable]:
""" """
@ -196,7 +222,7 @@ def get_dependencies_ssa(
def get_all_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]]: ) -> Dict[Variable, Set[Variable]]:
""" """
Return the dictionary of dependencies. 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 ####") print("#### SSA ####")
context = context.context context = caller_context.context
for k, values in context[KEY_SSA].items(): for k, values in context[KEY_SSA].items():
print("{} ({}):".format(k, id(k))) print(f"{k} ({id(k)}):")
for v in values: for v in values:
print("\t- {}".format(v)) print(f"\t- {v}")
print("#### NON SSA ####") print("#### NON SSA ####")
for k, values in context[KEY_NON_SSA].items(): 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: for v in values:
print("\t- {} ({})".format(v, hex(id(v)))) print(f"\t- {v} ({hex(id(v))})")
# endregion # 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] = set()
compilation_unit.context[KEY_INPUT_SSA] = set() compilation_unit.context[KEY_INPUT_SSA] = set()
@ -269,14 +295,16 @@ def compute_dependency(compilation_unit: "SlitherCompilationUnit"):
compute_dependency_contract(contract, compilation_unit) 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: if KEY_SSA in contract.context:
return return
contract.context[KEY_SSA] = dict() contract.context[KEY_SSA] = {}
contract.context[KEY_SSA_UNPROTECTED] = dict() contract.context[KEY_SSA_UNPROTECTED] = {}
for function in contract.functions + contract.modifiers: for function in contract.functions + list(contract.modifiers):
compute_dependency_function(function) compute_dependency_function(function)
propagate_function(contract, function, KEY_SSA, KEY_NON_SSA) 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) 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) transitive_close_dependencies(function, context_key, context_key_non_ssa)
# Propage data dependency # Propage data dependency
data_depencencies = function.context[context_key] 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) 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 # transitive closure
changed = True changed = True
keys = context.context[context_key].keys() 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]) 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) 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]: if not lvalue in function.context[KEY_SSA]:
function.context[KEY_SSA][lvalue] = set() function.context[KEY_SSA][lvalue] = set()
if not is_protected: 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: if KEY_SSA in function.context:
return return
function.context[KEY_SSA] = dict() function.context[KEY_SSA] = {}
function.context[KEY_SSA_UNPROTECTED] = dict() function.context[KEY_SSA_UNPROTECTED] = {}
is_protected = function.is_protected() is_protected = function.is_protected()
for node in function.nodes: 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( if isinstance(
v, v,
( (
@ -398,14 +430,18 @@ def convert_variable_to_non_ssa(v):
Structure, Structure,
Function, Function,
Type, Type,
SolidityImportPlaceHolder,
TopLevelVariable,
), ),
) )
return v 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 # 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(): for (k, values) in data_depencies.items():
var = convert_variable_to_non_ssa(k) var = convert_variable_to_non_ssa(k)
if not var in ret: if not var in ret:

@ -13,7 +13,6 @@ from slither.slithir.operations import (
OperationWithLValue, OperationWithLValue,
SolidityCall, SolidityCall,
Length, Length,
Balance,
) )
from slither.slithir.variables import ReferenceVariable, TemporaryVariable from slither.slithir.variables import ReferenceVariable, TemporaryVariable
@ -65,7 +64,7 @@ def _visit(
continue continue
if isinstance(ir, (Index, Member)): if isinstance(ir, (Index, Member)):
refs[ir.lvalue] = ir.variable_left refs[ir.lvalue] = ir.variable_left
if isinstance(ir, (Length, Balance)): if isinstance(ir, Length):
refs[ir.lvalue] = ir.value refs[ir.lvalue] = ir.value
if ir.lvalue and not isinstance(ir.lvalue, (TemporaryVariable, ReferenceVariable)): 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.core.solidity_types import ElementaryType
from slither.slithir.convert import convert_expression from slither.slithir.convert import convert_expression
from slither.slithir.operations import ( from slither.slithir.operations import (
Balance,
HighLevelCall, HighLevelCall,
Index, Index,
InternalCall, InternalCall,
@ -40,12 +39,11 @@ from slither.slithir.variables import (
TupleVariable, TupleVariable,
) )
from slither.all_exceptions import SlitherException 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 from slither.core.expressions.expression import Expression
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.declarations import Function
from slither.slithir.variables.variable import SlithIRVariable from slither.slithir.variables.variable import SlithIRVariable
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.utils.type_helpers import ( from slither.utils.type_helpers import (
@ -55,6 +53,7 @@ if TYPE_CHECKING:
LowLevelCallType, LowLevelCallType,
) )
from slither.core.cfg.scope import Scope 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 # pylint: disable=too-many-lines,too-many-branches,too-many-instance-attributes
@ -140,7 +139,7 @@ class NodeType(Enum):
return "END_LOOP" return "END_LOOP"
if self == NodeType.OTHER_ENTRYPOINT: if self == NodeType.OTHER_ENTRYPOINT:
return "OTHER_ENTRYPOINT" return "OTHER_ENTRYPOINT"
return "Unknown type {}".format(hex(self.value)) return f"Unknown type {hex(self.value)}"
# endregion # 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__() super().__init__()
self._node_type = node_type 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._external_calls_as_expressions: List[Expression] = []
self._internal_calls_as_expressions: List[Expression] = [] self._internal_calls_as_expressions: List[Expression] = []
self._irs: List[Operation] = [] self._irs: List[Operation] = []
self._all_slithir_operations: Optional[List[Operation]] = None
self._irs_ssa: List[Operation] = [] self._irs_ssa: List[Operation] = []
self._state_vars_written: List[StateVariable] = [] 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._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() self._find_read_write_call()
def all_slithir_operations(self) -> List[Operation]: def all_slithir_operations(self) -> List[Operation]:
irs = self.irs if self._all_slithir_operations is None:
for ir in irs: irs = list(self.irs)
if isinstance(ir, InternalCall): for ir in self.irs:
irs += ir.function.all_slithir_operations() if isinstance(ir, InternalCall):
return irs irs += ir.function.all_slithir_operations()
self._all_slithir_operations = irs
return self._all_slithir_operations
@staticmethod @staticmethod
def _is_non_slithir_var(var: Variable): 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) self._vars_read.append(origin)
if isinstance(ir, OperationWithLValue): 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 continue # Don't consider Member and Index operations -> ReferenceVariable
var = ir.lvalue var = ir.lvalue
if isinstance(var, ReferenceVariable): if isinstance(var, ReferenceVariable):
@ -903,10 +912,11 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
except AttributeError as error: except AttributeError as error:
# pylint: disable=raise-missing-from # pylint: disable=raise-missing-from
raise SlitherException( 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): elif isinstance(ir, LibraryCall):
assert isinstance(ir.destination, Contract) assert isinstance(ir.destination, Contract)
assert isinstance(ir.function, Function)
self._high_level_calls.append((ir.destination, ir.function)) self._high_level_calls.append((ir.destination, ir.function))
self._library_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) self._ssa_vars_read.append(origin)
if isinstance(ir, OperationWithLValue): 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 continue # Don't consider Member and Index operations -> ReferenceVariable
var = ir.lvalue var = ir.lvalue
if isinstance(var, ReferenceVariable): if isinstance(var, ReferenceVariable):

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

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

@ -3,9 +3,9 @@ from typing import Dict
class Context: # pylint: disable=too-few-public-methods class Context: # pylint: disable=too-few-public-methods
def __init__(self): def __init__(self) -> None:
super().__init__() super().__init__()
self._context = {"MEMBERS": defaultdict(None)} self._context: Dict = {"MEMBERS": defaultdict(None)}
@property @property
def context(self) -> Dict: 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.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping 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 ( from slither.utils.erc import (
ERC20_signatures, ERC20_signatures,
ERC165_signatures, ERC165_signatures,
@ -20,6 +20,10 @@ from slither.utils.erc import (
ERC1820_signatures, ERC1820_signatures,
ERC777_signatures, ERC777_signatures,
ERC1155_signatures, ERC1155_signatures,
ERC2612_signatures,
ERC1363_signatures,
ERC4524_signatures,
ERC4626_signatures,
) )
from slither.utils.tests_pattern import is_test_contract 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.variable import Variable
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
from slither.core.compilation_unit import SlitherCompilationUnit 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") LOGGER = logging.getLogger("Contract")
@ -48,7 +54,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Contract class Contract class
""" """
def __init__(self, compilation_unit: "SlitherCompilationUnit"): def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
super().__init__() super().__init__()
self._name: Optional[str] = None self._name: Optional[str] = None
@ -68,11 +74,13 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._modifiers: Dict[str, "Modifier"] = {} self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "FunctionContract"] = {} self._functions: Dict[str, "FunctionContract"] = {}
self._linearizedBaseContracts: List[int] = [] self._linearizedBaseContracts: List[int] = []
self._custom_errors: Dict[str, "CustomErrorContract"] = {}
# The only str is "*" # 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._kind: Optional[str] = None
self._is_interface: bool = False self._is_interface: bool = False
self._is_library: bool = False
self._signatures: Optional[List[str]] = None self._signatures: Optional[List[str]] = None
self._signatures_declared: 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._all_functions_called: Optional[List["InternalCallType"]] = None
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit 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): def is_interface(self, is_interface: bool):
self._is_interface = is_interface 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 # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -239,9 +256,41 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
################################################################################### ###################################################################################
@property @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 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 # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -506,7 +555,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def available_elements_from_inheritances( def available_elements_from_inheritances(
self, self,
elements: Dict[str, "Function"], elements: Dict[str, "Function"],
getter_available: Callable[["Contract"], List["Function"]], getter_available: Callable[["Contract"], List["FunctionContract"]],
) -> Dict[str, "Function"]: ) -> Dict[str, "Function"]:
""" """
@ -517,14 +566,16 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
# keep track of the contracts visited # keep track of the contracts visited
# to prevent an ovveride due to multiple inheritance of the same contract # 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 # 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 = {} accessible_elements = {}
contracts_visited = [] contracts_visited = []
for father in self.inheritance_reverse: for father in self.inheritance_reverse:
functions: Dict[str, "Function"] = { functions: Dict[str, "FunctionContract"] = {
v.full_name: v v.full_name: v
for v in getter_available(father) for v in getter_available(father)
if v.contract not in contracts_visited 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) contracts_visited.append(father)
inherited_elements.update(functions) inherited_elements.update(functions)
@ -862,6 +913,9 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
("ERC223", self.is_erc223), ("ERC223", self.is_erc223),
("ERC721", self.is_erc721), ("ERC721", self.is_erc721),
("ERC777", self.is_erc777), ("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()] 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 full_names = self.functions_signatures
return all(s in full_names for s in ERC1155_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 @property
def is_token(self) -> bool: def is_token(self) -> bool:
""" """
@ -1050,14 +1144,16 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._is_upgradeable = False self._is_upgradeable = False
if self.is_upgradeable_proxy: if self.is_upgradeable_proxy:
return False return False
initializable = self.compilation_unit.get_contract_from_name("Initializable") initializable = self.file_scope.get_contract_from_name("Initializable")
if initializable: if initializable:
if initializable in self.inheritance: if initializable in self.inheritance:
self._is_upgradeable = True self._is_upgradeable = True
else: else:
for c in self.inheritance + [self]: for contract in self.inheritance + [self]:
# This might lead to false positive # 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: if "upgradeable" in lower_name or "upgradable" in lower_name:
self._is_upgradeable = True self._is_upgradeable = True
break break
@ -1188,7 +1284,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
) )
# Function uses to create node for state variable declaration statements # 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_offset(variable.source_mapping, self.compilation_unit)
node.set_function(func) node.set_function(func)
func.add_node(node) func.add_node(node)
@ -1219,7 +1315,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
""" """
from slither.slithir.variables import StateIRVariable from slither.slithir.variables import StateIRVariable
all_ssa_state_variables_instances = dict() all_ssa_state_variables_instances = {}
for contract in self.inheritance: for contract in self.inheritance:
for v in contract.state_variables_declared: 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) func.generate_slithir_ssa(all_ssa_state_variables_instances)
def fix_phi(self): def fix_phi(self):
last_state_variables_instances = dict() last_state_variables_instances = {}
initial_state_variables_instances = dict() initial_state_variables_instances = {}
for v in self._initial_state_variables: for v in self._initial_state_variables:
last_state_variables_instances[v.canonical_name] = [] last_state_variables_instances[v.canonical_name] = []
initial_state_variables_instances[v.canonical_name] = v 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._name = name
self._canonical_name = canonical_name self._canonical_name = canonical_name
self._values = values 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 @property
def canonical_name(self) -> str: def canonical_name(self) -> str:
@ -22,5 +25,13 @@ class Enum(SourceMapping):
def values(self) -> List[str]: def values(self) -> List[str]:
return self._values return self._values
@property
def min(self) -> int:
return self._min
@property
def max(self) -> int:
return self._max
def __str__(self): def __str__(self):
return self.name return self.name

@ -1,6 +1,13 @@
from typing import TYPE_CHECKING, List
from slither.core.declarations import Enum from slither.core.declarations import Enum
from slither.core.declarations.top_level import TopLevel from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class EnumTopLevel(Enum, TopLevel): 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.core.expressions.expression import Expression
from slither.slithir.operations import Operation from slither.slithir.operations import Operation
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
LOGGER = logging.getLogger("Function") LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"]) ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
@ -104,14 +105,20 @@ def _filter_state_variables_written(expressions: List["Expression"]):
return ret 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 Function class
""" """
def __init__(self, compilation_unit: "SlitherCompilationUnit"): def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__() super().__init__()
self._scope: List[str] = [] self._internal_scope: List[str] = []
self._name: Optional[str] = None self._name: Optional[str] = None
self._view: bool = False self._view: bool = False
self._pure: 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 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 # region General properties
@ -235,18 +247,18 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
self._name = new_name self._name = new_name
@property @property
def scope(self) -> List[str]: def internal_scope(self) -> List[str]:
""" """
Return a list of name representing the scope of the function Return a list of name representing the scope of the function
This is used to model nested functions declared in YUL This is used to model nested functions declared in YUL
:return: :return:
""" """
return self._scope return self._internal_scope
@scope.setter @internal_scope.setter
def scope(self, new_scope: List[str]): def internal_scope(self, new_scope: List[str]):
self._scope = new_scope self._internal_scope = new_scope
@property @property
def full_name(self) -> str: 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: if self._full_name is None:
name, parameters, _ = self.signature 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 self._full_name = full_name
return self._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" 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 # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -506,7 +538,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
self._nodes = nodes self._nodes = nodes
@property @property
def entry_point(self) -> "Node": def entry_point(self) -> Optional["Node"]:
""" """
Node: Entry point of the function 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 from slither.slithir.variables import Constant
if self._return_values is None: if self._return_values is None:
return_values = list() return_values = []
returns = [n for n in self.nodes if n.type == NodeType.RETURN] returns = [n for n in self.nodes if n.type == NodeType.RETURN]
[ # pylint: disable=expression-not-assigned [ # pylint: disable=expression-not-assigned
return_values.extend(ir.values) 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 from slither.slithir.variables import Constant
if self._return_values_ssa is None: 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] returns = [n for n in self.nodes if n.type == NodeType.RETURN]
[ # pylint: disable=expression-not-assigned [ # pylint: disable=expression-not-assigned
return_values_ssa.extend(ir.values) 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: with open(filename, "w", encoding="utf8") as f:
f.write("digraph{\n") f.write("digraph{\n")
for node in self.nodes: 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: 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") f.write("}\n")
@ -1286,20 +1318,18 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
""" """
def description(node): def description(node):
desc = "{}\n".format(node) desc = f"{node}\n"
desc += "id: {}".format(node.node_id) desc += f"id: {node.node_id}"
if node.dominance_frontier: if node.dominance_frontier:
desc += "\ndominance frontier: {}".format( desc += f"\ndominance frontier: {[n.node_id for n in node.dominance_frontier]}"
[n.node_id for n in node.dominance_frontier]
)
return desc return desc
with open(filename, "w", encoding="utf8") as f: with open(filename, "w", encoding="utf8") as f:
f.write("digraph{\n") f.write("digraph{\n")
for node in self.nodes: 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: 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") f.write("}\n")
@ -1324,22 +1354,22 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
content = "" content = ""
content += "digraph{\n" content += "digraph{\n"
for node in self.nodes: 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: 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: if node.irs and not skip_expressions:
label += "\nIRs:\n" + "\n".join([str(ir) for ir in node.irs]) 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]: if node.type in [NodeType.IF, NodeType.IFLOOP]:
true_node = node.son_true true_node = node.son_true
if true_node: 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 false_node = node.son_false
if false_node: 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: else:
for son in node.sons: 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" content += "}\n"
return content return content
@ -1543,7 +1573,7 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
) -> "Node": ) -> "Node":
from slither.core.cfg.node import 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) node.set_offset(src, self.compilation_unit)
self._counter_nodes += 1 self._counter_nodes += 1
node.set_function(self) 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 from slither.core.cfg.node import NodeType
if not self.is_implemented: if not self.is_implemented:
return dict() return {}
if self._entry_point is None: if self._entry_point is None:
return dict() return {}
# node, values # node, values
to_explore: List[Tuple["Node", Dict]] = [(self._entry_point, dict())] to_explore: List[Tuple["Node", Dict]] = [(self._entry_point, {})]
# node -> values # node -> values
explored: Dict = dict() explored: Dict = {}
# name -> instances # name -> instances
ret: Dict = dict() ret: Dict = {}
while to_explore: while to_explore:
node, values = to_explore[0] node, values = to_explore[0]
@ -1694,6 +1724,6 @@ class Function(metaclass=ABCMeta): # pylint: disable=too-many-public-methods
################################################################################### ###################################################################################
def __str__(self): def __str__(self):
return self._name return self.name
# endregion # 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_contract import ChildContract
from slither.core.children.child_inheritance import ChildInheritance from slither.core.children.child_inheritance import ChildInheritance
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.declarations import Function from slither.core.declarations import Function
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines # pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.declarations import Contract 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 @property
def canonical_name(self) -> str: def canonical_name(self) -> str:
""" """
@ -24,7 +24,7 @@ class FunctionContract(Function, ChildContract, ChildInheritance, SourceMapping)
if self._canonical_name is None: if self._canonical_name is None:
name, parameters, _ = self.signature name, parameters, _ = self.signature
self._canonical_name = ( self._canonical_name = (
".".join([self.contract_declarer.name] + self._scope + [name]) ".".join([self.contract_declarer.name] + self._internal_scope + [name])
+ "(" + "("
+ ",".join(parameters) + ",".join(parameters)
+ ")" + ")"
@ -39,6 +39,10 @@ class FunctionContract(Function, ChildContract, ChildInheritance, SourceMapping)
""" """
return self.contract_declarer == contract return self.contract_declarer == contract
@property
def file_scope(self) -> "FileScope":
return self.contract.file_scope
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################

@ -1,14 +1,25 @@
""" """
Function module Function module
""" """
from typing import List, Tuple from typing import List, Tuple, TYPE_CHECKING
from slither.core.declarations import Function from slither.core.declarations import Function
from slither.core.declarations.top_level import TopLevel 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 @property
def canonical_name(self) -> str: def canonical_name(self) -> str:
""" """
@ -17,7 +28,9 @@ class FunctionTopLevel(Function, TopLevel, SourceMapping):
""" """
if self._canonical_name is None: if self._canonical_name is None:
name, parameters, _ = self.signature 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 return self._canonical_name
# endregion # 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 from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class Import(SourceMapping): class Import(SourceMapping):
def __init__(self, filename: str): def __init__(self, filename: Path, scope: "FileScope"):
super().__init__() super().__init__()
self._filename = filename self._filename: Path = filename
self._alias: Optional[str] = None self._alias: Optional[str] = None
self.scope: "FileScope" = scope
# Map local name -> original name
self.renaming: Dict[str, str] = {}
@property @property
def filename(self) -> str: 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 return self._filename
@property @property

@ -1,12 +1,16 @@
from typing import List from typing import List, TYPE_CHECKING
from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class Pragma(SourceMapping): class Pragma(SourceMapping):
def __init__(self, directive: List[str]): def __init__(self, directive: List[str], scope: "FileScope"):
super().__init__() super().__init__()
self._directive = directive self._directive = directive
self.scope: "FileScope" = scope
@property @property
def directive(self) -> List[str]: 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 typing import List, Dict, Union, TYPE_CHECKING
from slither.core.context.context import Context 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.core.solidity_types import ElementaryType, TypeInformation
from slither.exceptions import SlitherException from slither.exceptions import SlitherException
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.declarations import Import pass
SOLIDITY_VARIABLES = { SOLIDITY_VARIABLES = {
"now": "uint256", "now": "uint256",
@ -19,6 +20,7 @@ SOLIDITY_VARIABLES = {
} }
SOLIDITY_VARIABLES_COMPOSED = { SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint",
"block.coinbase": "address", "block.coinbase": "address",
"block.difficulty": "uint256", "block.difficulty": "uint256",
"block.gaslimit": "uint256", "block.gaslimit": "uint256",
@ -42,6 +44,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"require(bool,string)": [], "require(bool,string)": [],
"revert()": [], "revert()": [],
"revert(string)": [], "revert(string)": [],
"revert ": [],
"addmod(uint256,uint256,uint256)": ["uint256"], "addmod(uint256,uint256,uint256)": ["uint256"],
"mulmod(uint256,uint256,uint256)": ["uint256"], "mulmod(uint256,uint256,uint256)": ["uint256"],
"keccak256()": ["bytes32"], "keccak256()": ["bytes32"],
@ -67,10 +70,16 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"abi.encodePacked()": ["bytes"], "abi.encodePacked()": ["bytes"],
"abi.encodeWithSelector()": ["bytes"], "abi.encodeWithSelector()": ["bytes"],
"abi.encodeWithSignature()": ["bytes"], "abi.encodeWithSignature()": ["bytes"],
"bytes.concat()": ["bytes"],
"string.concat()": ["string"],
# abi.decode returns an a list arbitrary types # abi.decode returns an a list arbitrary types
"abi.decode()": [], "abi.decode()": [],
"type(address)": [], "type(address)": [],
"type()": [], # 0.6.8 changed type(address) to type() "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: Returns:
str str
""" """
return name + " returns({})".format(",".join(SOLIDITY_FUNCTIONS[name])) return name + f" returns({','.join(SOLIDITY_FUNCTIONS[name])})"
class SolidityVariable(Context): class SolidityVariable(Context):
@ -95,7 +104,7 @@ class SolidityVariable(Context):
# dev function, will be removed once the code is stable # dev function, will be removed once the code is stable
def _check_name(self, name: str): # pylint: disable=no-self-use 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 @property
def state_variable(self): def state_variable(self):
@ -186,35 +195,18 @@ class SolidityFunction:
return hash(self.name) return hash(self.name)
class SolidityImportPlaceHolder(SolidityVariable): class SolidityCustomRevert(SolidityFunction):
""" def __init__(self, custom_error: CustomError): # pylint: disable=super-init-not-called
Placeholder for import on top level objects self._name = "revert " + custom_error.solidity_signature
See the example at https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/ self._custom_error = custom_error
In the long term we should remove this and better integrate import aliases self._return_type: List[Union[TypeInformation, ElementaryType]] = []
"""
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")
def __eq__(self, other): def __eq__(self, other):
return ( return (
self.__class__ == other.__class__ self.__class__ == other.__class__
and self.name == other.name 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): 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: if TYPE_CHECKING:
from slither.core.variables.structure_variable import StructureVariable from slither.core.variables.structure_variable import StructureVariable
from slither.core.compilation_unit import SlitherCompilationUnit
class Structure(SourceMapping): class Structure(SourceMapping):
def __init__(self): def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__() super().__init__()
self._name = None self._name = None
self._canonical_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 # Name of the elements in the order of declaration
self._elems_ordered: List[str] = [] self._elems_ordered: List[str] = []
self.compilation_unit = compilation_unit
@property @property
def canonical_name(self) -> str: def canonical_name(self) -> str:

@ -1,6 +1,14 @@
from typing import TYPE_CHECKING
from slither.core.declarations import Structure from slither.core.declarations import Structure
from slither.core.declarations.top_level import TopLevel 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): 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 # %= ASSIGN_MODULO = 10 # %=
@staticmethod @staticmethod
def get_type(operation_type: "AssignmentOperationType"): def get_type(operation_type: str) -> "AssignmentOperationType":
if operation_type == "=": if operation_type == "=":
return AssignmentOperationType.ASSIGN return AssignmentOperationType.ASSIGN
if operation_type == "|=": if operation_type == "|=":
@ -50,9 +50,9 @@ class AssignmentOperationType(Enum):
if operation_type == "%=": if operation_type == "%=":
return AssignmentOperationType.ASSIGN_MODULO 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: if self == AssignmentOperationType.ASSIGN:
return "=" return "="
if self == AssignmentOperationType.ASSIGN_OR: if self == AssignmentOperationType.ASSIGN_OR:
@ -75,7 +75,7 @@ class AssignmentOperationType(Enum):
return "/=" return "/="
if self == AssignmentOperationType.ASSIGN_MODULO: if self == AssignmentOperationType.ASSIGN_MODULO:
return "%=" return "%="
raise SlitherCoreError("str: Unknown operation type {})".format(self)) raise SlitherCoreError(f"str: Unknown operation type {self})")
class AssignmentOperation(ExpressionTyped): class AssignmentOperation(ExpressionTyped):
@ -91,7 +91,7 @@ class AssignmentOperation(ExpressionTyped):
super().__init__() super().__init__()
left_expression.set_lvalue() left_expression.set_lvalue()
self._expressions = [left_expression, right_expression] 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 self._expression_return_type: Optional["Type"] = expression_return_type
@property @property
@ -111,8 +111,8 @@ class AssignmentOperation(ExpressionTyped):
return self._expressions[1] return self._expressions[1]
@property @property
def type(self) -> Optional["Type"]: def type(self) -> Optional["AssignmentOperationType"]:
return self._type return self._type
def __str__(self): def __str__(self) -> str:
return str(self.expression_left) + " " + str(self.type) + " " + str(self.expression_right) return str(self.expression_left) + " " + str(self.type) + " " + str(self.expression_right)

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

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

@ -41,7 +41,7 @@ class UnaryOperationType(Enum):
return UnaryOperationType.PLUSPLUS_POST return UnaryOperationType.PLUSPLUS_POST
if operation_type == "--": if operation_type == "--":
return UnaryOperationType.MINUSMINUS_POST 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): def __str__(self):
if self == UnaryOperationType.BANG: if self == UnaryOperationType.BANG:
@ -62,7 +62,7 @@ class UnaryOperationType(Enum):
]: ]:
return "--" return "--"
raise SlitherCoreError("str: Unknown operation type {}".format(self)) raise SlitherCoreError(f"str: Unknown operation type {self}")
@staticmethod @staticmethod
def is_prefix(operation_type): def is_prefix(operation_type):
@ -82,7 +82,7 @@ class UnaryOperationType(Enum):
]: ]:
return False 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): 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 json
import logging import logging
import os import os
import pathlib
import posixpath
import re import re
from typing import Optional, Dict, List, Set, Union from typing import Optional, Dict, List, Set, Union
@ -110,9 +112,7 @@ class SlitherCore(Context):
""" """
contracts = [] contracts = []
for compilation_unit in self._compilation_units: for compilation_unit in self._compilation_units:
contract = compilation_unit.get_contract_from_name(contract_name) contracts += compilation_unit.get_contract_from_name(contract_name)
if contract:
contracts.append(contract)
return contracts return contracts
################################################################################### ###################################################################################
@ -123,7 +123,7 @@ class SlitherCore(Context):
@property @property
def source_code(self) -> Dict[str, str]: def source_code(self) -> Dict[str, str]:
""" {filename: source_code (str)}: source code """ """{filename: source_code (str)}: source code"""
return self._raw_source_code return self._raw_source_code
@property @property
@ -157,7 +157,7 @@ class SlitherCore(Context):
for compilation_unit in self._compilation_units: for compilation_unit in self._compilation_units:
for c in compilation_unit.contracts: for c in compilation_unit.contracts:
for f in c.functions: 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 # endregion
################################################################################### ###################################################################################
@ -174,7 +174,7 @@ class SlitherCore(Context):
return False return False
mapping_elements_with_lines = ( mapping_elements_with_lines = (
( (
os.path.normpath(elem["source_mapping"]["filename_absolute"]), posixpath.normpath(elem["source_mapping"]["filename_absolute"]),
elem["source_mapping"]["lines"], elem["source_mapping"]["lines"],
) )
for elem in r["elements"] for elem in r["elements"]
@ -219,8 +219,12 @@ class SlitherCore(Context):
for elem in r["elements"] for elem in r["elements"]
if "source_mapping" in elem 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 matching = False
@ -241,14 +245,14 @@ class SlitherCore(Context):
if r["elements"] and matching: if r["elements"] and matching:
return False 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: if self._show_ignored_findings:
return True return True
if r["id"] in self._previous_results_ids:
return False
if self.has_ignore_comment(r): if self.has_ignore_comment(r):
return False 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 # 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] 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 filename = self._previous_results_filename
try: try:
if os.path.isfile(filename): if os.path.isfile(filename):
with open(filename) as f: with open(filename, encoding="utf8") as f:
self._previous_results = json.load(f) self._previous_results = json.load(f)
if self._previous_results: if self._previous_results:
for r in self._previous_results: for r in self._previous_results:
if "id" in r: if "id" in r:
self._previous_results_ids.add(r["id"]) self._previous_results_ids.add(r["id"])
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
logger.error( logger.error(red(f"Impossible to decode {filename}. Consider removing the file"))
red("Impossible to decode {}. Consider removing the file".format(filename))
)
def write_results_to_hide(self): def write_results_to_hide(self):
if not self._results_to_hide: if not self._results_to_hide:
@ -334,6 +336,6 @@ class SlitherCore(Context):
@property @property
def show_ignore_findings(self) -> bool: def show_ignore_findings(self) -> bool:
return self.show_ignore_findings return self._show_ignored_findings
# endregion # endregion

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

@ -34,9 +34,17 @@ class ArrayType(Type):
return self._length return self._length
@property @property
def lenght_value(self) -> Optional[Literal]: def length_value(self) -> Optional[Literal]:
return self._length_value 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 @property
def storage_size(self) -> Tuple[int, bool]: def storage_size(self) -> Tuple[int, bool]:
if self._length_value: if self._length_value:
@ -46,7 +54,7 @@ class ArrayType(Type):
def __str__(self): def __str__(self):
if self._length: 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) + "[]" return str(self._type) + "[]"
def __eq__(self, other): def __eq__(self, other):

@ -43,8 +43,8 @@ Int = [
"int256", "int256",
] ]
Max_Int = {k: 2 ** (8 * i - 1) - 1 if i > 0 else 2 ** 255 - 1 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)} Min_Int = {k: -(2 ** (8 * i - 1)) if i > 0 else -(2**255) for i, k in enumerate(Int)}
Uint = [ Uint = [
"uint", "uint",
@ -82,7 +82,7 @@ Uint = [
"uint256", "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} 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["bytes"] = None
Max_Byte["string"] = None Max_Byte["string"] = None
Max_Byte["byte"] = 255 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["bytes"] = 0x0
Min_Byte["string"] = None Min_Byte["string"] = 0x0
Min_Byte["byte"] = 0x10 Min_Byte["byte"] = 0x0
MaxValues = dict(dict(Max_Int, **Max_Uint), **Max_Byte) MaxValues = dict(dict(Max_Int, **Max_Uint), **Max_Byte)
MinValues = dict(dict(Min_Int, **Min_Uint), **Min_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)) N = list(range(0, 81))
MN = list(itertools.product(M, N)) MN = list(itertools.product(M, N))
Fixed = ["fixed{}x{}".format(m, n) for (m, n) in MN] + ["fixed"] Fixed = [f"fixed{m}x{n}" for (m, n) in MN] + ["fixed"]
Ufixed = ["ufixed{}x{}".format(m, n) for (m, n) in MN] + ["ufixed"] Ufixed = [f"ufixed{m}x{n}" for (m, n) in MN] + ["ufixed"]
ElementaryTypeName = ["address", "bool", "string", "var"] + Int + Uint + Byte + Fixed + Ufixed ElementaryTypeName = ["address", "bool", "string", "var"] + Int + Uint + Byte + Fixed + Ufixed
@ -188,13 +188,13 @@ class ElementaryType(Type):
return int(8) return int(8)
if t == "address": if t == "address":
return int(160) return int(160)
if t.startswith("bytes"): if t.startswith("bytes") and t != "bytes":
return int(t[len("bytes") :]) return int(t[len("bytes") :]) * 8
return None return None
@property @property
def storage_size(self) -> Tuple[int, bool]: def storage_size(self) -> Tuple[int, bool]:
if self._type == "string" or self._type == "bytes": if self._type in ["string", "bytes"]:
return 32, True return 32, True
if self.size is None: if self.size is None:
return 32, True return 32, True

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

@ -24,7 +24,7 @@ class MappingType(Type):
return 32, True return 32, True
def __str__(self): 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): def __eq__(self, other):
if not isinstance(other, MappingType): 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): def __init__(self, c):
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from slither.core.declarations.contract import Contract 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__() super().__init__()
self._type = c self._type = c

@ -8,7 +8,7 @@ if TYPE_CHECKING:
class SourceMapping(Context): class SourceMapping(Context):
def __init__(self): def __init__(self) -> None:
super().__init__() super().__init__()
# TODO create a namedtuple for the source mapping rather than a dict # TODO create a namedtuple for the source mapping rather than a dict
self._source_mapping: Optional[Dict] = None self._source_mapping: Optional[Dict] = None
@ -128,7 +128,7 @@ class SourceMapping(Context):
if not lines: if not lines:
lines = "" lines = ""
elif len(lines) == 1: elif len(lines) == 1:
lines = "#{}{}".format(line_descr, lines[0]) lines = f"#{line_descr}{lines[0]}"
else: else:
lines = f"#{line_descr}{lines[0]}-{line_descr}{lines[-1]}" lines = f"#{line_descr}{lines[0]}-{line_descr}{lines[-1]}"
return lines return lines

@ -58,4 +58,4 @@ class LocalVariable(ChildFunction, Variable):
@property @property
def canonical_name(self) -> str: 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 @property
def canonical_name(self) -> str: def canonical_name(self) -> str:
return "{}.{}".format(self.contract.name, self.name) return f"{self.contract.name}.{self.name}"
@property @property
def full_name(self) -> str: def full_name(self) -> str:

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

@ -10,7 +10,7 @@ from slither.core.solidity_types.elementary_type import ElementaryType
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.expressions.expression import Expression from slither.core.expressions.expression import Expression
# pylint: disable=too-many-instance-attributes
class Variable(SourceMapping): class Variable(SourceMapping):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -20,6 +20,9 @@ class Variable(SourceMapping):
self._initialized: Optional[bool] = None self._initialized: Optional[bool] = None
self._visibility: Optional[str] = None self._visibility: Optional[str] = None
self._is_constant = False self._is_constant = False
self._is_immutable: bool = False
self._is_reentrant: bool = True
self._write_protection: Optional[List[str]] = None
@property @property
def is_scalar(self) -> bool: def is_scalar(self) -> bool:
@ -89,6 +92,22 @@ class Variable(SourceMapping):
def is_constant(self, is_cst: bool): def is_constant(self, is_cst: bool):
self._is_constant = is_cst 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 @property
def visibility(self) -> Optional[str]: def visibility(self) -> Optional[str]:
""" """
@ -106,6 +125,19 @@ class Variable(SourceMapping):
assert isinstance(t, (Type, list)) or t is None assert isinstance(t, (Type, list)) or t is None
self._type = t 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 @property
def function_name(self): def function_name(self):
""" """

@ -1,6 +1,7 @@
import abc import abc
import re 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.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract 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.exceptions import FormatImpossible
from slither.formatters.utils.patches import apply_patch, create_diff from slither.formatters.utils.patches import apply_patch, create_diff
from slither.utils.comparable_enum import ComparableEnum from slither.utils.comparable_enum import ComparableEnum
from slither.utils.output import Output from slither.utils.output import Output, SupportedOutput
if TYPE_CHECKING: if TYPE_CHECKING:
from slither import Slither from slither import Slither
@ -25,8 +26,10 @@ class DetectorClassification(ComparableEnum):
INFORMATIONAL = 3 INFORMATIONAL = 3
OPTIMIZATION = 4 OPTIMIZATION = 4
UNIMPLEMENTED = 999
classification_colors = {
classification_colors: Dict[DetectorClassification, Callable[[str], str]] = {
DetectorClassification.INFORMATIONAL: green, DetectorClassification.INFORMATIONAL: green,
DetectorClassification.OPTIMIZATION: green, DetectorClassification.OPTIMIZATION: green,
DetectorClassification.LOW: green, DetectorClassification.LOW: green,
@ -46,8 +49,8 @@ classification_txt = {
class AbstractDetector(metaclass=abc.ABCMeta): class AbstractDetector(metaclass=abc.ABCMeta):
ARGUMENT = "" # run the detector with slither.py --ARGUMENT ARGUMENT = "" # run the detector with slither.py --ARGUMENT
HELP = "" # help information HELP = "" # help information
IMPACT: Optional[DetectorClassification] = None IMPACT: DetectorClassification = DetectorClassification.UNIMPLEMENTED
CONFIDENCE: Optional[DetectorClassification] = None CONFIDENCE: DetectorClassification = DetectorClassification.UNIMPLEMENTED
WIKI = "" WIKI = ""
@ -58,7 +61,9 @@ class AbstractDetector(metaclass=abc.ABCMeta):
STANDARD_JSON = True 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.compilation_unit: SlitherCompilationUnit = compilation_unit
self.contracts: List[Contract] = compilation_unit.contracts self.contracts: List[Contract] = compilation_unit.contracts
self.slither: "Slither" = slither self.slither: "Slither" = slither
@ -67,27 +72,27 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if not self.HELP: if not self.HELP:
raise IncorrectDetectorInitialization( raise IncorrectDetectorInitialization(
"HELP is not initialized {}".format(self.__class__.__name__) f"HELP is not initialized {self.__class__.__name__}"
) )
if not self.ARGUMENT: if not self.ARGUMENT:
raise IncorrectDetectorInitialization( raise IncorrectDetectorInitialization(
"ARGUMENT is not initialized {}".format(self.__class__.__name__) f"ARGUMENT is not initialized {self.__class__.__name__}"
) )
if not self.WIKI: if not self.WIKI:
raise IncorrectDetectorInitialization( raise IncorrectDetectorInitialization(
"WIKI is not initialized {}".format(self.__class__.__name__) f"WIKI is not initialized {self.__class__.__name__}"
) )
if not self.WIKI_TITLE: if not self.WIKI_TITLE:
raise IncorrectDetectorInitialization( 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: if not self.WIKI_DESCRIPTION:
raise IncorrectDetectorInitialization( 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 [ if not self.WIKI_EXPLOIT_SCENARIO and self.IMPACT not in [
@ -95,17 +100,17 @@ class AbstractDetector(metaclass=abc.ABCMeta):
DetectorClassification.OPTIMIZATION, DetectorClassification.OPTIMIZATION,
]: ]:
raise IncorrectDetectorInitialization( 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: if not self.WIKI_RECOMMENDATION:
raise IncorrectDetectorInitialization( 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: if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
raise IncorrectDetectorInitialization( raise IncorrectDetectorInitialization(
"ARGUMENT has illegal character {}".format(self.__class__.__name__) f"ARGUMENT has illegal character {self.__class__.__name__}"
) )
if self.IMPACT not in [ if self.IMPACT not in [
@ -116,7 +121,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
DetectorClassification.OPTIMIZATION, DetectorClassification.OPTIMIZATION,
]: ]:
raise IncorrectDetectorInitialization( raise IncorrectDetectorInitialization(
"IMPACT is not initialized {}".format(self.__class__.__name__) f"IMPACT is not initialized {self.__class__.__name__}"
) )
if self.CONFIDENCE not in [ if self.CONFIDENCE not in [
@ -127,42 +132,35 @@ class AbstractDetector(metaclass=abc.ABCMeta):
DetectorClassification.OPTIMIZATION, DetectorClassification.OPTIMIZATION,
]: ]:
raise IncorrectDetectorInitialization( 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: if self.logger:
self.logger.info(self.color(info)) self.logger.info(self.color(info))
@abc.abstractmethod @abc.abstractmethod
def _detect(self): def _detect(self) -> List[Output]:
"""TODO Documentation""" """TODO Documentation"""
return [] return []
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def detect(self): def detect(self) -> List[Dict]:
results = [] results: List[Dict] = []
# only keep valid result, and remove dupplicate # only keep valid result, and remove dupplicate
# Keep only dictionaries # 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: if self.compilation_unit.core.valid_result(r) and r not in results:
results.append(r) results.append(r)
if results: if results and self.logger:
if self.logger: self._log_result(results)
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 self.compilation_unit.core.generate_patches: if self.compilation_unit.core.generate_patches:
for result in results: for result in results:
try: try:
self._format(self.compilation_unit, result) self._format(self.compilation_unit, result)
if not "patches" in result: if not "patches" in result:
continue continue
result["patches_diff"] = dict() result["patches_diff"] = {}
for file in result["patches"]: for file in result["patches"]:
original_txt = self.compilation_unit.core.source_code[file].encode("utf8") original_txt = self.compilation_unit.core.source_code[file].encode("utf8")
patched_txt = original_txt patched_txt = original_txt
@ -191,9 +189,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if results and self.slither.triage_mode: if results and self.slither.triage_mode:
while True: while True:
indexes = input( indexes = input(
'Results to hide during next runs: "0,1,...,{}" or "All" (enter to not hide results): '.format( f'Results to hide during next runs: "0,1,...,{len(results)}" or "All" (enter to not hide results): '
len(results)
)
) )
if indexes == "All": if indexes == "All":
self.slither.save_results_to_hide(results) self.slither.save_results_to_hide(results)
@ -205,20 +201,26 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if indexes.endswith("]"): if indexes.endswith("]"):
indexes = indexes[:-1] indexes = indexes[:-1]
try: try:
indexes = [int(i) for i in indexes.split(",")] indexes_converted = [int(i) for i in indexes.split(",")]
self.slither.save_results_to_hide( 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: except ValueError:
self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3")) self.logger.error(yellow("Malformed input. Example of valid input: 0,1,2,3"))
results = sorted(results, key=lambda x: x["id"])
return results return results
@property @property
def color(self): def color(self) -> Callable[[str], str]:
return classification_colors[self.IMPACT] 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( output = Output(
info, info,
additional_fields, additional_fields,
@ -233,6 +235,15 @@ class AbstractDetector(metaclass=abc.ABCMeta):
return output return output
@staticmethod @staticmethod
def _format(_compilation_unit: SlitherCompilationUnit, _result): def _format(_compilation_unit: SlitherCompilationUnit, _result: Dict) -> None:
"""Implement format""" """Implement format"""
return 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.constant_pragma import ConstantPragma
from .attributes.incorrect_solc import IncorrectSolc from .attributes.incorrect_solc import IncorrectSolc
from .attributes.locked_ether import LockedEther 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.suicidal import Suicidal
# from .functions.complex_function import ComplexFunction # from .functions.complex_function import ComplexFunction
@ -34,7 +36,7 @@ from .shadowing.builtin_symbols import BuiltinSymbolShadowing
from .operations.block_timestamp import Timestamp from .operations.block_timestamp import Timestamp
from .statements.calls_in_loop import MultipleCallsInLoop from .statements.calls_in_loop import MultipleCallsInLoop
from .statements.incorrect_strict_equality import IncorrectStrictEquality 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.incorrect_erc721_interface import IncorrectERC721InterfaceDetection
from .erc.unindexed_event_parameters import UnindexedERC20EventParameters from .erc.unindexed_event_parameters import UnindexedERC20EventParameters
from .statements.deprecated_calls import DeprecatedStandards from .statements.deprecated_calls import DeprecatedStandards
@ -79,6 +81,6 @@ from .statements.unary import IncorrectUnaryExpressionDetection
from .operations.missing_zero_address_validation import MissingZeroAddressValidation from .operations.missing_zero_address_validation import MissingZeroAddressValidation
from .functions.dead_code import DeadCode from .functions.dead_code import DeadCode
from .statements.write_after_write import WriteAfterWrite 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 IMPACT = DetectorClassification.HIGH
CONFIDENCE = 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_TITLE = "Incorrect shift in assembly."
WIKI_DESCRIPTION = "Detect if the values in a shift operation are reversed" WIKI_DESCRIPTION = "Detect if the values in a shift operation are reversed"

@ -29,7 +29,7 @@ class ConstantPragma(AbstractDetector):
versions = sorted(list(set(versions))) versions = sorted(list(set(versions)))
if len(versions) > 1: 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"] info += [f"\t- Version used: {[str(v) for v in versions]}\n"]
for p in pragma: for p in pragma:

@ -14,7 +14,7 @@ from slither.formatters.attributes.incorrect_solc import custom_format
# 4: version number # 4: version number
# pylint: disable=anomalous-backslash-in-string # pylint: disable=anomalous-backslash-in-string
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)") PATTERN = re.compile(r"(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
class IncorrectSolc(AbstractDetector): class IncorrectSolc(AbstractDetector):
@ -43,6 +43,7 @@ Deploy with any of the following Solidity versions:
- 0.5.16 - 0.5.17 - 0.5.16 - 0.5.17
- 0.6.11 - 0.6.12 - 0.6.11 - 0.6.12
- 0.7.5 - 0.7.6 - 0.7.5 - 0.7.6
- 0.8.4 - 0.8.7
Use a simple pragma version that allows any of these versions. Use a simple pragma version that allows any of these versions.
Consider using the latest version of Solidity for testing.""" Consider using the latest version of Solidity for testing."""
# endregion wiki_recommendation # endregion wiki_recommendation
@ -51,9 +52,7 @@ Consider using the latest version of Solidity for testing."""
OLD_VERSION_TXT = "allows old versions" OLD_VERSION_TXT = "allows old versions"
LESS_THAN_TXT = "uses lesser than" LESS_THAN_TXT = "uses lesser than"
TOO_RECENT_VERSION_TXT = ( TOO_RECENT_VERSION_TXT = "necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6/0.8.7"
"necessitates a version too recent to be trusted. Consider deploying with 0.6.12/0.7.6"
)
BUGGY_VERSION_TXT = ( BUGGY_VERSION_TXT = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)" "is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
) )
@ -66,6 +65,10 @@ Consider using the latest version of Solidity for testing."""
"0.6.12", "0.6.12",
"0.7.5", "0.7.5",
"0.7.6", "0.7.6",
"0.8.4",
"0.8.5",
"0.8.6",
"0.8.7",
] ]
# Indicates the versions that should not be used. # Indicates the versions that should not be used.
@ -80,6 +83,8 @@ Consider using the latest version of Solidity for testing."""
"^0.5.14", "^0.5.14",
"0.6.9", "0.6.9",
"^0.6.9", "^0.6.9",
"0.8.8",
"^0.8.8",
] ]
def _check_version(self, version): def _check_version(self, version):
@ -87,6 +92,8 @@ Consider using the latest version of Solidity for testing."""
if op and op not in [">", ">=", "^"]: if op and op not in [">", ">=", "^"]:
return self.LESS_THAN_TXT return self.LESS_THAN_TXT
version_number = ".".join(version[2:]) 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 version_number not in self.ALLOWED_VERSIONS:
if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split("."))): if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split("."))):
return self.TOO_RECENT_VERSION_TXT return self.TOO_RECENT_VERSION_TXT
@ -144,11 +151,20 @@ Consider using the latest version of Solidity for testing."""
results.append(json) results.append(json)
if self.compilation_unit.solc_version not in self.ALLOWED_VERSIONS: if self.compilation_unit.solc_version not in self.ALLOWED_VERSIONS:
info = [
"solc-", if self.compilation_unit.solc_version in self.BUGGY_VERSIONS:
self.compilation_unit.solc_version, info = [
" is not recommended for deployment\n", "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) json = self.generate_result(info)

@ -87,7 +87,7 @@ contract Something {
if not intended_interface_is_subset_parent: if not intended_interface_is_subset_parent:
# Should not be a subset of an earlier determined intended_interface or derive from it # Should not be a subset of an earlier determined intended_interface or derive from it
intended_interface_is_subset_intended = False intended_interface_is_subset_intended = False
for intended_interface in intended_interfaces: for intended_interface in list(intended_interfaces):
sigs_intended_interface = { sigs_intended_interface = {
f.full_name for f in intended_interface.functions_entry_points 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. :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]} :return: Dictionary of function:list(tuple): { constructor : [(invoking_contract, called_by_constructor]}
""" """
results = dict() results = {}
# Create a set to track all completed contracts # Create a set to track all completed contracts
processed_contracts = set() 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 = "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." 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 # 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() function.contract_declarer.is_from_dependency()
): ):
continue 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"] info = [function, " is never used and should be removed\n"]
res = self.generate_result(info) res = self.generate_result(info)
results.append(res) 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: if func.is_constructor:
continue continue
if not self.is_mixed_case(func.name): if not self.is_mixed_case(func.name):
if ( if func.visibility in [
func.visibility "internal",
in [ "private",
"internal", ] and self.is_mixed_case_with_underscore(func.name):
"private",
]
and self.is_mixed_case_with_underscore(func.name)
):
continue continue
if func.name.startswith("echidna_") or func.name.startswith("crytic_"): if func.name.startswith(("echidna_", "crytic_")):
continue continue
info = ["Function ", func, " is not in mixedCase\n"] 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 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.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 ( from slither.core.declarations.solidity_variables import (
SolidityVariable, SolidityVariable,
SolidityFunction, SolidityFunction,
SolidityVariableComposed, SolidityVariableComposed,
) )
from slither.core.variables.variable import Variable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import BinaryType, Binary from slither.slithir.operations import BinaryType, Binary
from slither.slithir.operations import SolidityCall 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() Return the return-values of calls to blockhash()
Args: Args:
@ -33,7 +39,7 @@ def collect_return_values_of_bad_PRNG_functions(f):
return values_returned 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() Check if any node in function has a modulus operator and the first operand is dependent on block.timestamp, now or blockhash()
Returns: Returns:
@ -57,7 +63,7 @@ def contains_bad_PRNG_sources(func, blockhash_ret_values):
return list(ret) return list(ret)
def detect_bad_PRNG(contract): def detect_bad_PRNG(contract: Contract) -> List[Tuple[Function, List[Node]]]:
""" """
Args: Args:
contract (Contract) contract (Contract)
@ -67,7 +73,7 @@ def detect_bad_PRNG(contract):
blockhash_ret_values = [] blockhash_ret_values = []
for f in contract.functions: for f in contract.functions:
blockhash_ret_values += collect_return_values_of_bad_PRNG_functions(f) blockhash_ret_values += collect_return_values_of_bad_PRNG_functions(f)
ret = [] ret: List[Tuple[Function, List[Node]]] = []
for f in contract.functions: for f in contract.functions:
bad_prng_nodes = contains_bad_PRNG_sources(f, blockhash_ret_values) bad_prng_nodes = contains_bad_PRNG_sources(f, blockhash_ret_values)
if bad_prng_nodes: 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" "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""" """Detect bad PRNG due to the use of block.timestamp, now or blockhash (block.blockhash) as a source of randomness"""
results = [] results = []
for c in self.compilation_unit.contracts_derived: 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 func, nodes in values:
for node in nodes: 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) res = self.generate_result(info)
results.append(res) 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 # endregion wiki_exploit_scenario

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

@ -2,19 +2,28 @@
Module detecting shadowing variables on abstract contract Module detecting shadowing variables on abstract contract
Recursively check the called functions 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.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output, AllSupportedOutput
from .common import is_upgradable_gap_variable
def detect_shadowing(contract): def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
ret = [] ret: List[List[StateVariable]] = []
variables_fathers = [] variables_fathers = []
for father in contract.inheritance: 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 variables_fathers += father.state_variables_declared
var: StateVariable
for var in contract.state_variables_declared: 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: if shadow:
ret.append([var] + shadow) ret.append([var] + shadow)
return ret return ret
@ -51,7 +60,7 @@ contract DerivedContract is BaseContract{
WIKI_RECOMMENDATION = "Remove the state variable shadowing." WIKI_RECOMMENDATION = "Remove the state variable shadowing."
def _detect(self): def _detect(self) -> List[Output]:
"""Detect shadowing """Detect shadowing
Recursively visit the calls Recursively visit the calls
@ -59,14 +68,14 @@ contract DerivedContract is BaseContract{
list: {'vuln', 'filename,'contract','func', 'shadow'} list: {'vuln', 'filename,'contract','func', 'shadow'}
""" """
results = [] results: List[Output] = []
for contract in self.contracts: for contract in self.contracts:
shadowing = detect_shadowing(contract) shadowing = detect_shadowing(contract)
if shadowing: if shadowing:
for all_variables in shadowing: for all_variables in shadowing:
shadow = all_variables[0] shadow = all_variables[0]
variables = all_variables[1:] variables = all_variables[1:]
info = [shadow, " shadows:\n"] info: List[AllSupportedOutput] = [shadow, " shadows:\n"]
for var in variables: for var in variables:
info += ["\t- ", var, "\n"] 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.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 = [] ret = []
variables_fathers = [] variables_fathers = []
for father in contract.inheritance: for father in contract.inheritance:
@ -13,6 +15,10 @@ def detect_shadowing(contract):
variables_fathers += father.state_variables_declared variables_fathers += father.state_variables_declared
for var in contract.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] shadow = [v for v in variables_fathers if v.name == var.name]
if shadow: if shadow:
ret.append([var] + 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 def _detect(self): # pylint: disable=too-many-locals,too-many-branches
results = [] results = []
compilation_unit = self.compilation_unit 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 # First show the contracts that we know are missing
incorrectly_constructed = [ incorrectly_constructed = [

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

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

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

Loading…
Cancel
Save