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

pull/877/head
Josselin Feist 2 years ago
commit 5802174562
  1. 57
      .github/ISSUE_TEMPLATE/bug_report.yml
  2. 1
      .github/ISSUE_TEMPLATE/feature_request.yml
  3. 42
      .github/workflows/IR.yml
  4. 11
      .github/workflows/black.yml
  5. 52
      .github/workflows/ci.yml
  6. 18
      .github/workflows/detectors.yml
  7. 20
      .github/workflows/features.yml
  8. 11
      .github/workflows/linter.yml
  9. 26
      .github/workflows/parser.yml
  10. 24
      .github/workflows/pip-audit.yml
  11. 11
      .github/workflows/pylint.yml
  12. 50
      .github/workflows/read_storage.yml
  13. 27
      CONTRIBUTING.md
  14. 11
      Dockerfile
  15. 35
      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. 29
      slither/core/slither_core.py
  36. 2
      slither/core/solidity_types/__init__.py
  37. 14
      slither/core/solidity_types/array_type.py
  38. 16
      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. 10
      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. 25
      slither/detectors/statements/unprotected_upgradeable.py
  70. 4
      slither/detectors/variables/similar_variables.py
  71. 6
      slither/detectors/variables/uninitialized_local_variables.py
  72. 2
      slither/detectors/variables/uninitialized_storage_variables.py
  73. 2
      slither/formatters/attributes/const_functions.py
  74. 2
      slither/formatters/attributes/constant_pragma.py
  75. 3
      slither/formatters/attributes/incorrect_solc.py
  76. 2
      slither/formatters/functions/external_function.py
  77. 10
      slither/formatters/naming_convention/naming_convention.py
  78. 1
      slither/printers/all_printers.py
  79. 27
      slither/printers/guidance/echidna.py
  80. 4
      slither/printers/summary/function_ids.py
  81. 2
      slither/printers/summary/variable_order.py
  82. 61
      slither/printers/summary/when_not_paused.py
  83. 2
      slither/slither.py
  84. 50
      slither/slithir/convert.py
  85. 18
      slither/slithir/operations/high_level_call.py
  86. 2
      slither/slithir/operations/library_call.py
  87. 2
      slither/slithir/operations/low_level_call.py
  88. 31
      slither/solc_parsing/declarations/contract.py
  89. 1
      slither/solc_parsing/declarations/custom_error.py
  90. 3
      slither/solc_parsing/expressions/expression_parsing.py
  91. 34
      slither/solc_parsing/expressions/find_variable.py
  92. 84
      slither/solc_parsing/slither_compilation_unit_solc.py
  93. 58
      slither/solc_parsing/solidity_types/type_parsing.py
  94. 20
      slither/solc_parsing/variables/variable_declaration.py
  95. 5
      slither/solc_parsing/yul/parse_yul.py
  96. 3
      slither/tools/erc_conformance/erc/ercs.py
  97. 12
      slither/tools/flattening/__main__.py
  98. 184
      slither/tools/flattening/flattening.py
  99. 4
      slither/tools/kspec_coverage/analysis.py
  100. 91
      slither/tools/read_storage/README.md
  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
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

@ -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:
@ -17,41 +17,61 @@ on:
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
- 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
# Used by ci_test.sh
pip install deepdiff
pip install solc-select
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
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v16
- 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: |

@ -16,27 +16,27 @@ on:
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

@ -16,24 +16,25 @@ on:
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

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

@ -16,32 +16,30 @@ on:
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
uses: actions/checkout@v3
- name: Install Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install pip-audit
- name: Install Slither
run: |
python -m venv /tmp/pip-audit-env
source /tmp/pip-audit-env/bin/activate
python -m pip install --upgrade pip
python -m pip install pip-audit
- name: Run pip-audit
run: |
python -m pip install .
pip-audit --desc -v
- name: Run pip-audit
uses: trailofbits/gh-action-pip-audit@v0.0.4
with:
virtual-environment: /tmp/pip-audit-env

@ -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
@ -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 self.has_ignore_comment(r):
return False
if r["id"] in self._previous_results_ids:
return False
if self.has_ignore_comment(r):
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:

@ -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 [
if func.visibility in [
"internal",
"private",
]
and self.is_mixed_case_with_underscore(func.name)
):
] 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

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

