Merge branch 'dev' into pull/1880/head

pull/2326/head
alpharush 9 months ago
commit 80efe77dfc
  1. 6
      .github/actions/upload-coverage/action.yml
  2. 8
      .github/dependabot.yml
  3. 4
      .github/workflows/black.yml
  4. 13
      .github/workflows/ci.yml
  5. 10
      .github/workflows/docker.yml
  6. 10
      .github/workflows/docs.yml
  7. 4
      .github/workflows/doctor.yml
  8. 11
      .github/workflows/linter.yml
  9. 32
      .github/workflows/matchers/pylint.json
  10. 22
      .github/workflows/matchers/yamllint.json
  11. 4
      .github/workflows/pip-audit.yml
  12. 53
      .github/workflows/publish.yml
  13. 10
      .github/workflows/pylint.yml
  14. 40
      .github/workflows/test.yml
  15. 64
      CITATION.cff
  16. 11
      CONTRIBUTING.md
  17. 2
      Dockerfile
  18. 295
      README.md
  19. 3
      examples/scripts/possible_paths.py
  20. 2
      scripts/ci_test_printers.sh
  21. 9
      setup.py
  22. 104
      slither/__main__.py
  23. 2
      slither/analyses/evm/convert.py
  24. 10
      slither/core/cfg/node.py
  25. 6
      slither/core/cfg/scope.py
  26. 44
      slither/core/compilation_unit.py
  27. 5
      slither/core/declarations/__init__.py
  28. 58
      slither/core/declarations/contract.py
  29. 4
      slither/core/declarations/custom_error_contract.py
  30. 4
      slither/core/declarations/custom_error_top_level.py
  31. 24
      slither/core/declarations/event.py
  32. 25
      slither/core/declarations/event_contract.py
  33. 13
      slither/core/declarations/event_top_level.py
  34. 30
      slither/core/declarations/function.py
  35. 37
      slither/core/declarations/solidity_variables.py
  36. 13
      slither/core/dominators/utils.py
  37. 1
      slither/core/expressions/__init__.py
  38. 2
      slither/core/expressions/binary_operation.py
  39. 37
      slither/core/expressions/call_expression.py
  40. 1
      slither/core/expressions/identifier.py
  41. 6
      slither/core/expressions/self_identifier.py
  42. 2
      slither/core/expressions/unary_operation.py
  43. 13
      slither/core/scope/scope.py
  44. 26
      slither/core/slither_core.py
  45. 5
      slither/core/solidity_types/type_alias.py
  46. 6
      slither/core/variables/__init__.py
  47. 4
      slither/core/variables/local_variable.py
  48. 3
      slither/core/variables/variable.py
  49. 24
      slither/detectors/abstract_detector.py
  50. 6
      slither/detectors/all_detectors.py
  51. 93
      slither/detectors/assembly/incorrect_return.py
  52. 68
      slither/detectors/assembly/return_instead_of_leave.py
  53. 2
      slither/detectors/attributes/incorrect_solc.py
  54. 28
      slither/detectors/attributes/locked_ether.py
  55. 13
      slither/detectors/compiler_bugs/array_by_reference.py
  56. 2
      slither/detectors/functions/suicidal.py
  57. 21
      slither/detectors/naming_convention/naming_convention.py
  58. 225
      slither/detectors/operations/cache_array_length.py
  59. 93
      slither/detectors/operations/incorrect_exp.py
  60. 62
      slither/detectors/operations/unchecked_low_level_return_values.py
  61. 6
      slither/detectors/operations/unused_return_values.py
  62. 2
      slither/detectors/statements/deprecated_calls.py
  63. 10
      slither/detectors/statements/divide_before_multiply.py
  64. 18
      slither/detectors/statements/mapping_deletion.py
  65. 2
      slither/detectors/statements/msg_value_in_loop.py
  66. 123
      slither/detectors/statements/return_bomb.py
  67. 69
      slither/detectors/statements/tautological_compare.py
  68. 2
      slither/detectors/variables/predeclaration_usage_local.py
  69. 30
      slither/detectors/variables/similar_variables.py
  70. 2
      slither/detectors/variables/unchanged_state_variables.py
  71. 4
      slither/detectors/variables/unused_state_variables.py
  72. 4
      slither/printers/all_printers.py
  73. 128
      slither/printers/guidance/echidna.py
  74. 58
      slither/printers/summary/ck.py
  75. 49
      slither/printers/summary/halstead.py
  76. 85
      slither/printers/summary/human_summary.py
  77. 35
      slither/printers/summary/loc.py
  78. 32
      slither/printers/summary/martin.py
  79. 37
      slither/slither.py
  80. 149
      slither/slithir/convert.py
  81. 18
      slither/slithir/operations/call.py
  82. 9
      slither/slithir/operations/high_level_call.py
  83. 7
      slither/slithir/operations/index.py
  84. 10
      slither/slithir/operations/internal_call.py
  85. 2
      slither/slithir/operations/new_array.py
  86. 13
      slither/slithir/operations/new_contract.py
  87. 11
      slither/slithir/operations/new_structure.py
  88. 5
      slither/slithir/operations/type_conversion.py
  89. 5
      slither/slithir/operations/unary.py
  90. 19
      slither/slithir/tmp_operations/tmp_call.py
  91. 15
      slither/slithir/utils/ssa.py
  92. 2
      slither/slithir/variables/constant.py
  93. 3
      slither/slithir/variables/reference.py
  94. 20
      slither/solc_parsing/declarations/contract.py
  95. 9
      slither/solc_parsing/declarations/custom_error.py
  96. 19
      slither/solc_parsing/declarations/event.py
  97. 261
      slither/solc_parsing/declarations/function.py
  98. 22
      slither/solc_parsing/declarations/using_for_top_level.py
  99. 74
      slither/solc_parsing/expressions/expression_parsing.py
  100. 99
      slither/solc_parsing/expressions/find_variable.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -15,15 +15,15 @@ runs:
# 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
echo "COVERAGE_UUID=${COVERAGE_UUID}" >> "$GITHUB_OUTPUT"
if [ -f .coverage ]; then
mv .coverage .coverage.${COVERAGE_UUID}
fi
id: coverage-uuid
shell: bash
- uses: actions/upload-artifact@v3.1.0
- uses: actions/upload-artifact@v4
with:
name: coverage-data
name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }}
path: |
.coverage.*
*.lcov

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

@ -26,13 +26,13 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v3
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.8
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: 3.8

@ -26,6 +26,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }}
type: ["cli",
"dapp",
"data_dependency",
@ -52,11 +53,11 @@ jobs:
- os: windows-2022
type: truffle
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: ${{ matrix.python }}
- name: Install dependencies
run: |
pip install ".[test]"
@ -66,11 +67,11 @@ jobs:
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v20
uses: cachix/install-nix-action@v25
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v10
uses: cachix/cachix-action@v14
with:
name: dapp

@ -17,13 +17,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
id: buildx
with:
install: true
@ -40,14 +40,14 @@ jobs:
type=edge
- name: GitHub Container Registry Login
uses: docker/login-action@v2
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@v3
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7
target: final

@ -28,19 +28,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v3
- uses: actions/setup-python@v4
uses: actions/configure-pages@v4
- 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@v1
uses: actions/upload-pages-artifact@v3
with:
# Upload the doc
path: './html/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v4

@ -29,10 +29,10 @@ jobs:
- os: windows-2022
python: 3.8
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}

@ -9,8 +9,6 @@ defaults:
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
schedule:
# run CI every day even if no PRs/merges occur
@ -27,13 +25,13 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v3
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.8
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: 3.8
@ -42,6 +40,10 @@ jobs:
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: super-linter/super-linter/slim@v4.9.2
if: always()
@ -55,7 +57,6 @@ jobs:
VALIDATE_PYTHON_PYLINT: false
VALIDATE_PYTHON_BLACK: false
VALIDATE_PYTHON_ISORT: false
# Always false
VALIDATE_JSON: false
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_PYTHON_FLAKE8: false

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

@ -18,10 +18,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.10"

@ -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.8.11
- name: sign
uses: sigstore/gh-action-sigstore-python@v2.1.1
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true

@ -9,6 +9,8 @@ defaults:
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -21,13 +23,13 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v3
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.8
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: 3.8
@ -36,6 +38,10 @@ jobs:
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: super-linter/super-linter/slim@v4.9.2
if: always()

@ -25,12 +25,13 @@ jobs:
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["unit", "integration", "tool"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }}
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v4
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: ${{ matrix.python }}
cache: "pip"
cache-dependency-path: setup.py
@ -39,7 +40,7 @@ jobs:
pip install ".[test]"
- name: Setup node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '16'
cache: 'npm'
@ -56,7 +57,23 @@ jobs:
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 }}
@ -74,7 +91,7 @@ jobs:
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' }}
if: ${{ matrix.os == 'ubuntu-latest' && matrix.python == '3.8' }}
coverage:
needs:
@ -83,18 +100,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.8
- run: pip install coverage[toml]
- name: download coverage data
uses: actions/download-artifact@v3.0.2
uses: actions/download-artifact@v4
with:
name: coverage-data
pattern: coverage-data-*
merge-multiple: true
- name: combine coverage data
id: combinecoverage

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

