Merge branch 'dev' into dev-reworked-fail-on

pull/1462/head
alpharush 1 year ago committed by GitHub
commit 1bfaf2a151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 38
      .github/DISCUSSION_TEMPLATE/installation.yml
  2. 2
      .github/ISSUE_TEMPLATE/bug_report.yml
  3. 61
      .github/ISSUE_TEMPLATE/false_negative.yml
  4. 61
      .github/ISSUE_TEMPLATE/false_positive.yml
  5. 30
      .github/actions/upload-coverage/action.yml
  6. 13
      .github/scripts/integration_test_runner.sh
  7. 13
      .github/scripts/tool_test_runner.sh
  8. 13
      .github/scripts/unit_test_runner.sh
  9. 42
      .github/workflows/IR.yml
  10. 17
      .github/workflows/black.yml
  11. 20
      .github/workflows/ci.yml
  12. 42
      .github/workflows/detectors.yml
  13. 4
      .github/workflows/docker.yml
  14. 46
      .github/workflows/docs.yml
  15. 91
      .github/workflows/doctor.yml
  16. 52
      .github/workflows/features.yml
  17. 18
      .github/workflows/linter.yml
  18. 45
      .github/workflows/parser.yml
  19. 8
      .github/workflows/pip-audit.yml
  20. 24
      .github/workflows/pylint.yml
  21. 50
      .github/workflows/read_storage.yml
  22. 105
      .github/workflows/test.yml
  23. 4
      .gitignore
  24. 6
      CODEOWNERS
  25. 72
      CONTRIBUTING.md
  26. 5
      Dockerfile
  27. 88
      Makefile
  28. 243
      README.md
  29. 10
      examples/flat/a.sol
  30. 13
      examples/flat/b.sol
  31. 6
      examples/scripts/data_dependency.py
  32. 1
      examples/scripts/variable_in_condition.py
  33. 6
      scripts/ci_test_cli.sh
  34. 5
      scripts/ci_test_dapp.sh
  35. 2
      scripts/ci_test_erc.sh
  36. 12
      scripts/ci_test_etherscan.sh
  37. 10
      scripts/ci_test_flat.sh
  38. 95
      scripts/ci_test_interface.sh
  39. 2
      scripts/ci_test_path_filtering.sh
  40. 15
      scripts/ci_test_printers.sh
  41. 28
      scripts/ci_test_upgradability.sh
  42. 36
      setup.py
  43. 3
      slither/__init__.py
  44. 106
      slither/__main__.py
  45. 147
      slither/analyses/data_dependency/data_dependency.py
  46. 2
      slither/analyses/evm/convert.py
  47. 29
      slither/analyses/write/are_variables_written.py
  48. 249
      slither/core/cfg/node.py
  49. 2
      slither/core/cfg/scope.py
  50. 19
      slither/core/children/child_contract.py
  51. 17
      slither/core/children/child_event.py
  52. 17
      slither/core/children/child_expression.py
  53. 17
      slither/core/children/child_function.py
  54. 17
      slither/core/children/child_inheritance.py
  55. 31
      slither/core/children/child_node.py
  56. 17
      slither/core/children/child_structure.py
  57. 47
      slither/core/compilation_unit.py
  58. 4
      slither/core/declarations/__init__.py
  59. 248
      slither/core/declarations/contract.py
  60. 29
      slither/core/declarations/contract_level.py
  61. 23
      slither/core/declarations/custom_error.py
  62. 12
      slither/core/declarations/custom_error_contract.py
  63. 2
      slither/core/declarations/custom_error_top_level.py
  64. 4
      slither/core/declarations/enum.py
  65. 4
      slither/core/declarations/enum_contract.py
  66. 4
      slither/core/declarations/enum_top_level.py
  67. 8
      slither/core/declarations/event.py
  68. 115
      slither/core/declarations/function.py
  69. 39
      slither/core/declarations/function_contract.py
  70. 9
      slither/core/declarations/function_top_level.py
  71. 2
      slither/core/declarations/import_directive.py
  72. 4
      slither/core/declarations/pragma_directive.py
  73. 8
      slither/core/declarations/solidity_import_placeholder.py
  74. 47
      slither/core/declarations/solidity_variables.py
  75. 14
      slither/core/declarations/structure.py
  76. 4
      slither/core/declarations/structure_contract.py
  77. 2
      slither/core/declarations/structure_top_level.py
  78. 6
      slither/core/declarations/top_level.py
  79. 19
      slither/core/declarations/using_for_top_level.py
  80. 13
      slither/core/dominators/utils.py
  81. 7
      slither/core/expressions/assignment_operation.py
  82. 3
      slither/core/expressions/binary_operation.py
  83. 14
      slither/core/expressions/call_expression.py
  84. 21
      slither/core/expressions/conditional_expression.py
  85. 5
      slither/core/expressions/elementary_type_name_expression.py
  86. 20
      slither/core/expressions/expression_typed.py
  87. 78
      slither/core/expressions/identifier.py
  88. 28
      slither/core/expressions/index_access.py
  89. 23
      slither/core/expressions/literal.py
  90. 7
      slither/core/expressions/member_access.py
  91. 26
      slither/core/expressions/new_array.py
  92. 4
      slither/core/expressions/new_contract.py
  93. 32
      slither/core/expressions/type_conversion.py
  94. 23
      slither/core/expressions/unary_operation.py
  95. 123
      slither/core/scope/scope.py
  96. 90
      slither/core/slither_core.py
  97. 28
      slither/core/solidity_types/array_type.py
  98. 10
      slither/core/solidity_types/elementary_type.py
  99. 6
      slither/core/solidity_types/function_type.py
  100. 18
      slither/core/solidity_types/mapping_type.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,38 @@
---
body:
-
attributes:
label: "What operating system are you using?"
id: os
type: textarea
validations:
required: true
-
attributes:
label: "How did you install slither?"
description: |
For example, using git or python's pip.
id: install-method
type: textarea
validations:
required: true
- type: dropdown
id: python
attributes:
label: Do you have python added to your $PATH?
multiple: true
options:
- "Yes"
- "No"
- "Not sure"
-
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: "Output of running `slither-doctor .`:"
id: logs
labels:
- installation-help
title: "[Installation-Help]: "

@ -4,6 +4,8 @@ body:
attributes:
value: |
Please check the issues tab to avoid duplicates.
If you are having difficulty installing slither,
please head over to the "Discussions" page.
Thanks for taking the time to fill out this bug report!
type: markdown
-

@ -0,0 +1,61 @@
---
body:
-
attributes:
value: |
Please check the issues tab to avoid duplicates.
Thanks for helping make Slither the best it can be!
type: markdown
-
attributes:
label: "What bug did Slither miss and which detector did you anticipate would catch it?"
id: what-happened
type: textarea
validations:
required: true
-
attributes:
label: Frequency
description: How often do you run across this false negative?
options:
- Very Frequently
- Occasionally
- Rarely
- Not sure
id: frequency
type: dropdown
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 the result output. This
will be automatically formatted into code, so no need for backticks.
render: shell
label: "Relevant log output:"
id: logs
type: textarea
description: "Slither missed a bug it should find."
labels:
- false-negative
name: False Negative
title: "[False Negative]: "

@ -0,0 +1,61 @@
---
body:
-
attributes:
value: |
Please check the issues tab to avoid duplicates.
Thanks for helping make Slither the best it can be!
type: markdown
-
attributes:
label: "Describe the false alarm that Slither raise and how you know it's inaccurate:"
id: what-happened
type: textarea
validations:
required: true
-
attributes:
label: Frequency
description: How often do you run across this false positive?
options:
- Very Frequently
- Occasionally
- Rarely
- Not sure
id: frequency
type: dropdown
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 the result output. This
will be automatically formatted into code, so no need for backticks.
render: shell
label: "Relevant log output:"
id: logs
type: textarea
description: "Slither warned of an issue that is not legitimate and does not need to be fixed."
labels:
- false-positive
name: "False Positive"
title: "[False-Positive]: "

@ -0,0 +1,30 @@
# Derived from <https://github.com/pyca/cryptography/blob/SOME_REF/.github/actions/upload-coverage/action.yml>
# Originally authored by the PyCA Cryptography maintainers, and licensed under
# the terms of the BSD license:
# <https://github.com/pyca/cryptography/blob/main/LICENSE.BSD>
name: Upload Coverage
description: Upload coverage files
runs:
using: "composite"
steps:
# FIXME(jl): codecov has the option of including machine information in filename that would solve this unique naming
# issue more completely.
# This method has the limitation of 1 coverage file per run, limiting some coverage between online/offline tests.
- run: |
COVERAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "COVERAGE_UUID=${COVERAGE_UUID}" >> $GITHUB_OUTPUT
if [ -f .coverage ]; then
mv .coverage .coverage.${COVERAGE_UUID}
fi
id: coverage-uuid
shell: bash
- uses: actions/upload-artifact@v3.1.0
with:
name: coverage-data
path: |
.coverage.*
*.lcov
if-no-files-found: ignore

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# used to pass --cov=$path and --cov-append to pytest
if [ "$1" != "" ]; then
pytest "$1" tests/e2e/ -n auto
status_code=$?
python -m coverage report
else
pytest tests/e2e/ -n auto
status_code=$?
fi
exit "$status_code"

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# used to pass --cov=$path and --cov-append to pytest
if [ "$1" != "" ]; then
pytest "$1" tests/tools/read-storage/test_read_storage.py
status_code=$?
python -m coverage report
else
pytest tests/tools/read-storage/test_read_storage.py
status_code=$?
fi
exit "$status_code"

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# used to pass --cov=$path and --cov-append to pytest
if [ "$1" != "" ]; then
pytest "$1" tests/unit/ -n auto
status_code=$?
python -m coverage report
else
pytest tests/unit/ -n auto
status_code=$?
fi
exit "$status_code"

@ -1,42 +0,0 @@
---
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

@ -1,5 +1,5 @@
---
name: Lint Code Base
name: Run black
defaults:
run:
@ -9,18 +9,27 @@ defaults:
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Lint Code Base
name: Black
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
@ -33,7 +42,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Black
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -15,6 +15,10 @@ on:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
runs-on: ${{ matrix.os }}
@ -29,9 +33,10 @@ jobs:
# "embark",
"erc",
# "etherlime",
# "etherscan"
"etherscan",
"find_paths",
"flat",
"interface",
"kspec",
"printers",
# "prop"
@ -47,22 +52,21 @@ jobs:
- os: windows-2022
type: truffle
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
solc-select install all
solc-select use 0.5.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install ".[test]"
solc-select use 0.4.25 --always-install
solc-select use 0.8.0 --always-install
solc-select use 0.5.1 --always-install
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v16
uses: cachix/install-nix-action@v20
- name: Set up cachix
if: matrix.type == 'dapp'

@ -1,42 +0,0 @@
---
name: Detectors 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: Detectors 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.7.3
- name: Test with pytest
run: |
pytest tests/test_detectors.py

@ -8,6 +8,10 @@ on:
tags:
- '*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
docker:
runs-on: ubuntu-latest

@ -0,0 +1,46 @@
name: docs
on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
# Single deploy job since we're just deploying
build:
environment:
name: Slither Documentation
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v3
- uses: actions/setup-python@v4
with:
python-version: '3.8'
- run: pip install -e ".[doc]"
- run: pdoc -o html/ slither '!slither.tools' #TODO fix import errors on pdoc run
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
# Upload the doc
path: './html/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

@ -0,0 +1,91 @@
---
name: Doctor
defaults:
run:
shell: bash
on:
workflow_dispatch:
pull_request:
paths:
- 'slither/tools/doctor/**'
- '.github/workflows/doctor.yml'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
slither-doctor:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
python: ["3.8", "3.9", "3.10", "3.11"]
exclude:
# strange failure
- os: windows-2022
python: 3.8
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- name: Try system-wide Slither
run: |
echo "::group::Install slither"
pip3 install .
echo "::endgroup::"
# escape cwd so python doesn't pick up local module
cd /
echo "::group::Via module"
python3 -m slither.tools.doctor .
echo "::endgroup::"
echo "::group::Via binary"
slither-doctor .
echo "::endgroup::"
- name: Try user Slither
run: |
echo "::group::Install slither"
pip3 install --user .
echo "::endgroup::"
# escape cwd so python doesn't pick up local module
cd /
echo "::group::Via module"
python3 -m slither.tools.doctor .
echo "::endgroup::"
echo "::group::Via binary"
slither-doctor .
echo "::endgroup::"
- name: Try venv Slither
run: |
echo "::group::Install slither"
python3 -m venv venv
source venv/bin/activate || source venv/Scripts/activate
hash -r
pip3 install .
echo "::endgroup::"
# escape cwd so python doesn't pick up local module
cd /
echo "::group::Via module"
python3 -m slither.tools.doctor .
echo "::endgroup::"
echo "::group::Via binary"
slither-doctor .
echo "::endgroup::"

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

@ -9,18 +9,28 @@ defaults:
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Lint Code Base
name: Superlinter
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
@ -33,7 +43,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Lint everything else
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems
@ -54,5 +64,7 @@ jobs:
VALIDATE_EDITORCONFIG: false
VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false
# Until we upgrade the super linter for actionlintÒ
VALIDATE_GITHUB_ACTIONS: false
SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -1,45 +0,0 @@
---
name: Parser 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: Parser 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]"
- name: Install solc
run: |
solc-select install all
solc-select use 0.8.0
- name: Test with pytest
run: |
pytest tests/test_ast_parsing.py

