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

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

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

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

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

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

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

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

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

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

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

@ -11,7 +11,7 @@ on:
branches: [master, dev] branches: [master, dev]
schedule: schedule:
# run CI every day even if no PRs/merges occur # run CI every day even if no PRs/merges occur
- cron: '0 12 * * *' - cron: '0 12 * * *'
jobs: jobs:
build: build:
@ -22,21 +22,18 @@ jobs:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Python 3.6 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v3
with: with:
python-version: 3.6 python-version: 3.8
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters mkdir -p .github/linters
cp pyproject.toml .github/linters cp pyproject.toml .github/linters
- name: Pylint - name: Pylint
uses: github/super-linter/slim@v4 uses: github/super-linter/slim@v4.9.2
if: always() if: always()
env: env:
# run linter on everything to catch preexisting problems # 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. - 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. - 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 ## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation). Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).
To run the unit tests, you need To run the unit tests, you need to clone this repository and run `pip install ".[dev]"`.
- `deepdiff` installed (`pip install deepdiff`).
- `pycov` installed (`pip install pytest-cov`).
- [`solc-select`](https://github.com/crytic/solc-select) installed.
### Linters ### Linters
@ -37,7 +52,8 @@ To run them locally in the root dir of the repository:
- `pylint slither tests --rcfile pyproject.toml` - `pylint slither tests --rcfile pyproject.toml`
- `black . --config 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 ### Detectors tests
For each new detector, at least one regression tests must be present. 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` - Create a test in `tests`
- Update `ALL_TEST` in `tests/test_detectors.py` - Update `ALL_TEST` in `tests/test_detectors.py`
- Run `python ./tests/test_detectors.py --generate`. This will generate the json artifacts in `tests/expected_json`. Add the generated files to git. - Run `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. - Run `pytest ./tests/test_detectors.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html` To 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 name=slither
LABEL src="https://github.com/trailofbits/slither" LABEL src="https://github.com/trailofbits/slither"
@ -6,11 +6,12 @@ LABEL creator=trailofbits
LABEL dockerfile_maintenance=trailofbits LABEL dockerfile_maintenance=trailofbits
LABEL desc="Static Analyzer for Solidity" LABEL desc="Static Analyzer for Solidity"
RUN apt-get update \ RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get upgrade -y \ && apt-get update \
&& apt-get install -y git python3 python3-setuptools wget software-properties-common && 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 \ && chmod +x solc-static-linux \
&& mv solc-static-linux /usr/bin/solc && 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) - [Tools](#tools)
- [How to Install](#how-to-install) - [How to Install](#how-to-install)
- [Getting Help](#getting-help) - [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications) - [Publications](#publications)
## Features ## Features
@ -40,9 +41,12 @@ Run Slither on a single file:
slither tests/uninitialized.sol 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 ### 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 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 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 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 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 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 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 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 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 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 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 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 For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector - 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) - `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) - `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) - `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. 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-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-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-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. 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 ## 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 ### Using Pip
@ -181,7 +189,7 @@ We recommend using a Python virtual environment, as detailed in the [Developer I
### Using Docker ### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container. Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash ```bash
docker pull trailofbits/eth-security-toolbox docker pull trailofbits/eth-security-toolbox
@ -205,11 +213,23 @@ Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation. * 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 ## 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. 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 ## Publications
### Trail of Bits publication ### 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 [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 [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) [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/). 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): def visit_node(node, visited):
if node is None:
return
if node in visited: if node in visited:
return return

@ -7,7 +7,7 @@ setup(
author="Trail of Bits", author="Trail of Bits",
version="0.0", version="0.0",
packages=find_packages(), packages=find_packages(),
python_requires=">=3.6", python_requires=">=3.8",
install_requires=["slither-analyzer==0.1"], install_requires=["slither-analyzer==0.1"],
entry_points={ entry_points={
"slither_analyzer.plugin": "slither my-plugin=slither_my_plugin:make_plugin", "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.email "ci@trailofbits.com"
git config --global user.name "CI User" git config --global user.name "CI User"
curl https://nixos.org/nix/install | sh which nix-env || exit 255
# shellcheck disable=SC1090,SC1091
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
nix-env -iA nixpkgs.cachix
cachix use dapp
git clone --recursive https://github.com/dapphub/dapptools "$HOME/.dapp/dapptools" git clone --recursive https://github.com/dapphub/dapptools "$HOME/.dapp/dapptools"
nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign
dapp init dapp init
slither . slither . --detect external-function
if [ $? -eq 21 ] # TODO: make more elaborate test
if [ $? -eq 4 ]
then then
exit 0 exit 0
fi fi
echo "Truffle test failed" echo "Dapp test failed"
exit 255 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") DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt")
if [ "$DIFF" != "" ] if [ "$DIFF" != "" ]
then then
echo "slither-check-upgradeability 1 failed" echo "slither-check-kspec 1 failed"
cat test_1.txt cat test_1.txt
echo "" echo ""
cat "$DIR_TESTS/test_1.txt" cat "$DIR_TESTS/test_1.txt"

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

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

@ -8,16 +8,27 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.", description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither", url="https://github.com/crytic/slither",
author="Trail of Bits", author="Trail of Bits",
version="0.8.2", version="0.8.3",
packages=find_packages(), packages=find_packages(),
python_requires=">=3.6", python_requires=">=3.8",
install_requires=[ install_requires=[
"prettytable>=0.7.2", "prettytable>=0.7.2",
"pysha3>=1.0.2", "pysha3>=1.0.2",
"crytic-compile>=0.2.2", # "crytic-compile>=0.2.3",
# "crytic-compile", "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", license="AGPL-3.0",
long_description=long_description, long_description=long_description,
entry_points={ entry_points={
@ -32,6 +43,7 @@ setup(
"slither-check-kspec = slither.tools.kspec_coverage.__main__:main", "slither-check-kspec = slither.tools.kspec_coverage.__main__:main",
"slither-prop = slither.tools.properties.__main__:main", "slither-prop = slither.tools.properties.__main__:main",
"slither-mutate = slither.tools.mutator.__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_detector = parser.add_argument_group("Detectors")
group_printer = parser.add_argument_group("Printers") 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_misc = parser.add_argument_group("Additional options")
group_detector.add_argument( group_detector.add_argument(
@ -312,7 +315,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_printer.add_argument( group_printer.add_argument(
"--print", "--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)}", f"available printers: {', '.join(d.ARGUMENT for d in printer_classes)}",
action="store", action="store",
dest="printers_to_run", 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"], 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( group_misc.add_argument(
"--json", "--json",
help='Export the results as a JSON file ("--json -" to export to stdout)', 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"], 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( group_misc.add_argument(
"--disable-color", "--disable-color",
help="Disable output colorization", 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) 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( parser.add_argument(
"--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False "--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False
) )
@ -656,7 +667,7 @@ def main_impl(all_detector_classes, all_printer_classes):
cp.enable() cp.enable()
# Set colorization option # 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 # Define some variables for potential JSON output
json_results = {} json_results = {}
@ -784,7 +795,7 @@ def main_impl(all_detector_classes, all_printer_classes):
if args.checklist: if args.checklist:
output_results_to_markdown(results_detectors, args.checklist_limit) 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: if number_contracts == 0:
logger.warning(red("No contract was analyzed")) logger.warning(red("No contract was analyzed"))
if printer_classes: if printer_classes:

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

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

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

@ -21,6 +21,8 @@ from slither.utils.erc import (
ERC777_signatures, ERC777_signatures,
ERC1155_signatures, ERC1155_signatures,
ERC2612_signatures, ERC2612_signatures,
ERC1363_signatures,
ERC4524_signatures,
ERC4626_signatures, ERC4626_signatures,
) )
from slither.utils.tests_pattern import is_test_contract 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"] = {} self._custom_errors: Dict[str, "CustomErrorContract"] = {}
# The only str is "*" # 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._kind: Optional[str] = None
self._is_interface: bool = False self._is_interface: bool = False
self._is_library: bool = False
self._signatures: Optional[List[str]] = None self._signatures: Optional[List[str]] = None
self._signatures_declared: 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): def is_interface(self, is_interface: bool):
self._is_interface = is_interface 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 # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -245,7 +256,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
################################################################################### ###################################################################################
@property @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 return self._using_for
# endregion # 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)] 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"]: def get_function_from_signature(self, function_signature: str) -> Optional["Function"]:
""" """
Return a function from a signature Return a function from a signature
@ -648,7 +674,11 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Function Function
""" """
return next( 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, None,
) )
@ -903,6 +933,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
("ERC721", self.is_erc721), ("ERC721", self.is_erc721),
("ERC777", self.is_erc777), ("ERC777", self.is_erc777),
("ERC2612", self.is_erc2612), ("ERC2612", self.is_erc2612),
("ERC1363", self.is_erc1363),
("ERC4626", self.is_erc4626), ("ERC4626", self.is_erc4626),
] ]
@ -998,6 +1029,26 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
full_names = self.functions_signatures full_names = self.functions_signatures
return all(s in full_names for s in ERC2612_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 @property
def is_token(self) -> bool: def is_token(self) -> bool:
""" """

@ -56,13 +56,24 @@ class CustomError(SourceMapping):
Contract and converted into address Contract and converted into address
:return: the solidity signature :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: if self._solidity_signature is None:
parameters = [ raise ValueError("Custom Error not yet built")
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature 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 # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################

@ -9,6 +9,9 @@ class Enum(SourceMapping):
self._name = name self._name = name
self._canonical_name = canonical_name self._canonical_name = canonical_name
self._values = values 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 @property
def canonical_name(self) -> str: def canonical_name(self) -> str:
@ -22,5 +25,13 @@ class Enum(SourceMapping):
def values(self) -> List[str]: def values(self) -> List[str]:
return self._values return self._values
@property
def min(self) -> int:
return self._min
@property
def max(self) -> int:
return self._max
def __str__(self): def __str__(self):
return self.name return self.name

@ -20,11 +20,11 @@ from slither.core.expressions import (
MemberAccess, MemberAccess,
UnaryOperation, UnaryOperation,
) )
from slither.core.solidity_types import UserDefinedType
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable 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 from slither.utils.utils import unroll
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines # 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) str: func_name(type1,type2)
Return the function signature without the return values 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: if self._full_name is None:
name, parameters, _ = self.signature name, parameters, _ = self.signature
@ -538,7 +540,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._nodes = nodes self._nodes = nodes
@property @property
def entry_point(self) -> "Node": def entry_point(self) -> Optional["Node"]:
""" """
Node: Entry point of the function 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 @property
def solidity_signature(self) -> str: 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: if self._solidity_signature is None:
parameters = [ 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) + ")" self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature return self._solidity_signature
@ -1724,6 +1718,6 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
################################################################################### ###################################################################################
def __str__(self): def __str__(self):
return self._name return self.name
# endregion # endregion

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

@ -104,7 +104,7 @@ class SolidityVariable(SourceMapping):
# dev function, will be removed once the code is stable # dev function, will be removed once the code is stable
def _check_name(self, name: str): # pylint: disable=no-self-use 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 @property
def state_variable(self): 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 from slither.core.source_mapping.source_mapping import SourceMapping
@ -10,7 +10,7 @@ if TYPE_CHECKING:
class Structure(SourceMapping): class Structure(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"): def __init__(self, compilation_unit: "SlitherCompilationUnit"):
super().__init__() super().__init__()
self._name = None self._name: Optional[str] = None
self._canonical_name = None self._canonical_name = None
self._elems: Dict[str, "StructureVariable"] = {} self._elems: Dict[str, "StructureVariable"] = {}
# Name of the elements in the order of declaration # 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.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel 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.core.variables.top_level_variable import TopLevelVariable
from slither.slithir.variables import Constant from slither.slithir.variables import Constant
@ -39,6 +40,15 @@ class FileScope:
self.structures: Dict[str, StructureTopLevel] = {} self.structures: Dict[str, StructureTopLevel] = {}
self.variables: Dict[str, TopLevelVariable] = {} 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: def add_accesible_scopes(self) -> bool:
""" """
Add information from accessible scopes. Return true if new information was obtained 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): if not _dict_contain(new_scope.variables, self.variables):
self.variables.update(new_scope.variables) self.variables.update(new_scope.variables)
learn_something = True 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 return learn_something

@ -4,6 +4,8 @@
import json import json
import logging import logging
import os import os
import pathlib
import posixpath
import re import re
from collections import defaultdict from collections import defaultdict
from typing import Optional, Dict, List, Set, Union from typing import Optional, Dict, List, Set, Union
@ -52,7 +54,7 @@ class SlitherCore(Context):
self._previous_results_ids: Set[str] = set() self._previous_results_ids: Set[str] = set()
# Every slither object has a list of result from detector # Every slither object has a list of result from detector
# Because of the multiple compilation support, we might analyze # 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._currently_seen_resuts: Set[str] = set()
self._paths_to_filter: Set[str] = set() self._paths_to_filter: Set[str] = set()
@ -290,7 +292,7 @@ class SlitherCore(Context):
return False return False
mapping_elements_with_lines = ( mapping_elements_with_lines = (
( (
os.path.normpath(elem["source_mapping"]["filename_absolute"]), posixpath.normpath(elem["source_mapping"]["filename_absolute"]),
elem["source_mapping"]["lines"], elem["source_mapping"]["lines"],
) )
for elem in r["elements"] for elem in r["elements"]
@ -325,7 +327,7 @@ class SlitherCore(Context):
- There is an ignore comment on the preceding line - 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: if r["id"] in self._currently_seen_resuts:
return False return False
self._currently_seen_resuts.add(r["id"]) self._currently_seen_resuts.add(r["id"])
@ -335,8 +337,12 @@ class SlitherCore(Context):
for elem in r["elements"] for elem in r["elements"]
if "source_mapping" in elem 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 matching = False
@ -357,16 +363,21 @@ class SlitherCore(Context):
if r["elements"] and matching: if r["elements"] and matching:
return False 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: if self._show_ignored_findings:
return True return True
if r["id"] in self._previous_results_ids:
return False
if self.has_ignore_comment(r): if self.has_ignore_comment(r):
return False return False
if r["id"] in self._previous_results_ids:
return False
if r["elements"] and self._exclude_dependencies:
if all(element["source_mapping"]["is_dependency"] for element in r["elements"]):
return False
# Conserve previous result filtering. This is conserved for compatibility, but is meant to be removed # 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): def load_previous_results(self):
filename = self._previous_results_filename filename = self._previous_results_filename

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

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

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

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

@ -23,6 +23,10 @@ class MappingType(Type):
def storage_size(self) -> Tuple[int, bool]: def storage_size(self) -> Tuple[int, bool]:
return 32, True return 32, True
@property
def is_dynamic(self) -> bool:
return True
def __str__(self): def __str__(self):
return f"mapping({str(self._from)} => {str(self._to)})" 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 :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 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): def __init__(self, c):
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from slither.core.declarations.contract import Contract 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__() super().__init__()
self._type = c self._type = c
@ -34,6 +35,10 @@ class TypeInformation(Type):
""" """
return 32, True return 32, True
@property
def is_dynamic(self) -> bool:
raise NotImplementedError
def __str__(self): def __str__(self):
return f"type({self.type.name})" return f"type({self.type.name})"

@ -20,6 +20,10 @@ class UserDefinedType(Type):
super().__init__() super().__init__()
self._type = t self._type = t
@property
def is_dynamic(self) -> bool:
return False
@property @property
def type(self) -> Union["Contract", "Enum", "Structure"]: def type(self) -> Union["Contract", "Enum", "Structure"]:
return self._type 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.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: if TYPE_CHECKING:
from slither.core.cfg.node import Node from slither.core.cfg.node import Node
@ -22,33 +21,6 @@ class StateVariable(ChildContract, Variable):
""" """
return self.contract == contract 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 # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################

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

@ -6,7 +6,9 @@ from .variables.uninitialized_local_variables import UninitializedLocalVars
from .attributes.constant_pragma import ConstantPragma from .attributes.constant_pragma import ConstantPragma
from .attributes.incorrect_solc import IncorrectSolc from .attributes.incorrect_solc import IncorrectSolc
from .attributes.locked_ether import LockedEther 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.suicidal import Suicidal
# from .functions.complex_function import ComplexFunction # from .functions.complex_function import ComplexFunction
@ -34,7 +36,7 @@ from .shadowing.builtin_symbols import BuiltinSymbolShadowing
from .operations.block_timestamp import Timestamp from .operations.block_timestamp import Timestamp
from .statements.calls_in_loop import MultipleCallsInLoop from .statements.calls_in_loop import MultipleCallsInLoop
from .statements.incorrect_strict_equality import IncorrectStrictEquality 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.incorrect_erc721_interface import IncorrectERC721InterfaceDetection
from .erc.unindexed_event_parameters import UnindexedERC20EventParameters from .erc.unindexed_event_parameters import UnindexedERC20EventParameters
from .statements.deprecated_calls import DeprecatedStandards 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.write_after_write import WriteAfterWrite
from .statements.msg_value_in_loop import MsgValueInLoop from .statements.msg_value_in_loop import MsgValueInLoop
from .statements.delegatecall_in_loop import DelegatecallInLoop from .statements.delegatecall_in_loop import DelegatecallInLoop
from .functions.protected_variable import ProtectedVariables

@ -13,7 +13,9 @@ class ShiftParameterMixup(AbstractDetector):
IMPACT = DetectorClassification.HIGH IMPACT = DetectorClassification.HIGH
CONFIDENCE = 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_TITLE = "Incorrect shift in assembly."
WIKI_DESCRIPTION = "Detect if the values in a shift operation are reversed" WIKI_DESCRIPTION = "Detect if the values in a shift operation are reversed"

@ -29,7 +29,7 @@ class ConstantPragma(AbstractDetector):
versions = sorted(list(set(versions))) versions = sorted(list(set(versions)))
if len(versions) > 1: 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"] info += [f"\t- Version used: {[str(v) for v in versions]}\n"]
for p in pragma: for p in pragma:

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

@ -87,7 +87,7 @@ contract Something {
if not intended_interface_is_subset_parent: if not intended_interface_is_subset_parent:
# Should not be a subset of an earlier determined intended_interface or derive from it # Should not be a subset of an earlier determined intended_interface or derive from it
intended_interface_is_subset_intended = False intended_interface_is_subset_intended = False
for intended_interface in intended_interfaces: for intended_interface in list(intended_interfaces):
sigs_intended_interface = { sigs_intended_interface = {
f.full_name for f in intended_interface.functions_entry_points 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 return ret
class ArbitrarySend(AbstractDetector): class ArbitrarySendEth(AbstractDetector):
ARGUMENT = "arbitrary-send" ARGUMENT = "arbitrary-send-eth"
HELP = "Functions that send Ether to arbitrary destinations" HELP = "Functions that send Ether to arbitrary destinations"
IMPACT = DetectorClassification.HIGH IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM
@ -104,7 +104,7 @@ class ArbitrarySend(AbstractDetector):
# region wiki_exploit_scenario # region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """ WIKI_EXPLOIT_SCENARIO = """
```solidity ```solidity
contract ArbitrarySend{ contract ArbitrarySendEth{
address destination; address destination;
function setDestination(){ function setDestination(){
destination = msg.sender; 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: if func.is_constructor:
continue continue
if not self.is_mixed_case(func.name): if not self.is_mixed_case(func.name):
if ( if func.visibility in [
func.visibility "internal",
in [ "private",
"internal", ] and self.is_mixed_case_with_underscore(func.name):
"private",
]
and self.is_mixed_case_with_underscore(func.name)
):
continue continue
if func.name.startswith("echidna_") or func.name.startswith("crytic_"): if func.name.startswith(("echidna_", "crytic_")):
continue continue
info = ["Function ", func, " is not in mixedCase\n"] 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.core.variables.state_variable import StateVariable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output, AllSupportedOutput from slither.utils.output import Output, AllSupportedOutput
from .common import is_upgradable_gap_variable
def detect_shadowing(contract: Contract) -> List[List[StateVariable]]: def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
@ -19,6 +20,9 @@ def detect_shadowing(contract: Contract) -> List[List[StateVariable]]:
var: StateVariable var: StateVariable
for var in contract.state_variables_declared: 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] shadow: List[StateVariable] = [v for v in variables_fathers if v.name == var.name]
if shadow: if shadow:
ret.append([var] + 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.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 = [] ret = []
variables_fathers = [] variables_fathers = []
for father in contract.inheritance: for father in contract.inheritance:
@ -13,6 +15,10 @@ def detect_shadowing(contract):
variables_fathers += father.state_variables_declared variables_fathers += father.state_variables_declared
for var in contract.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] shadow = [v for v in variables_fathers if v.name == var.name]
if shadow: if shadow:
ret.append([var] + shadow) ret.append([var] + shadow)

@ -1,7 +1,7 @@
import re import re
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
# pylint: disable=bidirectional-unicode
class RightToLeftOverride(AbstractDetector): class RightToLeftOverride(AbstractDetector):
""" """
Detect the usage of a Right-To-Left-Override (U+202E) character 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.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract from slither.core.declarations import Contract
@ -22,7 +22,11 @@ def detect_call_in_loop(contract: Contract) -> List[Node]:
return ret 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: if node in visited:
return return
# shared visited # 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.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract 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( 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: ) -> None:
if node is None:
return
if node in visited: if node in visited:
return return
# shared visited # 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.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import LowLevelCall, InternalCall from slither.slithir.operations import LowLevelCall, InternalCall
@ -15,8 +15,12 @@ def detect_delegatecall_in_loop(contract: Contract) -> List[Node]:
def delegatecall_in_loop( 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: ) -> None:
if node is None:
return
if node in visited: if node in visited:
return return
# shared visited # 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.core.cfg.node import NodeType, Node
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import InternalCall 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( 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: ) -> None:
if node is None:
return
if node in visited: if node in visited:
return return
# shared visited # shared visited

@ -2,9 +2,19 @@
Module detecting numbers with too many digits. Module detecting numbers with too many digits.
""" """
import re
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.variables import Constant 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): class TooManyDigits(AbstractDetector):
""" """
@ -58,7 +68,7 @@ Use:
if isinstance(read, Constant): if isinstance(read, Constant):
# read.value can return an int or a str. Convert it to str # read.value can return an int or a str. Convert it to str
value_as_str = read.original_value 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 # Info to be printed
ret.append(node) ret.append(node)
return ret return ret

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

@ -1,11 +1,12 @@
from typing import List from typing import List
from slither.core.declarations import SolidityFunction, Function from slither.core.declarations import SolidityFunction, Function
from slither.core.declarations.contract import Contract
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import LowLevelCall, SolidityCall from slither.slithir.operations import LowLevelCall, SolidityCall
def _can_be_destroyed(contract) -> List[Function]: def _can_be_destroyed(contract: Contract) -> List[Function]:
targets = [] targets = []
for f in contract.functions_entry_points: for f in contract.functions_entry_points:
for ir in f.all_slithir_operations(): for ir in f.all_slithir_operations():
@ -29,6 +30,18 @@ def _has_initializer_modifier(functions: List[Function]) -> bool:
return False 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): class UnprotectedUpgradeable(AbstractDetector):
ARGUMENT = "unprotected-upgrade" ARGUMENT = "unprotected-upgrade"
@ -72,12 +85,10 @@ class UnprotectedUpgradeable(AbstractDetector):
if not _has_initializer_modifier(contract.constructors): if not _has_initializer_modifier(contract.constructors):
functions_that_can_destroy = _can_be_destroyed(contract) functions_that_can_destroy = _can_be_destroyed(contract)
if functions_that_can_destroy: if functions_that_can_destroy:
initiliaze_functions = [ initialize_functions = _initialize_functions(contract)
f for f in contract.functions if f.name == "initialize"
]
vars_init_ = [ 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] vars_init = [item for sublist in vars_init_ for item in sublist]
@ -91,9 +102,9 @@ class UnprotectedUpgradeable(AbstractDetector):
info = ( info = (
[ [
contract, 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: ", ". Anyone can delete the contract with: ",
] ]

@ -17,7 +17,9 @@ class SimilarVarsDetection(AbstractDetector):
IMPACT = DetectorClassification.INFORMATIONAL IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.MEDIUM 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_TITLE = "Variable names too similar"
WIKI_DESCRIPTION = "Detect variables with names that are 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 contract in self.compilation_unit.contracts:
for function in contract.functions: 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: if function.contains_assembly:
continue continue
# dont consider storage variable, as they are detected by another detector # 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 contract in self.compilation_unit.contracts:
for function in contract.functions: for function in contract.functions:
if function.is_implemented: if function.is_implemented and function.entry_point:
uninitialized_storage_variables = [ uninitialized_storage_variables = [
v for v in function.local_variables if v.is_storage and v.uninitialized 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"] element["type_specific_fields"]["parent"]["name"]
) )
if target_contract: if target_contract:
function = target_contract.get_function_from_signature( function = target_contract.get_function_from_full_name(
element["type_specific_fields"]["signature"] element["type_specific_fields"]["signature"]
) )
if function: if function:

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

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

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

@ -257,7 +257,7 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
] ]
param_name = element["name"] param_name = element["name"]
contract = scope.get_contract_from_name(contract_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) target = function.get_local_variable_from_name(param_name)
elif _target in ["variable", "variable_constant"]: elif _target in ["variable", "variable_constant"]:
@ -271,7 +271,7 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
] ]
var_name = element["name"] var_name = element["name"]
contract = scope.get_contract_from_name(contract_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) target = function.get_local_variable_from_name(var_name)
# State variable # State variable
else: else:
@ -303,10 +303,10 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
# group 2: beginning of the to type # group 2: beginning of the to type
# nested mapping are within the group 1 # nested mapping are within the group 1
# RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)' # RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)'
RE_MAPPING_FROM = b"([a-zA-Z0-9\._\[\]]*)" RE_MAPPING_FROM = rb"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = b"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)" RE_MAPPING_TO = rb"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING = ( 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 .summary.constructor_calls import ConstructorPrinter
from .guidance.echidna import Echidna from .guidance.echidna import Echidna
from .summary.evm import PrinterEVM from .summary.evm import PrinterEVM
from .summary.when_not_paused import PrinterWhenNotPaused
from .summary.declaration import Declaration from .summary.declaration import Declaration

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

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

@ -28,7 +28,7 @@ class VariableOrder(AbstractPrinter):
txt += f"\n{contract.name}:\n" txt += f"\n{contract.name}:\n"
table = MyPrettyTable(["Name", "Type", "Slot", "Offset"]) table = MyPrettyTable(["Name", "Type", "Slot", "Offset"])
for variable in contract.state_variables_ordered: 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) slot, offset = contract.compilation_unit.storage_layout_of(contract, variable)
table.add_row([variable.canonical_name, str(variable.type), slot, offset]) 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: Keyword Args:
solc (str): solc binary location (default 'solc') solc (str): solc binary location (default 'solc')
disable_solc_warnings (bool): True to disable solc warnings (default false) 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') ast_format (str): ast format (default '--ast-compact-json')
filter_paths (list(str)): list of path to filter (default []) filter_paths (list(str)): list of path to filter (default [])
triage_mode (bool): if true, switch to triage mode (default false) triage_mode (bool): if true, switch to triage mode (default false)

@ -33,6 +33,7 @@ from slither.core.solidity_types.elementary_type import (
MaxValues, MaxValues,
) )
from slither.core.solidity_types.type import Type 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.function_type_variable import FunctionTypeVariable
from slither.core.variables.state_variable import StateVariable from slither.core.variables.state_variable import StateVariable
from slither.core.variables.variable import Variable from slither.core.variables.variable import Variable
@ -170,10 +171,10 @@ def _fits_under_integer(val: int, can_be_int: bool, can_be_uint) -> List[str]:
assert can_be_int | can_be_uint assert can_be_int | can_be_uint
while n <= 256: while n <= 256:
if can_be_uint: if can_be_uint:
if val <= 2 ** n - 1: if val <= 2**n - 1:
ret.append(f"uint{n}") ret.append(f"uint{n}")
if can_be_int: if can_be_int:
if val <= (2 ** n) / 2 - 1: if val <= (2**n) / 2 - 1:
ret.append(f"int{n}") ret.append(f"int{n}")
n = n + 8 n = n + 8
return ret return ret
@ -196,7 +197,7 @@ def _fits_under_byte(val: Union[int, str]) -> List[str]:
return [f"bytes{size}"] return [f"bytes{size}"]
# val is a str # val is a str
length = len(val.encode("utf-8")) 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]: 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 ins = new_ins
result[idx] = 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): if isinstance(ins, Argument):
# In case of dupplicate arguments we overwrite the value # In case of dupplicate arguments we overwrite the value
# This can happen because of addr.call.value(1).value(2) # 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) return convert_type_of_high_and_internal_level_call(ir, contract)
# Convert HighLevelCall to LowLevelCall # 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. # Cannot be a top level function with this.
assert isinstance(node_function, FunctionContract) assert isinstance(node_function, FunctionContract)
if ir.destination.name == "this": if ir.destination.name == "this":
@ -817,12 +827,28 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
# lib L { event E()} # lib L { event E()}
# ... # ...
# emit L.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 = EventCall(ins.ori.variable_right)
eventcall.set_expression(ins.expression) eventcall.set_expression(ins.expression)
eventcall.call_id = ins.call_id eventcall.call_id = ins.call_id
return eventcall 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( libcall = LibraryCall(
ins.ori.variable_left, ins.ori.variable_left,
ins.ori.variable_right, ins.ori.variable_right,
@ -1369,7 +1395,11 @@ def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract):
if len(candidates) == 1: if len(candidates) == 1:
func = candidates[0] 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 # TODO: handle collision with multiple state variables/functions
func = lib_contract.get_state_variable_from_name(ir.function_name) func = lib_contract.get_state_variable_from_name(ir.function_name)
if func is None and candidates: 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: for import_statement in contract.file_scope.imports:
if import_statement.alias and import_statement.alias == ir.contract_name: if import_statement.alias and import_statement.alias == ir.contract_name:
imported_scope = contract.compilation_unit.get_scope(import_statement.filename) 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) func = _find_function_from_parameter(ir, candidates)
@ -1653,7 +1687,10 @@ def convert_constant_types(irs):
if isinstance(ir.rvalue, TupleVariable): if isinstance(ir.rvalue, TupleVariable):
# TODO: fix missing Unpack conversion # TODO: fix missing Unpack conversion
continue 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)) ir.rvalue.set_type(ElementaryType(ir.lvalue.type.type))
was_changed = True was_changed = True
if isinstance(ir, Binary): if isinstance(ir, Binary):
@ -1672,6 +1709,7 @@ def convert_constant_types(irs):
# TODO: add POP instruction # TODO: add POP instruction
break break
types = [p.type for p in func.parameters] types = [p.type for p in func.parameters]
assert len(types) == len(ir.arguments)
for idx, arg in enumerate(ir.arguments): for idx, arg in enumerate(ir.arguments):
t = types[idx] t = types[idx]
if isinstance(t, ElementaryType): if isinstance(t, ElementaryType):

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

@ -17,6 +17,8 @@ class LibraryCall(HighLevelCall):
Must be called after slithIR analysis pass Must be called after slithIR analysis pass
:return: bool :return: bool
""" """
if self.is_static_call():
return False
# In case of recursion, return False # In case of recursion, return False
callstack = [] if callstack is None else callstack callstack = [] if callstack is None else callstack
if self.function in 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 Must be called after slithIR analysis pass
:return: bool :return: bool
""" """
if self.function_name == "staticcall":
return False
return True return True
def can_send_eth(self): 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.contract import Contract
from slither.core.declarations.custom_error_contract import CustomErrorContract from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.declarations.function_contract import FunctionContract 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.core.variables.state_variable import StateVariable
from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.declarations.caller_context import CallerContextExpression
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
@ -156,7 +157,10 @@ class ContractSolc(CallerContextExpression):
if "contractKind" in attributes: if "contractKind" in attributes:
if attributes["contractKind"] == "interface": if attributes["contractKind"] == "interface":
self._contract.is_interface = True 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._linearized_base_contracts = attributes["linearizedBaseContracts"]
# self._contract.fullyImplemented = attributes["fullyImplemented"] # self._contract.fullyImplemented = attributes["fullyImplemented"]
@ -230,6 +234,7 @@ class ContractSolc(CallerContextExpression):
self.baseConstructorContractsCalled.append(referencedDeclaration) self.baseConstructorContractsCalled.append(referencedDeclaration)
def _parse_contract_items(self): def _parse_contract_items(self):
# pylint: disable=too-many-branches
if not self.get_children() in self._data: # empty contract if not self.get_children() in self._data: # empty contract
return return
for item in self._data[self.get_children()]: for item in self._data[self.get_children()]:
@ -253,10 +258,34 @@ class ContractSolc(CallerContextExpression):
self._usingForNotParsed.append(item) self._usingForNotParsed.append(item)
elif item[self.get_key()] == "ErrorDefinition": elif item[self.get_key()] == "ErrorDefinition":
self._customErrorParsed.append(item) self._customErrorParsed.append(item)
elif item[self.get_key()] == "UserDefinedValueTypeDefinition":
self._parse_type_alias(item)
else: else:
raise ParsingError("Unknown contract item: " + item[self.get_key()]) raise ParsingError("Unknown contract item: " + item[self.get_key()])
return 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): def _parse_struct(self, struct: Dict):
st = StructureContract(self._contract.compilation_unit) st = StructureContract(self._contract.compilation_unit)

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

@ -448,7 +448,7 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
t = expression["attributes"]["type"] t = expression["attributes"]["type"]
if t: 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 assert len(found) <= 1
if found: if found:
value = value + "(" + found[0] + ")" value = value + "(" + found[0] + ")"
@ -458,7 +458,6 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
referenced_declaration = expression["referencedDeclaration"] referenced_declaration = expression["referencedDeclaration"]
else: else:
referenced_declaration = None referenced_declaration = None
var, was_created = find_variable(value, caller_context, referenced_declaration) var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created: if was_created:
var.set_offset(src, caller_context.compilation_unit) var.set_offset(src, caller_context.compilation_unit)

@ -18,6 +18,7 @@ from slither.core.solidity_types import (
ArrayType, ArrayType,
FunctionType, FunctionType,
MappingType, MappingType,
TypeAlias,
) )
from slither.core.variables.top_level_variable import TopLevelVariable from slither.core.variables.top_level_variable import TopLevelVariable
from slither.core.variables.variable import Variable from slither.core.variables.variable import Variable
@ -125,13 +126,27 @@ def _find_top_level(
new_val = SolidityImportPlaceHolder(import_directive) new_val = SolidityImportPlaceHolder(import_directive)
return new_val, True return new_val, True
for custom_error in scope.custom_errors:
if custom_error.solidity_signature == var_name:
return custom_error, False
if var_name in scope.variables: if var_name in scope.variables:
return scope.variables[var_name], False return scope.variables[var_name], False
# This path should be reached only after the top level custom error have been parsed
# If not, slither will crash
# It does not seem to be reacheable, but if so, we will have to adapt the order of logic
# This must be at the end, because other top level objects might require to go over "_find_top_level"
# Before the parsing of the top level custom error
# For example, a top variable that use another top level variable
# IF more top level objects are added to Solidity, we have to be careful with the order of the lookup
# in this function
try:
for custom_error in scope.custom_errors:
if custom_error.solidity_signature == var_name:
return custom_error, False
except ValueError:
# This can happen as custom error sol signature might not have been built
# when find_variable was called
# TODO refactor find_variable to prevent this from happening
pass
return None, False 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 # 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 # As a result, we need to iterate over all the custom errors here instead of using the dict
custom_errors = contract.custom_errors custom_errors = contract.custom_errors
for custom_error in custom_errors: try:
if var_name == custom_error.solidity_signature: for custom_error in custom_errors:
return custom_error 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 # If the enum is refered as its name rather than its canonicalName
enums = {e.name: e for e in contract.enums} enums = {e.name: e for e in contract.enums}
@ -284,6 +305,7 @@ def find_variable(
Enum, Enum,
Structure, Structure,
CustomError, CustomError,
TypeAlias,
], ],
bool, bool,
]: ]:
@ -326,6 +348,12 @@ def find_variable(
# Because functions are copied between contracts, two functions can have the same ref # 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 # 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 # Use ret0/ret1 to help mypy
ret0 = _find_variable_from_ref_declaration( ret0 = _find_variable_from_ref_declaration(
referenced_declaration, direct_contracts, direct_functions 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.import_directive import Import
from slither.core.declarations.pragma_directive import Pragma from slither.core.declarations.pragma_directive import Pragma
from slither.core.declarations.structure_top_level import StructureTopLevel 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.core.variables.top_level_variable import TopLevelVariable
from slither.exceptions import SlitherException from slither.exceptions import SlitherException
from slither.solc_parsing.declarations.contract import ContractSolc from slither.solc_parsing.declarations.contract import ContractSolc
@ -28,6 +30,33 @@ logger = logging.getLogger("SlitherSolcParsing")
logger.setLevel(logging.INFO) 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: class SlitherCompilationUnitSolc:
# pylint: disable=no-self-use,too-many-instance-attributes # pylint: disable=no-self-use,too-many-instance-attributes
def __init__(self, compilation_unit: SlitherCompilationUnit): def __init__(self, compilation_unit: SlitherCompilationUnit):
@ -204,6 +233,9 @@ class SlitherCompilationUnitSolc:
# TODO investigate unitAlias in version < 0.7 and legacy ast # TODO investigate unitAlias in version < 0.7 and legacy ast
if "unitAlias" in top_level_data: if "unitAlias" in top_level_data:
import_directive.alias = top_level_data["unitAlias"] 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: else:
import_directive = Import( import_directive = Import(
Path( Path(
@ -267,6 +299,23 @@ class SlitherCompilationUnitSolc:
self._compilation_unit.custom_errors.append(custom_error) self._compilation_unit.custom_errors.append(custom_error)
self._custom_error_parser.append(custom_error_parser) 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: else:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported") 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 = [] father_constructors = []
# try: # try:
# Resolve linearized base contracts. # Resolve linearized base contracts.
missing_inheritance = False missing_inheritance = None
for i in contract_parser.linearized_base_contracts[1:]: for i in contract_parser.linearized_base_contracts[1:]:
if i in contract_parser.remapping: if i in contract_parser.remapping:
ancestors.append( contract_name = contract_parser.remapping[i]
contract_parser.underlying_contract.file_scope.get_contract_from_name( if contract_name in contract_parser.underlying_contract.file_scope.renaming:
contract_parser.remapping[i] contract_name = contract_parser.underlying_contract.file_scope.renaming[
) contract_name
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i]) ]
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: elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i]) ancestors.append(self._contracts_by_id[i])
else: else:
missing_inheritance = True missing_inheritance = i
# Resolve immediate base contracts # Resolve immediate base contracts
for i in contract_parser.baseContracts: 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: elif i in self._contracts_by_id:
fathers.append(self._contracts_by_id[i]) fathers.append(self._contracts_by_id[i])
else: else:
missing_inheritance = True missing_inheritance = i
# Resolve immediate base constructor calls # Resolve immediate base constructor calls
for i in contract_parser.baseConstructorContractsCalled: 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: elif i in self._contracts_by_id:
father_constructors.append(self._contracts_by_id[i]) father_constructors.append(self._contracts_by_id[i])
else: else:
missing_inheritance = True missing_inheritance = i
contract_parser.underlying_contract.set_inheritance( contract_parser.underlying_contract.set_inheritance(
ancestors, fathers, father_constructors 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( self._compilation_unit.contracts_with_missing_inheritance.add(
contract_parser.underlying_contract 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.set_is_analyzed(True)
contract_parser.delete_content() 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: for func in contract.functions + contract.modifiers:
try: try:
func.generate_slithir_and_analyze() func.generate_slithir_and_analyze()
except AttributeError: except AttributeError as e:
# This can happens for example if there is a call to an interface # This can happens for example if there is a call to an interface
# And the interface is redefined due to contract's name reuse # And the interface is redefined due to contract's name reuse
# But the available version misses some functions # But the available version misses some functions
self._underlying_contract_to_parser[contract].log_incorrect_parsing( 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() 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.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.function_contract import FunctionContract from slither.core.declarations.function_contract import FunctionContract
from slither.core.expressions.literal import Literal 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.array_type import ArrayType
from slither.core.solidity_types.elementary_type import ( from slither.core.solidity_types.elementary_type import (
ElementaryType, ElementaryType,
@ -111,7 +112,7 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
if not var_type: if not var_type:
if name.startswith("function "): if name.startswith("function "):
found = re.findall( 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, name,
) )
assert len(found) == 1 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("): if name.startswith("mapping("):
# nested mapping declared with var # nested mapping declared with var
if name.count("mapping(") == 1: 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: else:
found = re.findall( 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, name,
) )
assert len(found) == 1 assert len(found) == 1
@ -204,7 +205,7 @@ def _add_type_references(type_found: Type, src: str, sl: "SlitherCompilationUnit
def parse_type( def parse_type(
t: Union[Dict, UnknownType], t: Union[Dict, UnknownType],
caller_context: Union[CallerContextExpression, "SlitherCompilationUnitSolc"], caller_context: Union[CallerContextExpression, "SlitherCompilationUnitSolc"],
): ) -> Type:
""" """
caller_context can be a SlitherCompilationUnitSolc because we recursively call the function 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 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 from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
sl: "SlitherCompilationUnit" 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 # Note: for convenicence top level functions use the same parser than function in contract
# but contract_parser is set to None # but contract_parser is set to None
if isinstance(caller_context, SlitherCompilationUnitSolc) or ( if isinstance(caller_context, SlitherCompilationUnitSolc) or (
@ -238,10 +241,14 @@ def parse_type(
if isinstance(caller_context, SlitherCompilationUnitSolc): if isinstance(caller_context, SlitherCompilationUnitSolc):
sl = caller_context.compilation_unit sl = caller_context.compilation_unit
next_context = caller_context next_context = caller_context
renaming = {}
user_defined_types = {}
else: else:
assert isinstance(caller_context, FunctionSolc) assert isinstance(caller_context, FunctionSolc)
sl = caller_context.underlying_function.compilation_unit sl = caller_context.underlying_function.compilation_unit
next_context = caller_context.slither_parser 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 structures_direct_access = sl.structures_top_level
all_structuress = [c.structures for c in sl.contracts] all_structuress = [c.structures for c in sl.contracts]
all_structures = [item for sublist in all_structuress for item in sublist] 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_structuress = [c.structures for c in scope.contracts.values()]
all_structures = [item for sublist in all_structuress for item in sublist] all_structures = [item for sublist in all_structuress for item in sublist]
all_structures += structures_direct_access all_structures += structures_direct_access
enums_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() contracts = scope.contracts.values()
functions = list(scope.functions) functions = list(scope.functions)
renaming = scope.renaming
user_defined_types = scope.user_defined_types
elif isinstance(caller_context, (ContractSolc, FunctionSolc)): elif isinstance(caller_context, (ContractSolc, FunctionSolc)):
sl = caller_context.compilation_unit sl = caller_context.compilation_unit
if isinstance(caller_context, FunctionSolc): if isinstance(caller_context, FunctionSolc):
@ -285,9 +299,11 @@ def parse_type(
assert isinstance(underlying_func, FunctionContract) assert isinstance(underlying_func, FunctionContract)
contract = underlying_func.contract contract = underlying_func.contract
next_context = caller_context.contract_parser next_context = caller_context.contract_parser
scope = caller_context.underlying_function.file_scope
else: else:
contract = caller_context.underlying_contract contract = caller_context.underlying_contract
next_context = caller_context next_context = caller_context
scope = caller_context.underlying_contract.file_scope
structures_direct_access = contract.structures structures_direct_access = contract.structures
structures_direct_access += contract.file_scope.structures.values() structures_direct_access += contract.file_scope.structures.values()
@ -301,6 +317,9 @@ def parse_type(
all_enums += contract.file_scope.enums.values() all_enums += contract.file_scope.enums.values()
contracts = contract.file_scope.contracts.values() contracts = contract.file_scope.contracts.values()
functions = contract.functions + contract.modifiers functions = contract.functions + contract.modifiers
renaming = scope.renaming
user_defined_types = scope.user_defined_types
else: else:
raise ParsingError(f"Incorrect caller context: {type(caller_context)}") raise ParsingError(f"Incorrect caller context: {type(caller_context)}")
@ -311,8 +330,13 @@ def parse_type(
key = "name" key = "name"
if isinstance(t, UnknownType): 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( return _find_from_type_name(
t.name, name,
functions, functions,
contracts, contracts,
structures_direct_access, structures_direct_access,
@ -328,8 +352,13 @@ def parse_type(
if t[key] == "UserDefinedTypeName": if t[key] == "UserDefinedTypeName":
if is_compact_ast: 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( type_found = _find_from_type_name(
t["typeDescriptions"]["typeString"], name,
functions, functions,
contracts, contracts,
structures_direct_access, 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'). # 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 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( type_found = _find_from_type_name(
t["attributes"][type_name_key], name,
functions, functions,
contracts, contracts,
structures_direct_access, structures_direct_access,
@ -357,8 +392,13 @@ def parse_type(
# Introduced with Solidity 0.8 # Introduced with Solidity 0.8
if t[key] == "IdentifierPath": if t[key] == "IdentifierPath":
if is_compact_ast: if is_compact_ast:
type_found = _find_from_type_name( name = t["name"]
t["name"], if name in renaming:
name = renaming[name]
if name in user_defined_types:
return user_defined_types[name]
type_found = _find_from_type_name(
name,
functions, functions,
contracts, contracts,
structures_direct_access, structures_direct_access,

@ -1,4 +1,5 @@
import logging import logging
import re
from typing import Dict from typing import Dict
from slither.solc_parsing.declarations.caller_context import CallerContextExpression from slither.solc_parsing.declarations.caller_context import CallerContextExpression
@ -103,6 +104,23 @@ class VariableDeclarationSolc:
""" """
return self._reference_id 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): def _analyze_variable_attributes(self, attributes: Dict):
if "visibility" in attributes: if "visibility" in attributes:
self._variable.visibility = attributes["visibility"] self._variable.visibility = attributes["visibility"]
@ -145,6 +163,8 @@ class VariableDeclarationSolc:
if attributes["mutability"] == "immutable": if attributes["mutability"] == "immutable":
self._variable.is_immutable = True self._variable.is_immutable = True
self._handle_comment(attributes)
self._analyze_variable_attributes(attributes) self._analyze_variable_attributes(attributes)
if self._is_compact_ast: 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"] __slots__ = ["_entrypoint", "_parent_func", "_nodes", "node_scope"]
def __init__( 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 # Currently SlithIR doesnt support raw access to memory
# So things like .offset/.slot will return the variable # So things like .offset/.slot will return the variable
# Instaed of the actual offset/slot # Instaed of the actual offset/slot
if name.endswith("_slot") or name.endswith(".slot"): if name.endswith(("_slot", ".slot")):
potential_name = name[:-5] potential_name = name[:-5]
variable_found = _check_for_state_variable_name(root, potential_name) variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found: 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) var = root.function.get_local_variable_from_name(potential_name)
if var and var.is_storage: if var and var.is_storage:
return Identifier(var) return Identifier(var)
if name.endswith("_offset") or name.endswith(".offset"): if name.endswith(("_offset", ".offset")):
potential_name = name[:-7] potential_name = name[:-7]
variable_found = _check_for_state_variable_name(root, potential_name) variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found: if variable_found:

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

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

@ -1,17 +1,21 @@
import logging import logging
import re import re
import uuid
from collections import namedtuple from collections import namedtuple
from enum import Enum as PythonEnum from enum import Enum as PythonEnum
from pathlib import Path from pathlib import Path
from typing import List, Set, Dict, Optional 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 import SolidityFunction, EnumContract, StructureContract
from slither.core.declarations.contract import Contract 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 import MappingType, ArrayType
from slither.core.solidity_types.type import Type
from slither.core.solidity_types.user_defined_type import UserDefinedType from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.exceptions import SlitherException 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 ( from slither.tools.flattening.export.export import (
Export, Export,
export_as_json, 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 # pylint: disable=too-many-instance-attributes,too-many-arguments,too-many-locals,too-few-public-methods
def __init__( def __init__(
self, self,
slither: SlitherCore, compilation_unit: SlitherCompilationUnit,
external_to_public=False, external_to_public=False,
remove_assert=False, remove_assert=False,
convert_library_to_internal=False,
private_to_internal=False, private_to_internal=False,
export_path: Optional[str] = None, export_path: Optional[str] = None,
pragma_solidity: Optional[str] = None, pragma_solidity: Optional[str] = None,
): ):
self._source_codes: Dict[Contract, str] = {} 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._external_to_public = external_to_public
self._remove_assert = remove_assert self._remove_assert = remove_assert
self._use_abi_encoder_v2 = False self._use_abi_encoder_v2 = False
self._convert_library_to_internal = convert_library_to_internal
self._private_to_internal = private_to_internal self._private_to_internal = private_to_internal
self._pragma_solidity = pragma_solidity self._pragma_solidity = pragma_solidity
@ -63,20 +70,32 @@ class Flattening:
self._check_abi_encoder_v2() 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(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): def _check_abi_encoder_v2(self):
""" """
Check if ABIEncoderV2 is required Check if ABIEncoderV2 is required
Set _use_abi_encorder_v2 Set _use_abi_encorder_v2
:return: :return:
""" """
for compilation_unit in self._slither.compilation_units: for p in self._compilation_unit.pragma_directives:
for p in compilation_unit.pragma_directives: if "ABIEncoderV2" in str(p.directive):
if "ABIEncoderV2" in str(p.directive): self._use_abi_encoder_v2 = True
self._use_abi_encoder_v2 = True return
return
def _get_source_code( def _get_source_code(
self, contract: Contract self, contract: Contract
@ -88,13 +107,13 @@ class Flattening:
:return: :return:
""" """
src_mapping = contract.source_mapping 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 start = src_mapping.start
end = src_mapping.start + src_mapping.length end = src_mapping.start + src_mapping.length
to_patch = [] to_patch = []
# interface must use external # 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: for f in contract.functions_declared:
# fallback must be external # fallback must be external
if f.is_fallback or f.is_constructor_variables: 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: if self._private_to_internal:
for variable in contract.state_variables_declared: for variable in contract.state_variables_declared:
if variable.visibility == "private": if variable.visibility == "private":
print(variable.source_mapping)
attributes_start = variable.source_mapping.start attributes_start = variable.source_mapping.start
attributes_end = attributes_start + variable.source_mapping.length attributes_end = attributes_start + variable.source_mapping.length
attributes = content[attributes_start:attributes_end] attributes = content[attributes_start:attributes_end]
print(attributes)
regex = re.search(r" private ", attributes) regex = re.search(r" private ", attributes)
if regex: if regex:
to_patch.append( to_patch.append(
@ -171,6 +218,10 @@ class Flattening:
index = index - start index = index - start
if patch_type == "public_to_external": if patch_type == "public_to_external":
content = content[:index] + "public" + content[index + len("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": elif patch_type == "private_to_internal":
content = content[:index] + "internal" + content[index + len("private") :] content = content[:index] + "internal" + content[index + len("private") :]
elif patch_type == "calldata_to_memory": elif patch_type == "calldata_to_memory":
@ -191,35 +242,54 @@ class Flattening:
ret += f"pragma solidity {self._pragma_solidity};\n" ret += f"pragma solidity {self._pragma_solidity};\n"
else: else:
# TODO support multiple compiler version # 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: if self._use_abi_encoder_v2:
ret += "pragma experimental ABIEncoderV2;\n" ret += "pragma experimental ABIEncoderV2;\n"
return ret 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, UserDefinedType):
if isinstance(t.type, (EnumContract, StructureContract)): t_type = t.type
if t.type.contract != contract and t.type.contract not in exported: if isinstance(t_type, (EnumContract, StructureContract)):
self._export_list_used_contracts(t.type.contract, exported, list_contract) 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: else:
assert isinstance(t.type, Contract) assert isinstance(t.type, Contract)
if t.type != contract and t.type not in exported: 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): elif isinstance(t, MappingType):
self._export_from_type(t.type_from, 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) self._export_from_type(t.type_to, contract, exported, list_contract, list_top_level)
elif isinstance(t, ArrayType): 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 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: if contract.name in exported:
return return
exported.add(contract.name) exported.add(contract.name)
for inherited in contract.inheritance: 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 # Find all the external contracts called
externals = contract.all_library_calls + contract.all_high_level_calls 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}) externals = list({e[0] for e in externals if e[0] != contract})
for inherited in externals: 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 # Find all the external contracts use as a base type
local_vars = [] local_vars = []
@ -236,11 +315,11 @@ class Flattening:
local_vars += f.variables local_vars += f.variables
for v in contract.variables + local_vars: 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 s in contract.structures:
for elem in s.elems.values(): 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 # Find all convert and "new" operation that can lead to use an external contract
for f in contract.functions_declared: for f in contract.functions_declared:
@ -248,21 +327,38 @@ class Flattening:
if isinstance(ir, NewContract): if isinstance(ir, NewContract):
if ir.contract_created != contract and not ir.contract_created in exported: if ir.contract_created != contract and not ir.contract_created in exported:
self._export_list_used_contracts( self._export_list_used_contracts(
ir.contract_created, exported, list_contract ir.contract_created, exported, list_contract, list_top_level
) )
if isinstance(ir, TypeConversion): 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: if contract not in list_contract:
list_contract.append(contract) list_contract.append(contract)
def _export_contract_with_inheritance(self, contract) -> Export: def _export_contract_with_inheritance(self, contract) -> Export:
list_contracts: List[Contract] = [] # will contain contract itself list_contracts: List[Contract] = [] # will contain contract itself
self._export_list_used_contracts(contract, set(), list_contracts) list_top_level: List[TopLevel] = []
path = Path(self._export_path, f"{contract.name}.sol") 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 = ""
content += self._pragmas() 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: for listed_contract in list_contracts:
content += self._source_codes[listed_contract] content += self._source_codes[listed_contract]
content += "\n" content += "\n"
@ -271,7 +367,7 @@ class Flattening:
def _export_most_derived(self) -> List[Export]: def _export_most_derived(self) -> List[Export]:
ret: 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)) ret.append(self._export_contract_with_inheritance(contract))
return ret return ret
@ -281,8 +377,13 @@ class Flattening:
content = "" content = ""
content += self._pragmas() 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_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 # 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 # 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]: def _export_with_import(self) -> List[Export]:
exports: 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 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") path = Path(self._export_path, f"{contract.name}.sol")
@ -341,12 +450,13 @@ class Flattening:
elif strategy == Strategy.LocalImport: elif strategy == Strategy.LocalImport:
exports = self._export_with_import() exports = self._export_with_import()
else: else:
contracts = self._slither.get_contract_from_name(target) contracts = self._compilation_unit.get_contract_from_name(target)
if len(contracts) != 1: if len(contracts) == 0:
logger.error(f"{target} not found") logger.error(f"{target} not found")
return return
contract = contracts[0] exports = []
exports = [self._export_contract_with_inheritance(contract)] for contract in contracts:
exports.append(self._export_contract_with_inheritance(contract))
if json: if json:
export_as_json(exports, 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 # Create a set of our discovered functions which are covered
covered_functions: Set[Tuple[str, str]] = set() covered_functions: Set[Tuple[str, str]] = set()
BEHAVIOUR_PATTERN = re.compile("behaviour\s+(\S+)\s+of\s+(\S+)") BEHAVIOUR_PATTERN = re.compile(r"behaviour\s+(\S+)\s+of\s+(\S+)")
INTERFACE_PATTERN = re.compile("interface\s+([^\r\n]+)") INTERFACE_PATTERN = re.compile(r"interface\s+([^\r\n]+)")
# Read the file contents # Read the file contents
with open(target, "r", encoding="utf8") as target_file: with open(target, "r", encoding="utf8") as target_file:

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

Loading…
Cancel
Save