@ -38,7 +38,6 @@ def _get_name(f: Union[Function, Variable]) -> str:
if f.is_fallback or f.is_receive:
return "()"
return f.solidity_signature
return f.function_name
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
@ -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,11 +113,7 @@ 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):
if self.is_static_call():
return False
# If there is a call to itself
# We can check that the function called is
@ -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,12 +126,26 @@ def _find_top_level(
new_val = SolidityImportPlaceHolder(import_directive)
return new_val, True
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
if var_name in scope.variables:
return scope.variables[var_name], 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
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:
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(
t["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,6 +192,7 @@ 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)
if erc_events:
logger.info("\n## Check events")
for erc_event in erc_events:
_check_events(erc_event, contract, ret)

@ -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,10 +110,14 @@ def main():
args = parse_args()
slither = Slither(args.filename, **vars(args))
for compilation_unit in slither.compilation_units:
flat = Flattening(
slither,
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,

@ -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,17 +70,29 @@ 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:
for p in self._compilation_unit.pragma_directives:
if "ABIEncoderV2" in str(p.directive):
self._use_abi_encoder_v2 = True
return
@ -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:

@ -0,0 +1,91 @@
# Slither-read-storage
Slither-read-storage is a tool to retrieve the storage slots and values of entire contracts or single variables.
## Usage
### CLI Interface
```shell
positional arguments:
contract_source (DIR) ADDRESS The deployed contract address if verified on etherscan. Prepend project directory for unverified contracts.
optional arguments:
--variable-name VARIABLE_NAME The name of the variable whose value will be returned.
--rpc-url RPC_URL An endpoint for web3 requests.
--key KEY The key/ index whose value will be returned from a mapping or array.
--deep-key DEEP_KEY The key/ index whose value will be returned from a deep mapping or multidimensional array.
--struct-var STRUCT_VAR The name of the variable whose value will be returned from a struct.
--storage-address STORAGE_ADDRESS The address of the storage contract (if a proxy pattern is used).
--contract-name CONTRACT_NAME The name of the logic contract.
--layout Toggle used to write a JSON file with the entire storage layout.
--value Toggle used to include values in output.
--max-depth MAX_DEPTH Max depth to search in data structure.
```
### Examples
Retrieve the storage slots of a local contract:
```shell
slither-read-storage file.sol 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --layout
```
Retrieve the storage slots of a contract verified on an Etherscan-like platform:
```shell
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --layout
```
To retrieve the values as well, pass `--value` and `--rpc-url $RPC_URL`:
```shell
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --layout --rpc-url $RPC_URL --value
```
To view only the slot of the `slot0` structure variable, pass `--variable-name slot0`:
```shell
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --variable-name slot0 --rpc-url $RPC_URL --value
```
To view a member of the `slot0` struct, pass `--struct-var tick`
```shell
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --variable-name slot0 --rpc-url $RPC_URL --value --struct-var tick
```
Retrieve the ERC20 balance slot of an account:
```shell
slither-read-storage 0xa2327a938Febf5FEC13baCFb16Ae10EcBc4cbDCF --variable-name balances --key 0xab5801a7d398351b8be11c439e05c5b3259aec9b
```
To retrieve the actual balance, pass `--variable-name balances` and `--key 0xab5801a7d398351b8be11c439e05c5b3259aec9b`. (`balances` is a `mapping(address => uint)`)
Since this contract uses the delegatecall-proxy pattern, the proxy address must be passed as the `--storage-address`. Otherwise, it is not required.
```shell
slither-read-storage 0xa2327a938Febf5FEC13baCFb16Ae10EcBc4cbDCF --variable-name balances --key 0xab5801a7d398351b8be11c439e05c5b3259aec9b --storage-address 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 --rpc-url $RPC_URL --value
```
## Troubleshooting/FAQ
- If the storage slots or values of a contract verified Etherscan are wrong, try passing `--contract $CONTRACT_NAME` explicitly. Otherwise, the storage may be retrieved from storage slots based off an unrelated contract (Etherscan includes these). (Also, make sure that the RPC is for the correct network.)
- If Etherscan fails to return a source code, try passing `--etherscan-apikey $API_KEY` to avoid hitting a rate-limit.
- How do I use this tool on other chains?
If an EVM chain has an Etherscan-like platform the crytic-compile supports, this tool supports it by making the following modifications.
Take Avalanche, for instance:
```shell
slither-read-storage avax:0x0000000000000000000000000000000000000000 --layout --value --rpc-url $AVAX_RPC_URL
```
## Limitations
- Requires source code.
- Only works on Solidity contracts.
- Cannot find variables with unstructured storage.
- Does not support all data types (please open an issue or PR).
- Mappings cannot be completely enumerated since all keys used historically are not immediately available.

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

Loading…
Cancel
Save