@ -8,6 +8,10 @@ on:
branches: [ dev, master ]
schedule: [ cron: "0 7 * * 2" ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
audit:
runs-on: ubuntu-latest
@ -26,10 +30,10 @@ jobs:
python -m venv /tmp/pip-audit-env
source /tmp/pip-audit-env/bin/activate
python -m pip install --upgrade pip
python -m pip install --upgrade pip setuptools wheel
python -m pip install .
- name: Run pip-audit
uses: trailofbits/gh-action-pip-audit@v0.0.4
uses: pypa/gh-action-pip-audit@v1.0.8
with:
virtual-environment: /tmp/pip-audit-env

@ -1,5 +1,5 @@
---
name: Lint Code Base
name: Run pylint
defaults:
run:
@ -9,9 +9,10 @@ defaults:
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
@ -20,7 +21,10 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
@ -33,12 +37,14 @@ jobs:
cp pyproject.toml .github/linters
- name: Pylint
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems
VALIDATE_ALL_CODEBASE: true
DEFAULT_BRANCH: master
# Run linters only on new files for pylint to speed up the CI
VALIDATE_ALL_CODEBASE: false
# Compare against the base branch
# This is only accessible on PR
DEFAULT_BRANCH: ${{ github.base_ref }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Run only pylint
VALIDATE_PYTHON: true

@ -1,50 +0,0 @@
---
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

@ -0,0 +1,105 @@
---
name: Pytest
defaults:
run:
shell: bash
on:
push:
branches: [master, dev]
pull_request:
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
tests:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["unit", "integration", "tool"]
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
cache: "pip"
cache-dependency-path: setup.py
- name: Install dependencies
run: |
pip install ".[test]"
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
cache-dependency-path: '**/package-lock.json'
- name: Install test dependencies
run: |
if [ ${{ matrix.type }} = "tool" ]; then
# Setup Ganache for slither-read-storage tests.
npm install --global ganache
elif [ ${{ matrix.type }} = "integration" ]; then
# Setup Hardhat for compilation tests.
pushd tests/e2e/compilation/test_data/test_node_modules/ || exit
npm install hardhat
popd || exit
fi
- name: Run ${{ matrix.type }} tests
env:
TEST_TYPE: ${{ matrix.type }}
# Only run coverage on ubuntu-latest.
run: |
if [ ${{ matrix.os }} = "ubuntu-latest" ]; then
TEST_ARGS="--cov=slither --cov-append"
elif [ ${{ matrix.os }} = "windows-2022" ]; then
TEST_ARGS=""
fi
bash "./.github/scripts/${TEST_TYPE}_test_runner.sh" $TEST_ARGS
- name: Upload coverage
uses: ./.github/actions/upload-coverage
# only aggregate test coverage over linux-based tests to avoid any OS-specific filesystem information stored in
# coverage metadata.
if: ${{ matrix.os == 'ubuntu-latest' }}
coverage:
needs:
- tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
- run: pip install coverage[toml]
- name: download coverage data
uses: actions/download-artifact@v3.0.2
with:
name: coverage-data
- name: combine coverage data
id: combinecoverage
run: |
set +e
python -m coverage combine
echo "## python coverage" >> $GITHUB_STEP_SUMMARY
python -m coverage report -m --format=markdown >> $GITHUB_STEP_SUMMARY

4
.gitignore vendored

@ -42,6 +42,7 @@ htmlcov/
.coverage
.coverage.*
.cache
.pytest_cache
nosetests.xml
coverage.xml
*.cover
@ -111,3 +112,6 @@ test_artifacts/
# crytic export
crytic-export/
# Auto-generated Github pages docs
docs/

@ -0,0 +1,6 @@
* @montyly @0xalpharush @smonicas
/slither/tools/read_storage/ @0xalpharush
/slither/tools/doctor/ @elopez
/slither/slithir/ @montyly
/slither/analyses/ @montyly
/.github/workflows/ @elopez

@ -1,15 +1,19 @@
# Contributing to Slither
First, thanks for your interest in contributing to Slither! We welcome and appreciate all contributions, including bug reports, feature suggestions, tutorials/blog posts, and code improvements.
If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels.
## Bug reports and feature suggestions
Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email opensource@trailofbits.com instead.
## Questions
Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel).
Questions can be submitted to the "Discussions" page, and you may also join our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel).
## Code
Slither uses the pull request contribution model. Please make an account on Github, fork this repo, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/).
Some pull request guidelines:
@ -23,6 +27,7 @@ Some pull request guidelines:
## Directory Structure
Below is a rough outline of slither's design:
```text
.
├── analyses # Provides additional info such as data dependency
@ -39,9 +44,10 @@ Below is a rough outline of slither's design:
A code walkthrough is available [here](https://www.youtube.com/watch?v=EUl3UlYSluU).
## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).
To run the unit tests, you need to clone this repository and run `pip install ".[dev]"`.
To run the unit tests, you need to clone this repository and run `make test`. Run a specific test with `make test TESTS=$test_name`. The names of tests can be obtained with `pytest tests --collect-only`.
### Linters
@ -49,33 +55,63 @@ Several linters and security checkers are run on the PRs.
To run them locally in the root dir of the repository:
- `pylint slither tests --rcfile pyproject.toml`
- `black . --config pyproject.toml`
- `make lint`
> Note, this only validates but does not modify the code.
To automatically reformat the code:
- `make reformat`
We use pylint `2.13.4`, black `22.3.0`.
### Detectors tests
### Testing
For each new detector, at least one regression tests must be present.
Slither's test suite is divided into three categories end-to-end (`tests/e2e`), unit (`tests/unit`), and tools (`tests/tools/`).
- Create a test in `tests`
- Update `ALL_TEST` in `tests/test_detectors.py`
- Run `python ./tests/test_detectors.py --generate`. This will generate the json artifacts in `tests/expected_json`. Add the generated files to git.
- If updating an existing detector, identify the respective json artifacts and then delete them, or run `python ./tests/test_detectors.py --overwrite` instead.
- Run `pytest ./tests/test_detectors.py` and check that everything worked.
How do I know what kind of test(s) to write?
To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`
- End-to-end: functionality that requires invoking `Slither` and inspecting some output such as printers and detectors.
- Unit: additions and modifications to objects should be accompanied by a unit test that defines the expected behavior. Aim to write functions in as pure a way as possible such that they are easier to test.
- Tools: tools built on top of Slither (`slither/tools`) but not apart of its core functionality
### Parser tests
- Create a test in `tests/ast-parsing`
- Run `python ./tests/test_ast_parsing.py --compile`. This will compile the artifact in `tests/ast-parsing/compile`. Add the compiled artifact to git.
- Run `python ./tests/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/ast-parsing/expected_json`. Add the generated files to git.
- Run `pytest ./tests/test_ast_parsing.py` and check that everything worked.
#### Adding detector tests
To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`
For each new detector, at least one regression tests must be present.
1. Create a folder in `tests/e2e/detectors/test_data` with the detector's argument name.
2. Create a test contract in `tests/e2e/detectors/test_data/<detector_name>/`.
3. Update `ALL_TEST` in `tests/e2e/detectors/test_detectors.py`
4. Run `python tests/e2e/detectors/test_detectors.py --compile` to create a zip file of the compilation artifacts.
5. `pytest tests/e2e/detectors/test_detectors.py --insta update-new`. This will generate a snapshot of the detector output in `tests/e2e/detectors/snapshots/`. If updating an existing detector, run `pytest tests/e2e/detectors/test_detectors.py --insta review` and accept or reject the updates.
6. Run `pytest tests/e2e/detectors/test_detectors.py` to ensure everything worked. Then, add and commit the files to git.
> ##### Helpful commands for detector tests
>
> - To see the tests coverage, run `pytest tests/e2e/detectors/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`.
> - To run tests for a specific detector, run `pytest tests/e2e/detectors/test_detectors.py -k ReentrancyReadBeforeWritten`(the detector's class name is the argument).
> - To run tests for a specific version, run `pytest tests/e2e/detectors/test_detectors.py -k 0.7.6`.
> - The IDs of tests can be inspected using `pytest tests/e2e/detectors/test_detectors.py --collect-only`.
#### Adding parsing tests
1. Create a test in `tests/e2e/solc_parsing/`
2. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git.
3. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git.
4. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked.
> ##### Helpful commands for parsing tests
>
> - To see the tests coverage, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`
> - To run tests for a specific test case, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k user_defined_value_type` (the filename is the argument).
> - To run tests for a specific version, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k 0.8.12`.
> - To run tests for a specific compiler json format, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k legacy` (can be legacy or compact).
> - The IDs of tests can be inspected using ``pytest tests/e2e/solc_parsing/test_ast_parsing.py --collect-only`.
### Synchronization with crytic-compile
By default, `slither` follows either the latest version of crytic-compile in pip, or `crytic-compile@master` (look for dependencies in [`setup.py`](./setup.py). If crytic-compile development comes with breaking changes, the process to update `slither` is:
- Update `slither/setup.py` to point to the related crytic-compile's branch
- Create a PR in `slither` and ensure it passes the CI
- Once the development branch is merged in `crytic-compile@master`, ensure `slither` follows the `master` branch

@ -2,6 +2,7 @@
FROM ubuntu:jammy AS python-wheels
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gcc \
git \
python3-dev \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
@ -9,7 +10,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
COPY . /slither
RUN cd /slither && \
echo pip3 install --no-cache-dir --upgrade pip && \
pip3 install --no-cache-dir --upgrade pip && \
pip3 wheel -w /wheels . solc-select pip setuptools wheel
@ -44,7 +45,7 @@ ENV PATH="/home/slither/.local/bin:${PATH}"
# no-index ensures we install the freshly-built wheels
RUN --mount=type=bind,target=/mnt,source=/wheels,from=python-wheels \
pip3 install --user --no-cache-dir --upgrade --no-index --find-links /mnt pip slither-analyzer solc-select
pip3 install --user --no-cache-dir --upgrade --no-index --find-links /mnt --no-deps /mnt/*.whl
RUN solc-select install 0.4.25 && solc-select use 0.4.25

@ -0,0 +1,88 @@
SHELL := /bin/bash
PY_MODULE := slither
TEST_MODULE := tests
ALL_PY_SRCS := $(shell find $(PY_MODULE) -name '*.py') \
$(shell find test -name '*.py')
# Optionally overriden by the user, if they're using a virtual environment manager.
VENV ?= env
# On Windows, venv scripts/shims are under `Scripts` instead of `bin`.
VENV_BIN := $(VENV)/bin
ifeq ($(OS),Windows_NT)
VENV_BIN := $(VENV)/Scripts
endif
# Optionally overridden by the user in the `release` target.
BUMP_ARGS :=
# Optionally overridden by the user in the `test` target.
TESTS :=
# Optionally overridden by the user/CI, to limit the installation to a specific
# subset of development dependencies.
SLITHER_EXTRA := dev
# If the user selects a specific test pattern to run, set `pytest` to fail fast
# and only run tests that match the pattern.
# Otherwise, run all tests and enable coverage assertions, since we expect
# complete test coverage.
ifneq ($(TESTS),)
TEST_ARGS := -x -k $(TESTS)
COV_ARGS :=
else
TEST_ARGS := -n auto
COV_ARGS := # --fail-under 100
endif
.PHONY: all
all:
@echo "Run my targets individually!"
.PHONY: dev
dev: $(VENV)/pyvenv.cfg
.PHONY: run
run: $(VENV)/pyvenv.cfg
@. $(VENV_BIN)/activate && slither $(ARGS)
$(VENV)/pyvenv.cfg: pyproject.toml
# Create our Python 3 virtual environment
python3 -m venv env
$(VENV_BIN)/python -m pip install --upgrade pip
$(VENV_BIN)/python -m pip install -e .[$(SLITHER_EXTRA)]
.PHONY: lint
lint: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
black --check . && \
pylint $(PY_MODULE) $(TEST_MODULE)
# ruff $(ALL_PY_SRCS) && \
# mypy $(PY_MODULE) &&
.PHONY: reformat
reformat:
. $(VENV_BIN)/activate && \
black .
.PHONY: test tests
test tests: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
pytest --cov=$(PY_MODULE) $(T) $(TEST_ARGS) && \
python -m coverage report -m $(COV_ARGS)
.PHONY: doc
doc: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
PDOC_ALLOW_EXEC=1 pdoc -o html slither '!slither.tools'
.PHONY: package
package: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
python3 -m build
.PHONY: edit
edit:
$(EDITOR) $(ALL_PY_SRCS)

@ -1,17 +1,19 @@
# Slither, the Solidity source analyzer
<img src="./logo.png" alt="Logo" width="500"/>
[![Build Status](https://img.shields.io/github/workflow/status/crytic/slither/CI/master)](https://github.com/crytic/slither/actions?query=workflow%3ACI)
[![Build Status](https://img.shields.io/github/actions/workflow/status/crytic/slither/ci.yml?branch=master)](https://github.com/crytic/slither/actions?query=workflow%3ACI)
[![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com)
[![PyPI version](https://badge.fury.io/py/slither-analyzer.svg)](https://badge.fury.io/py/slither-analyzer)
Slither is a Solidity static analysis framework written in Python 3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.
Slither is a Solidity static analysis framework written in Python3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.
- [Features](#features)
- [Bugs and Optimizations Detection](#bugs-and-optimizations-detection)
- [Usage](#usage)
- [How to Install](#how-to-install)
- [Detectors](#detectors)
- [Printers](#printers)
- [Tools](#tools)
- [How to Install](#how-to-install)
- [API Documentation](#api-documentation)
- [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications)
@ -20,41 +22,74 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s
* Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md))
* Identifies where the error condition occurs in the source code
* Easily integrates into continuous integration and Truffle builds
* Easily integrates into continuous integration and Hardhat/Foundry builds
* Built-in 'printers' quickly report crucial contract information
* Detector API to write custom analyses in Python
* Ability to analyze contracts written with Solidity >= 0.4
* Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses
* Correctly parses 99.9% of all public Solidity code
* Average execution time of less than 1 second per contract
* Integrates with Github's code scanning in [CI](https://github.com/marketplace/actions/slither-action)
## Usage
## Bugs and Optimizations Detection
Run Slither on a Truffle/Embark/Dapp/Etherlime/Hardhat application:
Run Slither on a Hardhat/Foundry/Dapp/Brownie application:
```bash
slither .
```
This is the preferred option if your project has dependencies as Slither relies on the underlying compilation framework to compile source code.
Run Slither on a single file:
However, you can run Slither on a single file that does not import dependencies:
```bash
slither tests/uninitialized.sol
```
## How to install
Slither requires Python 3.8+.
If you're **not** going to use one of the [supported compilation frameworks](https://github.com/crytic/crytic-compile), you need [solc](https://github.com/ethereum/solidity/), the Solidity compiler; we recommend using [solc-select](https://github.com/crytic/solc-select) to conveniently switch between solc versions.
### Using Pip
```bash
pip3 install slither-analyzer
```
### Using Git
```bash
git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
```
We recommend using a Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
```
To share a directory in the container:
```bash
docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
```
### 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. For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
### Detectors
## Detectors
Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | ---
1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High
2 | `arbitrary-send-erc20` | [transferFrom uses arbitrary `from`](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20) | High | High
2 | `arbitrary-send-erc20` | [transferFrom uses arbitrary `from`](https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom) | High | High
3 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High
4 | `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
5 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
@ -67,72 +102,76 @@ Num | Detector | What it Detects | Impact | Confidence
12 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
13 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
14 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High
15 | `arbitrary-send-erc20-permit` | [transferFrom uses arbitrary from with permit](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit) | High | Medium
16 | `arbitrary-send-eth` | [Functions that send Ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
17 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
18 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
19 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
20 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
21 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
22 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
23 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
24 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
25 | `domain-separator-collision` | [Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()](https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision) | Medium | High
26 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
27 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
28 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
29 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
30 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
31 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
32 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
33 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
34 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
35 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
36 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
37 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
38 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
39 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
40 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
41 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
42 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
43 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
44 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
45 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
46 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
47 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
48 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
49 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
50 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
51 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
52 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
53 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
54 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
55 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
56 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
57 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
58 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
59 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
60 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
61 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
62 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
63 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
64 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
65 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
66 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
67 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
68 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
69 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
70 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
71 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
72 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
73 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
74 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
75 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
76 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
77 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
78 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
79 | `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
80 | `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
15 | `codex` | [Use Codex to find vulnerabilities.](https://github.com/crytic/slither/wiki/Detector-Documentation#codex) | High | Low
16 | `arbitrary-send-erc20-permit` | [transferFrom uses arbitrary from with permit](https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom-used-with-permit) | High | Medium
17 | `arbitrary-send-eth` | [Functions that send Ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
18 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
19 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
20 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
21 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
22 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
23 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
24 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
25 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
26 | `domain-separator-collision` | [Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()](https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision) | Medium | High
27 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
28 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
29 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
30 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
31 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
32 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
33 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
34 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
35 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
36 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
37 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
38 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
39 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
40 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
41 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
42 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
43 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
44 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
45 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
46 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
47 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
48 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
49 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
50 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
51 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
52 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
53 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
54 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
55 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
56 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
57 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
58 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
59 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
60 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
61 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
62 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
63 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
64 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
65 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
66 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
67 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
68 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
69 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
70 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
71 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
72 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
73 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
74 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
75 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
76 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
77 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
78 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
79 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
80 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
81 | `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
82 | `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
83 | `immutable-states` | [State variables that could be declared immutable](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-immutable) | Optimization | High
84 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Vulnerabilities-Description#public-variable-read-in-external-context) | Optimization | High
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
@ -170,38 +209,8 @@ See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documen
[Contact us](https://www.trailofbits.com/contact/) to get help on building custom tools.
## How to install
Slither requires Python 3.8+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
### Using Pip
```bash
pip3 install slither-analyzer
```
### Using Git
```bash
git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
```
We recommend using a Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
```
To share a directory in the container:
```bash
docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
```
## API Documentation
Documentation on Slither's internals is available [here](https://crytic.github.io/slither/slither.html).
## Getting Help
@ -238,17 +247,19 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
- [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
### External publications
Title | Usage | Authors | Venue
--- | --- | --- | ---
Title | Usage | Authors | Venue | Code
--- | --- | --- | --- | ---
[ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method) | AST-based analysis built on top of Slither | Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen | CTCIS 19
[MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf) | Leverage data dependency through Slither | William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh | ISSRE 2019
[MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf) | Leverage data dependency through Slither | William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh | ISSRE 2019 | [MPro](https://github.com/QuanZhang-William/M-Pro)
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20
[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 | [SCRepair](https://github.com/xiaoly8/SCRepair/)
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22
[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 | [Sailfish](https://github.com/ucsb-seclab/sailfish)
[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)
[MANDO: Multi-Level Heterogeneous Graph Embeddings for Fine-Grained Detection of Smart Contract Vulnerabilities](https://arxiv.org/abs/2208.13252) | Use Slither to extract the CFG and call graph | Hoang Nguyen, Nhat-Minh Nguyen, Chunyao Xie, Zahra Ahmadi, Daniel Kudendo, Thanh-Nam Doan and Lingxiao Jiang| IEEE 9th International Conference on Data Science and Advanced Analytics (DSAA, 2022) | [ge-sc](https://github.com/MANDO-Project/ge-sc)
[Automated Auditing of Price Gouging TOD Vulnerabilities in Smart Contracts](https://www.cs.toronto.edu/~fanl/papers/price-icbc22.pdf) | Use Slither to extract the CFG and data dependencies| Sidi Mohamed Beillahi, Eric Keilty, Keerthi Nelaturu, Andreas Veneris, and Fan Long | 2022 IEEE International Conference on Blockchain and Cryptocurrency (ICBC) | [Smart-Contract-Repair](https://github.com/Veneris-Group/TOD-Location-Rectification)
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

@ -1,3 +1,9 @@
contract A{
pragma solidity 0.8.19;
}
error RevertIt();
contract Example {
function reverts() external pure {
revert RevertIt();
}
}

@ -1,5 +1,16 @@
import "./a.sol";
contract B is A{
pragma solidity 0.8.19;
enum B {
a,
b
}
contract T {
Example e = new Example();
function b() public returns(uint) {
B b = B.a;
return 4;
}
}

@ -18,6 +18,8 @@ assert len(contracts) == 1
contract = contracts[0]
destination = contract.get_state_variable_from_name("destination")
source = contract.get_state_variable_from_name("source")
assert source
assert destination
print(f"{source} is dependent of {destination}: {is_dependent(source, destination, contract)}")
assert not is_dependent(source, destination, contract)
@ -47,9 +49,11 @@ print(f"{destination} is tainted {is_tainted(destination, contract)}")
assert is_tainted(destination, contract)
destination_indirect_1 = contract.get_state_variable_from_name("destination_indirect_1")
assert destination_indirect_1
print(f"{destination_indirect_1} is tainted {is_tainted(destination_indirect_1, contract)}")
assert is_tainted(destination_indirect_1, contract)
destination_indirect_2 = contract.get_state_variable_from_name("destination_indirect_2")
assert destination_indirect_2
print(f"{destination_indirect_2} is tainted {is_tainted(destination_indirect_2, contract)}")
assert is_tainted(destination_indirect_2, contract)
@ -88,6 +92,8 @@ contract = contracts[0]
contract_derived = slither.get_contract_from_name("Derived")[0]
destination = contract.get_state_variable_from_name("destination")
source = contract.get_state_variable_from_name("source")
assert destination
assert source
print(f"{destination} is dependent of {source}: {is_dependent(destination, source, contract)}")
assert not is_dependent(destination, source, contract)

@ -14,6 +14,7 @@ assert len(contracts) == 1
contract = contracts[0]
# Get the variable
var_a = contract.get_state_variable_from_name("a")
assert var_a
# Get the functions reading the variable
functions_reading_a = contract.get_functions_reading_from_variable(var_a)

@ -4,17 +4,17 @@
solc-select use 0.7.0
if ! slither "tests/config/test.sol" --solc-ast --no-fail-pedantic; then
if ! slither "tests/e2e/config/test_json_config/test.sol" --solc-ast --no-fail-pedantic; then
echo "--solc-ast failed"
exit 1
fi
if ! slither "tests/config/test.sol" --solc-disable-warnings --no-fail-pedantic; then
if ! slither "tests/e2e/config/test_json_config/test.sol" --solc-disable-warnings --no-fail-pedantic; then
echo "--solc-disable-warnings failed"
exit 1
fi
if ! slither "tests/config/test.sol" --disable-color --no-fail-pedantic; then
if ! slither "tests/e2e/config/test_json_config/test.sol" --disable-color --no-fail-pedantic; then
echo "--disable-color failed"
exit 1
fi

@ -2,6 +2,11 @@
### Test Dapp integration
# work around having two python versions loading libraries from each other in CI
OLD_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
alias crytic-compile='LD_LIBRARY_PATH=$OLD_LD_LIBRARY_PATH crytic-compile'
unset LD_LIBRARY_PATH
mkdir test_dapp
cd test_dapp || exit 255
# The dapp init process makes a temporary local git repo and needs certain values to be set

@ -2,7 +2,7 @@
### Test slither-check-erc
DIR_TESTS="tests/check-erc"
DIR_TESTS="tests/tools/check-erc"
solc-select use 0.5.0
slither-check-erc "$DIR_TESTS/erc20.sol" ERC20 > test_1.txt 2>&1

@ -5,14 +5,16 @@
mkdir etherscan
cd etherscan || exit 255
if ! slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN"; then
echo "Etherscan test failed"
echo "::group::Etherscan mainnet"
if ! slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN" --no-fail-pedantic; then
echo "Etherscan mainnet test failed"
exit 1
fi
echo "::endgroup::"
if ! slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN"; then
echo "Etherscan test failed"
exit 1
# Perform a small sleep when API key is not available (e.g. on PR CI from external contributor)
if [ "$GITHUB_ETHERSCAN" = "" ]; then
sleep $(( ( RANDOM % 5 ) + 1 ))s
fi
exit 0

@ -1,6 +1,8 @@
#!/usr/bin/env bash
shopt -s extglob
### Test slither-prop
### Test slither-flat
solc-select use 0.8.19 --always-install
cd examples/flat || exit 1
@ -8,5 +10,11 @@ if ! slither-flat b.sol; then
echo "slither-flat failed"
exit 1
fi
SUFFIX="@(sol)"
if ! solc "crytic-export/flattening/"*$SUFFIX; then
echo "solc failed on flattened files"
exit 1
fi
exit 0

@ -0,0 +1,95 @@
#!/usr/bin/env bash
### Test slither-interface
DIR_TESTS="tests/tools/interface"
solc-select use 0.8.19 --always-install
#Test 1 - Etherscan target
slither-interface WETH9 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
DIFF=$(diff crytic-export/interfaces/IWETH9.sol "$DIR_TESTS/test_1.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 1 failed"
cat "crytic-export/interfaces/IWETH9.sol"
echo ""
cat "$DIR_TESTS/test_1.sol"
exit 255
fi
#Test 2 - Local file target
slither-interface Mock tests/tools/interface/ContractMock.sol
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_2.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 2 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_2.sol"
exit 255
fi
#Test 3 - unroll structs
slither-interface Mock tests/tools/interface/ContractMock.sol --unroll-structs
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_3.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 3 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_3.sol"
exit 255
fi
#Test 4 - exclude structs
slither-interface Mock tests/tools/interface/ContractMock.sol --exclude-structs
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_4.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 4 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_4.sol"
exit 255
fi
#Test 5 - exclude errors
slither-interface Mock tests/tools/interface/ContractMock.sol --exclude-errors
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_5.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 5 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_5.sol"
exit 255
fi
#Test 6 - exclude enums
slither-interface Mock tests/tools/interface/ContractMock.sol --exclude-enums
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_6.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 6 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_6.sol"
exit 255
fi
#Test 7 - exclude events
slither-interface Mock tests/tools/interface/ContractMock.sol --exclude-events
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_7.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 7 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_7.sol"
exit 255
fi
rm -r crytic-export

@ -3,7 +3,7 @@
### Test path filtering across POSIX and Windows
solc-select use 0.8.0
slither "tests/test_path_filtering/test_path_filtering.sol" --config "tests/test_path_filtering/slither.config.json" > "output.txt" 2>&1
slither "tests/e2e/config/test_path_filtering/test_path_filtering.sol" --config "tests/e2e/config/test_path_filtering/slither.config.json" > "output.txt" 2>&1
if ! grep -q "0 result(s) found" "output.txt"
then

@ -2,14 +2,23 @@
### Test printer
cd tests/ast-parsing/compile || exit
cd tests/e2e/solc_parsing/test_data/compile/ || exit
# Do not test the evm printer,as it needs a refactoring
ALL_PRINTERS="cfg,constructor-calls,contract-summary,data-dependency,echidna,function-id,function-summary,modifiers,call-graph,human-summary,inheritance,inheritance-graph,slithir,slithir-ssa,vars-and-auth,require,variable-order"
ALL_PRINTERS="cfg,constructor-calls,contract-summary,data-dependency,echidna,function-id,function-summary,modifiers,call-graph,human-summary,inheritance,inheritance-graph,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration"
# Only test 0.5.17 to limit test time
for file in *0.5.17-compact.zip; do
if ! slither "$file" --print "$ALL_PRINTERS" > /dev/null 2>&1 ; then
if ! slither "$file" --print "$ALL_PRINTERS" ; then
echo "Printer failed"
echo "$file"
exit 1
fi
done
# Only test 0.8.12 to limit test time
for file in *0.8.12-compact.zip; do
if ! slither "$file" --print "declaration" ; then
echo "Printer failed"
echo "$file"
exit 1

@ -155,6 +155,32 @@ then
exit 255
fi
slither-check-upgradeability "$DIR_TESTS/contractV1_struct.sol" ContractV1 --new-contract-filename "$DIR_TESTS/contractV2_struct.sol" --new-contract-name ContractV2 > test_12.txt 2>&1
DIFF=$(diff test_12.txt "$DIR_TESTS/test_12.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 12 failed"
cat test_12.txt
echo ""
cat "$DIR_TESTS/test_12.txt"
echo ""
echo "$DIFF"
exit 255
fi
slither-check-upgradeability "$DIR_TESTS/contractV1_struct.sol" ContractV1 --new-contract-filename "$DIR_TESTS/contractV2_struct_bug.sol" --new-contract-name ContractV2 > test_13.txt 2>&1
DIFF=$(diff test_13.txt "$DIR_TESTS/test_13.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 13 failed"
cat test_13.txt
echo ""
cat "$DIR_TESTS/test_13.txt"
echo ""
echo "$DIFF"
exit 255
fi
rm test_1.txt
rm test_2.txt
rm test_3.txt
@ -166,3 +192,5 @@ rm test_8.txt
rm test_9.txt
rm test_10.txt
rm test_11.txt
rm test_12.txt
rm test_13.txt

@ -8,25 +8,43 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.9.0",
version="0.9.3",
packages=find_packages(),
python_requires=">=3.8",
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
"crytic-compile>=0.2.4",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"packaging",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
# "crytic-compile>=0.3.1,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
"eth-utils>=2.1.0",
],
extras_require={
"dev": [
"lint": [
"black==22.3.0",
"pylint==2.13.4",
],
"test": [
"pytest",
"pytest-cov",
"pytest-xdist",
"deepdiff",
"numpy",
"solc-select>=v1.0.0b1",
]
"coverage[toml]",
"filelock",
"pytest-insta",
"solc-select@git+https://github.com/crytic/solc-select.git@query-artifact-path#egg=solc-select",
],
"doc": [
"pdoc",
],
"dev": [
"slither-analyzer[lint,test,doc]",
"openai",
],
},
license="AGPL-3.0",
long_description=long_description,
@ -45,6 +63,8 @@ setup(
"slither-mutate = slither.tools.mutator.__main__:main",
"slither-read-storage = slither.tools.read_storage.__main__:main",
"slither-doctor = slither.tools.doctor.__main__:main",
"slither-documentation = slither.tools.documentation.__main__:main",
"slither-interface = slither.tools.interface.__main__:main",
]
},
)

@ -1 +1,4 @@
"""
.. include:: ../README.md
"""
from .slither import Slither

@ -24,7 +24,14 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi
from slither.printers import all_printers
from slither.printers.abstract_printer import AbstractPrinter
from slither.slither import Slither
from slither.utils.output import output_to_json, output_to_zip, output_to_sarif, ZIP_TYPES_ACCEPTED
from slither.utils import codex
from slither.utils.output import (
output_to_json,
output_to_zip,
output_to_sarif,
ZIP_TYPES_ACCEPTED,
Output,
)
from slither.utils.output_capture import StandardOutputCapture
from slither.utils.colors import red, set_colorization_enabled
from slither.utils.command_line import (
@ -61,7 +68,7 @@ def process_single(
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
) -> Tuple[Slither, List[Dict], List[Output], int]:
"""
The core high-level code for running Slither static analysis.
@ -71,9 +78,6 @@ def process_single(
ast = "--ast-compact-json"
if args.legacy_ast:
ast = "--ast-json"
if args.checklist:
args.show_ignored_findings = True
slither = Slither(target, ast_format=ast, **vars(args))
return _process(slither, detector_classes, printer_classes)
@ -84,7 +88,7 @@ def process_all(
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[List[Slither], List[Dict], List[Dict], int]:
) -> Tuple[List[Slither], List[Dict], List[Output], int]:
compilations = compile_all(target, **vars(args))
slither_instances = []
results_detectors = []
@ -113,7 +117,7 @@ def _process(
slither: Slither,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
) -> Tuple[Slither, List[Dict], List[Output], int]:
for detector_cls in detector_classes:
slither.register_detector(detector_cls)
@ -126,9 +130,9 @@ def _process(
results_printers = []
if not printer_classes:
detector_results = slither.run_detectors()
detector_results = [x for x in detector_results if x] # remove empty results
detector_results = [item for sublist in detector_results for item in sublist] # flatten
detector_resultss = slither.run_detectors()
detector_resultss = [x for x in detector_resultss if x] # remove empty results
detector_results = [item for sublist in detector_resultss for item in sublist] # flatten
results_detectors.extend(detector_results)
else:
@ -139,23 +143,6 @@ def _process(
return slither, results_detectors, results_printers, analyzed_contracts_count
# TODO: delete me?
def process_from_asts(
filenames: List[str],
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
all_contracts: List[str] = []
for filename in filenames:
with open(filename, encoding="utf8") as file_open:
contract_loaded = json.load(file_open)
all_contracts.append(contract_loaded["ast"])
return process_single(all_contracts, args, detector_classes, printer_classes)
# endregion
###################################################################################
###################################################################################
@ -168,7 +155,6 @@ def process_from_asts(
def get_detectors_and_printers() -> Tuple[
List[Type[AbstractDetector]], List[Type[AbstractPrinter]]
]:
detectors_ = [getattr(all_detectors, name) for name in dir(all_detectors)]
detectors = [d for d in detectors_ if inspect.isclass(d) and issubclass(d, AbstractDetector)]
@ -288,7 +274,6 @@ def parse_filter_paths(args: argparse.Namespace) -> List[str]:
def parse_args(
detector_classes: List[Type[AbstractDetector]], printer_classes: List[Type[AbstractPrinter]]
) -> argparse.Namespace:
usage = "slither target [flag]\n"
usage += "\ntarget can be:\n"
usage += "\t- file.sol // a Solidity file\n"
@ -517,7 +502,7 @@ def parse_args(
group_misc.add_argument(
"--filter-paths",
help="Comma-separated list of paths for which results will be excluded",
help="Regex filter to exclude detector results matching file path e.g. (mocks/|test/)",
action="store",
dest="filter_paths",
default=defaults_flag_in_config["filter_paths"],
@ -561,6 +546,15 @@ def parse_args(
default=False,
)
group_misc.add_argument(
"--no-fail",
help="Do not fail in case of parsing (echidna mode only)",
action="store_true",
default=defaults_flag_in_config["no_fail"],
)
codex.init_parser(parser)
# debugger command
parser.add_argument("--debug", help=argparse.SUPPRESS, action="store_true", default=False)
@ -599,9 +593,6 @@ def parse_args(
default=False,
)
# if the json is splitted in different files
parser.add_argument("--splitted", help=argparse.SUPPRESS, action="store_true", default=False)
# Disable the throw/catch on partial analyses
parser.add_argument(
"--disallow-partial", help=argparse.SUPPRESS, action="store_true", default=False
@ -617,7 +608,7 @@ def parse_args(
args.filter_paths = parse_filter_paths(args)
# Verify our json-type output is valid
args.json_types = set(args.json_types.split(","))
args.json_types = set(args.json_types.split(",")) # type:ignore
for json_type in args.json_types:
if json_type not in JSON_OUTPUT_TYPES:
raise Exception(f'Error: "{json_type}" is not a valid JSON result output type.')
@ -626,7 +617,9 @@ def parse_args(
class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs
def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
detectors, _ = get_detectors_and_printers()
output_detectors(detectors)
parser.exit()
@ -688,14 +681,14 @@ class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods
class FormatterCryticCompile(logging.Formatter):
def format(self, record):
def format(self, record: logging.LogRecord) -> str:
# for i, msg in enumerate(record.msg):
if record.msg.startswith("Compilation warnings/errors on "):
txt = record.args[1]
txt = txt.split("\n")
txt = record.args[1] # type:ignore
txt = txt.split("\n") # type:ignore
txt = [red(x) if "Error" in x else x for x in txt]
txt = "\n".join(txt)
record.args = (record.args[0], txt)
record.args = (record.args[0], txt) # type:ignore
return super().format(record)
@ -738,7 +731,7 @@ def main_impl(
set_colorization_enabled(False if args.disable_color else sys.stdout.isatty())
# Define some variables for potential JSON output
json_results = {}
json_results: Dict[str, Any] = {}
output_error = None
outputting_json = args.json is not None
outputting_json_stdout = args.json == "-"
@ -751,7 +744,7 @@ def main_impl(
# If we are outputting JSON, capture all standard output. If we are outputting to stdout, we block typical stdout
# output.
if outputting_json or output_to_sarif:
if outputting_json or outputting_sarif:
StandardOutputCapture.enable(outputting_json_stdout or outputting_sarif_stdout)
printer_classes = choose_printers(args, all_printer_classes)
@ -787,7 +780,7 @@ def main_impl(
crytic_compile_error.setLevel(logging.INFO)
results_detectors: List[Dict] = []
results_printers: List[Dict] = []
results_printers: List[Output] = []
try:
filename = args.filename
@ -800,26 +793,17 @@ def main_impl(
number_contracts = 0
slither_instances = []
if args.splitted:
for filename in filenames:
(
slither_instance,
results_detectors,
results_printers,
number_contracts,
) = process_from_asts(filenames, args, detector_classes, printer_classes)
results_detectors_tmp,
results_printers_tmp,
number_contracts_tmp,
) = process_single(filename, args, detector_classes, printer_classes)
number_contracts += number_contracts_tmp
results_detectors += results_detectors_tmp
results_printers += results_printers_tmp
slither_instances.append(slither_instance)
else:
for filename in filenames:
(
slither_instance,
results_detectors_tmp,
results_printers_tmp,
number_contracts_tmp,
) = process_single(filename, args, detector_classes, printer_classes)
number_contracts += number_contracts_tmp
results_detectors += results_detectors_tmp
results_printers += results_printers_tmp
slither_instances.append(slither_instance)
# Rely on CryticCompile to discern the underlying type of compilations.
else:
@ -862,7 +846,9 @@ def main_impl(
# Output our results to markdown if we wish to compile a checklist.
if args.checklist:
output_results_to_markdown(results_detectors, args.checklist_limit)
output_results_to_markdown(
results_detectors, args.checklist_limit, args.show_ignored_findings
)
# Don't print the number of result for printers
if number_contracts == 0:

@ -2,8 +2,9 @@
Compute the data depenency between all the SSA variables
"""
from collections import defaultdict
from typing import Union, Set, Dict, TYPE_CHECKING
from typing import Union, Set, Dict, TYPE_CHECKING, List
from slither.core.cfg.node import Node
from slither.core.declarations import (
Contract,
Enum,
@ -12,11 +13,14 @@ from slither.core.declarations import (
SolidityVariable,
SolidityVariableComposed,
Structure,
FunctionContract,
)
from slither.core.declarations.solidity_import_placeholder import SolidityImportPlaceHolder
from slither.core.solidity_types.type import Type
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.core.variables.variable import Variable
from slither.slithir.operations import Index, OperationWithLValue, InternalCall, Operation
from slither.slithir.utils.utils import LVALUE
from slither.slithir.variables import (
Constant,
LocalIRVariable,
@ -26,12 +30,11 @@ from slither.slithir.variables import (
TemporaryVariableSSA,
TupleVariableSSA,
)
from slither.core.solidity_types.type import Type
from slither.slithir.variables.variable import SlithIRVariable
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
###################################################################################
###################################################################################
# region User APIs
@ -39,26 +42,39 @@ if TYPE_CHECKING:
###################################################################################
Variable_types = Union[Variable, SolidityVariable]
SUPPORTED_TYPES = Union[Variable, SolidityVariable]
# TODO refactor the data deps to be better suited for top level function object
# Right now we allow to pass a node to ease the API, but we need something
# better
# The deps propagation for top level elements is also not working as expected
Context_types_API = Union[Contract, Function, Node]
Context_types = Union[Contract, Function]
def is_dependent(
variable: Variable_types,
source: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
source: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
) -> bool:
"""
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
Args:
variable (Variable)
source (Variable)
context (Contract|Function)
context (Contract|Function|Node).
only_unprotected (bool): True only unprotected function are considered
Returns:
bool
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
if isinstance(variable, Constant):
return False
if variable == source:
@ -74,12 +90,15 @@ def is_dependent(
def is_dependent_ssa(
variable: Variable_types,
source: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
source: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
) -> bool:
"""
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
Args:
variable (Variable)
taint (Variable)
@ -88,7 +107,10 @@ def is_dependent_ssa(
Returns:
bool
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
context_dict = context.context
if isinstance(variable, Constant):
return False
@ -107,16 +129,20 @@ GENERIC_TAINT = {
SolidityVariableComposed("msg.value"),
SolidityVariableComposed("msg.data"),
SolidityVariableComposed("tx.origin"),
SolidityVariableComposed("tx.gasprice"),
}
def is_tainted(
variable: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
) -> bool:
"""
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
Args:
variable
context (Contract|Function)
@ -124,7 +150,10 @@ def is_tainted(
Returns:
bool
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if isinstance(variable, Constant):
return False
@ -138,12 +167,15 @@ def is_tainted(
def is_tainted_ssa(
variable: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
ignore_generic_taint: bool = False,
):
) -> bool:
"""
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
Args:
variable
context (Contract|Function)
@ -151,7 +183,10 @@ def is_tainted_ssa(
Returns:
bool
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if isinstance(variable, Constant):
return False
@ -165,19 +200,24 @@ def is_tainted_ssa(
def get_dependencies(
variable: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
) -> Set[Variable]:
"""
Return the variables for which `variable` depends on.
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
:param variable: The target
:param context: Either a function (interprocedural) or a contract (inter transactional)
:param only_unprotected: True if consider only protected functions
:return: set(Variable)
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if only_unprotected:
return context.context[KEY_NON_SSA_UNPROTECTED].get(variable, set())
@ -185,16 +225,21 @@ def get_dependencies(
def get_all_dependencies(
context: Context_types, only_unprotected: bool = False
context: Context_types_API, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
:param context: Either a function (interprocedural) or a contract (inter transactional)
:param only_unprotected: True if consider only protected functions
:return: Dict(Variable, set(Variable))
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if only_unprotected:
return context.context[KEY_NON_SSA_UNPROTECTED]
@ -202,19 +247,24 @@ def get_all_dependencies(
def get_dependencies_ssa(
variable: Variable_types,
context: Context_types,
variable: SUPPORTED_TYPES,
context: Context_types_API,
only_unprotected: bool = False,
) -> Set[Variable]:
"""
Return the variables for which `variable` depends on (SSA version).
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
:param variable: The target (must be SSA variable)
:param context: Either a function (interprocedural) or a contract (inter transactional)
:param only_unprotected: True if consider only protected functions
:return: set(Variable)
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if only_unprotected:
return context.context[KEY_SSA_UNPROTECTED].get(variable, set())
@ -222,16 +272,21 @@ def get_dependencies_ssa(
def get_all_dependencies_ssa(
context: Context_types, only_unprotected: bool = False
context: Context_types_API, only_unprotected: bool = False
) -> Dict[Variable, Set[Variable]]:
"""
Return the dictionary of dependencies.
If Node is provided as context, the context will be the broader context, either the contract or the function,
depending on if the node is in a top level function or not
:param context: Either a function (interprocedural) or a contract (inter transactional)
:param only_unprotected: True if consider only protected functions
:return: Dict(Variable, set(Variable))
"""
assert isinstance(context, (Contract, Function))
assert isinstance(context, (Contract, Function, Node))
if isinstance(context, Node):
func = context.function
context = func.contract if isinstance(func, FunctionContract) else func
assert isinstance(only_unprotected, bool)
if only_unprotected:
return context.context[KEY_SSA_UNPROTECTED]
@ -341,13 +396,9 @@ def transitive_close_dependencies(
while changed:
changed = False
to_add = defaultdict(set)
[ # pylint: disable=expression-not-assigned
[
for key, items in context.context[context_key].items():
for item in items & keys:
to_add[key].update(context.context[context_key][item] - {key} - items)
for item in items & keys
]
for key, items in context.context[context_key].items()
]
for k, v in to_add.items():
# Because we dont have any check on the update operation
# We might update an empty set with an empty set
@ -366,20 +417,20 @@ def add_dependency(lvalue: Variable, function: Function, ir: Operation, is_prote
function.context[KEY_SSA][lvalue] = set()
if not is_protected:
function.context[KEY_SSA_UNPROTECTED][lvalue] = set()
read: Union[List[Union[LVALUE, SolidityVariableComposed]], List[SlithIRVariable]]
if isinstance(ir, Index):
read = [ir.variable_left]
elif isinstance(ir, InternalCall):
elif isinstance(ir, InternalCall) and ir.function:
read = ir.function.return_values_ssa
else:
read = ir.read
# pylint: disable=expression-not-assigned
[function.context[KEY_SSA][lvalue].add(v) for v in read if not isinstance(v, Constant)]
for v in read:
if not isinstance(v, Constant):
function.context[KEY_SSA][lvalue].add(v)
if not is_protected:
[
function.context[KEY_SSA_UNPROTECTED][lvalue].add(v)
for v in read
if not isinstance(v, Constant)
]
for v in read:
if not isinstance(v, Constant):
function.context[KEY_SSA_UNPROTECTED][lvalue].add(v)
def compute_dependency_function(function: Function) -> None:
@ -407,7 +458,7 @@ def compute_dependency_function(function: Function) -> None:
)
def convert_variable_to_non_ssa(v: Variable_types) -> Variable_types:
def convert_variable_to_non_ssa(v: SUPPORTED_TYPES) -> SUPPORTED_TYPES:
if isinstance(
v,
(
@ -438,10 +489,10 @@ def convert_variable_to_non_ssa(v: Variable_types) -> Variable_types:
def convert_to_non_ssa(
data_depencies: Dict[Variable_types, Set[Variable_types]]
) -> Dict[Variable_types, Set[Variable_types]]:
data_depencies: Dict[SUPPORTED_TYPES, Set[SUPPORTED_TYPES]]
) -> Dict[SUPPORTED_TYPES, Set[SUPPORTED_TYPES]]:
# Need to create new set() as its changed during iteration
ret: Dict[Variable_types, Set[Variable_types]] = {}
ret: Dict[SUPPORTED_TYPES, Set[SUPPORTED_TYPES]] = {}
for (k, values) in data_depencies.items():
var = convert_variable_to_non_ssa(k)
if not var in ret:

@ -186,7 +186,7 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither
if mapping_item[i] == "":
mapping_item[i] = int(prev_mapping[i])
offset, _length, file_id, _ = mapping_item
offset, _length, file_id, *_ = mapping_item
prev_mapping = mapping_item
if file_id == "-1":

@ -2,10 +2,10 @@
Detect if all the given variables are written in all the paths of the function
"""
from collections import defaultdict
from typing import Dict, Set, List
from typing import Dict, Set, List, Any, Optional
from slither.core.cfg.node import NodeType, Node
from slither.core.declarations import SolidityFunction
from slither.core.declarations import SolidityFunction, Function
from slither.core.variables.variable import Variable
from slither.slithir.operations import (
Index,
@ -18,7 +18,7 @@ from slither.slithir.variables import ReferenceVariable, TemporaryVariable
class State: # pylint: disable=too-few-public-methods
def __init__(self):
def __init__(self) -> None:
# Map node -> list of variables set
# Were each variables set represents a configuration of a path
# If two paths lead to the exact same set of variables written, we dont need to explore both
@ -34,11 +34,11 @@ class State: # pylint: disable=too-few-public-methods
# pylint: disable=too-many-branches
def _visit(
node: Node,
node: Optional[Node],
state: State,
variables_written: Set[Variable],
variables_to_write: List[Variable],
):
) -> List[Variable]:
"""
Explore all the nodes to look for values not written when the node's function return
Fixpoint reaches if no new written variables are found
@ -51,6 +51,8 @@ def _visit(
refs = {}
variables_written = set(variables_written)
if not node:
return []
for ir in node.irs:
if isinstance(ir, SolidityCall):
# TODO convert the revert to a THROW node
@ -70,17 +72,20 @@ def _visit(
if ir.lvalue and not isinstance(ir.lvalue, (TemporaryVariable, ReferenceVariable)):
variables_written.add(ir.lvalue)
lvalue = ir.lvalue
lvalue: Any = ir.lvalue
while isinstance(lvalue, ReferenceVariable):
if lvalue not in refs:
break
if refs[lvalue] and not isinstance(
refs[lvalue], (TemporaryVariable, ReferenceVariable)
refs_lvalues = refs[lvalue]
if (
refs_lvalues
and isinstance(refs_lvalues, Variable)
and not isinstance(refs_lvalues, (TemporaryVariable, ReferenceVariable))
):
variables_written.add(refs[lvalue])
lvalue = refs[lvalue]
variables_written.add(refs_lvalues)
lvalue = refs_lvalues
ret = []
ret: List[Variable] = []
if not node.sons and node.type not in [NodeType.THROW, NodeType.RETURN]:
ret += [v for v in variables_to_write if v not in variables_written]
@ -96,7 +101,7 @@ def _visit(
return ret
def are_variables_written(function, variables_to_write):
def are_variables_written(function: Function, variables_to_write: List[Variable]) -> List[Variable]:
"""
Return the list of variable that are not written at the end of the function

@ -4,16 +4,18 @@
from enum import Enum
from typing import Optional, List, Set, Dict, Tuple, Union, TYPE_CHECKING
from slither.core.children.child_function import ChildFunction
from slither.all_exceptions import SlitherException
from slither.core.declarations import Contract, Function, FunctionContract
from slither.core.declarations.solidity_variables import (
SolidityVariable,
SolidityFunction,
)
from slither.core.expressions.expression import Expression
from slither.core.solidity_types import ElementaryType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.variable import Variable
from slither.core.solidity_types import ElementaryType
from slither.slithir.convert import convert_expression
from slither.slithir.operations import (
HighLevelCall,
@ -30,6 +32,7 @@ from slither.slithir.operations import (
Return,
Operation,
)
from slither.slithir.utils.utils import RVALUE
from slither.slithir.variables import (
Constant,
LocalIRVariable,
@ -38,10 +41,6 @@ from slither.slithir.variables import (
TemporaryVariable,
TupleVariable,
)
from slither.all_exceptions import SlitherException
from slither.core.declarations import Contract, Function
from slither.core.expressions.expression import Expression
if TYPE_CHECKING:
from slither.slithir.variables.variable import SlithIRVariable
@ -66,87 +65,48 @@ if TYPE_CHECKING:
class NodeType(Enum):
ENTRYPOINT = 0x0 # no expression
ENTRYPOINT = "ENTRY_POINT" # no expression
# Node with expression
# Nodes that may have an expression
EXPRESSION = 0x10 # normal case
RETURN = 0x11 # RETURN may contain an expression
IF = 0x12
VARIABLE = 0x13 # Declaration of variable
ASSEMBLY = 0x14
IFLOOP = 0x15
EXPRESSION = "EXPRESSION" # normal case
RETURN = "RETURN" # RETURN may contain an expression
IF = "IF"
VARIABLE = "NEW VARIABLE" # Variable declaration
ASSEMBLY = "INLINE ASM"
IFLOOP = "IF_LOOP"
# Merging nodes
# Nodes where control flow merges
# Can have phi IR operation
ENDIF = 0x50 # ENDIF node source mapping points to the if/else body
STARTLOOP = 0x51 # STARTLOOP node source mapping points to the entire loop body
ENDLOOP = 0x52 # ENDLOOP node source mapping points to the entire loop body
ENDIF = "END_IF" # ENDIF node source mapping points to the if/else "body"
STARTLOOP = "BEGIN_LOOP" # STARTLOOP node source mapping points to the entire loop "body"
ENDLOOP = "END_LOOP" # ENDLOOP node source mapping points to the entire loop "body"
# Below the nodes have no expression
# But are used to expression CFG structure
# Below the nodes do not have an expression but are used to expression CFG structure.
# Absorbing node
THROW = 0x20
THROW = "THROW"
# Loop related nodes
BREAK = 0x31
CONTINUE = 0x32
BREAK = "BREAK"
CONTINUE = "CONTINUE"
# Only modifier node
PLACEHOLDER = 0x40
PLACEHOLDER = "_"
TRY = 0x41
CATCH = 0x42
TRY = "TRY"
CATCH = "CATCH"
# Node not related to the CFG
# Use for state variable declaration
OTHER_ENTRYPOINT = 0x60
# @staticmethod
def __str__(self):
if self == NodeType.ENTRYPOINT:
return "ENTRY_POINT"
if self == NodeType.EXPRESSION:
return "EXPRESSION"
if self == NodeType.RETURN:
return "RETURN"
if self == NodeType.IF:
return "IF"
if self == NodeType.VARIABLE:
return "NEW VARIABLE"
if self == NodeType.ASSEMBLY:
return "INLINE ASM"
if self == NodeType.IFLOOP:
return "IF_LOOP"
if self == NodeType.THROW:
return "THROW"
if self == NodeType.BREAK:
return "BREAK"
if self == NodeType.CONTINUE:
return "CONTINUE"
if self == NodeType.PLACEHOLDER:
return "_"
if self == NodeType.TRY:
return "TRY"
if self == NodeType.CATCH:
return "CATCH"
if self == NodeType.ENDIF:
return "END_IF"
if self == NodeType.STARTLOOP:
return "BEGIN_LOOP"
if self == NodeType.ENDLOOP:
return "END_LOOP"
if self == NodeType.OTHER_ENTRYPOINT:
return "OTHER_ENTRYPOINT"
return f"Unknown type {hex(self.value)}"
OTHER_ENTRYPOINT = "OTHER_ENTRYPOINT"
# endregion
# I am not sure why, but pylint reports a lot of "no-member" issue that are not real (Josselin)
# pylint: disable=no-member
class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-methods
class Node(SourceMapping): # pylint: disable=too-many-public-methods
"""
Node class
@ -158,7 +118,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
node_id: int,
scope: Union["Scope", "Function"],
file_scope: "FileScope",
):
) -> None:
super().__init__()
self._node_type = node_type
@ -186,12 +146,12 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._node_id: int = node_id
self._vars_written: List[Variable] = []
self._vars_read: List[Variable] = []
self._vars_read: List[Union[Variable, SolidityVariable]] = []
self._ssa_vars_written: List["SlithIRVariable"] = []
self._ssa_vars_read: List["SlithIRVariable"] = []
self._internal_calls: List["Function"] = []
self._internal_calls: List[Union["Function", "SolidityFunction"]] = []
self._solidity_calls: List[SolidityFunction] = []
self._high_level_calls: List["HighLevelCallType"] = [] # contains library calls
self._library_calls: List["LibraryCallType"] = []
@ -212,7 +172,9 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._local_vars_read: List[LocalVariable] = []
self._local_vars_written: List[LocalVariable] = []
self._slithir_vars: Set["SlithIRVariable"] = set() # non SSA
self._slithir_vars: Set[
Union["SlithIRVariable", ReferenceVariable, TemporaryVariable, TupleVariable]
] = set() # non SSA
self._ssa_local_vars_read: List[LocalIRVariable] = []
self._ssa_local_vars_written: List[LocalIRVariable] = []
@ -229,6 +191,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self.scope: Union["Scope", "Function"] = scope
self.file_scope: "FileScope" = file_scope
self._function: Optional["Function"] = None
###################################################################################
###################################################################################
@ -253,7 +216,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._node_type
@type.setter
def type(self, new_type: NodeType):
def type(self, new_type: NodeType) -> None:
self._node_type = new_type
@property
@ -264,6 +227,13 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return True
return False
def set_function(self, function: "Function") -> None:
self._function = function
@property
def function(self) -> "Function":
return self._function
# endregion
###################################################################################
###################################################################################
@ -272,7 +242,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
@property
def variables_read(self) -> List[Variable]:
def variables_read(self) -> List[Union[Variable, SolidityVariable]]:
"""
list(Variable): Variables read (local/state/solidity)
"""
@ -325,11 +295,13 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._expression_vars_read
@variables_read_as_expression.setter
def variables_read_as_expression(self, exprs: List[Expression]):
def variables_read_as_expression(self, exprs: List[Expression]) -> None:
self._expression_vars_read = exprs
@property
def slithir_variables(self) -> List["SlithIRVariable"]:
def slithir_variables(
self,
) -> List[Union["SlithIRVariable", ReferenceVariable, TemporaryVariable, TupleVariable]]:
return list(self._slithir_vars)
@property
@ -379,7 +351,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._expression_vars_written
@variables_written_as_expression.setter
def variables_written_as_expression(self, exprs: List[Expression]):
def variables_written_as_expression(self, exprs: List[Expression]) -> None:
self._expression_vars_written = exprs
# endregion
@ -439,7 +411,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._external_calls_as_expressions
@external_calls_as_expressions.setter
def external_calls_as_expressions(self, exprs: List[Expression]):
def external_calls_as_expressions(self, exprs: List[Expression]) -> None:
self._external_calls_as_expressions = exprs
@property
@ -450,7 +422,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._internal_calls_as_expressions
@internal_calls_as_expressions.setter
def internal_calls_as_expressions(self, exprs: List[Expression]):
def internal_calls_as_expressions(self, exprs: List[Expression]) -> None:
self._internal_calls_as_expressions = exprs
@property
@ -458,10 +430,10 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return list(self._expression_calls)
@calls_as_expression.setter
def calls_as_expression(self, exprs: List[Expression]):
def calls_as_expression(self, exprs: List[Expression]) -> None:
self._expression_calls = exprs
def can_reenter(self, callstack=None) -> bool:
def can_reenter(self, callstack: Optional[List[Union[Function, Variable]]] = None) -> bool:
"""
Check if the node can re-enter
Do not consider CREATE as potential re-enter, but check if the
@ -513,11 +485,11 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
return self._expression
def add_expression(self, expression: Expression, bypass_verif_empty: bool = False):
def add_expression(self, expression: Expression, bypass_verif_empty: bool = False) -> None:
assert self._expression is None or bypass_verif_empty
self._expression = expression
def add_variable_declaration(self, var: LocalVariable):
def add_variable_declaration(self, var: LocalVariable) -> None:
assert self._variable_declaration is None
self._variable_declaration = var
if var.expression:
@ -550,7 +522,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
for c in self.internal_calls
)
def contains_if(self, include_loop=True) -> bool:
def contains_if(self, include_loop: bool = True) -> bool:
"""
Check if the node is a IF node
Returns:
@ -560,7 +532,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self.type in [NodeType.IF, NodeType.IFLOOP]
return self.type == NodeType.IF
def is_conditional(self, include_loop=True) -> bool:
def is_conditional(self, include_loop: bool = True) -> bool:
"""
Check if the node is a conditional node
A conditional node is either a IF or a require/assert or a RETURN bool
@ -589,7 +561,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
def inline_asm(self) -> Optional[Union[str, Dict]]:
return self._asm_source_code
def add_inline_asm(self, asm: Union[str, Dict]):
def add_inline_asm(self, asm: Union[str, Dict]) -> None:
self._asm_source_code = asm
# endregion
@ -599,7 +571,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
###################################################################################
def add_father(self, father: "Node"):
def add_father(self, father: "Node") -> None:
"""Add a father node
Args:
@ -607,7 +579,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._fathers.append(father)
def set_fathers(self, fathers: List["Node"]):
def set_fathers(self, fathers: List["Node"]) -> None:
"""Set the father nodes
Args:
@ -624,7 +596,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
return list(self._fathers)
def remove_father(self, father: "Node"):
def remove_father(self, father: "Node") -> None:
"""Remove the father node. Do nothing if the node is not a father
Args:
@ -632,7 +604,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._fathers = [x for x in self._fathers if x.node_id != father.node_id]
def remove_son(self, son: "Node"):
def remove_son(self, son: "Node") -> None:
"""Remove the son node. Do nothing if the node is not a son
Args:
@ -640,7 +612,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._sons = [x for x in self._sons if x.node_id != son.node_id]
def add_son(self, son: "Node"):
def add_son(self, son: "Node") -> None:
"""Add a son node
Args:
@ -648,7 +620,22 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
"""
self._sons.append(son)
def set_sons(self, sons: List["Node"]):
def replace_son(self, ori_son: "Node", new_son: "Node") -> None:
"""Replace a son node. Do nothing if the node to replace is not a son
Args:
ori_son: son to replace
new_son: son to replace with
"""
for i, s in enumerate(self._sons):
if s.node_id == ori_son.node_id:
idx = i
break
else:
return
self._sons[idx] = new_son
def set_sons(self, sons: List["Node"]) -> None:
"""Set the son nodes
Args:
@ -703,20 +690,20 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._irs_ssa
@irs_ssa.setter
def irs_ssa(self, irs):
def irs_ssa(self, irs: List[Operation]) -> None:
self._irs_ssa = irs
def add_ssa_ir(self, ir: Operation):
def add_ssa_ir(self, ir: Operation) -> None:
"""
Use to place phi operation
"""
ir.set_node(self)
ir.set_node(self) # type: ignore
self._irs_ssa.append(ir)
def slithir_generation(self):
def slithir_generation(self) -> None:
if self.expression:
expression = self.expression
self._irs = convert_expression(expression, self)
self._irs = convert_expression(expression, self) # type:ignore
self._find_read_write_call()
@ -730,11 +717,11 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._all_slithir_operations
@staticmethod
def _is_non_slithir_var(var: Variable):
def _is_non_slithir_var(var: Variable) -> bool:
return not isinstance(var, (Constant, ReferenceVariable, TemporaryVariable, TupleVariable))
@staticmethod
def _is_valid_slithir_var(var: Variable):
def _is_valid_slithir_var(var: Variable) -> bool:
return isinstance(var, (ReferenceVariable, TemporaryVariable, TupleVariable))
# endregion
@ -753,7 +740,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._dominators
@dominators.setter
def dominators(self, dom: Set["Node"]):
def dominators(self, dom: Set["Node"]) -> None:
self._dominators = dom
@property
@ -765,7 +752,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._immediate_dominator
@immediate_dominator.setter
def immediate_dominator(self, idom: "Node"):
def immediate_dominator(self, idom: "Node") -> None:
self._immediate_dominator = idom
@property
@ -777,7 +764,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
return self._dominance_frontier
@dominance_frontier.setter
def dominance_frontier(self, doms: Set["Node"]):
def dominance_frontier(self, doms: Set["Node"]) -> None:
"""
Returns:
set(Node)
@ -785,7 +772,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._dominance_frontier = doms
@property
def dominator_successors(self):
def dominator_successors(self) -> Set["Node"]:
return self._dom_successors
@property
@ -827,14 +814,15 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
# def phi_origin_member_variables(self) -> Dict[str, Tuple[MemberVariable, Set["Node"]]]:
# return self._phi_origins_member_variables
def add_phi_origin_local_variable(self, variable: LocalVariable, node: "Node"):
def add_phi_origin_local_variable(self, variable: LocalVariable, node: "Node") -> None:
if variable.name not in self._phi_origins_local_variables:
assert variable.name
self._phi_origins_local_variables[variable.name] = (variable, set())
(v, nodes) = self._phi_origins_local_variables[variable.name]
assert v == variable
nodes.add(node)
def add_phi_origin_state_variable(self, variable: StateVariable, node: "Node"):
def add_phi_origin_state_variable(self, variable: StateVariable, node: "Node") -> None:
if variable.canonical_name not in self._phi_origins_state_variables:
self._phi_origins_state_variables[variable.canonical_name] = (
variable,
@ -858,7 +846,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
###################################################################################
def _find_read_write_call(self): # pylint: disable=too-many-statements
def _find_read_write_call(self) -> None: # pylint: disable=too-many-statements
for ir in self.irs:
@ -867,7 +855,8 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
if isinstance(ir, OperationWithLValue):
var = ir.lvalue
if var and self._is_valid_slithir_var(var):
self._slithir_vars.add(var)
# The type is checked by is_valid_slithir_var
self._slithir_vars.add(var) # type: ignore
if not isinstance(ir, (Phi, Index, Member)):
self._vars_read += [v for v in ir.read if self._is_non_slithir_var(v)]
@ -875,8 +864,9 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
if isinstance(var, ReferenceVariable):
self._vars_read.append(var.points_to_origin)
elif isinstance(ir, (Member, Index)):
# TODO investigate types for member variable left
var = ir.variable_left if isinstance(ir, Member) else ir.variable_right
if self._is_non_slithir_var(var):
if var and self._is_non_slithir_var(var):
self._vars_read.append(var)
if isinstance(var, ReferenceVariable):
origin = var.points_to_origin
@ -900,14 +890,21 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._internal_calls.append(ir.function)
if isinstance(ir, LowLevelCall):
assert isinstance(ir.destination, (Variable, SolidityVariable))
self._low_level_calls.append((ir.destination, ir.function_name.value))
self._low_level_calls.append((ir.destination, str(ir.function_name.value)))
elif isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall):
# Todo investigate this if condition
# It does seem right to compare against a contract
# This might need a refactoring
if isinstance(ir.destination.type, Contract):
self._high_level_calls.append((ir.destination.type, ir.function))
elif ir.destination == SolidityVariable("this"):
self._high_level_calls.append((self.function.contract, ir.function))
func = self.function
# Can't use this in a top level function
assert isinstance(func, FunctionContract)
self._high_level_calls.append((func.contract, ir.function))
else:
try:
# Todo this part needs more tests and documentation
self._high_level_calls.append((ir.destination.type.type, ir.function))
except AttributeError as error:
# pylint: disable=raise-missing-from
@ -923,7 +920,9 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._vars_read = list(set(self._vars_read))
self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)]
self._local_vars_read = [v for v in self._vars_read if isinstance(v, LocalVariable)]
self._solidity_vars_read = [v for v in self._vars_read if isinstance(v, SolidityVariable)]
self._solidity_vars_read = [
v_ for v_ in self._vars_read if isinstance(v_, SolidityVariable)
]
self._vars_written = list(set(self._vars_written))
self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)]
self._local_vars_written = [v for v in self._vars_written if isinstance(v, LocalVariable)]
@ -934,17 +933,20 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._low_level_calls = list(set(self._low_level_calls))
@staticmethod
def _convert_ssa(v: Variable):
def _convert_ssa(v: Variable) -> Optional[Union[StateVariable, LocalVariable]]:
non_ssa_var: Optional[Union[StateVariable, LocalVariable]]
if isinstance(v, StateIRVariable):
contract = v.contract
assert v.name
non_ssa_var = contract.get_state_variable_from_name(v.name)
return non_ssa_var
assert isinstance(v, LocalIRVariable)
function = v.function
assert v.name
non_ssa_var = function.get_local_variable_from_name(v.name)
return non_ssa_var
def update_read_write_using_ssa(self):
def update_read_write_using_ssa(self) -> None:
if not self.expression:
return
for ir in self.irs_ssa:
@ -961,10 +963,11 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._ssa_vars_read.append(origin)
elif isinstance(ir, (Member, Index)):
if isinstance(ir.variable_right, (StateIRVariable, LocalIRVariable)):
self._ssa_vars_read.append(ir.variable_right)
if isinstance(ir.variable_right, ReferenceVariable):
origin = ir.variable_right.points_to_origin
variable_right: RVALUE = ir.variable_right
if isinstance(variable_right, (StateIRVariable, LocalIRVariable)):
self._ssa_vars_read.append(variable_right)
if isinstance(variable_right, ReferenceVariable):
origin = variable_right.points_to_origin
if isinstance(origin, (StateIRVariable, LocalIRVariable)):
self._ssa_vars_read.append(origin)
@ -984,20 +987,20 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._ssa_local_vars_read = [v for v in self._ssa_vars_read if isinstance(v, LocalVariable)]
self._ssa_vars_written = list(set(self._ssa_vars_written))
self._ssa_state_vars_written = [
v for v in self._ssa_vars_written if isinstance(v, StateVariable)
v for v in self._ssa_vars_written if v and isinstance(v, StateIRVariable)
]
self._ssa_local_vars_written = [
v for v in self._ssa_vars_written if isinstance(v, LocalVariable)
v for v in self._ssa_vars_written if v and isinstance(v, LocalIRVariable)
]
vars_read = [self._convert_ssa(x) for x in self._ssa_vars_read]
vars_written = [self._convert_ssa(x) for x in self._ssa_vars_written]
self._vars_read += [v for v in vars_read if v not in self._vars_read]
self._vars_read += [v_ for v_ in vars_read if v_ and v_ not in self._vars_read]
self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)]
self._local_vars_read = [v for v in self._vars_read if isinstance(v, LocalVariable)]
self._vars_written += [v for v in vars_written if v not in self._vars_written]
self._vars_written += [v_ for v_ in vars_written if v_ and v_ not in self._vars_written]
self._state_vars_written = [v for v in self._vars_written if isinstance(v, StateVariable)]
self._local_vars_written = [v for v in self._vars_written if isinstance(v, LocalVariable)]
@ -1008,13 +1011,13 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
###################################################################################
def __str__(self):
def __str__(self) -> str:
additional_info = ""
if self.expression:
additional_info += " " + str(self.expression)
elif self.variable_declaration:
additional_info += " " + str(self.variable_declaration)
txt = str(self._node_type) + additional_info
txt = str(self._node_type.value) + additional_info
return txt
@ -1026,12 +1029,12 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
def link_nodes(node1: Node, node2: Node):
def link_nodes(node1: Node, node2: Node) -> None:
node1.add_son(node2)
node2.add_father(node1)
def insert_node(origin: Node, node_inserted: Node):
def insert_node(origin: Node, node_inserted: Node) -> None:
sons = origin.sons
link_nodes(origin, node_inserted)
for son in sons:

@ -7,7 +7,7 @@ if TYPE_CHECKING:
# pylint: disable=too-few-public-methods
class Scope:
def __init__(self, is_checked: bool, is_yul: bool, scope: Union["Scope", "Function"]):
def __init__(self, is_checked: bool, is_yul: bool, scope: Union["Scope", "Function"]) -> None:
self.nodes: List["Node"] = []
self.is_checked = is_checked
self.is_yul = is_yul

@ -1,19 +0,0 @@
from typing import TYPE_CHECKING
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.declarations import Contract
class ChildContract(SourceMapping):
def __init__(self):
super().__init__()
self._contract = None
def set_contract(self, contract: "Contract"):
self._contract = contract
@property
def contract(self) -> "Contract":
return self._contract

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Event
class ChildEvent:
def __init__(self):
super().__init__()
self._event = None
def set_event(self, event: "Event"):
self._event = event
@property
def event(self) -> "Event":
return self._event

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
class ChildExpression:
def __init__(self):
super().__init__()
self._expression = None
def set_expression(self, expression: "Expression"):
self._expression = expression
@property
def expression(self) -> "Expression":
return self._expression

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Function
class ChildFunction:
def __init__(self):
super().__init__()
self._function = None
def set_function(self, function: "Function"):
self._function = function
@property
def function(self) -> "Function":
return self._function

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Contract
class ChildInheritance:
def __init__(self):
super().__init__()
self._contract_declarer = None
def set_contract_declarer(self, contract: "Contract"):
self._contract_declarer = contract
@property
def contract_declarer(self) -> "Contract":
return self._contract_declarer

@ -1,31 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.cfg.node import Node
from slither.core.declarations import Function, Contract
class ChildNode:
def __init__(self):
super().__init__()
self._node = None
def set_node(self, node: "Node"):
self._node = node
@property
def node(self) -> "Node":
return self._node
@property
def function(self) -> "Function":
return self.node.function
@property
def contract(self) -> "Contract":
return self.node.function.contract
@property
def compilation_unit(self) -> "SlitherCompilationUnit":
return self.node.compilation_unit

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Structure
class ChildStructure:
def __init__(self):
super().__init__()
self._structure = None
def set_structure(self, structure: "Structure"):
self._structure = structure
@property
def structure(self) -> "Structure":
return self._structure

@ -13,11 +13,13 @@ from slither.core.declarations import (
Function,
Modifier,
)
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.scope.scope import FileScope
from slither.core.solidity_types.type_alias import TypeAliasTopLevel
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.operations import InternalCall
@ -29,7 +31,7 @@ if TYPE_CHECKING:
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherCompilationUnit(Context):
def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit):
def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit) -> None:
super().__init__()
self._core = core
@ -41,9 +43,11 @@ class SlitherCompilationUnit(Context):
self._enums_top_level: List[EnumTopLevel] = []
self._variables_top_level: List[TopLevelVariable] = []
self._functions_top_level: List[FunctionTopLevel] = []
self._using_for_top_level: List[UsingForTopLevel] = []
self._pragma_directives: List[Pragma] = []
self._import_directives: List[Import] = []
self._custom_errors: List[CustomError] = []
self._custom_errors: List[CustomErrorTopLevel] = []
self._user_defined_value_types: Dict[str, TypeAliasTopLevel] = {}
self._all_functions: Set[Function] = set()
self._all_modifiers: Set[Modifier] = set()
@ -53,7 +57,7 @@ class SlitherCompilationUnit(Context):
self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
self._contract_with_missing_inheritance = set()
self._contract_with_missing_inheritance: Set[Contract] = set()
self._source_units: Dict[int, str] = {}
@ -84,7 +88,8 @@ class SlitherCompilationUnit(Context):
@property
def solc_version(self) -> str:
return self._crytic_compile_compilation_unit.compiler_version.version
# TODO: make version a non optional argument of compiler version in cc
return self._crytic_compile_compilation_unit.compiler_version.version # type:ignore
@property
def crytic_compile_compilation_unit(self) -> CompilationUnit:
@ -123,7 +128,7 @@ class SlitherCompilationUnit(Context):
"""list(Contract): List of contracts that are derived and not inherited."""
inheritances = [x.inheritance for x in self.contracts]
inheritance = [item for sublist in inheritances for item in sublist]
return [c for c in self.contracts if c not in inheritance and not c.is_top_level]
return [c for c in self.contracts if c not in inheritance]
def get_contract_from_name(self, contract_name: Union[str, Constant]) -> List[Contract]:
"""
@ -146,25 +151,26 @@ class SlitherCompilationUnit(Context):
def functions(self) -> List[Function]:
return list(self._all_functions)
def add_function(self, func: Function):
def add_function(self, func: Function) -> None:
self._all_functions.add(func)
@property
def modifiers(self) -> List[Modifier]:
return list(self._all_modifiers)
def add_modifier(self, modif: Modifier):
def add_modifier(self, modif: Modifier) -> None:
self._all_modifiers.add(modif)
@property
def functions_and_modifiers(self) -> List[Function]:
return self.functions + self.modifiers
return self.functions + list(self.modifiers)
def propagate_function_calls(self):
def propagate_function_calls(self) -> None:
for f in self.functions_and_modifiers:
for node in f.nodes:
for ir in node.irs_ssa:
if isinstance(ir, InternalCall):
assert ir.function
ir.function.add_reachable_from_node(node, ir)
# endregion
@ -177,8 +183,8 @@ class SlitherCompilationUnit(Context):
@property
def state_variables(self) -> List[StateVariable]:
if self._all_state_variables is None:
state_variables = [c.state_variables for c in self.contracts]
state_variables = [item for sublist in state_variables for item in sublist]
state_variabless = [c.state_variables for c in self.contracts]
state_variables = [item for sublist in state_variabless for item in sublist]
self._all_state_variables = set(state_variables)
return list(self._all_state_variables)
@ -206,9 +212,17 @@ class SlitherCompilationUnit(Context):
return self._functions_top_level
@property
def custom_errors(self) -> List[CustomError]:
def using_for_top_level(self) -> List[UsingForTopLevel]:
return self._using_for_top_level
@property
def custom_errors(self) -> List[CustomErrorTopLevel]:
return self._custom_errors
@property
def user_defined_value_types(self) -> Dict[str, TypeAliasTopLevel]:
return self._user_defined_value_types
# endregion
###################################################################################
###################################################################################
@ -217,7 +231,7 @@ class SlitherCompilationUnit(Context):
###################################################################################
@property
def contracts_with_missing_inheritance(self) -> Set:
def contracts_with_missing_inheritance(self) -> Set[Contract]:
return self._contract_with_missing_inheritance
# endregion
@ -244,7 +258,7 @@ class SlitherCompilationUnit(Context):
###################################################################################
###################################################################################
def compute_storage_layout(self):
def compute_storage_layout(self) -> None:
for contract in self.contracts_derived:
self._storage_layouts[contract.name] = {}
@ -254,6 +268,7 @@ class SlitherCompilationUnit(Context):
if var.is_constant or var.is_immutable:
continue
assert var.type
size, new_slot = var.type.storage_size
if new_slot:
@ -273,7 +288,7 @@ class SlitherCompilationUnit(Context):
else:
offset += size
def storage_layout_of(self, contract, var) -> Tuple[int, int]:
def storage_layout_of(self, contract: Contract, var: StateVariable) -> Tuple[int, int]:
return self._storage_layouts[contract.name][var.canonical_name]
# endregion

@ -12,5 +12,9 @@ from .solidity_variables import (
)
from .structure import Structure
from .enum_contract import EnumContract
from .enum_top_level import EnumTopLevel
from .structure_contract import StructureContract
from .structure_top_level import StructureTopLevel
from .function_contract import FunctionContract
from .function_top_level import FunctionTopLevel
from .custom_error_contract import CustomErrorContract

@ -2,8 +2,9 @@
Contract module
"""
import logging
from collections import defaultdict
from pathlib import Path
from typing import Optional, List, Dict, Callable, Tuple, TYPE_CHECKING, Union
from typing import Optional, List, Dict, Callable, Tuple, TYPE_CHECKING, Union, Set, Any
from crytic_compile.platform import Type as PlatformType
@ -37,24 +38,27 @@ if TYPE_CHECKING:
EnumContract,
StructureContract,
FunctionContract,
CustomErrorContract,
)
from slither.slithir.variables.variable import SlithIRVariable
from slither.core.variables.variable import Variable
from slither.core.variables.state_variable import StateVariable
from slither.core.variables import Variable, StateVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.scope.scope import FileScope
from slither.core.cfg.node import Node
LOGGER = logging.getLogger("Contract")
USING_FOR_KEY = Union[str, Type]
USING_FOR_ITEM = List[Union[Type, Function]]
class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
Contract class
"""
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__()
self._name: Optional[str] = None
@ -69,6 +73,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._enums: Dict[str, "EnumContract"] = {}
self._structures: Dict[str, "StructureContract"] = {}
self._events: Dict[str, "Event"] = {}
# map accessible variable from name -> variable
# do not contain private variables inherited from contract
self._variables: Dict[str, "StateVariable"] = {}
self._variables_ordered: List["StateVariable"] = []
self._modifiers: Dict[str, "Modifier"] = {}
@ -77,18 +83,22 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._custom_errors: Dict[str, "CustomErrorContract"] = {}
# The only str is "*"
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self._using_for: Dict[USING_FOR_KEY, USING_FOR_ITEM] = {}
self._using_for_complete: Optional[Dict[USING_FOR_KEY, USING_FOR_ITEM]] = None
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._is_fully_implemented: bool = False
self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
self._fallback_function: Optional["FunctionContract"] = None
self._receive_function: Optional["FunctionContract"] = None
self._is_upgradeable: Optional[bool] = None
self._is_upgradeable_proxy: Optional[bool] = None
self.is_top_level = False # heavily used, so no @property
self._upgradeable_version: Optional[str] = None
self._initial_state_variables: List["StateVariable"] = [] # ssa
@ -100,6 +110,13 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
self.file_scope: "FileScope" = scope
# memoize
self._state_variables_used_in_reentrant_targets: Optional[
Dict["StateVariable", Set[Union["StateVariable", "Function"]]]
] = None
self._comments: Optional[str] = None
###################################################################################
###################################################################################
# region General's properties
@ -113,7 +130,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._name
@name.setter
def name(self, name: str):
def name(self, name: str) -> None:
self._name = name
@property
@ -123,7 +140,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._id
@id.setter
def id(self, new_id):
def id(self, new_id: int) -> None:
"""Unique id."""
self._id = new_id
@ -136,7 +153,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._kind
@contract_kind.setter
def contract_kind(self, kind):
def contract_kind(self, kind: str) -> None:
self._kind = kind
@property
@ -144,7 +161,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_interface
@is_interface.setter
def is_interface(self, is_interface: bool):
def is_interface(self, is_interface: bool) -> None:
self._is_interface = is_interface
@property
@ -152,9 +169,42 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_library
@is_library.setter
def is_library(self, is_library: bool):
def is_library(self, is_library: bool) -> None:
self._is_library = is_library
@property
def comments(self) -> Optional[str]:
"""
Return the comments associated with the contract.
When using comments, avoid strict text matching, as the solc behavior might change.
For example, for old solc version, the first space after the * is not kept, i.e:
* @title Test Contract
* @dev Test comment
Returns
- " @title Test Contract\n @dev Test comment" for newest versions
- "@title Test Contract\n@dev Test comment" for older versions
Returns:
the comment as a string
"""
return self._comments
@comments.setter
def comments(self, comments: str):
self._comments = comments
@property
def is_fully_implemented(self) -> bool:
return self._is_fully_implemented
@is_fully_implemented.setter
def is_fully_implemented(self, is_fully_implemented: bool):
self._is_fully_implemented = is_fully_implemented
# endregion
###################################################################################
###################################################################################
@ -256,9 +306,32 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def using_for(self) -> Dict[Union[str, Type], List[Type]]:
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
return self._using_for
@property
def using_for_complete(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
"""
Dict[Union[str, Type], List[Type]]: Dict of merged local using for directive with top level directive
"""
def _merge_using_for(
uf1: Dict[USING_FOR_KEY, USING_FOR_ITEM], uf2: Dict[USING_FOR_KEY, USING_FOR_ITEM]
) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
result = {**uf1, **uf2}
for key, value in result.items():
if key in uf1 and key in uf2:
result[key] = value + uf1[key]
return result
if self._using_for_complete is None:
result = self.using_for
top_level_using_for = self.file_scope.using_for_directives
for uftl in top_level_using_for:
result = _merge_using_for(result, uftl.using_for)
self._using_for_complete = result
return self._using_for_complete
# endregion
###################################################################################
###################################################################################
@ -301,7 +374,9 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def variables(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables. Alias to self.state_variables
Returns all the accessible variables (do not include private variable from inherited contract)
list(StateVariable): List of the state variables. Alias to self.state_variables.
"""
return list(self.state_variables)
@ -312,6 +387,9 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def state_variables(self) -> List["StateVariable"]:
"""
Returns all the accessible variables (do not include private variable from inherited contract).
Use state_variables_ordered for all the variables following the storage order
list(StateVariable): List of the state variables.
"""
return list(self._variables.values())
@ -330,7 +408,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return list(self._variables_ordered)
def add_variables_ordered(self, new_vars: List["StateVariable"]):
def add_variables_ordered(self, new_vars: List["StateVariable"]) -> None:
self._variables_ordered += new_vars
@property
@ -356,6 +434,33 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
slithir_variables = [item for sublist in slithir_variabless for item in sublist]
return list(set(slithir_variables))
@property
def state_variables_used_in_reentrant_targets(
self,
) -> Dict["StateVariable", Set[Union["StateVariable", "Function"]]]:
"""
Returns the state variables used in reentrant targets. Heuristics:
- Variable used (read/write) in entry points that are reentrant
- State variables that are public
"""
from slither.core.variables.state_variable import StateVariable
if self._state_variables_used_in_reentrant_targets is None:
reentrant_functions = [f for f in self.functions_entry_points if f.is_reentrant]
variables_used: Dict[
StateVariable, Set[Union[StateVariable, "Function"]]
] = defaultdict(set)
for function in reentrant_functions:
for ir in function.all_slithir_operations():
state_variables = [v for v in ir.used if isinstance(v, StateVariable)]
for state_variable in state_variables:
variables_used[state_variable].add(ir.node.function)
for variable in [v for v in self.state_variables if v.visibility == "public"]:
variables_used[variable].add(variable)
self._state_variables_used_in_reentrant_targets = variables_used
return self._state_variables_used_in_reentrant_targets
# endregion
###################################################################################
###################################################################################
@ -392,7 +497,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
)
@property
def constructors(self) -> List["Function"]:
def constructors(self) -> List["FunctionContract"]:
"""
Return the list of constructors (including inherited)
"""
@ -461,17 +566,17 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return list(self._functions.values())
def available_functions_as_dict(self) -> Dict[str, "FunctionContract"]:
def available_functions_as_dict(self) -> Dict[str, "Function"]:
if self._available_functions_as_dict is None:
self._available_functions_as_dict = {
f.full_name: f for f in self._functions.values() if not f.is_shadowed
}
return self._available_functions_as_dict
def add_function(self, func: "FunctionContract"):
def add_function(self, func: "FunctionContract") -> None:
self._functions[func.canonical_name] = func
def set_functions(self, functions: Dict[str, "FunctionContract"]):
def set_functions(self, functions: Dict[str, "FunctionContract"]) -> None:
"""
Set the functions
@ -515,7 +620,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def available_modifiers_as_dict(self) -> Dict[str, "Modifier"]:
return {m.full_name: m for m in self._modifiers.values() if not m.is_shadowed}
def set_modifiers(self, modifiers: Dict[str, "Modifier"]):
def set_modifiers(self, modifiers: Dict[str, "Modifier"]) -> None:
"""
Set the modifiers
@ -559,6 +664,24 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return self.functions_declared + self.modifiers_declared # type: ignore
@property
def fallback_function(self) -> Optional["FunctionContract"]:
if self._fallback_function is None:
for f in self.functions:
if f.is_fallback:
self._fallback_function = f
break
return self._fallback_function
@property
def receive_function(self) -> Optional["FunctionContract"]:
if self._receive_function is None:
for f in self.functions:
if f.is_receive:
self._receive_function = f
break
return self._receive_function
def available_elements_from_inheritances(
self,
elements: Dict[str, "Function"],
@ -625,7 +748,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
inheritance: List["Contract"],
immediate_inheritance: List["Contract"],
called_base_constructor_contracts: List["Contract"],
):
) -> None:
self._inheritance = inheritance
self._immediate_inheritance = immediate_inheritance
self._explicit_base_constructor_calls = called_base_constructor_contracts
@ -636,7 +759,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
list(Contract): Return the list of contracts derived from self
"""
candidates = self.compilation_unit.contracts
return [c for c in candidates if self in c.inheritance]
return [c for c in candidates if self in c.inheritance] # type: ignore
# endregion
###################################################################################
@ -740,23 +863,25 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return next((v for v in self.state_variables if v.name == canonical_name), None)
def get_structure_from_name(self, structure_name: str) -> Optional["Structure"]:
def get_structure_from_name(self, structure_name: str) -> Optional["StructureContract"]:
"""
Return a structure from a name
Args:
structure_name (str): name of the structure
Returns:
Structure
StructureContract
"""
return next((st for st in self.structures if st.name == structure_name), None)
def get_structure_from_canonical_name(self, structure_name: str) -> Optional["Structure"]:
def get_structure_from_canonical_name(
self, structure_name: str
) -> Optional["StructureContract"]:
"""
Return a structure from a canonical name
Args:
structure_name (str): canonical name of the structure
Returns:
Structure
StructureContract
"""
return next((st for st in self.structures if st.canonical_name == structure_name), None)
@ -790,7 +915,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return next((e for e in self.enums if e.name == enum_name), None)
def get_enum_from_canonical_name(self, enum_name) -> Optional["Enum"]:
def get_enum_from_canonical_name(self, enum_name: str) -> Optional["Enum"]:
"""
Return an enum from a canonical name
Args:
@ -891,7 +1016,9 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def get_summary(self, include_shadowed=True) -> Tuple[str, List[str], List[str], List, List]:
def get_summary(
self, include_shadowed: bool = True
) -> Tuple[str, List[str], List[str], List, List]:
"""Return the function summary
:param include_shadowed: boolean to indicate if shadowed functions should be included (default True)
@ -1144,7 +1271,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def is_test(self) -> bool:
return is_test_contract(self) or self.is_truffle_migration
return is_test_contract(self) or self.is_truffle_migration # type: ignore
# endregion
###################################################################################
@ -1153,8 +1280,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def update_read_write_using_ssa(self):
for function in self.functions + self.modifiers:
def update_read_write_using_ssa(self) -> None:
for function in self.functions + list(self.modifiers):
function.update_read_write_using_ssa()
# endregion
@ -1188,6 +1315,10 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
break
return self._is_upgradeable
@is_upgradeable.setter
def is_upgradeable(self, upgradeable: bool) -> None:
self._is_upgradeable = upgradeable
@property
def is_upgradeable_proxy(self) -> bool:
from slither.core.cfg.node import NodeType
@ -1213,6 +1344,18 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_upgradeable_proxy
return self._is_upgradeable_proxy
@is_upgradeable_proxy.setter
def is_upgradeable_proxy(self, upgradeable_proxy: bool) -> None:
self._is_upgradeable_proxy = upgradeable_proxy
@property
def upgradeable_version(self) -> Optional[str]:
return self._upgradeable_version
@upgradeable_version.setter
def upgradeable_version(self, version_name: str) -> None:
self._upgradeable_version = version_name
# endregion
###################################################################################
###################################################################################
@ -1229,10 +1372,10 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return self._is_incorrectly_parsed
@is_incorrectly_constructed.setter
def is_incorrectly_constructed(self, incorrect: bool):
def is_incorrectly_constructed(self, incorrect: bool) -> None:
self._is_incorrectly_parsed = incorrect
def add_constructor_variables(self):
def add_constructor_variables(self) -> None:
from slither.core.declarations.function_contract import FunctionContract
if self.state_variables:
@ -1241,8 +1384,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
constructor_variable = FunctionContract(self.compilation_unit)
constructor_variable.set_function_type(FunctionType.CONSTRUCTOR_VARIABLES)
constructor_variable.set_contract(self)
constructor_variable.set_contract_declarer(self)
constructor_variable.set_contract(self) # type: ignore
constructor_variable.set_contract_declarer(self) # type: ignore
constructor_variable.set_visibility("internal")
# For now, source mapping of the constructor variable is the whole contract
# Could be improved with a targeted source mapping
@ -1273,8 +1416,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
constructor_variable.set_function_type(
FunctionType.CONSTRUCTOR_CONSTANT_VARIABLES
)
constructor_variable.set_contract(self)
constructor_variable.set_contract_declarer(self)
constructor_variable.set_contract(self) # type: ignore
constructor_variable.set_contract_declarer(self) # type: ignore
constructor_variable.set_visibility("internal")
# For now, source mapping of the constructor variable is the whole contract
# Could be improved with a targeted source mapping
@ -1301,7 +1444,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def _create_node(
self, func: Function, counter: int, variable: "Variable", scope: Union[Scope, Function]
):
) -> "Node":
from slither.core.cfg.node import Node, NodeType
from slither.core.expressions import (
AssignmentOperationType,
@ -1333,7 +1476,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def convert_expression_to_slithir_ssa(self):
def convert_expression_to_slithir_ssa(self) -> None:
"""
Assume generate_slithir_and_analyze was called on all functions
@ -1355,22 +1498,23 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
all_ssa_state_variables_instances[v.canonical_name] = new_var
self._initial_state_variables.append(new_var)
for func in self.functions + self.modifiers:
for func in self.functions + list(self.modifiers):
func.generate_slithir_ssa(all_ssa_state_variables_instances)
def fix_phi(self):
last_state_variables_instances = {}
initial_state_variables_instances = {}
def fix_phi(self) -> None:
last_state_variables_instances: Dict[str, List["StateVariable"]] = {}
initial_state_variables_instances: Dict[str, "StateVariable"] = {}
for v in self._initial_state_variables:
last_state_variables_instances[v.canonical_name] = []
initial_state_variables_instances[v.canonical_name] = v
for func in self.functions + self.modifiers:
for func in self.functions + list(self.modifiers):
result = func.get_last_ssa_state_variables_instances()
for variable_name, instances in result.items():
last_state_variables_instances[variable_name] += instances
# TODO: investigate the next operation
last_state_variables_instances[variable_name] += list(instances)
for func in self.functions + self.modifiers:
for func in self.functions + list(self.modifiers):
func.fix_phi(last_state_variables_instances, initial_state_variables_instances)
# endregion
@ -1380,20 +1524,20 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
###################################################################################
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if isinstance(other, str):
return other == self.name
return NotImplemented
def __neq__(self, other):
def __neq__(self, other: Any) -> bool:
if isinstance(other, str):
return other != self.name
return NotImplemented
def __str__(self):
def __str__(self) -> str:
return self.name
def __hash__(self):
return self._id
def __hash__(self) -> int:
return self._id # type:ignore
# endregion

@ -0,0 +1,29 @@
from typing import TYPE_CHECKING, Optional
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING:
from slither.core.declarations import Contract
class ContractLevel(SourceMapping):
"""
This class is used to represent objects that are at the contract level
The opposite is TopLevel
"""
def __init__(self) -> None:
super().__init__()
# TODO remove all the setters for the child objects
# And make it a constructor arguement
# This will remove the optional
self._contract: Optional["Contract"] = None
def set_contract(self, contract: "Contract") -> None:
self._contract = contract
@property
def contract(self) -> "Contract":
assert self._contract
return self._contract

@ -1,15 +1,15 @@
from typing import List, TYPE_CHECKING, Optional, Type, Union
from typing import List, TYPE_CHECKING, Optional, Type
from slither.core.solidity_types import UserDefinedType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
from slither.utils.type import is_underlying_type_address
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
class CustomError(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__()
self._name: str = ""
self._parameters: List[LocalVariable] = []
@ -30,7 +30,7 @@ class CustomError(SourceMapping):
def parameters(self) -> List[LocalVariable]:
return self._parameters
def add_parameters(self, p: "LocalVariable"):
def add_parameters(self, p: "LocalVariable") -> None:
self._parameters.append(p)
@property
@ -42,16 +42,13 @@ class CustomError(SourceMapping):
###################################################################################
@staticmethod
def _convert_type_for_solidity_signature(t: Optional[Union[Type, List[Type]]]):
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
def _convert_type_for_solidity_signature(t: Optional[Type]) -> str:
if is_underlying_type_address(t):
return "address"
return str(t)
@property
def solidity_signature(self) -> Optional[str]:
def solidity_signature(self) -> str:
"""
Return a signature following the Solidity Standard
Contract and converted into address
@ -63,7 +60,7 @@ class CustomError(SourceMapping):
# (set_solidity_sig was not called before find_variable)
if self._solidity_signature is None:
raise ValueError("Custom Error not yet built")
return self._solidity_signature
return self._solidity_signature # type: ignore
def set_solidity_sig(self) -> None:
"""
@ -72,7 +69,7 @@ class CustomError(SourceMapping):
Returns:
"""
parameters = [x.type for x in self.parameters]
parameters = [x.type for x in self.parameters if x.type]
self._full_name = self.name + "(" + ",".join(map(str, parameters)) + ")"
solidity_parameters = map(self._convert_type_for_solidity_signature, parameters)
self._solidity_signature = self.name + "(" + ",".join(solidity_parameters) + ")"
@ -92,5 +89,5 @@ class CustomError(SourceMapping):
###################################################################################
###################################################################################
def __str__(self):
def __str__(self) -> str:
return "revert " + self.solidity_signature

@ -1,9 +1,15 @@
from slither.core.children.child_contract import ChildContract
from typing import TYPE_CHECKING
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations.custom_error import CustomError
if TYPE_CHECKING:
from slither.core.declarations import Contract
class CustomErrorContract(CustomError, ChildContract):
def is_declared_by(self, contract):
class CustomErrorContract(CustomError, ContractLevel):
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:

@ -9,6 +9,6 @@ if TYPE_CHECKING:
class CustomErrorTopLevel(CustomError, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__(compilation_unit)
self.file_scope: "FileScope" = scope

@ -4,7 +4,7 @@ from slither.core.source_mapping.source_mapping import SourceMapping
class Enum(SourceMapping):
def __init__(self, name: str, canonical_name: str, values: List[str]):
def __init__(self, name: str, canonical_name: str, values: List[str]) -> None:
super().__init__()
self._name = name
self._canonical_name = canonical_name
@ -33,5 +33,5 @@ class Enum(SourceMapping):
def max(self) -> int:
return self._max
def __str__(self):
def __str__(self) -> str:
return self.name

@ -1,13 +1,13 @@
from typing import TYPE_CHECKING
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Enum
if TYPE_CHECKING:
from slither.core.declarations import Contract
class EnumContract(Enum, ChildContract):
class EnumContract(Enum, ContractLevel):
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract

@ -8,6 +8,8 @@ if TYPE_CHECKING:
class EnumTopLevel(Enum, TopLevel):
def __init__(self, name: str, canonical_name: str, values: List[str], scope: "FileScope"):
def __init__(
self, name: str, canonical_name: str, values: List[str], scope: "FileScope"
) -> None:
super().__init__(name, canonical_name, values)
self.file_scope: "FileScope" = scope

@ -1,6 +1,6 @@
from typing import List, Tuple, TYPE_CHECKING
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.event_variable import EventVariable
@ -8,8 +8,8 @@ if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(ChildContract, SourceMapping):
def __init__(self):
class Event(ContractLevel, SourceMapping):
def __init__(self) -> None:
super().__init__()
self._name = None
self._elems: List[EventVariable] = []
@ -59,5 +59,5 @@ class Event(ChildContract, SourceMapping):
"""
return self.contract == contract
def __str__(self):
def __str__(self) -> str:
return self.name

@ -6,7 +6,7 @@ from abc import abstractmethod, ABCMeta
from collections import namedtuple
from enum import Enum
from itertools import groupby
from typing import Dict, TYPE_CHECKING, List, Optional, Set, Union, Callable, Tuple
from typing import Any, Dict, TYPE_CHECKING, List, Optional, Set, Union, Callable, Tuple
from slither.core.cfg.scope import Scope
from slither.core.declarations.solidity_variables import (
@ -27,6 +27,7 @@ from slither.core.variables.state_variable import StateVariable
from slither.utils.type import convert_type_for_solidity_signature_to_string
from slither.utils.utils import unroll
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
if TYPE_CHECKING:
@ -45,6 +46,7 @@ if TYPE_CHECKING:
from slither.slithir.operations import Operation
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable
LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
@ -56,7 +58,7 @@ class ModifierStatements:
modifier: Union["Contract", "Function"],
entry_point: "Node",
nodes: List["Node"],
):
) -> None:
self._modifier = modifier
self._entry_point = entry_point
self._nodes = nodes
@ -116,7 +118,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
Function class
"""
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__()
self._internal_scope: List[str] = []
self._name: Optional[str] = None
@ -189,7 +191,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
# set(ReacheableNode)
self._reachable_from_nodes: Set[ReacheableNode] = set()
self._reachable_from_functions: Set[ReacheableNode] = set()
self._reachable_from_functions: Set[Function] = set()
self._all_reachable_from_functions: Optional[Set[Function]] = None
# Constructor, fallback, State variable constructor
self._function_type: Optional[FunctionType] = None
@ -214,11 +217,14 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
# Assume we are analyzing Solidty by default
# Assume we are analyzing Solidity by default
self.function_language: FunctionLanguage = FunctionLanguage.Solidity
self._id: Optional[str] = None
# To be improved with a parsing of the documentation
self.has_documentation: bool = False
###################################################################################
###################################################################################
# region General properties
@ -291,7 +297,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def contains_assembly(self, c: bool):
self._contains_assembly = c
def can_reenter(self, callstack=None) -> bool:
def can_reenter(self, callstack: Optional[List[Union["Function", "Variable"]]] = None) -> bool:
"""
Check if the function can re-enter
Follow internal calls.
@ -366,7 +372,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
def set_function_type(self, t: FunctionType):
def set_function_type(self, t: FunctionType) -> None:
assert isinstance(t, FunctionType)
self._function_type = t
@ -451,7 +457,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def visibility(self, v: str):
self._visibility = v
def set_visibility(self, v: str):
def set_visibility(self, v: str) -> None:
self._visibility = v
@property
@ -550,7 +556,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def entry_point(self, node: "Node"):
self._entry_point = node
def add_node(self, node: "Node"):
def add_node(self, node: "Node") -> None:
if not self._entry_point:
self._entry_point = node
self._nodes.append(node)
@ -594,7 +600,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return list(self._parameters)
def add_parameters(self, p: "LocalVariable"):
def add_parameters(self, p: "LocalVariable") -> None:
self._parameters.append(p)
@property
@ -604,7 +610,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return list(self._parameters_ssa)
def add_parameter_ssa(self, var: "LocalIRVariable"):
def add_parameter_ssa(self, var: "LocalIRVariable") -> None:
self._parameters_ssa.append(var)
def parameters_src(self) -> SourceMapping:
@ -647,7 +653,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return list(self._returns)
def add_return(self, r: "LocalVariable"):
def add_return(self, r: "LocalVariable") -> None:
self._returns.append(r)
@property
@ -657,7 +663,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return list(self._returns_ssa)
def add_return_ssa(self, var: "LocalIRVariable"):
def add_return_ssa(self, var: "LocalIRVariable") -> None:
self._returns_ssa.append(var)
# endregion
@ -676,7 +682,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
return [c.modifier for c in self._modifiers]
def add_modifier(self, modif: "ModifierStatements"):
def add_modifier(self, modif: "ModifierStatements") -> None:
self._modifiers.append(modif)
@property
@ -710,7 +716,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
# This is a list of contracts internally, so we convert it to a list of constructor functions.
return list(self._explicit_base_constructor_calls)
def add_explicit_base_constructor_calls_statements(self, modif: ModifierStatements):
def add_explicit_base_constructor_calls_statements(self, modif: ModifierStatements) -> None:
self._explicit_base_constructor_calls.append(modif)
# endregion
@ -1029,10 +1035,31 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
return self._reachable_from_nodes
@property
def reachable_from_functions(self) -> Set[ReacheableNode]:
def reachable_from_functions(self) -> Set["Function"]:
return self._reachable_from_functions
def add_reachable_from_node(self, n: "Node", ir: "Operation"):
@property
def all_reachable_from_functions(self) -> Set["Function"]:
"""
Give the recursive version of reachable_from_functions (all the functions that lead to call self in the CFG)
"""
if self._all_reachable_from_functions is None:
functions: Set["Function"] = set()
new_functions = self.reachable_from_functions
# iterate until we have are finding new functions
while new_functions and not new_functions.issubset(functions):
functions = functions.union(new_functions)
# Use a temporary set, because we iterate over new_functions
new_functionss: Set["Function"] = set()
for f in new_functions:
new_functionss = new_functionss.union(f.reachable_from_functions)
new_functions = new_functionss - functions
self._all_reachable_from_functions = functions
return self._all_reachable_from_functions
def add_reachable_from_node(self, n: "Node", ir: "Operation") -> None:
self._reachable_from_nodes.add(ReacheableNode(n, ir))
self._reachable_from_functions.add(n.function)
@ -1043,7 +1070,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
def _explore_functions(self, f_new_values: Callable[["Function"], List]):
def _explore_functions(self, f_new_values: Callable[["Function"], List]) -> List[Any]:
values = f_new_values(self)
explored = [self]
to_explore = [
@ -1193,11 +1220,13 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
func: "Function",
f: Callable[["Node"], List[SolidityVariable]],
include_loop: bool,
):
) -> List[Any]:
ret = [f(n) for n in func.nodes if n.is_conditional(include_loop)]
return [item for sublist in ret for item in sublist]
def all_conditional_solidity_variables_read(self, include_loop=True) -> List[SolidityVariable]:
def all_conditional_solidity_variables_read(
self, include_loop: bool = True
) -> List[SolidityVariable]:
"""
Return the Soldiity variables directly used in a condtion
@ -1233,7 +1262,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
return [var for var in ret if isinstance(var, SolidityVariable)]
@staticmethod
def _explore_func_nodes(func: "Function", f: Callable[["Node"], List[SolidityVariable]]):
def _explore_func_nodes(
func: "Function", f: Callable[["Node"], List[SolidityVariable]]
) -> List[Union[Any, SolidityVariableComposed]]:
ret = [f(n) for n in func.nodes]
return [item for sublist in ret for item in sublist]
@ -1342,7 +1373,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
with open(filename, "w", encoding="utf8") as f:
f.write(content)
def slithir_cfg_to_dot_str(self, skip_expressions=False) -> str:
def slithir_cfg_to_dot_str(self, skip_expressions: bool = False) -> str:
"""
Export the CFG to a DOT format. The nodes includes the Solidity expressions and the IRs
:return: the DOT content
@ -1353,7 +1384,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
content = ""
content += "digraph{\n"
for node in self.nodes:
label = f"Node Type: {str(node.type)} {node.node_id}\n"
label = f"Node Type: {node.type.value} {node.node_id}\n"
if node.expression and not skip_expressions:
label += f"\nEXPRESSION:\n{node.expression}\n"
if node.irs and not skip_expressions:
@ -1460,6 +1491,26 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
)
return self._is_protected
@property
def is_reentrant(self) -> bool:
"""
Determine if the function can be re-entered
"""
# TODO: compare with hash of known nonReentrant modifier instead of the name
if "nonReentrant" in [m.name for m in self.modifiers]:
return False
if self.visibility in ["public", "external"]:
return True
# If it's an internal function, check if all its entry points have the nonReentrant modifier
all_entry_points = [
f for f in self.all_reachable_from_functions if f.visibility in ["public", "external"]
]
if not all_entry_points:
return True
return not all(("nonReentrant" in [m.name for m in f.modifiers] for f in all_entry_points))
# endregion
###################################################################################
###################################################################################
@ -1467,7 +1518,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
def _analyze_read_write(self):
def _analyze_read_write(self) -> None:
"""Compute variables read/written/..."""
write_var = [x.variables_written_as_expression for x in self.nodes]
write_var = [x for x in write_var if x]
@ -1525,7 +1576,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
slithir_variables = [x for x in slithir_variables if x]
self._slithir_variables = [item for sublist in slithir_variables for item in sublist]
def _analyze_calls(self):
def _analyze_calls(self) -> None:
calls = [x.calls_as_expression for x in self.nodes]
calls = [x for x in calls if x]
calls = [item for sublist in calls for item in sublist]
@ -1657,7 +1708,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
return self._get_last_ssa_variable_instances(target_state=False, target_local=True)
@staticmethod
def _unchange_phi(ir: "Operation"):
def _unchange_phi(ir: "Operation") -> bool:
from slither.slithir.operations import Phi, PhiCallback
if not isinstance(ir, (Phi, PhiCallback)) or len(ir.rvalues) > 1:
@ -1666,7 +1717,11 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
return True
return ir.rvalues[0] == ir.lvalue
def fix_phi(self, last_state_variables_instances, initial_state_variables_instances):
def fix_phi(
self,
last_state_variables_instances: Dict[str, List["StateVariable"]],
initial_state_variables_instances: Dict[str, "StateVariable"],
) -> None:
from slither.slithir.operations import InternalCall, PhiCallback
from slither.slithir.variables import Constant, StateIRVariable
@ -1700,7 +1755,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)]
def generate_slithir_and_analyze(self):
def generate_slithir_and_analyze(self) -> None:
for node in self.nodes:
node.slithir_generation()
@ -1711,7 +1766,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def generate_slithir_ssa(self, all_ssa_state_variables_instances):
pass
def update_read_write_using_ssa(self):
def update_read_write_using_ssa(self) -> None:
for node in self.nodes:
node.update_read_write_using_ssa()
self._analyze_read_write()
@ -1722,7 +1777,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
def __str__(self):
def __str__(self) -> str:
return self.name
# endregion

@ -1,20 +1,44 @@
"""
Function module
"""
from typing import TYPE_CHECKING, List, Tuple
from typing import Dict, TYPE_CHECKING, List, Tuple, Optional
from slither.core.children.child_contract import ChildContract
from slither.core.children.child_inheritance import ChildInheritance
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Function
from slither.utils.code_complexity import compute_cyclomatic_complexity
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
if TYPE_CHECKING:
from slither.core.declarations import Contract
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable
from slither.core.compilation_unit import SlitherCompilationUnit
class FunctionContract(Function, ContractLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__(compilation_unit)
self._contract_declarer: Optional["Contract"] = None
def set_contract_declarer(self, contract: "Contract") -> None:
self._contract_declarer = contract
@property
def contract_declarer(self) -> "Contract":
"""
Return the contract where this function was declared. Only functions have both a contract, and contract_declarer
This is because we need to have separate representation of the function depending of the contract's context
For example a function calling super.f() will generate different IR depending on the current contract's inheritance
Returns:
The contract where this function was declared
"""
assert self._contract_declarer
return self._contract_declarer
class FunctionContract(Function, ChildContract, ChildInheritance):
@property
def canonical_name(self) -> str:
"""
@ -71,7 +95,7 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
def get_summary(
self,
) -> Tuple[str, str, str, List[str], List[str], List[str], List[str], List[str]]:
) -> Tuple[str, str, str, List[str], List[str], List[str], List[str], List[str], int]:
"""
Return the function summary
Returns:
@ -87,6 +111,7 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
[str(x) for x in self.state_variables_written],
[str(x) for x in self.internal_calls],
[str(x) for x in self.external_calls_as_expressions],
compute_cyclomatic_complexity(self),
)
# endregion
@ -96,7 +121,9 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
###################################################################################
###################################################################################
def generate_slithir_ssa(self, all_ssa_state_variables_instances):
def generate_slithir_ssa(
self, all_ssa_state_variables_instances: Dict[str, "StateIRVariable"]
) -> None:
from slither.slithir.utils.ssa import add_ssa_ir, transform_slithir_vars_to_ssa
from slither.core.dominators.utils import (
compute_dominance_frontier,

@ -1,7 +1,7 @@
"""
Function module
"""
from typing import List, Tuple, TYPE_CHECKING
from typing import Dict, List, Tuple, TYPE_CHECKING
from slither.core.declarations import Function
from slither.core.declarations.top_level import TopLevel
@ -9,10 +9,11 @@ from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable
class FunctionTopLevel(Function, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__(compilation_unit)
self._scope: "FileScope" = scope
@ -78,7 +79,9 @@ class FunctionTopLevel(Function, TopLevel):
###################################################################################
###################################################################################
def generate_slithir_ssa(self, all_ssa_state_variables_instances):
def generate_slithir_ssa(
self, all_ssa_state_variables_instances: Dict[str, "StateIRVariable"]
) -> None:
# pylint: disable=import-outside-toplevel
from slither.slithir.utils.ssa import add_ssa_ir, transform_slithir_vars_to_ssa
from slither.core.dominators.utils import (

@ -8,7 +8,7 @@ if TYPE_CHECKING:
class Import(SourceMapping):
def __init__(self, filename: Path, scope: "FileScope"):
def __init__(self, filename: Path, scope: "FileScope") -> None:
super().__init__()
self._filename: Path = filename
self._alias: Optional[str] = None

@ -7,7 +7,7 @@ if TYPE_CHECKING:
class Pragma(SourceMapping):
def __init__(self, directive: List[str], scope: "FileScope"):
def __init__(self, directive: List[str], scope: "FileScope") -> None:
super().__init__()
self._directive = directive
self.scope: "FileScope" = scope
@ -39,5 +39,5 @@ class Pragma(SourceMapping):
return self._directive[0] == "experimental" and self._directive[1] == "ABIEncoderV2"
return False
def __str__(self):
def __str__(self) -> str:
return "pragma " + "".join(self.directive)

@ -1,7 +1,11 @@
"""
Special variable to model import with renaming
"""
from typing import Union
from slither.core.declarations import Import
from slither.core.declarations.contract import Contract
from slither.core.declarations.solidity_variables import SolidityVariable
from slither.core.solidity_types import ElementaryType
from slither.core.variables.variable import Variable
@ -13,7 +17,7 @@ class SolidityImportPlaceHolder(Variable):
In the long term we should remove this and better integrate import aliases
"""
def __init__(self, import_directive: Import):
def __init__(self, import_directive: Import) -> None:
super().__init__()
assert import_directive.alias is not None
self._import_directive = import_directive
@ -27,7 +31,7 @@ class SolidityImportPlaceHolder(Variable):
def type(self) -> ElementaryType:
return ElementaryType("string")
def __eq__(self, other):
def __eq__(self, other: Union[Contract, SolidityVariable]) -> bool:
return (
self.__class__ == other.__class__
and self._import_directive.filename == self._import_directive.filename

@ -1,13 +1,11 @@
# https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html
from typing import List, Dict, Union, TYPE_CHECKING
from typing import List, Dict, Union, Any
from slither.core.declarations.custom_error import CustomError
from slither.core.solidity_types import ElementaryType, TypeInformation
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.exceptions import SlitherException
if TYPE_CHECKING:
pass
SOLIDITY_VARIABLES = {
"now": "uint256",
@ -70,6 +68,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"abi.encodePacked()": ["bytes"],
"abi.encodeWithSelector()": ["bytes"],
"abi.encodeWithSignature()": ["bytes"],
"abi.encodeCall()": ["bytes"],
"bytes.concat()": ["bytes"],
"string.concat()": ["string"],
# abi.decode returns an a list arbitrary types
@ -83,7 +82,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
}
def solidity_function_signature(name):
def solidity_function_signature(name: str) -> str:
"""
Return the function signature (containing the return value)
It is useful if a solidity function is used as a pointer
@ -97,17 +96,17 @@ def solidity_function_signature(name):
class SolidityVariable(SourceMapping):
def __init__(self, name: str):
def __init__(self, name: str) -> None:
super().__init__()
self._check_name(name)
self._name = name
# 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) -> None: # pylint: disable=no-self-use
assert name in SOLIDITY_VARIABLES or name.endswith(("_slot", "_offset"))
@property
def state_variable(self):
def state_variable(self) -> str:
if self._name.endswith("_slot"):
return self._name[:-5]
if self._name.endswith("_offset"):
@ -123,18 +122,18 @@ class SolidityVariable(SourceMapping):
def type(self) -> ElementaryType:
return ElementaryType(SOLIDITY_VARIABLES[self.name])
def __str__(self):
def __str__(self) -> str:
return self._name
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self):
def __hash__(self) -> int:
return hash(self.name)
class SolidityVariableComposed(SolidityVariable):
def _check_name(self, name: str):
def _check_name(self, name: str) -> None:
assert name in SOLIDITY_VARIABLES_COMPOSED
@property
@ -145,13 +144,13 @@ class SolidityVariableComposed(SolidityVariable):
def type(self) -> ElementaryType:
return ElementaryType(SOLIDITY_VARIABLES_COMPOSED[self.name])
def __str__(self):
def __str__(self) -> str:
return self._name
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self):
def __hash__(self) -> int:
return hash(self.name)
@ -161,7 +160,7 @@ class SolidityFunction(SourceMapping):
# https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#type-information
# As a result, we set return_type during the Ir conversion
def __init__(self, name: str):
def __init__(self, name: str) -> None:
super().__init__()
assert name in SOLIDITY_FUNCTIONS
self._name = name
@ -183,31 +182,35 @@ class SolidityFunction(SourceMapping):
return self._return_type
@return_type.setter
def return_type(self, r: List[Union[TypeInformation, ElementaryType]]):
def return_type(self, r: List[Union[TypeInformation, ElementaryType]]) -> None:
self._return_type = r
def __str__(self):
def __str__(self) -> str:
return self._name
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
return self.__class__ == other.__class__ and self.name == other.name
def __hash__(self):
def __hash__(self) -> int:
return hash(self.name)
class SolidityCustomRevert(SolidityFunction):
def __init__(self, custom_error: CustomError): # pylint: disable=super-init-not-called
def __init__(self, custom_error: CustomError) -> None: # pylint: disable=super-init-not-called
self._name = "revert " + custom_error.solidity_signature
self._custom_error = custom_error
self._return_type: List[Union[TypeInformation, ElementaryType]] = []
def __eq__(self, other):
@property
def custom_error(self) -> CustomError:
return self._custom_error
def __eq__(self, other: Any) -> bool:
return (
self.__class__ == other.__class__
and self.name == other.name
and self._custom_error == other._custom_error
)
def __hash__(self):
def __hash__(self) -> int:
return hash(hash(self.name) + hash(self._custom_error))

@ -51,3 +51,17 @@ class Structure(SourceMapping):
def __str__(self) -> str:
return self.name
def __eq__(self, other) -> bool:
if not isinstance(other, Structure):
return False
if len(self.elems) != len(other.elems):
return False
for idx, elem in enumerate(self.elems_ordered):
other_elem = other.elems_ordered[idx]
if str(other_elem.type) != str(elem.type) or other_elem.name != elem.name:
return False
return self.name == other.name
def __hash__(self):
return hash(self.name)

@ -1,8 +1,8 @@
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Structure
class StructureContract(Structure, ChildContract):
class StructureContract(Structure, ContractLevel):
def is_declared_by(self, contract):
"""
Check if the element is declared by the contract

@ -9,6 +9,6 @@ if TYPE_CHECKING:
class StructureTopLevel(Structure, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope"):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__(compilation_unit)
self.file_scope: "FileScope" = scope

@ -2,4 +2,8 @@ from slither.core.source_mapping.source_mapping import SourceMapping
class TopLevel(SourceMapping):
pass
"""
This class is used to represent objects that are at the top level
The opposite is ContractLevel
"""

@ -0,0 +1,19 @@
from typing import TYPE_CHECKING, List, Dict, Union
from slither.core.declarations.contract import USING_FOR_KEY, USING_FOR_ITEM
from slither.core.solidity_types.type import Type
from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class UsingForTopLevel(TopLevel):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self.file_scope: "FileScope" = scope
@property
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
return self._using_for

@ -1,4 +1,4 @@
from typing import List, TYPE_CHECKING
from typing import Set, List, TYPE_CHECKING
from slither.core.cfg.node import NodeType
@ -6,7 +6,7 @@ if TYPE_CHECKING:
from slither.core.cfg.node import Node
def intersection_predecessor(node: "Node"):
def intersection_predecessor(node: "Node") -> Set["Node"]:
if not node.fathers:
return set()
ret = node.fathers[0].dominators
@ -15,7 +15,7 @@ def intersection_predecessor(node: "Node"):
return ret
def _compute_dominators(nodes: List["Node"]):
def _compute_dominators(nodes: List["Node"]) -> None:
changed = True
while changed:
@ -28,7 +28,7 @@ def _compute_dominators(nodes: List["Node"]):
changed = True
def _compute_immediate_dominators(nodes: List["Node"]):
def _compute_immediate_dominators(nodes: List["Node"]) -> None:
for node in nodes:
idom_candidates = set(node.dominators)
idom_candidates.remove(node)
@ -58,7 +58,7 @@ def _compute_immediate_dominators(nodes: List["Node"]):
idom.dominator_successors.add(node)
def compute_dominators(nodes: List["Node"]):
def compute_dominators(nodes: List["Node"]) -> None:
"""
Naive implementation of Cooper, Harvey, Kennedy algo
See 'A Simple,Fast Dominance Algorithm'
@ -74,7 +74,7 @@ def compute_dominators(nodes: List["Node"]):
_compute_immediate_dominators(nodes)
def compute_dominance_frontier(nodes: List["Node"]):
def compute_dominance_frontier(nodes: List["Node"]) -> None:
"""
Naive implementation of Cooper, Harvey, Kennedy algo
See 'A Simple,Fast Dominance Algorithm'
@ -95,4 +95,5 @@ def compute_dominance_frontier(nodes: List["Node"]):
runner.dominance_frontier = runner.dominance_frontier.union({node})
while runner != node.immediate_dominator:
runner.dominance_frontier = runner.dominance_frontier.union({node})
assert runner.immediate_dominator
runner = runner.immediate_dominator

@ -2,7 +2,6 @@ import logging
from enum import Enum
from typing import Optional, TYPE_CHECKING, List
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.exceptions import SlitherCoreError
@ -78,20 +77,20 @@ class AssignmentOperationType(Enum):
raise SlitherCoreError(f"str: Unknown operation type {self})")
class AssignmentOperation(ExpressionTyped):
class AssignmentOperation(Expression):
def __init__(
self,
left_expression: Expression,
right_expression: Expression,
expression_type: AssignmentOperationType,
expression_return_type: Optional["Type"],
):
) -> None:
assert isinstance(left_expression, Expression)
assert isinstance(right_expression, Expression)
super().__init__()
left_expression.set_lvalue()
self._expressions = [left_expression, right_expression]
self._type: Optional["AssignmentOperationType"] = expression_type
self._type: AssignmentOperationType = expression_type
self._expression_return_type: Optional["Type"] = expression_return_type
@property

@ -2,7 +2,6 @@ import logging
from enum import Enum
from typing import List
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.exceptions import SlitherCoreError
@ -148,7 +147,7 @@ class BinaryOperationType(Enum):
raise SlitherCoreError(f"str: Unknown operation type {self})")
class BinaryOperation(ExpressionTyped):
class BinaryOperation(Expression):
def __init__(
self,
left_expression: Expression,

@ -1,10 +1,10 @@
from typing import Optional, List
from typing import Any, Optional, List
from slither.core.expressions.expression import Expression
class CallExpression(Expression): # pylint: disable=too-many-instance-attributes
def __init__(self, called, arguments, type_call):
def __init__(self, called: Expression, arguments: List[Any], type_call: str) -> None:
assert isinstance(called, Expression)
super().__init__()
self._called: Expression = called
@ -22,7 +22,7 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
return self._value
@call_value.setter
def call_value(self, v):
def call_value(self, v: Optional[Expression]) -> None:
self._value = v
@property
@ -30,15 +30,15 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
return self._gas
@call_gas.setter
def call_gas(self, gas):
def call_gas(self, gas: Optional[Expression]) -> None:
self._gas = gas
@property
def call_salt(self):
def call_salt(self) -> Optional[Expression]:
return self._salt
@call_salt.setter
def call_salt(self, salt):
def call_salt(self, salt: Optional[Expression]) -> None:
self._salt = salt
@property
@ -53,7 +53,7 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
def type_call(self) -> str:
return self._type_call
def __str__(self):
def __str__(self) -> str:
txt = str(self._called)
if self.call_gas or self.call_value:
gas = f"gas: {self.call_gas}" if self.call_gas else ""

@ -1,10 +1,23 @@
from typing import List
from typing import Union, List
from .expression import Expression
from slither.core.expressions.binary_operation import BinaryOperation
from slither.core.expressions.expression import Expression
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.literal import Literal
from slither.core.expressions.tuple_expression import TupleExpression
from slither.core.expressions.type_conversion import TypeConversion
from slither.core.expressions.unary_operation import UnaryOperation
class ConditionalExpression(Expression):
def __init__(self, if_expression, then_expression, else_expression):
def __init__(
self,
if_expression: Union[BinaryOperation, Identifier, Literal],
then_expression: Union[
"ConditionalExpression", TypeConversion, Literal, TupleExpression, Identifier
],
else_expression: Union[TupleExpression, UnaryOperation, Identifier, Literal],
) -> None:
assert isinstance(if_expression, Expression)
assert isinstance(then_expression, Expression)
assert isinstance(else_expression, Expression)
@ -29,7 +42,7 @@ class ConditionalExpression(Expression):
def then_expression(self) -> Expression:
return self._then_expression
def __str__(self):
def __str__(self) -> str:
return (
"if "
+ str(self._if_expression)

@ -3,10 +3,11 @@
"""
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
from slither.core.solidity_types.elementary_type import ElementaryType
class ElementaryTypeNameExpression(Expression):
def __init__(self, t):
def __init__(self, t: ElementaryType) -> None:
assert isinstance(t, Type)
super().__init__()
self._type = t
@ -20,5 +21,5 @@ class ElementaryTypeNameExpression(Expression):
assert isinstance(new_type, Type)
self._type = new_type
def __str__(self):
def __str__(self) -> str:
return str(self._type)

@ -1,20 +0,0 @@
from typing import Optional, TYPE_CHECKING
from slither.core.expressions.expression import Expression
if TYPE_CHECKING:
from slither.core.solidity_types.type import Type
class ExpressionTyped(Expression): # pylint: disable=too-few-public-methods
def __init__(self) -> None:
super().__init__()
self._type: Optional["Type"] = None
@property
def type(self) -> Optional["Type"]:
return self._type
@type.setter
def type(self, new_type: "Type"):
self._type = new_type

@ -1,19 +1,81 @@
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional, Union
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations.top_level import TopLevel
from slither.core.expressions.expression import Expression
from slither.core.variables.variable import Variable
from slither.core.expressions.expression_typed import ExpressionTyped
if TYPE_CHECKING:
from slither.core.variables.variable import Variable
from slither.core.solidity_types.type import Type
from slither.core.declarations import Contract, SolidityVariable, SolidityFunction
from slither.solc_parsing.yul.evm_functions import YulBuiltin
class Identifier(ExpressionTyped):
def __init__(self, value):
class Identifier(Expression):
def __init__(
self,
value: Union[
Variable,
"TopLevel",
"ContractLevel",
"Contract",
"SolidityVariable",
"SolidityFunction",
"YulBuiltin",
],
) -> None:
super().__init__()
self._value: "Variable" = value
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract, SolidityVariable, SolidityFunction
from slither.solc_parsing.yul.evm_functions import YulBuiltin
assert isinstance(
value,
(
Variable,
TopLevel,
ContractLevel,
Contract,
SolidityVariable,
SolidityFunction,
YulBuiltin,
),
)
self._value: Union[
Variable,
"TopLevel",
"ContractLevel",
"Contract",
"SolidityVariable",
"SolidityFunction",
"YulBuiltin",
] = value
self._type: Optional["Type"] = None
@property
def type(self) -> Optional["Type"]:
return self._type
@type.setter
def type(self, new_type: "Type") -> None:
self._type = new_type
@property
def value(self) -> "Variable":
def value(
self,
) -> Union[
Variable,
"TopLevel",
"ContractLevel",
"Contract",
"SolidityVariable",
"SolidityFunction",
"YulBuiltin",
]:
return self._value
def __str__(self):
def __str__(self) -> str:
return str(self._value)

@ -1,20 +1,18 @@
from typing import List, TYPE_CHECKING
from typing import Union, List
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.literal import Literal
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
class IndexAccess(ExpressionTyped):
def __init__(self, left_expression, right_expression, index_type):
class IndexAccess(Expression):
def __init__(
self,
left_expression: Union["IndexAccess", Identifier],
right_expression: Union[Literal, Identifier],
) -> None:
super().__init__()
self._expressions = [left_expression, right_expression]
# TODO type of undexAccess is not always a Type
# assert isinstance(index_type, Type)
self._type: "Type" = index_type
@property
def expressions(self) -> List["Expression"]:
@ -28,9 +26,5 @@ class IndexAccess(ExpressionTyped):
def expression_right(self) -> "Expression":
return self._expressions[1]
@property
def type(self) -> "Type":
return self._type
def __str__(self):
def __str__(self) -> str:
return str(self.expression_left) + "[" + str(self.expression_right) + "]"

@ -1,4 +1,4 @@
from typing import Optional, Union, TYPE_CHECKING
from typing import Optional, Union, TYPE_CHECKING, Any
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.elementary_type import Fixed, Int, Ufixed, Uint
@ -10,16 +10,25 @@ if TYPE_CHECKING:
class Literal(Expression):
def __init__(self, value, custom_type, subdenomination=None):
def __init__(
self, value: Union[int, str], custom_type: "Type", subdenomination: Optional[str] = None
) -> None:
super().__init__()
self._value: Union[int, str] = value
self._value = value
self._type = custom_type
self._subdenomination: Optional[str] = subdenomination
self._subdenomination = subdenomination
@property
def value(self) -> Union[int, str]:
return self._value
@property
def converted_value(self) -> Union[int, str]:
"""Return the value of the literal, accounting for subdenomination e.g. ether"""
if self.subdenomination:
return convert_subdenomination(self._value, self.subdenomination)
return self._value
@property
def type(self) -> "Type":
return self._type
@ -28,9 +37,9 @@ class Literal(Expression):
def subdenomination(self) -> Optional[str]:
return self._subdenomination
def __str__(self):
def __str__(self) -> str:
if self.subdenomination:
return str(convert_subdenomination(self._value, self.subdenomination))
return str(self.converted_value)
if self.type in Int + Uint + Fixed + Ufixed + ["address"]:
return str(convert_string_to_int(self._value))
@ -38,7 +47,7 @@ class Literal(Expression):
# be sure to handle any character
return str(self._value)
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Literal):
return False
return (self.value, self.subdenomination) == (other.value, other.subdenomination)

@ -1,11 +1,10 @@
from slither.core.expressions.expression import Expression
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.solidity_types.type import Type
class MemberAccess(ExpressionTyped):
def __init__(self, member_name, member_type, expression):
class MemberAccess(Expression):
def __init__(self, member_name: str, member_type: str, expression: Expression) -> None:
# assert isinstance(member_type, Type)
# TODO member_type is not always a Type
assert isinstance(expression, Expression)
@ -26,5 +25,5 @@ class MemberAccess(ExpressionTyped):
def type(self) -> Type:
return self._type
def __str__(self):
def __str__(self) -> str:
return str(self.expression) + "." + self.member_name

@ -1,23 +1,23 @@
from typing import TYPE_CHECKING
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
if TYPE_CHECKING:
from slither.core.solidity_types.array_type import ArrayType
class NewArray(Expression):
# note: dont conserve the size of the array if provided
def __init__(self, depth, array_type):
class NewArray(Expression):
def __init__(self, array_type: "ArrayType") -> None:
super().__init__()
assert isinstance(array_type, Type)
self._depth: int = depth
self._array_type: Type = array_type
# pylint: disable=import-outside-toplevel
from slither.core.solidity_types.array_type import ArrayType
@property
def array_type(self) -> Type:
return self._array_type
assert isinstance(array_type, ArrayType)
self._array_type = array_type
@property
def depth(self) -> int:
return self._depth
def array_type(self) -> "ArrayType":
return self._array_type
def __str__(self):
return "new " + str(self._array_type) + "[]" * self._depth
return "new " + str(self._array_type)

@ -2,7 +2,7 @@ from slither.core.expressions.expression import Expression
class NewContract(Expression):
def __init__(self, contract_name):
def __init__(self, contract_name: str) -> None:
super().__init__()
self._contract_name: str = contract_name
self._gas = None
@ -29,5 +29,5 @@ class NewContract(Expression):
def call_salt(self, salt):
self._salt = salt
def __str__(self):
def __str__(self) -> str:
return "new " + str(self._contract_name)

@ -1,19 +1,43 @@
from slither.core.expressions.expression_typed import ExpressionTyped
from typing import Union, TYPE_CHECKING
from slither.core.expressions.expression import Expression
from slither.core.solidity_types.type import Type
if TYPE_CHECKING:
from slither.core.expressions.call_expression import CallExpression
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.literal import Literal
from slither.core.expressions.member_access import MemberAccess
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type_alias import TypeAliasContract
from slither.core.solidity_types.user_defined_type import UserDefinedType
class TypeConversion(ExpressionTyped):
def __init__(self, expression, expression_type):
class TypeConversion(Expression):
def __init__(
self,
expression: Union[
"MemberAccess", "Literal", "CallExpression", "TypeConversion", "Identifier"
],
expression_type: Union["ElementaryType", "UserDefinedType", "TypeAliasContract"],
) -> None:
super().__init__()
assert isinstance(expression, Expression)
assert isinstance(expression_type, Type)
self._expression: Expression = expression
self._type: Type = expression_type
@property
def type(self) -> Type:
return self._type
@type.setter
def type(self, new_type: Type) -> None:
self._type = new_type
@property
def expression(self) -> Expression:
return self._expression
def __str__(self):
def __str__(self) -> str:
return str(self.type) + "(" + str(self.expression) + ")"

@ -1,9 +1,14 @@
import logging
from typing import Union
from enum import Enum
from slither.core.expressions.expression_typed import ExpressionTyped
from slither.core.expressions.expression import Expression
from slither.core.exceptions import SlitherCoreError
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.index_access import IndexAccess
from slither.core.expressions.literal import Literal
from slither.core.expressions.tuple_expression import TupleExpression
logger = logging.getLogger("UnaryOperation")
@ -20,7 +25,7 @@ class UnaryOperationType(Enum):
MINUS_PRE = 8 # for stuff like uint(-1)
@staticmethod
def get_type(operation_type, isprefix):
def get_type(operation_type: str, isprefix: bool) -> "UnaryOperationType":
if isprefix:
if operation_type == "!":
return UnaryOperationType.BANG
@ -43,7 +48,7 @@ class UnaryOperationType(Enum):
return UnaryOperationType.MINUSMINUS_POST
raise SlitherCoreError(f"get_type: Unknown operation type {operation_type}")
def __str__(self):
def __str__(self) -> str:
if self == UnaryOperationType.BANG:
return "!"
if self == UnaryOperationType.TILD:
@ -65,7 +70,7 @@ class UnaryOperationType(Enum):
raise SlitherCoreError(f"str: Unknown operation type {self}")
@staticmethod
def is_prefix(operation_type):
def is_prefix(operation_type: "UnaryOperationType") -> bool:
if operation_type in [
UnaryOperationType.BANG,
UnaryOperationType.TILD,
@ -85,8 +90,12 @@ class UnaryOperationType(Enum):
raise SlitherCoreError(f"is_prefix: Unknown operation type {operation_type}")
class UnaryOperation(ExpressionTyped):
def __init__(self, expression, expression_type):
class UnaryOperation(Expression):
def __init__(
self,
expression: Union[Literal, Identifier, IndexAccess, TupleExpression],
expression_type: UnaryOperationType,
) -> None:
assert isinstance(expression, Expression)
super().__init__()
self._expression: Expression = expression
@ -114,7 +123,7 @@ class UnaryOperation(ExpressionTyped):
def is_prefix(self) -> bool:
return UnaryOperationType.is_prefix(self._type)
def __str__(self):
def __str__(self) -> str:
if self.is_prefix:
return str(self.type) + " " + str(self._expression)
return str(self._expression) + " " + str(self.type)

@ -1,10 +1,14 @@
from typing import List, Any, Dict, Optional, Union, Set
from typing import List, Any, Dict, Optional, Union, Set, TypeVar, Callable
from crytic_compile import CompilationUnit
from crytic_compile.source_unit import SourceUnit
from crytic_compile.utils.naming import Filename
from slither.core.declarations import Contract, Import, Pragma
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
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
@ -21,7 +25,7 @@ def _dict_contain(d1: Dict, d2: Dict) -> bool:
# pylint: disable=too-many-instance-attributes
class FileScope:
def __init__(self, filename: Filename):
def __init__(self, filename: Filename) -> None:
self.filename = filename
self.accessible_scopes: List[FileScope] = []
@ -35,6 +39,7 @@ class FileScope:
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
self.functions: Set[FunctionTopLevel] = set()
self.using_for_directives: Set[UsingForTopLevel] = set()
self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set()
self.structures: Dict[str, StructureTopLevel] = {}
@ -72,6 +77,9 @@ class FileScope:
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
if not new_scope.using_for_directives.issubset(self.using_for_directives):
self.using_for_directives |= new_scope.using_for_directives
learn_something = True
if not new_scope.imports.issubset(self.imports):
self.imports |= new_scope.imports
learn_something = True
@ -98,6 +106,117 @@ class FileScope:
return self.contracts.get(name.name, None)
return self.contracts.get(name, None)
AbstractReturnType = TypeVar("AbstractReturnType")
def _generic_source_unit_getter(
self,
crytic_compile_compilation_unit: CompilationUnit,
name: str,
getter: Callable[[SourceUnit], Dict[str, AbstractReturnType]],
) -> Optional[AbstractReturnType]:
assert self.filename in crytic_compile_compilation_unit.source_units
source_unit = crytic_compile_compilation_unit.source_unit(self.filename)
if name in getter(source_unit):
return getter(source_unit)[name]
for scope in self.accessible_scopes:
source_unit = crytic_compile_compilation_unit.source_unit(scope.filename)
if name in getter(source_unit):
return getter(source_unit)[name]
return None
def bytecode_init(
self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str
) -> Optional[str]:
"""
Return the init bytecode
Args:
crytic_compile_compilation_unit:
contract_name:
Returns:
"""
getter: Callable[[SourceUnit], Dict[str, str]] = lambda x: x.bytecodes_init
return self._generic_source_unit_getter(
crytic_compile_compilation_unit, contract_name, getter
)
def bytecode_runtime(
self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str
) -> Optional[str]:
"""
Return the runtime bytecode
Args:
crytic_compile_compilation_unit:
contract_name:
Returns:
"""
getter: Callable[[SourceUnit], Dict[str, str]] = lambda x: x.bytecodes_runtime
return self._generic_source_unit_getter(
crytic_compile_compilation_unit, contract_name, getter
)
def srcmap_init(
self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str
) -> Optional[List[str]]:
"""
Return the init scrmap
Args:
crytic_compile_compilation_unit:
contract_name:
Returns:
"""
getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.srcmaps_init
return self._generic_source_unit_getter(
crytic_compile_compilation_unit, contract_name, getter
)
def srcmap_runtime(
self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str
) -> Optional[List[str]]:
"""
Return the runtime srcmap
Args:
crytic_compile_compilation_unit:
contract_name:
Returns:
"""
getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.srcmaps_runtime
return self._generic_source_unit_getter(
crytic_compile_compilation_unit, contract_name, getter
)
def abi(self, crytic_compile_compilation_unit: CompilationUnit, contract_name: str) -> Any:
"""
Return the abi
Args:
crytic_compile_compilation_unit:
contract_name:
Returns:
"""
getter: Callable[[SourceUnit], Dict[str, List[str]]] = lambda x: x.abis
return self._generic_source_unit_getter(
crytic_compile_compilation_unit, contract_name, getter
)
# region Built in definitions
###################################################################################
###################################################################################

@ -8,12 +8,12 @@ import pathlib
import posixpath
import re
from collections import defaultdict
from typing import Optional, Dict, List, Set, Union
from typing import Optional, Dict, List, Set, Union, Tuple
from crytic_compile import CryticCompile
from crytic_compile.utils.naming import Filename
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.contract_level import ContractLevel
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.context.context import Context
from slither.core.declarations import Contract, FunctionContract
@ -40,7 +40,7 @@ class SlitherCore(Context):
Slither static analyzer
"""
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._filename: Optional[str] = None
@ -71,6 +71,12 @@ class SlitherCore(Context):
self._show_ignored_findings = False
# Maps from file to detector name to the start/end ranges for that detector.
# Infinity is used to signal a detector has no end range.
self._ignore_ranges: Dict[str, Dict[str, List[Tuple[int, ...]]]] = defaultdict(
lambda: defaultdict(lambda: [(-1, -1)])
)
self._compilation_units: List[SlitherCompilationUnit] = []
self._contracts: List[Contract] = []
@ -86,6 +92,10 @@ class SlitherCore(Context):
# But we allow to alter this (ex: file.sol:1) for vscode integration
self.line_prefix: str = "#"
# Use by the echidna printer
# If true, partial analysis is allowed
self.no_fail = False
@property
def compilation_units(self) -> List[SlitherCompilationUnit]:
return list(self._compilation_units)
@ -151,7 +161,7 @@ class SlitherCore(Context):
def filename(self, filename: str):
self._filename = filename
def add_source_code(self, path):
def add_source_code(self, path: str) -> None:
"""
:param path:
:return:
@ -162,6 +172,8 @@ class SlitherCore(Context):
with open(path, encoding="utf8", newline="") as f:
self.source_code[path] = f.read()
self.parse_ignore_comments(path)
@property
def markdown_root(self) -> str:
return self._markdown_root
@ -194,7 +206,7 @@ class SlitherCore(Context):
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (isinstance(thing, ChildContract) and not isinstance(thing, FunctionContract))
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
):
self._offset_to_objects[definition.filename][offset].add(thing)
@ -212,7 +224,7 @@ class SlitherCore(Context):
and thing.contract_declarer == thing.contract
)
or (
isinstance(thing, ChildContract) and not isinstance(thing, FunctionContract)
isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract)
)
):
self._offset_to_objects[definition.filename][offset].add(thing)
@ -284,9 +296,52 @@ class SlitherCore(Context):
###################################################################################
###################################################################################
def parse_ignore_comments(self, file: str) -> None:
# The first time we check a file, find all start/end ignore comments and memoize them.
line_number = 1
while True:
line_text = self.crytic_compile.get_code_from_line(file, line_number)
if line_text is None:
break
start_regex = r"^\s*//\s*slither-disable-start\s*([a-zA-Z0-9_,-]*)"
end_regex = r"^\s*//\s*slither-disable-end\s*([a-zA-Z0-9_,-]*)"
start_match = re.findall(start_regex, line_text.decode("utf8"))
end_match = re.findall(end_regex, line_text.decode("utf8"))
if start_match:
ignored = start_match[0].split(",")
if ignored:
for check in ignored:
vals = self._ignore_ranges[file][check]
if len(vals) == 0 or vals[-1][1] != float("inf"):
# First item in the array, or the prior item is fully populated.
self._ignore_ranges[file][check].append((line_number, float("inf")))
else:
logger.error(
f"Consecutive slither-disable-starts without slither-disable-end in {file}#{line_number}"
)
return
if end_match:
ignored = end_match[0].split(",")
if ignored:
for check in ignored:
vals = self._ignore_ranges[file][check]
if len(vals) == 0 or vals[-1][1] != float("inf"):
logger.error(
f"slither-disable-end without slither-disable-start in {file}#{line_number}"
)
return
self._ignore_ranges[file][check][-1] = (vals[-1][0], line_number)
line_number += 1
def has_ignore_comment(self, r: Dict) -> bool:
"""
Check if the result has an ignore comment on the proceeding line, in which case, it is not valid
Check if the result has an ignore comment in the file or on the preceding line, in which
case, it is not valid
"""
if not self.crytic_compile:
return False
@ -303,6 +358,15 @@ class SlitherCore(Context):
)
for file, lines in mapping_elements_with_lines:
# Check if result is within an ignored range.
ignore_ranges = self._ignore_ranges[file][r["check"]] + self._ignore_ranges[file]["all"]
for start, end in ignore_ranges:
# The full check must be within the ignore range to be ignored.
if start < lines[0] and end > lines[-1]:
return True
# Check for next-line matchers.
ignore_line_index = min(lines) - 1
ignore_line_text = self.crytic_compile.get_code_from_line(file, ignore_line_index)
if ignore_line_text:
@ -324,7 +388,7 @@ class SlitherCore(Context):
- All its source paths belong to the source path filtered
- Or a similar result was reported and saved during a previous run
- The --exclude-dependencies flag is set and results are only related to dependencies
- There is an ignore comment on the preceding line
- There is an ignore comment on the preceding line or in the file
"""
# Remove duplicate due to the multiple compilation support
@ -379,7 +443,7 @@ class SlitherCore(Context):
return True
def load_previous_results(self):
def load_previous_results(self) -> None:
filename = self._previous_results_filename
try:
if os.path.isfile(filename):
@ -392,7 +456,7 @@ class SlitherCore(Context):
except json.decoder.JSONDecodeError:
logger.error(red(f"Impossible to decode {filename}. Consider removing the file"))
def write_results_to_hide(self):
def write_results_to_hide(self) -> None:
if not self._results_to_hide:
return
filename = self._previous_results_filename
@ -400,7 +464,7 @@ class SlitherCore(Context):
results = self._results_to_hide + self._previous_results
json.dump(results, f)
def save_results_to_hide(self, results: List[Dict]):
def save_results_to_hide(self, results: List[Dict]) -> None:
self._results_to_hide += results
def add_path_to_filter(self, path: str):
@ -418,8 +482,8 @@ class SlitherCore(Context):
###################################################################################
@property
def crytic_compile(self) -> Optional[CryticCompile]:
return self._crytic_compile
def crytic_compile(self) -> CryticCompile:
return self._crytic_compile # type: ignore
# endregion
###################################################################################

@ -1,27 +1,37 @@
from typing import Optional, Tuple
from typing import Union, Optional, Tuple, Any, TYPE_CHECKING
from slither.core.expressions import Literal
from slither.core.expressions.expression import Expression
from slither.core.expressions.literal import Literal
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type import Type
from slither.visitors.expression.constants_folding import ConstantFolding
if TYPE_CHECKING:
from slither.core.expressions.binary_operation import BinaryOperation
from slither.core.expressions.identifier import Identifier
class ArrayType(Type):
def __init__(self, t, length):
def __init__(
self,
t: Type,
length: Optional[Union["Identifier", Literal, "BinaryOperation", int]],
) -> None:
assert isinstance(t, Type)
if length:
if isinstance(length, int):
length = Literal(length, "uint256")
assert isinstance(length, Expression)
length = Literal(length, ElementaryType("uint256"))
super().__init__()
self._type: Type = t
assert length is None or isinstance(length, Expression)
self._length: Optional[Expression] = length
if length:
if not isinstance(length, Literal):
cf = ConstantFolding(length, "uint256")
length = cf.result()
self._length_value = length
self._length_value: Optional[Literal] = length
else:
self._length_value = None
@ -56,15 +66,15 @@ class ArrayType(Type):
return elem_size * int(str(self._length_value)), True
return 32, True
def __str__(self):
def __str__(self) -> str:
if self._length:
return str(self._type) + f"[{str(self._length_value)}]"
return str(self._type) + "[]"
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, ArrayType):
return False
return self._type == other.type and self.length == other.length
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

@ -1,5 +1,5 @@
import itertools
from typing import Tuple
from typing import Tuple, Optional, Any
from slither.core.solidity_types.type import Type
@ -176,7 +176,7 @@ class ElementaryType(Type):
return self.type
@property
def size(self) -> int:
def size(self) -> Optional[int]:
"""
Return the size in bits
Return None if the size is not known
@ -216,13 +216,13 @@ class ElementaryType(Type):
return MaxValues[self.name]
raise SlitherException(f"{self.name} does not have a max value")
def __str__(self):
def __str__(self) -> str:
return self._type
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, ElementaryType):
return False
return self.type == other.type
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

