Merge branch 'dev' into dev-source-mapping-refactor

pull/877/head
Josselin Feist 2 years ago
commit 5802174562
  1. 61
      .github/ISSUE_TEMPLATE/bug_report.yml
  2. 1
      .github/ISSUE_TEMPLATE/feature_request.yml
  3. 42
      .github/workflows/IR.yml
  4. 13
      .github/workflows/black.yml
  5. 76
      .github/workflows/ci.yml
  6. 20
      .github/workflows/detectors.yml
  7. 22
      .github/workflows/features.yml
  8. 13
      .github/workflows/linter.yml
  9. 28
      .github/workflows/parser.yml
  10. 36
      .github/workflows/pip-audit.yml
  11. 13
      .github/workflows/pylint.yml
  12. 50
      .github/workflows/read_storage.yml
  13. 27
      CONTRIBUTING.md
  14. 11
      Dockerfile
  15. 37
      README.md
  16. 3
      examples/scripts/taint_mapping.py
  17. 2
      plugin_example/setup.py
  18. 13
      scripts/ci_test_dapp.sh
  19. 2
      scripts/ci_test_kspec.sh
  20. 4
      scripts/ci_test_simil.sh
  21. 2
      scripts/ci_test_truffle.sh
  22. 22
      setup.py
  23. 45
      slither/__main__.py
  24. 4
      slither/core/cfg/node.py
  25. 2
      slither/core/children/child_node.py
  26. 2
      slither/core/compilation_unit.py
  27. 57
      slither/core/declarations/contract.py
  28. 19
      slither/core/declarations/custom_error.py
  29. 11
      slither/core/declarations/enum.py
  30. 18
      slither/core/declarations/function.py
  31. 6
      slither/core/declarations/import_directive.py
  32. 2
      slither/core/declarations/solidity_variables.py
  33. 4
      slither/core/declarations/structure.py
  34. 16
      slither/core/scope/scope.py
  35. 31
      slither/core/slither_core.py
  36. 2
      slither/core/solidity_types/__init__.py
  37. 14
      slither/core/solidity_types/array_type.py
  38. 22
      slither/core/solidity_types/elementary_type.py
  39. 4
      slither/core/solidity_types/function_type.py
  40. 4
      slither/core/solidity_types/mapping_type.py
  41. 5
      slither/core/solidity_types/type.py
  42. 45
      slither/core/solidity_types/type_alias.py
  43. 7
      slither/core/solidity_types/type_information.py
  44. 4
      slither/core/solidity_types/user_defined_type.py
  45. 32
      slither/core/variables/state_variable.py
  46. 78
      slither/core/variables/variable.py
  47. 7
      slither/detectors/all_detectors.py
  48. 4
      slither/detectors/assembly/shift_parameter_mixup.py
  49. 2
      slither/detectors/attributes/constant_pragma.py
  50. 2
      slither/detectors/attributes/incorrect_solc.py
  51. 2
      slither/detectors/attributes/unimplemented_interface.py
  52. 0
      slither/detectors/erc/erc20/__init__.py
  53. 95
      slither/detectors/erc/erc20/arbitrary_send_erc20.py
  54. 45
      slither/detectors/erc/erc20/arbitrary_send_erc20_no_permit.py
  55. 53
      slither/detectors/erc/erc20/arbitrary_send_erc20_permit.py
  56. 0
      slither/detectors/erc/erc20/incorrect_erc20_interface.py
  57. 6
      slither/detectors/functions/arbitrary_send_eth.py
  58. 81
      slither/detectors/functions/protected_variable.py
  59. 14
      slither/detectors/naming_convention/naming_convention.py
  60. 4
      slither/detectors/shadowing/abstract.py
  61. 30
      slither/detectors/shadowing/common.py
  62. 8
      slither/detectors/shadowing/state.py
  63. 2
      slither/detectors/source/rtlo.py
  64. 8
      slither/detectors/statements/calls_in_loop.py
  65. 8
      slither/detectors/statements/costly_operations_in_loop.py
  66. 8
      slither/detectors/statements/delegatecall_in_loop.py
  67. 8
      slither/detectors/statements/msg_value_in_loop.py
  68. 12
      slither/detectors/statements/too_many_digits.py
  69. 2
      slither/detectors/statements/type_based_tautology.py
  70. 25
      slither/detectors/statements/unprotected_upgradeable.py
  71. 4
      slither/detectors/variables/similar_variables.py
  72. 6
      slither/detectors/variables/uninitialized_local_variables.py
  73. 2
      slither/detectors/variables/uninitialized_storage_variables.py
  74. 2
      slither/formatters/attributes/const_functions.py
  75. 2
      slither/formatters/attributes/constant_pragma.py
  76. 3
      slither/formatters/attributes/incorrect_solc.py
  77. 2
      slither/formatters/functions/external_function.py
  78. 10
      slither/formatters/naming_convention/naming_convention.py
  79. 1
      slither/printers/all_printers.py
  80. 29
      slither/printers/guidance/echidna.py
  81. 4
      slither/printers/summary/function_ids.py
  82. 2
      slither/printers/summary/variable_order.py
  83. 61
      slither/printers/summary/when_not_paused.py
  84. 2
      slither/slither.py
  85. 54
      slither/slithir/convert.py
  86. 20
      slither/slithir/operations/high_level_call.py
  87. 2
      slither/slithir/operations/library_call.py
  88. 2
      slither/slithir/operations/low_level_call.py
  89. 31
      slither/solc_parsing/declarations/contract.py
  90. 1
      slither/solc_parsing/declarations/custom_error.py
  91. 3
      slither/solc_parsing/expressions/expression_parsing.py
  92. 42
      slither/solc_parsing/expressions/find_variable.py
  93. 84
      slither/solc_parsing/slither_compilation_unit_solc.py
  94. 60
      slither/solc_parsing/solidity_types/type_parsing.py
  95. 20
      slither/solc_parsing/variables/variable_declaration.py
  96. 5
      slither/solc_parsing/yul/parse_yul.py
  97. 9
      slither/tools/erc_conformance/erc/ercs.py
  98. 52
      slither/tools/flattening/__main__.py
  99. 190
      slither/tools/flattening/flattening.py
  100. 4
      slither/tools/kspec_coverage/analysis.py
  101. Some files were not shown because too many files have changed in this diff Show More

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

@ -1,3 +1,4 @@
---
name: Feature request
description: Suggest a feature
labels: ["enhancement"]

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

@ -11,7 +11,7 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Black
uses: github/super-linter/slim@v4.8.7
uses: github/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -1,9 +1,9 @@
---
name: CI
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
shell: bash
on:
push:
@ -13,46 +13,66 @@ on:
pull_request:
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
tests:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["cli",
"dapp",
"data_dependency",
"embark",
# "embark",
"erc",
"etherlime",
# "etherlime",
# "etherscan"
"find_paths",
"flat",
"kspec",
"printers",
# "prop"
"simil",
"slither_config",
"truffle",
"upgradability",
# "prop",
"flat"]
"upgradability"]
exclude:
# Requires nix
- os: windows-2022
type: dapp
# Requires nvm
- os: windows-2022
type: truffle
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.6
uses: actions/setup-python@v1
with:
python-version: 3.6
- name: Install dependencies
run: |
python setup.py install
# Used by ci_test.sh
pip install deepdiff
- uses: actions/checkout@v1
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[dev]"
solc-select install all
solc-select use 0.5.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install solc-select
solc-select install all
solc-select use 0.5.1
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v16
- name: Run Tests
env:
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: |
bash "scripts/ci_test_${TEST_TYPE}.sh"
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v10
with:
name: dapp
- name: Run Tests
env:
PYTHONUTF8: 1
TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: |
bash "scripts/ci_test_${TEST_TYPE}.sh"

@ -11,32 +11,32 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
name: Detectors tests
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest
pip install ".[dev]"
pip install solc-select
solc-select install all
solc-select use 0.7.3
- name: Test with pytest
run: |
pytest tests/test_detectors.py

@ -11,29 +11,30 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
name: Features tests
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest
pip install ".[dev]"
pip install solc-select
solc-select install all
solc-select use 0.8.0
@ -45,4 +46,5 @@ jobs:
run: |
pytest tests/test_features.py
pytest tests/test_constant_folding_unary.py
pytest tests/slithir/test_ternary_expressions.py
pytest tests/test_functions_ids.py

@ -11,7 +11,7 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Lint everything else
uses: github/super-linter/slim@v4
uses: github/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -11,37 +11,35 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
name: Parser tests
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-2022]
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest
git clone https://github.com/crytic/solc-select.git
cd solc-select
git checkout 119dd05f58341811cb02b546f25269a7e8a10875
python setup.py install
pip install ".[dev]"
- name: Install solc
run: |
solc-select install all
solc-select use 0.8.0
cd ..
- name: Test with pytest
run: |
pytest tests/test_ast_parsing.py

@ -1,3 +1,4 @@
---
name: pip-audit
on:
@ -10,18 +11,25 @@ on:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Python 3.10
uses: actions/setup-python@v2
with:
python-version: "3.10"
- name: Install pip-audit
run: |
python -m pip install --upgrade pip
python -m pip install pip-audit
- name: Run pip-audit
run: |
python -m pip install .
pip-audit --desc -v
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install Slither
run: |
python -m venv /tmp/pip-audit-env
source /tmp/pip-audit-env/bin/activate
python -m pip install --upgrade pip
python -m pip install .
- name: Run pip-audit
uses: trailofbits/gh-action-pip-audit@v0.0.4
with:
virtual-environment: /tmp/pip-audit-env

@ -11,7 +11,7 @@ on:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
- cron: '0 12 * * *'
jobs:
build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
uses: actions/setup-python@v2
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Pylint
uses: github/super-linter/slim@v4
uses: github/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -0,0 +1,50 @@
---
name: Test slither-read-storage
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
schedule:
# run CI every day even if no PRs/merges occur
- cron: '0 12 * * *'
jobs:
build:
name: Test slither-read-storage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install ganache
run: npm install --global ganache
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install python dependencies
run: |
pip install ".[dev]"
pip install web3
solc-select install 0.8.1
solc-select install 0.8.10
solc-select use 0.8.1
- name: Run slither-read-storage
run: |
pytest tests/test_read_storage.py
- name: Run storage layout tests
run: |
pytest tests/test_storage_layout.py

