Merge branch 'dev' into docs/update-slither

pull/2498/head
Jean Cvllr 1 month ago committed by GitHub
commit 2bf85163dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 25
      .coderabbit.yaml
  2. 38
      .github/DISCUSSION_TEMPLATE/installation.yml
  3. 12
      .github/ISSUE_TEMPLATE/bug_report.yml
  4. 61
      .github/ISSUE_TEMPLATE/false_negative.yml
  5. 61
      .github/ISSUE_TEMPLATE/false_positive.yml
  6. 31
      .github/actions/upload-coverage/action.yml
  7. 8
      .github/dependabot.yml
  8. 13
      .github/scripts/integration_test_runner.sh
  9. 13
      .github/scripts/tool_test_runner.sh
  10. 13
      .github/scripts/unit_test_runner.sh
  11. 48
      .github/workflows/IR.yml
  12. 26
      .github/workflows/black.yml
  13. 43
      .github/workflows/black_auto.yml
  14. 64
      .github/workflows/ci.yml
  15. 48
      .github/workflows/detectors.yml
  16. 60
      .github/workflows/docker.yml
  17. 46
      .github/workflows/docs.yml
  18. 91
      .github/workflows/doctor.yml
  19. 55
      .github/workflows/features.yml
  20. 40
      .github/workflows/issue-metrics.yml
  21. 32
      .github/workflows/linter.yml
  22. 32
      .github/workflows/matchers/pylint.json
  23. 22
      .github/workflows/matchers/yamllint.json
  24. 51
      .github/workflows/parser.yml
  25. 29
      .github/workflows/pip-audit.yml
  26. 53
      .github/workflows/publish.yml
  27. 39
      .github/workflows/pylint.yml
  28. 53
      .github/workflows/read_storage.yml
  29. 123
      .github/workflows/test.yml
  30. 11
      .gitignore
  31. 9
      .pre-commit-hooks.yaml
  32. 64
      CITATION.cff
  33. 5
      CODEOWNERS
  34. 99
      CONTRIBUTING.md
  35. 47
      Dockerfile
  36. 12
      FUNDING.json
  37. 88
      Makefile
  38. 412
      README.md
  39. 10
      examples/flat/a.sol
  40. 13
      examples/flat/b.sol
  41. 2
      examples/printers/human_printer.sol
  42. 20
      examples/scripts/data_dependency.py
  43. 9
      examples/scripts/data_dependency.sol
  44. 3
      examples/scripts/possible_paths.py
  45. 3
      examples/scripts/taint_mapping.py
  46. 1
      examples/scripts/variable_in_condition.py
  47. 6
      plugin_example/setup.py
  48. 9
      plugin_example/slither_my_plugin/__init__.py
  49. 0
      plugin_example/slither_my_plugin/detectors/__init__.py
  50. 10
      plugin_example/slither_my_plugin/detectors/example.py
  51. 4
      pyproject.toml
  52. 86
      scripts/ci_test.sh
  53. 6
      scripts/ci_test_cli.sh
  54. 17
      scripts/ci_test_dapp.sh
  55. 11
      scripts/ci_test_embark.sh
  56. 2
      scripts/ci_test_erc.sh
  57. 10
      scripts/ci_test_etherlime.sh
  58. 22
      scripts/ci_test_etherscan.sh
  59. 2
      scripts/ci_test_find_paths.sh
  60. 10
      scripts/ci_test_flat.sh
  61. 95
      scripts/ci_test_interface.sh
  62. 2
      scripts/ci_test_kspec.sh
  63. 15
      scripts/ci_test_path_filtering.sh
  64. 17
      scripts/ci_test_printers.sh
  65. 4
      scripts/ci_test_simil.sh
  66. 10
      scripts/ci_test_truffle.sh
  67. 59
      scripts/ci_test_upgradability.sh
  68. 27
      scripts/json_diff.py
  69. 28
      scripts/update_buggy_versions.py
  70. 47
      setup.py
  71. 3
      slither/__init__.py
  72. 450
      slither/__main__.py
  73. 147
      slither/analyses/data_dependency/data_dependency.py
  74. 14
      slither/analyses/evm/convert.py
  75. 29
      slither/analyses/write/are_variables_written.py
  76. 343
      slither/core/cfg/node.py
  77. 6
      slither/core/cfg/scope.py
  78. 17
      slither/core/children/child_contract.py
  79. 17
      slither/core/children/child_event.py
  80. 17
      slither/core/children/child_expression.py
  81. 17
      slither/core/children/child_function.py
  82. 17
      slither/core/children/child_inheritance.py
  83. 31
      slither/core/children/child_node.py
  84. 17
      slither/core/children/child_structure.py
  85. 90
      slither/core/compilation_unit.py
  86. 9
      slither/core/declarations/__init__.py
  87. 441
      slither/core/declarations/contract.py
  88. 29
      slither/core/declarations/contract_level.py
  89. 38
      slither/core/declarations/custom_error.py
  90. 16
      slither/core/declarations/custom_error_contract.py
  91. 6
      slither/core/declarations/custom_error_top_level.py
  92. 15
      slither/core/declarations/enum.py
  93. 4
      slither/core/declarations/enum_contract.py
  94. 4
      slither/core/declarations/enum_top_level.py
  95. 28
      slither/core/declarations/event.py
  96. 25
      slither/core/declarations/event_contract.py
  97. 13
      slither/core/declarations/event_top_level.py
  98. 347
      slither/core/declarations/function.py
  99. 44
      slither/core/declarations/function_contract.py
  100. 24
      slither/core/declarations/function_top_level.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,25 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "en"
early_access: false
knowledge_base:
learnings:
scope: auto
issues:
scope: global
reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
poem: false
review_status: true
collapse_walkthrough: true
auto_review:
enabled: true
ignore_title_keywords:
- "WIP"
- "DO NOT MERGE"
drafts: false
base_branches:
- dev
chat:
auto_reply: true

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

@ -3,7 +3,13 @@ body:
-
attributes:
value: |
Please check the issues tab to avoid duplicates.
Please check the issues tab to avoid duplicates, and
confirm that the bug exists on the latest release (upgrade
by running `python3 -m pip install --upgrade slither-analyzer`).
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
-
@ -15,7 +21,7 @@ body:
required: true
-
attributes:
description: "It can be a github repo, etherscan link, or code snippet."
description: "It can be a github repo (preferred), etherscan link, or code snippet."
label: "Code example to reproduce the issue:"
placeholder: "`contract A {}`\n"
id: reproduce
@ -25,7 +31,7 @@ body:
-
attributes:
description: |
What version of slither are you running?
What version of slither are you running?
Run `slither --version`
label: "Version:"
id: version

@ -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,31 @@
# 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@v4
with:
name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }}
path: |
.coverage.*
*.lcov
if-no-files-found: ignore
include-hidden-files: true

@ -0,0 +1,8 @@
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
target-branch: "dev"
schedule:
interval: "weekly"

@ -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
status_code=$?
python -m coverage report
else
pytest tests/tools
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,48 +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.6
uses: actions/setup-python@v3
with:
python-version: 3.6
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
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,34 +9,40 @@ 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@v4
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.6
uses: actions/setup-python@v3
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Black
uses: 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

@ -0,0 +1,43 @@
---
name: Run black (auto)
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Black
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Run black
uses: psf/black@stable
with:
options: ""
summary: false
version: "~= 22.3.0"
- name: Annotate diff changes using reviewdog
uses: reviewdog/action-suggester@v1
with:
tool_name: blackfmt

@ -3,8 +3,7 @@ name: CI
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
shell: bash
on:
push:
@ -16,77 +15,52 @@ 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 }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["cli",
"dapp",
"data_dependency",
# "embark",
"erc",
"etherlime",
# "etherscan"
"find_paths",
"flat",
"kspec",
"printers",
# "prop"
"simil",
"slither_config",
"truffle",
"upgradability"]
os: ${{ (github.event_name == 'pull_request' && fromJSON('["ubuntu-latest"]')) || fromJSON('["ubuntu-latest", "windows-2022"]') }}
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
type: ${{ (github.event_name == 'pull_request' && fromJSON('["data_dependency", "path_filtering","erc","find_paths","flat","interface", "printers","slither_config","upgradability"]')) || fromJSON('["data_dependency", "path_filtering","erc","find_paths","flat","interface", "printers","slither_config","upgradability", "cli", "dapp", "etherscan", "kspec", "simil", "truffle"]') }}
exclude:
# Requires nix
- os: windows-2022
type: dapp
# Requires nvm
- os: windows-2022
type: etherlime
# Requires nvm
- os: windows-2022
type: truffle
steps:
- uses: actions/checkout@v1
- name: Set up shell
if: runner.os == 'Windows'
run: |
echo 'C:\msys64\mingw64\bin' >> "$GITHUB_PATH"
echo 'C:\msys64\usr\bin' >> "$GITHUB_PATH"
- name: Set up Python 3.6
uses: actions/setup-python@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: 3.6
python-version: ${{ matrix.python }}
- name: Install dependencies
run: |
python setup.py install
# Used by ci_test.sh
pip install deepdiff
pip install "solc-select>=v1.0.0b1"
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@v30
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v10
uses: cachix/cachix-action@v15
with:
name: dapp
- name: Run Tests
env:
PYTHONUTF8: 1
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: |

@ -1,48 +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.6
uses: actions/setup-python@v3
with:
python-version: 3.6
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
solc-select install all
solc-select use 0.7.3
- name: Test with pytest
run: |
pytest tests/test_detectors.py

@ -0,0 +1,60 @@
name: Docker
on:
push:
branches:
- master
- dev
tags:
- '*'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
id: buildx
with:
install: true
- name: Set Docker metadata
id: metadata
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}
tags: |
type=ref,event=tag
type=ref,event=branch,prefix=testing-
type=edge
- name: GitHub Container Registry Login
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Build and Push
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7
target: final
file: Dockerfile
pull: true
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

@ -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@v4
- name: Setup Pages
uses: actions/configure-pages@v5
- uses: actions/setup-python@v5
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@v3
with:
# Upload the doc
path: './html/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

@ -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", "3.12"]
exclude:
# strange failure
- os: windows-2022
python: 3.8
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
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,55 +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.6
uses: actions/setup-python@v3
with:
python-version: 3.6
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
solc-select install all
solc-select use 0.8.0
cd tests/test_node_modules/
npm install hardhat
cd ../..
- name: Test with pytest
run: |
pytest tests/test_features.py
pytest tests/test_constant_folding_unary.py
pytest tests/slithir/test_ternary_expressions.py

@ -0,0 +1,40 @@
name: Monthly issue metrics
on:
workflow_dispatch:
schedule:
- cron: '3 2 1 * *'
permissions:
issues: write
pull-requests: read
jobs:
build:
name: issue metrics
runs-on: ubuntu-latest
steps:
- name: Get dates for last month
shell: bash
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-01)
# Calculate the last day of the previous month
last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d)
#Set an environment variable with the date range
echo "$first_day..$last_day"
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@v3
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:crytic/slither is:issue created:${{ env.last_month }} -reason:"not planned" -reason:"duplicate"'
- name: Create issue
uses: peter-evans/create-issue-from-file@v5
with:
title: Monthly issue metrics report
token: ${{ secrets.GITHUB_TOKEN }}
content-filepath: ./issue_metrics.md

@ -9,34 +9,43 @@ 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:
name: Lint Code Base
name: Superlinter
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v4
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.6
uses: actions/setup-python@v3
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Register yamllint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/yamllint.json"
- name: Lint everything else
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v6.1.1
if: always()
env:
# run linter on everything to catch preexisting problems
@ -48,8 +57,8 @@ jobs:
VALIDATE_PYTHON_PYLINT: false
VALIDATE_PYTHON_BLACK: false
VALIDATE_PYTHON_ISORT: false
# Always false
VALIDATE_JSON: false
VALIDATE_JAVASCRIPT_ES: false
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_PYTHON_FLAKE8: false
VALIDATE_DOCKERFILE: false
@ -57,5 +66,8 @@ jobs:
VALIDATE_EDITORCONFIG: false
VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false
VALIDATE_CHECKOV: false
# TODO: consider enabling
VALIDATE_SHELL_SHFMT: false
SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -0,0 +1,32 @@
{
"problemMatcher": [
{
"owner": "pylint-error",
"severity": "error",
"pattern": [
{
"regexp": "^(.+):(\\d+):(\\d+):\\s(([EF]\\d{4}):\\s.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"code": 5
}
]
},
{
"owner": "pylint-warning",
"severity": "warning",
"pattern": [
{
"regexp": "^(.+):(\\d+):(\\d+):\\s(([CRW]\\d{4}):\\s.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"code": 5
}
]
}
]
}

@ -0,0 +1,22 @@
{
"problemMatcher": [
{
"owner": "yamllint",
"pattern": [
{
"regexp": "^(.*\\.ya?ml)$",
"file": 1
},
{
"regexp": "^\\s{2}(\\d+):(\\d+)\\s+(error|warning)\\s+(.*?)\\s+\\((.*)\\)$",
"line": 1,
"column": 2,
"severity": 3,
"message": 4,
"code": 5,
"loop": true
}
]
}
]
}

@ -1,51 +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.6
uses: actions/setup-python@v3
with:
python-version: 3.6
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
- 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,21 +8,32 @@ 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
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python 3.10
uses: actions/setup-python@v3
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install pip-audit
run: |
python -m pip install --upgrade pip
python -m pip install pip-audit
- name: Run pip-audit
- name: Install Slither
run: |
python -m venv /tmp/pip-audit-env
source /tmp/pip-audit-env/bin/activate
python -m pip install --upgrade pip setuptools wheel
python -m pip install .
pip-audit --desc -v
- name: Run pip-audit
uses: pypa/gh-action-pip-audit@v1.1.0
with:
virtual-environment: /tmp/pip-audit-env