@ -1,4 +1,4 @@
from typing import List, Tuple
from typing import List, Tuple, Any
from slither.core.solidity_types.type import Type
from slither.core.variables.function_type_variable import FunctionTypeVariable
@ -9,7 +9,7 @@ class FunctionType(Type):
self,
params: List[FunctionTypeVariable],
return_values: List[FunctionTypeVariable],
):
) -> None:
assert all(isinstance(x, FunctionTypeVariable) for x in params)
assert all(isinstance(x, FunctionTypeVariable) for x in return_values)
super().__init__()
@ -68,7 +68,7 @@ class FunctionType(Type):
return f"({params}) returns({return_values})"
return f"({params})"
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, FunctionType):
return False
return self.params == other.params and self.return_values == other.return_values

@ -1,10 +1,18 @@
from typing import Tuple
from typing import Union, Tuple, TYPE_CHECKING, Any
from slither.core.solidity_types.type import Type
if TYPE_CHECKING:
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type_alias import TypeAliasTopLevel
class MappingType(Type):
def __init__(self, type_from, type_to):
def __init__(
self,
type_from: "ElementaryType",
type_to: Union["MappingType", "TypeAliasTopLevel", "ElementaryType"],
) -> None:
assert isinstance(type_from, Type)
assert isinstance(type_to, Type)
super().__init__()
@ -27,13 +35,13 @@ class MappingType(Type):
def is_dynamic(self) -> bool:
return True
def __str__(self):
def __str__(self) -> str:
return f"mapping({str(self._from)} => {str(self._to)})"
def __eq__(self, other):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, MappingType):
return False
return self.type_from == other.type_from and self.type_to == other.type_to
def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

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

Loading…
Cancel
Save