Merge branch 'dev' into dev-baseline-tests

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

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

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

@ -0,0 +1,42 @@
---
name: IR tests
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
jobs:
build:
name: IR tests
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
solc-select install all
solc-select use 0.8.11
- name: Test with pytest
run: |
pytest tests/test_ssa_generation.py

@ -11,7 +11,7 @@ on:
branches: [master, dev] 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:v4 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

@ -11,29 +11,30 @@ 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: Features tests name: Features 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.8.0 solc-select use 0.8.0
@ -44,4 +45,5 @@ jobs:
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest tests/test_features.py 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:v4 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

@ -1,3 +1,4 @@
---
name: pip-audit name: pip-audit
on: on:
@ -10,18 +11,25 @@ on:
jobs: jobs:
audit: audit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Set up Python 3.10
uses: actions/setup-python@v2 - name: Install Python
with: uses: actions/setup-python@v4
python-version: "3.10" with:
- name: Install pip-audit python-version: "3.10"
run: |
python -m pip install --upgrade pip - name: Install Slither
python -m pip install pip-audit run: |
- name: Run pip-audit python -m venv /tmp/pip-audit-env
run: | source /tmp/pip-audit-env/bin/activate
python -m pip install .
pip-audit --desc -v 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:v4 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.12.2` black `21.10b0`. 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`

@ -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
@ -121,10 +125,12 @@ Num | Detector | What it Detects | Impact | Confidence
70 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium 70 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium 71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium 72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-are-too-similar) | Informational | Medium 73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium 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 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 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
@ -143,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.
@ -155,6 +162,7 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d
- `slither-flat`: [Flatten a codebase](https://github.com/crytic/slither/wiki/Contract-Flattening) - `slither-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.
@ -162,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
@ -181,7 +189,7 @@ We recommend using a Python virtual environment, as detailed in the [Developer I
### 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
@ -205,11 +213,23 @@ Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation. * 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
@ -225,5 +245,8 @@ Title | Usage | Authors | Venue
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20 [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 [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) [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]
)
)

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

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

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

@ -8,16 +8,27 @@ setup(
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.2", 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.2", # "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=long_description, long_description=long_description,
entry_points={ entry_points={
@ -32,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",
] ]
}, },
) )

@ -64,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)
@ -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
@ -296,6 +299,9 @@ 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(
@ -309,7 +315,7 @@ 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, "
f"available printers: {', '.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",
@ -389,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)',
@ -426,14 +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",
type=check_and_sanitize_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",
@ -462,7 +482,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
help="Provide a config file (default: slither.config.json)", 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(
@ -484,12 +504,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False) 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
) )
@ -645,7 +659,7 @@ 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 = {}
@ -773,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:

@ -14,8 +14,9 @@ from slither.core.declarations import (
Structure, Structure,
) )
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder 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,
@ -38,10 +39,14 @@ if TYPE_CHECKING:
################################################################################### ###################################################################################
Variable_types = Union[Variable, SolidityVariable]
Context_types = Union[Contract, Function]
def is_dependent( def is_dependent(
variable: Variable, variable: Variable_types,
source: Variable, source: Variable_types,
context: Union[Contract, Function], context: Context_types,
only_unprotected: bool = False, only_unprotected: bool = False,
) -> bool: ) -> bool:
""" """
@ -69,9 +74,9 @@ def is_dependent(
def is_dependent_ssa( def is_dependent_ssa(
variable: Variable, variable: Variable_types,
source: Variable, source: Variable_types,
context: Union[Contract, Function], context: Context_types,
only_unprotected: bool = False, only_unprotected: bool = False,
) -> bool: ) -> bool:
""" """
@ -105,7 +110,12 @@ GENERIC_TAINT = {
} }
def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=False): def is_tainted(
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
) -> bool:
""" """
Args: Args:
variable variable
@ -127,7 +137,12 @@ def is_tainted(variable, context, only_unprotected=False, ignore_generic_taint=F
) )
def is_tainted_ssa(variable, context, only_unprotected=False, ignore_generic_taint=False): def is_tainted_ssa(
variable: Variable_types,
context: Context_types,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
):
""" """
Args: Args:
variable variable
@ -150,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]:
""" """
@ -170,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.
@ -187,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]:
""" """
@ -207,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.
@ -249,19 +264,19 @@ KEY_INPUT_SSA = "DATA_DEPENDENCY_INPUT_SSA"
################################################################################### ###################################################################################
def pprint_dependency(context): def pprint_dependency(caller_context: Context_types) -> None:
print("#### SSA ####") 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
@ -272,7 +287,7 @@ def pprint_dependency(context):
################################################################################### ###################################################################################
def compute_dependency(compilation_unit: "SlitherCompilationUnit"): def compute_dependency(compilation_unit: "SlitherCompilationUnit") -> None:
compilation_unit.context[KEY_INPUT] = set() compilation_unit.context[KEY_INPUT] = set()
compilation_unit.context[KEY_INPUT_SSA] = set() compilation_unit.context[KEY_INPUT_SSA] = set()
@ -280,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] = {} contract.context[KEY_SSA] = {}
contract.context[KEY_SSA_UNPROTECTED] = {} 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)
@ -302,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]
@ -313,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()
@ -336,11 +357,11 @@ def transitive_close_dependencies(context, context_key, context_key_non_ssa):
context.context[context_key_non_ssa] = convert_to_non_ssa(context.context[context_key]) 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:
@ -361,7 +382,7 @@ def add_dependency(lvalue, function, ir, is_protected):
] ]
def compute_dependency_function(function): def compute_dependency_function(function: Function) -> None:
if KEY_SSA in function.context: if KEY_SSA in function.context:
return return
@ -386,7 +407,7 @@ def compute_dependency_function(function):
) )
def convert_variable_to_non_ssa(v): def convert_variable_to_non_ssa(v: Variable_types) -> Variable_types:
if isinstance( if isinstance(
v, v,
( (
@ -410,14 +431,17 @@ def convert_variable_to_non_ssa(v):
Function, Function,
Type, Type,
SolidityImportPlaceHolder, 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 = {} 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:

@ -39,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 (
@ -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
@ -917,6 +916,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
) )
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))

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

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

@ -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
@ -73,9 +77,10 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._custom_errors: Dict[str, "CustomErrorContract"] = {} 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
@ -142,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
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -243,7 +256,7 @@ 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 # endregion
@ -900,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()]
@ -974,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:
""" """