@ -0,0 +1,53 @@
name: Publish to PyPI
on:
release:
types: [published]
jobs:
build-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Build distributions
run: |
python -m pip install --upgrade pip
python -m pip install build
python -m build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: slither-dists
path: dist/
publish:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write # For trusted publishing + codesigning.
contents: write # For attaching signing artifacts to the release.
needs:
- build-release
steps:
- name: fetch dists
uses: actions/download-artifact@v4
with:
name: slither-dists
path: dist/
- name: publish
uses: pypa/gh-action-pypi-publish@v1.10.3
- name: sign
uses: sigstore/gh-action-sigstore-python@v3.0.0
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true

@ -1,5 +1,5 @@
---
name: Lint Code Base
name: Run pylint
defaults:
run:
@ -9,9 +9,12 @@ defaults:
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
paths:
- "**/*.py"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
@ -20,28 +23,34 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v4
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.6
uses: actions/setup-python@v3
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Register pylint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/pylint.json"
- name: Pylint
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v6.1.1
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,53 +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.6
uses: actions/setup-python@v2
with:
python-version: 3.6
- name: Install python dependencies
run: |
python3 setup.py install
pip install web3 pytest deepdiff solc-select
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
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,123 @@
---
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"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: "pip"
cache-dependency-path: setup.py
- name: Install dependencies
run: |
pip install ".[test]"
- name: Setup node
uses: actions/setup-node@v4
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: Install Vyper
run: |
INSTALLDIR="$RUNNER_TEMP/vyper-install"
if [[ "$RUNNER_OS" = "Windows" ]]; then
URL="https://github.com/vyperlang/vyper/releases/download/v0.3.7/vyper.0.3.7+commit.6020b8bb.windows.exe"
FILENAME="vyper.exe"
elif [[ "$RUNNER_OS" = "Linux" ]]; then
URL="https://github.com/vyperlang/vyper/releases/download/v0.3.7/vyper.0.3.7+commit.6020b8bb.linux"
FILENAME="vyper"
else
echo "Unknown OS"
exit 1
fi
mkdir -p "$INSTALLDIR"
curl "$URL" -o "$INSTALLDIR/$FILENAME" -L
chmod 755 "$INSTALLDIR/$FILENAME"
echo "$INSTALLDIR" >> "$GITHUB_PATH"
- 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' && matrix.python == '3.8' }}
coverage:
needs:
- tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: 3.8
- run: pip install coverage[toml]
- name: download coverage data
uses: actions/download-artifact@v4
with:
pattern: coverage-data-*
merge-multiple: true
- 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"

11
.gitignore vendored

@ -42,11 +42,13 @@ htmlcov/
.coverage
.coverage.*
.cache
.pytest_cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.vscode/
storage_layout.json
# Translations
*.mo
*.pot
@ -107,3 +109,12 @@ ENV/
# Test results
test_artifacts/
# crytic export
crytic-export/
# Auto-generated Github pages docs
docs/
# slither.db.json
slither.db.json

@ -0,0 +1,9 @@
- id: slither
name: Slither
description: Run Slither on your project
entry: slither
args:
- .
pass_filenames: false
language: python
files: \.sol$

@ -0,0 +1,64 @@
cff-version: 1.2.0
title: Slither Analyzer
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Josselin
family-names: Feist
- given-names: Gustavo
family-names: Grieco
- given-names: Alex
family-names: Groce
identifiers:
- type: doi
value: 10.48550/arXiv.1908.09878
description: arXiv.1908.09878
- type: url
value: 'https://arxiv.org/abs/1908.09878'
description: arxiv
- type: doi
value: 10.1109/wetseb.2019.00008
repository-code: 'https://github.com/crytic/slither'
url: 'https://www.trailofbits.com/'
repository-artifact: 'https://github.com/crytic/slither/releases'
abstract: >-
Slither is a static analysis framework designed to provide
rich information about Ethereum smart contracts.
It works by converting Solidity smart contracts into an
intermediate representation called SlithIR.
SlithIR uses Static Single Assignment (SSA) form and a
reduced instruction set to ease implementation of analyses
while preserving semantic information that would be lost
in transforming Solidity to bytecode.
Slither allows for the application of commonly used
program analysis techniques like dataflow and taint
tracking.
Our framework has four main use cases:
(1) automated detection of vulnerabilities,
(2) automated detection of code optimization
opportunities,
(3) improvement of the user's understanding of the
contracts, and
(4) assistance with code review.
keywords:
- Ethereum
- Static Analysis
- Smart contracts
- EVM
- bug detection
- Software Engineering
license: AGPL-3.0-only
commit: 3d4f934d3228f072b7df2c5e7252c64df4601bc8
version: 0.9.5
date-released: '2023-06-28'

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

