Merge branch 'dev' into type-improvemetns

type-improvemetns
alpharush 1 year ago committed by GitHub
commit 3ac88be839
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .github/dependabot.yml
  2. 4
      .github/scripts/unit_test_runner.sh
  3. 4
      .github/workflows/black.yml
  4. 12
      .github/workflows/ci.yml
  5. 2
      .github/workflows/docker.yml
  6. 4
      .github/workflows/docs.yml
  7. 4
      .github/workflows/linter.yml
  8. 2
      .github/workflows/pip-audit.yml
  9. 54
      .github/workflows/publish.yml
  10. 4
      .github/workflows/pylint.yml
  11. 9
      .github/workflows/test.yml
  12. 1
      .gitignore
  13. 84
      CONTRIBUTING.md
  14. 88
      Makefile
  15. 212
      README.md
  16. 10
      examples/flat/a.sol
  17. 13
      examples/flat/b.sol
  18. 10
      scripts/ci_test_flat.sh
  19. 95
      scripts/ci_test_interface.sh
  20. 4
      scripts/ci_test_printers.sh
  21. 13
      setup.py
  22. 3
      slither/__init__.py
  23. 77
      slither/__main__.py
  24. 1
      slither/analyses/data_dependency/data_dependency.py
  25. 2
      slither/analyses/evm/convert.py
  26. 15
      slither/core/cfg/node.py
  27. 6
      slither/core/cfg/scope.py
  28. 8
      slither/core/compilation_unit.py
  29. 2
      slither/core/declarations/contract.py
  30. 7
      slither/core/declarations/custom_error.py
  31. 2
      slither/core/declarations/event.py
  32. 8
      slither/core/declarations/solidity_variables.py
  33. 29
      slither/core/expressions/new_array.py
  34. 5
      slither/core/solidity_types/type_alias.py
  35. 3
      slither/detectors/all_detectors.py
  36. 4
      slither/detectors/assembly/shift_parameter_mixup.py
  37. 28
      slither/detectors/attributes/locked_ether.py
  38. 13
      slither/detectors/compiler_bugs/array_by_reference.py
  39. 226
      slither/detectors/operations/cache_array_length.py
  40. 104
      slither/detectors/operations/encode_packed.py
  41. 62
      slither/detectors/operations/unchecked_low_level_return_values.py
  42. 48
      slither/detectors/operations/unused_return_values.py
  43. 31
      slither/detectors/reentrancy/reentrancy_events.py
  44. 23
      slither/detectors/statements/incorrect_strict_equality.py
  45. 223
      slither/detectors/statements/incorrect_using_for.py
  46. 2
      slither/detectors/statements/msg_value_in_loop.py
  47. 30
      slither/detectors/variables/similar_variables.py
  48. 2
      slither/detectors/variables/unchanged_state_variables.py
  49. 11
      slither/detectors/variables/uninitialized_local_variables.py
  50. 4
      slither/detectors/variables/unused_state_variables.py
  51. 2
      slither/detectors/variables/var_read_using_this.py
  52. 1
      slither/printers/all_printers.py
  53. 2
      slither/printers/functions/authorization.py
  54. 2
      slither/printers/functions/cfg.py
  55. 17
      slither/printers/guidance/echidna.py
  56. 4
      slither/printers/inheritance/inheritance.py
  57. 2
      slither/printers/inheritance/inheritance_graph.py
  58. 3
      slither/printers/summary/contract.py
  59. 2
      slither/printers/summary/data_depenency.py
  60. 2
      slither/printers/summary/function.py
  61. 87
      slither/printers/summary/human_summary.py
  62. 35
      slither/printers/summary/loc.py
  63. 2
      slither/printers/summary/slithir.py
  64. 2
      slither/printers/summary/slithir_ssa.py
  65. 6
      slither/printers/summary/when_not_paused.py
  66. 16
      slither/slithir/convert.py
  67. 2
      slither/slithir/operations/assignment.py
  68. 1
      slither/slithir/operations/binary.py
  69. 7
      slither/slithir/operations/index.py
  70. 2
      slither/slithir/operations/init_array.py
  71. 21
      slither/slithir/operations/new_array.py
  72. 3
      slither/slithir/operations/new_contract.py
  73. 3
      slither/slithir/operations/new_structure.py
  74. 2
      slither/slithir/operations/unary.py
  75. 8
      slither/slithir/tmp_operations/tmp_new_array.py
  76. 3
      slither/slithir/utils/ssa.py
  77. 3
      slither/slithir/variables/reference.py
  78. 3
      slither/solc_parsing/declarations/contract.py
  79. 274
      slither/solc_parsing/declarations/function.py
  80. 3
      slither/solc_parsing/declarations/modifier.py
  81. 48
      slither/solc_parsing/declarations/using_for_top_level.py
  82. 94
      slither/solc_parsing/expressions/expression_parsing.py
  83. 65
      slither/solc_parsing/slither_compilation_unit_solc.py
  84. 18
      slither/solc_parsing/variables/local_variable_init_from_tuple.py
  85. 3
      slither/solc_parsing/yul/evm_functions.py
  86. 33
      slither/solc_parsing/yul/parse_yul.py
  87. 2
      slither/tools/flattening/export/export.py
  88. 45
      slither/tools/flattening/flattening.py
  89. 21
      slither/tools/interface/README.md
  90. 0
      slither/tools/interface/__init__.py
  91. 106
      slither/tools/interface/__main__.py
  92. 33
      slither/tools/read_storage/README.md
  93. 2
      slither/tools/read_storage/__init__.py
  94. 36
      slither/tools/read_storage/__main__.py
  95. 374
      slither/tools/read_storage/read_storage.py
  96. 2
      slither/tools/read_storage/utils/utils.py
  97. 181
      slither/utils/code_generation.py
  98. 50
      slither/utils/command_line.py
  99. 11
      slither/utils/expression_manipulations.py
  100. 4
      slither/utils/integer_conversion.py
  101. Some files were not shown because too many files have changed in this diff Show More

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

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

@ -32,7 +32,7 @@ jobs:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: 3.8
@ -42,7 +42,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Black
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -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",
@ -36,6 +37,7 @@ jobs:
"etherscan",
"find_paths",
"flat",
"interface",
"kspec",
"printers",
# "prop"
@ -52,10 +54,10 @@ jobs:
type: truffle
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v3
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: ${{ matrix.python }}
- name: Install dependencies
run: |
pip install ".[test]"
@ -65,11 +67,11 @@ jobs:
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v20
uses: cachix/install-nix-action@v22
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v10
uses: cachix/cachix-action@v12
with:
name: dapp

@ -47,7 +47,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Build and Push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7
target: final

@ -37,10 +37,10 @@ jobs:
- 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@v2
with:
# Upload the doc
path: './html/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1
uses: actions/deploy-pages@v2

@ -33,7 +33,7 @@ jobs:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: 3.8
@ -43,7 +43,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Lint everything else
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -34,6 +34,6 @@ jobs:
python -m pip install .
- name: Run pip-audit
uses: trailofbits/gh-action-pip-audit@v0.0.4
uses: pypa/gh-action-pip-audit@v1.0.8
with:
virtual-environment: /tmp/pip-audit-env

@ -0,0 +1,54 @@
name: Publish to PyPI
on:
release:
types: [published]
jobs:
build-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
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@v3
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@v3
with:
name: slither-dists
path: dist/
- name: publish
uses: pypa/gh-action-pypi-publish@v1.8.8
- name: sign
uses: sigstore/gh-action-sigstore-python@v1.2.3
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true
bundle-only: true

@ -27,7 +27,7 @@ jobs:
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v3
uses: actions/setup-python@v4
with:
python-version: 3.8
@ -37,7 +37,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Pylint
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# Run linters only on new files for pylint to speed up the CI

@ -25,20 +25,19 @@ 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
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: ${{ matrix.python }}
cache: "pip"
cache-dependency-path: setup.py
- name: Install dependencies
run: |
pip install ".[test]"
solc-select install 0.8.0
solc-select use 0.8.0
- name: Setup node
uses: actions/setup-node@v3
@ -76,7 +75,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:

1
.gitignore vendored

@ -42,6 +42,7 @@ htmlcov/
.coverage
.coverage.*
.cache
.pytest_cache
nosetests.xml
coverage.xml
*.cover

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

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