@ -81,7 +81,7 @@ For each new detector, at least one regression tests must be present.
1. Create a folder in `tests/e2e/detectors/test_data` with the detector's argument name.
2. Create a test contract in `tests/e2e/detectors/test_data/<detector_name>/`.
3. Update `ALL_TEST` in `tests/e2e/detectors/test_detectors.py`
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.
@ -96,9 +96,10 @@ For each new detector, at least one regression tests must be present.
#### Adding parsing tests
1. Create a test in `tests/e2e/solc_parsing/`
2. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git.
3. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git.
4. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked.
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
>
@ -106,7 +107,7 @@ For each new detector, at least one regression tests must be present.
> - 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`.
> - The IDs of tests can be inspected using `pytest tests/e2e/solc_parsing/test_ast_parsing.py --collect-only`.
### Synchronization with crytic-compile

@ -47,6 +47,6 @@ ENV PATH="/home/slither/.local/bin:${PATH}"
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 install 0.4.25 && solc-select use 0.4.25
RUN solc-select use latest --always-install
CMD /bin/bash

@ -1,22 +1,38 @@
# Slither, the Solidity source analyzer
<img src="./logo.png" alt="Logo" width="500"/>
# [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)
[![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 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)
- [Detectors](#detectors)
- [Printers](#printers)
- [Tools](#tools)
- [API Documentation](#api-documentation)
- [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications)
![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
@ -30,36 +46,41 @@ Slither is a Solidity static analysis framework written in Python3. It runs a su
* 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
Run Slither on a Hardhat/Foundry/Dapp/Brownie application:
```bash
```console
slither .
```
This is the preferred option if your project has dependencies as Slither relies on the underlying compilation framework to compile source code.
However, you can run Slither on a single file that does not import dependencies:
```bash
```console
slither tests/uninitialized.sol
```
## How to install
Slither requires Python 3.8+.
> **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
```
### Using Git
```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.
@ -79,118 +100,130 @@ docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
```
### Integration
- For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action).
- To generate a Markdown report, use `slither [target] --checklist`.
- To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`)
## Detectors
* 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`)
## 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 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High
5 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
6 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
7 | `protected-vars` | [Detected unprotected variables](https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables) | High | High
8 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
9 | `rtlo` | [Right-To-Left-Override control character is used](https://github.com/crytic/slither/wiki/Detector-Documentation#right-to-left-override-character) | High | High
10 | `shadowing-state` | [State variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing) | High | High
11 | `suicidal` | [Functions allowing anyone to destruct the contract](https://github.com/crytic/slither/wiki/Detector-Documentation#suicidal) | High | High
12 | `uninitialized-state` | [Uninitialized state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-state-variables) | High | High
13 | `uninitialized-storage` | [Uninitialized storage variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-storage-variables) | High | High
14 | `unprotected-upgrade` | [Unprotected upgradeable contract](https://github.com/crytic/slither/wiki/Detector-Documentation#unprotected-upgradeable-contract) | High | High
15 | `codex` | [Use Codex to find vulnerabilities.](https://github.com/crytic/slither/wiki/Detector-Documentation#codex) | High | Low
16 | `arbitrary-send-erc20-permit` | [transferFrom uses arbitrary from with permit](https://github.com/crytic/slither/wiki/Detector-Documentation#arbitrary-from-in-transferfrom-used-with-permit) | High | Medium
17 | `arbitrary-send-eth` | [Functions that send Ether to arbitrary destinations](https://github.com/crytic/slither/wiki/Detector-Documentation#functions-that-send-ether-to-arbitrary-destinations) | High | Medium
18 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
19 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
20 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
21 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
22 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
23 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
24 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
25 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
26 | `domain-separator-collision` | [Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()](https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision) | Medium | High
27 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
28 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
29 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
30 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
31 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
32 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
33 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
34 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
35 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
36 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
37 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
38 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
39 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
40 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
41 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
42 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
43 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
44 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
45 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
46 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
47 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
48 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
49 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
50 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
51 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
52 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
53 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
54 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
55 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
56 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
57 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
58 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
59 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
60 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
61 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
62 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
63 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
64 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
65 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
66 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
67 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
68 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
69 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
70 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
71 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
72 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
73 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
74 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
75 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
76 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
77 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
78 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
79 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
80 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
81 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
82 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
83 | `immutable-states` | [State variables that could be declared immutable](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-immutable) | Optimization | High
84 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Vulnerabilities-Description#public-variable-read-in-external-context) | Optimization | High
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 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
46 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
47 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
48 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
49 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
50 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
51 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
52 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
53 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
54 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
55 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
56 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
57 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
58 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
59 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
60 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
61 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
62 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
63 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
64 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
65 | `return-bomb` | [A low level callee may consume all callers gas unexpectedly.](https://github.com/crytic/slither/wiki/Detector-Documentation#return-bomb) | Low | Medium
66 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
67 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
68 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
69 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
70 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
71 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
72 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
73 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
74 | `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
75 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
76 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
77 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
78 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
79 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
80 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
81 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
82 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
83 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
84 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
85 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
86 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
87 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
88 | `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
89 | `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
90 | `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
91 | `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
92 | `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
* 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)
* `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)
- `when-not-paused`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
* `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.
@ -198,18 +231,20 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d
## 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-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
@ -227,10 +262,12 @@ Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (
## FAQ
How do I exclude mocks or tests?
- View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
* 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.
* 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,
@ -244,9 +281,11 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
## 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 | 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

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

@ -5,7 +5,7 @@
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,declaration"
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

@ -5,18 +5,18 @@ 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.9.3",
version="0.10.0",
packages=find_packages(),
python_requires=">=3.8",
install_requires=[
"packaging",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
# "crytic-compile>=0.3.1,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile",
"crytic-compile>=0.3.5,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
@ -36,7 +36,6 @@ setup(
"coverage[toml]",
"filelock",
"pytest-insta",
"solc-select@git+https://github.com/crytic/solc-select.git@query-artifact-path#egg=solc-select",
],
"doc": [
"pdoc",

@ -35,6 +35,7 @@ from slither.utils.output import (
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,
@ -78,6 +79,11 @@ def process_single(
ast = "--ast-json"
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)
@ -206,22 +212,22 @@ def choose_detectors(
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
if args.exclude_optimization and not args.fail_pedantic:
if args.exclude_optimization:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION
]
if args.exclude_informational and not args.fail_pedantic:
if args.exclude_informational:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL
]
if args.exclude_low and not args.fail_low:
if args.exclude_low:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW]
if args.exclude_medium and not args.fail_medium:
if args.exclude_medium:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM
]
if args.exclude_high and not args.fail_high:
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 = [
@ -386,41 +392,44 @@ def parse_args(
default=defaults_flag_in_config["exclude_high"],
)
group_detector.add_argument(
fail_on_group = group_detector.add_mutually_exclusive_group()
fail_on_group.add_argument(
"--fail-pedantic",
help="Return the number of findings in the exit code",
action="store_true",
default=defaults_flag_in_config["fail_pedantic"],
)
group_detector.add_argument(
"--no-fail-pedantic",
help="Do not return the number of findings in the exit code. Opposite of --fail-pedantic",
dest="fail_pedantic",
action="store_false",
required=False,
help="Fail if any findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.PEDANTIC,
)
group_detector.add_argument(
fail_on_group.add_argument(
"--fail-low",
help="Fail if low or greater impact finding is detected",
action="store_true",
default=defaults_flag_in_config["fail_low"],
help="Fail if any low or greater impact findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.LOW,
)
group_detector.add_argument(
fail_on_group.add_argument(
"--fail-medium",
help="Fail if medium or greater impact finding is detected",
action="store_true",
default=defaults_flag_in_config["fail_medium"],
help="Fail if any medium or greater impact findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.MEDIUM,
)
group_detector.add_argument(
fail_on_group.add_argument(
"--fail-high",
help="Fail if high impact finding is detected",
action="store_true",
default=defaults_flag_in_config["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",
@ -438,7 +447,7 @@ def parse_args(
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="",
)
@ -465,6 +474,20 @@ def parse_args(
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 "
@ -866,12 +889,6 @@ def main_impl(
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()
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:
@ -896,17 +913,18 @@ def main_impl(
stats = pstats.Stats(cp).sort_stats("cumtime")
stats.print_stats()
if args.fail_high:
fail_on = FailOnLevel(args.fail_on)
if fail_on == FailOnLevel.HIGH:
fail_on_detection = any(result["impact"] == "High" for result in results_detectors)
elif args.fail_medium:
elif fail_on == FailOnLevel.MEDIUM:
fail_on_detection = any(
result["impact"] in ["Medium", "High"] for result in results_detectors
)
elif args.fail_low:
elif fail_on == FailOnLevel.LOW:
fail_on_detection = any(
result["impact"] in ["Low", "Medium", "High"] for result in results_detectors
)
elif args.fail_pedantic:
elif fail_on == FailOnLevel.PEDANTIC:
fail_on_detection = bool(results_detectors)
else:
fail_on_detection = False

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

@ -74,6 +74,7 @@ class NodeType(Enum):
IF = "IF"
VARIABLE = "NEW VARIABLE" # Variable declaration
ASSEMBLY = "INLINE ASM"
ENDASSEMBLY = "END INLINE ASM"
IFLOOP = "IF_LOOP"
# Nodes where control flow merges
@ -193,6 +194,8 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
self.file_scope: "FileScope" = file_scope
self._function: Optional["Function"] = None
self._is_reachable: bool = False
###################################################################################
###################################################################################
# region General's properties
@ -234,6 +237,13 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
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
###################################################################################
###################################################################################

@ -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"]) -> None:
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,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
@ -15,6 +16,7 @@ from slither.core.declarations import (
)
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
@ -29,6 +31,20 @@ 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) -> None:
@ -36,18 +52,20 @@ class SlitherCompilationUnit(Context):
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[CustomErrorTopLevel] = []
self._user_defined_value_types: Dict[str, TypeAliasTopLevel] = {}
self._type_aliases: Dict[str, TypeAliasTopLevel] = {}
self._all_functions: Set[Function] = set()
self._all_modifiers: Set[Modifier] = set()
@ -81,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:
@ -166,6 +195,10 @@ class SlitherCompilationUnit(Context):
return self.functions + list(self.modifiers)
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:
@ -203,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
@ -220,8 +257,8 @@ class SlitherCompilationUnit(Context):
return self._custom_errors
@property
def user_defined_value_types(self) -> Dict[str, TypeAliasTopLevel]:
return self._user_defined_value_types
def type_aliases(self) -> Dict[str, TypeAliasTopLevel]:
return self._type_aliases
# endregion
###################################################################################
@ -259,6 +296,7 @@ class SlitherCompilationUnit(Context):
###################################################################################
def compute_storage_layout(self) -> None:
assert self.is_solidity
for contract in self.contracts_derived:
self._storage_layouts[contract.name] = {}

@ -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
@ -18,3 +20,6 @@ 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

@ -33,7 +33,7 @@ if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import (
Enum,
Event,
EventContract,
Modifier,
EnumContract,
StructureContract,
@ -45,6 +45,7 @@ if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
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")
@ -72,15 +73,18 @@ 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[USING_FOR_KEY, USING_FOR_ITEM] = {}
@ -136,7 +140,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def id(self) -> int:
"""Unique id."""
assert self._id
assert self._id is not None
return self._id
@id.setter
@ -274,28 +278,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
@ -364,12 +368,50 @@ 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"]:
@ -861,7 +903,7 @@ 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["StructureContract"]:
"""

@ -16,3 +16,7 @@ class CustomErrorContract(CustomError, ContractLevel):
:return:
"""
return self.contract == contract
@property
def canonical_name(self) -> str:
return self.contract.name + "." + self.full_name

@ -12,3 +12,7 @@ class CustomErrorTopLevel(CustomError, TopLevel):
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

@ -1,14 +1,10 @@
from typing import List, Tuple, TYPE_CHECKING
from typing import List, Tuple
from slither.core.declarations.contract_level import ContractLevel
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.event_variable import EventVariable
if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(ContractLevel, SourceMapping):
class Event(SourceMapping):
def __init__(self) -> None:
super().__init__()
self._name = None
@ -39,25 +35,9 @@ class Event(ContractLevel, 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) -> 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

@ -137,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()
@ -217,8 +219,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
# Assume we are analyzing Solidity 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
@ -238,7 +241,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"
@ -985,14 +988,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 = (
# 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
# self._signature = signature
# return self._signature
@property
def signature_str(self) -> str:
@ -1496,8 +1500,13 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
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 "nonReentrant" in [m.name for m in self.modifiers]:
if reentrancy_modifier in [m.name for m in self.modifiers]:
return False
if self.visibility in ["public", "external"]:
@ -1509,7 +1518,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
]
if not all_entry_points:
return True
return not all(("nonReentrant" in [m.name for m in f.modifiers] for f in all_entry_points))
return not all(
(reentrancy_modifier in [m.name for m in f.modifiers] for f in all_entry_points)
)
# endregion
###################################################################################
@ -1756,6 +1767,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)]
def generate_slithir_and_analyze(self) -> None:
for node in self.nodes:
node.slithir_generation()

@ -10,21 +10,25 @@ from slither.exceptions import SlitherException
SOLIDITY_VARIABLES = {
"now": "uint256",
"this": "address",
"self": "address",
"abi": "address", # to simplify the conversion, assume that abi return an address
"msg": "",
"tx": "",
"block": "",
"super": "",
"chain": "",
"ZERO_ADDRESS": "address",
}
SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint",
"block.coinbase": "address",
"block.difficulty": "uint256",
"block.prevrandao": "uint256",
"block.gaslimit": "uint256",
"block.number": "uint256",
"block.timestamp": "uint256",
"block.blockhash": "uint256", # alias for blockhash. It's a call
"block.blockhash": "bytes32", # alias for blockhash. It's a call
"block.chainid": "uint256",
"msg.data": "bytes",
"msg.gas": "uint256",
@ -33,6 +37,10 @@ SOLIDITY_VARIABLES_COMPOSED = {
"msg.value": "uint256",
"tx.gasprice": "uint256",
"tx.origin": "address",
# Vyper
"chain.id": "uint256",
"block.prevhash": "bytes32",
"self.balance": "uint256",
}
SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
@ -60,6 +68,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"log2(bytes32,bytes32,bytes32)": [],
"log3(bytes32,bytes32,bytes32,bytes32)": [],
"blockhash(uint256)": ["bytes32"],
"prevrandao()": ["uint256"],
# the following need a special handling
# as they are recognized as a SolidityVariableComposed
# and converted to a SolidityFunction by SlithIR
@ -79,6 +88,32 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"balance(address)": ["uint256"],
"code(address)": ["bytes"],
"codehash(address)": ["bytes32"],
# Vyper
"create_from_blueprint()": [],
"create_minimal_proxy_to()": [],
"empty()": [],
"convert()": [],
"len()": ["uint256"],
"method_id()": [],
"unsafe_sub()": [],
"unsafe_add()": [],
"unsafe_div()": [],
"unsafe_mul()": [],
"pow_mod256()": [],
"max_value()": [],
"min_value()": [],
"concat()": [],
"ecrecover()": [],
"isqrt()": [],
"range()": [],
"min()": [],
"max()": [],
"shift()": [],
"abs()": [],
"raw_call()": ["bool", "bytes32"],
"_abi_encode()": [],
"slice()": [],
"uint2str()": ["string"],
}

@ -9,9 +9,20 @@ if TYPE_CHECKING:
def intersection_predecessor(node: "Node") -> Set["Node"]:
if not node.fathers:
return set()
ret = node.fathers[0].dominators
for pred in node.fathers[1:]:
ret = ret.intersection(pred.dominators)
if not any(father.is_reachable for father in node.fathers):
return set()
ret = set()
for pred in node.fathers:
ret = ret.union(pred.dominators)
for pred in node.fathers:
if pred.is_reachable:
ret = ret.intersection(pred.dominators)
return ret
@ -84,6 +95,8 @@ def compute_dominance_frontier(nodes: List["Node"]) -> None:
for node in nodes:
if len(node.fathers) >= 2:
for father in node.fathers:
if not father.is_reachable:
continue
runner = father
# Corner case: if there is a if without else
# we need to add update the conditional node

@ -12,6 +12,7 @@ from .new_contract import NewContract
from .new_elementary_type import NewElementaryType
from .super_call_expression import SuperCallExpression
from .super_identifier import SuperIdentifier
from .self_identifier import SelfIdentifier
from .tuple_expression import TupleExpression
from .type_conversion import TypeConversion
from .unary_operation import UnaryOperation, UnaryOperationType

@ -42,7 +42,7 @@ class BinaryOperationType(Enum):
# pylint: disable=too-many-branches
@staticmethod
def get_type(
operation_type: "BinaryOperation",
operation_type: "str",
) -> "BinaryOperationType":
if operation_type == "**":
return BinaryOperationType.POWER

@ -4,12 +4,32 @@ from slither.core.expressions.expression import Expression
class CallExpression(Expression): # pylint: disable=too-many-instance-attributes
def __init__(self, called: Expression, arguments: List[Any], type_call: str) -> None:
def __init__(
self,
called: Expression,
arguments: List[Any],
type_call: str,
names: Optional[List[str]] = None,
) -> None:
"""
#### Parameters
called -
The expression denoting the function to be called
arguments -
List of argument expressions
type_call -
A string formatting of the called function's return type
names -
For calls with named fields, list fields in call order.
For calls without named fields, None.
"""
assert isinstance(called, Expression)
assert (names is None) or isinstance(names, list)
super().__init__()
self._called: Expression = called
self._arguments: List[Expression] = arguments
self._type_call: str = type_call
self._names: Optional[List[str]] = names
# gas and value are only available if the syntax is {gas: , value: }
# For the .gas().value(), the member are considered as function call
# And converted later to the correct info (convert.py)
@ -17,6 +37,14 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
self._value: Optional[Expression] = None
self._salt: Optional[Expression] = None
@property
def names(self) -> Optional[List[str]]:
"""
For calls with named fields, list fields in call order.
For calls without named fields, None.
"""
return self._names
@property
def call_value(self) -> Optional[Expression]:
return self._value
@ -62,4 +90,9 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
if gas or value or salt:
options = [gas, value, salt]
txt += "{" + ",".join([o for o in options if o != ""]) + "}"
return txt + "(" + ",".join([str(a) for a in self._arguments]) + ")"
args = (
"{" + ",".join([f"{n}:{str(a)}" for (a, n) in zip(self._arguments, self._names)]) + "}"
if self._names is not None
else ",".join([str(a) for a in self._arguments])
)
return txt + "(" + args + ")"

@ -26,7 +26,6 @@ class Identifier(Expression):
],
) -> None:
super().__init__()
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract, SolidityVariable, SolidityFunction
from slither.solc_parsing.yul.evm_functions import YulBuiltin

@ -0,0 +1,6 @@
from slither.core.expressions.identifier import Identifier
class SelfIdentifier(Identifier):
def __str__(self):
return "self." + str(self._value)

@ -106,8 +106,6 @@ class UnaryOperation(Expression):
UnaryOperationType.MINUSMINUS_PRE,
UnaryOperationType.PLUSPLUS_POST,
UnaryOperationType.MINUSMINUS_POST,
UnaryOperationType.PLUS_PRE,
UnaryOperationType.MINUS_PRE,
]:
expression.set_lvalue()

@ -7,6 +7,7 @@ from crytic_compile.utils.naming import Filename
from slither.core.declarations import Contract, Import, Pragma
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
@ -35,6 +36,7 @@ class FileScope:
# So we simplify the logic and have the scope fields all populated
self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {}
self.events: Dict[str, EventTopLevel] = {}
# Functions is a list instead of a dict
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
@ -52,9 +54,9 @@ class FileScope:
# User defined types
# Name -> type alias
self.user_defined_types: Dict[str, TypeAlias] = {}
self.type_aliases: Dict[str, TypeAlias] = {}
def add_accesible_scopes(self) -> bool:
def add_accesible_scopes(self) -> bool: # pylint: disable=too-many-branches
"""
Add information from accessible scopes. Return true if new information was obtained
@ -74,6 +76,9 @@ class FileScope:
if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums)
learn_something = True
if not _dict_contain(new_scope.events, self.events):
self.events.update(new_scope.events)
learn_something = True
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
@ -95,8 +100,8 @@ class FileScope:
if not _dict_contain(new_scope.renaming, self.renaming):
self.renaming.update(new_scope.renaming)
learn_something = True
if not _dict_contain(new_scope.user_defined_types, self.user_defined_types):
self.user_defined_types.update(new_scope.user_defined_types)
if not _dict_contain(new_scope.type_aliases, self.type_aliases):
self.type_aliases.update(new_scope.type_aliases)
learn_something = True
return learn_something

@ -21,6 +21,7 @@ from slither.core.declarations.top_level import TopLevel
from slither.core.source_mapping.source_mapping import SourceMapping, Source
from slither.slithir.variables import Constant
from slither.utils.colors import red
from slither.utils.sarif import read_triage_info
from slither.utils.source_mapping import get_definition, get_references, get_implementation
logger = logging.getLogger("Slither")
@ -48,6 +49,10 @@ class SlitherCore(Context):
self._source_code_to_line: Optional[Dict[str, List[str]]] = None
self._previous_results_filename: str = "slither.db.json"
# TODO: add cli flag to set these variables
self.sarif_input: str = "export.sarif"
self.sarif_triage: str = "export.sarif.sarifexplorer"
self._results_to_hide: List = []
self._previous_results: List = []
# From triaged result
@ -96,6 +101,8 @@ class SlitherCore(Context):
# If true, partial analysis is allowed
self.no_fail = False
self.skip_data_dependency = False
@property
def compilation_units(self) -> List[SlitherCompilationUnit]:
return list(self._compilation_units)
@ -262,6 +269,8 @@ class SlitherCore(Context):
self._compute_offsets_from_thing(event)
for enum in compilation_unit.enums_top_level:
self._compute_offsets_from_thing(enum)
for event in compilation_unit.events_top_level:
self._compute_offsets_from_thing(event)
for function in compilation_unit.functions_top_level:
self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level:
@ -444,6 +453,8 @@ class SlitherCore(Context):
return True
def load_previous_results(self) -> None:
self.load_previous_results_from_sarif()
filename = self._previous_results_filename
try:
if os.path.isfile(filename):
@ -453,9 +464,24 @@ class SlitherCore(Context):
for r in self._previous_results:
if "id" in r:
self._previous_results_ids.add(r["id"])
except json.decoder.JSONDecodeError:
logger.error(red(f"Impossible to decode {filename}. Consider removing the file"))
def load_previous_results_from_sarif(self) -> None:
sarif = pathlib.Path(self.sarif_input)
triage = pathlib.Path(self.sarif_triage)
if not sarif.exists():
return
if not triage.exists():
return
triaged = read_triage_info(sarif, triage)
for id_triaged in triaged:
self._previous_results_ids.add(id_triaged)
def write_results_to_hide(self) -> None:
if not self._results_to_hide:
return

@ -1,10 +1,11 @@
from typing import TYPE_CHECKING, Tuple
from typing import TYPE_CHECKING, Tuple, Dict
from slither.core.declarations.top_level import TopLevel
from slither.core.declarations.contract_level import ContractLevel
from slither.core.solidity_types import Type, ElementaryType
if TYPE_CHECKING:
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations import Contract
from slither.core.scope.scope import FileScope
@ -43,6 +44,8 @@ class TypeAliasTopLevel(TypeAlias, TopLevel):
def __init__(self, underlying_type: ElementaryType, name: str, scope: "FileScope") -> None:
super().__init__(underlying_type, name)
self.file_scope: "FileScope" = scope
# operators redefined
self.operators: Dict[str, "FunctionTopLevel"] = {}
def __str__(self) -> str:
return self.name

@ -1,2 +1,8 @@
from .state_variable import StateVariable
from .variable import Variable
from .local_variable_init_from_tuple import LocalVariableInitFromTuple
from .local_variable import LocalVariable
from .top_level_variable import TopLevelVariable
from .event_variable import EventVariable
from .function_type_variable import FunctionTypeVariable
from .structure_variable import StructureVariable

@ -2,7 +2,6 @@ from typing import Optional, TYPE_CHECKING
from slither.core.variables.variable import Variable
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.mapping_type import MappingType
from slither.core.solidity_types.elementary_type import ElementaryType
@ -51,6 +50,9 @@ class LocalVariable(Variable):
Returns:
(bool)
"""
# pylint: disable=import-outside-toplevel
from slither.core.solidity_types.array_type import ArrayType
if self.location == "memory":
return False
if self.location == "calldata":

@ -179,5 +179,6 @@ class Variable(SourceMapping):
return f'{name}({",".join(parameters)})'
def __str__(self) -> str:
assert self._name
if self._name is None:
return ""
return self._name

@ -3,7 +3,7 @@ import re
from logging import Logger
from typing import Optional, List, TYPE_CHECKING, Dict, Union, Callable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.compilation_unit import SlitherCompilationUnit, Language
from slither.core.declarations import Contract
from slither.formatters.exceptions import FormatImpossible
from slither.formatters.utils.patches import apply_patch, create_diff
@ -80,6 +80,9 @@ class AbstractDetector(metaclass=abc.ABCMeta):
# list of vulnerable solc versions as strings (e.g. ["0.4.25", "0.5.0"])
# If the detector is meant to run on all versions, use None
VULNERABLE_SOLC_VERSIONS: Optional[List[str]] = None
# If the detector is meant to run on all languages, use None
# Otherwise, use `solidity` or `vyper`
LANGUAGE: Optional[str] = None
def __init__(
self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger
@ -133,6 +136,14 @@ class AbstractDetector(metaclass=abc.ABCMeta):
f"VULNERABLE_SOLC_VERSIONS should not be an empty list {self.__class__.__name__}"
)
if self.LANGUAGE is not None and self.LANGUAGE not in [
Language.SOLIDITY.value,
Language.VYPER.value,
]:
raise IncorrectDetectorInitialization(
f"LANGUAGE should not be either 'solidity' or 'vyper' {self.__class__.__name__}"
)
if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
raise IncorrectDetectorInitialization(
f"ARGUMENT has illegal character {self.__class__.__name__}"
@ -164,9 +175,14 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if self.logger:
self.logger.info(self.color(info))
def _uses_vulnerable_solc_version(self) -> bool:
def _is_applicable_detector(self) -> bool:
if self.VULNERABLE_SOLC_VERSIONS:
return self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
return (
self.compilation_unit.is_solidity
and self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
)
if self.LANGUAGE:
return self.compilation_unit.language.value == self.LANGUAGE
return True
@abc.abstractmethod
@ -179,7 +195,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
results: List[Dict] = []
# check solc version
if not self._uses_vulnerable_solc_version():
if not self._is_applicable_detector():
return results
# only keep valid result, and remove duplicate

@ -89,5 +89,11 @@ from .functions.protected_variable import ProtectedVariables
from .functions.permit_domain_signature_collision import DomainSeparatorCollision
from .functions.codex import Codex
from .functions.cyclomatic_complexity import CyclomaticComplexity
from .operations.cache_array_length import CacheArrayLength
from .statements.incorrect_using_for import IncorrectUsingFor
from .operations.encode_packed import EncodePackedCollision
from .assembly.incorrect_return import IncorrectReturn
from .assembly.return_instead_of_leave import ReturnInsteadOfLeave
from .operations.incorrect_exp import IncorrectOperatorExponentiation
from .statements.tautological_compare import TautologicalCompare
from .statements.return_bomb import ReturnBomb

@ -0,0 +1,93 @@
from typing import List, Optional
from slither.core.declarations import SolidityFunction, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output
def _assembly_node(function: Function) -> Optional[SolidityCall]:
"""
Check if there is a node that use return in assembly
Args:
function:
Returns:
"""
for ir in function.all_slithir_operations():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
return ir
return None
class IncorrectReturn(AbstractDetector):
"""
Check for cases where a return(a,b) is used in an assembly function
"""
ARGUMENT = "incorrect-return"
HELP = "If a `return` is incorrectly used in assembly mode."
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-return-in-assembly"
)
WIKI_TITLE = "Incorrect return in assembly"
WIKI_DESCRIPTION = "Detect if `return` in an assembly block halts unexpectedly the execution."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
function f() internal returns (uint a, uint b) {
assembly {
return (5, 6)
}
}
function g() returns (bool){
f();
return true;
}
}
```
The return statement in `f` will cause execution in `g` to halt.
The function will return 6 bytes starting from offset 5, instead of returning a boolean."""
WIKI_RECOMMENDATION = "Use the `leave` statement."
# pylint: disable=too-many-nested-blocks
def _detect(self) -> List[Output]:
results: List[Output] = []
for c in self.contracts:
for f in c.functions_and_modifiers_declared:
for node in f.nodes:
if node.sons:
for function_called in node.internal_calls:
if isinstance(function_called, Function):
found = _assembly_node(function_called)
if found:
info: DETECTOR_INFO = [
f,
" calls ",
function_called,
" which halt the execution ",
found.node,
"\n",
]
json = self.generate_result(info)
results.append(json)
return results

@ -0,0 +1,68 @@
from typing import List
from slither.core.declarations import SolidityFunction, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output
class ReturnInsteadOfLeave(AbstractDetector):
"""
Check for cases where a return(a,b) is used in an assembly function that also returns two variables
"""
ARGUMENT = "return-leave"
HELP = "If a `return` is used instead of a `leave`."
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#return-instead-of-leave-in-assembly"
WIKI_TITLE = "Return instead of leave in assembly"
WIKI_DESCRIPTION = "Detect if a `return` is used where a `leave` should be used."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
function f() internal returns (uint a, uint b) {
assembly {
return (5, 6)
}
}
}
```
The function will halt the execution, instead of returning a two uint."""
WIKI_RECOMMENDATION = "Use the `leave` statement."
def _check_function(self, f: Function) -> List[Output]:
results: List[Output] = []
for node in f.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", node, "\n"]
json = self.generate_result(info)
results.append(json)
return results
def _detect(self) -> List[Output]:
results: List[Output] = []
for c in self.contracts:
for f in c.functions_declared:
if (
len(f.returns) == 2
and f.contains_assembly
and f.visibility not in ["public", "external"]
):
results += self._check_function(f)
return results

@ -33,7 +33,7 @@ class IncorrectSolc(AbstractDetector):
HELP = "Incorrect Solidity version"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity"
WIKI_TITLE = "Incorrect versions of Solidity"

@ -3,7 +3,7 @@
"""
from typing import List
from slither.core.declarations.contract import Contract
from slither.core.declarations import Contract, SolidityFunction
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
@ -17,7 +17,9 @@ from slither.slithir.operations import (
NewContract,
LibraryCall,
InternalCall,
SolidityCall,
)
from slither.slithir.variables import Constant
from slither.utils.output import Output
@ -68,8 +70,28 @@ Every Ether sent to `Locked` will be lost."""
):
if ir.call_value and ir.call_value != 0:
return False
if isinstance(ir, (LowLevelCall)):
if ir.function_name in ["delegatecall", "callcode"]:
if isinstance(ir, (LowLevelCall)) and ir.function_name in [
"delegatecall",
"callcode",
]:
return False
if isinstance(ir, SolidityCall):
call_can_send_ether = ir.function in [
SolidityFunction(
"delegatecall(uint256,uint256,uint256,uint256,uint256,uint256)"
),
SolidityFunction(
"callcode(uint256,uint256,uint256,uint256,uint256,uint256,uint256)"
),
SolidityFunction(
"call(uint256,uint256,uint256,uint256,uint256,uint256,uint256)"
),
]
nonzero_call_value = call_can_send_ether and (
not isinstance(ir.arguments[2], Constant)
or ir.arguments[2].value != 0
)
if nonzero_call_value:
return False
# If a new internal call or librarycall
# Add it to the list to explore

@ -133,7 +133,7 @@ As a result, Bob's usage of the contract is incorrect."""
continue
# Verify one of these parameters is an array in storage.
for arg in ir.arguments:
for (param, arg) in zip(ir.function.parameters, ir.arguments):
# Verify this argument is a variable that is an array type.
if not isinstance(arg, (StateVariable, LocalVariable)):
continue
@ -141,8 +141,11 @@ As a result, Bob's usage of the contract is incorrect."""
continue
# If it is a state variable OR a local variable referencing storage, we add it to the list.
if isinstance(arg, StateVariable) or (
isinstance(arg, LocalVariable) and arg.location == "storage"
if (
isinstance(arg, StateVariable)
or (isinstance(arg, LocalVariable) and arg.location == "storage")
) and (
isinstance(param.type, ArrayType) and param.location != "storage"
):
results.append((node, arg, ir.function))
return results
@ -165,9 +168,9 @@ As a result, Bob's usage of the contract is incorrect."""
calling_node.function,
" passes array ",
affected_argument,
"by reference to ",
" by reference to ",
invoked_function,
"which only takes arrays by value\n",
" which only takes arrays by value\n",
]
res = self.generate_result(info)

@ -59,7 +59,7 @@ Bob calls `kill` and destructs the contract."""
if func.visibility not in ["public", "external"]:
return False
calls = [c.name for c in func.internal_calls]
calls = [c.name for c in func.all_internal_calls()]
if not ("suicide(address)" in calls or "selfdestruct(address)" in calls):
return False

@ -24,7 +24,7 @@ class NamingConvention(AbstractDetector):
HELP = "Conformity to Solidity naming conventions"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions"
WIKI_TITLE = "Conformance to Solidity naming conventions"
@ -45,6 +45,14 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
def is_cap_words(name: str) -> bool:
return re.search("^[A-Z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_immutable_naming(name: str) -> bool:
return re.search("^i_[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_state_naming(name: str) -> bool:
return re.search("^s_[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_mixed_case(name: str) -> bool:
return re.search("^[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@ -167,10 +175,17 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
results.append(res)
else:
if var.visibility == "private":
correct_naming = self.is_mixed_case_with_underscore(var.name)
if var.visibility in ["private", "internal"]:
correct_naming = self.is_mixed_case_with_underscore(
var.name
) or self.is_state_naming(var.name)
if not correct_naming and var.is_immutable:
correct_naming = self.is_immutable_naming(var.name)
else:
correct_naming = self.is_mixed_case(var.name)
if not correct_naming:
info = ["Variable ", var, " is not in mixedCase\n"]

@ -0,0 +1,225 @@
from typing import List, Set
from slither.core.cfg.node import Node, NodeType
from slither.core.declarations import Function
from slither.core.expressions import BinaryOperation, Identifier, MemberAccess, UnaryOperation
from slither.core.solidity_types import ArrayType
from slither.core.variables import StateVariable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import Length, Delete, HighLevelCall
class CacheArrayLength(AbstractDetector):
"""
Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it.
"""
ARGUMENT = "cache-array-length"
HELP = (
"Detects `for` loops that use `length` member of some storage array in their loop condition and don't "
"modify it."
)
IMPACT = DetectorClassification.OPTIMIZATION
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length"
WIKI_TITLE = "Cache array length"
WIKI_DESCRIPTION = (
"Detects `for` loops that use `length` member of some storage array in their loop condition "
"and don't modify it. "
)
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C
{
uint[] array;
function f() public
{
for (uint i = 0; i < array.length; i++)
{
// code that does not modify length of `array`
}
}
}
```
Since the `for` loop in `f` doesn't modify `array.length`, it is more gas efficient to cache it in some local variable and use that variable instead, like in the following example:
```solidity
contract C
{
uint[] array;
function f() public
{
uint array_length = array.length;
for (uint i = 0; i < array_length; i++)
{
// code that does not modify length of `array`
}
}
}
```
"""
WIKI_RECOMMENDATION = (
"Cache the lengths of storage arrays if they are used and not modified in `for` loops."
)
@staticmethod
def _is_identifier_member_access_comparison(exp: BinaryOperation) -> bool:
"""
Checks whether a BinaryOperation `exp` is an operation on Identifier and MemberAccess.
"""
return (
isinstance(exp.expression_left, Identifier)
and isinstance(exp.expression_right, MemberAccess)
) or (
isinstance(exp.expression_left, MemberAccess)
and isinstance(exp.expression_right, Identifier)
)
@staticmethod
def _extract_array_from_length_member_access(exp: MemberAccess) -> StateVariable:
"""
Given a member access `exp`, it returns state array which `length` member is accessed through `exp`.
If array is not a state array or its `length` member is not referenced, it returns `None`.
"""
if exp.member_name != "length":
return None
if not isinstance(exp.expression, Identifier):
return None
if not isinstance(exp.expression.value, StateVariable):
return None
if not isinstance(exp.expression.value.type, ArrayType):
return None
return exp.expression.value
@staticmethod
def _is_loop_referencing_array_length(
node: Node, visited: Set[Node], array: StateVariable, depth: int
) -> True:
"""
For a given loop, checks if it references `array.length` at some point.
Will also return True if `array.length` is referenced but not changed.
This may potentially generate false negatives in the detector, but it was done this way because:
- situations when array `length` is referenced but not modified in loop are rare
- checking if `array.length` is indeed modified would require much more work
"""
visited.add(node)
if node.type == NodeType.STARTLOOP:
depth += 1
if node.type == NodeType.ENDLOOP:
depth -= 1
if depth == 0:
return False
# Array length may change in the following situations:
# - when `push` is called
# - when `pop` is called
# - when `delete` is called on the entire array
# - when external function call is made (instructions from internal function calls are already in
# `node.all_slithir_operations()`, so we don't need to handle internal calls separately)
if node.type == NodeType.EXPRESSION:
for op in node.all_slithir_operations():
if isinstance(op, Length) and op.value == array:
# op accesses array.length, not necessarily modifying it
return True
if isinstance(op, Delete):
# take into account only delete entire array, since delete array[i] doesn't change `array.length`
if (
isinstance(op.expression, UnaryOperation)
and isinstance(op.expression.expression, Identifier)
and op.expression.expression.value == array
):
return True
if (
isinstance(op, HighLevelCall)
and isinstance(op.function, Function)
and not op.function.view
and not op.function.pure
):
return True
for son in node.sons:
if son not in visited:
if CacheArrayLength._is_loop_referencing_array_length(son, visited, array, depth):
return True
return False
@staticmethod
def _handle_loops(nodes: List[Node], non_optimal_array_len_usages: List[Node]) -> None:
"""
For each loop, checks if it has a comparison with `length` array member and, if it has, checks whether that
array size could potentially change in that loop.
If it cannot, the loop condition is added to `non_optimal_array_len_usages`.
There may be some false negatives here - see docs for `_is_loop_referencing_array_length` for more information.
"""
for node in nodes:
if node.type == NodeType.STARTLOOP:
if_node = node.sons[0]
if if_node.type != NodeType.IFLOOP:
continue
if not isinstance(if_node.expression, BinaryOperation):
continue
exp: BinaryOperation = if_node.expression
if not CacheArrayLength._is_identifier_member_access_comparison(exp):
continue
array: StateVariable
if isinstance(exp.expression_right, MemberAccess):
array = CacheArrayLength._extract_array_from_length_member_access(
exp.expression_right
)
else: # isinstance(exp.expression_left, MemberAccess) == True
array = CacheArrayLength._extract_array_from_length_member_access(
exp.expression_left
)
if array is None:
continue
visited: Set[Node] = set()
if not CacheArrayLength._is_loop_referencing_array_length(
if_node, visited, array, 1
):
non_optimal_array_len_usages.append(if_node)
@staticmethod
def _get_non_optimal_array_len_usages_for_function(f: Function) -> List[Node]:
"""
Finds non-optimal usages of array length in loop conditions in a given function.
"""
non_optimal_array_len_usages: List[Node] = []
CacheArrayLength._handle_loops(f.nodes, non_optimal_array_len_usages)
return non_optimal_array_len_usages
@staticmethod
def _get_non_optimal_array_len_usages(functions: List[Function]) -> List[Node]:
"""
Finds non-optimal usages of array length in loop conditions in given functions.
"""
non_optimal_array_len_usages: List[Node] = []
for f in functions:
non_optimal_array_len_usages += (
CacheArrayLength._get_non_optimal_array_len_usages_for_function(f)
)
return non_optimal_array_len_usages
def _detect(self):
results = []
non_optimal_array_len_usages = CacheArrayLength._get_non_optimal_array_len_usages(
self.compilation_unit.functions
)
for usage in non_optimal_array_len_usages:
info = [
"Loop condition ",
usage,
" should use cached array length instead of referencing `length` member "
"of the storage array.\n ",
]
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,93 @@
"""
Module detecting incorrect operator usage for exponentiation where bitwise xor '^' is used instead of '**'
"""
from typing import Tuple, List, Union
from slither.core.cfg.node import Node
from slither.core.declarations import Contract, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import Binary, BinaryType, Operation
from slither.slithir.utils.utils import RVALUE
from slither.slithir.variables.constant import Constant
from slither.utils.output import Output
def _is_constant_candidate(var: Union[RVALUE, Function]) -> bool:
"""
Check if the variable is a constant.
Do not consider variable that are expressed with hexadecimal.
Something like 2^0xf is likely to be a correct bitwise operator
:param var:
:return:
"""
return isinstance(var, Constant) and not var.original_value.startswith("0x")
def _is_bitwise_xor_on_constant(ir: Operation) -> bool:
return (
isinstance(ir, Binary)
and ir.type == BinaryType.CARET
and (_is_constant_candidate(ir.variable_left) or _is_constant_candidate(ir.variable_right))
)
def _detect_incorrect_operator(contract: Contract) -> List[Tuple[Function, Node]]:
ret: List[Tuple[Function, Node]] = []
f: Function
for f in contract.functions + contract.modifiers: # type:ignore
# Heuristic: look for binary expressions with ^ operator where at least one of the operands is a constant, and
# the constant is not in hex, because hex typically is used with bitwise xor and not exponentiation
nodes = [node for node in f.nodes for ir in node.irs if _is_bitwise_xor_on_constant(ir)]
for node in nodes:
ret.append((f, node))
return ret
# pylint: disable=too-few-public-methods
class IncorrectOperatorExponentiation(AbstractDetector):
"""
Incorrect operator usage of bitwise xor mistaking it for exponentiation
"""
ARGUMENT = "incorrect-exp"
HELP = "Incorrect exponentiation"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-exponentiation"
WIKI_TITLE = "Incorrect exponentiation"
WIKI_DESCRIPTION = "Detect use of bitwise `xor ^` instead of exponential `**`"
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Bug{
uint UINT_MAX = 2^256 - 1;
...
}
```
Alice deploys a contract in which `UINT_MAX` incorrectly uses `^` operator instead of `**` for exponentiation"""
WIKI_RECOMMENDATION = "Use the correct operator `**` for exponentiation."
def _detect(self) -> List[Output]:
"""Detect the incorrect operator usage for exponentiation where bitwise xor ^ is used instead of **
Returns:
list: (function, node)
"""
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
res = _detect_incorrect_operator(c)
for (func, node) in res:
info: DETECTOR_INFO = [
func,
" has bitwise-xor operator ^ instead of the exponentiation operator **: \n",
]
info += ["\t - ", node, "\n"]
results.append(self.generate_result(info))
return results

@ -1,15 +1,24 @@
"""
Module detecting unused return values from low level
"""
from slither.detectors.abstract_detector import DetectorClassification
from slither.detectors.operations.unused_return_values import UnusedReturnValues
from typing import List
from slither.core.cfg.node import Node
from slither.slithir.operations import LowLevelCall
from slither.slithir.operations.operation import Operation
from slither.core.declarations.function_contract import FunctionContract
from slither.core.variables.state_variable import StateVariable
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
class UncheckedLowLevel(UnusedReturnValues):
class UncheckedLowLevel(AbstractDetector):
"""
If the return value of a send is not checked, it might lead to losing ether
If the return value of a low-level call is not checked, it might lead to losing ether
"""
ARGUMENT = "unchecked-lowlevel"
@ -38,5 +47,44 @@ If the low level is used to prevent blocking operations, consider logging failed
WIKI_RECOMMENDATION = "Ensure that the return value of a low-level call is checked or logged."
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use
return isinstance(ir, LowLevelCall)
@staticmethod
def detect_unused_return_values(f: FunctionContract) -> List[Node]:
"""
Return the nodes where the return value of a call is unused
Args:
f (Function)
Returns:
list(Node)
"""
values_returned = []
nodes_origin = {}
for n in f.nodes:
for ir in n.irs:
if isinstance(ir, LowLevelCall):
# if a return value is stored in a state variable, it's ok
if ir.lvalue and not isinstance(ir.lvalue, StateVariable):
values_returned.append(ir.lvalue)
nodes_origin[ir.lvalue] = ir
for read in ir.read:
if read in values_returned:
values_returned.remove(read)
return [nodes_origin[value].node for value in values_returned]
def _detect(self) -> List[Output]:
"""Detect low level calls where the success value is not checked"""
results = []
for c in self.compilation_unit.contracts_derived:
for f in c.functions_and_modifiers:
unused_return = UncheckedLowLevel.detect_unused_return_values(f)
if unused_return:
for node in unused_return:
info: DETECTOR_INFO = [f, " ignores return value by ", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -101,10 +101,8 @@ contract MyConc{
def _detect(self) -> List[Output]:
"""Detect high level calls which return a value that are never used"""
results = []
for c in self.compilation_unit.contracts:
for f in c.functions + c.modifiers:
if f.contract_declarer != c:
continue
for c in self.compilation_unit.contracts_derived:
for f in c.functions_and_modifiers:
unused_return = self.detect_unused_return_values(f)
if unused_return:

@ -31,7 +31,7 @@ class DeprecatedStandards(AbstractDetector):
HELP = "Deprecated Solidity Standards"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards"
WIKI_TITLE = "Deprecated standards"

@ -2,7 +2,7 @@
Module detecting possible loss of precision due to divide before multiple
"""
from collections import defaultdict
from typing import DefaultDict, List, Set, Tuple
from typing import DefaultDict, List, Tuple
from slither.core.cfg.node import Node
from slither.core.declarations.contract import Contract
@ -63,7 +63,7 @@ def is_assert(node: Node) -> bool:
# pylint: disable=too-many-branches
def _explore(
to_explore: Set[Node], f_results: List[List[Node]], divisions: DefaultDict[LVALUE, List[Node]]
to_explore: List[Node], f_results: List[List[Node]], divisions: DefaultDict[LVALUE, List[Node]]
) -> None:
explored = set()
while to_explore: # pylint: disable=too-many-nested-blocks
@ -114,7 +114,7 @@ def _explore(
f_results.append(node_results)
for son in node.sons:
to_explore.add(son)
to_explore.append(son)
def detect_divide_before_multiply(
@ -133,7 +133,7 @@ def detect_divide_before_multiply(
results: List[Tuple[FunctionContract, List[Node]]] = []
# Loop for each function and modifier.
for function in contract.functions_declared:
for function in contract.functions_declared + contract.modifiers_declared:
if not function.entry_point:
continue
@ -145,7 +145,7 @@ def detect_divide_before_multiply(
# track all the division results (and the assignment of the division results)
divisions: DefaultDict[LVALUE, List[Node]] = defaultdict(list)
_explore({function.entry_point}, f_results, divisions)
_explore([function.entry_point], f_results, divisions)
for f_result in f_results:
results.append((function, f_result))

@ -6,6 +6,7 @@ from typing import List, Tuple
from slither.core.cfg.node import Node
from slither.core.declarations import Structure
from slither.core.declarations.contract import Contract
from slither.core.variables.variable import Variable
from slither.core.declarations.function_contract import FunctionContract
from slither.core.solidity_types import MappingType, UserDefinedType
from slither.detectors.abstract_detector import (
@ -69,13 +70,24 @@ The mapping `balances` is never deleted, so `remove` does not work as intended."
for ir in node.irs:
if isinstance(ir, Delete):
value = ir.variable
if isinstance(value.type, UserDefinedType) and isinstance(
value.type.type, Structure
MappingDeletionDetection.check_if_mapping(value, ret, f, node)
return ret
@staticmethod
def check_if_mapping(
value: Variable,
ret: List[Tuple[FunctionContract, Structure, Node]],
f: FunctionContract,
node: Node,
):
if isinstance(value.type, UserDefinedType) and isinstance(value.type.type, Structure):
st = value.type.type
if any(isinstance(e.type, MappingType) for e in st.elems.values()):
ret.append((f, st, node))
return ret
return
for e in st.elems.values():
MappingDeletionDetection.check_if_mapping(e, ret, f, node)
def _detect(self) -> List[Output]:
"""Detect mapping deletion

@ -79,7 +79,7 @@ contract MsgValueInLoop{
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = """
Track msg.value through a local variable and decrease its amount on every iteration/usage.
Provide an explicit array of amounts alongside the receivers array, and check that the sum of all amounts matches `msg.value`.
"""
def _detect(self) -> List[Output]:

@ -0,0 +1,123 @@
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations import Contract
from slither.core.declarations.function import Function
from slither.core.solidity_types import Type
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import LowLevelCall, HighLevelCall
from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.utils.output import Output
class ReturnBomb(AbstractDetector):
ARGUMENT = "return-bomb"
HELP = "A low level callee may consume all callers gas unexpectedly."
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#return-bomb"
WIKI_TITLE = "Return Bomb"
WIKI_DESCRIPTION = "A low level callee may consume all callers gas unexpectedly."
WIKI_EXPLOIT_SCENARIO = """
```solidity
//Modified from https://github.com/nomad-xyz/ExcessivelySafeCall
contract BadGuy {
function youveActivateMyTrapCard() external pure returns (bytes memory) {
assembly{
revert(0, 1000000)
}
}
}
contract Mark {
function oops(address badGuy) public{
bool success;
bytes memory ret;
// Mark pays a lot of gas for this copy
//(success, ret) = badGuy.call{gas:10000}(
(success, ret) = badGuy.call(
abi.encodeWithSelector(
BadGuy.youveActivateMyTrapCard.selector
)
);
// Mark may OOG here, preventing local state changes
//importantCleanup();
}
}
```
After Mark calls BadGuy bytes are copied from returndata to memory, the memory expansion cost is paid. This means that when using a standard solidity call, the callee can "returnbomb" the caller, imposing an arbitrary gas cost.
Callee unexpectedly makes the caller OOG.
"""
WIKI_RECOMMENDATION = "Avoid unlimited implicit decoding of returndata."
@staticmethod
def is_dynamic_type(ty: Type) -> bool:
# ty.is_dynamic ?
name = str(ty)
if "[]" in name or name in ("bytes", "string"):
return True
return False
def get_nodes_for_function(self, function: Function, contract: Contract) -> List[Node]:
nodes = []
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, (HighLevelCall, LowLevelCall)):
if not is_tainted(ir.destination, contract): # type:ignore
# Only interested if the target address is controlled/tainted
continue
if isinstance(ir, HighLevelCall) and isinstance(ir.function, Function):
# in normal highlevel calls return bombs are _possible_
# if the return type is dynamic and the caller tries to copy and decode large data
has_dyn = False
if ir.function.return_type:
has_dyn = any(
self.is_dynamic_type(ty) for ty in ir.function.return_type
)
if not has_dyn:
continue
# If a gas budget was specified then the
# user may not know about the return bomb
if ir.call_gas is None:
# if a gas budget was NOT specified then the caller
# may already suspect the call may spend all gas?
continue
nodes.append(node)
# TODO: check that there is some state change after the call
return nodes
def _detect(self) -> List[Output]:
results = []
for contract in self.compilation_unit.contracts:
for function in contract.functions_declared:
nodes = self.get_nodes_for_function(function, contract)
if nodes:
info: DETECTOR_INFO = [
function,
" tries to limit the gas of an external call that controls implicit decoding\n",
]
for node in sorted(nodes, key=lambda x: x.node_id):
info += ["\t", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,69 @@
from typing import List
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import (
Binary,
BinaryType,
)
from slither.core.declarations import Function
from slither.utils.output import Output
class TautologicalCompare(AbstractDetector):
"""
Same variable comparison detector
"""
ARGUMENT = "tautological-compare"
HELP = "Comparing a variable to itself always returns true or false, depending on comparison"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#tautological-compare"
WIKI_TITLE = "Tautological compare"
WIKI_DESCRIPTION = "A variable compared to itself is probably an error as it will always return `true` for `==`, `>=`, `<=` and always `false` for `<`, `>` and `!=`."
WIKI_EXPLOIT_SCENARIO = """
```solidity
function check(uint a) external returns(bool){
return (a >= a);
}
```
`check` always return true."""
WIKI_RECOMMENDATION = "Remove comparison or compare to different value."
def _check_function(self, f: Function) -> List[Output]:
affected_nodes = set()
for node in f.nodes:
for ir in node.irs:
if isinstance(ir, Binary):
if ir.type in [
BinaryType.GREATER,
BinaryType.GREATER_EQUAL,
BinaryType.LESS,
BinaryType.LESS_EQUAL,
BinaryType.EQUAL,
BinaryType.NOT_EQUAL,
]:
if ir.variable_left == ir.variable_right:
affected_nodes.add(node)
results = []
for n in affected_nodes:
info: DETECTOR_INFO = [f, " compares a variable to itself:\n\t", n, "\n"]
res = self.generate_result(info)
results.append(res)
return results
def _detect(self):
results = []
for f in self.compilation_unit.functions_and_modifiers:
results.extend(self._check_function(f))
return results

@ -36,7 +36,7 @@ class PredeclarationUsageLocal(AbstractDetector):
```solidity
contract C {
function f(uint z) public returns (uint) {
uint y = x + 9 + z; // 'z' is used pre-declaration
uint y = x + 9 + z; // 'x' is used pre-declaration
uint x = 7;
if (z % 2 == 0) {

@ -47,9 +47,7 @@ class SimilarVarsDetection(AbstractDetector):
Returns:
bool: true if names are similar
"""
if len(seq1) != len(seq2):
return False
val = difflib.SequenceMatcher(a=seq1.lower(), b=seq2.lower()).ratio()
val = difflib.SequenceMatcher(a=seq1, b=seq2).ratio()
ret = val > 0.90
return ret
@ -65,17 +63,23 @@ class SimilarVarsDetection(AbstractDetector):
contract_var = contract.variables
all_var = set(all_var + contract_var)
all_var = list(set(all_var + contract_var))
ret = set()
# pylint: disable=consider-using-enumerate
for i in range(len(all_var)):
v1 = all_var[i]
_v1_name_lower = v1.name.lower()
for j in range(i, len(all_var)):
v2 = all_var[j]
if len(v1.name) != len(v2.name):
continue
_v2_name_lower = v2.name.lower()
if _v1_name_lower != _v2_name_lower:
if SimilarVarsDetection.similar(_v1_name_lower, _v2_name_lower):
ret.add((v1, v2))
ret = []
for v1 in all_var:
for v2 in all_var:
if v1.name.lower() != v2.name.lower():
if SimilarVarsDetection.similar(v1.name, v2.name):
if (v2, v1) not in ret:
ret.append((v1, v2))
return set(ret)
return ret
def _detect(self) -> List[Output]:
"""Detect similar variables name

@ -87,6 +87,8 @@ class UnchangedStateVariables:
def detect(self) -> None:
"""Detect state variables that could be constant or immutable"""
for c in self.compilation_unit.contracts_derived:
if c.is_signature_only():
continue
variables = []
functions = []

@ -20,8 +20,6 @@ from slither.visitors.expression.export_values import ExportValues
def detect_unused(contract: Contract) -> Optional[List[StateVariable]]:
if contract.is_signature_only():
return None
# Get all the variables read in all the functions and modifiers
all_functions = [
@ -73,6 +71,8 @@ class UnusedStateVars(AbstractDetector):
"""Detect unused state variables"""
results = []
for c in self.compilation_unit.contracts_derived:
if c.is_signature_only():
continue
unusedVars = detect_unused(c)
if unusedVars:
for var in unusedVars:

@ -1,6 +1,7 @@
# pylint: disable=unused-import,relative-beyond-top-level
from .summary.function import FunctionSummary
from .summary.contract import ContractSummary
from .summary.loc import LocPrinter
from .inheritance.inheritance import PrinterInheritance
from .inheritance.inheritance_graph import PrinterInheritanceGraph
from .call.call_graph import PrinterCallGraph
@ -8,6 +9,8 @@ from .functions.authorization import PrinterWrittenVariablesAndAuthorization
from .summary.slithir import PrinterSlithIR
from .summary.slithir_ssa import PrinterSlithIRSSA
from .summary.human_summary import PrinterHumanSummary
from .summary.ck import CK
from .summary.halstead import Halstead
from .functions.cfg import CFG
from .summary.function_ids import FunctionIds
from .summary.variable_order import VariableOrder
@ -20,3 +23,4 @@ from .summary.evm import PrinterEVM
from .summary.when_not_paused import PrinterWhenNotPaused
from .summary.declaration import Declaration
from .functions.dominator import Dominator
from .summary.martin import Martin

@ -4,7 +4,7 @@ from typing import Dict, List, Set, Tuple, NamedTuple, Union
from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.cfg.node import Node
from slither.core.declarations import Enum, Function
from slither.core.declarations import Enum, Function, Contract
from slither.core.declarations.solidity_variables import (
SolidityVariableComposed,
SolidityFunction,
@ -30,9 +30,9 @@ from slither.slithir.operations import (
TypeConversion,
)
from slither.slithir.operations.binary import Binary
from slither.slithir.variables import Constant
from slither.slithir.variables import Constant, ReferenceVariable
from slither.utils.output import Output
from slither.visitors.expression.constants_folding import ConstantFolding
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant
def _get_name(f: Union[Function, Variable]) -> str:
@ -43,9 +43,9 @@ def _get_name(f: Union[Function, Variable]) -> str:
return f.solidity_signature
def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_payable(contracts: List[Contract]) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
payable_functions = [_get_name(f) for f in contract.functions_entry_points if f.payable]
if payable_functions:
ret[contract.name] = payable_functions
@ -53,10 +53,10 @@ def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_solidity_variable_usage(
slither: SlitherCore, sol_var: SolidityVariable
contracts: List[Contract], sol_var: SolidityVariable
) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
functions_using_sol_var = []
for f in contract.functions_entry_points:
for v in f.all_solidity_variables_read():
@ -114,9 +114,9 @@ def _is_constant(f: Function) -> bool: # pylint: disable=too-many-branches
return True
def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_constant_functions(contracts: List[Contract]) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)]
cst_functions += [
v.solidity_signature for v in contract.state_variables if v.visibility in ["public"]
@ -126,15 +126,29 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _extract_assert(slither: SlitherCore) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
functions_using_assert = []
def _extract_assert(contracts: List[Contract]) -> Dict[str, Dict[str, List[Dict]]]:
"""
Return the list of contract -> function name -> List(source mapping of the assert))
Args:
contracts: list of contracts
Returns:
"""
ret: Dict[str, Dict[str, List[Dict]]] = {}
for contract in contracts:
functions_using_assert = [] # Dict[str, List[Dict]] = defaultdict(list)
for f in contract.functions_entry_points:
for v in f.all_solidity_calls():
if v == SolidityFunction("assert(bool)"):
functions_using_assert.append(_get_name(f))
break
# Revert https://github.com/crytic/slither/pull/2105 until format is supported by echidna.
# for node in f.all_nodes():
# if SolidityFunction("assert(bool)") in node.solidity_calls and node.source_mapping:
# func_name = _get_name(f)
# functions_using_assert[func_name].append(node.source_mapping.to_json())
if functions_using_assert:
ret[contract.name] = functions_using_assert
return ret
@ -178,11 +192,16 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
all_cst_used_in_binary[str(ir.type)].append(
ConstantValue(str(r.value), str(r.type))
)
if isinstance(ir.variable_left, Constant) and isinstance(ir.variable_right, Constant):
if isinstance(ir.variable_left, Constant) or isinstance(
ir.variable_right, Constant
):
if ir.lvalue:
try:
type_ = ir.lvalue.type
cst = ConstantFolding(ir.expression, type_).result()
all_cst_used.append(ConstantValue(str(cst.value), str(type_)))
except NotConstant:
pass
if isinstance(ir, TypeConversion):
if isinstance(ir.variable, Constant):
if isinstance(ir.type, TypeAlias):
@ -203,19 +222,20 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
except ValueError: # index could fail; should never happen in working solidity code
pass
for r in ir.read:
var_read = r.points_to_origin if isinstance(r, ReferenceVariable) else r
# Do not report struct_name in a.struct_name
if isinstance(ir, Member):
continue
if isinstance(r, Constant):
all_cst_used.append(ConstantValue(str(r.value), str(r.type)))
if isinstance(r, StateVariable):
if r.node_initialization:
if r.node_initialization.irs:
if r.node_initialization in context_explored:
if isinstance(var_read, Constant):
all_cst_used.append(ConstantValue(str(var_read.value), str(var_read.type)))
if isinstance(var_read, StateVariable):
if var_read.node_initialization:
if var_read.node_initialization.irs:
if var_read.node_initialization in context_explored:
continue
context_explored.add(r.node_initialization)
context_explored.add(var_read.node_initialization)
_extract_constants_from_irs(
r.node_initialization.irs,
var_read.node_initialization.irs,
all_cst_used,
all_cst_used_in_binary,
context_explored,
@ -223,13 +243,13 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
def _extract_constants(
slither: SlitherCore,
contracts: List[Contract],
) -> Tuple[Dict[str, Dict[str, List]], Dict[str, Dict[str, Dict]]]:
# contract -> function -> [ {"value": value, "type": type} ]
ret_cst_used: Dict[str, Dict[str, List[ConstantValue]]] = defaultdict(dict)
# contract -> function -> binary_operand -> [ {"value": value, "type": type ]
ret_cst_used_in_binary: Dict[str, Dict[str, Dict[str, List[ConstantValue]]]] = defaultdict(dict)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
all_cst_used: List = []
all_cst_used_in_binary: Dict = defaultdict(list)
@ -255,11 +275,11 @@ def _extract_constants(
def _extract_function_relations(
slither: SlitherCore,
contracts: List[Contract],
) -> Dict[str, Dict[str, Dict[str, List[str]]]]:
# contract -> function -> [functions]
ret: Dict[str, Dict[str, Dict[str, List[str]]]] = defaultdict(dict)
for contract in slither.contracts:
for contract in contracts:
ret[contract.name] = defaultdict(dict)
written = {
_get_name(function): function.all_state_variables_written()
@ -283,14 +303,14 @@ def _extract_function_relations(
return ret
def _have_external_calls(slither: SlitherCore) -> Dict[str, List[str]]:
def _have_external_calls(contracts: List[Contract]) -> Dict[str, List[str]]:
"""
Detect the functions with external calls
:param slither:
:return:
"""
ret: Dict[str, List[str]] = defaultdict(list)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.all_high_level_calls() or function.all_low_level_calls():
ret[contract.name].append(_get_name(function))
@ -299,14 +319,14 @@ def _have_external_calls(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
def _use_balance(contracts: List[Contract]) -> Dict[str, List[str]]:
"""
Detect the functions with external calls
:param slither:
:return:
"""
ret: Dict[str, List[str]] = defaultdict(list)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
for ir in function.all_slithir_operations():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
@ -318,25 +338,25 @@ def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _with_fallback(slither: SlitherCore) -> Set[str]:
def _with_fallback(contracts: List[Contract]) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.is_fallback:
ret.add(contract.name)
return ret
def _with_receive(slither: SlitherCore) -> Set[str]:
def _with_receive(contracts: List[Contract]) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.is_receive:
ret.add(contract.name)
return ret
def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]:
def _call_a_parameter(slither: SlitherCore, contracts: List[Contract]) -> Dict[str, List[Dict]]:
"""
Detect the functions with external calls
:param slither:
@ -344,7 +364,7 @@ def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]:
"""
# contract -> [ (function, idx, interface_called) ]
ret: Dict[str, List[Dict]] = defaultdict(list)
for contract in slither.contracts: # pylint: disable=too-many-nested-blocks
for contract in contracts: # pylint: disable=too-many-nested-blocks
for function in contract.functions_entry_points:
try:
for ir in function.all_slithir_operations():
@ -390,40 +410,40 @@ class Echidna(AbstractPrinter):
_filename(string)
"""
payable = _extract_payable(self.slither)
contracts = self.slither.contracts
payable = _extract_payable(contracts)
timestamp = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("block.timestamp")
contracts, SolidityVariableComposed("block.timestamp")
)
block_number = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("block.number")
contracts, SolidityVariableComposed("block.number")
)
msg_sender = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.sender")
)
msg_gas = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.gas")
contracts, SolidityVariableComposed("msg.sender")
)
assert_usage = _extract_assert(self.slither)
cst_functions = _extract_constant_functions(self.slither)
(cst_used, cst_used_in_binary) = _extract_constants(self.slither)
msg_gas = _extract_solidity_variable_usage(contracts, SolidityVariableComposed("msg.gas"))
assert_usage = _extract_assert(contracts)
cst_functions = _extract_constant_functions(contracts)
(cst_used, cst_used_in_binary) = _extract_constants(contracts)
functions_relations = _extract_function_relations(self.slither)
functions_relations = _extract_function_relations(contracts)
constructors = {
contract.name: contract.constructor.full_name
for contract in self.slither.contracts
for contract in contracts
if contract.constructor
}
external_calls = _have_external_calls(self.slither)
external_calls = _have_external_calls(contracts)
call_parameters = _call_a_parameter(self.slither)
# call_parameters = _call_a_parameter(self.slither, contracts)
use_balance = _use_balance(self.slither)
use_balance = _use_balance(contracts)
with_fallback = list(_with_fallback(self.slither))
with_fallback = list(_with_fallback(contracts))
with_receive = list(_with_receive(self.slither))
with_receive = list(_with_receive(contracts))
d = {
"payable": payable,
@ -438,7 +458,7 @@ class Echidna(AbstractPrinter):
"functions_relations": functions_relations,
"constructors": constructors,
"have_external_calls": external_calls,
"call_a_parameter": call_parameters,
# "call_a_parameter": call_parameters,
"use_balance": use_balance,
"solc_versions": [unit.solc_version for unit in self.slither.compilation_units],
"with_fallback": with_fallback,

@ -0,0 +1,58 @@
"""
CK Metrics are a suite of six software metrics proposed by Chidamber and Kemerer in 1994.
These metrics are used to measure the complexity of a class.
https://en.wikipedia.org/wiki/Programming_complexity
- Response For a Class (RFC) is a metric that measures the number of unique method calls within a class.
- Number of Children (NOC) is a metric that measures the number of children a class has.
- Depth of Inheritance Tree (DIT) is a metric that measures the number of parent classes a class has.
- Coupling Between Object Classes (CBO) is a metric that measures the number of classes a class is coupled to.
Not implemented:
- Lack of Cohesion of Methods (LCOM) is a metric that measures the lack of cohesion in methods.
- Weighted Methods per Class (WMC) is a metric that measures the complexity of a class.
During the calculation of the metrics above, there are a number of other intermediate metrics that are calculated.
These are also included in the output:
- State variables: total number of state variables
- Constants: total number of constants
- Immutables: total number of immutables
- Public: total number of public functions
- External: total number of external functions
- Internal: total number of internal functions
- Private: total number of private functions
- Mutating: total number of state mutating functions
- View: total number of view functions
- Pure: total number of pure functions
- External mutating: total number of external mutating functions
- No auth or onlyOwner: total number of functions without auth or onlyOwner modifiers
- No modifiers: total number of functions without modifiers
- Ext calls: total number of external calls
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.ck import CKMetrics
from slither.utils.output import Output
class CK(AbstractPrinter):
ARGUMENT = "ck"
HELP = "Chidamber and Kemerer (CK) complexity metrics and related function attributes"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#ck"
def output(self, _filename: str) -> Output:
if len(self.contracts) == 0:
return self.generate_output("No contract found")
ck = CKMetrics(self.contracts)
res = self.generate_output(ck.full_text)
res.add_pretty_table(ck.auxiliary1.pretty_table, ck.auxiliary1.title)
res.add_pretty_table(ck.auxiliary2.pretty_table, ck.auxiliary2.title)
res.add_pretty_table(ck.auxiliary3.pretty_table, ck.auxiliary3.title)
res.add_pretty_table(ck.auxiliary4.pretty_table, ck.auxiliary4.title)
res.add_pretty_table(ck.core.pretty_table, ck.core.title)
self.info(ck.full_text)
return res

@ -0,0 +1,49 @@
"""
Halstead complexity metrics
https://en.wikipedia.org/wiki/Halstead_complexity_measures
12 metrics based on the number of unique operators and operands:
Core metrics:
n1 = the number of distinct operators
n2 = the number of distinct operands
N1 = the total number of operators
N2 = the total number of operands
Extended metrics1:
n = n1 + n2 # Program vocabulary
N = N1 + N2 # Program length
S = n1 * log2(n1) + n2 * log2(n2) # Estimated program length
V = N * log2(n) # Volume
Extended metrics2:
D = (n1 / 2) * (N2 / n2) # Difficulty
E = D * V # Effort
T = E / 18 seconds # Time required to program
B = (E^(2/3)) / 3000 # Number of delivered bugs
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.halstead import HalsteadMetrics
from slither.utils.output import Output
class Halstead(AbstractPrinter):
ARGUMENT = "halstead"
HELP = "Computes the Halstead complexity metrics for each contract"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#halstead"
def output(self, _filename: str) -> Output:
if len(self.contracts) == 0:
return self.generate_output("No contract found")
halstead = HalsteadMetrics(self.contracts)
res = self.generate_output(halstead.full_text)
res.add_pretty_table(halstead.core.pretty_table, halstead.core.title)
res.add_pretty_table(halstead.extended1.pretty_table, halstead.extended1.title)
res.add_pretty_table(halstead.extended2.pretty_table, halstead.extended2.title)
self.info(halstead.full_text)
return res

@ -2,12 +2,12 @@
Module printing summary of the contract
"""
import logging
from pathlib import Path
from typing import Tuple, List, Dict
from slither.core.declarations import SolidityFunction, Function
from slither.core.variables.state_variable import StateVariable
from slither.printers.abstract_printer import AbstractPrinter
from slither.printers.summary.loc import compute_loc_metrics
from slither.slithir.operations import (
LowLevelCall,
HighLevelCall,
@ -21,7 +21,6 @@ from slither.utils.colors import green, red, yellow
from slither.utils.myprettytable import MyPrettyTable
from slither.utils.standard_libraries import is_standard_library
from slither.core.cfg.node import NodeType
from slither.utils.tests_pattern import is_test_file
class PrinterHumanSummary(AbstractPrinter):
@ -32,7 +31,6 @@ class PrinterHumanSummary(AbstractPrinter):
@staticmethod
def _get_summary_erc20(contract):
functions_name = [f.name for f in contract.functions]
state_variables = [v.name for v in contract.state_variables]
@ -165,28 +163,7 @@ class PrinterHumanSummary(AbstractPrinter):
def _number_functions(contract):
return len(contract.functions)
def _lines_number(self):
if not self.slither.source_code:
return None
total_dep_lines = 0
total_lines = 0
total_tests_lines = 0
for filename, source_code in self.slither.source_code.items():
lines = len(source_code.splitlines())
is_dep = False
if self.slither.crytic_compile:
is_dep = self.slither.crytic_compile.is_dependency(filename)
if is_dep:
total_dep_lines += lines
else:
if is_test_file(Path(filename)):
total_tests_lines += lines
else:
total_lines += lines
return total_lines, total_dep_lines, total_tests_lines
def _get_number_of_assembly_lines(self):
def _get_number_of_assembly_lines(self) -> int:
total_asm_lines = 0
for contract in self.contracts:
for function in contract.functions_declared:
@ -202,9 +179,7 @@ class PrinterHumanSummary(AbstractPrinter):
return "Compilation non standard\n"
return f"Compiled with {str(self.slither.crytic_compile.type)}\n"
def _number_contracts(self):
if self.slither.crytic_compile is None:
return len(self.slither.contracts), 0, 0
def _number_contracts(self) -> Tuple[int, int, int]:
contracts = self.slither.contracts
deps = [c for c in contracts if c.is_from_dependency()]
tests = [c for c in contracts if c.is_test]
@ -226,7 +201,6 @@ class PrinterHumanSummary(AbstractPrinter):
return list(set(ercs))
def _get_features(self, contract): # pylint: disable=too-many-branches
has_payable = False
can_send_eth = False
can_selfdestruct = False
@ -291,6 +265,36 @@ class PrinterHumanSummary(AbstractPrinter):
"Proxy": contract.is_upgradeable_proxy,
}
def _get_contracts(self, txt: str) -> str:
(
number_contracts,
number_contracts_deps,
number_contracts_tests,
) = self._number_contracts()
txt += f"Total number of contracts in source files: {number_contracts}\n"
if number_contracts_deps > 0:
txt += f"Number of contracts in dependencies: {number_contracts_deps}\n"
if number_contracts_tests > 0:
txt += f"Number of contracts in tests : {number_contracts_tests}\n"
return txt
def _get_number_lines(self, txt: str, results: Dict) -> Tuple[str, Dict]:
loc = compute_loc_metrics(self.slither)
txt += "Source lines of code (SLOC) in source files: "
txt += f"{loc.src.sloc}\n"
if loc.dep.sloc > 0:
txt += "Source lines of code (SLOC) in dependencies: "
txt += f"{loc.dep.sloc}\n"
if loc.test.sloc > 0:
txt += "Source lines of code (SLOC) in tests : "
txt += f"{loc.test.sloc}\n"
results["number_lines"] = loc.src.sloc
results["number_lines__dependencies"] = loc.dep.sloc
total_asm_lines = self._get_number_of_assembly_lines()
txt += f"Number of assembly lines: {total_asm_lines}\n"
results["number_lines_assembly"] = total_asm_lines
return txt, results
def output(self, _filename): # pylint: disable=too-many-locals,too-many-statements
"""
_filename is not used
@ -311,24 +315,8 @@ class PrinterHumanSummary(AbstractPrinter):
"number_findings": {},
"detectors": [],
}
lines_number = self._lines_number()
if lines_number:
total_lines, total_dep_lines, total_tests_lines = lines_number
txt += f"Number of lines: {total_lines} (+ {total_dep_lines} in dependencies, + {total_tests_lines} in tests)\n"
results["number_lines"] = total_lines
results["number_lines__dependencies"] = total_dep_lines
total_asm_lines = self._get_number_of_assembly_lines()
txt += f"Number of assembly lines: {total_asm_lines}\n"
results["number_lines_assembly"] = total_asm_lines
(
number_contracts,
number_contracts_deps,
number_contracts_tests,
) = self._number_contracts()
txt += f"Number of contracts: {number_contracts} (+ {number_contracts_deps} in dependencies, + {number_contracts_tests} tests) \n\n"
txt = self._get_contracts(txt)
txt, results = self._get_number_lines(txt, results)
(
txt_detectors,
detectors_results,
@ -352,7 +340,7 @@ class PrinterHumanSummary(AbstractPrinter):
libs = self._standard_libraries()
if libs:
txt += f'\nUse: {", ".join(libs)}\n'
results["standard_libraries"] = [str(l) for l in libs]
results["standard_libraries"] = [str(lib) for lib in libs]
ercs = self._ercs()
if ercs:
@ -363,7 +351,6 @@ class PrinterHumanSummary(AbstractPrinter):
["Name", "# functions", "ERCS", "ERC20 info", "Complex code", "Features"]
)
for contract in self.slither.contracts_derived:
if contract.is_from_dependency() or contract.is_test:
continue

@ -0,0 +1,35 @@
"""
Lines of Code (LOC) printer
Definitions:
cloc: comment lines of code containing only comments
sloc: source lines of code with no whitespace or comments
loc: all lines of code including whitespace and comments
src: source files (excluding tests and dependencies)
dep: dependency files
test: test files
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.loc import compute_loc_metrics
from slither.utils.output import Output
class LocPrinter(AbstractPrinter):
ARGUMENT = "loc"
HELP = """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)."""
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#loc"
def output(self, _filename: str) -> Output:
# compute loc metrics
loc = compute_loc_metrics(self.slither)
table = loc.to_pretty_table()
txt = "Lines of Code \n" + str(table)
self.info(txt)
res = self.generate_output(txt)
res.add_pretty_table(table, "Code Lines")
return res

@ -0,0 +1,32 @@
"""
Robert "Uncle Bob" Martin - Agile software metrics
https://en.wikipedia.org/wiki/Software_package_metrics
Efferent Coupling (Ce): Number of contracts that the contract depends on
Afferent Coupling (Ca): Number of contracts that depend on a contract
Instability (I): Ratio of efferent coupling to total coupling (Ce / (Ce + Ca))
Abstractness (A): Number of abstract contracts / total number of contracts
Distance from the Main Sequence (D): abs(A + I - 1)
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.martin import MartinMetrics
from slither.utils.output import Output
class Martin(AbstractPrinter):
ARGUMENT = "martin"
HELP = "Martin agile software metrics (Ca, Ce, I, A, D)"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#martin"
def output(self, _filename: str) -> Output:
if len(self.contracts) == 0:
return self.generate_output("No contract found")
martin = MartinMetrics(self.contracts)
res = self.generate_output(martin.full_text)
res.add_pretty_table(martin.core.pretty_table, martin.core.title)
self.info(martin.full_text)
return res

@ -11,7 +11,9 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi
from slither.exceptions import SlitherError
from slither.printers.abstract_printer import AbstractPrinter
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.vyper_parsing.vyper_compilation_unit import VyperCompilationUnit
from slither.utils.output import Output
from slither.vyper_parsing.ast.ast import parse
logger = logging.getLogger("Slither")
logging.basicConfig()
@ -48,7 +50,9 @@ def _update_file_scopes(candidates: ValuesView[FileScope]):
learned_something = False
class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
class Slither(
SlitherCore
): # pylint: disable=too-many-instance-attributes,too-many-locals,too-many-statements
def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None:
"""
Args:
@ -62,16 +66,6 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
triage_mode (bool): if true, switch to triage mode (default false)
exclude_dependencies (bool): if true, exclude results that are only related to dependencies
generate_patches (bool): if true, patches are generated (json output only)
truffle_ignore (bool): ignore truffle.js presence (default false)
truffle_build_directory (str): build truffle directory (default 'build/contracts')
truffle_ignore_compile (bool): do not run truffle compile (default False)
truffle_version (str): use a specific truffle version (default None)
embark_ignore (bool): ignore embark.js presence (default false)
embark_ignore_compile (bool): do not run embark build (default False)
embark_overwrite_config (bool): overwrite original config file (default false)
change_line_prefix (str): Change the line prefix (default #)
for the displayed source codes (i.e. file.sol#1).
@ -108,10 +102,20 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
for compilation_unit in crytic_compile.compilation_units.values():
compilation_unit_slither = SlitherCompilationUnit(self, compilation_unit)
self._compilation_units.append(compilation_unit_slither)
parser = SlitherCompilationUnitSolc(compilation_unit_slither)
self._parsers.append(parser)
if compilation_unit_slither.is_vyper:
vyper_parser = VyperCompilationUnit(compilation_unit_slither)
for path, ast in compilation_unit.asts.items():
parser.parse_top_level_from_loaded_json(ast, path)
ast_nodes = parse(ast["ast"])
vyper_parser.parse_module(ast_nodes, path)
self._parsers.append(vyper_parser)
else:
# Solidity specific
assert compilation_unit_slither.is_solidity
sol_parser = SlitherCompilationUnitSolc(compilation_unit_slither)
self._parsers.append(sol_parser)
for path, ast in compilation_unit.asts.items():
sol_parser.parse_top_level_items(ast, path)
self.add_source_code(path)
_update_file_scopes(compilation_unit_slither.scopes.values())
@ -132,6 +136,11 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
triage_mode = kwargs.get("triage_mode", False)
self._triage_mode = triage_mode
printers_to_run = kwargs.get("printers_to_run", "")
if printers_to_run == "echidna":
self.skip_data_dependency = True
self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))
def _init_parsing_and_analyses(self, skip_analyze: bool) -> None:

@ -114,8 +114,8 @@ def convert_expression(expression: Expression, node: "Node") -> List[Operation]:
visitor = ExpressionToSlithIR(expression, node)
result = visitor.result()
result = apply_ir_heuristics(result, node)
is_solidity = node.compilation_unit.is_solidity
result = apply_ir_heuristics(result, node, is_solidity)
if result:
if node.type in [NodeType.IF, NodeType.IFLOOP]:
@ -385,6 +385,70 @@ def integrate_value_gas(result: List[Operation]) -> List[Operation]:
###################################################################################
def get_declared_param_names(
ins: Union[
NewStructure,
NewContract,
InternalCall,
LibraryCall,
HighLevelCall,
InternalDynamicCall,
EventCall,
]
) -> Optional[List[str]]:
"""
Given a call operation, return the list of parameter names, in the order
listed in the function declaration.
#### Parameters
ins -
The call instruction
#### Possible Returns
List[str] -
A list of the parameters in declaration order
None -
Workaround: Unable to obtain list of parameters in declaration order
"""
if isinstance(ins, NewStructure):
return [x.name for x in ins.structure.elems_ordered if not isinstance(x.type, MappingType)]
if isinstance(ins, (InternalCall, LibraryCall, HighLevelCall)):
if isinstance(ins.function, Function):
return [p.name for p in ins.function.parameters]
return None
if isinstance(ins, InternalDynamicCall):
return [p.name for p in ins.function_type.params]
assert isinstance(ins, (EventCall, NewContract))
return None
def reorder_arguments(
args: List[Variable], call_names: List[str], decl_names: List[str]
) -> List[Variable]:
"""
Reorder named struct constructor arguments so that they match struct declaration ordering rather
than call ordering
E.g. for `struct S { int x; int y; }` we reorder `S({y : 2, x : 3})` to `S(3, 2)`
#### Parameters
args -
Arguments to constructor call, in call order
names -
Parameter names in call order
decl_names -
Parameter names in declaration order
#### Returns
Reordered arguments to constructor call, now in declaration order
"""
assert len(args) == len(call_names)
assert len(call_names) == len(decl_names)
args_ret = []
for n in decl_names:
ind = call_names.index(n)
args_ret.append(args[ind])
return args_ret
def propagate_type_and_convert_call(result: List[Operation], node: "Node") -> List[Operation]:
"""
Propagate the types variables and convert tmp call to real call operation
@ -434,6 +498,23 @@ def propagate_type_and_convert_call(result: List[Operation], node: "Node") -> Li
if ins.call_id in calls_gas and isinstance(ins, (HighLevelCall, InternalDynamicCall)):
ins.call_gas = calls_gas[ins.call_id]
if isinstance(ins, Call) and (ins.names is not None):
assert isinstance(
ins,
(
NewStructure,
NewContract,
InternalCall,
LibraryCall,
HighLevelCall,
InternalDynamicCall,
EventCall,
),
)
decl_param_names = get_declared_param_names(ins)
if decl_param_names is not None:
call_data = reorder_arguments(call_data, ins.names, decl_param_names)
if isinstance(ins, (Call, NewContract, NewStructure)):
# We might have stored some arguments for libraries
if ins.arguments:
@ -549,6 +630,17 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
if new_ir:
return new_ir
# convert library function when used with "this"
if (
isinstance(t, ElementaryType)
and t.name == "address"
and ir.destination.name == "this"
and UserDefinedType(node_function.contract) in using_for
):
new_ir = convert_to_library_or_top_level(ir, node, using_for)
if new_ir:
return new_ir
if isinstance(t, UserDefinedType):
# UserdefinedType
t_type = t.type
@ -576,7 +668,9 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
if isinstance(t, ArrayType) or (
isinstance(t, ElementaryType) and t.type == "bytes"
):
if ir.function_name == "push" and len(ir.arguments) <= 1:
# Solidity uses push
# Vyper uses append
if ir.function_name in ["push", "append"] and len(ir.arguments) <= 1:
return convert_to_push(ir, node)
if ir.function_name == "pop" and len(ir.arguments) == 0:
return convert_to_pop(ir, node)
@ -855,7 +949,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
if isinstance(ins.ori.variable_left, Contract):
st = ins.ori.variable_left.get_structure_from_name(ins.ori.variable_right)
if st:
op = NewStructure(st, ins.lvalue)
op = NewStructure(st, ins.lvalue, names=ins.names)
op.set_expression(ins.expression)
op.call_id = ins.call_id
return op
@ -892,6 +986,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
names=ins.names,
)
libcall.set_expression(ins.expression)
libcall.call_id = ins.call_id
@ -950,6 +1045,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
len(lib_func.parameters),
ins.lvalue,
"d",
names=ins.names,
)
lib_call.set_expression(ins.expression)
lib_call.set_node(ins.node)
@ -1031,6 +1127,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
names=ins.names,
)
msgcall.call_id = ins.call_id
@ -1082,7 +1179,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
return n
if isinstance(ins.called, Structure):
op = NewStructure(ins.called, ins.lvalue)
op = NewStructure(ins.called, ins.lvalue, names=ins.names)
op.set_expression(ins.expression)
op.call_id = ins.call_id
op.set_expression(ins.expression)
@ -1106,7 +1203,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
if len(ins.called.constructor.parameters) != ins.nbr_arguments:
return Nop()
internalcall = InternalCall(
ins.called.constructor, ins.nbr_arguments, ins.lvalue, ins.type_call
ins.called.constructor, ins.nbr_arguments, ins.lvalue, ins.type_call, ins.names
)
internalcall.call_id = ins.call_id
internalcall.set_expression(ins.expression)
@ -1131,6 +1228,7 @@ def can_be_low_level(ir: HighLevelCall) -> bool:
"delegatecall",
"callcode",
"staticcall",
"raw_call",
]
@ -1159,13 +1257,14 @@ def convert_to_low_level(
ir.set_node(prev_ir.node)
ir.lvalue.set_type(ElementaryType("bool"))
return ir
if ir.function_name in ["call", "delegatecall", "callcode", "staticcall"]:
if ir.function_name in ["call", "delegatecall", "callcode", "staticcall", "raw_call"]:
new_ir = LowLevelCall(
ir.destination, ir.function_name, ir.nbr_arguments, ir.lvalue, ir.type_call
)
new_ir.call_gas = ir.call_gas
new_ir.call_value = ir.call_value
new_ir.arguments = ir.arguments
# TODO fix this for Vyper
if ir.node.compilation_unit.solc_version >= "0.5":
new_ir.lvalue.set_type([ElementaryType("bool"), ElementaryType("bytes")])
else:
@ -1210,7 +1309,12 @@ def convert_to_solidity_func(
and len(new_ir.arguments) == 2
and isinstance(new_ir.arguments[1], list)
):
types = list(new_ir.arguments[1])
types = []
for arg_type in new_ir.arguments[1]:
decode_type = arg_type
if isinstance(decode_type, (Structure, Enum, Contract)):
decode_type = UserDefinedType(decode_type)
types.append(decode_type)
new_ir.lvalue.set_type(types)
# abi.decode where the type to decode is a singleton
# abi.decode(a, (uint))
@ -1363,11 +1467,12 @@ def convert_to_pop(ir: HighLevelCall, node: "Node") -> List[Operation]:
# TODO the following is equivalent to length.points_to = arr
# Should it be removed?
ir_length.lvalue.points_to = arr
# Note bytes is an ElementaryType not ArrayType so in that case we use ir.destination.type
# Note bytes is an ElementaryType not ArrayType and bytes1 should be returned
# since bytes is bytes1[] without padding between the elements
# while in other cases such as uint256[] (ArrayType) we use ir.destination.type.type
# in this way we will have the type always set to the corresponding ElementaryType
element_to_delete.set_type(
ir.destination.type
ElementaryType("bytes1")
if isinstance(ir.destination.type, ElementaryType)
else ir.destination.type.type
)
@ -1439,6 +1544,7 @@ def look_for_library_or_top_level(
ir.nbr_arguments,
ir.lvalue,
ir.type_call,
names=ir.names,
)
lib_call.set_expression(ir.expression)
lib_call.set_node(ir.node)
@ -1469,6 +1575,18 @@ def convert_to_library_or_top_level(
if new_ir:
return new_ir
if (
isinstance(t, ElementaryType)
and t.name == "address"
and ir.destination.name == "this"
and UserDefinedType(node.function.contract) in using_for
):
new_ir = look_for_library_or_top_level(
contract, ir, using_for, UserDefinedType(node.function.contract)
)
if new_ir:
return new_ir
return None
@ -1583,7 +1701,9 @@ def _convert_to_structure_to_list(return_type: Type) -> List[Type]:
# }
if isinstance(return_type, (MappingType, ArrayType)):
return []
return [return_type.type]
assert isinstance(return_type, (ElementaryType, UserDefinedType, TypeAlias))
return [return_type]
def convert_type_of_high_and_internal_level_call(
@ -1854,7 +1974,7 @@ def convert_constant_types(irs: List[Operation]) -> None:
if isinstance(ir.lvalue.type.type, ElementaryType):
if ir.lvalue.type.type.type in ElementaryTypeInt:
for r in ir.read:
if r.type.type not in ElementaryTypeInt:
if r.type.type.type not in ElementaryTypeInt:
r.set_type(ElementaryType(ir.lvalue.type.type.type))
was_changed = True
@ -1903,7 +2023,7 @@ def _find_source_mapping_references(irs: List[Operation]) -> None:
###################################################################################
def apply_ir_heuristics(irs: List[Operation], node: "Node") -> List[Operation]:
def apply_ir_heuristics(irs: List[Operation], node: "Node", is_solidity: bool) -> List[Operation]:
"""
Apply a set of heuristic to improve slithIR
"""
@ -1913,6 +2033,9 @@ def apply_ir_heuristics(irs: List[Operation], node: "Node") -> List[Operation]:
irs = propagate_type_and_convert_call(irs, node)
irs = remove_unused(irs)
find_references_origin(irs)
# These are heuristics that are only applied to Solidity
if is_solidity:
convert_constant_types(irs)
convert_delete(irs)

@ -6,9 +6,25 @@ from slither.slithir.operations.operation import Operation
class Call(Operation):
def __init__(self) -> None:
def __init__(self, names: Optional[List[str]] = None) -> None:
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert (names is None) or isinstance(names, list)
super().__init__()
self._arguments: List[Variable] = []
self._names = names
@property
def names(self) -> Optional[List[str]]:
"""
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
return self._names
@property
def arguments(self) -> List[Variable]:

@ -28,11 +28,18 @@ class HighLevelCall(Call, OperationWithLValue):
nbr_arguments: int,
result: Optional[Union[TemporaryVariable, TupleVariable, TemporaryVariableSSA]],
type_call: str,
names: Optional[List[str]] = None,
) -> None:
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(function_name, Constant)
assert is_valid_lvalue(result) or result is None
self._check_destination(destination)
super().__init__()
super().__init__(names=names)
# Contract is only possible for library call, which inherits from highlevelcall
self._destination: Union[Variable, SolidityVariable, Contract] = destination # type: ignore
self._function_name = function_name

@ -3,6 +3,7 @@ from typing import List, Union
from slither.core.declarations import SolidityVariableComposed
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.variable import Variable
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue, RVALUE, LVALUE
from slither.slithir.variables.reference import ReferenceVariable
@ -13,8 +14,10 @@ class Index(OperationWithLValue):
self, result: ReferenceVariable, left_variable: Variable, right_variable: RVALUE
) -> None:
super().__init__()
assert is_valid_lvalue(left_variable) or left_variable == SolidityVariableComposed(
"msg.data"
assert (
is_valid_lvalue(left_variable)
or left_variable == SolidityVariableComposed("msg.data")
or isinstance(left_variable, TopLevelVariable)
)
assert is_valid_rvalue(right_variable)
assert isinstance(result, ReferenceVariable)

@ -20,8 +20,16 @@ class InternalCall(Call, OperationWithLValue): # pylint: disable=too-many-insta
Union[TupleVariableSSA, TemporaryVariableSSA, TupleVariable, TemporaryVariable]
],
type_call: str,
names: Optional[List[str]] = None,
) -> None:
super().__init__()
# pylint: disable=too-many-arguments
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
super().__init__(names=names)
self._contract_name = ""
if isinstance(function, Function):
self._function: Optional[Function] = function

@ -33,4 +33,4 @@ class NewArray(Call, OperationWithLValue):
def __str__(self):
args = [str(a) for a in self.arguments]
lvalue = self.lvalue
return f"{lvalue}{lvalue.type}) = new {self.array_type}({','.join(args)})"
return f"{lvalue}({lvalue.type}) = new {self.array_type}({','.join(args)})"

@ -12,11 +12,20 @@ from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes
def __init__(
self, contract_name: Constant, lvalue: Union[TemporaryVariableSSA, TemporaryVariable]
self,
contract_name: Constant,
lvalue: Union[TemporaryVariableSSA, TemporaryVariable],
names: Optional[List[str]] = None,
) -> None:
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(contract_name, Constant)
assert is_valid_lvalue(lvalue)
super().__init__()
super().__init__(names=names)
self._contract_name = contract_name
# todo create analyze to add the contract instance
self._lvalue = lvalue

@ -1,4 +1,4 @@
from typing import List, Union
from typing import List, Optional, Union
from slither.slithir.operations.call import Call
from slither.slithir.operations.lvalue import OperationWithLValue
@ -17,8 +17,15 @@ class NewStructure(Call, OperationWithLValue):
self,
structure: StructureContract,
lvalue: Union[TemporaryVariableSSA, TemporaryVariable],
names: Optional[List[str]] = None,
) -> None:
super().__init__()
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
super().__init__(names=names)
assert isinstance(structure, Structure)
assert is_valid_lvalue(lvalue)
self._structure = structure

@ -4,6 +4,7 @@ from slither.core.declarations import Contract
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type_alias import TypeAlias
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
@ -21,10 +22,10 @@ class TypeConversion(OperationWithLValue):
super().__init__()
assert is_valid_rvalue(variable) or isinstance(variable, Contract)
assert is_valid_lvalue(result)
assert isinstance(variable_type, (TypeAlias, UserDefinedType, ElementaryType))
assert isinstance(variable_type, (TypeAlias, UserDefinedType, ElementaryType, ArrayType))
self._variable = variable
self._type: Union[TypeAlias, UserDefinedType, ElementaryType] = variable_type
self._type: Union[TypeAlias, UserDefinedType, ElementaryType, ArrayType] = variable_type
self._lvalue = result
@property

@ -5,7 +5,6 @@ from enum import Enum
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
from slither.slithir.exceptions import SlithIRError
from slither.core.expressions.unary_operation import UnaryOperationType
from slither.core.variables.local_variable import LocalVariable
from slither.slithir.variables.constant import Constant
from slither.slithir.variables.local_variable import LocalIRVariable
@ -35,7 +34,7 @@ class Unary(OperationWithLValue):
self,
result: Union[TemporaryVariableSSA, TemporaryVariable],
variable: Union[Constant, LocalIRVariable, LocalVariable],
operation_type: UnaryOperationType,
operation_type: UnaryType,
) -> None:
assert is_valid_rvalue(variable)
assert is_valid_lvalue(result)
@ -53,7 +52,7 @@ class Unary(OperationWithLValue):
return self._variable
@property
def type(self) -> UnaryOperationType:
def type(self) -> UnaryType:
return self._type
@property

@ -1,4 +1,4 @@
from typing import Optional, Union
from typing import List, Optional, Union
from slither.core.declarations import (
Event,
@ -25,7 +25,15 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
nbr_arguments: int,
result: Union[TupleVariable, TemporaryVariable],
type_call: str,
names: Optional[List[str]] = None,
) -> None:
# pylint: disable=too-many-arguments
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(
called,
(
@ -42,6 +50,7 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
self._called = called
self._nbr_arguments = nbr_arguments
self._type_call = type_call
self._names = names
self._lvalue = result
self._ori = None #
self._callid = None
@ -49,6 +58,14 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
self._value = None
self._salt = None
@property
def names(self) -> Optional[List[str]]:
"""
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
return self._names
@property
def call_value(self):
return self._value

@ -735,12 +735,17 @@ def copy_ir(ir: Operation, *instances) -> Operation:
destination = get_variable(ir, lambda x: x.destination, *instances)
function_name = ir.function_name
nbr_arguments = ir.nbr_arguments
names = ir.names
lvalue = get_variable(ir, lambda x: x.lvalue, *instances)
type_call = ir.type_call
if isinstance(ir, LibraryCall):
new_ir = LibraryCall(destination, function_name, nbr_arguments, lvalue, type_call)
new_ir = LibraryCall(
destination, function_name, nbr_arguments, lvalue, type_call, names=names
)
else:
new_ir = HighLevelCall(destination, function_name, nbr_arguments, lvalue, type_call)
new_ir = HighLevelCall(
destination, function_name, nbr_arguments, lvalue, type_call, names=names
)
new_ir.call_id = ir.call_id
new_ir.call_value = get_variable(ir, lambda x: x.call_value, *instances)
new_ir.call_gas = get_variable(ir, lambda x: x.call_gas, *instances)
@ -761,7 +766,8 @@ def copy_ir(ir: Operation, *instances) -> Operation:
nbr_arguments = ir.nbr_arguments
lvalue = get_variable(ir, lambda x: x.lvalue, *instances)
type_call = ir.type_call
new_ir = InternalCall(function, nbr_arguments, lvalue, type_call)
names = ir.names
new_ir = InternalCall(function, nbr_arguments, lvalue, type_call, names=names)
new_ir.arguments = get_arguments(ir, *instances)
return new_ir
if isinstance(ir, InternalDynamicCall):
@ -811,7 +817,8 @@ def copy_ir(ir: Operation, *instances) -> Operation:
if isinstance(ir, NewStructure):
structure = ir.structure
lvalue = get_variable(ir, lambda x: x.lvalue, *instances)
new_ir = NewStructure(structure, lvalue)
names = ir.names
new_ir = NewStructure(structure, lvalue, names=names)
new_ir.arguments = get_arguments(ir, *instances)
return new_ir
if isinstance(ir, Nop):

@ -11,7 +11,7 @@ from slither.utils.integer_conversion import convert_string_to_int
class Constant(SlithIRVariable):
def __init__(
self,
val: Union[int, str],
val: str,
constant_type: Optional[ElementaryType] = None,
subdenomination: Optional[str] = None,
) -> None: # pylint: disable=too-many-branches

@ -2,6 +2,7 @@ from typing import Optional, TYPE_CHECKING
from slither.core.declarations import Contract, Enum, SolidityVariable, Function
from slither.core.variables.variable import Variable
from slither.core.variables.top_level_variable import TopLevelVariable
if TYPE_CHECKING:
from slither.core.cfg.node import Node
@ -46,7 +47,7 @@ class ReferenceVariable(Variable):
from slither.slithir.utils.utils import is_valid_lvalue
assert is_valid_lvalue(points_to) or isinstance(
points_to, (SolidityVariable, Contract, Enum)
points_to, (SolidityVariable, Contract, Enum, TopLevelVariable)
)
self._points_to = points_to

@ -4,7 +4,7 @@ from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequenc
from slither.core.declarations import (
Modifier,
Event,
EventContract,
EnumContract,
StructureContract,
Function,
@ -291,10 +291,10 @@ class ContractSolc(CallerContextExpression):
alias = item["name"]
alias_canonical = self._contract.name + "." + item["name"]
user_defined_type = TypeAliasContract(original_type, alias, self.underlying_contract)
user_defined_type.set_offset(item["src"], self.compilation_unit)
self._contract.file_scope.user_defined_types[alias] = user_defined_type
self._contract.file_scope.user_defined_types[alias_canonical] = user_defined_type
type_alias = TypeAliasContract(original_type, alias, self.underlying_contract)
type_alias.set_offset(item["src"], self.compilation_unit)
self._contract.type_aliases_as_dict[alias] = type_alias
self._contract.file_scope.type_aliases[alias_canonical] = type_alias
def _parse_struct(self, struct: Dict) -> None:
@ -319,7 +319,7 @@ class ContractSolc(CallerContextExpression):
ce.set_contract(self._contract)
ce.set_offset(custom_error["src"], self.compilation_unit)
ce_parser = CustomErrorSolc(ce, custom_error, self._slither_parser)
ce_parser = CustomErrorSolc(ce, custom_error, self, self._slither_parser)
self._contract.custom_errors_as_dict[ce.name] = ce
self._custom_errors_parser.append(ce_parser)
@ -357,6 +357,8 @@ class ContractSolc(CallerContextExpression):
self._variables_parser.append(var_parser)
assert var.name
if var_parser.reference_id is not None:
self._contract.state_variables_by_ref_id[var_parser.reference_id] = var
self._contract.variables_as_dict[var.name] = var
self._contract.add_variables_ordered([var])
@ -745,12 +747,12 @@ class ContractSolc(CallerContextExpression):
self._contract.events_as_dict.update(father.events_as_dict)
for event_to_parse in self._eventsNotParsed:
event = Event()
event = EventContract()
event.set_contract(self._contract)
event.set_offset(event_to_parse["src"], self._contract.compilation_unit)
event_parser = EventSolc(event, event_to_parse, self) # type: ignore
event_parser.analyze(self) # type: ignore
event_parser = EventSolc(event, event_to_parse, self._slither_parser) # type: ignore
event_parser.analyze() # type: ignore
self._contract.events_as_dict[event.full_name] = event
except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing event {e}")

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Dict
from typing import TYPE_CHECKING, Dict, Optional
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.custom_error_contract import CustomErrorContract
@ -10,6 +10,7 @@ from slither.solc_parsing.variables.local_variable import LocalVariableSolc
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.solc_parsing.declarations.contract import ContractSolc
# Part of the code was copied from the function parsing
@ -21,11 +22,13 @@ class CustomErrorSolc(CallerContextExpression):
self,
custom_error: CustomError,
custom_error_data: dict,
contract_parser: Optional["ContractSolc"],
slither_parser: "SlitherCompilationUnitSolc",
) -> None:
self._slither_parser: "SlitherCompilationUnitSolc" = slither_parser
self._custom_error = custom_error
custom_error.name = custom_error_data["name"]
self._contract_parser = contract_parser
self._params_was_analyzed = False
if not self._slither_parser.is_compact_ast:
@ -56,6 +59,10 @@ class CustomErrorSolc(CallerContextExpression):
if params:
self._parse_params(params)
@property
def contract_parser(self) -> Optional["ContractSolc"]:
return self._contract_parser
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast

@ -8,7 +8,7 @@ from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.core.declarations.event import Event
if TYPE_CHECKING:
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
class EventSolc:
@ -16,11 +16,12 @@ class EventSolc:
Event class
"""
def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSolc") -> None:
def __init__(
self, event: Event, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc"
) -> None:
self._event = event
event.set_contract(contract_parser.underlying_contract)
self._parser_contract = contract_parser
self._slither_parser = slither_parser
if self.is_compact_ast:
self._event.name = event_data["name"]
@ -41,18 +42,16 @@ class EventSolc:
@property
def is_compact_ast(self) -> bool:
return self._parser_contract.is_compact_ast
return self._slither_parser.is_compact_ast
def analyze(self, contract: "ContractSolc") -> None:
def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed:
elem = EventVariable()
# Todo: check if the source offset is always here
if "src" in elem_to_parse:
elem.set_offset(
elem_to_parse["src"], self._parser_contract.underlying_contract.compilation_unit
)
elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(contract)
elem_parser.analyze(self._slither_parser)
self._event.elems.append(elem)

@ -321,6 +321,9 @@ class FunctionSolc(CallerContextExpression):
if return_ast:
self._fix_implicit_return(return_ast)
if self._function.entry_point:
self._update_reachability(self._function.entry_point)
# endregion
###################################################################################
###################################################################################
@ -360,7 +363,7 @@ class FunctionSolc(CallerContextExpression):
###################################################################################
###################################################################################
def _parse_if(self, if_statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_if(self, if_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
# IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
falseStatement = None
@ -368,21 +371,15 @@ class FunctionSolc(CallerContextExpression):
condition = if_statement["condition"]
# Note: check if the expression could be directly
# parsed here
condition_node = self._new_node(
NodeType.IF, condition["src"], node.underlying_node.scope
)
condition_node = self._new_node(NodeType.IF, condition["src"], scope)
condition_node.add_unparsed_expression(condition)
link_underlying_nodes(node, condition_node)
true_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
true_scope = Scope(scope.is_checked, False, scope)
trueStatement = self._parse_statement(
if_statement["trueBody"], condition_node, true_scope
)
if "falseBody" in if_statement and if_statement["falseBody"]:
false_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
false_scope = Scope(scope.is_checked, False, scope)
falseStatement = self._parse_statement(
if_statement["falseBody"], condition_node, false_scope
)
@ -391,22 +388,16 @@ class FunctionSolc(CallerContextExpression):
condition = children[0]
# Note: check if the expression could be directly
# parsed here
condition_node = self._new_node(
NodeType.IF, condition["src"], node.underlying_node.scope
)
condition_node = self._new_node(NodeType.IF, condition["src"], scope)
condition_node.add_unparsed_expression(condition)
link_underlying_nodes(node, condition_node)
true_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
true_scope = Scope(scope.is_checked, False, scope)
trueStatement = self._parse_statement(children[1], condition_node, true_scope)
if len(children) == 3:
false_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
false_scope = Scope(scope.is_checked, False, scope)
falseStatement = self._parse_statement(children[2], condition_node, false_scope)
endIf_node = self._new_node(NodeType.ENDIF, if_statement["src"], node.underlying_node.scope)
endIf_node = self._new_node(NodeType.ENDIF, if_statement["src"], scope)
link_underlying_nodes(trueStatement, endIf_node)
if falseStatement:
@ -415,32 +406,26 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(condition_node, endIf_node)
return endIf_node
def _parse_while(self, whilte_statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_while(self, whilte_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
# WhileStatement = 'while' '(' Expression ')' Statement
node_startWhile = self._new_node(
NodeType.STARTLOOP, whilte_statement["src"], node.underlying_node.scope
)
node_startWhile = self._new_node(NodeType.STARTLOOP, whilte_statement["src"], scope)
body_scope = Scope(node.underlying_node.scope.is_checked, False, node.underlying_node.scope)
body_scope = Scope(scope.is_checked, False, scope)
if self.is_compact_ast:
node_condition = self._new_node(
NodeType.IFLOOP, whilte_statement["condition"]["src"], node.underlying_node.scope
NodeType.IFLOOP, whilte_statement["condition"]["src"], scope
)
node_condition.add_unparsed_expression(whilte_statement["condition"])
statement = self._parse_statement(whilte_statement["body"], node_condition, body_scope)
else:
children = whilte_statement[self.get_children("children")]
expression = children[0]
node_condition = self._new_node(
NodeType.IFLOOP, expression["src"], node.underlying_node.scope
)
node_condition = self._new_node(NodeType.IFLOOP, expression["src"], scope)
node_condition.add_unparsed_expression(expression)
statement = self._parse_statement(children[1], node_condition, body_scope)
node_endWhile = self._new_node(
NodeType.ENDLOOP, whilte_statement["src"], node.underlying_node.scope
)
node_endWhile = self._new_node(NodeType.ENDLOOP, whilte_statement["src"], scope)
link_underlying_nodes(node, node_startWhile)
link_underlying_nodes(node_startWhile, node_condition)
@ -568,7 +553,7 @@ class FunctionSolc(CallerContextExpression):
return pre, cond, post, body
def _parse_for(self, statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_for(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
# ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
if self.is_compact_ast:
@ -576,17 +561,13 @@ class FunctionSolc(CallerContextExpression):
else:
pre, cond, post, body = self._parse_for_legacy_ast(statement)
node_startLoop = self._new_node(
NodeType.STARTLOOP, statement["src"], node.underlying_node.scope
)
node_endLoop = self._new_node(
NodeType.ENDLOOP, statement["src"], node.underlying_node.scope
)
node_startLoop = self._new_node(NodeType.STARTLOOP, statement["src"], scope)
node_endLoop = self._new_node(NodeType.ENDLOOP, statement["src"], scope)
last_scope = node.underlying_node.scope
last_scope = scope
if pre:
pre_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope)
pre_scope = Scope(scope.is_checked, False, last_scope)
last_scope = pre_scope
node_init_expression = self._parse_statement(pre, node, pre_scope)
link_underlying_nodes(node_init_expression, node_startLoop)
@ -594,7 +575,7 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node, node_startLoop)
if cond:
cond_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope)
cond_scope = Scope(scope.is_checked, False, last_scope)
last_scope = cond_scope
node_condition = self._new_node(NodeType.IFLOOP, cond["src"], cond_scope)
node_condition.add_unparsed_expression(cond)
@ -605,7 +586,7 @@ class FunctionSolc(CallerContextExpression):
node_condition = None
node_beforeBody = node_startLoop
body_scope = Scope(node.underlying_node.scope.is_checked, False, last_scope)
body_scope = Scope(scope.is_checked, False, last_scope)
last_scope = body_scope
node_body = self._parse_statement(body, node_beforeBody, body_scope)
@ -625,14 +606,10 @@ class FunctionSolc(CallerContextExpression):
return node_endLoop
def _parse_dowhile(self, do_while_statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_dowhile(self, do_while_statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
node_startDoWhile = self._new_node(
NodeType.STARTLOOP, do_while_statement["src"], node.underlying_node.scope
)
condition_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
node_startDoWhile = self._new_node(NodeType.STARTLOOP, do_while_statement["src"], scope)
condition_scope = Scope(scope.is_checked, False, scope)
if self.is_compact_ast:
node_condition = self._new_node(
@ -650,7 +627,7 @@ class FunctionSolc(CallerContextExpression):
node_condition.add_unparsed_expression(expression)
statement = self._parse_statement(children[1], node_condition, condition_scope)
body_scope = Scope(node.underlying_node.scope.is_checked, False, condition_scope)
body_scope = Scope(scope.is_checked, False, condition_scope)
node_endDoWhile = self._new_node(NodeType.ENDLOOP, do_while_statement["src"], body_scope)
link_underlying_nodes(node, node_startDoWhile)
@ -666,33 +643,111 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node_condition, node_endDoWhile)
return node_endDoWhile
def _parse_try_catch(self, statement: Dict, node: NodeSolc) -> NodeSolc:
# pylint: disable=no-self-use
def _construct_try_expression(self, externalCall: Dict, parameters_list: Dict) -> Dict:
# if the parameters are more than 1 we make the leftHandSide of the Assignment node
# a TupleExpression otherwise an Identifier
# case when there isn't returns(...)
# e.g. external call that doesn't have any return variable
if not parameters_list:
return externalCall
ret: Dict = {"nodeType": "Assignment", "operator": "=", "src": parameters_list["src"]}
parameters = parameters_list.get("parameters", None)
# if the name is "" it means the return variable is not used
if len(parameters) == 1:
if parameters[0]["name"] != "":
self._add_param(parameters[0])
ret["typeDescriptions"] = {
"typeString": parameters[0]["typeName"]["typeDescriptions"]["typeString"]
}
leftHandSide = {
"name": parameters[0]["name"],
"nodeType": "Identifier",
"src": parameters[0]["src"],
"referencedDeclaration": parameters[0]["id"],
"typeDescriptions": parameters[0]["typeDescriptions"],
}
else:
# we don't need an Assignment so we return only the external call
return externalCall
else:
ret["typeDescriptions"] = {"typeString": "tuple()"}
leftHandSide = {
"components": [],
"nodeType": "TupleExpression",
"src": parameters_list["src"],
}
for i, p in enumerate(parameters):
if p["name"] == "":
continue
new_statement = {
"nodeType": "VariableDefinitionStatement",
"src": p["src"],
"declarations": [p],
}
self._add_param_init_tuple(new_statement, i)
ident = {
"name": p["name"],
"nodeType": "Identifier",
"src": p["src"],
"referencedDeclaration": p["id"],
"typeDescriptions": p["typeDescriptions"],
}
leftHandSide["components"].append(ident)
ret["leftHandSide"] = leftHandSide
ret["rightHandSide"] = externalCall
return ret
def _parse_try_catch(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
externalCall = statement.get("externalCall", None)
if externalCall is None:
raise ParsingError(f"Try/Catch not correctly parsed by Slither {statement}")
catch_scope = Scope(
node.underlying_node.scope.is_checked, False, node.underlying_node.scope
)
catch_scope = Scope(scope.is_checked, False, scope)
new_node = self._new_node(NodeType.TRY, statement["src"], catch_scope)
new_node.add_unparsed_expression(externalCall)
clauses = statement.get("clauses", [])
# the first clause is the try scope
returned_variables = clauses[0].get("parameters", None)
constructed_try_expression = self._construct_try_expression(
externalCall, returned_variables
)
new_node.add_unparsed_expression(constructed_try_expression)
link_underlying_nodes(node, new_node)
node = new_node
for clause in statement.get("clauses", []):
self._parse_catch(clause, node)
for index, clause in enumerate(clauses):
# clauses after the first one are related to catch cases
# we set the parameters (e.g. data in this case. catch(string memory data) ...)
# to be initialized so they are not reported by the uninitialized-local-variables detector
if index >= 1:
self._parse_catch(clause, node, catch_scope, True)
else:
# the parameters for the try scope were already added in _construct_try_expression
self._parse_catch(clause, node, catch_scope, False)
return node
def _parse_catch(self, statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_catch(
self, statement: Dict, node: NodeSolc, scope: Scope, add_param: bool
) -> NodeSolc:
block = statement.get("block", None)
if block is None:
raise ParsingError(f"Catch not correctly parsed by Slither {statement}")
try_scope = Scope(node.underlying_node.scope.is_checked, False, node.underlying_node.scope)
try_scope = Scope(scope.is_checked, False, scope)
try_node = self._new_node(NodeType.CATCH, statement["src"], try_scope)
link_underlying_nodes(node, try_node)
if add_param:
if self.is_compact_ast:
params = statement.get("parameters", None)
else:
@ -701,11 +756,11 @@ class FunctionSolc(CallerContextExpression):
if params:
for param in params.get("parameters", []):
assert param[self.get_key()] == "VariableDeclaration"
self._add_param(param)
self._add_param(param, True)
return self._parse_statement(block, try_node, try_scope)
def _parse_variable_definition(self, statement: Dict, node: NodeSolc) -> NodeSolc:
def _parse_variable_definition(self, statement: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
try:
local_var = LocalVariable()
local_var.set_function(self._function)
@ -715,9 +770,7 @@ class FunctionSolc(CallerContextExpression):
self._add_local_variable(local_var_parser)
# local_var.analyze(self)
new_node = self._new_node(
NodeType.VARIABLE, statement["src"], node.underlying_node.scope
)
new_node = self._new_node(NodeType.VARIABLE, statement["src"], scope)
new_node.underlying_node.add_variable_declaration(local_var)
link_underlying_nodes(node, new_node)
return new_node
@ -747,7 +800,7 @@ class FunctionSolc(CallerContextExpression):
"declarations": [variable],
"initialValue": init,
}
new_node = self._parse_variable_definition(new_statement, new_node)
new_node = self._parse_variable_definition(new_statement, new_node, scope)
else:
# If we have
@ -769,7 +822,7 @@ class FunctionSolc(CallerContextExpression):
variables.append(variable)
new_node = self._parse_variable_definition_init_tuple(
new_statement, i, new_node
new_statement, i, new_node, scope
)
i = i + 1
@ -801,9 +854,7 @@ class FunctionSolc(CallerContextExpression):
"typeDescriptions": {"typeString": "tuple()"},
}
node = new_node
new_node = self._new_node(
NodeType.EXPRESSION, statement["src"], node.underlying_node.scope
)
new_node = self._new_node(NodeType.EXPRESSION, statement["src"], scope)
new_node.add_unparsed_expression(expression)
link_underlying_nodes(node, new_node)
@ -834,7 +885,7 @@ class FunctionSolc(CallerContextExpression):
self.get_children("children"): [variable, init],
}
new_node = self._parse_variable_definition(new_statement, new_node)
new_node = self._parse_variable_definition(new_statement, new_node, scope)
else:
# If we have
# var (a, b) = f()
@ -853,7 +904,7 @@ class FunctionSolc(CallerContextExpression):
variables.append(variable)
new_node = self._parse_variable_definition_init_tuple(
new_statement, i, new_node
new_statement, i, new_node, scope
)
i = i + 1
var_identifiers = []
@ -883,16 +934,14 @@ class FunctionSolc(CallerContextExpression):
],
}
node = new_node
new_node = self._new_node(
NodeType.EXPRESSION, statement["src"], node.underlying_node.scope
)
new_node = self._new_node(NodeType.EXPRESSION, statement["src"], scope)
new_node.add_unparsed_expression(expression)
link_underlying_nodes(node, new_node)
return new_node
def _parse_variable_definition_init_tuple(
self, statement: Dict, index: int, node: NodeSolc
self, statement: Dict, index: int, node: NodeSolc, scope
) -> NodeSolc:
local_var = LocalVariableInitFromTuple()
local_var.set_function(self._function)
@ -902,7 +951,7 @@ class FunctionSolc(CallerContextExpression):
self._add_local_variable(local_var_parser)
new_node = self._new_node(NodeType.VARIABLE, statement["src"], node.underlying_node.scope)
new_node = self._new_node(NodeType.VARIABLE, statement["src"], scope)
new_node.underlying_node.add_variable_declaration(local_var)
link_underlying_nodes(node, new_node)
return new_node
@ -923,15 +972,15 @@ class FunctionSolc(CallerContextExpression):
name = statement[self.get_key()]
# SimpleStatement = VariableDefinition | ExpressionStatement
if name == "IfStatement":
node = self._parse_if(statement, node)
node = self._parse_if(statement, node, scope)
elif name == "WhileStatement":
node = self._parse_while(statement, node)
node = self._parse_while(statement, node, scope)
elif name == "ForStatement":
node = self._parse_for(statement, node)
node = self._parse_for(statement, node, scope)
elif name == "Block":
node = self._parse_block(statement, node)
node = self._parse_block(statement, node, scope)
elif name == "UncheckedBlock":
node = self._parse_unchecked_block(statement, node)
node = self._parse_unchecked_block(statement, node, scope)
elif name == "InlineAssembly":
# Added with solc 0.6 - the yul code is an AST
if "AST" in statement and not self.compilation_unit.core.skip_assembly:
@ -943,7 +992,9 @@ class FunctionSolc(CallerContextExpression):
# technically, entrypoint and exitpoint are YulNodes and we should be returning a NodeSolc here
# but they both expose an underlying_node so oh well
link_underlying_nodes(node, entrypoint)
node = exitpoint
end_assembly = self._new_node(NodeType.ENDASSEMBLY, statement["src"], scope)
link_underlying_nodes(exitpoint, end_assembly)
node = end_assembly
else:
asm_node = self._new_node(NodeType.ASSEMBLY, statement["src"], scope)
self._function.contains_assembly = True
@ -953,7 +1004,7 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node, asm_node)
node = asm_node
elif name == "DoWhileStatement":
node = self._parse_dowhile(statement, node)
node = self._parse_dowhile(statement, node, scope)
# For Continue / Break / Return / Throw
# The is fixed later
elif name == "Continue":
@ -994,7 +1045,7 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node, new_node)
node = new_node
elif name in ["VariableDefinitionStatement", "VariableDeclarationStatement"]:
node = self._parse_variable_definition(statement, node)
node = self._parse_variable_definition(statement, node, scope)
elif name == "ExpressionStatement":
# assert len(statement[self.get_children('expression')]) == 1
# assert not 'attributes' in statement
@ -1008,7 +1059,7 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node, new_node)
node = new_node
elif name == "TryStatement":
node = self._parse_try_catch(statement, node)
node = self._parse_try_catch(statement, node, scope)
# elif name == 'TryCatchClause':
# self._parse_catch(statement, node)
elif name == "RevertStatement":
@ -1025,7 +1076,7 @@ class FunctionSolc(CallerContextExpression):
return node
def _parse_block(self, block: Dict, node: NodeSolc, check_arithmetic: bool = False) -> NodeSolc:
def _parse_block(self, block: Dict, node: NodeSolc, scope: Scope) -> NodeSolc:
"""
Return:
Node
@ -1037,13 +1088,12 @@ class FunctionSolc(CallerContextExpression):
else:
statements = block[self.get_children("children")]
check_arithmetic = check_arithmetic | node.underlying_node.scope.is_checked
new_scope = Scope(check_arithmetic, False, node.underlying_node.scope)
new_scope = Scope(scope.is_checked, False, scope)
for statement in statements:
node = self._parse_statement(statement, node, new_scope)
return node
def _parse_unchecked_block(self, block: Dict, node: NodeSolc):
def _parse_unchecked_block(self, block: Dict, node: NodeSolc, scope):
"""
Return:
Node
@ -1055,11 +1105,21 @@ class FunctionSolc(CallerContextExpression):
else:
statements = block[self.get_children("children")]
new_scope = Scope(False, False, node.underlying_node.scope)
new_scope = Scope(False, False, scope)
for statement in statements:
node = self._parse_statement(statement, node, new_scope)
return node
def _update_reachability(self, node: Node) -> None:
worklist = [node]
while worklist:
current = worklist.pop()
# fix point
if not current.is_reachable:
current.set_is_reachable(True)
worklist.extend(current.sons)
def _parse_cfg(self, cfg: Dict) -> None:
assert cfg[self.get_key()] == "Block"
@ -1076,8 +1136,7 @@ class FunctionSolc(CallerContextExpression):
self._function.is_empty = True
else:
self._function.is_empty = False
check_arithmetic = self.compilation_unit.solc_version >= "0.8.0"
self._parse_block(cfg, node, check_arithmetic=check_arithmetic)
self._parse_block(cfg, node, self.underlying_function)
self._remove_incorrect_edges()
self._remove_alone_endif()
@ -1182,7 +1241,7 @@ class FunctionSolc(CallerContextExpression):
###################################################################################
###################################################################################
def _add_param(self, param: Dict) -> LocalVariableSolc:
def _add_param(self, param: Dict, initialized: bool = False) -> LocalVariableSolc:
local_var = LocalVariable()
local_var.set_function(self._function)
@ -1192,6 +1251,9 @@ class FunctionSolc(CallerContextExpression):
local_var_parser.analyze(self)
if initialized:
local_var.initialized = True
# see https://solidity.readthedocs.io/en/v0.4.24/types.html?highlight=storage%20location#data-location
if local_var.location == "default":
local_var.set_location("memory")
@ -1199,6 +1261,17 @@ class FunctionSolc(CallerContextExpression):
self._add_local_variable(local_var_parser)
return local_var_parser
def _add_param_init_tuple(self, statement: Dict, index: int) -> LocalVariableInitFromTupleSolc:
local_var = LocalVariableInitFromTuple()
local_var.set_function(self._function)
local_var.set_offset(statement["src"], self._function.compilation_unit)
local_var_parser = LocalVariableInitFromTupleSolc(local_var, statement, index)
self._add_local_variable(local_var_parser)
return local_var_parser
def _parse_params(self, params: Dict):
assert params[self.get_key()] == "ParameterList"

@ -55,6 +55,13 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
self._propagate_global(type_name)
else:
for f in self._functions:
# User defined operator
if "operator" in f:
# Top level function
function_name: str = f["definition"]["name"]
operator: str = f["operator"]
self._analyze_operator(operator, function_name, type_name)
else:
full_name_split = f["function"]["name"].split(".")
if len(full_name_split) == 1:
# Top level function
@ -101,6 +108,19 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
self._propagate_global(type_name)
break
def _analyze_operator(
self, operator: str, function_name: str, type_name: TypeAliasTopLevel
) -> None:
for tl_function in self._using_for.file_scope.functions:
# The library function is bound to the first parameter's type
if (
tl_function.name == function_name
and tl_function.parameters
and type_name == tl_function.parameters[0].type
):
type_name.operators[operator] = tl_function
break
def _analyze_library_function(
self,
library_name: str,
@ -132,7 +152,7 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
if self._global:
for scope in self.compilation_unit.scopes.values():
if isinstance(type_name, TypeAliasTopLevel):
for alias in scope.user_defined_types.values():
for alias in scope.type_aliases.values():
if alias == type_name:
scope.using_for_directives.add(self._using_for)
elif isinstance(type_name, UserDefinedType):

@ -1,6 +1,6 @@
import logging
import re
from typing import Union, Dict, TYPE_CHECKING
from typing import Union, Dict, TYPE_CHECKING, List, Any
import slither.core.expressions.type_conversion
from slither.core.declarations.solidity_variables import (
@ -175,11 +175,12 @@ def parse_call(
called = parse_expression(children[0], caller_context)
arguments = [parse_expression(a, caller_context) for a in children[1::]]
if isinstance(called, SuperCallExpression):
if isinstance(called, SuperIdentifier):
sp = SuperCallExpression(called, arguments, type_return)
sp.set_offset(expression["src"], caller_context.compilation_unit)
return sp
call_expression = CallExpression(called, arguments, type_return)
names = expression["names"] if "names" in expression and len(expression["names"]) > 0 else None
call_expression = CallExpression(called, arguments, type_return, names=names)
call_expression.set_offset(src, caller_context.compilation_unit)
# Only available if the syntax {gas:, value:} was used
@ -236,6 +237,24 @@ if TYPE_CHECKING:
pass
def _user_defined_op_call(
caller_context: CallerContextExpression, src, function_id: int, args: List[Any], type_call: str
) -> CallExpression:
var, was_created = find_variable(None, caller_context, function_id)
if was_created:
var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit)
var.references.append(identifier.source_mapping)
call = CallExpression(identifier, args, type_call)
call.set_offset(src, caller_context.compilation_unit)
return call
def parse_expression(expression: Dict, caller_context: CallerContextExpression) -> "Expression":
# pylint: disable=too-many-nested-blocks,too-many-statements
"""
@ -274,16 +293,24 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
if name == "UnaryOperation":
if is_compact_ast:
attributes = expression
else:
attributes = expression["attributes"]
assert "prefix" in attributes
operation_type = UnaryOperationType.get_type(attributes["operator"], attributes["prefix"])
if is_compact_ast:
expression = parse_expression(expression["subExpression"], caller_context)
else:
attributes = expression["attributes"]
assert len(expression["children"]) == 1
expression = parse_expression(expression["children"][0], caller_context)
assert "prefix" in attributes
# Use of user defined operation
if "function" in attributes:
return _user_defined_op_call(
caller_context,
src,
attributes["function"],
[expression],
attributes["typeDescriptions"]["typeString"],
)
operation_type = UnaryOperationType.get_type(attributes["operator"], attributes["prefix"])
unary_op = UnaryOperation(expression, operation_type)
unary_op.set_offset(src, caller_context.compilation_unit)
return unary_op
@ -291,17 +318,25 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
if name == "BinaryOperation":
if is_compact_ast:
attributes = expression
else:
attributes = expression["attributes"]
operation_type = BinaryOperationType.get_type(attributes["operator"])
if is_compact_ast:
left_expression = parse_expression(expression["leftExpression"], caller_context)
right_expression = parse_expression(expression["rightExpression"], caller_context)
else:
assert len(expression["children"]) == 2
attributes = expression["attributes"]
left_expression = parse_expression(expression["children"][0], caller_context)
right_expression = parse_expression(expression["children"][1], caller_context)
# Use of user defined operation
if "function" in attributes:
return _user_defined_op_call(
caller_context,
src,
attributes["function"],
[left_expression, right_expression],
attributes["typeDescriptions"]["typeString"],
)
operation_type = BinaryOperationType.get_type(attributes["operator"])
binary_op = BinaryOperation(left_expression, right_expression, operation_type)
binary_op.set_offset(src, caller_context.compilation_unit)
return binary_op
@ -433,6 +468,8 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
type_candidate = ElementaryType("uint256")
else:
type_candidate = ElementaryType("string")
elif type_candidate.startswith("rational_const "):
type_candidate = ElementaryType("uint256")
elif type_candidate.startswith("int_const "):
type_candidate = ElementaryType("uint256")
elif type_candidate.startswith("bool"):
@ -450,13 +487,18 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
t = None
referenced_declaration = None
if caller_context.is_compact_ast:
value = expression["name"]
t = expression["typeDescriptions"]["typeString"]
if "referencedDeclaration" in expression:
referenced_declaration = expression["referencedDeclaration"]
else:
value = expression["attributes"]["value"]
if "type" in expression["attributes"]:
t = expression["attributes"]["type"]
if "referencedDeclaration" in expression["attributes"]:
referenced_declaration = expression["attributes"]["referencedDeclaration"]
if t:
found = re.findall(r"[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t)
@ -465,10 +507,6 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
value = value + "(" + found[0] + ")"
value = filter_name(value)
if "referencedDeclaration" in expression:
referenced_declaration = expression["referencedDeclaration"]
else:
referenced_declaration = None
var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created:
var.set_offset(src, caller_context.compilation_unit)

@ -3,6 +3,8 @@ from typing import TYPE_CHECKING, Optional, Union, List, Tuple
from slither.core.declarations import Event, Enum, Structure
from slither.core.declarations.contract import Contract
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.function import Function
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.function_top_level import FunctionTopLevel
@ -54,10 +56,24 @@ def _find_variable_from_ref_declaration(
referenced_declaration: Optional[int],
all_contracts: List["Contract"],
all_functions: List["Function"],
function_parser: Optional["FunctionSolc"],
contract_declarer: Optional["Contract"],
) -> Optional[Union[Contract, Function]]:
"""
Reference declarations take the highest priority, but they are not available for legacy AST.
"""
if referenced_declaration is None:
return None
# id of the contracts is the referenced declaration
# We look for variable declared with the referencedDeclaration attribute
if function_parser is not None and referenced_declaration in function_parser.variables_renamed:
return function_parser.variables_renamed[referenced_declaration].underlying_variable
if (
contract_declarer is not None
and referenced_declaration in contract_declarer.state_variables_by_ref_id
):
return contract_declarer.state_variables_by_ref_id[referenced_declaration]
# Ccontracts ids are the referenced declaration
# This is not true for the functions, as we dont always have the referenced_declaration
# But maybe we could? (TODO)
for contract_candidate in all_contracts:
@ -72,14 +88,9 @@ def _find_variable_from_ref_declaration(
def _find_variable_in_function_parser(
var_name: str,
function_parser: Optional["FunctionSolc"],
referenced_declaration: Optional[int] = None,
) -> Optional[Variable]:
if function_parser is None:
return None
# We look for variable declared with the referencedDeclaration attr
func_variables_renamed = function_parser.variables_renamed
if referenced_declaration and referenced_declaration in func_variables_renamed:
return func_variables_renamed[referenced_declaration].underlying_variable
# If not found, check for name
func_variables = function_parser.underlying_function.variables_as_dict
if var_name in func_variables:
@ -114,6 +125,8 @@ def find_top_level(
:return:
:rtype:
"""
if var_name in scope.type_aliases:
return scope.type_aliases[var_name], False
if var_name in scope.structures:
return scope.structures[var_name], False
@ -121,6 +134,9 @@ def find_top_level(
if var_name in scope.enums:
return scope.enums[var_name], False
if var_name in scope.events:
return scope.events[var_name], False
for import_directive in scope.imports:
if import_directive.alias == var_name:
new_val = SolidityImportPlaceHolder(import_directive)
@ -205,6 +221,10 @@ def _find_in_contract(
if sig == var_name:
return modifier
type_aliases = contract.type_aliases_as_dict
if var_name in type_aliases:
return type_aliases[var_name]
# structures are looked on the contract declarer
structures = contract.structures_as_dict
if var_name in structures:
@ -240,6 +260,7 @@ def _find_in_contract(
return None
# pylint: disable=too-many-statements
def _find_variable_init(
caller_context: CallerContextExpression,
) -> Tuple[List[Contract], List["Function"], FileScope,]:
@ -247,6 +268,7 @@ def _find_variable_init(
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
direct_contracts: List[Contract]
direct_functions_parser: List[Function]
@ -289,6 +311,24 @@ def _find_variable_init(
direct_contracts = []
direct_functions_parser = []
scope = caller_context.underlying_variable.file_scope
elif isinstance(caller_context, CustomErrorSolc):
if caller_context.contract_parser:
direct_contracts = [caller_context.contract_parser.underlying_contract]
direct_functions_parser = [
f.underlying_function
for f in caller_context.contract_parser.functions_parser
+ caller_context.contract_parser.modifiers_parser
]
else:
# Top level custom error
direct_contracts = []
direct_functions_parser = []
underlying_custom_error = caller_context.underlying_custom_error
if isinstance(underlying_custom_error, CustomErrorTopLevel):
scope = underlying_custom_error.file_scope
else:
assert isinstance(underlying_custom_error, CustomErrorContract)
scope = underlying_custom_error.contract.file_scope
else:
raise SlitherError(
f"{type(caller_context)} ({caller_context} is not valid for find_variable"
@ -337,6 +377,7 @@ def find_variable(
"""
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
# variable are looked from the contract declarer
# functions can be shadowed, but are looked from the contract instance, rather than the contract declarer
@ -362,23 +403,6 @@ def find_variable(
if var_name in current_scope.renaming:
var_name = current_scope.renaming[var_name]
if var_name in current_scope.user_defined_types:
return current_scope.user_defined_types[var_name], False
# Use ret0/ret1 to help mypy
ret0 = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions
)
if ret0:
return ret0, False
function_parser: Optional[FunctionSolc] = (
caller_context if isinstance(caller_context, FunctionSolc) else None
)
ret1 = _find_variable_in_function_parser(var_name, function_parser, referenced_declaration)
if ret1:
return ret1, False
contract: Optional[Contract] = None
contract_declarer: Optional[Contract] = None
if isinstance(caller_context, ContractSolc):
@ -391,6 +415,33 @@ def find_variable(
contract_declarer = underlying_func.contract_declarer
else:
assert isinstance(underlying_func, FunctionTopLevel)
elif isinstance(caller_context, CustomErrorSolc):
underlying_custom_error = caller_context.underlying_custom_error
if isinstance(underlying_custom_error, CustomErrorContract):
contract = underlying_custom_error.contract
# We check for contract variables here because _find_in_contract
# will return since in this case the contract_declarer is None
for var in contract.variables:
if var_name == var.name:
return var, False
function_parser: Optional[FunctionSolc] = (
caller_context if isinstance(caller_context, FunctionSolc) else None
)
# Use ret0/ret1 to help mypy
ret0 = _find_variable_from_ref_declaration(
referenced_declaration,
direct_contracts,
direct_functions,
function_parser,
contract_declarer,
)
if ret0:
return ret0, False
ret1 = _find_variable_in_function_parser(var_name, function_parser)
if ret1:
return ret1, False
ret = _find_in_contract(var_name, contract, contract_declarer, is_super, is_identifier_path)
if ret:
@ -442,6 +493,8 @@ def find_variable(
referenced_declaration,
list(current_scope.contracts.values()),
list(current_scope.functions),
None,
None,
)
if ret:
return ret, False

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

Loading…
Cancel
Save