@ -56,13 +56,24 @@ class CustomError(SourceMapping):
Contract and converted into address Contract and converted into address
:return: the solidity signature :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: if self._solidity_signature is None:
parameters = [ raise ValueError("Custom Error not yet built")
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature 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 # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################

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

@ -538,7 +538,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
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
""" """
@ -1304,9 +1304,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
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")
@ -1318,20 +1318,18 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
""" """
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")
@ -1356,22 +1354,22 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
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
@ -1726,6 +1724,6 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
################################################################################### ###################################################################################
def __str__(self): def __str__(self):
return self._name return self.name
# endregion # endregion

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

@ -71,6 +71,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"abi.encodeWithSelector()": ["bytes"], "abi.encodeWithSelector()": ["bytes"],
"abi.encodeWithSignature()": ["bytes"], "abi.encodeWithSignature()": ["bytes"],
"bytes.concat()": ["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)": [],
@ -92,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):
@ -103,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):

@ -50,7 +50,7 @@ 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) -> str: def __str__(self) -> str:
if self == AssignmentOperationType.ASSIGN: if self == AssignmentOperationType.ASSIGN:
@ -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):

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

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

@ -6,6 +6,8 @@ from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel from slither.core.declarations.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.solidity_types import TypeAlias
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.variables import Constant from slither.slithir.variables import Constant
@ -36,6 +38,16 @@ class FileScope:
self.imports: Set[Import] = set() self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set() self.pragmas: Set[Pragma] = set()
self.structures: Dict[str, StructureTopLevel] = {} 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: def add_accesible_scopes(self) -> bool:
""" """
@ -69,6 +81,15 @@ class FileScope:
if not _dict_contain(new_scope.structures, self.structures): if not _dict_contain(new_scope.structures, self.structures):
self.structures.update(new_scope.structures) self.structures.update(new_scope.structures)
learn_something = True 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 return learn_something

@ -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
@ -155,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
################################################################################### ###################################################################################
@ -172,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"]
@ -217,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
@ -239,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]
@ -261,9 +267,7 @@ class SlitherCore(Context):
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:
@ -332,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:

@ -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,8 +188,8 @@ 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

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

@ -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__()
@ -21,6 +21,8 @@ class Variable(SourceMapping):
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_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:
@ -90,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]:
""" """

@ -208,6 +208,8 @@ class AbstractDetector(metaclass=abc.ABCMeta):
return [r for (idx, r) in enumerate(results) if idx not in indexes_converted] 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

@ -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
@ -81,3 +83,4 @@ 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.msg_value_in_loop import MsgValueInLoop
from .statements.delegatecall_in_loop import DelegatecallInLoop 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):

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

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

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

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

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

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

@ -68,6 +68,9 @@ contract Contract{
function.contract_declarer.is_from_dependency() 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"]

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

@ -8,6 +8,7 @@ from slither.core.declarations import Contract
from slither.core.variables.state_variable import StateVariable 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 slither.utils.output import Output, AllSupportedOutput
from .common import is_upgradable_gap_variable
def detect_shadowing(contract: Contract) -> List[List[StateVariable]]: def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
@ -19,6 +20,9 @@ def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
var: StateVariable var: StateVariable
for var in contract.state_variables_declared: for var in contract.state_variables_declared:
if is_upgradable_gap_variable(contract, var):
continue
shadow: List[StateVariable] = [v for v in variables_fathers if v.name == var.name] 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)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save