@ -20,13 +20,28 @@ Some pull request guidelines:
- Fill out the pull request description with a summary of what your patch does, key changes that have been made, and any further points of discussion, if applicable.
- Title your pull request with a brief description of what it's changing. "Fixes #123" is a good comment to add to the description, but makes for an unclear title on its own.
## Directory Structure
Below is a rough outline of slither's design:
```text
.
├── analyses # Provides additional info such as data dependency
├── core # Ties everything together
├── detectors # Rules that define and identify issues
├── slither.py # Main entry point
├── slithir # Contains the semantics of slither's intermediate representation
├── solc_parsing # Responsible for parsing the solc AST
├── tools # Miscellaneous tools built on top of slither
├── visitors # Parses expressions and converts to slithir
└── ...
```
A code walkthrough is available [here](https://www.youtube.com/watch?v=EUl3UlYSluU).
## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).
To run the unit tests, you need
- `deepdiff` installed (`pip install deepdiff`).
- `pycov` installed (`pip install pytest-cov`).
- [`solc-select`](https://github.com/crytic/solc-select) installed.
To run the unit tests, you need to clone this repository and run `pip install ".[dev]"`.
### Linters
@ -37,7 +52,8 @@ To run them locally in the root dir of the repository:
- `pylint slither tests --rcfile pyproject.toml`
- `black . --config pyproject.toml`
We use pylint `2.12.2` black `21.10b0`.
We use pylint `2.13.4`, black `22.3.0`.
### Detectors tests
For each new detector, at least one regression tests must be present.
@ -45,6 +61,7 @@ For each new detector, at least one regression tests must be present.
- Create a test in `tests`
- 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`

@ -1,4 +1,4 @@
FROM ubuntu:bionic
FROM ubuntu:focal
LABEL name=slither
LABEL src="https://github.com/trailofbits/slither"
@ -6,11 +6,12 @@ LABEL creator=trailofbits
LABEL dockerfile_maintenance=trailofbits
LABEL desc="Static Analyzer for Solidity"
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y git python3 python3-setuptools wget software-properties-common
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get upgrade -yq \
&& apt-get install -yq gcc git python3 python3-dev python3-setuptools wget software-properties-common
RUN wget https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux \
RUN wget -q https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux \
&& chmod +x solc-static-linux \
&& mv solc-static-linux /usr/bin/solc

@ -13,6 +13,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s
- [Tools](#tools)
- [How to Install](#how-to-install)
- [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications)
## Features
@ -40,9 +41,12 @@ Run Slither on a single file:
slither tests/uninitialized.sol
```
For GitHub action integration, see [slither-action](https://github.com/marketplace/actions/slither-action). For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
### Integration
- For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action).
- To generate a Markdown report, use `slither [target] --checklist`.
- To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`)
Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc.
Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc. For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
### Detectors
@ -51,7 +55,7 @@ Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | ---
1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High
2 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#shift-parameter-mixup) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High
4 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
5 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
6 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
@ -121,10 +125,12 @@ Num | Detector | What it Detects | Impact | Confidence
70 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-are-too-similar) | Informational | Medium
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
75 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
76 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
77 | `arbitrary-send-erc20` | [Detect when `msg.sender` is not used as `from` in transferFrom](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20)
78 | `arbitrary-send-erc20-permit` | [Detect when `msg.sender` is not used as `from` in transferFrom in conjuction with permit](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit)
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
@ -143,6 +149,7 @@ For more information, see
- `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
- `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).
To run a printer, use `--print` and a comma-separated list of printers.
@ -155,6 +162,7 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d
- `slither-flat`: [Flatten a codebase](https://github.com/crytic/slither/wiki/Contract-Flattening)
- `slither-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)
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
@ -162,7 +170,7 @@ See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documen
## How to install
Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
Slither requires Python 3.8+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
### Using Pip
@ -181,7 +189,7 @@ We recommend using a Python virtual environment, as detailed in the [Developer I
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
@ -205,11 +213,23 @@ Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
## FAQ
How do I exclude mocks or tests?
- View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
How do I fix "unknown file" or compilation issues?
- Because slither requires the solc AST, it must have all dependencies available.
If a contract has dependencies, `slither contract.sol` will fail.
Instead, use `slither .` in the parent directory of `contracts/` (you should see `contracts/` when you run `ls`).
If you have a `node_modules/` folder, it must be in the same directory as `contracts/`. To verify that this issue is related to slither,
run the compilation command for the framework you are using e.g `npx hardhat compile`. That must work successfully;
otherwise, slither's compilation engine, crytic-compile, cannot generate the AST.
## License
Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms.
## Publications
### Trail of Bits publication
@ -225,5 +245,8 @@ Title | Usage | Authors | Venue
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

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

@ -7,7 +7,7 @@ setup(
author="Trail of Bits",
version="0.0",
packages=find_packages(),
python_requires=">=3.6",
python_requires=">=3.8",
install_requires=["slither-analyzer==0.1"],
entry_points={
"slither_analyzer.plugin": "slither my-plugin=slither_my_plugin:make_plugin",

@ -8,23 +8,20 @@ cd test_dapp || exit 255
git config --global user.email "ci@trailofbits.com"
git config --global user.name "CI User"
curl https://nixos.org/nix/install | sh
# shellcheck disable=SC1090,SC1091
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
nix-env -iA nixpkgs.cachix
cachix use dapp
which nix-env || exit 255
git clone --recursive https://github.com/dapphub/dapptools "$HOME/.dapp/dapptools"
nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign
dapp init
slither .
slither . --detect external-function
if [ $? -eq 21 ]
# TODO: make more elaborate test
if [ $? -eq 4 ]
then
exit 0
fi
echo "Truffle test failed"
echo "Dapp test failed"
exit 255

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

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

@ -15,7 +15,7 @@ npm install -g truffle
truffle unbox metacoin
slither .
if [ $? -eq 9 ]
if [ $? -eq 6 ]
then
exit 0
fi

@ -8,16 +8,27 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.8.2",
version="0.8.3",
packages=find_packages(),
python_requires=">=3.6",
python_requires=">=3.8",
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
"crytic-compile>=0.2.2",
# "crytic-compile",
# "crytic-compile>=0.2.3",
"crytic-compile",
],
# dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
extras_require={
"dev": [
"black==22.3.0",
"pylint==2.13.4",
"pytest",
"pytest-cov",
"deepdiff",
"numpy",
"solc-select>=v1.0.0b1",
]
},
dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0",
long_description=long_description,
entry_points={
@ -32,6 +43,7 @@ setup(
"slither-check-kspec = slither.tools.kspec_coverage.__main__:main",
"slither-prop = slither.tools.properties.__main__:main",
"slither-mutate = slither.tools.mutator.__main__:main",
"slither-read-storage = slither.tools.read_storage.__main__:main",
]
},
)

@ -299,6 +299,9 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_detector = parser.add_argument_group("Detectors")
group_printer = parser.add_argument_group("Printers")
group_checklist = parser.add_argument_group(
"Checklist (consider using https://github.com/crytic/slither-action)"
)
group_misc = parser.add_argument_group("Additional options")
group_detector.add_argument(
@ -312,7 +315,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_printer.add_argument(
"--print",
help="Comma-separated list fo contract information printers, "
help="Comma-separated list of contract information printers, "
f"available printers: {', '.join(d.ARGUMENT for d in printer_classes)}",
action="store",
dest="printers_to_run",
@ -392,6 +395,28 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["show_ignored_findings"],
)
group_checklist.add_argument(
"--checklist",
help="Generate a markdown page with the detector results",
action="store_true",
default=False,
)
group_checklist.add_argument(
"--checklist-limit",
help="Limite the number of results per detector in the markdown file",
action="store",
default="",
)
group_checklist.add_argument(
"--markdown-root",
type=check_and_sanitize_markdown_root,
help="URL for markdown generation",
action="store",
default="",
)
group_misc.add_argument(
"--json",
help='Export the results as a JSON file ("--json -" to export to stdout)',
@ -429,14 +454,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["zip_type"],
)
group_misc.add_argument(
"--markdown-root",
type=check_and_sanitize_markdown_root,
help="URL for markdown generation",
action="store",
default="",
)
group_misc.add_argument(
"--disable-color",
help="Disable output colorization",
@ -495,12 +512,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False)
group_misc.add_argument(
"--checklist", help=argparse.SUPPRESS, action="store_true", default=False
)
group_misc.add_argument("--checklist-limit", help=argparse.SUPPRESS, action="store", default="")
parser.add_argument(
"--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False
)
@ -656,7 +667,7 @@ def main_impl(all_detector_classes, all_printer_classes):
cp.enable()
# Set colorization option
set_colorization_enabled(not args.disable_color)
set_colorization_enabled(False if args.disable_color else sys.stdout.isatty())
# Define some variables for potential JSON output
json_results = {}
@ -784,7 +795,7 @@ def main_impl(all_detector_classes, all_printer_classes):
if args.checklist:
output_results_to_markdown(results_detectors, args.checklist_limit)
# Dont print the number of result for printers
# Don't print the number of result for printers
if number_contracts == 0:
logger.warning(red("No contract was analyzed"))
if printer_classes:

@ -39,12 +39,11 @@ from slither.slithir.variables import (
TupleVariable,
)
from slither.all_exceptions import SlitherException
from slither.core.declarations import Contract
from slither.core.declarations import Contract, Function
from slither.core.expressions.expression import Expression
if TYPE_CHECKING:
from slither.core.declarations import Function
from slither.slithir.variables.variable import SlithIRVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.utils.type_helpers import (
@ -917,6 +916,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
)
elif isinstance(ir, LibraryCall):
assert isinstance(ir.destination, Contract)
assert isinstance(ir.function, Function)
self._high_level_calls.append((ir.destination, ir.function))
self._library_calls.append((ir.destination, ir.function))

@ -28,4 +28,4 @@ class ChildNode:
@property
def compilation_unit(self) -> "SlitherCompilationUnit":
return self.contract.compilation_unit
return self.node.compilation_unit

@ -251,7 +251,7 @@ class SlitherCompilationUnit(Context):
slot = 0
offset = 0
for var in contract.state_variables_ordered:
if var.is_constant:
if var.is_constant or var.is_immutable:
continue
size, new_slot = var.type.storage_size

@ -21,6 +21,8 @@ from slither.utils.erc import (
ERC777_signatures,
ERC1155_signatures,
ERC2612_signatures,
ERC1363_signatures,
ERC4524_signatures,
ERC4626_signatures,
)
from slither.utils.tests_pattern import is_test_contract
@ -75,9 +77,10 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._custom_errors: Dict[str, "CustomErrorContract"] = {}
# The only str is "*"
self._using_for: Dict[Union[str, Type], List[str]] = {}
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
@ -144,6 +147,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_interface(self, is_interface: bool):
self._is_interface = is_interface
@property
def is_library(self) -> bool:
return self._is_library
@is_library.setter
def is_library(self, is_library: bool):
self._is_library = is_library
# endregion
###################################################################################
###################################################################################
@ -245,7 +256,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def using_for(self) -> Dict[Union[str, Type], List[str]]:
def using_for(self) -> Dict[Union[str, Type], List[Type]]:
return self._using_for
# endregion
@ -639,6 +650,21 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return [f for f in self.functions if f.is_writing(variable)]
def get_function_from_full_name(self, full_name: str) -> Optional["Function"]:
"""
Return a function from a full name
The full name differs from the solidity's signature are the type are conserved
For example contract type are kept, structure are not unrolled, etc
Args:
full_name (str): signature of the function (without return statement)
Returns:
Function
"""
return next(
(f for f in self.functions if f.full_name == full_name and not f.is_shadowed),
None,
)
def get_function_from_signature(self, function_signature: str) -> Optional["Function"]:
"""
Return a function from a signature
@ -648,7 +674,11 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Function
"""
return next(
(f for f in self.functions if f.full_name == function_signature and not f.is_shadowed),
(
f
for f in self.functions
if f.solidity_signature == function_signature and not f.is_shadowed
),
None,
)
@ -903,6 +933,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
("ERC721", self.is_erc721),
("ERC777", self.is_erc777),
("ERC2612", self.is_erc2612),
("ERC1363", self.is_erc1363),
("ERC4626", self.is_erc4626),
]
@ -998,6 +1029,26 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
full_names = self.functions_signatures
return all(s in full_names for s in ERC2612_signatures)
def is_erc1363(self) -> bool:
"""
Check if the contract is an erc1363
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc1363
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC1363_signatures)
def is_erc4524(self) -> bool:
"""
Check if the contract is an erc4524
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc4524
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC4524_signatures)
@property
def is_token(self) -> bool:
"""

@ -56,13 +56,24 @@ class CustomError(SourceMapping):
Contract and converted into address
:return: the solidity signature
"""
# Ideally this should be an assert
# But due to a logic limitation in the solc parsing (find_variable)
# We need to raise an error if the custom error sig was not yet built
# (set_solidity_sig was not called before find_variable)
if self._solidity_signature is None:
parameters = [
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
raise ValueError("Custom Error not yet built")
return self._solidity_signature
def set_solidity_sig(self) -> None:
"""
Function to be called once all the parameters have been set
Returns:
"""
parameters = [self._convert_type_for_solidity_signature(x.type) for x in self.parameters]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
# endregion
###################################################################################
###################################################################################

@ -9,6 +9,9 @@ class Enum(SourceMapping):
self._name = name
self._canonical_name = canonical_name
self._values = values
self._min = 0
# The max value of an Enum is the index of the last element
self._max = len(values) - 1
@property
def canonical_name(self) -> str:
@ -22,5 +25,13 @@ class Enum(SourceMapping):
def values(self) -> List[str]:
return self._values
@property
def min(self) -> int:
return self._min
@property
def max(self) -> int:
return self._max
def __str__(self):
return self.name

@ -20,11 +20,11 @@ from slither.core.expressions import (
MemberAccess,
UnaryOperation,
)
from slither.core.solidity_types import UserDefinedType
from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
from slither.utils.type import convert_type_for_solidity_signature_to_string
from slither.utils.utils import unroll
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
@ -265,6 +265,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
str: func_name(type1,type2)
Return the function signature without the return values
The difference between this function and solidity_function is that full_name does not translate the underlying
type (ex: structure, contract to address, ...)
"""
if self._full_name is None:
name, parameters, _ = self.signature
@ -538,7 +540,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._nodes = nodes
@property
def entry_point(self) -> "Node":
def entry_point(self) -> Optional["Node"]:
"""
Node: Entry point of the function
"""
@ -952,14 +954,6 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
###################################################################################
@staticmethod
def _convert_type_for_solidity_signature(t: Type):
from slither.core.declarations import Contract
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
return "address"
return str(t)
@property
def solidity_signature(self) -> str:
"""
@ -969,7 +963,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
if self._solidity_signature is None:
parameters = [
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
convert_type_for_solidity_signature_to_string(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature
@ -1724,6 +1718,6 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
def __str__(self):
return self._name
return self.name
# endregion

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

@ -104,7 +104,7 @@ class SolidityVariable(SourceMapping):
# dev function, will be removed once the code is stable
def _check_name(self, name: str): # pylint: disable=no-self-use
assert name in SOLIDITY_VARIABLES or name.endswith("_slot") or name.endswith("_offset")
assert name in SOLIDITY_VARIABLES or name.endswith(("_slot", "_offset"))
@property
def state_variable(self):

@ -1,4 +1,4 @@
from typing import List, TYPE_CHECKING, Dict
from typing import List, TYPE_CHECKING, Dict, Optional
from slither.core.source_mapping.source_mapping import SourceMapping
@ -10,7 +10,7 @@ if TYPE_CHECKING:
class Structure(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__()
self._name = None
self._name: Optional[str] = None
self._canonical_name = None
self._elems: Dict[str, "StructureVariable"] = {}
# Name of the elements in the order of declaration

@ -6,6 +6,7 @@ from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.solidity_types import TypeAlias
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.variables import Constant
@ -39,6 +40,15 @@ class FileScope:
self.structures: Dict[str, StructureTopLevel] = {}
self.variables: Dict[str, TopLevelVariable] = {}
# Renamed created by import
# import A as B
# local name -> original name (A -> B)
self.renaming: Dict[str, str] = {}
# User defined types
# Name -> type alias
self.user_defined_types: Dict[str, TypeAlias] = {}
def add_accesible_scopes(self) -> bool:
"""
Add information from accessible scopes. Return true if new information was obtained
@ -74,6 +84,12 @@ class FileScope:
if not _dict_contain(new_scope.variables, self.variables):
self.variables.update(new_scope.variables)
learn_something = True
if not _dict_contain(new_scope.renaming, self.renaming):
self.renaming.update(new_scope.renaming)
learn_something = True
if not _dict_contain(new_scope.user_defined_types, self.user_defined_types):
self.user_defined_types.update(new_scope.user_defined_types)
learn_something = True
return learn_something

@ -4,6 +4,8 @@
import json
import logging
import os
import pathlib
import posixpath
import re
from collections import defaultdict
from typing import Optional, Dict, List, Set, Union
@ -52,7 +54,7 @@ class SlitherCore(Context):
self._previous_results_ids: Set[str] = set()
# Every slither object has a list of result from detector
# Because of the multiple compilation support, we might analyze
# Multiple time the same result, so we remove dupplicate
# Multiple time the same result, so we remove duplicates
self._currently_seen_resuts: Set[str] = set()
self._paths_to_filter: Set[str] = set()
@ -290,7 +292,7 @@ class SlitherCore(Context):
return False
mapping_elements_with_lines = (
(
os.path.normpath(elem["source_mapping"]["filename_absolute"]),
posixpath.normpath(elem["source_mapping"]["filename_absolute"]),
elem["source_mapping"]["lines"],
)
for elem in r["elements"]
@ -325,7 +327,7 @@ class SlitherCore(Context):
- There is an ignore comment on the preceding line
"""
# Remove dupplicate due to the multiple compilation support
# Remove duplicate due to the multiple compilation support
if r["id"] in self._currently_seen_resuts:
return False
self._currently_seen_resuts.add(r["id"])
@ -335,8 +337,12 @@ class SlitherCore(Context):
for elem in r["elements"]
if "source_mapping" in elem
]
source_mapping_elements = map(
lambda x: os.path.normpath(x) if x else x, source_mapping_elements
# Use POSIX-style paths so that filter_paths works across different
# OSes. Convert to a list so elements don't get consumed and are lost
# while evaluating the first pattern
source_mapping_elements = list(
map(lambda x: pathlib.Path(x).resolve().as_posix() if x else x, source_mapping_elements)
)
matching = False
@ -357,16 +363,21 @@ class SlitherCore(Context):
if r["elements"] and matching:
return False
if r["elements"] and self._exclude_dependencies:
return not all(element["source_mapping"]["is_dependency"] for element in r["elements"])
if self._show_ignored_findings:
return True
if r["id"] in self._previous_results_ids:
return False
if self.has_ignore_comment(r):
return False
if r["id"] in self._previous_results_ids:
return False
if r["elements"] and self._exclude_dependencies:
if all(element["source_mapping"]["is_dependency"] for element in r["elements"]):
return False
# Conserve previous result filtering. This is conserved for compatibility, but is meant to be removed
return not r["description"] in [pr["description"] for pr in self._previous_results]
if r["description"] in [pr["description"] for pr in self._previous_results]:
return False
return True
def load_previous_results(self):
filename = self._previous_results_filename

@ -3,4 +3,6 @@ from .elementary_type import ElementaryType
from .function_type import FunctionType
from .mapping_type import MappingType
from .user_defined_type import UserDefinedType
from .type import Type
from .type_information import TypeInformation
from .type_alias import TypeAlias, TypeAliasTopLevel, TypeAliasContract

@ -29,14 +29,26 @@ class ArrayType(Type):
def type(self) -> Type:
return self._type
@property
def is_dynamic(self) -> bool:
return self.length is None
@property
def length(self) -> Optional[Expression]:
return self._length
@property
def lenght_value(self) -> Optional[Literal]:
def length_value(self) -> Optional[Literal]:
return self._length_value
@property
def is_fixed_array(self) -> bool:
return bool(self.length)
@property
def is_dynamic_array(self) -> bool:
return not self.is_fixed_array
@property
def storage_size(self) -> Tuple[int, bool]:
if self._length_value:

@ -43,8 +43,8 @@ Int = [
"int256",
]
Max_Int = {k: 2 ** (8 * i - 1) - 1 if i > 0 else 2 ** 255 - 1 for i, k in enumerate(Int)}
Min_Int = {k: -(2 ** (8 * i - 1)) if i > 0 else -(2 ** 255) for i, k in enumerate(Int)}
Max_Int = {k: 2 ** (8 * i - 1) - 1 if i > 0 else 2**255 - 1 for i, k in enumerate(Int)}
Min_Int = {k: -(2 ** (8 * i - 1)) if i > 0 else -(2**255) for i, k in enumerate(Int)}
Uint = [
"uint",
@ -82,7 +82,7 @@ Uint = [
"uint256",
]
Max_Uint = {k: 2 ** (8 * i) - 1 if i > 0 else 2 ** 256 - 1 for i, k in enumerate(Uint)}
Max_Uint = {k: 2 ** (8 * i) - 1 if i > 0 else 2**256 - 1 for i, k in enumerate(Uint)}
Min_Uint = {k: 0 for k in Uint}
@ -127,10 +127,10 @@ Max_Byte = {k: 2 ** (8 * (i + 1)) - 1 for i, k in enumerate(Byte[2:])}
Max_Byte["bytes"] = None
Max_Byte["string"] = None
Max_Byte["byte"] = 255
Min_Byte = {k: 1 << (4 + 8 * i) for i, k in enumerate(Byte[2:])}
Min_Byte = {k: 0 for k in Byte}
Min_Byte["bytes"] = 0x0
Min_Byte["string"] = None
Min_Byte["byte"] = 0x10
Min_Byte["string"] = 0x0
Min_Byte["byte"] = 0x0
MaxValues = dict(dict(Max_Int, **Max_Uint), **Max_Byte)
MinValues = dict(dict(Min_Int, **Min_Uint), **Min_Byte)
@ -151,7 +151,7 @@ class NonElementaryType(Exception):
class ElementaryType(Type):
def __init__(self, t):
def __init__(self, t: str) -> None:
if t not in ElementaryTypeName:
raise NonElementaryType
super().__init__()
@ -163,6 +163,10 @@ class ElementaryType(Type):
t = "bytes1"
self._type = t
@property
def is_dynamic(self) -> bool:
return self._type in ("bytes", "string")
@property
def type(self) -> str:
return self._type
@ -188,8 +192,8 @@ class ElementaryType(Type):
return int(8)
if t == "address":
return int(160)
if t.startswith("bytes"):
return int(t[len("bytes") :])
if t.startswith("bytes") and t != "bytes":
return int(t[len("bytes") :]) * 8
return None
@property

@ -32,6 +32,10 @@ class FunctionType(Type):
def storage_size(self) -> Tuple[int, bool]:
return 24, False
@property
def is_dynamic(self) -> bool:
return False
def __str__(self):
# Use x.type
# x.name may be empty

@ -23,6 +23,10 @@ class MappingType(Type):
def storage_size(self) -> Tuple[int, bool]:
return 32, True
@property
def is_dynamic(self) -> bool:
return True
def __str__(self):
return f"mapping({str(self._from)} => {str(self._to)})"

@ -14,3 +14,8 @@ class Type(SourceMapping, metaclass=abc.ABCMeta):
:return: (int, bool) - the number of bytes this type will require, and whether it must start in
a new slot regardless of whether the current slot can still fit it
"""
@property
@abc.abstractmethod
def is_dynamic(self) -> bool:
"""True if the size of the type is dynamic"""

@ -0,0 +1,45 @@
from typing import TYPE_CHECKING, Tuple
from slither.core.children.child_contract import ChildContract
from slither.core.declarations.top_level import TopLevel
from slither.core.solidity_types import Type
if TYPE_CHECKING:
from slither.core.declarations import Contract
from slither.core.scope.scope import FileScope
class TypeAlias(Type):
def __init__(self, underlying_type: Type, name: str):
super().__init__()
self.name = name
self.underlying_type = underlying_type
@property
def storage_size(self) -> Tuple[int, bool]:
return self.underlying_type.storage_size
def __hash__(self):
return hash(str(self))
@property
def is_dynamic(self) -> bool:
return self.underlying_type.is_dynamic
class TypeAliasTopLevel(TypeAlias, TopLevel):
def __init__(self, underlying_type: Type, name: str, scope: "FileScope"):
super().__init__(underlying_type, name)
self.file_scope: "FileScope" = scope
def __str__(self):
return self.name
class TypeAliasContract(TypeAlias, ChildContract):
def __init__(self, underlying_type: Type, name: str, contract: "Contract"):
super().__init__(underlying_type, name)
self._contract: "Contract" = contract
def __str__(self):
return self.contract.name + "." + self.name

@ -13,8 +13,9 @@ class TypeInformation(Type):
def __init__(self, c):
# pylint: disable=import-outside-toplevel
from slither.core.declarations.contract import Contract
from slither.core.declarations.enum import Enum
assert isinstance(c, (Contract, ElementaryType))
assert isinstance(c, (Contract, ElementaryType, Enum))
super().__init__()
self._type = c
@ -34,6 +35,10 @@ class TypeInformation(Type):
"""
return 32, True
@property
def is_dynamic(self) -> bool:
raise NotImplementedError
def __str__(self):
return f"type({self.type.name})"

@ -20,6 +20,10 @@ class UserDefinedType(Type):
super().__init__()
self._type = t
@property
def is_dynamic(self) -> bool:
return False
@property
def type(self) -> Union["Contract", "Enum", "Structure"]:
return self._type

@ -1,8 +1,7 @@
from typing import Optional, TYPE_CHECKING, Tuple, List
from typing import Optional, TYPE_CHECKING
from slither.core.variables.variable import Variable
from slither.core.children.child_contract import ChildContract
from slither.utils.type import export_nested_types_from_variable
from slither.core.variables.variable import Variable
if TYPE_CHECKING:
from slither.core.cfg.node import Node
@ -22,33 +21,6 @@ class StateVariable(ChildContract, Variable):
"""
return self.contract == contract
###################################################################################
###################################################################################
# region Signature
###################################################################################
###################################################################################
@property
def signature(self) -> Tuple[str, List[str], str]:
"""
Return the signature of the state variable as a function signature
:return: (str, list(str), list(str)), as (name, list parameters type, list return values type)
"""
return (
self.name,
[str(x) for x in export_nested_types_from_variable(self)],
str(self.type),
)
@property
def signature_str(self) -> str:
"""
Return the signature of the state variable as a function signature
:return: str: func_name(type1,type2) returns(type3)
"""
name, parameters, returnVars = self.signature
return name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")"
# endregion
###################################################################################
###################################################################################

@ -1,7 +1,7 @@
"""
Variable module
"""
from typing import Optional, TYPE_CHECKING, List, Union
from typing import Optional, TYPE_CHECKING, List, Union, Tuple
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.solidity_types.type import Type
@ -10,7 +10,7 @@ from slither.core.solidity_types.elementary_type import ElementaryType
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
# pylint: disable=too-many-instance-attributes
class Variable(SourceMapping):
def __init__(self):
super().__init__()
@ -21,6 +21,8 @@ class Variable(SourceMapping):
self._visibility: Optional[str] = None
self._is_constant = False
self._is_immutable: bool = False
self._is_reentrant: bool = True
self._write_protection: Optional[List[str]] = None
@property
def is_scalar(self) -> bool:
@ -42,7 +44,7 @@ class Variable(SourceMapping):
return self._initial_expression
@expression.setter
def expression(self, expr: "Expression"):
def expression(self, expr: "Expression") -> None:
self._initial_expression = expr
@property
@ -64,7 +66,7 @@ class Variable(SourceMapping):
return not self._initialized
@property
def name(self) -> str:
def name(self) -> Optional[str]:
"""
str: variable name
"""
@ -90,6 +92,22 @@ class Variable(SourceMapping):
def is_constant(self, is_cst: bool):
self._is_constant = is_cst
@property
def is_reentrant(self) -> bool:
return self._is_reentrant
@is_reentrant.setter
def is_reentrant(self, is_reentrant: bool) -> None:
self._is_reentrant = is_reentrant
@property
def write_protection(self) -> Optional[List[str]]:
return self._write_protection
@write_protection.setter
def write_protection(self, write_protection: List[str]) -> None:
self._write_protection = write_protection
@property
def visibility(self) -> Optional[str]:
"""
@ -98,12 +116,13 @@ class Variable(SourceMapping):
return self._visibility
@visibility.setter
def visibility(self, v: str):
def visibility(self, v: str) -> None:
self._visibility = v
def set_type(self, t):
def set_type(self, t: Optional[Union[List, Type, str]]) -> None:
if isinstance(t, str):
t = ElementaryType(t)
self._type = ElementaryType(t)
return
assert isinstance(t, (Type, list)) or t is None
self._type = t
@ -117,27 +136,46 @@ class Variable(SourceMapping):
return self._is_immutable
@is_immutable.setter
def is_immutable(self, immutablility: bool):
def is_immutable(self, immutablility: bool) -> None:
self._is_immutable = immutablility
###################################################################################
###################################################################################
# region Signature
###################################################################################
###################################################################################
@property
def function_name(self):
def signature(self) -> Tuple[str, List[str], List[str]]:
"""
Return the name of the variable as a function signature
:return:
Return the signature of the state variable as a function signature
:return: (str, list(str), list(str)), as (name, list parameters type, list return values type)
"""
# pylint: disable=import-outside-toplevel
from slither.core.solidity_types import ArrayType, MappingType
from slither.utils.type import export_nested_types_from_variable
from slither.utils.type import (
export_nested_types_from_variable,
export_return_type_from_variable,
)
variable_getter_args = ""
return_type = self.type
assert return_type
return (
self.name,
[str(x) for x in export_nested_types_from_variable(self)],
[str(x) for x in export_return_type_from_variable(self)],
)
if isinstance(return_type, (ArrayType, MappingType)):
variable_getter_args = ",".join(map(str, export_nested_types_from_variable(self)))
@property
def signature_str(self) -> str:
"""
Return the signature of the state variable as a function signature
:return: str: func_name(type1,type2) returns(type3)
"""
name, parameters, returnVars = self.signature
return name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")"
return f"{self.name}({variable_getter_args})"
@property
def solidity_signature(self) -> str:
name, parameters, _ = self.signature
return f'{name}({",".join(parameters)})'
def __str__(self):
def __str__(self) -> str:
return self._name

@ -6,7 +6,9 @@ from .variables.uninitialized_local_variables import UninitializedLocalVars
from .attributes.constant_pragma import ConstantPragma
from .attributes.incorrect_solc import IncorrectSolc
from .attributes.locked_ether import LockedEther
from .functions.arbitrary_send import ArbitrarySend
from .functions.arbitrary_send_eth import ArbitrarySendEth
from .erc.erc20.arbitrary_send_erc20_no_permit import ArbitrarySendErc20NoPermit
from .erc.erc20.arbitrary_send_erc20_permit import ArbitrarySendErc20Permit
from .functions.suicidal import Suicidal
# from .functions.complex_function import ComplexFunction
@ -34,7 +36,7 @@ from .shadowing.builtin_symbols import BuiltinSymbolShadowing
from .operations.block_timestamp import Timestamp
from .statements.calls_in_loop import MultipleCallsInLoop
from .statements.incorrect_strict_equality import IncorrectStrictEquality
from .erc.incorrect_erc20_interface import IncorrectERC20InterfaceDetection
from .erc.erc20.incorrect_erc20_interface import IncorrectERC20InterfaceDetection
from .erc.incorrect_erc721_interface import IncorrectERC721InterfaceDetection
from .erc.unindexed_event_parameters import UnindexedERC20EventParameters
from .statements.deprecated_calls import DeprecatedStandards
@ -81,3 +83,4 @@ from .functions.dead_code import DeadCode
from .statements.write_after_write import WriteAfterWrite
from .statements.msg_value_in_loop import MsgValueInLoop
from .statements.delegatecall_in_loop import DelegatecallInLoop
from .functions.protected_variable import ProtectedVariables

@ -13,7 +13,9 @@ class ShiftParameterMixup(AbstractDetector):
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#shift-parameter-mixup"
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly"
)
WIKI_TITLE = "Incorrect shift in assembly."
WIKI_DESCRIPTION = "Detect if the values in a shift operation are reversed"

@ -29,7 +29,7 @@ class ConstantPragma(AbstractDetector):
versions = sorted(list(set(versions)))
if len(versions) > 1:
info = ["Different versions of Solidity is used:\n"]
info = ["Different versions of Solidity are used:\n"]
info += [f"\t- Version used: {[str(v) for v in versions]}\n"]
for p in pragma:

@ -14,7 +14,7 @@ from slither.formatters.attributes.incorrect_solc import custom_format
# 4: version number
# pylint: disable=anomalous-backslash-in-string
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
PATTERN = re.compile(r"(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
class IncorrectSolc(AbstractDetector):

@ -87,7 +87,7 @@ contract Something {
if not intended_interface_is_subset_parent:
# Should not be a subset of an earlier determined intended_interface or derive from it
intended_interface_is_subset_intended = False
for intended_interface in intended_interfaces:
for intended_interface in list(intended_interfaces):
sigs_intended_interface = {
f.full_name for f in intended_interface.functions_entry_points
}

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

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

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

@ -90,8 +90,8 @@ def detect_arbitrary_send(contract: Contract):
return ret
class ArbitrarySend(AbstractDetector):
ARGUMENT = "arbitrary-send"
class ArbitrarySendEth(AbstractDetector):
ARGUMENT = "arbitrary-send-eth"
HELP = "Functions that send Ether to arbitrary destinations"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
@ -104,7 +104,7 @@ class ArbitrarySend(AbstractDetector):
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract ArbitrarySend{
contract ArbitrarySendEth{
address destination;
function setDestination(){
destination = msg.sender;

@ -0,0 +1,81 @@
"""
Module detecting suicidal contract
A suicidal contract is an unprotected function that calls selfdestruct
"""
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Function, Contract
from slither.utils.output import Output
class ProtectedVariables(AbstractDetector):
ARGUMENT = "protected-vars"
HELP = "Detected unprotected variables"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#protected-variables"
WIKI_TITLE = "Protected Variables"
WIKI_DESCRIPTION = "Detect unprotected variable that are marked protected"
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Buggy{
/// @custom:security write-protection="onlyOwner()"
address owner;
function set_protected() public onlyOwner(){
owner = msg.sender;
}
function set_not_protected() public{
owner = msg.sender;
}
}
```
`owner` must be always written by function using `onlyOwner` (`write-protection="onlyOwner()"`), however anyone can call `set_not_protected`.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Add access controls to the vulnerable function"
def _analyze_function(self, function: Function, contract: Contract) -> List[Output]:
results = []
for state_variable_written in function.state_variables_written:
if state_variable_written.write_protection:
for function_sig in state_variable_written.write_protection:
function_protection = contract.get_function_from_signature(function_sig)
if not function_protection:
function_protection = contract.get_modifier_from_signature(function_sig)
if not function_protection:
self.logger.error(f"{function_sig} not found")
continue
if function_protection not in function.all_internal_calls():
info = [
function,
" should have ",
function_protection,
" to protect ",
state_variable_written,
"\n",
]
res = self.generate_result(info)
results.append(res)
return results
def _detect(self):
"""Detect the suicidal functions"""
results = []
for contract in self.compilation_unit.contracts_derived:
for function in contract.functions_entry_points:
results += self._analyze_function(function, contract)
return results

@ -89,16 +89,12 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
if func.is_constructor:
continue
if not self.is_mixed_case(func.name):
if (
func.visibility
in [
"internal",
"private",
]
and self.is_mixed_case_with_underscore(func.name)
):
if func.visibility in [
"internal",
"private",
] and self.is_mixed_case_with_underscore(func.name):
continue
if func.name.startswith("echidna_") or func.name.startswith("crytic_"):
if func.name.startswith(("echidna_", "crytic_")):
continue
info = ["Function ", func, " is not in mixedCase\n"]

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

@ -0,0 +1,30 @@
from slither.core.declarations import Contract
from slither.core.variables.state_variable import StateVariable
from slither.core.solidity_types import ArrayType, ElementaryType
def is_upgradable_gap_variable(contract: Contract, variable: StateVariable) -> bool:
"""Helper function that returns true if 'variable' is a gap variable used
for upgradable contracts. More specifically, the function returns true if:
- variable is named "__gap"
- it is a uint256 array declared at the end of the contract
- it has private visibility"""
# Return early on if the variable name is != gap to avoid iterating over all the state variables
if variable.name != "__gap":
return False
declared_variable_ordered = [
v for v in contract.state_variables_ordered if v in contract.state_variables_declared
]
if not declared_variable_ordered:
return False
variable_type = variable.type
return (
declared_variable_ordered[-1] is variable
and isinstance(variable_type, ArrayType)
and variable_type.type == ElementaryType("uint256")
and variable.visibility == "private"
)

@ -3,9 +3,11 @@ Module detecting shadowing of state variables
"""
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
from .common import is_upgradable_gap_variable
def detect_shadowing(contract):
def detect_shadowing(contract: Contract):
ret = []
variables_fathers = []
for father in contract.inheritance:
@ -13,6 +15,10 @@ def detect_shadowing(contract):
variables_fathers += father.state_variables_declared
for var in contract.state_variables_declared:
# Ignore __gap variables for updatable contracts
if is_upgradable_gap_variable(contract, var):
continue
shadow = [v for v in variables_fathers if v.name == var.name]
if shadow:
ret.append([var] + shadow)

@ -1,7 +1,7 @@
import re
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
# pylint: disable=bidirectional-unicode
class RightToLeftOverride(AbstractDetector):
"""
Detect the usage of a Right-To-Left-Override (U+202E) character

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

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

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

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

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

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

@ -1,11 +1,12 @@
from typing import List
from slither.core.declarations import SolidityFunction, Function
from slither.core.declarations.contract import Contract
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import LowLevelCall, SolidityCall
def _can_be_destroyed(contract) -> List[Function]:
def _can_be_destroyed(contract: Contract) -> List[Function]:
targets = []
for f in contract.functions_entry_points:
for ir in f.all_slithir_operations():
@ -29,6 +30,18 @@ def _has_initializer_modifier(functions: List[Function]) -> bool:
return False
def _whitelisted_modifiers(f: Function) -> bool:
# The onlyProxy modifier prevents calling the implementation contract (must be delegatecall)
# https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/3dec82093ea4a490d63aab3e925fed4f692909e8/contracts/proxy/utils/UUPSUpgradeable.sol#L38-L42
return "onlyProxy" not in [modifier.name for modifier in f.modifiers]
def _initialize_functions(contract: Contract) -> List[Function]:
return list(
filter(_whitelisted_modifiers, [f for f in contract.functions if f.name == "initialize"])
)
class UnprotectedUpgradeable(AbstractDetector):
ARGUMENT = "unprotected-upgrade"
@ -72,12 +85,10 @@ class UnprotectedUpgradeable(AbstractDetector):
if not _has_initializer_modifier(contract.constructors):
functions_that_can_destroy = _can_be_destroyed(contract)
if functions_that_can_destroy:
initiliaze_functions = [
f for f in contract.functions if f.name == "initialize"
]
initialize_functions = _initialize_functions(contract)
vars_init_ = [
init.all_state_variables_written() for init in initiliaze_functions
init.all_state_variables_written() for init in initialize_functions
]
vars_init = [item for sublist in vars_init_ for item in sublist]
@ -91,9 +102,9 @@ class UnprotectedUpgradeable(AbstractDetector):
info = (
[
contract,
" is an upgradeable contract that does not protect its initiliaze functions: ",
" is an upgradeable contract that does not protect its initialize functions: ",
]
+ initiliaze_functions
+ initialize_functions
+ [
". Anyone can delete the contract with: ",
]

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

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

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

@ -16,7 +16,7 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result):
element["type_specific_fields"]["parent"]["name"]
)
if target_contract:
function = target_contract.get_function_from_signature(
function = target_contract.get_function_from_full_name(
element["type_specific_fields"]["signature"]
)
if function:

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

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

@ -12,7 +12,7 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result):
element["type_specific_fields"]["parent"]["name"]
)
if target_contract:
function = target_contract.get_function_from_signature(
function = target_contract.get_function_from_full_name(
element["type_specific_fields"]["signature"]
)
if function:

@ -257,7 +257,7 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
]
param_name = element["name"]
contract = scope.get_contract_from_name(contract_name)
function = contract.get_function_from_signature(function_sig)
function = contract.get_function_from_full_name(function_sig)
target = function.get_local_variable_from_name(param_name)
elif _target in ["variable", "variable_constant"]:
@ -271,7 +271,7 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
]
var_name = element["name"]
contract = scope.get_contract_from_name(contract_name)
function = contract.get_function_from_signature(function_sig)
function = contract.get_function_from_full_name(function_sig)
target = function.get_local_variable_from_name(var_name)
# State variable
else:
@ -303,10 +303,10 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
# group 2: beginning of the to type
# nested mapping are within the group 1
# RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)'
RE_MAPPING_FROM = b"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = b"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING_FROM = rb"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = rb"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING = (
b"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + b"\)"
rb"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + rb"\)"
)

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

@ -37,8 +37,7 @@ def _get_name(f: Union[Function, Variable]) -> str:
if isinstance(f, Function):
if f.is_fallback or f.is_receive:
return "()"
return f.solidity_signature
return f.function_name
return f.solidity_signature
def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]:
@ -117,7 +116,7 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
for contract in slither.contracts:
cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)]
cst_functions += [
v.function_name for v in contract.state_variables if v.visibility in ["public"]
v.solidity_signature for v in contract.state_variables if v.visibility in ["public"]
]
if cst_functions:
ret[contract.name] = cst_functions
@ -296,6 +295,24 @@ def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _with_fallback(slither: SlitherCore) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for function in contract.functions_entry_points:
if function.is_fallback:
ret.add(contract.name)
return ret
def _with_receive(slither: SlitherCore) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for function in contract.functions_entry_points:
if function.is_receive:
ret.add(contract.name)
return ret
def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]:
"""
Detect the functions with external calls
@ -376,6 +393,10 @@ class Echidna(AbstractPrinter):
use_balance = _use_balance(self.slither)
with_fallback = list(_with_fallback(self.slither))
with_receive = list(_with_receive(self.slither))
d = {
"payable": payable,
"timestamp": timestamp,
@ -392,6 +413,8 @@ class Echidna(AbstractPrinter):
"call_a_parameter": call_parameters,
"use_balance": use_balance,
"solc_versions": [unit.solc_version for unit in self.slither.compilation_units],
"with_fallback": with_fallback,
"with_receive": with_receive,
}
self.info(json.dumps(d, indent=4))

@ -26,12 +26,14 @@ class FunctionIds(AbstractPrinter):
txt += f"\n{contract.name}:\n"
table = MyPrettyTable(["Name", "ID"])
for function in contract.functions:
if function.is_shadowed or function.is_constructor_variables:
continue
if function.visibility in ["public", "external"]:
function_id = get_function_id(function.solidity_signature)
table.add_row([function.solidity_signature, f"{function_id:#0{10}x}"])
for variable in contract.state_variables:
if variable.visibility in ["public"]:
sig = variable.function_name
sig = variable.solidity_signature
function_id = get_function_id(sig)
table.add_row([sig, f"{function_id:#0{10}x}"])
txt += str(table) + "\n"

@ -28,7 +28,7 @@ class VariableOrder(AbstractPrinter):
txt += f"\n{contract.name}:\n"
table = MyPrettyTable(["Name", "Type", "Slot", "Offset"])
for variable in contract.state_variables_ordered:
if not variable.is_constant:
if not variable.is_constant and not variable.is_immutable:
slot, offset = contract.compilation_unit.storage_layout_of(contract, variable)
table.add_row([variable.canonical_name, str(variable.type), slot, offset])

@ -0,0 +1,61 @@
"""
Module printing summary of the contract
"""
from slither.core.declarations import Function
from slither.core.declarations.function import SolidityFunction
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils import output
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):
continue
if any(modifier.name == modifier_name for modifier in function.modifiers):
return True
return False
class PrinterWhenNotPaused(AbstractPrinter):
ARGUMENT = "pausable"
HELP = "Print functions that do not use whenNotPaused"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused"
def output(self, _filename: str) -> output.Output:
"""
_filename is not used
Args:
_filename(string)
"""
modifier_name: str = "whenNotPaused"
txt = ""
txt += "Constructor and pure/view functions are not displayed\n"
all_tables = []
for contract in self.slither.contracts:
txt += f"\n{contract.name}:\n"
table = MyPrettyTable(["Name", "Use whenNotPaused"])
for function in contract.functions_entry_points:
status = "X" if _use_modifier(function, modifier_name) else ""
table.add_row([function.solidity_signature, status])
txt += str(table) + "\n"
all_tables.append((contract.name, table))
self.info(txt)
res = self.generate_output(txt)
for name, table in all_tables:
res.add_pretty_table(table, name)
return res

@ -53,7 +53,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
Keyword Args:
solc (str): solc binary location (default 'solc')
disable_solc_warnings (bool): True to disable solc warnings (default false)
solc_arguments (str): solc arguments (default '')
solc_args (str): solc arguments (default '')
ast_format (str): ast format (default '--ast-compact-json')
filter_paths (list(str)): list of path to filter (default [])
triage_mode (bool): if true, switch to triage mode (default false)

@ -33,6 +33,7 @@ from slither.core.solidity_types.elementary_type import (
MaxValues,
)
from slither.core.solidity_types.type import Type
from slither.core.solidity_types.type_alias import TypeAlias
from slither.core.variables.function_type_variable import FunctionTypeVariable
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.variable import Variable
@ -170,10 +171,10 @@ def _fits_under_integer(val: int, can_be_int: bool, can_be_uint) -> List[str]:
assert can_be_int | can_be_uint
while n <= 256:
if can_be_uint:
if val <= 2 ** n - 1:
if val <= 2**n - 1:
ret.append(f"uint{n}")
if can_be_int:
if val <= (2 ** n) / 2 - 1:
if val <= (2**n) / 2 - 1:
ret.append(f"int{n}")
n = n + 8
return ret
@ -196,7 +197,7 @@ def _fits_under_byte(val: Union[int, str]) -> List[str]:
return [f"bytes{size}"]
# val is a str
length = len(val.encode("utf-8"))
return [f"bytes{f}" for f in range(length, 33)]
return [f"bytes{f}" for f in range(length, 33)] + ["bytes"]
def _find_function_from_parameter(ir: Call, candidates: List[Function]) -> Optional[Function]:
@ -399,6 +400,13 @@ def propagate_type_and_convert_call(result, node):
ins = new_ins
result[idx] = ins
# If there's two consecutive type conversions, keep only the last
# ARG_CALL x call_data = [x]
# TMP_4 = CONVERT x to Fix call_data = []
# TMP_5 = CONVERT TMP_4 to uint192 call_data = [TMP_5]
if isinstance(ins, TypeConversion) and ins.variable in call_data:
call_data.remove(ins.variable)
if isinstance(ins, Argument):
# In case of dupplicate arguments we overwrite the value
# This can happen because of addr.call.value(1).value(2)
@ -531,7 +539,9 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
return convert_type_of_high_and_internal_level_call(ir, contract)
# Convert HighLevelCall to LowLevelCall
if isinstance(t, ElementaryType) and t.name == "address":
if (isinstance(t, ElementaryType) and t.name == "address") or (
isinstance(t, TypeAlias) and t.underlying_type.name == "address"
):
# Cannot be a top level function with this.
assert isinstance(node_function, FunctionContract)
if ir.destination.name == "this":
@ -817,12 +827,28 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
# lib L { event E()}
# ...
# emit L.E();
if str(ins.ori.variable_right) in [f.name for f in ins.ori.variable_left.events]:
if str(ins.ori.variable_right) in ins.ori.variable_left.events_as_dict:
eventcall = EventCall(ins.ori.variable_right)
eventcall.set_expression(ins.expression)
eventcall.call_id = ins.call_id
return eventcall
# lib Lib { error Error()} ... revert Lib.Error()
if str(ins.ori.variable_right) in ins.ori.variable_left.custom_errors_as_dict:
custom_error = ins.ori.variable_left.custom_errors_as_dict[
str(ins.ori.variable_right)
]
assert isinstance(
custom_error,
CustomError,
)
sol_function = SolidityCustomRevert(custom_error)
solidity_call = SolidityCall(
sol_function, ins.nbr_arguments, ins.lvalue, ins.type_call
)
solidity_call.set_expression(ins.expression)
return solidity_call
libcall = LibraryCall(
ins.ori.variable_left,
ins.ori.variable_right,
@ -1369,7 +1395,11 @@ def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract):
if len(candidates) == 1:
func = candidates[0]
if func is None:
# We can discard if there are arguments here because libraries only support constant variables
# And constant variables cannot have non-value type
# i.e. "uint[2] constant arr = [1,2];" is not possible in Solidity
# If this were to change, the following condition might be broken
if func is None and not ir.arguments:
# TODO: handle collision with multiple state variables/functions
func = lib_contract.get_state_variable_from_name(ir.function_name)
if func is None and candidates:
@ -1463,7 +1493,11 @@ def convert_type_of_high_and_internal_level_call(ir: Operation, contract: Option
for import_statement in contract.file_scope.imports:
if import_statement.alias and import_statement.alias == ir.contract_name:
imported_scope = contract.compilation_unit.get_scope(import_statement.filename)
candidates += list(imported_scope.functions)
candidates += [
f
for f in list(imported_scope.functions)
if f.name == ir.function_name and len(f.parameters) == len(ir.arguments)
]
func = _find_function_from_parameter(ir, candidates)
@ -1653,7 +1687,10 @@ def convert_constant_types(irs):
if isinstance(ir.rvalue, TupleVariable):
# TODO: fix missing Unpack conversion
continue
if ir.rvalue.type.type not in ElementaryTypeInt:
if isinstance(ir.rvalue.type, TypeAlias):
ir.rvalue.set_type(ElementaryType(ir.lvalue.type.name))
was_changed = True
elif ir.rvalue.type.type not in ElementaryTypeInt:
ir.rvalue.set_type(ElementaryType(ir.lvalue.type.type))
was_changed = True
if isinstance(ir, Binary):
@ -1672,6 +1709,7 @@ def convert_constant_types(irs):
# TODO: add POP instruction
break
types = [p.type for p in func.parameters]
assert len(types) == len(ir.arguments)
for idx, arg in enumerate(ir.arguments):
t = types[idx]
if isinstance(t, ElementaryType):

@ -96,6 +96,14 @@ class HighLevelCall(Call, OperationWithLValue):
# region Analyses
###################################################################################
###################################################################################
def is_static_call(self):
# If solidity >0.5, STATICCALL is used
if self.compilation_unit.solc_version and self.compilation_unit.solc_version >= "0.5.0":
if isinstance(self.function, Function) and (self.function.view or self.function.pure):
return True
if isinstance(self.function, Variable):
return True
return False
def can_reenter(self, callstack=None):
"""
@ -105,12 +113,8 @@ class HighLevelCall(Call, OperationWithLValue):
:param callstack: check for recursion
:return: bool
"""
# If solidity >0.5, STATICCALL is used
if self.compilation_unit.solc_version and self.compilation_unit.solc_version >= "0.5.0":
if isinstance(self.function, Function) and (self.function.view or self.function.pure):
return False
if isinstance(self.function, Variable):
return False
if self.is_static_call():
return False
# If there is a call to itself
# We can check that the function called is
# reentrancy-safe
@ -124,6 +128,10 @@ class HighLevelCall(Call, OperationWithLValue):
callstack = callstack + [self.function]
if self.function.can_reenter(callstack):
return True
if isinstance(self.destination, Variable):
if not self.destination.is_reentrant:
return False
return True
def can_send_eth(self):

@ -17,6 +17,8 @@ class LibraryCall(HighLevelCall):
Must be called after slithIR analysis pass
:return: bool
"""
if self.is_static_call():
return False
# In case of recursion, return False
callstack = [] if callstack is None else callstack
if self.function in callstack:

@ -61,6 +61,8 @@ class LowLevelCall(Call, OperationWithLValue): # pylint: disable=too-many-insta
Must be called after slithIR analysis pass
:return: bool
"""
if self.function_name == "staticcall":
return False
return True
def can_send_eth(self):

@ -5,6 +5,7 @@ from slither.core.declarations import Modifier, Event, EnumContract, StructureCo
from slither.core.declarations.contract import Contract
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.declarations.function_contract import FunctionContract
from slither.core.solidity_types import ElementaryType, TypeAliasContract
from slither.core.variables.state_variable import StateVariable
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
@ -156,7 +157,10 @@ class ContractSolc(CallerContextExpression):
if "contractKind" in attributes:
if attributes["contractKind"] == "interface":
self._contract.is_interface = True
self._contract.kind = attributes["contractKind"]
elif attributes["contractKind"] == "library":
self._contract.is_library = True
self._contract.contract_kind = attributes["contractKind"]
self._linearized_base_contracts = attributes["linearizedBaseContracts"]
# self._contract.fullyImplemented = attributes["fullyImplemented"]
@ -230,6 +234,7 @@ class ContractSolc(CallerContextExpression):
self.baseConstructorContractsCalled.append(referencedDeclaration)
def _parse_contract_items(self):
# pylint: disable=too-many-branches
if not self.get_children() in self._data: # empty contract
return
for item in self._data[self.get_children()]:
@ -253,10 +258,34 @@ class ContractSolc(CallerContextExpression):
self._usingForNotParsed.append(item)
elif item[self.get_key()] == "ErrorDefinition":
self._customErrorParsed.append(item)
elif item[self.get_key()] == "UserDefinedValueTypeDefinition":
self._parse_type_alias(item)
else:
raise ParsingError("Unknown contract item: " + item[self.get_key()])
return
def _parse_type_alias(self, item: Dict) -> None:
assert "name" in item
assert "underlyingType" in item
underlying_type = item["underlyingType"]
assert "nodeType" in underlying_type and underlying_type["nodeType"] == "ElementaryTypeName"
assert "name" in underlying_type
original_type = ElementaryType(underlying_type["name"])
# For user defined types defined at the contract level the lookup can be done
# Using the name or the canonical name
# For example during the type parsing the canonical name
# Note that Solidity allows shadowing of user defined types
# Between top level and contract definitions
alias = item["name"]
alias_canonical = self._contract.name + "." + item["name"]
user_defined_type = TypeAliasContract(original_type, alias, self.underlying_contract)
user_defined_type.set_offset(item["src"], self.compilation_unit)
self._contract.file_scope.user_defined_types[alias] = user_defined_type
self._contract.file_scope.user_defined_types[alias_canonical] = user_defined_type
def _parse_struct(self, struct: Dict):
st = StructureContract(self._contract.compilation_unit)

@ -80,6 +80,7 @@ class CustomErrorSolc(CallerContextExpression):
assert param[self.get_key()] == "VariableDeclaration"
local_var = self._add_param(param)
self._custom_error.add_parameters(local_var.underlying_variable)
self._custom_error.set_solidity_sig()
def _add_param(self, param: Dict) -> LocalVariableSolc:

@ -448,7 +448,7 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
t = expression["attributes"]["type"]
if t:
found = re.findall("[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t)
found = re.findall(r"[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t)
assert len(found) <= 1
if found:
value = value + "(" + found[0] + ")"
@ -458,7 +458,6 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
referenced_declaration = expression["referencedDeclaration"]
else:
referenced_declaration = None
var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created:
var.set_offset(src, caller_context.compilation_unit)

@ -18,6 +18,7 @@ from slither.core.solidity_types import (
ArrayType,
FunctionType,
MappingType,
TypeAlias,
)
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.core.variables.variable import Variable
@ -125,13 +126,27 @@ def _find_top_level(
new_val = SolidityImportPlaceHolder(import_directive)
return new_val, True
for custom_error in scope.custom_errors:
if custom_error.solidity_signature == var_name:
return custom_error, False
if var_name in scope.variables:
return scope.variables[var_name], False
# This path should be reached only after the top level custom error have been parsed
# If not, slither will crash
# It does not seem to be reacheable, but if so, we will have to adapt the order of logic
# This must be at the end, because other top level objects might require to go over "_find_top_level"
# Before the parsing of the top level custom error
# For example, a top variable that use another top level variable
# IF more top level objects are added to Solidity, we have to be careful with the order of the lookup
# in this function
try:
for custom_error in scope.custom_errors:
if custom_error.solidity_signature == var_name:
return custom_error, False
except ValueError:
# This can happen as custom error sol signature might not have been built
# when find_variable was called
# TODO refactor find_variable to prevent this from happening
pass
return None, False
@ -199,9 +214,15 @@ def _find_in_contract(
# This is because when the dic is populated the underlying object is not yet parsed
# As a result, we need to iterate over all the custom errors here instead of using the dict
custom_errors = contract.custom_errors
for custom_error in custom_errors:
if var_name == custom_error.solidity_signature:
return custom_error
try:
for custom_error in custom_errors:
if var_name == custom_error.solidity_signature:
return custom_error
except ValueError:
# This can happen as custom error sol signature might not have been built
# when find_variable was called
# TODO refactor find_variable to prevent this from happening
pass
# If the enum is refered as its name rather than its canonicalName
enums = {e.name: e for e in contract.enums}
@ -284,6 +305,7 @@ def find_variable(
Enum,
Structure,
CustomError,
TypeAlias,
],
bool,
]:
@ -326,6 +348,12 @@ def find_variable(
# Because functions are copied between contracts, two functions can have the same ref
# So we need to first look with respect to the direct context
if var_name in current_scope.renaming:
var_name = current_scope.renaming[var_name]
if var_name in current_scope.user_defined_types:
return current_scope.user_defined_types[var_name], False
# Use ret0/ret1 to help mypy
ret0 = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions

@ -14,6 +14,8 @@ from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.import_directive import Import
from slither.core.declarations.pragma_directive import Pragma
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.scope.scope import FileScope
from slither.core.solidity_types import ElementaryType, TypeAliasTopLevel
from slither.core.variables.top_level_variable import TopLevelVariable
from slither.exceptions import SlitherException
from slither.solc_parsing.declarations.contract import ContractSolc
@ -28,6 +30,33 @@ logger = logging.getLogger("SlitherSolcParsing")
logger.setLevel(logging.INFO)
def _handle_import_aliases(
symbol_aliases: Dict, import_directive: Import, scope: FileScope
) -> None:
"""
Handle the parsing of import aliases
Args:
symbol_aliases (Dict): json dict from solc
import_directive (Import): current import directive
scope (FileScope): current file scape
Returns:
"""
for symbol_alias in symbol_aliases:
if (
"foreign" in symbol_alias
and "name" in symbol_alias["foreign"]
and "local" in symbol_alias
):
original_name = symbol_alias["foreign"]["name"]
local_name = symbol_alias["local"]
import_directive.renaming[local_name] = original_name
# Assuming that two imports cannot collide in renaming
scope.renaming[local_name] = original_name
class SlitherCompilationUnitSolc:
# pylint: disable=no-self-use,too-many-instance-attributes
def __init__(self, compilation_unit: SlitherCompilationUnit):
@ -204,6 +233,9 @@ class SlitherCompilationUnitSolc:
# TODO investigate unitAlias in version < 0.7 and legacy ast
if "unitAlias" in top_level_data:
import_directive.alias = top_level_data["unitAlias"]
if "symbolAliases" in top_level_data:
symbol_aliases = top_level_data["symbolAliases"]
_handle_import_aliases(symbol_aliases, import_directive, scope)
else:
import_directive = Import(
Path(
@ -267,6 +299,23 @@ class SlitherCompilationUnitSolc:
self._compilation_unit.custom_errors.append(custom_error)
self._custom_error_parser.append(custom_error_parser)
elif top_level_data[self.get_key()] == "UserDefinedValueTypeDefinition":
assert "name" in top_level_data
alias = top_level_data["name"]
assert "underlyingType" in top_level_data
underlying_type = top_level_data["underlyingType"]
assert (
"nodeType" in underlying_type
and underlying_type["nodeType"] == "ElementaryTypeName"
)
assert "name" in underlying_type
original_type = ElementaryType(underlying_type["name"])
user_defined_type = TypeAliasTopLevel(original_type, alias, scope)
user_defined_type.set_offset(top_level_data["src"], self._compilation_unit)
scope.user_defined_types[alias] = user_defined_type
else:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
@ -352,20 +401,24 @@ Please rename it, this name is reserved for Slither's internals"""
father_constructors = []
# try:
# Resolve linearized base contracts.
missing_inheritance = False
missing_inheritance = None
for i in contract_parser.linearized_base_contracts[1:]:
if i in contract_parser.remapping:
ancestors.append(
contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_parser.remapping[i]
)
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i])
contract_name = contract_parser.remapping[i]
if contract_name in contract_parser.underlying_contract.file_scope.renaming:
contract_name = contract_parser.underlying_contract.file_scope.renaming[
contract_name
]
target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_name
)
assert target
ancestors.append(target)
elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i])
else:
missing_inheritance = True
missing_inheritance = i
# Resolve immediate base contracts
for i in contract_parser.baseContracts:
@ -379,7 +432,7 @@ Please rename it, this name is reserved for Slither's internals"""
elif i in self._contracts_by_id:
fathers.append(self._contracts_by_id[i])
else:
missing_inheritance = True
missing_inheritance = i
# Resolve immediate base constructor calls
for i in contract_parser.baseConstructorContractsCalled:
@ -393,7 +446,7 @@ Please rename it, this name is reserved for Slither's internals"""
elif i in self._contracts_by_id:
father_constructors.append(self._contracts_by_id[i])
else:
missing_inheritance = True
missing_inheritance = i
contract_parser.underlying_contract.set_inheritance(
ancestors, fathers, father_constructors
@ -403,7 +456,14 @@ Please rename it, this name is reserved for Slither's internals"""
self._compilation_unit.contracts_with_missing_inheritance.add(
contract_parser.underlying_contract
)
contract_parser.log_incorrect_parsing(f"Missing inheritance {contract_parser}")
txt = f"Missing inheritance {contract_parser.underlying_contract} ({contract_parser.compilation_unit.crytic_compile_compilation_unit.unique_id})\n"
txt += f"Missing inheritance ID: {missing_inheritance}\n"
if contract_parser.underlying_contract.inheritance:
txt += "Inheritance found:\n"
for contract_inherited in contract_parser.underlying_contract.inheritance:
txt += f"\t - {contract_inherited} (ID {contract_inherited.id})\n"
contract_parser.log_incorrect_parsing(txt)
contract_parser.set_is_analyzed(True)
contract_parser.delete_content()
@ -626,12 +686,12 @@ Please rename it, this name is reserved for Slither's internals"""
for func in contract.functions + contract.modifiers:
try:
func.generate_slithir_and_analyze()
except AttributeError:
except AttributeError as e:
# This can happens for example if there is a call to an interface
# And the interface is redefined due to contract's name reuse
# But the available version misses some functions
self._underlying_contract_to_parser[contract].log_incorrect_parsing(
f"Impossible to generate IR for {contract.name}.{func.name}"
f"Impossible to generate IR for {contract.name}.{func.name}:\n {e}"
)
contract.convert_expression_to_slithir_ssa()

@ -6,6 +6,7 @@ from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.function_contract import FunctionContract
from slither.core.expressions.literal import Literal
from slither.core.solidity_types import TypeAlias
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.elementary_type import (
ElementaryType,
@ -111,7 +112,7 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
if not var_type:
if name.startswith("function "):
found = re.findall(
"function \(([ ()\[\]a-zA-Z0-9\.,]*?)\)(?: payable)?(?: (?:external|internal|pure|view))?(?: returns \(([a-zA-Z0-9() \.,]*)\))?",
r"function \(([ ()\[\]a-zA-Z0-9\.,]*?)\)(?: payable)?(?: (?:external|internal|pure|view))?(?: returns \(([a-zA-Z0-9() \.,]*)\))?",
name,
)
assert len(found) == 1
@ -158,10 +159,10 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
if name.startswith("mapping("):
# nested mapping declared with var
if name.count("mapping(") == 1:
found = re.findall("mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name)
found = re.findall(r"mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name)
else:
found = re.findall(
"mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)",
r"mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)",
name,
)
assert len(found) == 1
@ -204,7 +205,7 @@ def _add_type_references(type_found: Type, src: str, sl: "SlitherCompilationUnit
def parse_type(
t: Union[Dict, UnknownType],
caller_context: Union[CallerContextExpression, "SlitherCompilationUnitSolc"],
):
) -> Type:
"""
caller_context can be a SlitherCompilationUnitSolc because we recursively call the function
and go up in the context's scope. If we are really lost we just go over the SlitherCompilationUnitSolc
@ -229,6 +230,8 @@ def parse_type(
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
sl: "SlitherCompilationUnit"
renaming: Dict[str, str]
user_defined_types: Dict[str, TypeAlias]
# Note: for convenicence top level functions use the same parser than function in contract
# but contract_parser is set to None
if isinstance(caller_context, SlitherCompilationUnitSolc) or (
@ -238,10 +241,14 @@ def parse_type(
if isinstance(caller_context, SlitherCompilationUnitSolc):
sl = caller_context.compilation_unit
next_context = caller_context
renaming = {}
user_defined_types = {}
else:
assert isinstance(caller_context, FunctionSolc)
sl = caller_context.underlying_function.compilation_unit
next_context = caller_context.slither_parser
renaming = caller_context.underlying_function.file_scope.renaming
user_defined_types = caller_context.underlying_function.file_scope.user_defined_types
structures_direct_access = sl.structures_top_level
all_structuress = [c.structures for c in sl.contracts]
all_structures = [item for sublist in all_structuress for item in sublist]
@ -272,10 +279,17 @@ def parse_type(
all_structuress = [c.structures for c in scope.contracts.values()]
all_structures = [item for sublist in all_structuress for item in sublist]
all_structures += structures_direct_access
enums_direct_access = []
all_enums = scope.enums.values()
all_enumss = [c.enums for c in scope.contracts.values()]
all_enums = [item for sublist in all_enumss for item in sublist]
all_enums += scope.enums.values()
contracts = scope.contracts.values()
functions = list(scope.functions)
renaming = scope.renaming
user_defined_types = scope.user_defined_types
elif isinstance(caller_context, (ContractSolc, FunctionSolc)):
sl = caller_context.compilation_unit
if isinstance(caller_context, FunctionSolc):
@ -285,9 +299,11 @@ def parse_type(
assert isinstance(underlying_func, FunctionContract)
contract = underlying_func.contract
next_context = caller_context.contract_parser
scope = caller_context.underlying_function.file_scope
else:
contract = caller_context.underlying_contract
next_context = caller_context
scope = caller_context.underlying_contract.file_scope
structures_direct_access = contract.structures
structures_direct_access += contract.file_scope.structures.values()
@ -301,6 +317,9 @@ def parse_type(
all_enums += contract.file_scope.enums.values()
contracts = contract.file_scope.contracts.values()
functions = contract.functions + contract.modifiers
renaming = scope.renaming
user_defined_types = scope.user_defined_types
else:
raise ParsingError(f"Incorrect caller context: {type(caller_context)}")
@ -311,8 +330,13 @@ def parse_type(
key = "name"
if isinstance(t, UnknownType):
name = t.name
if name in renaming:
name = renaming[name]
if name in user_defined_types:
return user_defined_types[name]
return _find_from_type_name(
t.name,
name,
functions,
contracts,
structures_direct_access,
@ -328,8 +352,13 @@ def parse_type(
if t[key] == "UserDefinedTypeName":
if is_compact_ast:
name = t["typeDescriptions"]["typeString"]
if name in renaming:
name = renaming[name]
if name in user_defined_types:
return user_defined_types[name]
type_found = _find_from_type_name(
t["typeDescriptions"]["typeString"],
name,
functions,
contracts,
structures_direct_access,
@ -342,8 +371,14 @@ def parse_type(
# Determine if we have a type node (otherwise we use the name node, as some older solc did not have 'type').
type_name_key = "type" if "type" in t["attributes"] else key
name = t["attributes"][type_name_key]
if name in renaming:
name = renaming[name]
if name in user_defined_types:
return user_defined_types[name]
type_found = _find_from_type_name(
t["attributes"][type_name_key],
name,
functions,
contracts,
structures_direct_access,
@ -357,8 +392,13 @@ def parse_type(
# Introduced with Solidity 0.8
if t[key] == "IdentifierPath":
if is_compact_ast:
type_found = _find_from_type_name(
t["name"],
name = t["name"]
if name in renaming:
name = renaming[name]
if name in user_defined_types:
return user_defined_types[name]
type_found = _find_from_type_name(
name,
functions,
contracts,
structures_direct_access,

@ -1,4 +1,5 @@
import logging
import re
from typing import Dict
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
@ -103,6 +104,23 @@ class VariableDeclarationSolc:
"""
return self._reference_id
def _handle_comment(self, attributes: Dict):
if "documentation" in attributes and "text" in attributes["documentation"]:
candidates = attributes["documentation"]["text"].split(",")
for candidate in candidates:
if "@custom:security non-reentrant" in candidate:
self._variable.is_reentrant = False
write_protection = re.search(
r'@custom:security write-protection="([\w, ()]*)"', candidate
)
if write_protection:
if self._variable.write_protection is None:
self._variable.write_protection = []
self._variable.write_protection.append(write_protection.group(1))
def _analyze_variable_attributes(self, attributes: Dict):
if "visibility" in attributes:
self._variable.visibility = attributes["visibility"]
@ -145,6 +163,8 @@ class VariableDeclarationSolc:
if attributes["mutability"] == "immutable":
self._variable.is_immutable = True
self._handle_comment(attributes)
self._analyze_variable_attributes(attributes)
if self._is_compact_ast:

@ -279,6 +279,7 @@ class YulBlock(YulScope):
"""
# pylint: disable=redefined-slots-in-subclass
__slots__ = ["_entrypoint", "_parent_func", "_nodes", "node_scope"]
def __init__(
@ -736,7 +737,7 @@ def _parse_yul_magic_suffixes(name: str, root: YulScope) -> Optional[Expression]
# Currently SlithIR doesnt support raw access to memory
# So things like .offset/.slot will return the variable
# Instaed of the actual offset/slot
if name.endswith("_slot") or name.endswith(".slot"):
if name.endswith(("_slot", ".slot")):
potential_name = name[:-5]
variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found:
@ -744,7 +745,7 @@ def _parse_yul_magic_suffixes(name: str, root: YulScope) -> Optional[Expression]
var = root.function.get_local_variable_from_name(potential_name)
if var and var.is_storage:
return Identifier(var)
if name.endswith("_offset") or name.endswith(".offset"):
if name.endswith(("_offset", ".offset")):
potential_name = name[:-7]
variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found:

@ -51,7 +51,7 @@ def _check_signature(erc_function, contract, ret):
ret["missing_function"].append(missing_func.data)
return
function_return_type = [export_return_type_from_variable(state_variable_as_function)]
function_return_type = export_return_type_from_variable(state_variable_as_function)
function = state_variable_as_function
function_view = True
@ -192,9 +192,10 @@ def generic_erc_checks(contract, erc_functions, erc_events, ret, explored=None):
logger.info("## Check functions")
for erc_function in erc_functions:
_check_signature(erc_function, contract, ret)
logger.info("\n## Check events")
for erc_event in erc_events:
_check_events(erc_event, contract, ret)
if erc_events:
logger.info("\n## Check events")
for erc_event in erc_events:
_check_events(erc_event, contract, ret)
logger.info("\n")

@ -79,6 +79,12 @@ def parse_args():
action="store_true",
)
group_patching.add_argument(
"--convert-library-to-internal",
help="Convert external or public functions to internal in library.",
action="store_true",
)
group_patching.add_argument(
"--remove-assert", help="Remove call to assert().", action="store_true"
)
@ -104,28 +110,32 @@ def main():
args = parse_args()
slither = Slither(args.filename, **vars(args))
flat = Flattening(
slither,
external_to_public=args.convert_external,
remove_assert=args.remove_assert,
private_to_internal=args.convert_private,
export_path=args.dir,
pragma_solidity=args.pragma_solidity,
)
try:
strategy = Strategy[args.strategy]
except KeyError:
to_log = f"{args.strategy} is not a valid strategy, use: {STRATEGIES_NAMES} (default MostDerived)"
logger.error(to_log)
return
flat.export(
strategy=strategy,
target=args.contract,
json=args.json,
zip=args.zip,
zip_type=args.zip_type,
)
for compilation_unit in slither.compilation_units:
flat = Flattening(
compilation_unit,
external_to_public=args.convert_external,
remove_assert=args.remove_assert,
convert_library_to_internal=args.convert_library_to_internal,
private_to_internal=args.convert_private,
export_path=args.dir,
pragma_solidity=args.pragma_solidity,
)
try:
strategy = Strategy[args.strategy]
except KeyError:
to_log = f"{args.strategy} is not a valid strategy, use: {STRATEGIES_NAMES} (default MostDerived)"
logger.error(to_log)
return
flat.export(
strategy=strategy,
target=args.contract,
json=args.json,
zip=args.zip,
zip_type=args.zip_type,
)
if __name__ == "__main__":

@ -1,17 +1,21 @@
import logging
import re
import uuid
from collections import namedtuple
from enum import Enum as PythonEnum
from pathlib import Path
from typing import List, Set, Dict, Optional
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import SolidityFunction, EnumContract, StructureContract
from slither.core.declarations.contract import Contract
from slither.core.slither_core import SlitherCore
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.top_level import TopLevel
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
from slither.exceptions import SlitherException
from slither.slithir.operations import NewContract, TypeConversion, SolidityCall
from slither.slithir.operations import NewContract, TypeConversion, SolidityCall, InternalCall
from slither.tools.flattening.export.export import (
Export,
export_as_json,
@ -44,18 +48,21 @@ class Flattening:
# pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-locals,too-few-public-methods
def __init__(
self,
slither: SlitherCore,
compilation_unit: SlitherCompilationUnit,
external_to_public=False,
remove_assert=False,
convert_library_to_internal=False,
private_to_internal=False,
export_path: Optional[str] = None,
pragma_solidity: Optional[str] = None,
):
self._source_codes: Dict[Contract, str] = {}
self._slither: SlitherCore = slither
self._source_codes_top_level: Dict[TopLevel, str] = {}
self._compilation_unit: SlitherCompilationUnit = compilation_unit
self._external_to_public = external_to_public
self._remove_assert = remove_assert
self._use_abi_encoder_v2 = False
self._convert_library_to_internal = convert_library_to_internal
self._private_to_internal = private_to_internal
self._pragma_solidity = pragma_solidity
@ -63,20 +70,32 @@ class Flattening:
self._check_abi_encoder_v2()
for contract in slither.contracts:
for contract in compilation_unit.contracts:
self._get_source_code(contract)
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.variables_top_level)
self._get_source_code_top_level(compilation_unit.functions_top_level)
def _get_source_code_top_level(self, elems: List[TopLevel]) -> None:
for elem in elems:
src_mapping = elem.source_mapping
content = self._compilation_unit.core.source_code[src_mapping["filename_absolute"]]
start = src_mapping["start"]
end = src_mapping["start"] + src_mapping["length"]
self._source_codes_top_level[elem] = content[start:end]
def _check_abi_encoder_v2(self):
"""
Check if ABIEncoderV2 is required
Set _use_abi_encorder_v2
:return:
"""
for compilation_unit in self._slither.compilation_units:
for p in compilation_unit.pragma_directives:
if "ABIEncoderV2" in str(p.directive):
self._use_abi_encoder_v2 = True
return
for p in self._compilation_unit.pragma_directives:
if "ABIEncoderV2" in str(p.directive):
self._use_abi_encoder_v2 = True
return
def _get_source_code(
self, contract: Contract
@ -88,13 +107,13 @@ class Flattening:
:return:
"""
src_mapping = contract.source_mapping
content = self._slither.source_code[src_mapping.filename.absolute]
content = self._compilation_unit.core.source_code[src_mapping.filename.absolute]
start = src_mapping.start
end = src_mapping.start + src_mapping.length
to_patch = []
# interface must use external
if self._external_to_public and contract.contract_kind != "interface":
if self._external_to_public and not contract.is_interface:
for f in contract.functions_declared:
# fallback must be external
if f.is_fallback or f.is_constructor_variables:
@ -129,14 +148,42 @@ class Flattening:
)
)
if self._convert_library_to_internal and contract.is_library:
for f in contract.functions_declared:
visibility = ""
if f.visibility in ["external", "public"]:
visibility = f.visibility
attributes_start = (
f.parameters_src().source_mapping["start"]
+ f.parameters_src().source_mapping["length"]
)
attributes_end = f.returns_src().source_mapping["start"]
attributes = content[attributes_start:attributes_end]
regex = (
re.search(r"((\sexternal)\s+)|(\sexternal)$|(\)external)$", attributes)
if visibility == "external"
else re.search(r"((\spublic)\s+)|(\spublic)$|(\)public)$", attributes)
)
if regex:
to_patch.append(
Patch(
attributes_start + regex.span()[0] + 1,
"external_to_internal"
if visibility == "external"
else "public_to_internal",
)
)
else:
raise SlitherException(
f"{visibility} keyword not found {f.name} {attributes}"
)
if self._private_to_internal:
for variable in contract.state_variables_declared:
if variable.visibility == "private":
print(variable.source_mapping)
attributes_start = variable.source_mapping.start
attributes_end = attributes_start + variable.source_mapping.length
attributes = content[attributes_start:attributes_end]
print(attributes)
regex = re.search(r" private ", attributes)
if regex:
to_patch.append(
@ -171,6 +218,10 @@ class Flattening:
index = index - start
if patch_type == "public_to_external":
content = content[:index] + "public" + content[index + len("external") :]
elif patch_type == "external_to_internal":
content = content[:index] + "internal" + content[index + len("external") :]
elif patch_type == "public_to_internal":
content = content[:index] + "internal" + content[index + len("public") :]
elif patch_type == "private_to_internal":
content = content[:index] + "internal" + content[index + len("private") :]
elif patch_type == "calldata_to_memory":
@ -191,35 +242,54 @@ class Flattening:
ret += f"pragma solidity {self._pragma_solidity};\n"
else:
# TODO support multiple compiler version
ret += f"pragma solidity {list(self._slither.crytic_compile.compilation_units.values())[0].compiler_version.version};\n"
ret += f"pragma solidity {list(self._compilation_unit.crytic_compile.compilation_units.values())[0].compiler_version.version};\n"
if self._use_abi_encoder_v2:
ret += "pragma experimental ABIEncoderV2;\n"
return ret
def _export_from_type(self, t, contract, exported, list_contract):
def _export_from_type(
self,
t: Type,
contract: Contract,
exported: Set[str],
list_contract: List[Contract],
list_top_level: List[TopLevel],
):
if isinstance(t, UserDefinedType):
if 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)
t_type = t.type
if 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
)
else:
assert isinstance(t.type, Contract)
if t.type != contract and t.type not in exported:
self._export_list_used_contracts(t.type, exported, list_contract)
self._export_list_used_contracts(
t.type, exported, list_contract, list_top_level
)
elif isinstance(t, MappingType):
self._export_from_type(t.type_from, contract, exported, list_contract)
self._export_from_type(t.type_to, contract, exported, list_contract)
self._export_from_type(t.type_from, contract, exported, list_contract, list_top_level)
self._export_from_type(t.type_to, contract, exported, list_contract, list_top_level)
elif isinstance(t, ArrayType):
self._export_from_type(t.type, contract, exported, list_contract)
self._export_from_type(t.type, contract, exported, list_contract, list_top_level)
def _export_list_used_contracts( # pylint: disable=too-many-branches
self, contract: Contract, exported: Set[str], list_contract: List[Contract]
self,
contract: Contract,
exported: Set[str],
list_contract: List[Contract],
list_top_level: List[TopLevel],
):
# TODO: investigate why this happen
if not isinstance(contract, Contract):
return
if contract.name in exported:
return
exported.add(contract.name)
for inherited in contract.inheritance:
self._export_list_used_contracts(inherited, exported, list_contract)
self._export_list_used_contracts(inherited, exported, list_contract, list_top_level)
# Find all the external contracts called
externals = contract.all_library_calls + contract.all_high_level_calls
@ -228,7 +298,16 @@ class Flattening:
externals = list({e[0] for e in externals if e[0] != contract})
for inherited in externals:
self._export_list_used_contracts(inherited, exported, list_contract)
self._export_list_used_contracts(inherited, exported, list_contract, list_top_level)
for list_libs in contract.using_for.values():
for lib_candidate_type in list_libs:
if isinstance(lib_candidate_type, UserDefinedType):
lib_candidate = lib_candidate_type.type
if isinstance(lib_candidate, Contract):
self._export_list_used_contracts(
lib_candidate, exported, list_contract, list_top_level
)
# Find all the external contracts use as a base type
local_vars = []
@ -236,11 +315,11 @@ class Flattening:
local_vars += f.variables
for v in contract.variables + local_vars:
self._export_from_type(v.type, contract, exported, list_contract)
self._export_from_type(v.type, contract, exported, list_contract, list_top_level)
for s in contract.structures:
for elem in s.elems.values():
self._export_from_type(elem.type, contract, exported, list_contract)
self._export_from_type(elem.type, contract, exported, list_contract, list_top_level)
# Find all convert and "new" operation that can lead to use an external contract
for f in contract.functions_declared:
@ -248,21 +327,38 @@ class Flattening:
if isinstance(ir, NewContract):
if ir.contract_created != contract and not ir.contract_created in exported:
self._export_list_used_contracts(
ir.contract_created, exported, list_contract
ir.contract_created, exported, list_contract, list_top_level
)
if isinstance(ir, TypeConversion):
self._export_from_type(ir.type, contract, exported, list_contract)
self._export_from_type(
ir.type, contract, exported, list_contract, list_top_level
)
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)
def _export_contract_with_inheritance(self, contract) -> Export:
list_contracts: List[Contract] = [] # will contain contract itself
self._export_list_used_contracts(contract, set(), list_contracts)
path = Path(self._export_path, f"{contract.name}.sol")
list_top_level: List[TopLevel] = []
self._export_list_used_contracts(contract, set(), list_contracts, list_top_level)
path = Path(self._export_path, f"{contract.name}_{uuid.uuid4()}.sol")
content = ""
content += self._pragmas()
for listed_top_level in list_top_level:
content += self._source_codes_top_level[listed_top_level]
content += "\n"
for listed_contract in list_contracts:
content += self._source_codes[listed_contract]
content += "\n"
@ -271,7 +367,7 @@ class Flattening:
def _export_most_derived(self) -> List[Export]:
ret: List[Export] = []
for contract in self._slither.contracts_derived:
for contract in self._compilation_unit.contracts_derived:
ret.append(self._export_contract_with_inheritance(contract))
return ret
@ -281,8 +377,13 @@ class Flattening:
content = ""
content += self._pragmas()
for top_level_content in self._source_codes_top_level.values():
content += "\n"
content += top_level_content
content += "\n"
contract_seen = set()
contract_to_explore = list(self._slither.contracts)
contract_to_explore = list(self._compilation_unit.contracts)
# We only need the inheritance order here, as solc can compile
# a contract that use another contract type (ex: state variable) that he has not seen yet
@ -303,9 +404,17 @@ class Flattening:
def _export_with_import(self) -> List[Export]:
exports: List[Export] = []
for contract in self._slither.contracts:
for contract in self._compilation_unit.contracts:
list_contracts: List[Contract] = [] # will contain contract itself
self._export_list_used_contracts(contract, set(), list_contracts)
list_top_level: List[TopLevel] = []
self._export_list_used_contracts(contract, set(), list_contracts, list_top_level)
if list_top_level:
logger.info(
"Top level objects are not yet supported with the local import flattening"
)
for elem in list_top_level:
logger.info(f"Missing {elem} for {contract.name}")
path = Path(self._export_path, f"{contract.name}.sol")
@ -341,12 +450,13 @@ class Flattening:
elif strategy == Strategy.LocalImport:
exports = self._export_with_import()
else:
contracts = self._slither.get_contract_from_name(target)
if len(contracts) != 1:
contracts = self._compilation_unit.get_contract_from_name(target)
if len(contracts) == 0:
logger.error(f"{target} not found")
return
contract = contracts[0]
exports = [self._export_contract_with_inheritance(contract)]
exports = []
for contract in contracts:
exports.append(self._export_contract_with_inheritance(contract))
if json:
export_as_json(exports, json)

@ -22,8 +22,8 @@ def _get_all_covered_kspec_functions(target: str) -> Set[Tuple[str, str]]:
# Create a set of our discovered functions which are covered
covered_functions: Set[Tuple[str, str]] = set()
BEHAVIOUR_PATTERN = re.compile("behaviour\s+(\S+)\s+of\s+(\S+)")
INTERFACE_PATTERN = re.compile("interface\s+([^\r\n]+)")
BEHAVIOUR_PATTERN = re.compile(r"behaviour\s+(\S+)\s+of\s+(\S+)")
INTERFACE_PATTERN = re.compile(r"interface\s+([^\r\n]+)")
# Read the file contents
with open(target, "r", encoding="utf8") as target_file:

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

Loading…
Cancel
Save