@ -1,5 +1,6 @@
# Slither, the Solidity source analyzer
<img src="./logo.png" alt="Logo" width="500"/>
<img src="https://raw.githubusercontent.com/crytic/slither/master/logo.png" alt="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)
@ -20,26 +21,29 @@ Slither is a Solidity static analysis framework written in Python3. It runs a su
## Features
* Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md))
* Identifies where the error condition occurs in the source code
* Easily integrates into continuous integration and Hardhat/Foundry builds
* Built-in 'printers' quickly report crucial contract information
* Detector API to write custom analyses in Python
* Ability to analyze contracts written with Solidity >= 0.4
* Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses
* Correctly parses 99.9% of all public Solidity code
* Average execution time of less than 1 second per contract
* Integrates with Github's code scanning in [CI](https://github.com/marketplace/actions/slither-action)
- Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md))
- Identifies where the error condition occurs in the source code
- Easily integrates into continuous integration and Hardhat/Foundry builds
- Built-in 'printers' quickly report crucial contract information
- Detector API to write custom analyses in Python
- Ability to analyze contracts written with Solidity >= 0.4
- Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses
- Correctly parses 99.9% of all public Solidity code
- Average execution time of less than 1 second per contract
- Integrates with Github's code scanning in [CI](https://github.com/marketplace/actions/slither-action)
## Usage
Run Slither on a Hardhat/Foundry/Dapp/Brownie application:
```bash
slither .
```
This is the preferred option if your project has dependencies as Slither relies on the underlying compilation framework to compile source code.
However, you can run Slither on a single file that does not import dependencies:
```bash
slither tests/uninitialized.sol
```
@ -79,118 +83,122 @@ 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
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 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
23 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
24 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
25 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
26 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
27 | `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
28 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
29 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
30 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
31 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
32 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
33 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
34 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
35 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
36 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
37 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
38 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
39 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
40 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
41 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
42 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
43 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
44 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
45 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
46 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
47 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
48 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
49 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
50 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
51 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
52 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
53 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
54 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
55 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
56 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
57 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
58 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
59 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
60 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
61 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
62 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
63 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
64 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
65 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
66 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
67 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
68 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
69 | `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
70 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
71 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
72 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
73 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
74 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
75 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
76 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
77 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
78 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
79 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
80 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
81 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
82 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
83 | `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
84 | `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
85 | `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
86 | `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
87 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Detector-Documentation#public-variable-read-in-external-context) | Optimization | High
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
- The [Detection Selection](https://github.com/crytic/slither/wiki/Usage#detector-selection) to run only selected detectors. By default, all the detectors are run.
- The [Triage Mode](https://github.com/crytic/slither/wiki/Usage#triage-mode) to filter individual results
## Printers
### Quick Review Printers
- `human-summary`: [Print a human-readable summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary)
- `inheritance-graph`: [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph)
- `contract-summary`: [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary)
- `loc`: [Count the total number lines of code (LOC), source lines of code (SLOC), and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), and test files (TEST).](https://github.com/trailofbits/slither/wiki/Printer-documentation#loc)
### In-Depth Review Printers
- `call-graph`: [Export the call-graph of the contracts to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph)
- `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
- `function-summary`: [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary)
- `vars-and-auth`: [Print the state variables written and the authorization of the functions](https://github.com/crytic/slither/wiki/Printer-documentation#variables-written-and-authorization)
- `when-not-paused`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
- `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.
@ -204,32 +212,36 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d
- `slither-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance)
- `slither-format`: [Automatic patch generation](https://github.com/crytic/slither/wiki/Slither-format)
- `slither-read-storage`: [Read storage values from contracts](./slither/tools/read_storage/README.md)
- `slither-interface`: [Generate an interface for a contract](./slither/tools/interface/README.md)
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
[Contact us](https://www.trailofbits.com/contact/) to get help on building custom tools.
## API Documentation
Documentation on Slither's internals is available [here](https://crytic.github.io/slither/slither.html).
## Getting Help
Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (#ethereum) for help using or extending Slither.
* The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract.
- The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract.
* The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses.
- The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses.
* The [API documentation](https://github.com/crytic/slither/wiki/Python-API) describes the methods and objects available for custom analyses.
- The [API documentation](https://github.com/crytic/slither/wiki/Python-API) describes the methods and objects available for custom analyses.
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
- The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
## FAQ
How do I exclude mocks or tests?
- View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
How do I fix "unknown file" or compilation issues?
- Because slither requires the solc AST, it must have all dependencies available.
If a contract has dependencies, `slither contract.sol` will fail.
Instead, use `slither .` in the parent directory of `contracts/` (you should see `contracts/` when you run `ls`).
@ -244,9 +256,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
### 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

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

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

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

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

@ -1,11 +1,11 @@
#!/usr/bin/env bash
### Test printer
### Test printer
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,human-summary,inheritance,inheritance-graph,loc,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration"
# Only test 0.5.17 to limit test time
for file in *0.5.17-compact.zip; do

@ -13,11 +13,14 @@ setup(
python_requires=">=3.8",
install_requires=[
"packaging",
"prettytable>=0.7.2",
"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@master#egg=crytic-compile",
# "crytic-compile>=0.3.1,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
"eth-utils>=2.1.0",
],
extras_require={
"lint": [
@ -31,6 +34,9 @@ setup(
"deepdiff",
"numpy",
"coverage[toml]",
"filelock",
"pytest-insta",
"solc-select@git+https://github.com/crytic/solc-select.git@query-artifact-path#egg=solc-select",
],
"doc": [
"pdoc",
@ -58,6 +64,7 @@ setup(
"slither-read-storage = slither.tools.read_storage.__main__:main",
"slither-doctor = slither.tools.doctor.__main__:main",
"slither-documentation = slither.tools.documentation.__main__:main",
"slither-interface = slither.tools.interface.__main__:main",
]
},
)

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

@ -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,
@ -206,22 +207,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 +387,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"],
help="Fail if any findings are detected",
action="store_const",
dest="fail_on",
const=FailOnLevel.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,
)
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",
@ -896,17 +900,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

@ -129,6 +129,7 @@ GENERIC_TAINT = {
SolidityVariableComposed("msg.value"),
SolidityVariableComposed("msg.data"),
SolidityVariableComposed("tx.origin"),
SolidityVariableComposed("tx.gasprice"),
}

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

@ -620,6 +620,21 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
"""
self._sons.append(son)
def replace_son(self, ori_son: "Node", new_son: "Node") -> None:
"""Replace a son node. Do nothing if the node to replace is not a son
Args:
ori_son: son to replace
new_son: son to replace with
"""
for i, s in enumerate(self._sons):
if s.node_id == ori_son.node_id:
idx = i
break
else:
return
self._sons[idx] = new_son
def set_sons(self, sons: List["Node"]) -> None:
"""Set the son nodes

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

@ -13,7 +13,7 @@ from slither.core.declarations import (
Function,
Modifier,
)
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
@ -46,7 +46,7 @@ class SlitherCompilationUnit(Context):
self._using_for_top_level: List[UsingForTopLevel] = []
self._pragma_directives: List[Pragma] = []
self._import_directives: List[Import] = []
self._custom_errors: List[CustomError] = []
self._custom_errors: List[CustomErrorTopLevel] = []
self._user_defined_value_types: Dict[str, TypeAliasTopLevel] = {}
self._all_functions: Set[Function] = set()
@ -128,7 +128,7 @@ class SlitherCompilationUnit(Context):
"""list(Contract): List of contracts that are derived and not inherited."""
inheritances = [x.inheritance for x in self.contracts]
inheritance = [item for sublist in inheritances for item in sublist]
return [c for c in self.contracts if c not in inheritance and not c.is_top_level]
return [c for c in self.contracts if c not in inheritance]
def get_contract_from_name(self, contract_name: Union[str, Constant]) -> List[Contract]:
"""
@ -216,7 +216,7 @@ class SlitherCompilationUnit(Context):
return self._using_for_top_level
@property
def custom_errors(self) -> List[CustomError]:
def custom_errors(self) -> List[CustomErrorTopLevel]:
return self._custom_errors
@property

@ -100,8 +100,6 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._is_upgradeable_proxy: Optional[bool] = None
self._upgradeable_version: Optional[str] = None
self.is_top_level = False # heavily used, so no @property
self._initial_state_variables: List["StateVariable"] = [] # ssa
self._is_incorrectly_parsed: bool = False

@ -1,8 +1,8 @@
from typing import List, TYPE_CHECKING, Optional, Type
from slither.core.solidity_types import UserDefinedType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
from slither.utils.type import is_underlying_type_address
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
@ -43,10 +43,7 @@ class CustomError(SourceMapping):
@staticmethod
def _convert_type_for_solidity_signature(t: Optional[Type]) -> str:
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
if is_underlying_type_address(t):
return "address"
return str(t)

@ -45,7 +45,7 @@ class Event(ContractLevel, SourceMapping):
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + self.full_name
return self.contract.name + "." + self.full_name
@property
def elems(self) -> List["EventVariable"]:

@ -21,10 +21,11 @@ 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",
@ -60,6 +61,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
@ -201,6 +203,10 @@ class SolidityCustomRevert(SolidityFunction):
self._custom_error = custom_error
self._return_type: List[Union[TypeInformation, ElementaryType]] = []
@property
def custom_error(self) -> CustomError:
return self._custom_error
def __eq__(self, other: Any) -> bool:
return (
self.__class__ == other.__class__

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

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

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

@ -52,7 +52,9 @@ The shift statement will right-shift the constant 8 by `a` bits"""
BinaryType.LEFT_SHIFT,
BinaryType.RIGHT_SHIFT,
]:
if isinstance(ir.variable_left, Constant):
if isinstance(ir.variable_left, Constant) and not isinstance(
ir.variable_right, Constant
):
info: DETECTOR_INFO = [
f,
" contains an incorrect shift operation: ",

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

@ -0,0 +1,226 @@
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 ",
f"`{usage.source_mapping.content}` ",
f"({usage.source_mapping}) ",
"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,104 @@
"""
Module detecting usage of more than one dynamic type in abi.encodePacked() arguments which could lead to collision
"""
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations.solidity_variables import SolidityFunction
from slither.slithir.operations import SolidityCall
from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.core.solidity_types import ElementaryType
from slither.core.solidity_types import ArrayType
def _is_dynamic_type(arg):
"""
Args:
arg (function argument)
Returns:
Bool
"""
if isinstance(arg.type, ElementaryType) and (arg.type.name in ["string", "bytes"]):
return True
if isinstance(arg.type, ArrayType) and arg.type.length is None:
return True
return False
def _detect_abi_encodePacked_collision(contract):
"""
Args:
contract (Contract)
Returns:
list((Function), (list (Node)))
"""
ret = []
# pylint: disable=too-many-nested-blocks
for f in contract.functions_and_modifiers_declared:
for n in f.nodes:
for ir in n.irs:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"abi.encodePacked()"
):
dynamic_type_count = 0
for arg in ir.arguments:
if is_tainted(arg, contract) and _is_dynamic_type(arg):
dynamic_type_count += 1
elif dynamic_type_count > 1:
ret.append((f, n))
dynamic_type_count = 0
else:
dynamic_type_count = 0
if dynamic_type_count > 1:
ret.append((f, n))
return ret
class EncodePackedCollision(AbstractDetector):
"""
Detect usage of more than one dynamic type in abi.encodePacked() arguments which could to collision
"""
ARGUMENT = "encode-packed-collision"
HELP = "ABI encodePacked Collision"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#abi-encodePacked-collision"
)
WIKI_TITLE = "ABI encodePacked Collision"
WIKI_DESCRIPTION = """Detect collision due to dynamic type usages in `abi.encodePacked`"""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Sign {
function get_hash_for_signature(string name, string doc) external returns(bytes32) {
return keccak256(abi.encodePacked(name, doc));
}
}
```
Bob calls `get_hash_for_signature` with (`bob`, `This is the content`). The hash returned is used as an ID.
Eve creates a collision with the ID using (`bo`, `bThis is the content`) and compromises the system.
"""
WIKI_RECOMMENDATION = """Do not use more than one dynamic type in `abi.encodePacked()`
(see the [Solidity documentation](https://solidity.readthedocs.io/en/v0.5.10/abi-spec.html?highlight=abi.encodePacked#non-standard-packed-modeDynamic)).
Use `abi.encode()`, preferably."""
def _detect(self):
"""Detect usage of more than one dynamic type in abi.encodePacked(..) arguments which could lead to collision"""
results = []
for c in self.compilation_unit.contracts:
values = _detect_abi_encodePacked_collision(c)
for func, node in values:
info = [
func,
" calls abi.encodePacked() with multiple dynamic arguments:\n\t- ",
node,
"\n",
]
json = self.generate_result(info)
results.append(json)
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

@ -3,7 +3,7 @@ Module detecting unused return values from external calls
"""
from typing import List
from slither.core.cfg.node import Node
from slither.core.cfg.node import Node, NodeType
from slither.core.declarations import Function
from slither.core.declarations.function_contract import FunctionContract
from slither.core.variables.state_variable import StateVariable
@ -12,8 +12,8 @@ from slither.detectors.abstract_detector import (
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import HighLevelCall
from slither.slithir.operations.operation import Operation
from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation
from slither.slithir.variables import TupleVariable
from slither.utils.output import Output
@ -50,13 +50,18 @@ contract MyConc{
WIKI_RECOMMENDATION = "Ensure that all the return values of the function calls are used."
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use
return isinstance(ir, HighLevelCall) and (
(
isinstance(ir.function, Function)
and ir.function.solidity_signature
not in ["transfer(address,uint256)", "transferFrom(address,address,uint256)"]
return (
isinstance(ir, HighLevelCall)
and (
(
isinstance(ir.function, Function)
and ir.function.solidity_signature
not in ["transfer(address,uint256)", "transferFrom(address,address,uint256)"]
)
or not isinstance(ir.function, Function)
)
or not isinstance(ir.function, Function)
or ir.node.type == NodeType.TRY
and isinstance(ir, (Assignment, Unpack))
)
def detect_unused_return_values(
@ -71,26 +76,33 @@ contract MyConc{
"""
values_returned = []
nodes_origin = {}
# pylint: disable=too-many-nested-blocks
for n in f.nodes:
for ir in n.irs:
if self._is_instance(ir):
# 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)
values_returned.append((ir.lvalue, None))
nodes_origin[ir.lvalue] = ir
if isinstance(ir.lvalue, TupleVariable):
# we iterate the number of elements the tuple has
# and add a (variable, index) in values_returned for each of them
for index in range(len(ir.lvalue.type)):
values_returned.append((ir.lvalue, index))
for read in ir.read:
if read in values_returned:
values_returned.remove(read)
return [nodes_origin[value].node for value in values_returned]
remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None)
if remove in values_returned:
# this is needed to remove the tuple variable when the first time one of its element is used
if remove[1] is not None and (remove[0], None) in values_returned:
values_returned.remove((remove[0], None))
values_returned.remove(remove)
return [nodes_origin[value].node for (value, _) in values_returned]
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:

@ -29,24 +29,45 @@ class ReentrancyEvent(Reentrancy):
# region wiki_description
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Only report reentrancies leading to out-of-order events."""
Detects [reentrancies](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy) that allow manipulation of the order or value of events."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
function bug(Called d){
contract ReentrantContract {
function f() external {
if (BugReentrancyEvents(msg.sender).counter() == 1) {
BugReentrancyEvents(msg.sender).count(this);
}
}
}
contract Counter {
uint public counter;
event Counter(uint);
}
contract BugReentrancyEvents is Counter {
function count(ReentrantContract d) external {
counter += 1;
d.f();
emit Counter(counter);
}
}
contract NoReentrancyEvents is Counter {
function count(ReentrantContract d) external {
counter += 1;
emit Counter(counter);
d.f();
}
}
```
If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, which might lead to issues for third parties."""
If the external call `d.f()` re-enters `BugReentrancyEvents`, the `Counter` events will be incorrect (`Counter(2)`, `Counter(2)`) whereas `NoReentrancyEvents` will correctly emit
(`Counter(1)`, `Counter(2)`). This may cause issues for offchain components that rely on the values of events e.g. checking for the amount deposited to a bridge."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](https://docs.soliditylang.org/en/latest/security-considerations.html#re-entrancy)."
STANDARD_JSON = False

@ -31,6 +31,7 @@ from slither.slithir.variables.constant import Constant
from slither.slithir.variables.local_variable import LocalIRVariable
from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
from slither.utils.output import Output
from slither.utils.type import is_underlying_type_address
class IncorrectStrictEquality(AbstractDetector):
@ -72,6 +73,19 @@ contract Crowdsale{
def is_direct_comparison(ir: Operation) -> bool:
return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL
@staticmethod
def is_not_comparing_addresses(ir: Binary) -> bool:
"""
Comparing addresses strictly should not be flagged.
"""
if is_underlying_type_address(ir.variable_left.type) and is_underlying_type_address(
ir.variable_right.type
):
return False
return True
@staticmethod
def is_any_tainted(
variables: List[
@ -108,7 +122,6 @@ contract Crowdsale{
):
taints.append(ir.lvalue)
if isinstance(ir, HighLevelCall):
# print(ir.function.full_name)
if (
isinstance(ir.function, Function)
and ir.function.full_name == "balanceOf(address)"
@ -125,7 +138,6 @@ contract Crowdsale{
if isinstance(ir, Assignment):
if ir.rvalue in self.sources_taint:
taints.append(ir.lvalue)
return taints
# Retrieve all tainted (node, function) pairs
@ -145,7 +157,12 @@ contract Crowdsale{
for ir in node.irs_ssa:
# Filter to only tainted equality (==) comparisons
if self.is_direct_comparison(ir) and self.is_any_tainted(ir.used, taints, func):
if (
self.is_direct_comparison(ir)
# Filter out address comparisons which may occur due to lack of field sensitivity in data dependency
and self.is_not_comparing_addresses(ir)
and self.is_any_tainted(ir.used, taints, func)
):
if func not in results:
results[func] = []
results[func].append(node)

@ -0,0 +1,223 @@
from typing import List
from slither.core.declarations import Contract, Structure, Enum
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.solidity_types import (
UserDefinedType,
Type,
ElementaryType,
TypeAlias,
MappingType,
ArrayType,
)
from slither.core.solidity_types.elementary_type import Uint, Int, Byte
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
def _is_correctly_used(type_: Type, library: Contract) -> bool:
"""
Checks if a `using library for type_` statement is used correctly (that is, does library contain any function
with type_ as the first argument).
"""
for f in library.functions:
if len(f.parameters) == 0:
continue
if f.parameters[0].type and not _implicitly_convertible_to(type_, f.parameters[0].type):
continue
return True
return False
def _implicitly_convertible_to(type1: Type, type2: Type) -> bool:
"""
Returns True if type1 may be implicitly converted to type2.
"""
if isinstance(type1, TypeAlias) or isinstance(type2, TypeAlias):
if isinstance(type1, TypeAlias) and isinstance(type2, TypeAlias):
return type1.type == type2.type
return False
if isinstance(type1, UserDefinedType) and isinstance(type2, UserDefinedType):
if isinstance(type1.type, Contract) and isinstance(type2.type, Contract):
return _implicitly_convertible_to_for_contracts(type1.type, type2.type)
if isinstance(type1.type, Structure) and isinstance(type2.type, Structure):
return type1.type.canonical_name == type2.type.canonical_name
if isinstance(type1.type, Enum) and isinstance(type2.type, Enum):
return type1.type.canonical_name == type2.type.canonical_name
if isinstance(type1, ElementaryType) and isinstance(type2, ElementaryType):
return _implicitly_convertible_to_for_elementary_types(type1, type2)
if isinstance(type1, MappingType) and isinstance(type2, MappingType):
return _implicitly_convertible_to_for_mappings(type1, type2)
if isinstance(type1, ArrayType) and isinstance(type2, ArrayType):
return _implicitly_convertible_to_for_arrays(type1, type2)
return False
def _implicitly_convertible_to_for_arrays(type1: ArrayType, type2: ArrayType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2.
"""
return _implicitly_convertible_to(type1.type, type2.type)
def _implicitly_convertible_to_for_mappings(type1: MappingType, type2: MappingType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2.
"""
return type1.type_from == type2.type_from and type1.type_to == type2.type_to
def _implicitly_convertible_to_for_elementary_types(
type1: ElementaryType, type2: ElementaryType
) -> bool:
"""
Returns True if type1 may be implicitly converted to type2.
"""
if type1.type == "bool" and type2.type == "bool":
return True
if type1.type == "string" and type2.type == "string":
return True
if type1.type == "bytes" and type2.type == "bytes":
return True
if type1.type == "address" and type2.type == "address":
return _implicitly_convertible_to_for_addresses(type1, type2)
if type1.type in Uint and type2.type in Uint:
return _implicitly_convertible_to_for_uints(type1, type2)
if type1.type in Int and type2.type in Int:
return _implicitly_convertible_to_for_ints(type1, type2)
if (
type1.type != "bytes"
and type2.type != "bytes"
and type1.type in Byte
and type2.type in Byte
):
return _implicitly_convertible_to_for_bytes(type1, type2)
return False
def _implicitly_convertible_to_for_bytes(type1: ElementaryType, type2: ElementaryType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2 assuming they are both bytes.
"""
assert type1.type in Byte and type2.type in Byte
assert type1.size is not None
assert type2.size is not None
return type1.size <= type2.size
def _implicitly_convertible_to_for_addresses(type1: ElementaryType, type2: ElementaryType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2 assuming they are both addresses.
"""
assert type1.type == "address" and type2.type == "address"
# payable attribute to be implemented; for now, always return True
return True
def _implicitly_convertible_to_for_ints(type1: ElementaryType, type2: ElementaryType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2 assuming they are both ints.
"""
assert type1.type in Int and type2.type in Int
assert type1.size is not None
assert type2.size is not None
return type1.size <= type2.size
def _implicitly_convertible_to_for_uints(type1: ElementaryType, type2: ElementaryType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2 assuming they are both uints.
"""
assert type1.type in Uint and type2.type in Uint
assert type1.size is not None
assert type2.size is not None
return type1.size <= type2.size
def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool:
"""
Returns True if contract1 may be implicitly converted to contract2.
"""
return contract1 == contract2 or contract2 in contract1.inheritance
class IncorrectUsingFor(AbstractDetector):
"""
Detector for incorrect using-for statement usage.
"""
ARGUMENT = "incorrect-using-for"
HELP = "Detects using-for statement usage when no function from a given library matches a given type"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage"
WIKI_TITLE = "Incorrect usage of using-for statement"
WIKI_DESCRIPTION = (
"In Solidity, it is possible to use libraries for certain types, by the `using-for` statement "
"(`using <library> for <type>`). However, the Solidity compiler doesn't check whether a given "
"library has at least one function matching a given type. If it doesn't, such a statement has "
"no effect and may be confusing. "
)
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
library L {
function f(bool) public pure {}
}
using L for uint;
```
Such a code will compile despite the fact that `L` has no function with `uint` as its first argument."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Make sure that the libraries used in `using-for` statements have at least one function "
"matching a type used in these statements. "
)
def _append_result(
self, results: List[Output], uf: UsingForTopLevel, type_: Type, library: Contract
) -> None:
info: DETECTOR_INFO = [
f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in ",
library,
".\n",
]
res = self.generate_result(info)
results.append(res)
def _detect(self) -> List[Output]:
results: List[Output] = []
for uf in self.compilation_unit.using_for_top_level:
# UsingForTopLevel.using_for is a dict with a single entry, which is mapped to a list of functions/libraries
# the following code extracts the type from using-for and skips using-for statements with functions
type_ = list(uf.using_for.keys())[0]
for lib_or_fcn in uf.using_for[type_]:
# checking for using-for with functions is already performed by the compiler; we only consider libraries
if isinstance(lib_or_fcn, UserDefinedType):
lib_or_fcn_type = lib_or_fcn.type
if (
isinstance(type_, Type)
and isinstance(lib_or_fcn_type, Contract)
and not _is_correctly_used(type_, lib_or_fcn_type)
):
self._append_result(results, uf, type_, lib_or_fcn_type)
return results

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

@ -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 = []

@ -6,7 +6,7 @@
"""
from typing import List
from slither.core.cfg.node import Node
from slither.core.cfg.node import Node, NodeType
from slither.core.declarations.function_contract import FunctionContract
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output
@ -64,6 +64,15 @@ Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is
self.visited_all_paths[node] = list(set(self.visited_all_paths[node] + fathers_context))
# Remove a local variable declared in a for loop header
if (
node.type == NodeType.VARIABLE
and len(node.sons) == 1 # Should always be true for a node that has a STARTLOOP son
and node.sons[0].type == NodeType.STARTLOOP
):
if node.variable_declaration in fathers_context:
fathers_context.remove(node.variable_declaration)
if self.key in node.context:
fathers_context += node.context[self.key]

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

@ -17,7 +17,7 @@ class VarReadUsingThis(AbstractDetector):
IMPACT = DetectorClassification.OPTIMIZATION
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Vulnerabilities-Description#public-variable-read-in-external-context"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#public-variable-read-in-external-context"
WIKI_TITLE = "Public variable read in external context"
WIKI_DESCRIPTION = "The contract reads its own variable using `this`, adding overhead of an unnecessary STATICCALL."

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

@ -47,8 +47,6 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):
txt = ""
all_tables = []
for contract in self.contracts: # type: ignore
if contract.is_top_level:
continue
txt += f"\nContract {contract.name}\n"
table = MyPrettyTable(
["Function", "State variables written", "Conditions on msg.sender"]

@ -19,8 +19,6 @@ class CFG(AbstractPrinter):
info = ""
all_files = []
for contract in self.contracts: # type: ignore
if contract.is_top_level:
continue
for function in contract.functions + list(contract.modifiers):
if filename:
new_filename = f"{filename}-{contract.name}-{function.full_name}.dot"

@ -32,7 +32,7 @@ from slither.slithir.operations import (
from slither.slithir.operations.binary import Binary
from slither.slithir.variables import Constant
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:
@ -178,11 +178,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 ir.lvalue:
type_ = ir.lvalue.type
cst = ConstantFolding(ir.expression, type_).result()
all_cst_used.append(ConstantValue(str(cst.value), str(type_)))
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):

@ -36,8 +36,6 @@ class PrinterInheritance(AbstractPrinter):
result = {"child_to_base": {}}
for child in self.contracts:
if child.is_top_level:
continue
info += blue(f"\n+ {child.name}\n")
result["child_to_base"][child.name] = {"immediate": [], "not_immediate": []}
if child.inheritance:
@ -58,8 +56,6 @@ class PrinterInheritance(AbstractPrinter):
result["base_to_child"] = {}
for base in self.contracts:
if base.is_top_level:
continue
info += green(f"\n+ {base.name}") + "\n"
children = list(self._get_child_contracts(base))

@ -194,8 +194,6 @@ class PrinterInheritanceGraph(AbstractPrinter):
content = 'digraph "" {\n'
for c in self.contracts:
if c.is_top_level:
continue
content += self._summary(c) + "\n"
content += "}"

@ -28,9 +28,6 @@ class ContractSummary(AbstractPrinter):
all_contracts = []
for c in self.contracts:
if c.is_top_level:
continue
is_upgradeable_proxy = c.is_upgradeable_proxy
is_upgradeable = c.is_upgradeable

@ -40,8 +40,6 @@ class DataDependency(AbstractPrinter):
txt = ""
for c in self.contracts:
if c.is_top_level:
continue
txt += f"\nContract {c.name}\n"
table = MyPrettyTable(["Variable", "Dependencies"])
for v in c.state_variables:

@ -33,8 +33,6 @@ class FunctionSummary(AbstractPrinter):
all_txt = ""
for c in self.contracts:
if c.is_top_level:
continue
(name, inheritance, var, func_summaries, modif_summaries) = c.get_summary()
txt = f"\nContract {name}"
txt += "\nContract vars: " + str(var)

@ -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,10 +179,8 @@ 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
contracts = [c for c in self.slither.contracts if not c.is_top_level]
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]
return len(contracts) - len(deps) - len(tests), len(deps), len(tests)
@ -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

@ -36,8 +36,6 @@ class PrinterSlithIR(AbstractPrinter):
txt = ""
for compilation_unit in self.slither.compilation_units:
for contract in compilation_unit.contracts:
if contract.is_top_level:
continue
txt += f"Contract {contract.name}\n"
for function in contract.functions:
txt += f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}\n'

@ -21,8 +21,6 @@ class PrinterSlithIRSSA(AbstractPrinter):
txt = ""
for contract in self.contracts:
if contract.is_top_level:
continue
txt += f"Contract {contract.name}" + "\n"
for function in contract.functions:
txt += f"\tFunction {function.canonical_name}" + "\n"

@ -10,8 +10,6 @@ from slither.utils.myprettytable import MyPrettyTable
def _use_modifier(function: Function, modifier_name: str = "whenNotPaused") -> bool:
if function.is_constructor or function.view or function.pure:
return False
for internal_call in function.all_internal_calls():
if isinstance(internal_call, SolidityFunction):
@ -23,7 +21,7 @@ def _use_modifier(function: Function, modifier_name: str = "whenNotPaused") -> b
class PrinterWhenNotPaused(AbstractPrinter):
ARGUMENT = "pausable"
ARGUMENT = "not-pausable"
HELP = "Print functions that do not use whenNotPaused"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused"
@ -46,6 +44,8 @@ class PrinterWhenNotPaused(AbstractPrinter):
table = MyPrettyTable(["Name", "Use whenNotPaused"])
for function in contract.functions_entry_points:
if function.is_constructor or function.view or function.pure:
continue
status = "X" if _use_modifier(function, modifier_name) else ""
table.add_row([function.solidity_signature, status])

@ -1077,7 +1077,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
return op
if isinstance(ins.ori, TmpNewArray):
n = NewArray(ins.ori.depth, ins.ori.array_type, ins.lvalue)
n = NewArray(ins.ori.array_type, ins.lvalue)
n.set_expression(ins.expression)
return n
@ -1363,7 +1363,15 @@ 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
element_to_delete.set_type(ElementaryType("uint256"))
# 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(
ElementaryType("bytes1")
if isinstance(ir.destination.type, ElementaryType)
else ir.destination.type.type
)
ir_assign_element_to_delete.set_expression(ir.expression)
ir_assign_element_to_delete.set_node(ir.node)
ret.append(ir_assign_element_to_delete)
@ -1576,7 +1584,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(

@ -50,5 +50,5 @@ class Assignment(OperationWithLValue):
points = lvalue.points_to
while isinstance(points, ReferenceVariable):
points = points.points_to
return f"{lvalue} (->{points}) := {self.rvalue}({self.rvalue.type})"
return f"{lvalue}({lvalue.type}) (->{points}) := {self.rvalue}({self.rvalue.type})"
return f"{lvalue}({lvalue.type}) := {self.rvalue}({self.rvalue.type})"

@ -94,7 +94,6 @@ class BinaryType(Enum):
return self in [
BinaryType.POWER,
BinaryType.MULTIPLICATION,
BinaryType.MODULO,
BinaryType.ADDITION,
BinaryType.SUBTRACTION,
BinaryType.DIVISION,

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

@ -41,7 +41,7 @@ class InitArray(OperationWithLValue):
def convert(elem):
if isinstance(elem, (list,)):
return str([convert(x) for x in elem])
return str(elem)
return f"{elem}({elem.type})"
init_values = convert(self.init_values)
return f"{self.lvalue}({self.lvalue.type}) = {init_values}"

@ -1,10 +1,10 @@
from typing import List, Union, TYPE_CHECKING
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.core.solidity_types.array_type import ArrayType
from slither.slithir.operations.call import Call
from slither.core.solidity_types.type import Type
from slither.slithir.operations.lvalue import OperationWithLValue
if TYPE_CHECKING:
from slither.core.solidity_types.type_alias import TypeAliasTopLevel
from slither.slithir.variables.constant import Constant
from slither.slithir.variables.temporary import TemporaryVariable
from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
@ -13,29 +13,24 @@ if TYPE_CHECKING:
class NewArray(Call, OperationWithLValue):
def __init__(
self,
depth: int,
array_type: "TypeAliasTopLevel",
array_type: "ArrayType",
lvalue: Union["TemporaryVariableSSA", "TemporaryVariable"],
) -> None:
super().__init__()
assert isinstance(array_type, Type)
self._depth = depth
assert isinstance(array_type, ArrayType)
self._array_type = array_type
self._lvalue = lvalue
@property
def array_type(self) -> "TypeAliasTopLevel":
def array_type(self) -> "ArrayType":
return self._array_type
@property
def read(self) -> List["Constant"]:
return self._unroll(self.arguments)
@property
def depth(self) -> int:
return self._depth
def __str__(self):
args = [str(a) for a in self.arguments]
return f"{self.lvalue} = new {self.array_type}{'[]' * self.depth}({','.join(args)})"
lvalue = self.lvalue
return f"{lvalue}{lvalue.type}) = new {self.array_type}({','.join(args)})"

@ -104,4 +104,5 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
if self.call_salt:
options += f"salt:{self.call_salt} "
args = [str(a) for a in self.arguments]
return f"{self.lvalue} = new {self.contract_name}({','.join(args)}) {options}"
lvalue = self.lvalue
return f"{lvalue}({lvalue.type}) = new {self.contract_name}({','.join(args)}) {options}"

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

@ -58,7 +58,7 @@ class Unary(OperationWithLValue):
@property
def type_str(self):
return self._type.value
return str(self._type)
def __str__(self):
return f"{self.lvalue} = {self.type_str} {self.rvalue} "

@ -6,13 +6,11 @@ from slither.slithir.variables.temporary import TemporaryVariable
class TmpNewArray(OperationWithLValue):
def __init__(
self,
depth: int,
array_type: Type,
lvalue: TemporaryVariable,
) -> None:
super().__init__()
assert isinstance(array_type, Type)
self._depth = depth
self._array_type = array_type
self._lvalue = lvalue
@ -24,9 +22,5 @@ class TmpNewArray(OperationWithLValue):
def read(self):
return []
@property
def depth(self) -> int:
return self._depth
def __str__(self):
return f"{self.lvalue} = new {self.array_type}{'[]' * self._depth}"
return f"{self.lvalue} = new {self.array_type}"

@ -789,10 +789,9 @@ def copy_ir(ir: Operation, *instances) -> Operation:
variable_right = get_variable(ir, lambda x: x.variable_right, *instances)
return Member(variable_left, variable_right, lvalue)
if isinstance(ir, NewArray):
depth = ir.depth
array_type = ir.array_type
lvalue = get_variable(ir, lambda x: x.lvalue, *instances)
new_ir = NewArray(depth, array_type, lvalue)
new_ir = NewArray(array_type, lvalue)
new_ir.arguments = get_rec_values(ir, lambda x: x.arguments, *instances)
return new_ir
if isinstance(ir, NewElementaryType):

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

@ -170,10 +170,9 @@ class ContractSolc(CallerContextExpression):
elif attributes["contractKind"] == "library":
self._contract.is_library = True
self._contract.contract_kind = attributes["contractKind"]
self._contract.is_fully_implemented = attributes["fullyImplemented"]
self._contract.is_fully_implemented = attributes["fullyImplemented"]
self._linearized_base_contracts = attributes["linearizedBaseContracts"]
# self._contract.fullyImplemented = attributes["fullyImplemented"]
# Parse base contract information
self._parse_base_contract_info()

@ -1,5 +1,5 @@
import logging
from typing import Dict, Optional, Union, List, TYPE_CHECKING, Tuple
from typing import Dict, Optional, Union, List, TYPE_CHECKING, Tuple, Set
from slither.core.cfg.node import NodeType, link_nodes, insert_node, Node
from slither.core.cfg.scope import Scope
@ -354,7 +354,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
@ -362,21 +362,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
)
@ -385,22 +379,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:
@ -409,32 +397,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)
@ -562,7 +544,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:
@ -570,17 +552,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)
@ -588,7 +566,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)
@ -599,7 +577,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)
@ -619,14 +597,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(
@ -644,7 +618,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)
@ -660,46 +634,124 @@ 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 self.is_compact_ast:
params = statement.get("parameters", None)
else:
params = statement[self.get_children("children")]
if add_param:
if self.is_compact_ast:
params = statement.get("parameters", None)
else:
params = statement[self.get_children("children")]
if params:
for param in params.get("parameters", []):
assert param[self.get_key()] == "VariableDeclaration"
self._add_param(param)
if params:
for param in params.get("parameters", []):
assert param[self.get_key()] == "VariableDeclaration"
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)
@ -709,9 +761,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
@ -741,7 +791,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
@ -763,7 +813,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
@ -774,6 +824,7 @@ class FunctionSolc(CallerContextExpression):
"nodeType": "Identifier",
"src": v["src"],
"name": v["name"],
"referencedDeclaration": v["id"],
"typeDescriptions": {"typeString": v["typeDescriptions"]["typeString"]},
}
var_identifiers.append(identifier)
@ -794,9 +845,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)
@ -827,7 +876,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()
@ -846,7 +895,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 = []
@ -876,16 +925,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)
@ -895,7 +942,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
@ -916,15 +963,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:
@ -946,7 +993,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":
@ -987,7 +1034,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
@ -1001,7 +1048,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":
@ -1018,7 +1065,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
@ -1030,13 +1077,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
@ -1048,7 +1094,8 @@ 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
@ -1069,8 +1116,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()
@ -1150,17 +1196,18 @@ class FunctionSolc(CallerContextExpression):
if end_node:
for son in node.sons:
if son.type == NodeType.CATCH:
self._fix_catch(son, end_node)
self._fix_catch(son, end_node, set())
def _fix_catch(self, node: Node, end_node: Node) -> None:
def _fix_catch(self, node: Node, end_node: Node, visited: Set[Node]) -> None:
if not node.sons:
link_nodes(node, end_node)
else:
for son in node.sons:
if son != end_node:
self._fix_catch(son, end_node)
if son != end_node and son not in visited:
visited.add(son)
self._fix_catch(son, end_node, visited)
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)
@ -1170,6 +1217,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")
@ -1177,6 +1227,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"
@ -1395,8 +1456,7 @@ class FunctionSolc(CallerContextExpression):
endif_node = self._new_node(NodeType.ENDIF, node.source_mapping, node.scope)
for father in node.fathers:
father.remove_son(node)
father.add_son(condition_node.underlying_node)
father.replace_son(node, condition_node.underlying_node)
condition_node.underlying_node.add_father(father)
for son in node.sons:

@ -87,6 +87,9 @@ class ModifierSolc(FunctionSolc):
for node in self._node_to_nodesolc.values():
node.analyze_expressions(self)
for yul_parser in self._node_to_yulobject.values():
yul_parser.analyze_expressions()
self._rewrite_ternary_as_if_else()
self._remove_alone_endif()

@ -55,22 +55,29 @@ class UsingForTopLevelSolc(CallerContextExpression): # pylint: disable=too-few-
self._propagate_global(type_name)
else:
for f in self._functions:
full_name_split = f["function"]["name"].split(".")
if len(full_name_split) == 1:
# User defined operator
if "operator" in f:
# Top level function
function_name: str = full_name_split[0]
self._analyze_top_level_function(function_name, type_name)
elif len(full_name_split) == 2:
# It can be a top level function behind an aliased import
# or a library function
first_part = full_name_split[0]
function_name = full_name_split[1]
self._check_aliased_import(first_part, function_name, type_name)
function_name: str = f["definition"]["name"]
operator: str = f["operator"]
self._analyze_operator(operator, function_name, type_name)
else:
# MyImport.MyLib.a we don't care of the alias
library_name_str = full_name_split[1]
function_name = full_name_split[2]
self._analyze_library_function(library_name_str, function_name, type_name)
full_name_split = f["function"]["name"].split(".")
if len(full_name_split) == 1:
# Top level function
function_name: str = full_name_split[0]
self._analyze_top_level_function(function_name, type_name)
elif len(full_name_split) == 2:
# It can be a top level function behind an aliased import
# or a library function
first_part = full_name_split[0]
function_name = full_name_split[1]
self._check_aliased_import(first_part, function_name, type_name)
else:
# MyImport.MyLib.a we don't care of the alias
library_name_str = full_name_split[1]
function_name = full_name_split[2]
self._analyze_library_function(library_name_str, function_name, type_name)
def _check_aliased_import(
self,
@ -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,

@ -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 (
@ -236,6 +236,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 +292,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 +317,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 +467,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"):
@ -559,37 +595,9 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
type_name = children[0]
if type_name[caller_context.get_key()] == "ArrayTypeName":
depth = 0
while type_name[caller_context.get_key()] == "ArrayTypeName":
# Note: dont conserve the size of the array if provided
# We compute it directly
if is_compact_ast:
type_name = type_name["baseType"]
else:
type_name = type_name["children"][0]
depth += 1
if type_name[caller_context.get_key()] == "ElementaryTypeName":
if is_compact_ast:
array_type = ElementaryType(type_name["name"])
else:
array_type = ElementaryType(type_name["attributes"]["name"])
elif type_name[caller_context.get_key()] == "UserDefinedTypeName":
if is_compact_ast:
if "name" not in type_name:
name_type = type_name["pathNode"]["name"]
else:
name_type = type_name["name"]
array_type = parse_type(UnknownType(name_type), caller_context)
else:
array_type = parse_type(
UnknownType(type_name["attributes"]["name"]), caller_context
)
elif type_name[caller_context.get_key()] == "FunctionTypeName":
array_type = parse_type(type_name, caller_context)
else:
raise ParsingError(f"Incorrect type array {type_name}")
array = NewArray(depth, array_type)
array_type = parse_type(type_name, caller_context)
assert isinstance(array_type, ArrayType)
array = NewArray(array_type)
array.set_offset(src, caller_context.compilation_unit)
return array

@ -33,6 +33,10 @@ logger = logging.getLogger("SlitherSolcParsing")
logger.setLevel(logging.INFO)
class InheritanceResolutionError(SlitherException):
pass
def _handle_import_aliases(
symbol_aliases: Dict, import_directive: Import, scope: FileScope
) -> None:
@ -194,6 +198,13 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
def parse_top_level_from_loaded_json(self, data_loaded: Dict, filename: str) -> None:
if not data_loaded or data_loaded is None:
logger.error(
"crytic-compile returned an empty AST. "
"If you are trying to analyze a contract from etherscan or similar make sure it has source code available."
)
return
if "nodeType" in data_loaded:
self._is_compact_ast = True
@ -402,10 +413,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
# First we save all the contracts in a dict
# the key is the contractid
for contract in self._underlying_contract_to_parser:
if (
contract.name.startswith("SlitherInternalTopLevelContract")
and not contract.is_top_level
):
if contract.name.startswith("SlitherInternalTopLevelContract"):
raise SlitherException(
# region multi-line-string
"""Your codebase has a contract named 'SlitherInternalTopLevelContract'.
@ -435,7 +443,12 @@ Please rename it, this name is reserved for Slither's internals"""
target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_name
)
assert target
if target == contract_parser.underlying_contract:
raise InheritanceResolutionError(
"Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name."
)
assert target, f"Contract {contract_name} not found"
ancestors.append(target)
elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i])
@ -745,12 +758,46 @@ Please rename it, this name is reserved for Slither's internals"""
self._underlying_contract_to_parser[contract].log_incorrect_parsing(
f"Impossible to generate IR for {contract.name}.{func.name} ({func.source_mapping}):\n {e}"
)
contract.convert_expression_to_slithir_ssa()
except Exception as e:
func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
logger.error(
f"\nFailed to generate IR for {contract.name}.{func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{contract.name}.{func.name} ({func.source_mapping}):\n "
f"{func_expressions}"
)
raise e
try:
contract.convert_expression_to_slithir_ssa()
except Exception as e:
logger.error(
f"\nFailed to convert IR to SSA for {contract.name} contract. Please open an issue https://github.com/crytic/slither/issues.\n "
)
raise e
for func in self._compilation_unit.functions_top_level:
func.generate_slithir_and_analyze()
func.generate_slithir_ssa({})
try:
func.generate_slithir_and_analyze()
except AttributeError as e:
logger.error(
f"Impossible to generate IR for top level function {func.name} ({func.source_mapping}):\n {e}"
)
except Exception as e:
func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
logger.error(
f"\nFailed to generate IR for top level function {func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{func.name} ({func.source_mapping}):\n "
f"{func_expressions}"
)
raise e
try:
func.generate_slithir_ssa({})
except Exception as e:
func_expressions = "\n".join([f"\t{ex}" for ex in func.expressions])
logger.error(
f"\nFailed to convert IR to SSA for top level function {func.name}. Please open an issue https://github.com/crytic/slither/issues.\n{func.name} ({func.source_mapping}):\n "
f"{func_expressions}"
)
raise e
self._compilation_unit.propagate_function_calls()
for contract in self._compilation_unit.contracts:
contract.fix_phi()

@ -16,3 +16,21 @@ class LocalVariableInitFromTupleSolc(VariableDeclarationSolc[LocalVariableInitFr
# Todo: Not sure how to overcome this with mypy
assert isinstance(self._variable, LocalVariableInitFromTuple)
return self._variable
def _analyze_variable_attributes(self, attributes: Dict) -> None:
"""'
Variable Location
Can be storage/memory or default
"""
if "storageLocation" in attributes:
location = attributes["storageLocation"]
self.underlying_variable.set_location(location)
else:
if "memory" in attributes["type"]:
self.underlying_variable.set_location("memory")
elif "storage" in attributes["type"]:
self.underlying_variable.set_location("storage")
else:
self.underlying_variable.set_location("default")
super()._analyze_variable_attributes(attributes)

@ -51,6 +51,7 @@ evm_opcodes = [
"TIMESTAMP",
"NUMBER",
"DIFFICULTY",
"PREVRANDAO",
"GASLIMIT",
"CHAINID",
"SELFBALANCE",
@ -168,6 +169,7 @@ builtins = [
)
] + yul_funcs
# "identifier": [input_count, output_count]
function_args = {
"byte": [2, 1],
"addmod": [3, 1],
@ -221,6 +223,7 @@ function_args = {
"timestamp": [0, 1],
"number": [0, 1],
"difficulty": [0, 1],
"prevrandao": [0, 1],
"gaslimit": [0, 1],
}

@ -181,7 +181,7 @@ class YulScope(metaclass=abc.ABCMeta):
def add_yul_local_function(self, func: "YulFunction") -> None:
self._yul_local_functions.append(func)
def get_yul_local_function_from_name(self, func_name: str) -> Optional["YulLocalVariable"]:
def get_yul_local_function_from_name(self, func_name: str) -> Optional["YulFunction"]:
return next(
(v for v in self._yul_local_functions if v.underlying.name == func_name),
None,
@ -252,6 +252,10 @@ class YulFunction(YulScope):
def function(self) -> Function:
return self._function
@property
def root(self) -> YulScope:
return self._root
def convert_body(self) -> None:
node = self.new_node(NodeType.ENTRYPOINT, self._ast["src"])
link_underlying_nodes(self._entrypoint, node)
@ -271,6 +275,9 @@ class YulFunction(YulScope):
def parse_body(self) -> None:
for node in self._nodes:
node.analyze_expressions()
for f in self._yul_local_functions:
if f != self:
f.parse_body()
def new_node(self, node_type: NodeType, src: str) -> YulNode:
if self._function:
@ -325,7 +332,10 @@ class YulBlock(YulScope):
return yul_node
def convert(self, ast: Dict) -> YulNode:
return convert_yul(self, self._entrypoint, ast, self.node_scope)
yul_node = convert_yul(self, self._entrypoint, ast, self.node_scope)
for f in self._yul_local_functions:
f.parse_body()
return yul_node
def analyze_expressions(self) -> None:
for node in self._nodes:
@ -390,7 +400,6 @@ def convert_yul_function_definition(
root.add_yul_local_function(yul_function)
yul_function.convert_body()
yul_function.parse_body()
return parent
@ -778,6 +787,7 @@ def _parse_yul_magic_suffixes(name: str, root: YulScope) -> Optional[Expression]
return None
# pylint: disable=too-many-branches
def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[Expression]:
name = ast["name"]
@ -809,6 +819,23 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[
if func:
return Identifier(func.underlying)
# check yul-block scoped function
if isinstance(root, YulFunction):
yul_block = root.root
# Iterate until we searched in all the scopes until the YulBlock scope
while not isinstance(yul_block, YulBlock):
func = yul_block.get_yul_local_function_from_name(name)
if func:
return Identifier(func.underlying)
if isinstance(yul_block, YulFunction):
yul_block = yul_block.root
func = yul_block.get_yul_local_function_from_name(name)
if func:
return Identifier(func.underlying)
magic_suffix = _parse_yul_magic_suffixes(name, root)
if magic_suffix:
return magic_suffix

@ -15,7 +15,7 @@ ZIP_TYPES_ACCEPTED = {
Export = namedtuple("Export", ["filename", "content"])
logger = logging.getLogger("Slither")
logger = logging.getLogger("Slither-flat")
def save_to_zip(files: List[Export], zip_filename: str, zip_type: str = "lzma"):

@ -11,6 +11,7 @@ from slither.core.declarations import SolidityFunction, EnumContract, StructureC
from slither.core.declarations.contract import Contract
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.top_level import TopLevel
from slither.core.declarations.solidity_variables import SolidityCustomRevert
from slither.core.solidity_types import MappingType, ArrayType
from slither.core.solidity_types.type import Type
from slither.core.solidity_types.user_defined_type import UserDefinedType
@ -23,7 +24,8 @@ from slither.tools.flattening.export.export import (
save_to_disk,
)
logger = logging.getLogger("Slither-flattening")
logger = logging.getLogger("Slither-flat")
logger.setLevel(logging.INFO)
# index: where to start
# patch_type:
@ -75,6 +77,7 @@ class Flattening:
self._get_source_code_top_level(compilation_unit.structures_top_level)
self._get_source_code_top_level(compilation_unit.enums_top_level)
self._get_source_code_top_level(compilation_unit.custom_errors)
self._get_source_code_top_level(compilation_unit.variables_top_level)
self._get_source_code_top_level(compilation_unit.functions_top_level)
@ -249,12 +252,14 @@ class Flattening:
t: Type,
contract: Contract,
exported: Set[str],
list_contract: List[Contract],
list_top_level: List[TopLevel],
list_contract: Set[Contract],
list_top_level: Set[TopLevel],
):
if isinstance(t, UserDefinedType):
t_type = t.type
if isinstance(t_type, (EnumContract, StructureContract)):
if isinstance(t_type, TopLevel):
list_top_level.add(t_type)
elif isinstance(t_type, (EnumContract, StructureContract)):
if t_type.contract != contract and t_type.contract not in exported:
self._export_list_used_contracts(
t_type.contract, exported, list_contract, list_top_level
@ -275,8 +280,8 @@ class Flattening:
self,
contract: Contract,
exported: Set[str],
list_contract: List[Contract],
list_top_level: List[TopLevel],
list_contract: Set[Contract],
list_top_level: Set[TopLevel],
):
# TODO: investigate why this happen
if not isinstance(contract, Contract):
@ -332,19 +337,21 @@ class Flattening:
for read in ir.read:
if isinstance(read, TopLevel):
if read not in list_top_level:
list_top_level.append(read)
if isinstance(ir, InternalCall):
function_called = ir.function
if isinstance(function_called, FunctionTopLevel):
list_top_level.append(function_called)
if contract not in list_contract:
list_contract.append(contract)
list_top_level.add(read)
if isinstance(ir, InternalCall) and isinstance(ir.function, FunctionTopLevel):
list_top_level.add(ir.function)
if (
isinstance(ir, SolidityCall)
and isinstance(ir.function, SolidityCustomRevert)
and isinstance(ir.function.custom_error, TopLevel)
):
list_top_level.add(ir.function.custom_error)
list_contract.add(contract)
def _export_contract_with_inheritance(self, contract) -> Export:
list_contracts: List[Contract] = [] # will contain contract itself
list_top_level: List[TopLevel] = []
list_contracts: Set[Contract] = set() # will contain contract itself
list_top_level: Set[TopLevel] = set()
self._export_list_used_contracts(contract, set(), list_contracts, list_top_level)
path = Path(self._export_path, f"{contract.name}_{uuid.uuid4()}.sol")
@ -401,8 +408,8 @@ class Flattening:
def _export_with_import(self) -> List[Export]:
exports: List[Export] = []
for contract in self._compilation_unit.contracts:
list_contracts: List[Contract] = [] # will contain contract itself
list_top_level: List[TopLevel] = []
list_contracts: Set[Contract] = set() # will contain contract itself
list_top_level: Set[TopLevel] = set()
self._export_list_used_contracts(contract, set(), list_contracts, list_top_level)
if list_top_level:

@ -0,0 +1,21 @@
# Slither-interface
Generates code for a Solidity interface from contract
## Usage
Run `slither-interface <ContractName> <source file or deployment address>`.
## CLI Interface
```shell
positional arguments:
contract_source The name of the contract (case sensitive) followed by the deployed contract address if verified on etherscan or project directory/filename for local contracts.
optional arguments:
-h, --help show this help message and exit
--unroll-structs Whether to use structures' underlying types instead of the user-defined type
--exclude-events Excludes event signatures in the interface
--exclude-errors Excludes custom error signatures in the interface
--exclude-enums Excludes enum definitions in the interface
--exclude-structs Exclude struct definitions in the interface
```

@ -0,0 +1,106 @@
import argparse
import logging
from pathlib import Path
from crytic_compile import cryticparser
from slither import Slither
from slither.utils.code_generation import generate_interface
logging.basicConfig()
logger = logging.getLogger("Slither-Interface")
logger.setLevel(logging.INFO)
def parse_args() -> argparse.Namespace:
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
"""
parser = argparse.ArgumentParser(
description="Generates code for a Solidity interface from contract",
usage=("slither-interface <ContractName> <source file or deployment address>"),
)
parser.add_argument(
"contract_source",
help="The name of the contract (case sensitive) followed by the deployed contract address if verified on etherscan or project directory/filename for local contracts.",
nargs="+",
)
parser.add_argument(
"--unroll-structs",
help="Whether to use structures' underlying types instead of the user-defined type",
default=False,
action="store_true",
)
parser.add_argument(
"--exclude-events",
help="Excludes event signatures in the interface",
default=False,
action="store_true",
)
parser.add_argument(
"--exclude-errors",
help="Excludes custom error signatures in the interface",
default=False,
action="store_true",
)
parser.add_argument(
"--exclude-enums",
help="Excludes enum definitions in the interface",
default=False,
action="store_true",
)
parser.add_argument(
"--exclude-structs",
help="Exclude struct definitions in the interface",
default=False,
action="store_true",
)
cryticparser.init(parser)
return parser.parse_args()
def main() -> None:
args = parse_args()
contract_name, target = args.contract_source
slither = Slither(target, **vars(args))
_contract = slither.get_contract_from_name(contract_name)[0]
interface = generate_interface(
contract=_contract,
unroll_structs=args.unroll_structs,
include_events=not args.exclude_events,
include_errors=not args.exclude_errors,
include_enums=not args.exclude_enums,
include_structs=not args.exclude_structs,
)
# add version pragma
if _contract.compilation_unit.pragma_directives:
interface = (
f"pragma solidity {_contract.compilation_unit.pragma_directives[0].version};\n\n"
+ interface
)
# write interface to file
export = Path("crytic-export", "interfaces")
export.mkdir(parents=True, exist_ok=True)
filename = f"I{contract_name}.sol"
path = Path(export, filename)
logger.info(f" Interface exported to {path}")
with open(path, "w", encoding="utf8") as f:
f.write(interface)
if __name__ == "__main__":
main()

@ -8,20 +8,29 @@ Slither-read-storage is a tool to retrieve the storage slots and values of entir
```shell
positional arguments:
contract_source (DIR) ADDRESS The deployed contract address if verified on etherscan. Prepend project directory for unverified contracts.
contract_source The deployed contract address if verified on etherscan. Prepend project directory for unverified contracts.
optional arguments:
--variable-name VARIABLE_NAME The name of the variable whose value will be returned.
--rpc-url RPC_URL An endpoint for web3 requests.
--key KEY The key/ index whose value will be returned from a mapping or array.
--deep-key DEEP_KEY The key/ index whose value will be returned from a deep mapping or multidimensional array.
--struct-var STRUCT_VAR The name of the variable whose value will be returned from a struct.
--storage-address STORAGE_ADDRESS The address of the storage contract (if a proxy pattern is used).
--contract-name CONTRACT_NAME The name of the logic contract.
--json FILE Write the entire storage layout in JSON format to the specified FILE
--value Toggle used to include values in output.
--max-depth MAX_DEPTH Max depth to search in data structure.
--block BLOCK_NUMBER Block number to retrieve storage from (requires archive rpc node)
-h, --help show this help message and exit
--variable-name VARIABLE_NAME
The name of the variable whose value will be returned.
--rpc-url RPC_URL An endpoint for web3 requests.
--key KEY The key/ index whose value will be returned from a mapping or array.
--deep-key DEEP_KEY The key/ index whose value will be returned from a deep mapping or multidimensional array.
--struct-var STRUCT_VAR
The name of the variable whose value will be returned from a struct.
--storage-address STORAGE_ADDRESS
The address of the storage contract (if a proxy pattern is used).
--contract-name CONTRACT_NAME
The name of the logic contract.
--json JSON Save the result in a JSON file.
--value Toggle used to include values in output.
--table Print table view of storage layout
--silent Silence log outputs
--max-depth MAX_DEPTH
Max depth to search in data structure.
--block BLOCK The block number to read storage from. Requires an archive node to be provided as the RPC url.
--unstructured Include unstructured storage slots
```
### Examples

@ -1 +1 @@
from .read_storage import SlitherReadStorage
from .read_storage import SlitherReadStorage, RpcInfo

@ -7,7 +7,7 @@ import argparse
from crytic_compile import cryticparser
from slither import Slither
from slither.tools.read_storage.read_storage import SlitherReadStorage
from slither.tools.read_storage.read_storage import SlitherReadStorage, RpcInfo
def parse_args() -> argparse.Namespace:
@ -104,6 +104,12 @@ def parse_args() -> argparse.Namespace:
default="latest",
)
parser.add_argument(
"--unstructured",
action="store_true",
help="Include unstructured storage slots",
)
cryticparser.init(parser)
return parser.parse_args()
@ -126,22 +132,20 @@ def main() -> None:
else:
contracts = slither.contracts
srs = SlitherReadStorage(contracts, args.max_depth)
try:
srs.block = int(args.block)
except ValueError:
srs.block = str(args.block or "latest")
rpc_info = None
if args.rpc_url:
# Remove target prefix e.g. rinkeby:0x0 -> 0x0.
address = target[target.find(":") + 1 :]
# Default to implementation address unless a storage address is given.
if not args.storage_address:
args.storage_address = address
srs.storage_address = args.storage_address
srs.rpc = args.rpc_url
valid = ["latest", "earliest", "pending", "safe", "finalized"]
block = args.block if args.block in valid else int(args.block)
rpc_info = RpcInfo(args.rpc_url, block)
srs = SlitherReadStorage(contracts, args.max_depth, rpc_info)
srs.unstructured = bool(args.unstructured)
# Remove target prefix e.g. rinkeby:0x0 -> 0x0.
address = target[target.find(":") + 1 :]
# Default to implementation address unless a storage address is given.
if not args.storage_address:
args.storage_address = address
srs.storage_address = args.storage_address
if args.variable_name:
# Use a lambda func to only return variables that have same name as target.

@ -6,15 +6,30 @@ import dataclasses
from eth_abi import decode, encode
from eth_typing.evm import ChecksumAddress
from eth_utils import keccak
from eth_utils import keccak, to_checksum_address
from web3 import Web3
from web3.types import BlockIdentifier
from web3.exceptions import ExtraDataLengthError
from web3.middleware import geth_poa_middleware
from slither.core.declarations import Contract, Structure
from slither.core.solidity_types import ArrayType, ElementaryType, MappingType, UserDefinedType
from slither.core.solidity_types.type import Type
from slither.core.cfg.node import NodeType
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.structure_variable import StructureVariable
from slither.core.expressions import (
AssignmentOperation,
Literal,
Identifier,
BinaryOperation,
UnaryOperation,
TupleExpression,
TypeConversion,
CallExpression,
)
from slither.utils.myprettytable import MyPrettyTable
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant
from .utils import coerce_type, get_offset_value, get_storage_data
@ -42,20 +57,47 @@ class SlitherReadStorageException(Exception):
pass
# pylint: disable=too-many-instance-attributes
class RpcInfo:
def __init__(self, rpc_url: str, block: BlockIdentifier = "latest") -> None:
assert isinstance(block, int) or block in [
"latest",
"earliest",
"pending",
"safe",
"finalized",
]
self.rpc: str = rpc_url
self._web3: Web3 = Web3(Web3.HTTPProvider(self.rpc))
"""If the RPC is for a POA network, the first call to get_block fails, so we inject geth_poa_middleware"""
try:
self._block: int = self.web3.eth.get_block(block)["number"]
except ExtraDataLengthError:
self._web3.middleware_onion.inject(geth_poa_middleware, layer=0)
self._block: int = self.web3.eth.get_block(block)["number"]
@property
def web3(self) -> Web3:
return self._web3
@property
def block(self) -> int:
return self._block
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherReadStorage:
def __init__(self, contracts: List[Contract], max_depth: int) -> None:
def __init__(self, contracts: List[Contract], max_depth: int, rpc_info: RpcInfo = None) -> None:
self._checksum_address: Optional[ChecksumAddress] = None
self._contracts: List[Contract] = contracts
self._log: str = ""
self._max_depth: int = max_depth
self._slot_info: Dict[str, SlotInfo] = {}
self._target_variables: List[Tuple[Contract, StateVariable]] = []
self._web3: Optional[Web3] = None
self.block: Union[str, int] = "latest"
self.rpc: Optional[str] = None
self._constant_storage_slots: List[Tuple[Contract, StateVariable]] = []
self.rpc_info: Optional[RpcInfo] = rpc_info
self.storage_address: Optional[str] = None
self.table: Optional[MyPrettyTable] = None
self.unstructured: bool = False
@property
def contracts(self) -> List[Contract]:
@ -73,18 +115,12 @@ class SlitherReadStorage:
def log(self, log: str) -> None:
self._log = log
@property
def web3(self) -> Web3:
if not self._web3:
self._web3 = Web3(Web3.HTTPProvider(self.rpc))
return self._web3
@property
def checksum_address(self) -> ChecksumAddress:
if not self.storage_address:
raise ValueError
if not self._checksum_address:
self._checksum_address = self.web3.to_checksum_address(self.storage_address)
self._checksum_address = to_checksum_address(self.storage_address)
return self._checksum_address
@property
@ -92,6 +128,11 @@ class SlitherReadStorage:
"""Storage variables (not constant or immutable) and their associated contract."""
return self._target_variables
@property
def constant_slots(self) -> List[Tuple[Contract, StateVariable]]:
"""Constant bytes32 variables and their associated contract."""
return self._constant_storage_slots
@property
def slot_info(self) -> Dict[str, SlotInfo]:
"""Contains the location, type, size, offset, and value of contract slots."""
@ -111,9 +152,48 @@ class SlitherReadStorage:
elif isinstance(type_, ArrayType):
elems = self._all_array_slots(var, contract, type_, info.slot)
tmp[var.name].elems = elems
if self.unstructured:
tmp.update(self.get_unstructured_layout())
self._slot_info = tmp
def get_unstructured_layout(self) -> Dict[str, SlotInfo]:
tmp: Dict[str, SlotInfo] = {}
for _, var in self.constant_slots:
var_name = var.name
try:
exp = var.expression
if isinstance(
exp,
(
BinaryOperation,
UnaryOperation,
Identifier,
TupleExpression,
TypeConversion,
CallExpression,
),
):
exp = ConstantFolding(exp, "bytes32").result()
if isinstance(exp, Literal):
slot = coerce_type("int", exp.value)
else:
continue
offset = 0
type_string, size = self.find_constant_slot_storage_type(var)
if type_string:
tmp[var.name] = SlotInfo(
name=var_name, type_string=type_string, slot=slot, size=size, offset=offset
)
self.log += (
f"\nSlot Name: {var_name}\nType: bytes32"
f"\nStorage Type: {type_string}\nSlot: {str(exp)}\n"
)
logger.info(self.log)
self.log = ""
except NotConstant:
continue
return tmp
# TODO: remove this pylint exception (montyly)
# pylint: disable=too-many-locals
def get_storage_slot(
@ -122,7 +202,8 @@ class SlitherReadStorage:
contract: Contract,
**kwargs: Any,
) -> Union[SlotInfo, None]:
"""Finds the storage slot of a variable in a given contract.
"""
Finds the storage slot of a variable in a given contract.
Args:
target_variable (`StateVariable`): The variable to retrieve the slot for.
contracts (`Contract`): The contract that contains the given state variable.
@ -208,6 +289,78 @@ class SlitherReadStorage:
if slot_info:
self._slot_info[f"{contract.name}.{var.name}"] = slot_info
def find_constant_slot_storage_type(
self, var: StateVariable
) -> Tuple[Optional[str], Optional[int]]:
"""
Given a constant bytes32 StateVariable, tries to determine which variable type is stored there, using the
heuristic that if a function reads from the slot and returns a value, it probably stores that type of value.
Also uses the StorageSlot library as a heuristic when a function has no return but uses the library's getters.
Args:
var (StateVariable): The constant bytes32 storage slot.
Returns:
type (str): The type of value stored in the slot.
size (int): The type's size in bits.
"""
assert var.is_constant and var.type == ElementaryType("bytes32")
storage_type = None
size = None
funcs = []
for c in self.contracts:
c_funcs = c.get_functions_reading_from_variable(var)
c_funcs.extend(
f
for f in c.functions
if any(str(v.expression) == str(var.expression) for v in f.variables)
)
c_funcs = list(set(c_funcs))
funcs.extend(c_funcs)
fallback = [f for f in var.contract.functions if f.is_fallback]
funcs += fallback
for func in funcs:
rets = func.return_type if func.return_type is not None else []
for ret in rets:
size, _ = ret.storage_size
if size <= 32:
return str(ret), size * 8
for node in func.all_nodes():
exp = node.expression
# Look for use of the common OpenZeppelin StorageSlot library
if f"getAddressSlot({var.name})" in str(exp):
return "address", 160
if f"getBooleanSlot({var.name})" in str(exp):
return "bool", 1
if f"getBytes32Slot({var.name})" in str(exp):
return "bytes32", 256
if f"getUint256Slot({var.name})" in str(exp):
return "uint256", 256
# Look for variable assignment in assembly loaded from a hardcoded slot
if (
isinstance(exp, AssignmentOperation)
and isinstance(exp.expression_left, Identifier)
and isinstance(exp.expression_right, CallExpression)
and "sload" in str(exp.expression_right.called)
and str(exp.expression_right.arguments[0]) == str(var.expression)
):
if func.is_fallback:
return "address", 160
storage_type = exp.expression_left.value.type.name
size, _ = exp.expression_left.value.type.storage_size
return storage_type, size * 8
# Look for variable storage in assembly stored to a hardcoded slot
if (
isinstance(exp, CallExpression)
and "sstore" in str(exp.called)
and isinstance(exp.arguments[0], Identifier)
and isinstance(exp.arguments[1], Identifier)
and str(exp.arguments[0].value.expression) == str(var.expression)
):
storage_type = exp.arguments[1].value.type.name
size, _ = exp.arguments[1].value.type.storage_size
return storage_type, size * 8
return storage_type, size
def walk_slot_info(self, func: Callable) -> None:
stack = list(self.slot_info.values())
while stack:
@ -220,39 +373,178 @@ class SlitherReadStorage:
func(slot_info)
def get_slot_values(self, slot_info: SlotInfo) -> None:
"""Fetches the slot value of `SlotInfo` object
"""
Fetches the slot value of `SlotInfo` object
:param slot_info:
"""
assert self.rpc_info is not None
hex_bytes = get_storage_data(
self.web3,
self.rpc_info.web3,
self.checksum_address,
int.to_bytes(slot_info.slot, 32, byteorder="big"),
self.block,
self.rpc_info.block,
)
slot_info.value = self.convert_value_to_type(
hex_bytes, slot_info.size, slot_info.offset, slot_info.type_string
)
logger.info(f"\nValue: {slot_info.value}\n")
def get_all_storage_variables(self, func: Callable = None) -> None:
"""Fetches all storage variables from a list of contracts.
def get_all_storage_variables(self, func: Callable = lambda x: x) -> None:
"""
Fetches all storage variables from a list of contracts.
kwargs:
func (Callable, optional): A criteria to filter functions e.g. name.
"""
for contract in self.contracts:
self._target_variables.extend(
filter(
func,
[
(contract, var)
for var in contract.state_variables_ordered
if not var.is_constant and not var.is_immutable
],
)
for var in contract.state_variables_ordered:
if func(var):
if not var.is_constant and not var.is_immutable:
self._target_variables.append((contract, var))
elif (
self.unstructured
and var.is_constant
and var.type == ElementaryType("bytes32")
):
self._constant_storage_slots.append((contract, var))
if self.unstructured:
hardcoded_slot = self.find_hardcoded_slot_in_fallback(contract)
if hardcoded_slot is not None:
self._constant_storage_slots.append((contract, hardcoded_slot))
def find_hardcoded_slot_in_fallback(self, contract: Contract) -> Optional[StateVariable]:
"""
Searches the contract's fallback function for a sload from a literal storage slot, i.e.,
`let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7)`.
Args:
contract: a Contract object, which should have a fallback function.
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
fallback = None
for func in contract.functions_entry_points:
if func.is_fallback:
fallback = func
break
if fallback is None:
return None
queue = [fallback.entry_point]
visited = []
while len(queue) > 0:
node = queue.pop(0)
visited.append(node)
queue.extend(son for son in node.sons if son not in visited)
if node.type == NodeType.ASSEMBLY and isinstance(node.inline_asm, str):
return SlitherReadStorage.find_hardcoded_slot_in_asm_str(node.inline_asm, contract)
if node.type == NodeType.EXPRESSION:
sv = self.find_hardcoded_slot_in_exp(node.expression, contract)
if sv is not None:
return sv
return None
@staticmethod
def find_hardcoded_slot_in_asm_str(
inline_asm: str, contract: Contract
) -> Optional[StateVariable]:
"""
Searches a block of assembly code (given as a string) for a sload from a literal storage slot.
Does not work if the argument passed to sload does not start with "0x", i.e., `sload(add(1,1))`
or `and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff)`.
Args:
inline_asm: a string containing all the code in an assembly node (node.inline_asm for solc < 0.6.0).
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
asm_split = inline_asm.split("\n")
for asm in asm_split:
if "sload(" in asm: # Only handle literals
arg = asm.split("sload(")[1].split(")")[0]
if arg.startswith("0x"):
exp = Literal(arg, ElementaryType("bytes32"))
sv = StateVariable()
sv.name = "fallback_sload_hardcoded"
sv.expression = exp
sv.is_constant = True
sv.type = exp.type
sv.set_contract(contract)
return sv
return None
def find_hardcoded_slot_in_exp(
self, exp: "Expression", contract: Contract
) -> Optional[StateVariable]:
"""
Parses an expression to see if it contains a sload from a literal storage slot,
unrolling nested expressions if necessary to determine which slot it loads from.
Args:
exp: an Expression object to search.
contract: the Contract containing exp.
Returns:
A newly created StateVariable representing the Literal bytes32 slot, if one is found, otherwise None.
"""
if isinstance(exp, AssignmentOperation):
exp = exp.expression_right
while isinstance(exp, BinaryOperation):
exp = next(
(e for e in exp.expressions if isinstance(e, (CallExpression, BinaryOperation))),
exp.expression_left,
)
while isinstance(exp, CallExpression) and len(exp.arguments) > 0:
called = exp.called
exp = exp.arguments[0]
if "sload" in str(called):
break
if isinstance(
exp,
(
BinaryOperation,
UnaryOperation,
Identifier,
TupleExpression,
TypeConversion,
CallExpression,
),
):
try:
exp = ConstantFolding(exp, "bytes32").result()
except NotConstant:
return None
if (
isinstance(exp, Literal)
and isinstance(exp.type, ElementaryType)
and exp.type.name in ["bytes32", "uint256"]
):
sv = StateVariable()
sv.name = "fallback_sload_hardcoded"
value = exp.value
str_value = str(value)
if str_value.isdecimal():
value = int(value)
if isinstance(value, (int, bytes)):
if isinstance(value, bytes):
str_value = "0x" + value.hex()
value = int(str_value, 16)
exp = Literal(str_value, ElementaryType("bytes32"))
state_var_slots = [
self.get_variable_info(contract, var)[0]
for contract, var in self.target_variables
]
if value in state_var_slots:
return None
sv.expression = exp
sv.is_constant = True
sv.type = ElementaryType("bytes32")
sv.set_contract(contract)
return sv
return None
def convert_slot_info_to_rows(self, slot_info: SlotInfo) -> None:
"""Convert and append slot info to table. Create table if it
"""
Convert and append slot info to table. Create table if it
does not yet exist
:param slot_info:
"""
@ -270,7 +562,8 @@ class SlitherReadStorage:
def _find_struct_var_slot(
elems: List[StructureVariable], slot_as_bytes: bytes, struct_var: str
) -> Tuple[str, str, bytes, int, int]:
"""Finds the slot of a structure variable.
"""
Finds the slot of a structure variable.
Args:
elems (List[StructureVariable]): Ordered list of structure variables.
slot_as_bytes (bytes): The slot of the struct to begin searching at.
@ -312,7 +605,8 @@ class SlitherReadStorage:
deep_key: int = None,
struct_var: str = None,
) -> Tuple[str, str, bytes, int, int]:
"""Finds the slot of array's index.
"""
Finds the slot of array's index.
Args:
target_variable (`StateVariable`): The array that contains the target variable.
slot (bytes): The starting slot of the array.
@ -415,7 +709,8 @@ class SlitherReadStorage:
deep_key: Union[int, str] = None,
struct_var: str = None,
) -> Tuple[str, str, bytes, int, int]:
"""Finds the data slot of a target variable within a mapping.
"""
Finds the data slot of a target variable within a mapping.
target_variable (`StateVariable`): The mapping that contains the target variable.
slot (bytes): The starting slot of the mapping.
key (Union[int, str]): The key the variable is stored at.
@ -441,7 +736,7 @@ class SlitherReadStorage:
if "int" in key_type: # without this eth_utils encoding fails
key = int(key)
key = coerce_type(key_type, key)
slot = keccak(encode([key_type, "uint256"], [key, decode("uint256", slot)]))
slot = keccak(encode([key_type, "uint256"], [key, decode(["uint256"], slot)[0]]))
if isinstance(target_variable_type.type_to, UserDefinedType) and isinstance(
target_variable_type.type_to.type, Structure
@ -486,7 +781,7 @@ class SlitherReadStorage:
)
info += info_tmp
# TODO: suppory mapping with dynamic arrays
# TODO: support mapping with dynamic arrays
# mapping(elem => elem)
elif isinstance(target_variable_type.type_to, ElementaryType):
@ -592,7 +887,8 @@ class SlitherReadStorage:
return elems
def _get_array_length(self, type_: Type, slot: int) -> int:
"""Gets the length of dynamic and fixed arrays.
"""
Gets the length of dynamic and fixed arrays.
Args:
type_ (`AbstractType`): The array type.
slot (int): Slot a dynamic array's length is stored at.
@ -600,15 +896,15 @@ class SlitherReadStorage:
(int): The length of the array.
"""
val = 0
if self.rpc:
if self.rpc_info:
# The length of dynamic arrays is stored at the starting slot.
# Convert from hexadecimal to decimal.
val = int(
get_storage_data(
self.web3,
self.rpc_info.web3,
self.checksum_address,
int.to_bytes(slot, 32, byteorder="big"),
self.block,
self.rpc_info.block,
).hex(),
16,
)

@ -37,6 +37,8 @@ def coerce_type(
(Union[int, bool, str, ChecksumAddress, hex]): The type representation of the value.
"""
if "int" in solidity_type:
if str(value).startswith("0x"):
return to_int(hexstr=value)
return to_int(value)
if "bool" in solidity_type:
return bool(to_int(value))

@ -1,62 +1,149 @@
# Functions for generating Solidity code
from typing import TYPE_CHECKING, Optional
from slither.utils.type import convert_type_for_solidity_signature_to_string
from slither.utils.type import (
convert_type_for_solidity_signature_to_string,
export_nested_types_from_variable,
export_return_type_from_variable,
)
from slither.core.solidity_types import (
Type,
UserDefinedType,
MappingType,
ArrayType,
ElementaryType,
)
from slither.core.declarations import Structure, Enum, Contract
if TYPE_CHECKING:
from slither.core.declarations import FunctionContract, Structure, Contract
from slither.core.declarations import FunctionContract, CustomErrorContract
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.local_variable import LocalVariable
def generate_interface(contract: "Contract") -> str:
# pylint: disable=too-many-arguments
def generate_interface(
contract: "Contract",
unroll_structs: bool = True,
include_events: bool = True,
include_errors: bool = True,
include_enums: bool = True,
include_structs: bool = True,
) -> str:
"""
Generates code for a Solidity interface to the contract.
Args:
contract: A Contract object
contract: A Contract object.
unroll_structs: Whether to use structures' underlying types instead of the user-defined type (default: True).
include_events: Whether to include event signatures in the interface (default: True).
include_errors: Whether to include custom error signatures in the interface (default: True).
include_enums: Whether to include enum definitions in the interface (default: True).
include_structs: Whether to include struct definitions in the interface (default: True).
Returns:
A string with the code for an interface, with function stubs for all public or external functions and
state variables, as well as any events, custom errors and/or structs declared in the contract.
"""
interface = f"interface I{contract.name} {{\n"
for event in contract.events:
name, args = event.signature
interface += f" event {name}({', '.join(args)});\n"
for error in contract.custom_errors:
args = [
convert_type_for_solidity_signature_to_string(arg.type)
.replace("(", "")
.replace(")", "")
for arg in error.parameters
]
interface += f" error {error.name}({', '.join(args)});\n"
for enum in contract.enums:
interface += f" enum {enum.name} {{ {', '.join(enum.values)} }}\n"
for struct in contract.structures:
interface += generate_struct_interface_str(struct)
if include_events:
for event in contract.events:
name, args = event.signature
interface += f" event {name}({', '.join(args)});\n"
if include_errors:
for error in contract.custom_errors:
interface += f" error {generate_custom_error_interface(error, unroll_structs)};\n"
if include_enums:
for enum in contract.enums:
interface += f" enum {enum.name} {{ {', '.join(enum.values)} }}\n"
if include_structs:
for struct in contract.structures:
interface += generate_struct_interface_str(struct, indent=4)
for var in contract.state_variables_entry_points:
interface += f" function {var.signature_str.replace('returns', 'external returns ')};\n"
interface += f" function {generate_interface_variable_signature(var, unroll_structs)};\n"
for func in contract.functions_entry_points:
if func.is_constructor or func.is_fallback or func.is_receive:
continue
interface += f" function {generate_interface_function_signature(func)};\n"
interface += (
f" function {generate_interface_function_signature(func, unroll_structs)};\n"
)
interface += "}\n\n"
return interface
def generate_interface_function_signature(func: "FunctionContract") -> Optional[str]:
def generate_interface_variable_signature(
var: "StateVariable", unroll_structs: bool = True
) -> Optional[str]:
if var.visibility in ["private", "internal"]:
return None
if unroll_structs:
params = [
convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "")
for x in export_nested_types_from_variable(var)
]
returns = [
convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "")
for x in export_return_type_from_variable(var)
]
else:
_, params, _ = var.signature
params = [p + " memory" if p in ["bytes", "string"] else p for p in params]
returns = []
_type = var.type
while isinstance(_type, MappingType):
_type = _type.type_to
while isinstance(_type, (ArrayType, UserDefinedType)):
_type = _type.type
ret = str(_type)
if isinstance(_type, Structure) or (isinstance(_type, Type) and _type.is_dynamic):
ret += " memory"
elif isinstance(_type, Contract):
ret = "address"
returns.append(ret)
return f"{var.name}({','.join(params)}) external returns ({', '.join(returns)})"
def generate_interface_function_signature(
func: "FunctionContract", unroll_structs: bool = True
) -> Optional[str]:
"""
Generates a string of the form:
func_name(type1,type2) external {payable/view/pure} returns (type3)
Args:
func: A FunctionContract object
unroll_structs: Determines whether structs are unrolled into underlying types (default: True)
Returns:
The function interface as a str (contains the return values).
Returns None if the function is private or internal, or is a constructor/fallback/receive.
"""
name, parameters, return_vars = func.signature
def format_var(var: "LocalVariable", unroll: bool) -> str:
if unroll:
return (
convert_type_for_solidity_signature_to_string(var.type)
.replace("(", "")
.replace(")", "")
)
if isinstance(var.type, ArrayType) and isinstance(
var.type.type, (UserDefinedType, ElementaryType)
):
return (
convert_type_for_solidity_signature_to_string(var.type)
.replace("(", "")
.replace(")", "")
+ f" {var.location}"
)
if isinstance(var.type, UserDefinedType):
if isinstance(var.type.type, (Structure, Enum)):
return f"{str(var.type.type)} memory"
if isinstance(var.type.type, Contract):
return "address"
if var.type.is_dynamic:
return f"{var.type} {var.location}"
return str(var.type)
name, _, _ = func.signature
if (
func not in func.contract.functions_entry_points
or func.is_constructor
@ -64,26 +151,20 @@ def generate_interface_function_signature(func: "FunctionContract") -> Optional[
or func.is_receive
):
return None
view = " view" if func.view else ""
view = " view" if func.view and not func.pure else ""
pure = " pure" if func.pure else ""
payable = " payable" if func.payable else ""
returns = [
convert_type_for_solidity_signature_to_string(ret.type).replace("(", "").replace(")", "")
for ret in func.returns
]
parameters = [
convert_type_for_solidity_signature_to_string(param.type).replace("(", "").replace(")", "")
for param in func.parameters
]
returns = [format_var(ret, unroll_structs) for ret in func.returns]
parameters = [format_var(param, unroll_structs) for param in func.parameters]
_interface_signature_str = (
name + "(" + ",".join(parameters) + ") external" + payable + pure + view
)
if len(return_vars) > 0:
if len(returns) > 0:
_interface_signature_str += " returns (" + ",".join(returns) + ")"
return _interface_signature_str
def generate_struct_interface_str(struct: "Structure") -> str:
def generate_struct_interface_str(struct: "Structure", indent: int = 0) -> str:
"""
Generates code for a structure declaration in an interface of the form:
struct struct_name {
@ -92,13 +173,37 @@ def generate_struct_interface_str(struct: "Structure") -> str:
... ...
}
Args:
struct: A Structure object
struct: A Structure object.
indent: Number of spaces to indent the code block with.
Returns:
The structure declaration code as a string.
"""
definition = f" struct {struct.name} {{\n"
spaces = ""
for _ in range(0, indent):
spaces += " "
definition = f"{spaces}struct {struct.name} {{\n"
for elem in struct.elems_ordered:
definition += f" {elem.type} {elem.name};\n"
definition += " }\n"
if isinstance(elem.type, UserDefinedType):
if isinstance(elem.type.type, (Structure, Enum)):
definition += f"{spaces} {elem.type.type} {elem.name};\n"
elif isinstance(elem.type.type, Contract):
definition += f"{spaces} address {elem.name};\n"
else:
definition += f"{spaces} {elem.type} {elem.name};\n"
definition += f"{spaces}}}\n"
return definition
def generate_custom_error_interface(
error: "CustomErrorContract", unroll_structs: bool = True
) -> str:
args = [
convert_type_for_solidity_signature_to_string(arg.type).replace("(", "").replace(")", "")
if unroll_structs
else str(arg.type.type)
if isinstance(arg.type, UserDefinedType) and isinstance(arg.type.type, (Structure, Enum))
else str(arg.type)
for arg in error.parameters
]
return f"{error.name}({', '.join(args)})"

@ -1,4 +1,5 @@
import argparse
import enum
import json
import os
import re
@ -27,6 +28,15 @@ JSON_OUTPUT_TYPES = [
"list-printers",
]
class FailOnLevel(enum.Enum):
PEDANTIC = "pedantic"
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
NONE = "none"
# Those are the flags shared by the command line and the config file
defaults_flag_in_config = {
"codex": False,
@ -44,10 +54,7 @@ defaults_flag_in_config = {
"exclude_low": False,
"exclude_medium": False,
"exclude_high": False,
"fail_pedantic": True,
"fail_low": False,
"fail_medium": False,
"fail_high": False,
"fail_on": FailOnLevel.PEDANTIC,
"json": None,
"sarif": None,
"json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES),
@ -64,6 +71,13 @@ defaults_flag_in_config = {
**DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE,
}
deprecated_flags = {
"fail_pedantic": True,
"fail_low": False,
"fail_medium": False,
"fail_high": False,
}
def read_config_file(args: argparse.Namespace) -> None:
# No config file was provided as an argument
@ -80,6 +94,12 @@ def read_config_file(args: argparse.Namespace) -> None:
with open(args.config_file, encoding="utf8") as f:
config = json.load(f)
for key, elem in config.items():
if key in deprecated_flags:
logger.info(
yellow(f"{args.config_file} has a deprecated key: {key} : {elem}")
)
migrate_config_options(args, key, elem)
continue
if key not in defaults_flag_in_config:
logger.info(
yellow(f"{args.config_file} has an unknown key: {key} : {elem}")
@ -94,6 +114,28 @@ def read_config_file(args: argparse.Namespace) -> None:
logger.error(yellow("Falling back to the default settings..."))
def migrate_config_options(args: argparse.Namespace, key: str, elem):
if key.startswith("fail_") and getattr(args, "fail_on") == defaults_flag_in_config["fail_on"]:
if key == "fail_pedantic":
pedantic_setting = elem
fail_on = FailOnLevel.PEDANTIC if pedantic_setting else FailOnLevel.NONE
setattr(args, "fail_on", fail_on)
logger.info(f"Migrating fail_pedantic: {pedantic_setting} as fail_on: {fail_on.value}")
elif key == "fail_low" and elem is True:
logger.info("Migrating fail_low: true -> fail_on: low")
setattr(args, "fail_on", FailOnLevel.LOW)
elif key == "fail_medium" and elem is True:
logger.info("Migrating fail_medium: true -> fail_on: medium")
setattr(args, "fail_on", FailOnLevel.MEDIUM)
elif key == "fail_high" and elem is True:
logger.info("Migrating fail_high: true -> fail_on: high")
setattr(args, "fail_on", FailOnLevel.HIGH)
else:
logger.warning(yellow(f"Key {key} was deprecated but no migration was provided"))
def output_to_markdown(
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],

@ -21,7 +21,7 @@ from slither.core.expressions.new_array import NewArray
from slither.core.expressions.new_contract import NewContract
from slither.core.expressions.tuple_expression import TupleExpression
from slither.core.expressions.type_conversion import TypeConversion
from slither.core.expressions.new_elementary_type import NewElementaryType
# pylint: disable=protected-access
def f_expressions(
@ -100,7 +100,14 @@ class SplitTernaryExpression:
if isinstance(
expression,
(Literal, Identifier, NewArray, NewContract, ElementaryTypeNameExpression),
(
Literal,
Identifier,
NewArray,
NewContract,
ElementaryTypeNameExpression,
NewElementaryType,
),
):
return

@ -4,7 +4,9 @@ from typing import Union
from slither.exceptions import SlitherError
def convert_string_to_fraction(val: Union[str, int]) -> Fraction:
def convert_string_to_fraction(val: Union[str, bytes, int]) -> Fraction:
if isinstance(val, bytes):
return int.from_bytes(val, byteorder="big")
if isinstance(val, int):
return Fraction(val)
if val.startswith(("0x", "0X")):

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

Loading…
Cancel
Save