@ -1,16 +1,20 @@
# 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.
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/).
Slither uses the pull request contribution model. Please make an account on GitHub, fork this repository, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/).
Some pull request guidelines:
@ -20,13 +24,30 @@ Some pull request guidelines:
- Fill out the pull request description with a summary of what your patch does, key changes that have been made, and any further points of discussion, if applicable.
- Title your pull request with a brief description of what it's changing. "Fixes #123" is a good comment to add to the description, but makes for an unclear title on its own.
## Directory Structure
Below is a rough outline of slither's design:
```text
.
├── analyses # Provides additional info such as data dependency
├── core # Ties everything together
├── detectors # Rules that define and identify issues
├── slither.py # Main entry point
├── slithir # Contains the semantics of slither's intermediate representation
├── solc_parsing # Responsible for parsing the solc AST
├── tools # Miscellaneous tools built on top of slither
├── visitors # Parses expressions and converts to slithir
└── ...
```
A code walkthrough is available [here](https://www.youtube.com/watch?v=EUl3UlYSluU).
## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).
To run the unit tests, you need
- `deepdiff` installed (`pip install deepdiff`).
- `pycov` installed (`pip install pytest-cov`).
- [`solc-select`](https://github.com/crytic/solc-select) installed.
To run the unit tests, you need to clone this repository and run `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
@ -34,32 +55,64 @@ 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`
We use pylint `2.13.4`, black `22.3.0`.
> Note, this only validates but does not modify the code.
### Detectors tests
To automatically reformat the code:
For each new detector, at least one regression tests must be present.
- `make reformat`
We use pylint `3.0.3`, black `22.3.0`.
### Testing
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.
- 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/compile`. Add the compiled artifact to git.
- Run `python ./tests/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/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_TESTS` 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. Update `ALL_TESTS` in `tests/e2e/solc_parsing/test_ast_parsing.py`.
3. 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.
4. 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.
5. 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

@ -1,4 +1,21 @@
FROM ubuntu:bionic
# syntax=docker/dockerfile:1.3
FROM ubuntu:jammy AS python-wheels
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gcc \
git \
make \
python3-dev \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
COPY . /slither
RUN cd /slither && \
pip3 install --no-cache-dir --upgrade pip && \
pip3 wheel -w /wheels . solc-select pip setuptools wheel
FROM ubuntu:jammy AS final
LABEL name=slither
LABEL src="https://github.com/trailofbits/slither"
@ -6,23 +23,31 @@ LABEL creator=trailofbits
LABEL dockerfile_maintenance=trailofbits
LABEL desc="Static Analyzer for Solidity"
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y git python3 python3-setuptools wget software-properties-common
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get install -y --no-install-recommends python3-pip \
&& rm -rf /var/lib/apt/lists/*
RUN wget https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux \
&& chmod +x solc-static-linux \
&& mv solc-static-linux /usr/bin/solc
# improve compatibility with amd64 solc in non-amd64 environments (e.g. Docker Desktop on M1 Mac)
ENV QEMU_LD_PREFIX=/usr/x86_64-linux-gnu
RUN if [ ! "$(uname -m)" = "x86_64" ]; then \
export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get install -y --no-install-recommends libc6-amd64-cross \
&& rm -rf /var/lib/apt/lists/*; fi
RUN useradd -m slither
USER slither
# If this fails, the solc-static-linux binary has changed while it should not.
RUN [ "c9b268750506b88fe71371100050e9dd1e7edcf8f69da34d1cd09557ecb24580 /usr/bin/solc" = "$(sha256sum /usr/bin/solc)" ]
COPY --chown=slither:slither . /home/slither/slither
WORKDIR /home/slither/slither
RUN python3 setup.py install --user
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 --no-deps /mnt/*.whl
RUN solc-select use latest --always-install
CMD /bin/bash

@ -0,0 +1,12 @@
{
"Optimism": {
"op-mainnet": {
"ownedBy": "0xc44F30Be3eBBEfdDBB5a85168710b4f0e18f4Ff0"
}
},
"drips": {
"ethereum": {
"ownedBy": "0xc44F30Be3eBBEfdDBB5a85168710b4f0e18f4Ff0"
}
}
}

@ -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,176 +1,79 @@
# 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)
[![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.
- [Features](#features)
- [Bugs and Optimizations Detection](#bugs-and-optimizations-detection)
- [Printers](#printers)
- [Tools](#tools)
- [How to Install](#how-to-install)
- [Getting Help](#getting-help)
- [Publications](#publications)
# [Slither, the smart contract static analyzer](https://crytic.github.io/slither/slither.html)
<img src="https://raw.githubusercontent.com/crytic/slither/master/logo.png" alt="Slither Static Analysis Framework Logo" width="500" />
[![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)
![PyPI](https://img.shields.io/pypi/v/slither-analyzer?logo=python&logoColor=white&label=slither-analyzer)
[![Slither - Read the Docs](https://img.shields.io/badge/Slither-Read_the_Docs-2ea44f)](https://crytic.github.io/slither/slither.html)
[![Slither - Wiki](https://img.shields.io/badge/Slither-Wiki-2ea44f)](https://github.com/crytic/slither/wiki/SlithIR)
> Join the Empire Hacking Slack
>
> [![Slack Status](https://slack.empirehacking.nyc/badge.svg)](https://slack.empirehacking.nyc/)
> > <sub><i>- Discussions and Support </i></sub>
**Slither** is a Solidity & Vyper 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)
* [Usage](#usage)
* [How to install](#how-to-install)
* [Using Pip](#using-pip)
* [Using Git](#using-git)
* [Using Docker](#using-docker)
* [Integration](#integration)
* [Detectors](#detectors)
* [Printers](#printers)
* [Quick Review Printers](#quick-review-printers)
* [In-Depth Review Printers](#in-depth-review-printers)
* [Tools](#tools)
* [API Documentation](#api-documentation)
* [Getting Help](#getting-help)
* [FAQ](#faq)
* [License](#license)
* [Publications](#publications)
* [Trail of Bits publication](#trail-of-bits-publication)
* [External publications](#external-publications)
## Features
* 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)
* Support for Vyper smart contracts
## Usage
## Bugs and Optimizations Detection
Run Slither on a Hardhat/Foundry/Dapp/Brownie application:
Run Slither on a Truffle/Embark/Dapp/Etherlime/Hardhat application:
```bash
```console
slither .
```
Run Slither on a single file:
```bash
slither tests/uninitialized.sol
```
### 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
Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | ---
1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High
2 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#shift-parameter-mixup) | High | High
4 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
5 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
6 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
7 | `rtlo` | [Right-To-Left-Override control character is used](https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character) | High | High
8 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
9 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
10 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
11 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
12 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High
13 | `arbitrary-send` | [Functions that send Ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
14 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
15 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
16 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
17 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
18 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
19 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
20 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
21 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
22 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
23 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
24 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
25 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
26 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
27 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
28 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
29 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
30 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
31 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
32 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
33 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
34 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
35 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
36 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
37 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
38 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
39 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
40 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
41 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
42 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
43 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
44 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
45 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
46 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
47 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
48 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
49 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
50 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
51 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
52 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
53 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
54 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
55 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
56 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
57 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
58 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
59 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
60 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
61 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
62 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
63 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
64 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
65 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
66 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
67 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
68 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
69 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
70 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-are-too-similar) | Informational | Medium
74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
75 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
76 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
- The [Detection Selection](https://github.com/crytic/slither/wiki/Usage#detector-selection) to run only selected detectors. By default, all the detectors are run.
- The [Triage Mode](https://github.com/crytic/slither/wiki/Usage#triage-mode) to filter individual results
## Printers
### Quick Review Printers
- `human-summary`: [Print a human-readable summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary)
- `inheritance-graph`: [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph)
- `contract-summary`: [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary)
### In-Depth Review Printers
- `call-graph`: [Export the call-graph of the contracts to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph)
- `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
- `function-summary`: [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary)
- `vars-and-auth`: [Print the state variables written and the authorization of the functions](https://github.com/crytic/slither/wiki/Printer-documentation#variables-written-and-authorization)
To run a printer, use `--print` and a comma-separated list of printers.
See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-documentation) for the complete lists.
## Tools
- `slither-check-upgradeability`: [Review `delegatecall`-based upgradeability](https://github.com/crytic/slither/wiki/Upgradeability-Checks)
- `slither-prop`: [Automatic unit test and property generation](https://github.com/crytic/slither/wiki/Property-generation)
- `slither-flat`: [Flatten a codebase](https://github.com/crytic/slither/wiki/Contract-Flattening)
- `slither-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance)
- `slither-format`: [Automatic patch generation](https://github.com/crytic/slither/wiki/Slither-format)
This is the preferred option if your project has dependencies as Slither relies on the underlying compilation framework to compile source code.
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
However, you can run Slither on a single file that does not import dependencies:
[Contact us](https://www.trailofbits.com/contact/) to get help on building custom tools.
```console
slither tests/uninitialized.sol
```
## How to install
Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
> **Note** <br />
> 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
```console
python3 -m pip install slither-analyzer
```
#### How to upgrade
@ -183,14 +86,14 @@ python3 -m pip install --upgrade slither-analyzer
```bash
git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
python3 -m pip 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.
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
@ -202,6 +105,162 @@ To share a directory in the container:
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).
* For pre-commit integration, use (replace `$GIT_TAG` with real tag)
```YAML
- repo: https://github.com/crytic/slither
rev: $GIT_TAG
hooks:
- id: slither
```
* 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`)
## 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/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 | `encode-packed-collision` | [ABI encodePacked Collision](https://github.com/crytic/slither/wiki/Detector-Documentation#abi-encodePacked-collision) | High | High
5 | `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
6 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
7 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
8 | `protected-vars` | [Detected unprotected variables](https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables) | High | High
9 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
10 | `rtlo` | [Right-To-Left-Override control character is used](https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character) | High | High
11 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
12 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
13 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
14 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
15 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High
16 | `codex` | [Use Codex to find vulnerabilities.](https://github.com/crytic/slither/wiki/Detector-Documentation#codex) | High | Low
17 | `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
18 | `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
19 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
20 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
21 | `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
22 | `incorrect-exp` | [Incorrect exponentiation](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-exponentiation) | High | Medium
23 | `incorrect-return` | [If a `return` is incorrectly used in assembly mode.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-return-in-assembly) | High | Medium
24 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
25 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
26 | `return-leave` | [If a `return` is used instead of a `leave`.](https://github.com/crytic/slither/wiki/Detector-Documentation#return-instead-of-leave-in-assembly) | High | Medium
27 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
28 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
29 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
30 | `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
31 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
32 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
33 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
34 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
35 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
36 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
37 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
38 | `tautological-compare` | [Comparing a variable to itself always returns true or false, depending on comparison](https://github.com/crytic/slither/wiki/Detector-Documentation#tautological-compare) | Medium | High
39 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
40 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
41 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
42 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
43 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
44 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
45 | `out-of-order-retryable` | [Out-of-order retryable transactions](https://github.com/crytic/slither/wiki/Detector-Documentation#out-of-order-retryable-transactions) | Medium | Medium
46 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
47 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
48 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
49 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
50 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
51 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
52 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
53 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
54 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
55 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
56 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
57 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
58 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
59 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
60 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
61 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
62 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
63 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
64 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
65 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
66 | `return-bomb` | [A low level callee may consume all callers gas unexpectedly.](https://github.com/crytic/slither/wiki/Detector-Documentation#return-bomb) | Low | Medium
67 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
68 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
69 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
70 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
71 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
72 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
73 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
74 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
75 | `incorrect-using-for` | [Detects using-for statement usage when no function from a given library matches a given type](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage) | Informational | High
76 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
77 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
78 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
79 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
80 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
81 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
82 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
83 | `unused-import` | [Detects unused imports](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-imports) | Informational | High
84 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
85 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
86 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
87 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
88 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
89 | `cache-array-length` | [Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it.](https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length) | Optimization | High
90 | `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
91 | `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
92 | `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
93 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Detector-Documentation#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
* The [Detection Selection](https://github.com/crytic/slither/wiki/Usage#detector-selection) to run only selected detectors. By default, all the detectors are run.
* The [Triage Mode](https://github.com/crytic/slither/wiki/Usage#triage-mode) to filter individual results
## Printers
### Quick Review Printers
* `human-summary`: [Print a human-readable summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary)
* `inheritance-graph`: [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph)
* `contract-summary`: [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary)
* `loc`: [Count the total number lines of code (LOC), source lines of code (SLOC), and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), and test files (TEST).](https://github.com/trailofbits/slither/wiki/Printer-documentation#loc)
### In-Depth Review Printers
* `call-graph`: [Export the call-graph of the contracts to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph)
* `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
* `function-summary`: [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary)
* `vars-and-auth`: [Print the state variables written and the authorization of the functions](https://github.com/crytic/slither/wiki/Printer-documentation#variables-written-and-authorization)
* `not-pausable`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
To run a printer, use `--print` and a comma-separated list of printers.
See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-documentation) for the complete lists.
## Tools
* `slither-check-upgradeability`: [Review `delegatecall`-based upgradeability](https://github.com/crytic/slither/wiki/Upgradeability-Checks)
* `slither-prop`: [Automatic unit test and property generation](https://github.com/crytic/slither/wiki/Property-generation)
* `slither-flat`: [Flatten a codebase](https://github.com/crytic/slither/wiki/Contract-Flattening)
* `slither-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance)
* `slither-format`: [Automatic patch generation](https://github.com/crytic/slither/wiki/Slither-format)
* `slither-read-storage`: [Read storage values from contracts](./slither/tools/read_storage/README.md)
* `slither-interface`: [Generate an interface for a contract](./slither/tools/interface/README.md)
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
[Contact us](https://www.trailofbits.com/contact/) to get help on building custom tools.
## API Documentation
Documentation on Slither's internals is available [here](https://crytic.github.io/slither/slither.html).
## Getting Help
Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (#ethereum) for help using or extending Slither.
@ -214,28 +273,53 @@ Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
## FAQ
How do I exclude mocks or tests?
* View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
How do I fix "unknown file" or compilation issues?
* Because slither requires the solc AST, it must have all dependencies available.
If a contract has dependencies, `slither contract.sol` will fail.
Instead, use `slither .` in the parent directory of `contracts/` (you should see `contracts/` when you run `ls`).
If you have a `node_modules/` folder, it must be in the same directory as `contracts/`. To verify that this issue is related to slither,
run the compilation command for the framework you are using e.g `npx hardhat compile`. That must work successfully;
otherwise, slither's compilation engine, crytic-compile, cannot generate the AST.
## License
Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms.
## Publications
### Trail of Bits publication
- [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
* [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
### External publications
Title | Usage | Authors | Venue
--- | --- | --- | ---
[ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method) | AST-based analysis built on top of Slither | Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen | CTCIS 19
[MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf) | Leverage data dependency through Slither | William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh | ISSRE 2019
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
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](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 | [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](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)
[Modeling and Enforcing Access Control Policies for Smart Contracts](https://publikationen.bibliothek.kit.edu/1000152805/151859658) | Extend Slither's data dependencies | Jan-Philipp Toberg, Jonas Schiffl, Frederik Reiche, Bernhard Beckert, Robert Heinrich, Ralf Reussner | IEEE International Conference on Decentralized Applications and Infrastructures (DAPPS), 2022 | [SolidityAccessControlEnforcement](https://github.com/KASTEL-CSSDA/SolidityAccessControlEnforcement)
[Smart Contract Vulnerability Detection Based on Deep Learning and Multimodal Decision Fusion](https://www.mdpi.com/1424-8220/23/16/7246) | Use Slither to extract the CFG | Weichu Deng, Huanchun Wei, Teng Huang, Cong Cao, Yun Peng, and Xuan Hu | Sensors 2023, 23, 7246 | -
[Semantic-enriched Code Knowledge Graph to Reveal Unknowns in Smart Contract Code Reuse](https://www.researchgate.net/profile/Qing-Huang-26/publication/370638129_Semantic-enriched_Code_Knowledge_Graph_to_Reveal_Unknowns_in_Smart_Contract_Code_Reuse/links/645b7b8639c408339b3a54da/Semantic-Enriched-Code-Knowledge-Graph-to-Reveal-Unknowns-in-Smart-Contract-Code-Reuse.pdf) | Use Slither to extract the code features (CFG, function, parameters types, ..) | Qing Huang, Dianshu Liao, Zhenchang Xing, Zhengkang Zuo, Changjing Wang, Xin Xia | ACM Transactions on Software Engineering and Methodology, 2023 | -
[Smart Contract Parallel Execution with Fine-Grained State Accesses](https://personal.ntu.edu.sg/yi_li/files/Qi2023SCP.pdf) | Use Slither to build state access graphs | Xiaodong Qi, Jiao Jiao, Yi Li | International Conference on Distributed Computing Systems (ICDCS), 2023 | -
[Bad Apples: Understanding the Centralized Security Risks in Decentralized Ecosystems](https://diaowenrui.github.io/paper/www23-yan.pdf) | Implement an internal analysis on top of Slither | Kailun Yan , Jilian Zhang , Xiangyu Liu , Wenrui Diao , Shanqing Guo | ACM Web Conference April 2023 | -
[Identifying Vulnerabilities in Smart Contracts using Interval Analysis](https://arxiv.org/pdf/2309.13805.pdf) | Create 4 detectors on top of Slither | Ştefan-Claudiu Susan, Andrei Arusoaie | FROM 2023 | -
Storage State Analysis and Extraction of Ethereum Blockchain Smart Contracts (no PDF in open access) | Rely on Slither's CFG and AST | Maha Ayub , Tania Saleem , Muhammad Janjua , Talha Ahmad | TOSEM 2023 | [SmartMuv](https://github.com/WaizKhan7/SmartMuv)
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;
}
}

@ -65,7 +65,7 @@ contract SimpleVulnerableToken{
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @param _owner The address to query the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public view returns (uint256) {

@ -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)
@ -120,3 +126,17 @@ print(f"{var_tainted} is tainted: {is_tainted(var_tainted, contract)}")
assert is_tainted(var_tainted, contract)
print(f"{var_not_tainted} is tainted: {is_tainted(var_not_tainted, contract)}")
assert not is_tainted(var_not_tainted, contract)
print("SimpleModifier contract")
contracts = slither.get_contract_from_name("SimpleModifier")
assert len(contracts) == 1
contract = contracts[0]
dependent_state_var = contract.get_state_variable_from_name("owner")
assert dependent_state_var
baz = contract.get_modifier_from_signature("baz()")
intermediate = baz.get_local_variable_from_name("intermediate")
assert intermediate
print(
f"{intermediate} depends on msg.sender: {is_dependent(intermediate, SolidityVariableComposed('msg.sender'), baz)}"
)
assert is_dependent(intermediate, SolidityVariableComposed("msg.sender"), baz)

@ -115,3 +115,12 @@ contract PropagateThroughReturnValue {
return (var_state);
}
}
contract SimpleModifier {
address owner;
modifier baz {
bool intermediate = msg.sender == owner;
require(intermediate);
_;
}
}

@ -19,7 +19,8 @@ def resolve_function(contract_name, function_name):
contract = contracts[0]
# Obtain the target function
target_function = next(
(function for function in contract.functions if function.name == function_name), None
(function for function in contract.functions_declared if function.name == function_name),
None,
)
# Verify we have resolved the function specified.

@ -10,6 +10,9 @@ from slither.slithir.variables.temporary import TemporaryVariable
def visit_node(node, visited):
if node is None:
return
if node in visited:
return

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

@ -1,14 +1,14 @@
from setuptools import setup, find_packages
setup(
name="slither-my-plugins",
name="slither_my_plugin",
description="This is an example of detectors and printers to Slither.",
url="https://github.com/trailofbits/slither-plugins",
author="Trail of Bits",
version="0.0",
packages=find_packages(),
python_requires=">=3.6",
install_requires=["slither-analyzer==0.1"],
python_requires=">=3.8",
install_requires=["slither-analyzer>=0.6.0"],
entry_points={
"slither_analyzer.plugin": "slither my-plugin=slither_my_plugin:make_plugin",
},

@ -1,8 +1,13 @@
from typing import Tuple, List, Type
from slither_my_plugin.detectors.example import Example
from slither.detectors.abstract_detector import AbstractDetector
from slither.printers.abstract_printer import AbstractPrinter
def make_plugin():
def make_plugin() -> Tuple[List[Type[AbstractDetector]], List[Type[AbstractPrinter]]]:
plugin_detectors = [Example]
plugin_printers = []
plugin_printers: List[Type[AbstractPrinter]] = []
return plugin_detectors, plugin_printers

@ -11,12 +11,12 @@ class Example(AbstractDetector): # pylint: disable=too-few-public-methods
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = ""
WIKI = "https://www.example.com/#example-detector"
WIKI_TITLE = ""
WIKI_DESCRIPTION = ""
WIKI_EXPLOIT_SCENARIO = ""
WIKI_RECOMMENDATION = ""
WIKI_TITLE = "example detector"
WIKI_DESCRIPTION = "This is an example detector that always generates a finding"
WIKI_EXPLOIT_SCENARIO = "Scenario goes here"
WIKI_RECOMMENDATION = "Customize the detector"
def _detect(self):

@ -7,7 +7,6 @@ missing-module-docstring,
missing-class-docstring,
missing-function-docstring,
unnecessary-lambda,
bad-continuation,
cyclic-import,
line-too-long,
invalid-name,
@ -18,5 +17,6 @@ logging-fstring-interpolation,
logging-not-lazy,
duplicate-code,
import-error,
unsubscriptable-object
unsubscriptable-object,
unnecessary-lambda-assignment
"""

@ -1,86 +0,0 @@
#!/usr/bin/env bash
### Test Detectors
DIR="$(cd "$(dirname "$0")" && pwd)"
CURRENT_PATH=$(pwd)
TRAVIS_PATH='/home/travis/build/crytic/slither'
# test_slither file.sol detectors
test_slither(){
expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json"
# run slither detector on input file and save output as json
slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json"
if [ $? -eq 255 ]
then
echo "Slither crashed"
exit 255
fi
if [ ! -f "$DIR/tmp-test.json" ]; then
echo ""
echo "Missing generated file"
echo ""
exit 1
fi
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i
result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json")
rm "$DIR/tmp-test.json"
if [ "$result" != "{}" ]; then
echo ""
echo "failed test of file: $1, detector: $2"
echo ""
echo "$result"
echo ""
exit 1
fi
# run slither detector on input file and save output as json
slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json"
if [ $? -eq 255 ]
then
echo "Slither crashed"
exit 255
fi
if [ ! -f "$DIR/tmp-test.json" ]; then
echo ""
echo "Missing generated file"
echo ""
exit 1
fi
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i
result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json")
rm "$DIR/tmp-test.json"
if [ "$result" != "{}" ]; then
echo ""
echo "failed test of file: $1, detector: $2"
echo ""
echo "$result"
echo ""
exit 1
fi
}
# generate_expected_json file.sol detectors
generate_expected_json(){
# generate output filename
# e.g. file: uninitialized.sol detector: uninitialized-state
# ---> uninitialized.uninitialized-state.json
output_filename="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json"
output_filename_txt="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.txt"
# run slither detector on input file and save output as json
slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" > "$output_filename_txt" 2>&1
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename" -i
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i
}

@ -4,17 +4,17 @@
solc-select use 0.7.0
if ! slither "tests/config/test.sol" --solc-ast --ignore-return-value; 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 --ignore-return-value; 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 --ignore-return-value; 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
@ -15,13 +20,9 @@ nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign
dapp init
slither . --detect external-function
# TODO: make more elaborate test
if [ $? -eq 4 ]
then
exit 0
if ! slither . --detect external-function; then
echo "Dapp test failed"
exit 1
fi
echo "Dapp test failed"
exit 255
exit 0

@ -15,13 +15,10 @@ npm install -g embark@4.2.0
embark demo
cd embark_demo || exit 255
npm install
slither . --embark-overwrite-config
if [ $? -eq 4 ]
then
exit 0
if ! slither . --embark-overwrite-config; then
echo "Embark test failed"
exit 255
fi
echo "Embark test failed"
exit 255
exit 0

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

@ -13,12 +13,10 @@ nvm use 10.17.0
npm i -g etherlime
etherlime init
slither .
if [ $? -eq 7 ]
then
exit 0
if ! slither .; then
echo "Etherlime test failed"
exit 1
fi
echo "Etherlime test failed"
exit 255
exit 0

@ -5,19 +5,17 @@
mkdir etherscan
cd etherscan || exit 255
slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN"
if [ $? -ne 5 ]
then
echo "Etherscan test failed"
exit 255
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::"
slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN"
if [ $? -ne 70 ]
then
echo "Etherscan test failed"
exit 255
# 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,6 @@
#!/usr/bin/env bash
### Test slither-check-upgradability
### Test slither-check-upgradeability
DIR_TESTS="tests/possible_paths"

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

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

@ -0,0 +1,15 @@
#!/usr/bin/env bash
### Test path filtering across POSIX and Windows
solc-select use 0.8.0
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
echo "Path filtering across POSIX and Windows failed"
rm output.txt
exit 5
else
rm output.txt
fi

@ -1,15 +1,24 @@
#!/usr/bin/env bash
### Test printer
### 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,halstead,human-summary,inheritance,inheritance-graph,loc,martin,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration,ck"
# 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

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

@ -13,12 +13,10 @@ nvm use --lts
npm install -g truffle
truffle unbox metacoin
slither .
if [ $? -eq 9 ]
then
exit 0
if ! slither . --no-fail-pedantic; then
echo "Truffle test failed"
exit 1
fi
echo "Truffle test failed"
exit 255
exit 0

@ -2,7 +2,8 @@
### Test slither-check-upgradeability
DIR_TESTS="tests/check-upgradeability"
DIR_TESTS="tests/tools/check_upgradeability"
solc-select install "0.5.0"
solc-select use "0.5.0"
slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_1.txt 2>&1
@ -155,6 +156,58 @@ 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
slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug_reinitializer --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_14.txt 2>&1
DIFF=$(diff test_14.txt "$DIR_TESTS/test_14.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 14 failed"
cat test_14.txt
echo ""
cat "$DIR_TESTS/test_14.txt"
echo ""
echo "$DIFF"
exit 255
fi
slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_reinitializer_V2 --new-contract-name Counter_reinitializer_V3_V4 > test_15.txt 2>&1
DIFF=$(diff test_15.txt "$DIR_TESTS/test_15.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 14 failed"
cat test_15.txt
echo ""
cat "$DIR_TESTS/test_15.txt"
echo ""
echo "$DIFF"
exit 255
fi
rm test_1.txt
rm test_2.txt
rm test_3.txt
@ -166,3 +219,7 @@ rm test_8.txt
rm test_9.txt
rm test_10.txt
rm test_11.txt
rm test_12.txt
rm test_13.txt
rm test_14.txt
rm test_15.txt

@ -1,27 +0,0 @@
import sys
import json
from pprint import pprint
from deepdiff import DeepDiff # pip install deepdiff
if len(sys.argv) != 3:
print("Usage: python json_diff.py 1.json 2.json")
sys.exit(-1)
with open(sys.argv[1], encoding="utf8") as f:
d1 = json.load(f)
with open(sys.argv[2], encoding="utf8") as f:
d2 = json.load(f)
# Remove description field to allow non deterministic print
for elem in d1:
if "description" in elem:
del elem["description"]
for elem in d2:
if "description" in elem:
del elem["description"]
pprint(DeepDiff(d1, d2, ignore_order=True, verbose_level=2))

@ -0,0 +1,28 @@
import json
from pathlib import Path
import urllib.request
def retrieve_json(url):
with urllib.request.urlopen(url) as response:
data = response.read().decode("utf-8")
return json.loads(data)
def organize_data(json_data):
version_bugs = {}
for version, info in json_data.items():
version_bugs[version] = info["bugs"]
return version_bugs
if __name__ == "__main__":
bug_list_url = (
"https://raw.githubusercontent.com/ethereum/solidity/develop/docs/bugs_by_version.json"
)
bug_data = retrieve_json(bug_list_url)
bugs_by_version = organize_data(bug_data)
with open(Path.cwd() / Path("slither/utils/buggy_versions.py"), "w", encoding="utf-8") as file:
file.write("# pylint: disable=too-many-lines\n")
file.write(f"bugs_by_version = {bugs_by_version}")

@ -5,21 +5,49 @@ with open("README.md", "r", encoding="utf-8") as f:
setup(
name="slither-analyzer",
description="Slither is a Solidity static analysis framework written in Python 3.",
description="Slither is a Solidity and Vyper static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.8.3",
version="0.10.4",
packages=find_packages(),
python_requires=">=3.6",
python_requires=">=3.8",
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
# "crytic-compile>=0.2.3",
"crytic-compile",
"packaging",
"prettytable>=3.10.2",
"pycryptodome>=3.4.6",
"crytic-compile>=0.3.7,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"web3>=6.20.2, <7",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
"eth-utils>=2.1.0",
],
dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
extras_require={
"lint": [
"black==22.3.0",
"pylint==3.0.3",
],
"test": [
"pytest",
"pytest-cov",
"pytest-xdist",
"deepdiff",
"numpy",
"coverage[toml]",
"filelock",
"pytest-insta",
],
"doc": [
"pdoc",
],
"dev": [
"slither-analyzer[lint,test,doc]",
"openai",
],
},
license="AGPL-3.0",
long_description=long_description,
long_description_content_type="text/markdown",
entry_points={
"console_scripts": [
"slither = slither.__main__:main",
@ -33,6 +61,9 @@ setup(
"slither-prop = slither.tools.properties.__main__:main",
"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

@ -10,11 +10,11 @@ import os
import pstats
import sys
import traceback
from typing import Optional
from importlib import metadata
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union
from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser
from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export
from crytic_compile.platform.etherscan import SUPPORTED_NETWORK
from crytic_compile import compile_all, is_supported
@ -24,10 +24,18 @@ 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 (
FailOnLevel,
output_detectors,
output_results_to_markdown,
output_detectors_json,
@ -54,7 +62,12 @@ logger = logging.getLogger("Slither")
###################################################################################
def process_single(target, args, detector_classes, printer_classes):
def process_single(
target: Union[str, CryticCompile],
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Output], int]:
"""
The core high-level code for running Slither static analysis.
@ -64,15 +77,22 @@ def process_single(target, args, detector_classes, printer_classes):
ast = "--ast-compact-json"
if args.legacy_ast:
ast = "--ast-json"
if args.checklist:
args.show_ignored_findings = True
slither = Slither(target, ast_format=ast, **vars(args))
if args.sarif_input:
slither.sarif_input = args.sarif_input
if args.sarif_triage:
slither.sarif_triage = args.sarif_triage
return _process(slither, detector_classes, printer_classes)
def process_all(target, args, detector_classes, printer_classes):
def process_all(
target: str,
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[List[Slither], List[Dict], List[Output], int]:
compilations = compile_all(target, **vars(args))
slither_instances = []
results_detectors = []
@ -97,7 +117,11 @@ def process_all(target, args, detector_classes, printer_classes):
)
def _process(slither, detector_classes, printer_classes):
def _process(
slither: Slither,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Output], int]:
for detector_cls in detector_classes:
slither.register_detector(detector_cls)
@ -110,9 +134,9 @@ def _process(slither, detector_classes, printer_classes):
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:
@ -123,64 +147,45 @@ def _process(slither, detector_classes, printer_classes):
return slither, results_detectors, results_printers, analyzed_contracts_count
def process_from_asts(filenames, args, detector_classes, printer_classes):
all_contracts = []
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
###################################################################################
###################################################################################
# region Exit
###################################################################################
###################################################################################
def my_exit(results):
if not results:
sys.exit(0)
sys.exit(len(results))
# endregion
###################################################################################
###################################################################################
# region Detectors and printers
###################################################################################
###################################################################################
def get_detectors_and_printers():
"""
NOTE: This contains just a few detectors and printers that we made public.
"""
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)]
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)]
printers = [getattr(all_printers, name) for name in dir(all_printers)]
printers = [p for p in printers if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
printers_ = [getattr(all_printers, name) for name in dir(all_printers)]
printers = [p for p in printers_ if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
# Handle plugins!
for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None):
if sys.version_info >= (3, 10):
entry_points = metadata.entry_points(group="slither_analyzer.plugin")
else:
from pkg_resources import iter_entry_points # pylint: disable=import-outside-toplevel
entry_points = iter_entry_points(group="slither_analyzer.plugin", name=None)
for entry_point in entry_points:
make_plugin = entry_point.load()
plugin_detectors, plugin_printers = make_plugin()
detector = None
if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
raise Exception(
raise ValueError(
f"Error when loading plugin {entry_point}, {detector} is not a detector"
)
printer = None
if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
raise Exception(f"Error when loading plugin {entry_point}, {printer} is not a printer")
raise ValueError(f"Error when loading plugin {entry_point}, {printer} is not a printer")
# We convert those to lists in case someone returns a tuple
detectors += list(plugin_detectors)
@ -190,7 +195,9 @@ def get_detectors_and_printers():
# pylint: disable=too-many-branches
def choose_detectors(args, all_detector_classes):
def choose_detectors(
args: argparse.Namespace, all_detector_classes: List[Type[AbstractDetector]]
) -> List[Type[AbstractDetector]]:
# If detectors are specified, run only these ones
detectors_to_run = []
@ -198,48 +205,57 @@ def choose_detectors(args, all_detector_classes):
if args.detectors_to_run == "all":
detectors_to_run = all_detector_classes
if args.detectors_to_exclude:
detectors_excluded = args.detectors_to_exclude.split(",")
for detector in detectors:
if detector in detectors_excluded:
detectors_to_run.remove(detectors[detector])
else:
for detector in args.detectors_to_run.split(","):
if detector in detectors:
detectors_to_run.append(detectors[detector])
else:
raise Exception(f"Error: {detector} is not a detector")
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
detectors_to_run = __include_detectors(
set(detectors_to_run), args.detectors_to_run, detectors
)
return detectors_to_run
if args.exclude_optimization:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION
]
classification_map = {
DetectorClassification.HIGH: args.exclude_high,
DetectorClassification.MEDIUM: args.exclude_medium,
DetectorClassification.LOW: args.exclude_low,
DetectorClassification.INFORMATIONAL: args.exclude_informational,
DetectorClassification.OPTIMIZATION: args.exclude_optimization,
}
excluded_classification = [
classification for classification, included in classification_map.items() if included
]
detectors_to_run = [d for d in detectors_to_run if d.IMPACT not in excluded_classification]
if args.exclude_informational:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL
]
if args.exclude_low:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW]
if args.exclude_medium:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM
]
if args.exclude_high:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH]
if args.detectors_to_exclude:
detectors_to_run = [
d for d in detectors_to_run if d.ARGUMENT not in args.detectors_to_exclude
]
if args.detectors_to_include:
detectors_to_run = __include_detectors(
set(detectors_to_run), args.detectors_to_include, detectors
)
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
def __include_detectors(
detectors_to_run: Set[Type[AbstractDetector]],
detectors_to_include: str,
detectors: Dict[str, Type[AbstractDetector]],
) -> List[Type[AbstractDetector]]:
include_detectors = detectors_to_include.split(",")
for detector in include_detectors:
if detector in detectors:
detectors_to_run.add(detectors[detector])
else:
raise ValueError(f"Error: {detector} is not a detector")
return detectors_to_run
def choose_printers(args, all_printer_classes):
def choose_printers(
args: argparse.Namespace, all_printer_classes: List[Type[AbstractPrinter]]
) -> List[Type[AbstractPrinter]]:
printers_to_run = []
# disable default printer
@ -254,7 +270,7 @@ def choose_printers(args, all_printer_classes):
if printer in printers:
printers_to_run.append(printers[printer])
else:
raise Exception(f"Error: {printer} is not a printer")
raise ValueError(f"Error: {printer} is not a printer")
return printers_to_run
@ -266,19 +282,22 @@ def choose_printers(args, all_printer_classes):
###################################################################################
def parse_filter_paths(args):
if args.filter_paths:
return args.filter_paths.split(",")
def parse_filter_paths(args: argparse.Namespace, filter_path: bool) -> List[str]:
paths = args.filter_paths if filter_path else args.include_paths
if paths:
return paths.split(",")
return []
def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-statements
# pylint: disable=too-many-statements
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"
usage += "\t- project_directory // a project directory. See https://github.com/crytic/crytic-compile/#crytic-compile for the supported platforms\n"
usage += "\t- 0x.. // a contract on mainet\n"
usage += "\t- 0x.. // a contract on mainnet\n"
usage += f"\t- NETWORK:0x.. // a contract on a different network. Supported networks: {','.join(x[:-1] for x in SUPPORTED_NETWORK)}\n"
parser = argparse.ArgumentParser(
@ -293,7 +312,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
parser.add_argument(
"--version",
help="displays the current version",
version=require("slither-analyzer")[0].version,
version=metadata.version("slither-analyzer"),
action="version",
)
@ -303,6 +322,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
"Checklist (consider using https://github.com/crytic/slither-action)"
)
group_misc = parser.add_argument_group("Additional options")
group_filters = parser.add_mutually_exclusive_group()
group_detector.add_argument(
"--detect",
@ -315,13 +335,21 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_printer.add_argument(
"--print",
help="Comma-separated list fo contract information printers, "
help="Comma-separated list of contract information printers, "
f"available printers: {', '.join(d.ARGUMENT for d in printer_classes)}",
action="store",
dest="printers_to_run",
default=defaults_flag_in_config["printers_to_run"],
)
group_printer.add_argument(
"--include-interfaces",
help="Include interfaces from inheritance-graph printer",
action="store_true",
dest="include_interfaces",
default=False,
)
group_detector.add_argument(
"--list-detectors",
help="List available detectors",
@ -388,6 +416,53 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["exclude_high"],
)
group_detector.add_argument(
"--include-detectors",
help="Comma-separated list of detectors that should be included",
action="store",
dest="detectors_to_include",
default=defaults_flag_in_config["detectors_to_include"],
)
fail_on_group = group_detector.add_mutually_exclusive_group()
fail_on_group.add_argument(
"--fail-pedantic",
help="Fail if any findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.PEDANTIC,
)
fail_on_group.add_argument(
"--fail-low",
help="Fail if any low or greater impact findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.LOW,
)
fail_on_group.add_argument(
"--fail-medium",
help="Fail if any medium or greater impact findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.MEDIUM,
)
fail_on_group.add_argument(
"--fail-high",
help="Fail if any high impact findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.HIGH,
)
fail_on_group.add_argument(
"--fail-none",
"--no-fail-pedantic",
help="Do not return the number of findings in the exit code",
action="store_const",
dest="fail_on",
const=FailOnLevel.NONE,
)
fail_on_group.set_defaults(fail_on=FailOnLevel.PEDANTIC)
group_detector.add_argument(
"--show-ignored-findings",
help="Show all the findings",
@ -404,7 +479,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_checklist.add_argument(
"--checklist-limit",
help="Limite the number of results per detector in the markdown file",
help="Limit the number of results per detector in the markdown file",
action="store",
default="",
)
@ -431,6 +506,20 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["sarif"],
)
group_misc.add_argument(
"--sarif-input",
help="Sarif input (beta)",
action="store",
default=defaults_flag_in_config["sarif_input"],
)
group_misc.add_argument(
"--sarif-triage",
help="Sarif triage (beta)",
action="store",
default=defaults_flag_in_config["sarif_triage"],
)
group_misc.add_argument(
"--json-types",
help="Comma-separated list of result types to output to JSON, defaults to "
@ -461,22 +550,22 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["disable_color"],
)
group_misc.add_argument(
"--filter-paths",
help="Comma-separated list of paths for which results will be excluded",
action="store",
dest="filter_paths",
default=defaults_flag_in_config["filter_paths"],
)
group_misc.add_argument(
"--triage-mode",
help="Run triage mode (save results in slither.db.json)",
help="Run triage mode (save results in triage database)",
action="store_true",
dest="triage_mode",
default=False,
)
group_misc.add_argument(
"--triage-database",
help="File path to the triage database (default: slither.db.json)",
action="store",
dest="triage_database",
default=defaults_flag_in_config["triage_database"],
)
group_misc.add_argument(
"--config-file",
help="Provide a config file (default: slither.config.json)",
@ -485,6 +574,14 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=None,
)
group_misc.add_argument(
"--change-line-prefix",
help="Change the line prefix (default #) for the displayed source codes (i.e. file.sol#1).",
action="store",
dest="change_line_prefix",
default="#",
)
group_misc.add_argument(
"--solc-ast",
help="Provide the contract as a json AST",
@ -499,6 +596,31 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
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"],
)
group_filters.add_argument(
"--filter-paths",
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"],
)
group_filters.add_argument(
"--include-paths",
help="Regex filter to include detector results matching file path e.g. (src/|contracts/). Opposite of --filter-paths",
action="store",
dest="include_paths",
default=defaults_flag_in_config["include_paths"],
)
codex.init_parser(parser)
# debugger command
parser.add_argument("--debug", help=argparse.SUPPRESS, action="store_true", default=False)
@ -530,13 +652,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["skip_assembly"],
)
parser.add_argument(
"--ignore-return-value",
help=argparse.SUPPRESS,
action="store_true",
default=defaults_flag_in_config["ignore_return_value"],
)
parser.add_argument(
"--perf",
help=argparse.SUPPRESS,
@ -544,9 +659,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
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
@ -559,26 +671,31 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
args = parser.parse_args()
read_config_file(args)
args.filter_paths = parse_filter_paths(args)
args.filter_paths = parse_filter_paths(args, True)
args.include_paths = parse_filter_paths(args, False)
# 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.')
raise ValueError(f'Error: "{json_type}" is not a valid JSON result output type.')
return 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()
class ListDetectorsJson(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()
detector_types_json = output_detectors_json(detectors)
print(json.dumps(detector_types_json))
@ -586,22 +703,38 @@ class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-meth
class ListPrinters(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
_, printers = get_detectors_and_printers()
output_printers(printers)
parser.exit()
class OutputMarkdown(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, args, values, option_string=None):
def __call__(
self,
parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> None:
detectors, printers = get_detectors_and_printers()
assert isinstance(values, str)
output_to_markdown(detectors, printers, values)
parser.exit()
class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, args, values, option_string=None):
def __call__(
self,
parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> None:
detectors, _ = get_detectors_and_printers()
assert isinstance(values, str)
output_wiki(detectors, values)
parser.exit()
@ -615,14 +748,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)
@ -634,7 +767,7 @@ class FormatterCryticCompile(logging.Formatter):
###################################################################################
def main():
def main() -> None:
# Codebase with complex domninators can lead to a lot of SSA recursive call
sys.setrecursionlimit(1500)
@ -644,7 +777,10 @@ def main():
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
def main_impl(all_detector_classes, all_printer_classes):
def main_impl(
all_detector_classes: List[Type[AbstractDetector]],
all_printer_classes: List[Type[AbstractPrinter]],
) -> None:
"""
:param all_detector_classes: A list of all detectors that can be included/excluded.
:param all_printer_classes: A list of all printers that can be included.
@ -659,10 +795,10 @@ def main_impl(all_detector_classes, all_printer_classes):
cp.enable()
# Set colorization option
set_colorization_enabled(not args.disable_color)
set_colorization_enabled(False if args.disable_color else sys.stdout.isatty())
# Define some variables for potential JSON output
json_results = {}
json_results: Dict[str, Any] = {}
output_error = None
outputting_json = args.json is not None
outputting_json_stdout = args.json == "-"
@ -675,7 +811,7 @@ def main_impl(all_detector_classes, all_printer_classes):
# 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)
@ -710,8 +846,8 @@ def main_impl(all_detector_classes, all_printer_classes):
crytic_compile_error.propagate = False
crytic_compile_error.setLevel(logging.INFO)
results_detectors = []
results_printers = []
results_detectors: List[Dict] = []
results_printers: List[Output] = []
try:
filename = args.filename
@ -724,26 +860,17 @@ def main_impl(all_detector_classes, all_printer_classes):
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:
@ -760,6 +887,7 @@ def main_impl(all_detector_classes, all_printer_classes):
if "compilations" in args.json_types:
compilation_results = []
for slither_instance in slither_instances:
assert slither_instance.crytic_compile
compilation_results.append(
generate_standard_export(slither_instance.crytic_compile)
)
@ -785,7 +913,9 @@ def main_impl(all_detector_classes, all_printer_classes):
# 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:
@ -800,8 +930,6 @@ def main_impl(all_detector_classes, all_printer_classes):
len(detector_classes),
len(results_detectors),
)
if args.ignore_return_value:
return
except SlitherException as slither_exception:
output_error = str(slither_exception)
@ -810,12 +938,6 @@ def main_impl(all_detector_classes, all_printer_classes):
logging.error(red(output_error))
logging.error("Please report an issue to https://github.com/crytic/slither/issues")
except Exception: # pylint: disable=broad-except
output_error = traceback.format_exc()
logging.error(traceback.print_exc())
logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation
logging.error(output_error)
# If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON.
if outputting_json:
if "console" in args.json_types:
@ -835,16 +957,32 @@ def main_impl(all_detector_classes, all_printer_classes):
if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type)
if args.perf:
if args.perf and cp:
cp.disable()
stats = pstats.Stats(cp).sort_stats("cumtime")
stats.print_stats()
# Exit with the appropriate status code
if output_error:
fail_on = FailOnLevel(args.fail_on)
if fail_on == FailOnLevel.HIGH:
fail_on_detection = any(result["impact"] == "High" for result in results_detectors)
elif fail_on == FailOnLevel.MEDIUM:
fail_on_detection = any(
result["impact"] in ["Medium", "High"] for result in results_detectors
)
elif fail_on == FailOnLevel.LOW:
fail_on_detection = any(
result["impact"] in ["Low", "Medium", "High"] for result in results_detectors
)
elif fail_on == FailOnLevel.PEDANTIC:
fail_on_detection = bool(results_detectors)
else:
fail_on_detection = False
# Exit with them appropriate status code
if output_error or fail_on_detection:
sys.exit(-1)
else:
my_exit(results_detectors)
sys.exit(0)
if __name__ == "__main__":

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

@ -128,17 +128,17 @@ def _get_evm_instructions_node(node_info):
node_info["cfg"].instructions,
node_info["srcmap"],
node_info["slither"],
node_info["contract"].source_mapping["filename_absolute"],
node_info["contract"].source_mapping.filename.absolute,
)
contract_file = (
node_info["slither"]
.source_code[node_info["contract"].source_mapping["filename_absolute"]]
.source_code[node_info["contract"].source_mapping.filename.absolute]
.encode("utf-8")
)
# Get evm instructions corresponding to node's source line number
node_source_line = (
contract_file[0 : node_info["node"].source_mapping["start"]].count("\n".encode("utf-8")) + 1
contract_file[0 : node_info["node"].source_mapping.start].count("\n".encode("utf-8")) + 1
)
node_pcs = contract_pcs.get(node_source_line, [])
node_ins = []
@ -178,15 +178,14 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither
# In order to compress these source mappings especially for bytecode, the following rules are used:
# If a field is empty, the value of the preceding element is used.
# If a : is missing, all following fields are considered empty.
mapping_item = mapping.split(":")
mapping_item += prev_mapping[len(mapping_item) :]
for i, _ in enumerate(mapping_item):
if mapping_item[i] == "":
mapping_item[i] = int(prev_mapping[i])
mapping_item[i] = prev_mapping[i]
offset, _length, file_id, _ = mapping_item
offset, _, file_id, *_ = mapping_item
prev_mapping = mapping_item
if file_id == "-1":
@ -194,8 +193,7 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither
# See https://github.com/ethereum/solidity/issues/6119#issuecomment-467797635
continue
offset = int(offset)
line_number = file_source[0:offset].count("\n".encode("utf-8")) + 1
line_number = file_source[0 : int(offset)].count("\n".encode("utf-8")) + 1
# Append evm instructions to the corresponding source line number
# Note: Some evm instructions in mapping are not necessarily in program execution order

@ -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,19 @@
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.expressions import CallExpression, Identifier, AssignmentOperation
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 +33,7 @@ from slither.slithir.operations import (
Return,
Operation,
)
from slither.slithir.utils.utils import RVALUE
from slither.slithir.variables import (
Constant,
LocalIRVariable,
@ -38,21 +42,10 @@ from slither.slithir.variables import (
TemporaryVariable,
TupleVariable,
)
from slither.all_exceptions import SlitherException
from slither.core.declarations import Contract
from slither.core.expressions.expression import Expression
if TYPE_CHECKING:
from slither.core.declarations import Function
from slither.slithir.variables.variable import SlithIRVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.utils.type_helpers import (
InternalCallType,
HighLevelCallType,
LibraryCallType,
LowLevelCallType,
)
from slither.core.cfg.scope import Scope
from slither.core.scope.scope import FileScope
@ -67,87 +60,49 @@ 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"
ENDASSEMBLY = "END 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
@ -159,7 +114,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
@ -187,16 +142,16 @@ 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._solidity_calls: List[SolidityFunction] = []
self._high_level_calls: List["HighLevelCallType"] = [] # contains library calls
self._library_calls: List["LibraryCallType"] = []
self._low_level_calls: List["LowLevelCallType"] = []
self._internal_calls: List[InternalCall] = [] # contains solidity calls
self._solidity_calls: List[SolidityCall] = []
self._high_level_calls: List[Tuple[Contract, HighLevelCall]] = [] # contains library calls
self._library_calls: List[LibraryCall] = []
self._low_level_calls: List[LowLevelCall] = []
self._external_calls_as_expressions: List[Expression] = []
self._internal_calls_as_expressions: List[Expression] = []
self._irs: List[Operation] = []
@ -213,7 +168,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] = []
@ -230,6 +187,9 @@ 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
self._is_reachable: bool = False
###################################################################################
###################################################################################
@ -254,17 +214,32 @@ 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
def will_return(self) -> bool:
if not self.sons and self.type != NodeType.THROW:
if SolidityFunction("revert()") not in self.solidity_calls:
if SolidityFunction("revert(string)") not in self.solidity_calls:
solidity_calls = [ir.function for ir in self.solidity_calls]
if SolidityFunction("revert()") not in solidity_calls:
if SolidityFunction("revert(string)") not in solidity_calls:
return True
return False
def set_function(self, function: "Function") -> None:
self._function = function
@property
def function(self) -> "Function":
return self._function
@property
def is_reachable(self) -> bool:
return self._is_reachable
def set_is_reachable(self, new_is_reachable: bool) -> None:
self._is_reachable = new_is_reachable
# endregion
###################################################################################
###################################################################################
@ -273,7 +248,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)
"""
@ -326,11 +301,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
@ -380,7 +357,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
@ -391,44 +368,38 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
###################################################################################
@property
def internal_calls(self) -> List["InternalCallType"]:
def internal_calls(self) -> List[InternalCall]:
"""
list(Function or SolidityFunction): List of internal/soldiity function calls
list(InternalCall): List of IR operations with internal/solidity function calls
"""
return list(self._internal_calls)
@property
def solidity_calls(self) -> List[SolidityFunction]:
def solidity_calls(self) -> List[SolidityCall]:
"""
list(SolidityFunction): List of Soldity calls
list(SolidityCall): List of IR operations with solidity calls
"""
return list(self._solidity_calls)
@property
def high_level_calls(self) -> List["HighLevelCallType"]:
def high_level_calls(self) -> List[HighLevelCall]:
"""
list((Contract, Function|Variable)):
List of high level calls (external calls).
A variable is called in case of call to a public state variable
list(HighLevelCall): List of IR operations with high level calls (external calls).
Include library calls
"""
return list(self._high_level_calls)
@property
def library_calls(self) -> List["LibraryCallType"]:
def library_calls(self) -> List[LibraryCall]:
"""
list((Contract, Function)):
Include library calls
list(LibraryCall): List of IR operations with library calls.
"""
return list(self._library_calls)
@property
def low_level_calls(self) -> List["LowLevelCallType"]:
def low_level_calls(self) -> List[LowLevelCall]:
"""
list((Variable|SolidityVariable, str)): List of low_level call
A low level call is defined by
- the variable called
- the name of the function (call/delegatecall/codecall)
list(LowLevelCall): List of IR operations with low_level call
"""
return list(self._low_level_calls)
@ -440,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
@ -451,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
@ -459,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
@ -514,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:
@ -547,11 +518,12 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
bool: True if the node has a require or assert call
"""
return any(
c.name in ["require(bool)", "require(bool,string)", "assert(bool)"]
for c in self.internal_calls
ir.function.name
in ["require(bool)", "require(bool,string)", "require(bool,error)", "assert(bool)"]
for ir 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:
@ -561,7 +533,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
@ -590,7 +562,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
@ -600,7 +572,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:
@ -608,7 +580,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:
@ -625,7 +597,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:
@ -633,7 +605,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:
@ -641,7 +613,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:
@ -649,7 +621,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:
@ -704,20 +691,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()
@ -731,11 +718,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
@ -754,7 +741,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
@ -766,7 +753,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
@ -778,7 +765,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)
@ -786,7 +773,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
@ -828,14 +815,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,
@ -859,7 +847,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:
@ -868,7 +856,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)]
@ -876,8 +865,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
@ -894,36 +884,61 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
self._vars_written.append(var)
if isinstance(ir, InternalCall):
self._internal_calls.append(ir.function)
self._internal_calls.append(ir)
if isinstance(ir, SolidityCall):
# TODO: consider removing dependancy of solidity_call to internal_call
self._solidity_calls.append(ir.function)
self._internal_calls.append(ir.function)
self._solidity_calls.append(ir)
self._internal_calls.append(ir)
if (
isinstance(ir, SolidityCall)
and ir.function == SolidityFunction("sstore(uint256,uint256)")
and isinstance(ir.node.expression, CallExpression)
and isinstance(ir.node.expression.arguments[0], Identifier)
):
self._vars_written.append(ir.arguments[0])
if (
isinstance(ir, SolidityCall)
and ir.function == SolidityFunction("sload(uint256)")
and isinstance(ir.node.expression, AssignmentOperation)
and isinstance(ir.node.expression.expression_right, CallExpression)
and isinstance(ir.node.expression.expression_right.arguments[0], Identifier)
):
self._vars_read.append(ir.arguments[0])
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)
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))
self._high_level_calls.append((ir.destination.type, ir))
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))
else:
try:
self._high_level_calls.append((ir.destination.type.type, ir.function))
# Todo this part needs more tests and documentation
self._high_level_calls.append((ir.destination.type.type, ir))
except AttributeError as error:
# pylint: disable=raise-missing-from
raise SlitherException(
f"Function not found on IR: {ir}.\nNode: {self} ({self.source_mapping_str})\nFunction: {self.function}\nPlease try compiling with a recent Solidity version. {error}"
f"Function not found on IR: {ir}.\nNode: {self} ({self.source_mapping})\nFunction: {self.function}\nPlease try compiling with a recent Solidity version. {error}"
)
elif isinstance(ir, LibraryCall):
assert isinstance(ir.destination, Contract)
self._high_level_calls.append((ir.destination, ir.function))
self._library_calls.append((ir.destination, ir.function))
assert isinstance(ir.function, Function)
self._high_level_calls.append((ir.destination, ir))
self._library_calls.append(ir)
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 +949,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 +979,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 +1003,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 +1027,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 +1045,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,8 +7,10 @@ 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, parent_scope: Union["Scope", "Function"]
) -> None:
self.nodes: List["Node"] = []
self.is_checked = is_checked
self.is_yul = is_yul
self.father = scope
self.father = parent_scope

@ -1,17 +0,0 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from slither.core.declarations import Contract
class ChildContract:
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.contract.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

@ -1,4 +1,5 @@
import math
from enum import Enum
from typing import Optional, Dict, List, Set, Union, TYPE_CHECKING, Tuple
from crytic_compile import CompilationUnit, CryticCompile
@ -13,11 +14,14 @@ 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.event_top_level import EventTopLevel
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
@ -27,23 +31,41 @@ if TYPE_CHECKING:
from slither.core.slither_core import SlitherCore
class Language(Enum):
SOLIDITY = "solidity"
VYPER = "vyper"
@staticmethod
def from_str(label: str):
if label == "solc":
return Language.SOLIDITY
if label == "vyper":
return Language.VYPER
raise ValueError(f"Unknown language: {label}")
# 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
self._crytic_compile_compilation_unit = crytic_compilation_unit
self._language = Language.from_str(crytic_compilation_unit.compiler_version.compiler)
# Top level object
self.contracts: List[Contract] = []
self._structures_top_level: List[StructureTopLevel] = []
self._enums_top_level: List[EnumTopLevel] = []
self._events_top_level: List[EventTopLevel] = []
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._type_aliases: Dict[str, TypeAliasTopLevel] = {}
self._all_functions: Set[Function] = set()
self._all_modifiers: Set[Modifier] = set()
@ -53,7 +75,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] = {}
@ -77,6 +99,17 @@ class SlitherCompilationUnit(Context):
# region Compiler
###################################################################################
###################################################################################
@property
def language(self) -> Language:
return self._language
@property
def is_vyper(self) -> bool:
return self._language == Language.VYPER
@property
def is_solidity(self) -> bool:
return self._language == Language.SOLIDITY
@property
def compiler_version(self) -> CompilerVersion:
@ -84,7 +117,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 +157,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 +180,30 @@ 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:
"""This info is used to compute the rvalues of Phi operations in `fix_phi` and ultimately
is responsible for the `read` property of Phi operations which is vital to
propagating taints inter-procedurally
"""
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 +216,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)
@ -197,6 +236,10 @@ class SlitherCompilationUnit(Context):
def enums_top_level(self) -> List[EnumTopLevel]:
return self._enums_top_level
@property
def events_top_level(self) -> List[EventTopLevel]:
return self._events_top_level
@property
def variables_top_level(self) -> List[TopLevelVariable]:
return self._variables_top_level
@ -206,9 +249,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 type_aliases(self) -> Dict[str, TypeAliasTopLevel]:
return self._type_aliases
# endregion
###################################################################################
###################################################################################
@ -217,7 +268,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,16 +295,15 @@ class SlitherCompilationUnit(Context):
###################################################################################
###################################################################################
def compute_storage_layout(self):
def compute_storage_layout(self) -> None:
assert self.is_solidity
for contract in self.contracts_derived:
self._storage_layouts[contract.name] = {}
slot = 0
offset = 0
for var in contract.state_variables_ordered:
if var.is_constant or var.is_immutable:
continue
for var in contract.stored_state_variables_ordered:
assert var.type
size, new_slot = var.type.storage_size
if new_slot:
@ -273,7 +323,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

@ -1,6 +1,8 @@
from .contract import Contract
from .enum import Enum
from .event import Event
from .event_contract import EventContract
from .event_top_level import EventTopLevel
from .function import Function
from .import_directive import Import
from .modifier import Modifier
@ -12,5 +14,12 @@ 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
from .custom_error_top_level import CustomErrorTopLevel
from .custom_error import CustomError
from .solidity_import_placeholder import SolidityImportPlaceHolder

@ -2,15 +2,15 @@
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
from slither.core.cfg.scope import Scope
from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.utils.using_for import USING_FOR, merge_using_for
from slither.core.declarations.function import Function, FunctionType, FunctionLanguage
from slither.utils.erc import (
ERC20_signatures,
@ -21,27 +21,30 @@ from slither.utils.erc import (
ERC777_signatures,
ERC1155_signatures,
ERC2612_signatures,
ERC1363_signatures,
ERC4524_signatures,
ERC4626_signatures,
)
from slither.utils.tests_pattern import is_test_contract
# pylint: disable=too-many-lines,too-many-instance-attributes,import-outside-toplevel,too-many-nested-blocks
if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import (
Enum,
Event,
EventContract,
Modifier,
EnumContract,
StructureContract,
FunctionContract,
CustomErrorContract,
)
from slither.slithir.operations import HighLevelCall, LibraryCall
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
from slither.core.solidity_types import TypeAliasContract
LOGGER = logging.getLogger("Contract")
@ -52,7 +55,7 @@ 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
@ -66,37 +69,55 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._enums: Dict[str, "EnumContract"] = {}
self._structures: Dict[str, "StructureContract"] = {}
self._events: Dict[str, "Event"] = {}
self._events: Dict[str, "EventContract"] = {}
# map accessible variable from name -> variable
# do not contain private variables inherited from contract
self._variables: Dict[str, "StateVariable"] = {}
self._variables_ordered: List["StateVariable"] = []
# Reference id -> variable declaration (only available for compact AST)
self._state_variables_by_ref_id: Dict[int, "StateVariable"] = {}
self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "FunctionContract"] = {}
self._linearizedBaseContracts: List[int] = []
self._custom_errors: Dict[str, "CustomErrorContract"] = {}
self._type_aliases: Dict[str, "TypeAliasContract"] = {}
# The only str is "*"
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self._using_for: USING_FOR = {}
self._using_for_complete: Optional[USING_FOR] = None
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._is_fully_implemented: bool = False
self._is_abstract: 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
self._is_incorrectly_parsed: bool = False
self._available_functions_as_dict: Optional[Dict[str, "Function"]] = None
self._all_functions_called: Optional[List["InternalCallType"]] = None
self._all_functions_called: Optional[List["Function"]] = None
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
@ -110,17 +131,17 @@ 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
def id(self) -> int:
"""Unique id."""
assert self._id
assert self._id is not None
return self._id
@id.setter
def id(self, new_id):
def id(self, new_id: int) -> None:
"""Unique id."""
self._id = new_id
@ -133,7 +154,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
@ -141,9 +162,72 @@ 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
def is_library(self) -> bool:
return self._is_library
@is_library.setter
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:
"""
bool: True if the contract defines all functions.
In modern Solidity, virtual functions can lack an implementation.
Prior to Solidity 0.6.0, functions like the following would be not fully implemented:
```solidity
contract ImplicitAbstract{
function f() public;
}
```
"""
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
@property
def is_abstract(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the contract is abstract.
"""
return self._is_abstract
@is_abstract.setter
def is_abstract(self, is_abstract: bool):
self._is_abstract = is_abstract
# endregion
###################################################################################
###################################################################################
@ -213,28 +297,28 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def events(self) -> List["Event"]:
def events(self) -> List["EventContract"]:
"""
list(Event): List of the events
"""
return list(self._events.values())
@property
def events_inherited(self) -> List["Event"]:
def events_inherited(self) -> List["EventContract"]:
"""
list(Event): List of the inherited events
"""
return [e for e in self.events if e.contract != self]
@property
def events_declared(self) -> List["Event"]:
def events_declared(self) -> List["EventContract"]:
"""
list(Event): List of the events declared within the contract (not inherited)
"""
return [e for e in self.events if e.contract == self]
@property
def events_as_dict(self) -> Dict[str, "Event"]:
def events_as_dict(self) -> Dict[str, "EventContract"]:
return self._events
# endregion
@ -245,9 +329,23 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def using_for(self) -> Dict[Union[str, Type], List[Type]]:
def using_for(self) -> USING_FOR:
return self._using_for
@property
def using_for_complete(self) -> USING_FOR:
"""
USING_FOR: Dict of merged local using for directive with top level directive
"""
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
###################################################################################
###################################################################################
@ -280,17 +378,57 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def custom_errors_as_dict(self) -> Dict[str, "CustomErrorContract"]:
return self._custom_errors
# endregion
###################################################################################
###################################################################################
# region Custom Errors
###################################################################################
###################################################################################
@property
def type_aliases(self) -> List["TypeAliasContract"]:
"""
list(TypeAliasContract): List of the contract's custom errors
"""
return list(self._type_aliases.values())
@property
def type_aliases_inherited(self) -> List["TypeAliasContract"]:
"""
list(TypeAliasContract): List of the inherited custom errors
"""
return [s for s in self.type_aliases if s.contract != self]
@property
def type_aliases_declared(self) -> List["TypeAliasContract"]:
"""
list(TypeAliasContract): List of the custom errors declared within the contract (not inherited)
"""
return [s for s in self.type_aliases if s.contract == self]
@property
def type_aliases_as_dict(self) -> Dict[str, "TypeAliasContract"]:
return self._type_aliases
# endregion
###################################################################################
###################################################################################
# region Variables
###################################################################################
###################################################################################
@property
def state_variables_by_ref_id(self) -> Dict[int, "StateVariable"]:
"""
Returns the state variables by reference id (only available for compact AST).
"""
return self._state_variables_by_ref_id
@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)
@ -301,10 +439,47 @@ 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())
@property
def stored_state_variables(self) -> List["StateVariable"]:
"""
Returns state variables with storage locations, excluding private variables from inherited contracts.
Use stored_state_variables_ordered to access variables with storage locations in their declaration order.
This implementation filters out state variables if they are constant or immutable. It will be
updated to accommodate any new non-storage keywords that might replace 'constant' and 'immutable' in the future.
Returns:
List[StateVariable]: A list of state variables with storage locations.
"""
return [variable for variable in self.state_variables if variable.is_stored]
@property
def stored_state_variables_ordered(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables with storage locations by order of declaration.
This implementation filters out state variables if they are constant or immutable. It will be
updated to accommodate any new non-storage keywords that might replace 'constant' and 'immutable' in the future.
Returns:
List[StateVariable]: A list of state variables with storage locations ordered by declaration.
"""
return [variable for variable in self.state_variables_ordered if variable.is_stored]
@property
def state_variables_entry_points(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables that are public.
"""
return [var for var in self._variables.values() if var.visibility == "public"]
@property
def state_variables_ordered(self) -> List["StateVariable"]:
"""
@ -312,7 +487,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
@ -338,6 +513,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
###################################################################################
###################################################################################
@ -374,7 +576,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)
"""
@ -443,17 +645,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
@ -497,7 +699,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
@ -541,6 +743,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"],
@ -607,7 +827,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
@ -618,7 +838,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
###################################################################################
@ -639,6 +859,21 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return [f for f in self.functions if f.is_writing(variable)]
def get_function_from_full_name(self, full_name: str) -> Optional["Function"]:
"""
Return a function from a full name
The full name differs from the solidity's signature are the type are conserved
For example contract type are kept, structure are not unrolled, etc
Args:
full_name (str): signature of the function (without return statement)
Returns:
Function
"""
return next(
(f for f in self.functions if f.full_name == full_name and not f.is_shadowed),
None,
)
def get_function_from_signature(self, function_signature: str) -> Optional["Function"]:
"""
Return a function from a signature
@ -648,7 +883,11 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Function
"""
return next(
(f for f in self.functions if f.full_name == function_signature and not f.is_shadowed),
(
f
for f in self.functions
if f.solidity_signature == function_signature and not f.is_shadowed
),
None,
)
@ -665,7 +904,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def get_function_from_canonical_name(self, canonical_name: str) -> Optional["Function"]:
"""
Return a function from a a canonical name (contract.signature())
Return a function from a canonical name (contract.signature())
Args:
canonical_name (str): canonical name of the function (without return statement)
Returns:
@ -701,25 +940,27 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Returns:
StateVariable
"""
return next((v for v in self.state_variables if v.name == canonical_name), None)
return next((v for v in self.state_variables if v.canonical_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)
@ -753,7 +994,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:
@ -765,16 +1006,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def get_functions_overridden_by(self, function: "Function") -> List["Function"]:
"""
Return the list of functions overriden by the function
Return the list of functions overridden by the function
Args:
(core.Function)
Returns:
list(core.Function)
"""
candidatess = [c.functions_declared for c in self.inheritance]
candidates = [candidate for sublist in candidatess for candidate in sublist]
return [f for f in candidates if f.full_name == function.full_name]
return function.overrides
# endregion
###################################################################################
@ -784,15 +1023,21 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def all_functions_called(self) -> List["InternalCallType"]:
def all_functions_called(self) -> List["Function"]:
"""
list(Function): List of functions reachable from the contract
Includes super, and private/internal functions not shadowed
"""
from slither.slithir.operations import Operation
if self._all_functions_called is None:
all_functions = [f for f in self.functions + self.modifiers if not f.is_shadowed] # type: ignore
all_callss = [f.all_internal_calls() for f in all_functions] + [list(all_functions)]
all_calls = [item for sublist in all_callss for item in sublist]
all_calls = [
item.function if isinstance(item, Operation) else item
for sublist in all_callss
for item in sublist
]
all_calls = list(set(all_calls))
all_constructors = [c.constructor for c in self.inheritance if c.constructor]
@ -830,18 +1075,18 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return list(set(all_state_variables_read))
@property
def all_library_calls(self) -> List["LibraryCallType"]:
def all_library_calls(self) -> List["LibraryCall"]:
"""
list((Contract, Function): List all of the libraries func called
list(LibraryCall): List all of the libraries func called
"""
all_high_level_callss = [f.all_library_calls() for f in self.functions + self.modifiers] # type: ignore
all_high_level_calls = [item for sublist in all_high_level_callss for item in sublist]
return list(set(all_high_level_calls))
@property
def all_high_level_calls(self) -> List["HighLevelCallType"]:
def all_high_level_calls(self) -> List[Tuple["Contract", "HighLevelCall"]]:
"""
list((Contract, Function|Variable)): List all of the external high level calls
list(Tuple("Contract", "HighLevelCall")): List all of the external high level calls
"""
all_high_level_callss = [f.all_high_level_calls() for f in self.functions + self.modifiers] # type: ignore
all_high_level_calls = [item for sublist in all_high_level_callss for item in sublist]
@ -854,7 +1099,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)
@ -903,6 +1150,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
("ERC721", self.is_erc721),
("ERC777", self.is_erc777),
("ERC2612", self.is_erc2612),
("ERC1363", self.is_erc1363),
("ERC4626", self.is_erc4626),
]
@ -998,6 +1246,26 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
full_names = self.functions_signatures
return all(s in full_names for s in ERC2612_signatures)
def is_erc1363(self) -> bool:
"""
Check if the contract is an erc1363
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc1363
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC1363_signatures)
def is_erc4524(self) -> bool:
"""
Check if the contract is an erc4524
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc4524
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC4524_signatures)
@property
def is_token(self) -> bool:
"""
@ -1061,7 +1329,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_from_dependency(self) -> bool:
return self.compilation_unit.core.crytic_compile.is_dependency(
self.source_mapping["filename_absolute"]
self.source_mapping.filename.absolute
)
# endregion
@ -1079,14 +1347,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
if self.compilation_unit.core.crytic_compile.platform == PlatformType.TRUFFLE:
if self.name == "Migrations":
paths = Path(self.source_mapping["filename_absolute"]).parts
paths = Path(self.source_mapping.filename.absolute).parts
if len(paths) >= 2:
return paths[-2] == "contracts" and paths[-1] == "migrations.sol"
return False
@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
###################################################################################
@ -1095,8 +1363,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
@ -1110,8 +1378,6 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_upgradeable(self) -> bool:
if self._is_upgradeable is None:
self._is_upgradeable = False
if self.is_upgradeable_proxy:
return False
initializable = self.file_scope.get_contract_from_name("Initializable")
if initializable:
if initializable in self.inheritance:
@ -1130,6 +1396,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
@ -1155,6 +1425,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
###################################################################################
###################################################################################
@ -1171,10 +1453,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:
@ -1183,8 +1465,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
@ -1215,8 +1497,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
@ -1243,7 +1525,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,
@ -1275,7 +1557,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
@ -1297,22 +1579,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
@ -1322,20 +1605,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,21 +1,23 @@
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] = []
self._compilation_unit = compilation_unit
self._solidity_signature: Optional[str] = None
self._full_name: Optional[str] = None
self._pattern = "error"
@property
def name(self) -> str:
@ -29,7 +31,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
@ -41,11 +43,8 @@ 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)
@ -62,7 +61,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:
"""
@ -71,12 +70,25 @@ class CustomError(SourceMapping):
Returns:
"""
parameters = [self._convert_type_for_solidity_signature(x.type) for x in self.parameters]
self._solidity_signature = self.name + "(" + ",".join(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) + ")"
@property
def full_name(self) -> Optional[str]:
"""
Return the error signature without
converting contract into address
:return: the error signature
"""
if self._full_name is None:
raise ValueError("Custom Error not yet built")
return self._full_name
# endregion
###################################################################################
###################################################################################
def __str__(self):
def __str__(self) -> str:
return "revert " + self.solidity_signature

@ -1,12 +1,22 @@
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:
:return:
"""
return self.contract == contract
@property
def canonical_name(self) -> str:
return self.contract.name + "." + self.full_name

@ -9,6 +9,10 @@ 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
@property
def canonical_name(self) -> str:
return self.full_name

@ -4,11 +4,14 @@ 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
self._values = values
self._min = 0
# The max value of an Enum is the index of the last element
self._max = len(values) - 1
@property
def canonical_name(self) -> str:
@ -22,5 +25,13 @@ class Enum(SourceMapping):
def values(self) -> List[str]:
return self._values
def __str__(self):
@property
def min(self) -> int:
return self._min
@property
def max(self) -> int:
return self._max
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,15 +1,11 @@
from typing import List, Tuple, TYPE_CHECKING
from typing import List, Tuple
from slither.core.children.child_contract import ChildContract
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.event_variable import EventVariable
if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(ChildContract, SourceMapping):
def __init__(self):
class Event(SourceMapping):
def __init__(self) -> None:
super().__init__()
self._name = None
self._elems: List[EventVariable] = []
@ -39,25 +35,9 @@ class Event(ChildContract, SourceMapping):
name, parameters = self.signature
return name + "(" + ",".join(parameters) + ")"
@property
def canonical_name(self) -> str:
"""Return the function signature as a str
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + self.full_name
@property
def elems(self) -> List["EventVariable"]:
return self._elems
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract
def __str__(self):
def __str__(self) -> str:
return self.name

@ -0,0 +1,25 @@
from typing import TYPE_CHECKING
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Event
if TYPE_CHECKING:
from slither.core.declarations import Contract
class EventContract(Event, ContractLevel):
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract
@property
def canonical_name(self) -> str:
"""Return the function signature as a str
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + "." + self.full_name

@ -0,0 +1,13 @@
from typing import TYPE_CHECKING
from slither.core.declarations import Event
from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class EventTopLevel(Event, TopLevel):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self.file_scope: "FileScope" = scope

@ -2,11 +2,11 @@
Function module
"""
import logging
from abc import ABCMeta, abstractmethod
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 (
@ -20,29 +20,31 @@ from slither.core.expressions import (
MemberAccess,
UnaryOperation,
)
from slither.core.solidity_types import UserDefinedType
from slither.core.solidity_types.type import Type
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.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:
from slither.utils.type_helpers import (
InternalCallType,
LowLevelCallType,
HighLevelCallType,
LibraryCallType,
)
from slither.core.declarations import Contract
from slither.core.declarations import Contract, FunctionContract
from slither.core.cfg.node import Node, NodeType
from slither.core.variables.variable import Variable
from slither.slithir.variables.variable import SlithIRVariable
from slither.slithir.variables import LocalIRVariable
from slither.core.expressions.expression import Expression
from slither.slithir.operations import Operation
from slither.slithir.operations import (
HighLevelCall,
InternalCall,
LibraryCall,
LowLevelCall,
SolidityCall,
Operation,
)
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
@ -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
@ -93,14 +95,11 @@ class FunctionType(Enum):
def _filter_state_variables_written(expressions: List["Expression"]):
ret = []
for expression in expressions:
if isinstance(expression, Identifier):
ret.append(expression)
if isinstance(expression, UnaryOperation):
if isinstance(expression, (Identifier, UnaryOperation, MemberAccess)):
ret.append(expression.expression)
if isinstance(expression, MemberAccess):
ret.append(expression.expression)
if isinstance(expression, IndexAccess):
elif isinstance(expression, IndexAccess):
ret.append(expression.expression_left)
return ret
@ -116,7 +115,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
@ -124,6 +123,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._pure: bool = False
self._payable: bool = False
self._visibility: Optional[str] = None
self._virtual: bool = False
self._overrides: List["FunctionContract"] = []
self._overridden_by: List["FunctionContract"] = []
self._is_implemented: Optional[bool] = None
self._is_empty: Optional[bool] = None
@ -135,6 +137,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._parameters: List["LocalVariable"] = []
self._parameters_ssa: List["LocalIRVariable"] = []
self._parameters_src: SourceMapping = SourceMapping()
# This is used for vyper calls with default arguments
self._default_args_as_expressions: List["Expression"] = []
self._returns: List["LocalVariable"] = []
self._returns_ssa: List["LocalIRVariable"] = []
self._returns_src: SourceMapping = SourceMapping()
@ -146,11 +150,11 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._vars_read_or_written: List["Variable"] = []
self._solidity_vars_read: List["SolidityVariable"] = []
self._state_vars_written: List["StateVariable"] = []
self._internal_calls: List["InternalCallType"] = []
self._solidity_calls: List["SolidityFunction"] = []
self._low_level_calls: List["LowLevelCallType"] = []
self._high_level_calls: List["HighLevelCallType"] = []
self._library_calls: List["LibraryCallType"] = []
self._internal_calls: List["InternalCall"] = []
self._solidity_calls: List["SolidityCall"] = []
self._low_level_calls: List["LowLevelCall"] = []
self._high_level_calls: List[Tuple["Contract", "HighLevelCall"]] = []
self._library_calls: List["LibraryCall"] = []
self._external_calls_as_expressions: List["Expression"] = []
self._expression_vars_read: List["Expression"] = []
self._expression_vars_written: List["Expression"] = []
@ -166,11 +170,13 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._all_expressions: Optional[List["Expression"]] = None
self._all_slithir_operations: Optional[List["Operation"]] = None
self._all_internals_calls: Optional[List["InternalCallType"]] = None
self._all_high_level_calls: Optional[List["HighLevelCallType"]] = None
self._all_library_calls: Optional[List["LibraryCallType"]] = None
self._all_low_level_calls: Optional[List["LowLevelCallType"]] = None
self._all_solidity_calls: Optional[List["SolidityFunction"]] = None
self._all_internals_calls: Optional[List["InternalCall"]] = None
self._all_high_level_calls: Optional[List[Tuple["Contract", "HighLevelCall"]]] = None
self._all_library_calls: Optional[List["LibraryCall"]] = None
self._all_low_level_calls: Optional[List["LowLevelCall"]] = None
self._all_solidity_calls: Optional[List["SolidityCall"]] = None
self._all_variables_read: Optional[List["Variable"]] = None
self._all_variables_written: Optional[List["Variable"]] = None
self._all_state_variables_read: Optional[List["StateVariable"]] = None
self._all_solidity_variables_read: Optional[List["SolidityVariable"]] = None
self._all_state_variables_written: Optional[List["StateVariable"]] = None
@ -189,7 +195,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 +221,15 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
# Assume we are analyzing Solidty by default
self.function_language: FunctionLanguage = FunctionLanguage.Solidity
self.function_language: FunctionLanguage = (
FunctionLanguage.Solidity if compilation_unit.is_solidity else FunctionLanguage.Vyper
)
self._id: Optional[str] = None
# To be improved with a parsing of the documentation
self.has_documentation: bool = False
###################################################################################
###################################################################################
# region General properties
@ -232,7 +243,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
if self._name == "" and self._function_type == FunctionType.CONSTRUCTOR:
return "constructor"
if self._function_type == FunctionType.FALLBACK:
if self._name == "" and self._function_type == FunctionType.FALLBACK:
return "fallback"
if self._function_type == FunctionType.RECEIVE:
return "receive"
@ -265,6 +276,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
str: func_name(type1,type2)
Return the function signature without the return values
The difference between this function and solidity_function is that full_name does not translate the underlying
type (ex: structure, contract to address, ...)
"""
if self._full_name is None:
name, parameters, _ = self.signature
@ -289,7 +302,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.
@ -340,8 +353,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
@property
def id(self) -> Optional[str]:
"""
Return the ID of the funciton. For Solidity with compact-AST the ID is the reference ID
For other, the ID is None
Return the reference ID of the function, if available.
:return:
:rtype:
@ -364,7 +376,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
@ -430,6 +442,49 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def payable(self, p: bool):
self._payable = p
# endregion
###################################################################################
###################################################################################
# region Virtual
###################################################################################
###################################################################################
@property
def is_virtual(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the function is virtual
"""
return self._virtual
@is_virtual.setter
def is_virtual(self, v: bool):
self._virtual = v
@property
def is_override(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the function overrides a base function
"""
return len(self._overrides) > 0
@property
def overridden_by(self) -> List["FunctionContract"]:
"""
List["FunctionContract"]: List of functions in child contracts that override this function
This may include distinct instances of the same function due to inheritance
"""
return self._overridden_by
@property
def overrides(self) -> List["FunctionContract"]:
"""
List["FunctionContract"]: List of functions in parent contracts that this function overrides
This may include distinct instances of the same function due to inheritance
"""
return self._overrides
# endregion
###################################################################################
###################################################################################
@ -449,7 +504,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
@ -538,7 +593,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._nodes = nodes
@property
def entry_point(self) -> "Node":
def entry_point(self) -> Optional["Node"]:
"""
Node: Entry point of the function
"""
@ -548,7 +603,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)
@ -592,7 +647,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
@ -602,7 +657,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:
@ -645,7 +700,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
@ -655,7 +710,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
@ -674,7 +729,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
@ -708,7 +763,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
@ -803,43 +858,42 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
@property
def internal_calls(self) -> List["InternalCallType"]:
def internal_calls(self) -> List["InternalCall"]:
"""
list(Function or SolidityFunction): List of function calls (that does not create a transaction)
list(InternalCall): List of IR operations for internal calls
"""
return list(self._internal_calls)
@property
def solidity_calls(self) -> List[SolidityFunction]:
def solidity_calls(self) -> List["SolidityCall"]:
"""
list(SolidityFunction): List of Soldity calls
list(SolidityCall): List of IR operations for Solidity calls
"""
return list(self._solidity_calls)
@property
def high_level_calls(self) -> List["HighLevelCallType"]:
def high_level_calls(self) -> List[Tuple["Contract", "HighLevelCall"]]:
"""
list((Contract, Function|Variable)):
List of high level calls (external calls).
list(Tuple(Contract, "HighLevelCall")): List of call target contract and IR of the high level call
A variable is called in case of call to a public state variable
Include library calls
"""
return list(self._high_level_calls)
@property
def library_calls(self) -> List["LibraryCallType"]:
def library_calls(self) -> List["LibraryCall"]:
"""
list((Contract, Function)):
list(LibraryCall): List of IR operations for library calls
"""
return list(self._library_calls)
@property
def low_level_calls(self) -> List["LowLevelCallType"]:
def low_level_calls(self) -> List["LowLevelCall"]:
"""
list((Variable|SolidityVariable, str)): List of low_level call
list(LowLevelCall): List of IR operations for low level calls
A low level call is defined by
- the variable called
- the name of the function (call/delegatecall/codecall)
- the name of the function (call/delegatecall/callcode)
"""
return list(self._low_level_calls)
@ -952,24 +1006,21 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
@staticmethod
def _convert_type_for_solidity_signature(t: Type):
from slither.core.declarations import Contract
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
return "address"
return str(t)
@property
def solidity_signature(self) -> str:
"""
Return a signature following the Solidity Standard
Contract and converted into address
It might still keep internal types (ex: structure name) for internal functions.
The reason is that internal functions allows recursive structure definition, which
can't be converted following the Solidity stand ard
:return: the solidity signature
"""
if self._solidity_signature is None:
parameters = [
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
convert_type_for_solidity_signature_to_string(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature
@ -980,14 +1031,15 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
(str, list(str), list(str)): Function signature as
(name, list parameters type, list return values type)
"""
if self._signature is None:
signature = (
self.name,
[str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns],
)
self._signature = signature
return self._signature
# FIXME memoizing this function is not working properly for vyper
# if self._signature is None:
return (
self.name,
[str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns],
)
# self._signature = signature
# return self._signature
@property
def signature_str(self) -> str:
@ -1030,10 +1082,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)
@ -1044,14 +1117,18 @@ 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 = [
c for c in self.internal_calls if isinstance(c, Function) and c not in explored
ir.function
for ir in self.internal_calls
if isinstance(ir.function, Function) and ir.function not in explored
]
to_explore += [
c for (_, c) in self.library_calls if isinstance(c, Function) and c not in explored
ir.function
for ir in self.library_calls
if isinstance(ir.function, Function) and ir.function not in explored
]
to_explore += [m for m in self.modifiers if m not in explored]
@ -1065,19 +1142,35 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
values += f_new_values(f)
to_explore += [
c
for c in f.internal_calls
if isinstance(c, Function) and c not in explored and c not in to_explore
ir.function
for ir in f.internal_calls
if isinstance(ir.function, Function)
and ir.function not in explored
and ir.function not in to_explore
]
to_explore += [
c
for (_, c) in f.library_calls
if isinstance(c, Function) and c not in explored and c not in to_explore
ir.function
for ir in f.library_calls
if isinstance(ir.function, Function)
and ir.function not in explored
and ir.function not in to_explore
]
to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore]
return list(set(values))
def all_variables_read(self) -> List["Variable"]:
"""recursive version of variables_read"""
if self._all_variables_read is None:
self._all_variables_read = self._explore_functions(lambda x: x.variables_read)
return self._all_variables_read
def all_variables_written(self) -> List["Variable"]:
"""recursive version of variables_written"""
if self._all_variables_written is None:
self._all_variables_written = self._explore_functions(lambda x: x.variables_written)
return self._all_variables_written
def all_state_variables_read(self) -> List["StateVariable"]:
"""recursive version of variables_read"""
if self._all_state_variables_read is None:
@ -1125,31 +1218,31 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
)
return self._all_state_variables_written
def all_internal_calls(self) -> List["InternalCallType"]:
def all_internal_calls(self) -> List["InternalCall"]:
"""recursive version of internal_calls"""
if self._all_internals_calls is None:
self._all_internals_calls = self._explore_functions(lambda x: x.internal_calls)
return self._all_internals_calls
def all_low_level_calls(self) -> List["LowLevelCallType"]:
def all_low_level_calls(self) -> List["LowLevelCall"]:
"""recursive version of low_level calls"""
if self._all_low_level_calls is None:
self._all_low_level_calls = self._explore_functions(lambda x: x.low_level_calls)
return self._all_low_level_calls
def all_high_level_calls(self) -> List["HighLevelCallType"]:
def all_high_level_calls(self) -> List[Tuple["Contract", "HighLevelCall"]]:
"""recursive version of high_level calls"""
if self._all_high_level_calls is None:
self._all_high_level_calls = self._explore_functions(lambda x: x.high_level_calls)
return self._all_high_level_calls
def all_library_calls(self) -> List["LibraryCallType"]:
def all_library_calls(self) -> List["LibraryCall"]:
"""recursive version of library calls"""
if self._all_library_calls is None:
self._all_library_calls = self._explore_functions(lambda x: x.library_calls)
return self._all_library_calls
def all_solidity_calls(self) -> List[SolidityFunction]:
def all_solidity_calls(self) -> List["SolidityCall"]:
"""recursive version of solidity calls"""
if self._all_solidity_calls is None:
self._all_solidity_calls = self._explore_functions(lambda x: x.solidity_calls)
@ -1194,11 +1287,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
@ -1234,7 +1329,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]
@ -1343,7 +1440,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
@ -1354,7 +1451,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:
@ -1461,6 +1558,33 @@ 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
"""
reentrancy_modifier = "nonReentrant"
if self.function_language == FunctionLanguage.Vyper:
reentrancy_modifier = "nonreentrant(lock)"
# TODO: compare with hash of known nonReentrant modifier instead of the name
if reentrancy_modifier 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(
(reentrancy_modifier in [m.name for m in f.modifiers] for f in all_entry_points)
)
# endregion
###################################################################################
###################################################################################
@ -1468,13 +1592,13 @@ 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]
write_var = [item for sublist in write_var for item in sublist]
write_var = list(set(write_var))
# Remove dupplicate if they share the same string representation
# Remove duplicate if they share the same string representation
write_var = [
next(obj)
for i, obj in groupby(sorted(write_var, key=lambda x: str(x)), lambda x: str(x))
@ -1485,7 +1609,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
write_var = [x for x in write_var if x]
write_var = [item for sublist in write_var for item in sublist]
write_var = list(set(write_var))
# Remove dupplicate if they share the same string representation
# Remove duplicate if they share the same string representation
write_var = [
next(obj)
for i, obj in groupby(sorted(write_var, key=lambda x: str(x)), lambda x: str(x))
@ -1495,7 +1619,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
read_var = [x.variables_read_as_expression for x in self.nodes]
read_var = [x for x in read_var if x]
read_var = [item for sublist in read_var for item in sublist]
# Remove dupplicate if they share the same string representation
# Remove duplicate if they share the same string representation
read_var = [
next(obj)
for i, obj in groupby(sorted(read_var, key=lambda x: str(x)), lambda x: str(x))
@ -1505,7 +1629,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
read_var = [x.variables_read for x in self.nodes]
read_var = [x for x in read_var if x]
read_var = [item for sublist in read_var for item in sublist]
# Remove dupplicate if they share the same string representation
# Remove duplicate if they share the same string representation
read_var = [
next(obj)
for i, obj in groupby(sorted(read_var, key=lambda x: str(x)), lambda x: str(x))
@ -1526,7 +1650,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]
@ -1537,7 +1661,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
internal_calls = [item for sublist in internal_calls for item in sublist]
self._internal_calls = list(set(internal_calls))
self._solidity_calls = [c for c in internal_calls if isinstance(c, SolidityFunction)]
self._solidity_calls = [
ir for ir in internal_calls if isinstance(ir.function, SolidityFunction)
]
low_level_calls = [x.low_level_calls for x in self.nodes]
low_level_calls = [x for x in low_level_calls if x]
@ -1658,7 +1784,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:
@ -1667,7 +1793,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
@ -1701,7 +1831,8 @@ 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()
@ -1712,7 +1843,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()
@ -1723,7 +1854,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:
"""
@ -41,7 +65,10 @@ class FunctionContract(Function, ChildContract, ChildInheritance):
@property
def file_scope(self) -> "FileScope":
return self.contract.file_scope
# This is the contract declarer's file scope because inherited functions have access
# to the file scope which their declared in. This scope may contain references not
# available in the child contract's scope. See inherited_function_scope.sol for an example.
return self.contract_declarer.file_scope
# endregion
###################################################################################
@ -71,7 +98,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 +114,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 +124,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,25 +1,41 @@
"""
Function module
"""
from typing import List, Tuple, TYPE_CHECKING
from typing import Dict, List, Tuple, TYPE_CHECKING, Optional
from slither.core.declarations import Function
from slither.core.declarations.top_level import TopLevel
from slither.utils.using_for import USING_FOR, merge_using_for
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
self._using_for_complete: Optional[USING_FOR] = None
@property
def file_scope(self) -> "FileScope":
return self._scope
@property
def using_for_complete(self) -> USING_FOR:
"""
USING_FOR: Dict of top level directive
"""
if self._using_for_complete is None:
result = {}
for uftl in self.file_scope.using_for_directives:
result = merge_using_for(result, uftl.using_for)
self._using_for_complete = result
return self._using_for_complete
@property
def canonical_name(self) -> str:
"""
@ -78,7 +94,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 (

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

Loading…
Cancel
Save