Merge branch 'dev' into HEAD

pull/1394/head
Josselin Feist 2 years ago
commit 2a3a9dca29
  1. 12
      .github/workflows/IR.yml
  2. 7
      .github/workflows/black.yml
  3. 28
      .github/workflows/ci.yml
  4. 12
      .github/workflows/detectors.yml
  5. 56
      .github/workflows/docker.yml
  6. 17
      .github/workflows/features.yml
  7. 7
      .github/workflows/linter.yml
  8. 12
      .github/workflows/parser.yml
  9. 23
      .github/workflows/pip-audit.yml
  10. 7
      .github/workflows/pylint.yml
  11. 11
      .github/workflows/read_storage.yml
  12. 3
      .gitignore
  13. 24
      CONTRIBUTING.md
  14. 45
      Dockerfile
  15. 25
      README.md
  16. 3
      examples/scripts/taint_mapping.py
  17. 2
      plugin_example/setup.py
  18. 9
      plugin_example/slither_my_plugin/__init__.py
  19. 6
      scripts/ci_test.sh
  20. 6
      scripts/ci_test_cli.sh
  21. 12
      scripts/ci_test_dapp.sh
  22. 11
      scripts/ci_test_embark.sh
  23. 10
      scripts/ci_test_etherlime.sh
  24. 16
      scripts/ci_test_etherscan.sh
  25. 2
      scripts/ci_test_find_paths.sh
  26. 2
      scripts/ci_test_kspec.sh
  27. 15
      scripts/ci_test_path_filtering.sh
  28. 4
      scripts/ci_test_simil.sh
  29. 10
      scripts/ci_test_truffle.sh
  30. 14
      setup.py
  31. 205
      slither/__main__.py
  32. 6
      slither/analyses/evm/convert.py
  33. 6
      slither/core/cfg/node.py
  34. 4
      slither/core/children/child_contract.py
  35. 2
      slither/core/children/child_node.py
  36. 57
      slither/core/declarations/contract.py
  37. 20
      slither/core/declarations/custom_error.py
  38. 11
      slither/core/declarations/enum.py
  39. 23
      slither/core/declarations/function.py
  40. 7
      slither/core/declarations/solidity_variables.py
  41. 18
      slither/core/declarations/structure.py
  42. 11
      slither/core/expressions/literal.py
  43. 4
      slither/core/expressions/tuple_expression.py
  44. 142
      slither/core/slither_core.py
  45. 6
      slither/core/solidity_types/array_type.py
  46. 12
      slither/core/solidity_types/elementary_type.py
  47. 4
      slither/core/solidity_types/function_type.py
  48. 4
      slither/core/solidity_types/mapping_type.py
  49. 5
      slither/core/solidity_types/type.py
  50. 4
      slither/core/solidity_types/type_alias.py
  51. 7
      slither/core/solidity_types/type_information.py
  52. 4
      slither/core/solidity_types/user_defined_type.py
  53. 236
      slither/core/source_mapping/source_mapping.py
  54. 32
      slither/core/variables/state_variable.py
  55. 62
      slither/core/variables/variable.py
  56. 2
      slither/detectors/abstract_detector.py
  57. 4
      slither/detectors/assembly/shift_parameter_mixup.py
  58. 24
      slither/detectors/attributes/incorrect_solc.py
  59. 85
      slither/detectors/functions/external_function.py
  60. 8
      slither/detectors/statements/calls_in_loop.py
  61. 8
      slither/detectors/statements/costly_operations_in_loop.py
  62. 8
      slither/detectors/statements/delegatecall_in_loop.py
  63. 8
      slither/detectors/statements/msg_value_in_loop.py
  64. 15
      slither/detectors/statements/unprotected_upgradeable.py
  65. 4
      slither/detectors/variables/similar_variables.py
  66. 6
      slither/detectors/variables/uninitialized_local_variables.py
  67. 2
      slither/detectors/variables/uninitialized_storage_variables.py
  68. 8
      slither/formatters/attributes/const_functions.py
  69. 2
      slither/formatters/attributes/constant_pragma.py
  70. 3
      slither/formatters/attributes/incorrect_solc.py
  71. 8
      slither/formatters/functions/external_function.py
  72. 65
      slither/formatters/naming_convention/naming_convention.py
  73. 2
      slither/printers/all_printers.py
  74. 35
      slither/printers/functions/dominator.py
  75. 3
      slither/printers/guidance/echidna.py
  76. 11
      slither/printers/summary/constructor_calls.py
  77. 57
      slither/printers/summary/declaration.py
  78. 14
      slither/printers/summary/evm.py
  79. 4
      slither/printers/summary/function_ids.py
  80. 4
      slither/printers/summary/human_summary.py
  81. 19
      slither/slither.py
  82. 63
      slither/slithir/convert.py
  83. 8
      slither/slithir/operations/call.py
  84. 8
      slither/slithir/tmp_operations/tmp_call.py
  85. 12
      slither/slithir/tmp_operations/tmp_new_elementary_type.py
  86. 6
      slither/slithir/variables/constant.py
  87. 5
      slither/solc_parsing/declarations/contract.py
  88. 5
      slither/solc_parsing/declarations/function.py
  89. 2
      slither/solc_parsing/declarations/modifier.py
  90. 48
      slither/solc_parsing/expressions/expression_parsing.py
  91. 19
      slither/solc_parsing/expressions/find_variable.py
  92. 21
      slither/solc_parsing/slither_compilation_unit_solc.py
  93. 32
      slither/solc_parsing/solidity_types/type_parsing.py
  94. 5
      slither/solc_parsing/yul/parse_yul.py
  95. 4
      slither/tools/demo/__main__.py
  96. 9
      slither/tools/erc_conformance/__main__.py
  97. 18
      slither/tools/erc_conformance/erc/ercs.py
  98. 11
      slither/tools/flattening/__main__.py
  99. 62
      slither/tools/flattening/flattening.py
  100. 4
      slither/tools/kspec_coverage/__main__.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -26,20 +26,14 @@ 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@v3 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==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
solc-select install all solc-select install all
solc-select use 0.8.11 solc-select use 0.8.11

@ -22,16 +22,13 @@ 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@v3 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

@ -3,8 +3,7 @@ name: CI
defaults: defaults:
run: run:
# To load bashrc shell: bash
shell: bash -ieo pipefail {0}
on: on:
push: push:
@ -26,9 +25,10 @@ jobs:
type: ["cli", type: ["cli",
"dapp", "dapp",
"data_dependency", "data_dependency",
"path_filtering",
# "embark", # "embark",
"erc", "erc",
"etherlime", # "etherlime",
# "etherscan" # "etherscan"
"find_paths", "find_paths",
"flat", "flat",
@ -44,32 +44,17 @@ jobs:
- os: windows-2022 - os: windows-2022
type: dapp type: dapp
# Requires nvm # Requires nvm
- os: windows-2022
type: etherlime
# Requires nvm
- os: windows-2022 - os: windows-2022
type: truffle type: truffle
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Set up Python 3.8
- name: Set up shell
if: runner.os == 'Windows'
run: |
echo 'C:\msys64\mingw64\bin' >> "$GITHUB_PATH"
echo 'C:\msys64\usr\bin' >> "$GITHUB_PATH"
- name: Set up Python 3.6
uses: actions/setup-python@v3 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 ".[dev]"
python setup.py install
# Used by ci_test.sh
pip install deepdiff
pip install "solc-select>=v1.0.0b1"
solc-select install all solc-select install all
solc-select use 0.5.1 solc-select use 0.5.1
pip install typing_extensions==4.1.1 pip install typing_extensions==4.1.1
@ -87,6 +72,7 @@ jobs:
- name: Run Tests - name: Run Tests
env: env:
PYTHONUTF8: 1
TEST_TYPE: ${{ matrix.type }} TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }} GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: | run: |

@ -26,21 +26,15 @@ 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@v3 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 ".[dev]"
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
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

@ -0,0 +1,56 @@
name: Docker
on:
push:
branches:
- master
- dev
tags:
- '*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
id: buildx
with:
install: true
- name: Set Docker metadata
id: metadata
uses: docker/metadata-action@v4
with:
images: |
ghcr.io/${{ github.repository }}
tags: |
type=ref,event=tag
type=ref,event=branch,prefix=testing-
type=edge
- name: GitHub Container Registry Login
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Build and Push
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7
target: final
file: Dockerfile
pull: true
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

@ -26,21 +26,15 @@ 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@v3 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 ".[dev]"
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
solc-select install all solc-select install all
solc-select use 0.8.0 solc-select use 0.8.0
@ -51,5 +45,8 @@ jobs:
- name: Test with pytest - name: Test with pytest
run: | run: |
pytest tests/test_features.py pytest tests/test_features.py
pytest tests/test_constant_folding_unary.py pytest tests/test_constant_folding.py
pytest tests/slithir/test_ternary_expressions.py pytest tests/slithir/test_ternary_expressions.py
pytest tests/test_functions_ids.py
pytest tests/test_function.py
pytest tests/test_source_mapping.py

@ -22,16 +22,13 @@ 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@v3 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

@ -26,20 +26,14 @@ 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@v3 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 ".[dev]"
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
- name: Install solc - name: Install solc
run: | run: |

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

@ -22,16 +22,13 @@ 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@v3 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

@ -28,18 +28,15 @@ jobs:
- name: Install ganache - name: Install ganache
run: npm install --global ganache run: npm install --global ganache
- name: Set up Python 3.6 - name: Set up Python 3.8
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.6 python-version: 3.8
- name: Install python dependencies - name: Install python dependencies
run: | run: |
python3 setup.py install pip install ".[dev]"
pip install web3 pytest deepdiff solc-select pip install web3
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
solc-select install 0.8.1 solc-select install 0.8.1
solc-select install 0.8.10 solc-select install 0.8.10
solc-select use 0.8.1 solc-select use 0.8.1

3
.gitignore vendored

@ -107,3 +107,6 @@ ENV/
# Test results # Test results
test_artifacts/ test_artifacts/
# crytic export
crytic-export/

@ -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
@ -46,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,19 @@
FROM ubuntu:bionic # syntax=docker/dockerfile:1.3
FROM ubuntu:jammy AS python-wheels
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gcc \
python3-dev \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
COPY . /slither
RUN cd /slither && \
echo pip3 install --no-cache-dir --upgrade pip && \
pip3 wheel -w /wheels . solc-select pip setuptools wheel
FROM ubuntu:jammy AS final
LABEL name=slither LABEL name=slither
LABEL src="https://github.com/trailofbits/slither" LABEL src="https://github.com/trailofbits/slither"
@ -6,23 +21,31 @@ 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 install -y --no-install-recommends python3-pip \
&& rm -rf /var/lib/apt/lists/*
RUN wget https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux \ # improve compatibility with amd64 solc in non-amd64 environments (e.g. Docker Desktop on M1 Mac)
&& chmod +x solc-static-linux \ ENV QEMU_LD_PREFIX=/usr/x86_64-linux-gnu
&& mv solc-static-linux /usr/bin/solc RUN if [ ! "$(uname -m)" = "x86_64" ]; then \
export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get install -y --no-install-recommends libc6-amd64-cross \
&& rm -rf /var/lib/apt/lists/*; fi
RUN useradd -m slither RUN useradd -m slither
USER slither USER slither
# If this fails, the solc-static-linux binary has changed while it should not.
RUN [ "c9b268750506b88fe71371100050e9dd1e7edcf8f69da34d1cd09557ecb24580 /usr/bin/solc" = "$(sha256sum /usr/bin/solc)" ]
COPY --chown=slither:slither . /home/slither/slither COPY --chown=slither:slither . /home/slither/slither
WORKDIR /home/slither/slither WORKDIR /home/slither/slither
RUN python3 setup.py install --user
ENV PATH="/home/slither/.local/bin:${PATH}" ENV PATH="/home/slither/.local/bin:${PATH}"
# no-index ensures we install the freshly-built wheels
RUN --mount=type=bind,target=/mnt,source=/wheels,from=python-wheels \
pip3 install --user --no-cache-dir --upgrade --no-index --find-links /mnt pip slither-analyzer solc-select
RUN solc-select install 0.4.25 && solc-select use 0.4.25
CMD /bin/bash CMD /bin/bash

@ -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
@ -54,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
@ -124,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
@ -146,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.
@ -158,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.
@ -165,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
@ -208,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

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

@ -1,8 +1,13 @@
from typing import Tuple, List, Type
from slither_my_plugin.detectors.example import Example from slither_my_plugin.detectors.example import Example
from slither.detectors.abstract_detector import AbstractDetector
from slither.printers.abstract_printer import AbstractPrinter
def make_plugin(): def make_plugin() -> Tuple[List[Type[AbstractDetector]], List[Type[AbstractPrinter]]]:
plugin_detectors = [Example] plugin_detectors = [Example]
plugin_printers = [] plugin_printers: List[Type[AbstractPrinter]] = []
return plugin_detectors, plugin_printers return plugin_detectors, plugin_printers

@ -13,8 +13,7 @@ test_slither(){
expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json" expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json"
# run slither detector on input file and save output as json # run slither detector on input file and save output as json
slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json" if ! slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json";
if [ $? -eq 255 ]
then then
echo "Slither crashed" echo "Slither crashed"
exit 255 exit 255
@ -40,8 +39,7 @@ test_slither(){
fi fi
# run slither detector on input file and save output as json # run slither detector on input file and save output as json
slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json" if ! slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json";
if [ $? -eq 255 ]
then then
echo "Slither crashed" echo "Slither crashed"
exit 255 exit 255

@ -4,17 +4,17 @@
solc-select use 0.7.0 solc-select use 0.7.0
if ! slither "tests/config/test.sol" --solc-ast --ignore-return-value; then if ! slither "tests/config/test.sol" --solc-ast --no-fail-pedantic; then
echo "--solc-ast failed" echo "--solc-ast failed"
exit 1 exit 1
fi fi
if ! slither "tests/config/test.sol" --solc-disable-warnings --ignore-return-value; then if ! slither "tests/config/test.sol" --solc-disable-warnings --no-fail-pedantic; then
echo "--solc-disable-warnings failed" echo "--solc-disable-warnings failed"
exit 1 exit 1
fi fi
if ! slither "tests/config/test.sol" --disable-color --ignore-return-value; then if ! slither "tests/config/test.sol" --disable-color --no-fail-pedantic; then
echo "--disable-color failed" echo "--disable-color failed"
exit 1 exit 1
fi fi

@ -15,13 +15,9 @@ nix-env -f "$HOME/.dapp/dapptools" -iA dapp seth solc hevm ethsign
dapp init dapp init
slither . --detect external-function if ! slither . --detect external-function; then
echo "Dapp test failed"
# TODO: make more elaborate test exit 1
if [ $? -eq 4 ]
then
exit 0
fi fi
echo "Dapp test failed" exit 0
exit 255

@ -15,13 +15,10 @@ npm install -g embark@4.2.0
embark demo embark demo
cd embark_demo || exit 255 cd embark_demo || exit 255
npm install npm install
slither . --embark-overwrite-config
if [ $? -eq 4 ] if ! slither . --embark-overwrite-config; then
then echo "Embark test failed"
exit 0 exit 255
fi fi
echo "Embark test failed" exit 0
exit 255

@ -13,12 +13,10 @@ nvm use 10.17.0
npm i -g etherlime npm i -g etherlime
etherlime init etherlime init
slither .
if [ $? -eq 7 ] if ! slither .; then
then echo "Etherlime test failed"
exit 0 exit 1
fi fi
echo "Etherlime test failed" exit 0
exit 255

@ -5,19 +5,15 @@
mkdir etherscan mkdir etherscan
cd etherscan || exit 255 cd etherscan || exit 255
slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN" if ! slither 0x7F37f78cBD74481E593F9C737776F7113d76B315 --etherscan-apikey "$GITHUB_ETHERSCAN"; then
if [ $? -ne 5 ]
then
echo "Etherscan test failed" echo "Etherscan test failed"
exit 255 exit 1
fi fi
slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN" if ! slither rinkeby:0xFe05820C5A92D9bc906D4A46F662dbeba794d3b7 --etherscan-apikey "$GITHUB_ETHERSCAN"; then
if [ $? -ne 70 ]
then
echo "Etherscan test failed" echo "Etherscan test failed"
exit 255 exit 1
fi fi
exit 0

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
### Test slither-check-upgradability ### Test slither-check-upgradeability
DIR_TESTS="tests/possible_paths" DIR_TESTS="tests/possible_paths"

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

@ -0,0 +1,15 @@
#!/usr/bin/env bash
### Test path filtering across POSIX and Windows
solc-select use 0.8.0
slither "tests/test_path_filtering/test_path_filtering.sol" --config "tests/test_path_filtering/slither.config.json" > "output.txt" 2>&1
if ! grep -q "0 result(s) found" "output.txt"
then
echo "Path filtering across POSIX and Windows failed"
rm output.txt
exit 5
else
rm output.txt
fi

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

@ -13,12 +13,10 @@ nvm use --lts
npm install -g truffle npm install -g truffle
truffle unbox metacoin truffle unbox metacoin
slither .
if [ $? -eq 9 ] if ! slither . --no-fail-pedantic; then
then echo "Truffle test failed"
exit 0 exit 1
fi fi
echo "Truffle test failed" exit 0
exit 255

@ -10,16 +10,28 @@ setup(
author="Trail of Bits", author="Trail of Bits",
version="0.8.3", 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.3", # "crytic-compile>=0.2.3",
"crytic-compile", "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"], 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,
long_description_content_type="text/markdown",
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"slither = slither.__main__:main", "slither = slither.__main__:main",

@ -10,11 +10,11 @@ import os
import pstats import pstats
import sys import sys
import traceback import traceback
from typing import Optional from typing import Tuple, Optional, List, Dict, Type, Union, Any, Sequence
from pkg_resources import iter_entry_points, require from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export from crytic_compile.platform.standard import generate_standard_export
from crytic_compile.platform.etherscan import SUPPORTED_NETWORK from crytic_compile.platform.etherscan import SUPPORTED_NETWORK
from crytic_compile import compile_all, is_supported from crytic_compile import compile_all, is_supported
@ -54,7 +54,12 @@ logger = logging.getLogger("Slither")
################################################################################### ###################################################################################
def process_single(target, args, detector_classes, printer_classes): def process_single(
target: Union[str, CryticCompile],
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
""" """
The core high-level code for running Slither static analysis. The core high-level code for running Slither static analysis.
@ -72,7 +77,12 @@ def process_single(target, args, detector_classes, printer_classes):
return _process(slither, detector_classes, printer_classes) return _process(slither, detector_classes, printer_classes)
def process_all(target, args, detector_classes, printer_classes): def process_all(
target: str,
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[List[Slither], List[Dict], List[Dict], int]:
compilations = compile_all(target, **vars(args)) compilations = compile_all(target, **vars(args))
slither_instances = [] slither_instances = []
results_detectors = [] results_detectors = []
@ -97,7 +107,11 @@ def process_all(target, args, detector_classes, printer_classes):
) )
def _process(slither, detector_classes, printer_classes): def _process(
slither: Slither,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
for detector_cls in detector_classes: for detector_cls in detector_classes:
slither.register_detector(detector_cls) slither.register_detector(detector_cls)
@ -123,8 +137,14 @@ def _process(slither, detector_classes, printer_classes):
return slither, results_detectors, results_printers, analyzed_contracts_count return slither, results_detectors, results_printers, analyzed_contracts_count
def process_from_asts(filenames, args, detector_classes, printer_classes): # TODO: delete me?
all_contracts = [] def process_from_asts(
filenames: List[str],
args: argparse.Namespace,
detector_classes: List[Type[AbstractDetector]],
printer_classes: List[Type[AbstractPrinter]],
) -> Tuple[Slither, List[Dict], List[Dict], int]:
all_contracts: List[str] = []
for filename in filenames: for filename in filenames:
with open(filename, encoding="utf8") as file_open: with open(filename, encoding="utf8") as file_open:
@ -137,35 +157,21 @@ def process_from_asts(filenames, args, detector_classes, printer_classes):
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
# region Exit
###################################################################################
###################################################################################
def my_exit(results):
if not results:
sys.exit(0)
sys.exit(len(results))
# endregion
###################################################################################
###################################################################################
# region Detectors and printers # region Detectors and printers
################################################################################### ###################################################################################
################################################################################### ###################################################################################
def get_detectors_and_printers(): def get_detectors_and_printers() -> Tuple[
""" List[Type[AbstractDetector]], List[Type[AbstractPrinter]]
NOTE: This contains just a few detectors and printers that we made public. ]:
"""
detectors = [getattr(all_detectors, name) for name in dir(all_detectors)] detectors_ = [getattr(all_detectors, name) for name in dir(all_detectors)]
detectors = [d for d in detectors if inspect.isclass(d) and issubclass(d, AbstractDetector)] detectors = [d for d in detectors_ if inspect.isclass(d) and issubclass(d, AbstractDetector)]
printers = [getattr(all_printers, name) for name in dir(all_printers)] printers_ = [getattr(all_printers, name) for name in dir(all_printers)]
printers = [p for p in printers if inspect.isclass(p) and issubclass(p, AbstractPrinter)] printers = [p for p in printers_ if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
# Handle plugins! # Handle plugins!
for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None): for entry_point in iter_entry_points(group="slither_analyzer.plugin", name=None):
@ -190,7 +196,9 @@ def get_detectors_and_printers():
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def choose_detectors(args, all_detector_classes): def choose_detectors(
args: argparse.Namespace, all_detector_classes: List[Type[AbstractDetector]]
) -> List[Type[AbstractDetector]]:
# If detectors are specified, run only these ones # If detectors are specified, run only these ones
detectors_to_run = [] detectors_to_run = []
@ -212,22 +220,22 @@ def choose_detectors(args, all_detector_classes):
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT) detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run return detectors_to_run
if args.exclude_optimization: if args.exclude_optimization and not args.fail_pedantic:
detectors_to_run = [ detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION
] ]
if args.exclude_informational: if args.exclude_informational and not args.fail_pedantic:
detectors_to_run = [ detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL
] ]
if args.exclude_low: if args.exclude_low and not args.fail_low:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW] detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW]
if args.exclude_medium: if args.exclude_medium and not args.fail_medium:
detectors_to_run = [ detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM
] ]
if args.exclude_high: if args.exclude_high and not args.fail_high:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH] detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH]
if args.detectors_to_exclude: if args.detectors_to_exclude:
detectors_to_run = [ detectors_to_run = [
@ -239,7 +247,9 @@ def choose_detectors(args, all_detector_classes):
return detectors_to_run return detectors_to_run
def choose_printers(args, all_printer_classes): def choose_printers(
args: argparse.Namespace, all_printer_classes: List[Type[AbstractPrinter]]
) -> List[Type[AbstractPrinter]]:
printers_to_run = [] printers_to_run = []
# disable default printer # disable default printer
@ -266,13 +276,16 @@ def choose_printers(args, all_printer_classes):
################################################################################### ###################################################################################
def parse_filter_paths(args): def parse_filter_paths(args: argparse.Namespace) -> List[str]:
if args.filter_paths: if args.filter_paths:
return args.filter_paths.split(",") return args.filter_paths.split(",")
return [] return []
def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-statements # pylint: disable=too-many-statements
def parse_args(
detector_classes: List[Type[AbstractDetector]], printer_classes: List[Type[AbstractPrinter]]
) -> argparse.Namespace:
usage = "slither target [flag]\n" usage = "slither target [flag]\n"
usage += "\ntarget can be:\n" usage += "\ntarget can be:\n"
@ -388,6 +401,42 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["exclude_high"], default=defaults_flag_in_config["exclude_high"],
) )
group_detector.add_argument(
"--fail-pedantic",
help="Return the number of findings in the exit code",
action="store_true",
default=defaults_flag_in_config["fail_pedantic"],
)
group_detector.add_argument(
"--no-fail-pedantic",
help="Do not return the number of findings in the exit code. Opposite of --fail-pedantic",
dest="fail_pedantic",
action="store_false",
required=False,
)
group_detector.add_argument(
"--fail-low",
help="Fail if low or greater impact finding is detected",
action="store_true",
default=defaults_flag_in_config["fail_low"],
)
group_detector.add_argument(
"--fail-medium",
help="Fail if medium or greater impact finding is detected",
action="store_true",
default=defaults_flag_in_config["fail_medium"],
)
group_detector.add_argument(
"--fail-high",
help="Fail if high impact finding is detected",
action="store_true",
default=defaults_flag_in_config["fail_high"],
)
group_detector.add_argument( group_detector.add_argument(
"--show-ignored-findings", "--show-ignored-findings",
help="Show all the findings", help="Show all the findings",
@ -485,6 +534,14 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=None, default=None,
) )
group_misc.add_argument(
"--change-line-prefix",
help="Change the line prefix (default #) for the displayed source codes (i.e. file.sol#1).",
action="store",
dest="change_line_prefix",
default="#",
)
group_misc.add_argument( group_misc.add_argument(
"--solc-ast", "--solc-ast",
help="Provide the contract as a json AST", help="Provide the contract as a json AST",
@ -530,13 +587,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["skip_assembly"], default=defaults_flag_in_config["skip_assembly"],
) )
parser.add_argument(
"--ignore-return-value",
help=argparse.SUPPRESS,
action="store_true",
default=defaults_flag_in_config["ignore_return_value"],
)
parser.add_argument( parser.add_argument(
"--perf", "--perf",
help=argparse.SUPPRESS, help=argparse.SUPPRESS,
@ -578,7 +628,9 @@ class ListDetectors(argparse.Action): # pylint: disable=too-few-public-methods
class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-methods class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
detectors, _ = get_detectors_and_printers() detectors, _ = get_detectors_and_printers()
detector_types_json = output_detectors_json(detectors) detector_types_json = output_detectors_json(detectors)
print(json.dumps(detector_types_json)) print(json.dumps(detector_types_json))
@ -586,22 +638,38 @@ class ListDetectorsJson(argparse.Action): # pylint: disable=too-few-public-meth
class ListPrinters(argparse.Action): # pylint: disable=too-few-public-methods class ListPrinters(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, *args, **kwargs): # pylint: disable=signature-differs def __call__(
self, parser: Any, *args: Any, **kwargs: Any
) -> None: # pylint: disable=signature-differs
_, printers = get_detectors_and_printers() _, printers = get_detectors_and_printers()
output_printers(printers) output_printers(printers)
parser.exit() parser.exit()
class OutputMarkdown(argparse.Action): # pylint: disable=too-few-public-methods class OutputMarkdown(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, args, values, option_string=None): def __call__(
self,
parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> None:
detectors, printers = get_detectors_and_printers() detectors, printers = get_detectors_and_printers()
assert isinstance(values, str)
output_to_markdown(detectors, printers, values) output_to_markdown(detectors, printers, values)
parser.exit() parser.exit()
class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods class OutputWiki(argparse.Action): # pylint: disable=too-few-public-methods
def __call__(self, parser, args, values, option_string=None): def __call__(
self,
parser: Any,
args: Any,
values: Optional[Union[str, Sequence[Any]]],
option_string: Any = None,
) -> None:
detectors, _ = get_detectors_and_printers() detectors, _ = get_detectors_and_printers()
assert isinstance(values, str)
output_wiki(detectors, values) output_wiki(detectors, values)
parser.exit() parser.exit()
@ -634,7 +702,7 @@ class FormatterCryticCompile(logging.Formatter):
################################################################################### ###################################################################################
def main(): def main() -> None:
# Codebase with complex domninators can lead to a lot of SSA recursive call # Codebase with complex domninators can lead to a lot of SSA recursive call
sys.setrecursionlimit(1500) sys.setrecursionlimit(1500)
@ -644,7 +712,10 @@ def main():
# pylint: disable=too-many-statements,too-many-branches,too-many-locals # pylint: disable=too-many-statements,too-many-branches,too-many-locals
def main_impl(all_detector_classes, all_printer_classes): def main_impl(
all_detector_classes: List[Type[AbstractDetector]],
all_printer_classes: List[Type[AbstractPrinter]],
) -> None:
""" """
:param all_detector_classes: A list of all detectors that can be included/excluded. :param all_detector_classes: A list of all detectors that can be included/excluded.
:param all_printer_classes: A list of all printers that can be included. :param all_printer_classes: A list of all printers that can be included.
@ -659,7 +730,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 = {}
@ -710,8 +781,8 @@ def main_impl(all_detector_classes, all_printer_classes):
crytic_compile_error.propagate = False crytic_compile_error.propagate = False
crytic_compile_error.setLevel(logging.INFO) crytic_compile_error.setLevel(logging.INFO)
results_detectors = [] results_detectors: List[Dict] = []
results_printers = [] results_printers: List[Dict] = []
try: try:
filename = args.filename filename = args.filename
@ -760,6 +831,7 @@ def main_impl(all_detector_classes, all_printer_classes):
if "compilations" in args.json_types: if "compilations" in args.json_types:
compilation_results = [] compilation_results = []
for slither_instance in slither_instances: for slither_instance in slither_instances:
assert slither_instance.crytic_compile
compilation_results.append( compilation_results.append(
generate_standard_export(slither_instance.crytic_compile) generate_standard_export(slither_instance.crytic_compile)
) )
@ -800,8 +872,6 @@ def main_impl(all_detector_classes, all_printer_classes):
len(detector_classes), len(detector_classes),
len(results_detectors), len(results_detectors),
) )
if args.ignore_return_value:
return
except SlitherException as slither_exception: except SlitherException as slither_exception:
output_error = str(slither_exception) output_error = str(slither_exception)
@ -812,7 +882,7 @@ def main_impl(all_detector_classes, all_printer_classes):
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
output_error = traceback.format_exc() output_error = traceback.format_exc()
logging.error(traceback.print_exc()) traceback.print_exc()
logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation
logging.error(output_error) logging.error(output_error)
@ -835,16 +905,31 @@ def main_impl(all_detector_classes, all_printer_classes):
if outputting_zip: if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type) output_to_zip(args.zip, output_error, json_results, args.zip_type)
if args.perf: if args.perf and cp:
cp.disable() cp.disable()
stats = pstats.Stats(cp).sort_stats("cumtime") stats = pstats.Stats(cp).sort_stats("cumtime")
stats.print_stats() stats.print_stats()
# Exit with the appropriate status code if args.fail_high:
if output_error: fail_on_detection = any(result["impact"] == "High" for result in results_detectors)
elif args.fail_medium:
fail_on_detection = any(
result["impact"] in ["Medium", "High"] for result in results_detectors
)
elif args.fail_low:
fail_on_detection = any(
result["impact"] in ["Low", "Medium", "High"] for result in results_detectors
)
elif args.fail_pedantic:
fail_on_detection = bool(results_detectors)
else:
fail_on_detection = False
# Exit with them appropriate status code
if output_error or fail_on_detection:
sys.exit(-1) sys.exit(-1)
else: else:
my_exit(results_detectors) sys.exit(0)
if __name__ == "__main__": if __name__ == "__main__":

@ -128,17 +128,17 @@ def _get_evm_instructions_node(node_info):
node_info["cfg"].instructions, node_info["cfg"].instructions,
node_info["srcmap"], node_info["srcmap"],
node_info["slither"], node_info["slither"],
node_info["contract"].source_mapping["filename_absolute"], node_info["contract"].source_mapping.filename.absolute,
) )
contract_file = ( contract_file = (
node_info["slither"] node_info["slither"]
.source_code[node_info["contract"].source_mapping["filename_absolute"]] .source_code[node_info["contract"].source_mapping.filename.absolute]
.encode("utf-8") .encode("utf-8")
) )
# Get evm instructions corresponding to node's source line number # Get evm instructions corresponding to node's source line number
node_source_line = ( node_source_line = (
contract_file[0 : node_info["node"].source_mapping["start"]].count("\n".encode("utf-8")) + 1 contract_file[0 : node_info["node"].source_mapping.start].count("\n".encode("utf-8")) + 1
) )
node_pcs = contract_pcs.get(node_source_line, []) node_pcs = contract_pcs.get(node_source_line, [])
node_ins = [] node_ins = []

@ -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 (
@ -913,10 +912,11 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
except AttributeError as error: except AttributeError as error:
# pylint: disable=raise-missing-from # pylint: disable=raise-missing-from
raise SlitherException( raise SlitherException(
f"Function not found on IR: {ir}.\nNode: {self} ({self.source_mapping_str})\nFunction: {self.function}\nPlease try compiling with a recent Solidity version. {error}" f"Function not found on IR: {ir}.\nNode: {self} ({self.source_mapping})\nFunction: {self.function}\nPlease try compiling with a recent Solidity version. {error}"
) )
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))

@ -1,10 +1,12 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from slither.core.source_mapping.source_mapping import SourceMapping
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.declarations import Contract from slither.core.declarations import Contract
class ChildContract: class ChildContract(SourceMapping):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._contract = None self._contract = None

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

@ -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
@ -78,6 +80,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._using_for: Dict[Union[str, Type], List[Type]] = {} 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
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -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:
""" """
@ -1061,7 +1112,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_from_dependency(self) -> bool: def is_from_dependency(self) -> bool:
return self.compilation_unit.core.crytic_compile.is_dependency( return self.compilation_unit.core.crytic_compile.is_dependency(
self.source_mapping["filename_absolute"] self.source_mapping.filename.absolute
) )
# endregion # endregion
@ -1079,7 +1130,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
""" """
if self.compilation_unit.core.crytic_compile.platform == PlatformType.TRUFFLE: if self.compilation_unit.core.crytic_compile.platform == PlatformType.TRUFFLE:
if self.name == "Migrations": if self.name == "Migrations":
paths = Path(self.source_mapping["filename_absolute"]).parts paths = Path(self.source_mapping.filename.absolute).parts
if len(paths) >= 2: if len(paths) >= 2:
return paths[-2] == "contracts" and paths[-1] == "migrations.sol" return paths[-2] == "contracts" and paths[-1] == "migrations.sol"
return False return False

@ -16,6 +16,7 @@ class CustomError(SourceMapping):
self._compilation_unit = compilation_unit self._compilation_unit = compilation_unit
self._solidity_signature: Optional[str] = None self._solidity_signature: Optional[str] = None
self._full_name: Optional[str] = None
@property @property
def name(self) -> str: def name(self) -> str:
@ -50,7 +51,7 @@ class CustomError(SourceMapping):
return str(t) return str(t)
@property @property
def solidity_signature(self) -> str: def solidity_signature(self) -> Optional[str]:
""" """
Return a signature following the Solidity Standard Return a signature following the Solidity Standard
Contract and converted into address Contract and converted into address
@ -71,8 +72,21 @@ class CustomError(SourceMapping):
Returns: Returns:
""" """
parameters = [self._convert_type_for_solidity_signature(x.type) for x in self.parameters] parameters = [x.type for x in self.parameters]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")" self._full_name = self.name + "(" + ",".join(map(str, parameters)) + ")"
solidity_parameters = map(self._convert_type_for_solidity_signature, parameters)
self._solidity_signature = self.name + "(" + ",".join(solidity_parameters) + ")"
@property
def full_name(self) -> Optional[str]:
"""
Return the error signature without
converting contract into address
:return: the error signature
"""
if self._full_name is None:
raise ValueError("Custom Error not yet built")
return self._full_name
# 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

@ -2,7 +2,7 @@
Function module Function module
""" """
import logging import logging
from abc import ABCMeta, abstractmethod from abc import abstractmethod, ABCMeta
from collections import namedtuple from collections import namedtuple
from enum import Enum from enum import Enum
from itertools import groupby from itertools import groupby
@ -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,24 +954,21 @@ 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:
""" """
Return a signature following the Solidity Standard Return a signature following the Solidity Standard
Contract and converted into address Contract and converted into address
It might still keep internal types (ex: structure name) for internal functions.
The reason is that internal functions allows recursive structure definition, which
can't be converted following the Solidity stand ard
:return: the solidity signature :return: the solidity signature
""" """
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

@ -1,9 +1,9 @@
# https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html # https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html
from typing import List, Dict, Union, TYPE_CHECKING from typing import List, Dict, Union, TYPE_CHECKING
from slither.core.context.context import Context
from slither.core.declarations.custom_error import CustomError from slither.core.declarations.custom_error import CustomError
from slither.core.solidity_types import ElementaryType, TypeInformation from slither.core.solidity_types import ElementaryType, TypeInformation
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.exceptions import SlitherException from slither.exceptions import SlitherException
if TYPE_CHECKING: if TYPE_CHECKING:
@ -96,7 +96,7 @@ def solidity_function_signature(name):
return name + f" returns({','.join(SOLIDITY_FUNCTIONS[name])})" return name + f" returns({','.join(SOLIDITY_FUNCTIONS[name])})"
class SolidityVariable(Context): class SolidityVariable(SourceMapping):
def __init__(self, name: str): def __init__(self, name: str):
super().__init__() super().__init__()
self._check_name(name) self._check_name(name)
@ -155,13 +155,14 @@ class SolidityVariableComposed(SolidityVariable):
return hash(self.name) return hash(self.name)
class SolidityFunction: class SolidityFunction(SourceMapping):
# Non standard handling of type(address). This function returns an undefined object # Non standard handling of type(address). This function returns an undefined object
# The type is dynamic # The type is dynamic
# https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#type-information # https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#type-information
# As a result, we set return_type during the Ir conversion # As a result, we set return_type during the Ir conversion
def __init__(self, name: str): def __init__(self, name: str):
super().__init__()
assert name in SOLIDITY_FUNCTIONS assert name in SOLIDITY_FUNCTIONS
self._name = name self._name = name
# Can be TypeInformation if type(address) is used # Can be TypeInformation if type(address) is used

@ -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
@ -8,10 +8,10 @@ if TYPE_CHECKING:
class Structure(SourceMapping): class Structure(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"): def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__() super().__init__()
self._name = None self._name: Optional[str] = None
self._canonical_name = None self._canonical_name: Optional[str] = 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
self._elems_ordered: List[str] = [] self._elems_ordered: List[str] = []
@ -19,25 +19,27 @@ class Structure(SourceMapping):
@property @property
def canonical_name(self) -> str: def canonical_name(self) -> str:
assert self._canonical_name
return self._canonical_name return self._canonical_name
@canonical_name.setter @canonical_name.setter
def canonical_name(self, name: str): def canonical_name(self, name: str) -> None:
self._canonical_name = name self._canonical_name = name
@property @property
def name(self) -> str: def name(self) -> str:
assert self._name
return self._name return self._name
@name.setter @name.setter
def name(self, new_name: str): def name(self, new_name: str) -> None:
self._name = new_name self._name = new_name
@property @property
def elems(self) -> Dict[str, "StructureVariable"]: def elems(self) -> Dict[str, "StructureVariable"]:
return self._elems return self._elems
def add_elem_in_order(self, s: str): def add_elem_in_order(self, s: str) -> None:
self._elems_ordered.append(s) self._elems_ordered.append(s)
@property @property
@ -47,5 +49,5 @@ class Structure(SourceMapping):
ret.append(self._elems[e]) ret.append(self._elems[e])
return ret return ret
def __str__(self): def __str__(self) -> str:
return self.name return self.name

@ -1,7 +1,9 @@
from typing import Optional, Union, TYPE_CHECKING from typing import Optional, Union, TYPE_CHECKING
from slither.core.expressions.expression import Expression from slither.core.expressions.expression import Expression
from slither.core.solidity_types.elementary_type import Fixed, Int, Ufixed, Uint
from slither.utils.arithmetic import convert_subdenomination from slither.utils.arithmetic import convert_subdenomination
from slither.utils.integer_conversion import convert_string_to_int
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
@ -29,5 +31,14 @@ class Literal(Expression):
def __str__(self): def __str__(self):
if self.subdenomination: if self.subdenomination:
return str(convert_subdenomination(self._value, self.subdenomination)) return str(convert_subdenomination(self._value, self.subdenomination))
if self.type in Int + Uint + Fixed + Ufixed + ["address"]:
return str(convert_string_to_int(self._value))
# be sure to handle any character # be sure to handle any character
return str(self._value) return str(self._value)
def __eq__(self, other):
if not isinstance(other, Literal):
return False
return (self.value, self.subdenomination) == (other.value, other.subdenomination)

@ -4,7 +4,7 @@ from slither.core.expressions.expression import Expression
class TupleExpression(Expression): class TupleExpression(Expression):
def __init__(self, expressions): def __init__(self, expressions: List[Expression]) -> None:
assert all(isinstance(x, Expression) for x in expressions if x) assert all(isinstance(x, Expression) for x in expressions if x)
super().__init__() super().__init__()
self._expressions = expressions self._expressions = expressions
@ -13,6 +13,6 @@ class TupleExpression(Expression):
def expressions(self) -> List[Expression]: def expressions(self) -> List[Expression]:
return self._expressions return self._expressions
def __str__(self): def __str__(self) -> str:
expressions_str = [str(e) for e in self.expressions] expressions_str = [str(e) for e in self.expressions]
return "(" + ",".join(expressions_str) + ")" return "(" + ",".join(expressions_str) + ")"

@ -4,17 +4,24 @@
import json import json
import logging import logging
import os import os
import pathlib
import posixpath import posixpath
import re import re
from collections import defaultdict
from typing import Optional, Dict, List, Set, Union from typing import Optional, Dict, List, Set, Union
from crytic_compile import CryticCompile from crytic_compile import CryticCompile
from crytic_compile.utils.naming import Filename
from slither.core.children.child_contract import ChildContract
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.context.context import Context from slither.core.context.context import Context
from slither.core.declarations import Contract from slither.core.declarations import Contract, FunctionContract
from slither.core.declarations.top_level import TopLevel
from slither.core.source_mapping.source_mapping import SourceMapping, Source
from slither.slithir.variables import Constant from slither.slithir.variables import Constant
from slither.utils.colors import red from slither.utils.colors import red
from slither.utils.source_mapping import get_definition, get_references, get_implementation
logger = logging.getLogger("Slither") logger = logging.getLogger("Slither")
logging.basicConfig() logging.basicConfig()
@ -47,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()
@ -69,6 +76,16 @@ class SlitherCore(Context):
self._contracts: List[Contract] = [] self._contracts: List[Contract] = []
self._contracts_derived: List[Contract] = [] self._contracts_derived: List[Contract] = []
self._offset_to_objects: Optional[Dict[Filename, Dict[int, Set[SourceMapping]]]] = None
self._offset_to_references: Optional[Dict[Filename, Dict[int, Set[Source]]]] = None
self._offset_to_implementations: Optional[Dict[Filename, Dict[int, Set[Source]]]] = None
self._offset_to_definitions: Optional[Dict[Filename, Dict[int, Set[Source]]]] = None
# Line prefix is used during the source mapping generation
# By default we generate file.sol#1
# But we allow to alter this (ex: file.sol:1) for vscode integration
self.line_prefix: str = "#"
@property @property
def compilation_units(self) -> List[SlitherCompilationUnit]: def compilation_units(self) -> List[SlitherCompilationUnit]:
return list(self._compilation_units) return list(self._compilation_units)
@ -158,6 +175,108 @@ class SlitherCore(Context):
for f in c.functions: for f in c.functions:
f.cfg_to_dot(os.path.join(d, f"{c.name}.{f.name}.dot")) f.cfg_to_dot(os.path.join(d, f"{c.name}.{f.name}.dot"))
def offset_to_objects(self, filename_str: str, offset: int) -> Set[SourceMapping]:
if self._offset_to_objects is None:
self._compute_offsets_to_ref_impl_decl()
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
return self._offset_to_objects[filename][offset]
def _compute_offsets_from_thing(self, thing: SourceMapping):
definition = get_definition(thing, self.crytic_compile)
references = get_references(thing)
implementation = get_implementation(thing)
for offset in range(definition.start, definition.end + 1):
if (
isinstance(thing, TopLevel)
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (isinstance(thing, ChildContract) and not isinstance(thing, FunctionContract))
):
self._offset_to_objects[definition.filename][offset].add(thing)
self._offset_to_definitions[definition.filename][offset].add(definition)
self._offset_to_implementations[definition.filename][offset].add(implementation)
self._offset_to_references[definition.filename][offset] |= set(references)
for ref in references:
for offset in range(ref.start, ref.end + 1):
if (
isinstance(thing, TopLevel)
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (
isinstance(thing, ChildContract) and not isinstance(thing, FunctionContract)
)
):
self._offset_to_objects[definition.filename][offset].add(thing)
self._offset_to_definitions[ref.filename][offset].add(definition)
self._offset_to_implementations[ref.filename][offset].add(implementation)
self._offset_to_references[ref.filename][offset] |= set(references)
def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branches
self._offset_to_references = defaultdict(lambda: defaultdict(lambda: set()))
self._offset_to_definitions = defaultdict(lambda: defaultdict(lambda: set()))
self._offset_to_implementations = defaultdict(lambda: defaultdict(lambda: set()))
self._offset_to_objects = defaultdict(lambda: defaultdict(lambda: set()))
for compilation_unit in self._compilation_units:
for contract in compilation_unit.contracts:
self._compute_offsets_from_thing(contract)
for function in contract.functions:
self._compute_offsets_from_thing(function)
for variable in function.local_variables:
self._compute_offsets_from_thing(variable)
for modifier in contract.modifiers:
self._compute_offsets_from_thing(modifier)
for variable in modifier.local_variables:
self._compute_offsets_from_thing(variable)
for st in contract.structures:
self._compute_offsets_from_thing(st)
for enum in contract.enums:
self._compute_offsets_from_thing(enum)
for event in contract.events:
self._compute_offsets_from_thing(event)
for enum in compilation_unit.enums_top_level:
self._compute_offsets_from_thing(enum)
for function in compilation_unit.functions_top_level:
self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level:
self._compute_offsets_from_thing(st)
for import_directive in compilation_unit.import_directives:
self._compute_offsets_from_thing(import_directive)
for pragma in compilation_unit.pragma_directives:
self._compute_offsets_from_thing(pragma)
def offset_to_references(self, filename_str: str, offset: int) -> Set[Source]:
if self._offset_to_references is None:
self._compute_offsets_to_ref_impl_decl()
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
return self._offset_to_references[filename][offset]
def offset_to_implementations(self, filename_str: str, offset: int) -> Set[Source]:
if self._offset_to_implementations is None:
self._compute_offsets_to_ref_impl_decl()
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
return self._offset_to_implementations[filename][offset]
def offset_to_definitions(self, filename_str: str, offset: int) -> Set[Source]:
if self._offset_to_definitions is None:
self._compute_offsets_to_ref_impl_decl()
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
return self._offset_to_definitions[filename][offset]
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -208,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"])
@ -218,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: posixpath.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
@ -240,6 +363,7 @@ class SlitherCore(Context):
if r["elements"] and matching: if r["elements"] and matching:
return False return False
if self._show_ignored_findings: if self._show_ignored_findings:
return True return True
if self.has_ignore_comment(r): if self.has_ignore_comment(r):
@ -247,9 +371,13 @@ class SlitherCore(Context):
if r["id"] in self._previous_results_ids: if r["id"] in self._previous_results_ids:
return False return False
if r["elements"] and self._exclude_dependencies: if r["elements"] and self._exclude_dependencies:
return not all(element["source_mapping"]["is_dependency"] for element in r["elements"]) 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

@ -29,6 +29,10 @@ 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
@ -49,7 +53,7 @@ class ArrayType(Type):
def storage_size(self) -> Tuple[int, bool]: def storage_size(self) -> Tuple[int, bool]:
if self._length_value: if self._length_value:
elem_size, _ = self._type.storage_size elem_size, _ = self._type.storage_size
return elem_size * int(self._length_value.value), True return elem_size * int(str(self._length_value)), True
return 32, True return 32, True
def __str__(self): def __str__(self):

@ -1,5 +1,5 @@
import itertools import itertools
from typing import Optional, Tuple from typing import Tuple
from slither.core.solidity_types.type import Type from slither.core.solidity_types.type import Type
@ -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
@ -172,7 +176,7 @@ class ElementaryType(Type):
return self.type return self.type
@property @property
def size(self) -> Optional[int]: def size(self) -> int:
""" """
Return the size in bits Return the size in bits
Return None if the size is not known Return None if the size is not known
@ -190,7 +194,7 @@ class ElementaryType(Type):
return int(160) return int(160)
if t.startswith("bytes") and t != "bytes": if t.startswith("bytes") and t != "bytes":
return int(t[len("bytes") :]) * 8 return int(t[len("bytes") :]) * 8
return None raise SlitherException(f"{t} does not have a size")
@property @property
def storage_size(self) -> Tuple[int, bool]: def storage_size(self) -> Tuple[int, bool]:

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

@ -22,6 +22,10 @@ class TypeAlias(Type):
def __hash__(self): def __hash__(self):
return hash(str(self)) return hash(str(self))
@property
def is_dynamic(self) -> bool:
return self.underlying_type.is_dynamic
class TypeAliasTopLevel(TypeAlias, TopLevel): class TypeAliasTopLevel(TypeAlias, TopLevel):
def __init__(self, underlying_type: Type, name: str, scope: "FileScope"): def __init__(self, underlying_type: Type, name: str, scope: "FileScope"):

@ -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,5 +1,8 @@
import re import re
from typing import Dict, Union, Optional, List, Tuple, TYPE_CHECKING from abc import ABCMeta
from typing import Dict, Union, List, Tuple, TYPE_CHECKING, Optional
from crytic_compile.utils.naming import Filename
from slither.core.context.context import Context from slither.core.context.context import Context
@ -7,30 +10,95 @@ if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
class SourceMapping(Context): # We split the source mapping into two objects
# The reasoning is to allow any object to just inherit from SourceMapping
# To have then everything accessible through obj.source_mapping._
# All an object needs to do is to inherits from SourceMapping
# And call set_offset at some point
# pylint: disable=too-many-instance-attributes
class Source:
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() self.start: int = 0
# TODO create a namedtuple for the source mapping rather than a dict self.length: int = 0
self._source_mapping: Optional[Dict] = None self.filename: Filename = Filename("", "", "", "")
# self._start: Optional[int] = None self.is_dependency: bool = False
# self._length: Optional[int] = None self.lines: List[int] = []
# self._filename_used: Optional[str] = None self.starting_column: int = 0
# self._filename_relative: Optional[str] = None self.ending_column: int = 0
# self._filename_absolute: Optional[str] = None self.end: int = 0
# self._filename_short: Optional[str] = None self.compilation_unit: Optional["SlitherCompilationUnit"] = None
# self._is_dependency: Optional[bool] = None
# self._lines: Optional[List[int]] = None def to_json(self) -> Dict:
# self._starting_column: Optional[int] = None return {
# self._ending_column: Optional[int] = None "start": self.start,
"length": self.length,
@property # TODO investigate filename_used usecase
def source_mapping(self) -> Optional[Dict]: # It creates non-deterministic result
return self._source_mapping # As it sometimes refer to the relative and sometimes to the absolute
# "filename_used": self.filename.used,
@staticmethod "filename_relative": self.filename.relative,
def _compute_line( "filename_absolute": self.filename.absolute,
compilation_unit: "SlitherCompilationUnit", filename, start: int, length: int "filename_short": self.filename.short,
) -> Tuple[List[int], int, int]: "is_dependency": self.is_dependency,
"lines": self.lines,
"starting_column": self.starting_column,
"ending_column": self.ending_column,
}
def to_markdown(self, markdown_root: str) -> str:
lines = self._get_lines_str(line_descr="L")
filename_relative: str = self.filename.relative if self.filename.relative else ""
return f"{markdown_root}{filename_relative}{lines}"
def to_detailled_str(self) -> str:
lines = self._get_lines_str()
filename_short: str = self.filename.short if self.filename.short else ""
return f"{filename_short}{lines} ({self.starting_column} - {self.ending_column})"
def _get_lines_str(self, line_descr=""):
# If the compilation unit was not initialized, it means that the set_offset was never called
# on the corresponding object, which should not happen
assert self.compilation_unit is not None
line_prefix = self.compilation_unit.core.line_prefix
lines = self.lines
if not lines:
lines = ""
elif len(lines) == 1:
lines = f"{line_prefix}{line_descr}{lines[0]}"
else:
lines = f"{line_prefix}{line_descr}{lines[0]}-{line_descr}{lines[-1]}"
return lines
def __str__(self) -> str:
lines = self._get_lines_str()
filename_short: str = self.filename.short if self.filename.short else ""
return f"{filename_short}{lines}"
def __hash__(self):
return hash(str(self))
def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
return (
self.start == other.start
and self.length == other.length
and self.filename == other.filename
and self.is_dependency == other.is_dependency
and self.lines == other.lines
and self.starting_column == other.starting_column
and self.ending_column == other.ending_column
and self.end == other.end
)
def _compute_line(
compilation_unit: "SlitherCompilationUnit", filename: Filename, start: int, length: int
) -> Tuple[List[int], int, int]:
""" """
Compute line(s) numbers and starting/ending columns Compute line(s) numbers and starting/ending columns
from a start/end offset. All numbers start from 1. from a start/end offset. All numbers start from 1.
@ -45,9 +113,10 @@ class SourceMapping(Context):
) )
return list(range(start_line, end_line + 1)), starting_column, ending_column return list(range(start_line, end_line + 1)), starting_column, ending_column
def _convert_source_mapping(
self, offset: str, compilation_unit: "SlitherCompilationUnit" def _convert_source_mapping(
): # pylint: disable=too-many-locals offset: str, compilation_unit: "SlitherCompilationUnit"
) -> Source: # pylint: disable=too-many-locals
""" """
Convert a text offset to a real offset Convert a text offset to a real offset
see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings
@ -58,7 +127,7 @@ class SourceMapping(Context):
position = re.findall("([0-9]*):([0-9]*):([-]?[0-9]*)", offset) position = re.findall("([0-9]*):([0-9]*):([-]?[0-9]*)", offset)
if len(position) != 1: if len(position) != 1:
return {} return Source()
s, l, f = position[0] s, l, f = position[0]
s = int(s) s = int(s)
@ -66,78 +135,57 @@ class SourceMapping(Context):
f = int(f) f = int(f)
if f not in sourceUnits: if f not in sourceUnits:
return {"start": s, "length": l} new_source = Source()
new_source.start = s
new_source.length = l
return new_source
filename_used = sourceUnits[f] filename_used = sourceUnits[f]
filename_absolute = None
filename_relative = None
filename_short = None
is_dependency = False
# If possible, convert the filename to its absolute/relative version # If possible, convert the filename to its absolute/relative version
if compilation_unit.core.crytic_compile: assert compilation_unit.core.crytic_compile
filenames = compilation_unit.core.crytic_compile.filename_lookup(filename_used)
filename_absolute = filenames.absolute
filename_relative = filenames.relative
filename_short = filenames.short
is_dependency = compilation_unit.core.crytic_compile.is_dependency(filename_absolute)
if (
filename_absolute in compilation_unit.core.source_code
or filename_absolute in compilation_unit.core.crytic_compile.src_content
):
filename = filename_absolute
elif filename_relative in compilation_unit.core.source_code:
filename = filename_relative
elif filename_short in compilation_unit.core.source_code:
filename = filename_short
else:
filename = filename_used
else:
filename = filename_used
if compilation_unit.core.crytic_compile: filename: Filename = compilation_unit.core.crytic_compile.filename_lookup(filename_used)
(lines, starting_column, ending_column) = self._compute_line( is_dependency = compilation_unit.core.crytic_compile.is_dependency(filename.absolute)
compilation_unit, filename, s, l
)
else:
(lines, starting_column, ending_column) = ([], None, None)
return { (lines, starting_column, ending_column) = _compute_line(compilation_unit, filename, s, l)
"start": s,
"length": l,
"filename_used": filename_used,
"filename_relative": filename_relative,
"filename_absolute": filename_absolute,
"filename_short": filename_short,
"is_dependency": is_dependency,
"lines": lines,
"starting_column": starting_column,
"ending_column": ending_column,
}
def set_offset(self, offset: Union[Dict, str], compilation_unit: "SlitherCompilationUnit"): new_source = Source()
if isinstance(offset, dict): new_source.start = s
self._source_mapping = offset new_source.length = l
else: new_source.filename = filename
self._source_mapping = self._convert_source_mapping(offset, compilation_unit) new_source.is_dependency = is_dependency
new_source.lines = lines
new_source.starting_column = starting_column
new_source.ending_column = ending_column
new_source.end = new_source.start + l
return new_source
def _get_lines_str(self, line_descr=""):
lines = self.source_mapping.get("lines", None)
if not lines:
lines = ""
elif len(lines) == 1:
lines = f"#{line_descr}{lines[0]}"
else:
lines = f"#{line_descr}{lines[0]}-{line_descr}{lines[-1]}"
return lines
def source_mapping_to_markdown(self, markdown_root: str) -> str: class SourceMapping(Context, metaclass=ABCMeta):
lines = self._get_lines_str(line_descr="L") def __init__(self) -> None:
return f'{markdown_root}{self.source_mapping.get("filename_relative", "")}{lines}' super().__init__()
# self._source_mapping: Optional[Dict] = None
self.source_mapping: Source = Source()
self.references: List[Source] = []
def set_offset(
self, offset: Union["Source", str], compilation_unit: "SlitherCompilationUnit"
) -> None:
if isinstance(offset, Source):
self.source_mapping.start = offset.start
self.source_mapping.length = offset.length
self.source_mapping.filename = offset.filename
self.source_mapping.is_dependency = offset.is_dependency
self.source_mapping.lines = offset.lines
self.source_mapping.starting_column = offset.starting_column
self.source_mapping.ending_column = offset.ending_column
self.source_mapping.end = offset.end
else:
self.source_mapping = _convert_source_mapping(offset, compilation_unit)
self.source_mapping.compilation_unit = compilation_unit
@property def add_reference_from_raw_source(
def source_mapping_str(self) -> str: self, offset: str, compilation_unit: "SlitherCompilationUnit"
lines = self._get_lines_str() ) -> None:
return f'{self.source_mapping.get("filename_short", "")}{lines}' s = _convert_source_mapping(offset, compilation_unit)
self.references.append(s)

@ -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
@ -44,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
@ -66,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
""" """
@ -97,7 +97,7 @@ class Variable(SourceMapping):
return self._is_reentrant return self._is_reentrant
@is_reentrant.setter @is_reentrant.setter
def is_reentrant(self, is_reentrant: bool): def is_reentrant(self, is_reentrant: bool) -> None:
self._is_reentrant = is_reentrant self._is_reentrant = is_reentrant
@property @property
@ -105,7 +105,7 @@ class Variable(SourceMapping):
return self._write_protection return self._write_protection
@write_protection.setter @write_protection.setter
def write_protection(self, write_protection: List[str]): def write_protection(self, write_protection: List[str]) -> None:
self._write_protection = write_protection self._write_protection = write_protection
@property @property
@ -116,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
@ -135,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

@ -147,7 +147,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
def detect(self) -> List[Dict]: def detect(self) -> List[Dict]:
results: List[Dict] = [] results: List[Dict] = []
# only keep valid result, and remove dupplicate # only keep valid result, and remove duplicate
# Keep only dictionaries # Keep only dictionaries
for r in [output.data for output in self._detect()]: for r in [output.data for output in self._detect()]:
if self.compilation_unit.core.valid_result(r) and r not in results: if self.compilation_unit.core.valid_result(r) and r not in results:

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

@ -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):
@ -43,7 +43,14 @@ Deploy with any of the following Solidity versions:
- 0.5.16 - 0.5.17 - 0.5.16 - 0.5.17
- 0.6.11 - 0.6.12 - 0.6.11 - 0.6.12
- 0.7.5 - 0.7.6 - 0.7.5 - 0.7.6
- 0.8.4 - 0.8.7 - 0.8.16
The recommendations take into account:
- Risks related to recent releases
- Risks of complex code generation changes
- Risks of new language features
- Risks of known bugs
Use a simple pragma version that allows any of these versions. Use a simple pragma version that allows any of these versions.
Consider using the latest version of Solidity for testing.""" Consider using the latest version of Solidity for testing."""
# endregion wiki_recommendation # endregion wiki_recommendation
@ -58,18 +65,7 @@ Consider using the latest version of Solidity for testing."""
) )
# Indicates the allowed versions. Must be formatted in increasing order. # Indicates the allowed versions. Must be formatted in increasing order.
ALLOWED_VERSIONS = [ ALLOWED_VERSIONS = ["0.5.16", "0.5.17", "0.6.11", "0.6.12", "0.7.5", "0.7.6", "0.8.16"]
"0.5.16",
"0.5.17",
"0.6.11",
"0.6.12",
"0.7.5",
"0.7.6",
"0.8.4",
"0.8.5",
"0.8.6",
"0.8.7",
]
# Indicates the versions that should not be used. # Indicates the versions that should not be used.
BUGGY_VERSIONS = [ BUGGY_VERSIONS = [

@ -1,7 +1,15 @@
from typing import List, Set
from slither.core.declarations import Function, FunctionContract, Contract
from slither.core.declarations.structure import Structure
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.variables.variable import Variable
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.operations import SolidityCall
from slither.slithir.operations import InternalCall, InternalDynamicCall
from slither.formatters.functions.external_function import custom_format from slither.formatters.functions.external_function import custom_format
from slither.slithir.operations import InternalCall, InternalDynamicCall
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output
class ExternalFunction(AbstractDetector): class ExternalFunction(AbstractDetector):
@ -20,13 +28,11 @@ class ExternalFunction(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external" WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external"
WIKI_TITLE = "Public function that could be declared external" WIKI_TITLE = "Public function that could be declared external"
WIKI_DESCRIPTION = "`public` functions that are never called by the contract should be declared `external` to save gas." WIKI_DESCRIPTION = "`public` functions that are never called by the contract should be declared `external`, and its immutable parameters should be located in `calldata` to save gas."
WIKI_RECOMMENDATION = ( WIKI_RECOMMENDATION = "Use the `external` attribute for functions never called from the contract, and change the location of immutable parameters to `calldata` to save gas."
"Use the `external` attribute for functions never called from the contract."
)
@staticmethod @staticmethod
def detect_functions_called(contract): def detect_functions_called(contract: Contract) -> List[Function]:
"""Returns a list of InternallCall, SolidityCall """Returns a list of InternallCall, SolidityCall
calls made in a function calls made in a function
@ -37,6 +43,8 @@ class ExternalFunction(AbstractDetector):
# Obtain all functions reachable by this contract. # Obtain all functions reachable by this contract.
for func in contract.all_functions_called: for func in contract.all_functions_called:
if not isinstance(func, Function):
continue
# Loop through all nodes in the function, add all calls to a list. # Loop through all nodes in the function, add all calls to a list.
for node in func.nodes: for node in func.nodes:
for ir in node.irs: for ir in node.irs:
@ -45,7 +53,7 @@ class ExternalFunction(AbstractDetector):
return result return result
@staticmethod @staticmethod
def _contains_internal_dynamic_call(contract): def _contains_internal_dynamic_call(contract: Contract) -> bool:
""" """
Checks if a contract contains a dynamic call either in a direct definition, or through inheritance. Checks if a contract contains a dynamic call either in a direct definition, or through inheritance.
@ -53,6 +61,8 @@ class ExternalFunction(AbstractDetector):
(boolean): True if this contract contains a dynamic call (including through inheritance). (boolean): True if this contract contains a dynamic call (including through inheritance).
""" """
for func in contract.all_functions_called: for func in contract.all_functions_called:
if not isinstance(func, Function):
continue
for node in func.nodes: for node in func.nodes:
for ir in node.irs: for ir in node.irs:
if isinstance(ir, (InternalDynamicCall)): if isinstance(ir, (InternalDynamicCall)):
@ -60,7 +70,7 @@ class ExternalFunction(AbstractDetector):
return False return False
@staticmethod @staticmethod
def get_base_most_function(function): def get_base_most_function(function: FunctionContract) -> FunctionContract:
""" """
Obtains the base function definition for the provided function. This could be used to obtain the original Obtains the base function definition for the provided function. This could be used to obtain the original
definition of a function, if the provided function is an override. definition of a function, if the provided function is an override.
@ -84,7 +94,9 @@ class ExternalFunction(AbstractDetector):
raise Exception("Could not resolve the base-most function for the provided function.") raise Exception("Could not resolve the base-most function for the provided function.")
@staticmethod @staticmethod
def get_all_function_definitions(base_most_function): def get_all_function_definitions(
base_most_function: FunctionContract,
) -> List[FunctionContract]:
""" """
Obtains all function definitions given a base-most function. This includes the provided function, plus any Obtains all function definitions given a base-most function. This includes the provided function, plus any
overrides of that function. overrides of that function.
@ -99,22 +111,45 @@ class ExternalFunction(AbstractDetector):
for derived_contract in base_most_function.contract.derived_contracts for derived_contract in base_most_function.contract.derived_contracts
for function in derived_contract.functions for function in derived_contract.functions
if function.full_name == base_most_function.full_name if function.full_name == base_most_function.full_name
and isinstance(function, FunctionContract)
] ]
@staticmethod @staticmethod
def function_parameters_written(function): def function_parameters_written(function: Function) -> bool:
return any(p in function.variables_written for p in function.parameters) return any(p in function.variables_written for p in function.parameters)
def _detect(self): # pylint: disable=too-many-locals,too-many-branches @staticmethod
results = [] def is_reference_type(parameter: Variable) -> bool:
parameter_type = parameter.type
if isinstance(parameter_type, ArrayType):
return True
if isinstance(parameter_type, UserDefinedType) and isinstance(
parameter_type.type, Structure
):
return True
if str(parameter_type) in ["bytes", "string"]:
return True
return False
def _detect(self) -> List[Output]: # pylint: disable=too-many-locals,too-many-branches
results: List[Output] = []
# After solc 0.6.9, calldata arguments are allowed in public functions
if self.compilation_unit.solc_version >= "0.7." or self.compilation_unit.solc_version in [
"0.6.9",
"0.6.10",
"0.6.11",
"0.6.12",
]:
return results
# Create a set to track contracts with dynamic calls. All contracts with dynamic calls could potentially be # Create a set to track contracts with dynamic calls. All contracts with dynamic calls could potentially be
# calling functions internally, and thus we can't assume any function in such contracts isn't called by them. # calling functions internally, and thus we can't assume any function in such contracts isn't called by them.
dynamic_call_contracts = set() dynamic_call_contracts: Set[Contract] = set()
# Create a completed functions set to skip over functions already processed (any functions which are the base # Create a completed functions set to skip over functions already processed (any functions which are the base
# of, or override hierarchically are processed together). # of, or override hierarchically are processed together).
completed_functions = set() completed_functions: Set[Function] = set()
# First we build our set of all contracts with dynamic calls # First we build our set of all contracts with dynamic calls
for contract in self.contracts: for contract in self.contracts:
@ -131,6 +166,14 @@ class ExternalFunction(AbstractDetector):
# Next we'll want to loop through all functions defined directly in this contract. # Next we'll want to loop through all functions defined directly in this contract.
for function in contract.functions_declared: for function in contract.functions_declared:
# If all of the function arguments are non-reference type or calldata, we skip it.
reference_args = []
for arg in function.parameters:
if self.is_reference_type(arg) and arg.location == "memory":
reference_args.append(arg)
if len(reference_args) == 0:
continue
# If the function is a constructor, or is public, we skip it. # If the function is a constructor, or is public, we skip it.
if function.is_constructor or function.visibility != "public": if function.is_constructor or function.visibility != "public":
continue continue
@ -189,10 +232,12 @@ class ExternalFunction(AbstractDetector):
# As we collect all shadowed functions in get_all_function_definitions # As we collect all shadowed functions in get_all_function_definitions
# Some function coming from a base might already been declared as external # Some function coming from a base might already been declared as external
all_function_definitions = [ all_function_definitions: List[FunctionContract] = [
f f
for f in all_function_definitions for f in all_function_definitions
if f.visibility == "public" and f.contract == f.contract_declarer if isinstance(f, FunctionContract)
and f.visibility == "public"
and f.contract == f.contract_declarer
] ]
if all_function_definitions: if all_function_definitions:
all_function_definitions = sorted( all_function_definitions = sorted(
@ -203,6 +248,12 @@ class ExternalFunction(AbstractDetector):
info = [f"{function_definition.full_name} should be declared external:\n"] info = [f"{function_definition.full_name} should be declared external:\n"]
info += ["\t- ", function_definition, "\n"] info += ["\t- ", function_definition, "\n"]
if self.compilation_unit.solc_version >= "0.5.":
info += [
"Moreover, the following function parameters should change its data location:\n"
]
for reference_arg in reference_args:
info += [f"{reference_arg} location should be calldata\n"]
for other_function_definition in all_function_definitions: for other_function_definition in all_function_definitions:
info += ["\t- ", other_function_definition, "\n"] info += ["\t- ", other_function_definition, "\n"]

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

@ -22,11 +22,20 @@ def _can_be_destroyed(contract: Contract) -> List[Function]:
return targets return targets
def _has_initializer_modifier(functions: List[Function]) -> bool: def _has_initializing_protection(functions: List[Function]) -> bool:
# Detects "initializer" constructor modifiers and "_disableInitializers()" constructor internal calls
# https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#initializing_the_implementation_contract
for f in functions: for f in functions:
for m in f.modifiers: for m in f.modifiers:
if m.name == "initializer": if m.name == "initializer":
return True return True
for ifc in f.all_internal_calls():
if ifc.name == "_disableInitializers":
return True
# to avoid future FPs in different modifier + function naming implementations, we can also implement a broader check for state var "_initialized" being written to in the constructor
# though this is still subject to naming false positives...
return False return False
@ -82,7 +91,7 @@ class UnprotectedUpgradeable(AbstractDetector):
for contract in self.compilation_unit.contracts_derived: for contract in self.compilation_unit.contracts_derived:
if contract.is_upgradeable: if contract.is_upgradeable:
if not _has_initializer_modifier(contract.constructors): if not _has_initializing_protection(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:
initialize_functions = _initialize_functions(contract) initialize_functions = _initialize_functions(contract)
@ -102,7 +111,7 @@ 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: ",
] ]
+ initialize_functions + initialize_functions
+ [ + [

@ -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:
@ -25,10 +25,10 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result):
result, result,
element["source_mapping"]["filename_absolute"], element["source_mapping"]["filename_absolute"],
int( int(
function.parameters_src().source_mapping["start"] function.parameters_src().source_mapping.start
+ function.parameters_src().source_mapping["length"] + function.parameters_src().source_mapping.length
), ),
int(function.returns_src().source_mapping["start"]), int(function.returns_src().source_mapping.start),
) )

@ -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,16 +12,16 @@ 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:
_patch( _patch(
compilation_unit, file_scope,
result, result,
element["source_mapping"]["filename_absolute"], element["source_mapping"]["filename_absolute"],
int(function.parameters_src().source_mapping["start"]), int(function.parameters_src().source_mapping.start),
int(function.returns_src().source_mapping["start"]), int(function.returns_src().source_mapping.start),
) )

@ -1,6 +1,8 @@
import re import re
import logging import logging
from typing import List
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.slithir.operations import ( from slither.slithir.operations import (
Send, Send,
@ -10,6 +12,7 @@ from slither.slithir.operations import (
LowLevelCall, LowLevelCall,
InternalCall, InternalCall,
InternalDynamicCall, InternalDynamicCall,
Operation,
) )
from slither.core.declarations import Modifier from slither.core.declarations import Modifier
from slither.core.solidity_types import UserDefinedType, MappingType from slither.core.solidity_types import UserDefinedType, MappingType
@ -254,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"]:
@ -268,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:
@ -300,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"\)"
) )
@ -418,9 +421,9 @@ def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-ma
): ):
for variable in variables: for variable in variables:
# First explore the type of the variable # First explore the type of the variable
filename_source_code = variable.source_mapping["filename_absolute"] filename_source_code = variable.source_mapping.filename.absolute
full_txt_start = variable.source_mapping["start"] full_txt_start = variable.source_mapping.start
full_txt_end = full_txt_start + variable.source_mapping["length"] full_txt_end = full_txt_start + variable.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[ full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end full_txt_start:full_txt_end
] ]
@ -433,7 +436,7 @@ def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-ma
variable.type, variable.type,
filename_source_code, filename_source_code,
full_txt_start, full_txt_start,
variable.source_mapping["start"] + variable.source_mapping["length"], variable.source_mapping.start + variable.source_mapping.length,
) )
# If the variable is the target # If the variable is the target
@ -448,9 +451,9 @@ def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-ma
# Patch comment only makes sense for local variable declaration in the parameter list # Patch comment only makes sense for local variable declaration in the parameter list
if patch_comment and isinstance(variable, LocalVariable): if patch_comment and isinstance(variable, LocalVariable):
if "lines" in variable.source_mapping and variable.source_mapping["lines"]: if variable.source_mapping.lines:
func = variable.function func = variable.function
end_line = func.source_mapping["lines"][0] end_line = func.source_mapping.lines[0]
if variable in func.parameters: if variable in func.parameters:
idx = len(func.parameters) - func.parameters.index(variable) + 1 idx = len(func.parameters) - func.parameters.index(variable) + 1
first_line = end_line - idx - 2 first_line = end_line - idx - 2
@ -462,8 +465,8 @@ def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-ma
first_line : end_line - 1 first_line : end_line - 1
] ]
idx_beginning = func.source_mapping["start"] idx_beginning = func.source_mapping.start
idx_beginning += -func.source_mapping["starting_column"] + 1 idx_beginning += -func.source_mapping.starting_column + 1
idx_beginning += -sum([len(c) for c in potential_comments]) idx_beginning += -sum([len(c) for c in potential_comments])
old_comment = f"@param {old_str}".encode("utf8") old_comment = f"@param {old_str}".encode("utf8")
@ -498,9 +501,9 @@ def _explore_structures_declaration(slither, structures, result, target, convert
old_str = st.name old_str = st.name
new_str = convert(old_str, slither) new_str = convert(old_str, slither)
filename_source_code = st.source_mapping["filename_absolute"] filename_source_code = st.source_mapping.filename.absolute
full_txt_start = st.source_mapping["start"] full_txt_start = st.source_mapping.start
full_txt_end = full_txt_start + st.source_mapping["length"] full_txt_end = full_txt_start + st.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[ full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end full_txt_start:full_txt_end
] ]
@ -521,12 +524,12 @@ def _explore_events_declaration(slither, events, result, target, convert):
# If the event is the target # If the event is the target
if event == target: if event == target:
filename_source_code = event.source_mapping["filename_absolute"] filename_source_code = event.source_mapping.filename.absolute
old_str = event.name old_str = event.name
new_str = convert(old_str, slither) new_str = convert(old_str, slither)
loc_start = event.source_mapping["start"] loc_start = event.source_mapping.start
loc_end = loc_start + len(old_str) loc_end = loc_start + len(old_str)
create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str) create_patch(result, filename_source_code, loc_start, loc_end, old_str, new_str)
@ -550,7 +553,7 @@ def get_ir_variables(ir):
return [v for v in all_vars if v] return [v for v in all_vars if v]
def _explore_irs(slither, irs, result, target, convert): def _explore_irs(slither, irs: List[Operation], result, target, convert):
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
if irs is None: if irs is None:
return return
@ -562,9 +565,9 @@ def _explore_irs(slither, irs, result, target, convert):
and v.canonical_name == target.canonical_name and v.canonical_name == target.canonical_name
): ):
source_mapping = ir.expression.source_mapping source_mapping = ir.expression.source_mapping
filename_source_code = source_mapping["filename_absolute"] filename_source_code = source_mapping.filename.absolute
full_txt_start = source_mapping["start"] full_txt_start = source_mapping.start
full_txt_end = full_txt_start + source_mapping["length"] full_txt_end = full_txt_start + source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[ full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end full_txt_start:full_txt_end
] ]
@ -606,9 +609,9 @@ def _explore_functions(slither, functions, result, target, convert):
old_str = function.name old_str = function.name
new_str = convert(old_str, slither) new_str = convert(old_str, slither)
filename_source_code = function.source_mapping["filename_absolute"] filename_source_code = function.source_mapping.filename.absolute
full_txt_start = function.source_mapping["start"] full_txt_start = function.source_mapping.start
full_txt_end = full_txt_start + function.source_mapping["length"] full_txt_end = full_txt_start + function.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[ full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end full_txt_start:full_txt_end
] ]
@ -631,9 +634,9 @@ def _explore_enums(slither, enums, result, target, convert):
old_str = enum.name old_str = enum.name
new_str = convert(old_str, slither) new_str = convert(old_str, slither)
filename_source_code = enum.source_mapping["filename_absolute"] filename_source_code = enum.source_mapping.filename.absolute
full_txt_start = enum.source_mapping["start"] full_txt_start = enum.source_mapping.start
full_txt_end = full_txt_start + enum.source_mapping["length"] full_txt_end = full_txt_start + enum.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[ full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end full_txt_start:full_txt_end
] ]
@ -654,9 +657,9 @@ def _explore_contract(slither, contract, result, target, convert):
_explore_enums(slither, contract.enums, result, target, convert) _explore_enums(slither, contract.enums, result, target, convert)
if contract == target: if contract == target:
filename_source_code = contract.source_mapping["filename_absolute"] filename_source_code = contract.source_mapping.filename.absolute
full_txt_start = contract.source_mapping["start"] full_txt_start = contract.source_mapping.start
full_txt_end = full_txt_start + contract.source_mapping["length"] full_txt_end = full_txt_start + contract.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[ full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end full_txt_start:full_txt_end
] ]

@ -18,3 +18,5 @@ 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.when_not_paused import PrinterWhenNotPaused
from .summary.declaration import Declaration
from .functions.dominator import Dominator

@ -0,0 +1,35 @@
from slither.printers.abstract_printer import AbstractPrinter
class Dominator(AbstractPrinter):
ARGUMENT = "dominator"
HELP = "Export the dominator tree of each functions"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#dominator"
def output(self, filename):
"""
_filename is not used
Args:
_filename(string)
"""
info = ""
all_files = []
for contract in self.contracts:
for function in contract.functions + contract.modifiers:
if filename:
new_filename = f"{filename}-{contract.name}-{function.full_name}.dot"
else:
new_filename = f"dominator-{contract.name}-{function.full_name}.dot"
info += f"Export {new_filename}\n"
content = function.dominator_tree_to_dot(new_filename)
all_files.append((new_filename, content))
self.info(info)
res = self.generate_output(info)
for filename_result, content in all_files:
res.add_file(filename_result, content)
return res

@ -38,7 +38,6 @@ def _get_name(f: Union[Function, Variable]) -> str:
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

@ -1,6 +1,7 @@
""" """
Module printing summary of the contract Module printing summary of the contract
""" """
from slither.core.source_mapping.source_mapping import Source
from slither.printers.abstract_printer import AbstractPrinter from slither.printers.abstract_printer import AbstractPrinter
from slither.utils import output from slither.utils import output
@ -11,11 +12,11 @@ class ConstructorPrinter(AbstractPrinter):
HELP = "Print the constructors executed" HELP = "Print the constructors executed"
def _get_soruce_code(self, cst): def _get_soruce_code(self, cst):
src_mapping = cst.source_mapping src_mapping: Source = cst.source_mapping
content = self.slither.source_code[src_mapping["filename_absolute"]] content = self.slither.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
initial_space = src_mapping["starting_column"] initial_space = src_mapping.starting_column
return " " * initial_space + content[start:end] return " " * initial_space + content[start:end]
def output(self, _filename): def output(self, _filename):

@ -0,0 +1,57 @@
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.source_mapping import get_definition, get_implementation, get_references
class Declaration(AbstractPrinter):
ARGUMENT = "declaration"
HELP = "Prototype showing the source code declaration, implementation and references of the contracts objects"
WIKI = "TODO"
def output(self, _filename):
"""
_filename is not used
Args:
_filename(string)
"""
txt = ""
for compilation_unit in self.slither.compilation_units:
txt += "\n# Contracts\n"
for contract in compilation_unit.contracts:
txt += f"# {contract.name}\n"
txt += f"\t- Declaration: {get_definition(contract, compilation_unit.core.crytic_compile).to_detailled_str()}\n"
txt += f"\t- Implementation: {get_implementation(contract).to_detailled_str()}\n"
txt += (
f"\t- References: {[x.to_detailled_str() for x in get_references(contract)]}\n"
)
txt += "\n\t## Function\n"
for func in contract.functions:
txt += f"\t\t- {func.canonical_name}\n"
txt += f"\t\t\t- Declaration: {get_definition(func, compilation_unit.core.crytic_compile).to_detailled_str()}\n"
txt += (
f"\t\t\t- Implementation: {get_implementation(func).to_detailled_str()}\n"
)
txt += f"\t\t\t- References: {[x.to_detailled_str() for x in get_references(func)]}\n"
txt += "\n\t## State variables\n"
for var in contract.state_variables:
txt += f"\t\t- {var.name}\n"
txt += f"\t\t\t- Declaration: {get_definition(var, compilation_unit.core.crytic_compile).to_detailled_str()}\n"
txt += f"\t\t\t- Implementation: {get_implementation(var).to_detailled_str()}\n"
txt += f"\t\t\t- References: {[x.to_detailled_str() for x in get_references(var)]}\n"
txt += "\n\t## Structures\n"
for st in contract.structures:
txt += f"\t\t- {st.name}\n"
txt += f"\t\t\t- Declaration: {get_definition(st, compilation_unit.core.crytic_compile).to_detailled_str()}\n"
txt += f"\t\t\t- Implementation: {get_implementation(st).to_detailled_str()}\n"
txt += f"\t\t\t- References: {[x.to_detailled_str() for x in get_references(st)]}\n"
self.info(txt)
res = self.generate_output(txt)
return res

@ -35,7 +35,7 @@ def _extract_evm_info(slither):
cfg.instructions, cfg.instructions,
contract_srcmap_runtime, contract_srcmap_runtime,
slither, slither,
contract.source_mapping["filename_absolute"], contract.source_mapping.filename.absolute,
) )
contract_bytecode_init = ( contract_bytecode_init = (
@ -51,7 +51,7 @@ def _extract_evm_info(slither):
cfg_init.instructions, cfg_init.instructions,
contract_srcmap_init, contract_srcmap_init,
slither, slither,
contract.source_mapping["filename_absolute"], contract.source_mapping.filename.absolute,
) )
return evm_info return evm_info
@ -83,9 +83,9 @@ class PrinterEVM(AbstractPrinter):
txt += blue(f"Contract {contract.name}\n") txt += blue(f"Contract {contract.name}\n")
contract_file = self.slither.source_code[ contract_file = self.slither.source_code[
contract.source_mapping["filename_absolute"] contract.source_mapping.filename.absolute
].encode("utf-8") ].encode("utf-8")
with open(contract.source_mapping["filename_absolute"], "r", encoding="utf8") as f: with open(contract.source_mapping.filename.absolute, "r", encoding="utf8") as f:
contract_file_lines = f.readlines() contract_file_lines = f.readlines()
contract_pcs = {} contract_pcs = {}
@ -105,8 +105,7 @@ class PrinterEVM(AbstractPrinter):
for node in function.nodes: for node in function.nodes:
txt += green("\t\tNode: " + str(node) + "\n") txt += green("\t\tNode: " + str(node) + "\n")
node_source_line = ( node_source_line = (
contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8")) contract_file[0 : node.source_mapping.start].count("\n".encode("utf-8")) + 1
+ 1
) )
txt += green( txt += green(
f"\t\tSource line {node_source_line}: {contract_file_lines[node_source_line - 1].rstrip()}\n" f"\t\tSource line {node_source_line}: {contract_file_lines[node_source_line - 1].rstrip()}\n"
@ -121,8 +120,7 @@ class PrinterEVM(AbstractPrinter):
for node in modifier.nodes: for node in modifier.nodes:
txt += green("\t\tNode: " + str(node) + "\n") txt += green("\t\tNode: " + str(node) + "\n")
node_source_line = ( node_source_line = (
contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8")) contract_file[0 : node.source_mapping.start].count("\n".encode("utf-8")) + 1
+ 1
) )
txt += green( txt += green(
f"\t\tSource line {node_source_line}: {contract_file_lines[node_source_line - 1].rstrip()}\n" f"\t\tSource line {node_source_line}: {contract_file_lines[node_source_line - 1].rstrip()}\n"

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

@ -241,8 +241,8 @@ class PrinterHumanSummary(AbstractPrinter):
for compilation_unit in self.slither.compilation_units: for compilation_unit in self.slither.compilation_units:
for pragma in compilation_unit.pragma_directives: for pragma in compilation_unit.pragma_directives:
if ( if (
pragma.source_mapping["filename_absolute"] pragma.source_mapping.filename.absolute
== contract.source_mapping["filename_absolute"] == contract.source_mapping.filename.absolute
): ):
if pragma.is_abi_encoder_v2: if pragma.is_abi_encoder_v2:
use_abi_encoder = True use_abi_encoder = True

@ -1,5 +1,5 @@
import logging import logging
from typing import Union, List, ValuesView from typing import Union, List, ValuesView, Type, Dict
from crytic_compile import CryticCompile, InvalidCompilation from crytic_compile import CryticCompile, InvalidCompilation
@ -19,7 +19,9 @@ logger_detector = logging.getLogger("Detectors")
logger_printer = logging.getLogger("Printers") logger_printer = logging.getLogger("Printers")
def _check_common_things(thing_name, cls, base_cls, instances_list): def _check_common_things(
thing_name: str, cls: Type, base_cls: Type, instances_list: List[Type[AbstractDetector]]
) -> None:
if not issubclass(cls, base_cls) or cls is base_cls: if not issubclass(cls, base_cls) or cls is base_cls:
raise Exception( raise Exception(
@ -53,7 +55,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)
@ -69,6 +71,9 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
embark_ignore_compile (bool): do not run embark build (default False) embark_ignore_compile (bool): do not run embark build (default False)
embark_overwrite_config (bool): overwrite original config file (default false) embark_overwrite_config (bool): overwrite original config file (default false)
change_line_prefix (str): Change the line prefix (default #)
for the displayed source codes (i.e. file.sol#1).
""" """
super().__init__() super().__init__()
@ -76,6 +81,8 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
self._skip_assembly: bool = kwargs.get("skip_assembly", False) self._skip_assembly: bool = kwargs.get("skip_assembly", False)
self._show_ignored_findings: bool = kwargs.get("show_ignored_findings", False) self._show_ignored_findings: bool = kwargs.get("show_ignored_findings", False)
self.line_prefix = kwargs.get("change_line_prefix", "#")
self._parsers: List[SlitherCompilationUnitSolc] = [] self._parsers: List[SlitherCompilationUnitSolc] = []
try: try:
if isinstance(target, CryticCompile): if isinstance(target, CryticCompile):
@ -173,7 +180,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
def detectors_optimization(self): def detectors_optimization(self):
return [d for d in self.detectors if d.IMPACT == DetectorClassification.OPTIMIZATION] return [d for d in self.detectors if d.IMPACT == DetectorClassification.OPTIMIZATION]
def register_detector(self, detector_class): def register_detector(self, detector_class: Type[AbstractDetector]) -> None:
""" """
:param detector_class: Class inheriting from `AbstractDetector`. :param detector_class: Class inheriting from `AbstractDetector`.
""" """
@ -183,7 +190,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
instance = detector_class(compilation_unit, self, logger_detector) instance = detector_class(compilation_unit, self, logger_detector)
self._detectors.append(instance) self._detectors.append(instance)
def register_printer(self, printer_class): def register_printer(self, printer_class: Type[AbstractPrinter]) -> None:
""" """
:param printer_class: Class inheriting from `AbstractPrinter`. :param printer_class: Class inheriting from `AbstractPrinter`.
""" """
@ -192,7 +199,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
instance = printer_class(self, logger_printer) instance = printer_class(self, logger_printer)
self._printers.append(instance) self._printers.append(instance)
def run_detectors(self): def run_detectors(self) -> List[Dict]:
""" """
:return: List of registered detectors results. :return: List of registered detectors results.
""" """

@ -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
@ -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,
@ -909,14 +935,14 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
# For top level import, where the import statement renames the filename # For top level import, where the import statement renames the filename
# See https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/ # See https://blog.soliditylang.org/2020/09/02/solidity-0.7.1-release-announcement/
current_path = Path(ins.ori.variable_left.source_mapping["filename_absolute"]).parent current_path = Path(ins.ori.variable_left.source_mapping.filename.absolute).parent
target = str( target = str(
Path(current_path, ins.ori.variable_left.import_directive.filename).absolute() Path(current_path, ins.ori.variable_left.import_directive.filename).absolute()
) )
top_level_function_targets = [ top_level_function_targets = [
f f
for f in ins.compilation_unit.functions_top_level for f in ins.compilation_unit.functions_top_level
if f.source_mapping["filename_absolute"] == target if f.source_mapping.filename.absolute == target
and f.name == ins.ori.variable_right and f.name == ins.ori.variable_right
and len(f.parameters) == ins.nbr_arguments and len(f.parameters) == ins.nbr_arguments
] ]
@ -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:
@ -1657,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):
@ -1676,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):
@ -1723,6 +1757,21 @@ def convert_delete(irs):
ir.lvalue = ir.lvalue.points_to ir.lvalue = ir.lvalue.points_to
# endregion
###################################################################################
###################################################################################
# region Source Mapping
###################################################################################
###################################################################################
def _find_source_mapping_references(irs: List[Operation]):
for ir in irs:
if isinstance(ir, NewContract):
ir.contract_created.references.append(ir.expression.source_mapping)
# endregion # endregion
################################################################################### ###################################################################################
################################################################################### ###################################################################################
@ -1744,4 +1793,6 @@ def apply_ir_heuristics(irs, node):
convert_constant_types(irs) convert_constant_types(irs)
convert_delete(irs) convert_delete(irs)
_find_source_mapping_references(irs)
return irs return irs

@ -1,8 +1,10 @@
from typing import Optional, List
from slither.slithir.operations.operation import Operation from slither.slithir.operations.operation import Operation
class Call(Operation): class Call(Operation):
def __init__(self): def __init__(self) -> None:
super().__init__() super().__init__()
self._arguments = [] self._arguments = []
@ -14,14 +16,14 @@ class Call(Operation):
def arguments(self, v): def arguments(self, v):
self._arguments = v self._arguments = v
def can_reenter(self, _callstack=None): # pylint: disable=no-self-use def can_reenter(self, _callstack: Optional[List] = None) -> bool: # pylint: disable=no-self-use
""" """
Must be called after slithIR analysis pass Must be called after slithIR analysis pass
:return: bool :return: bool
""" """
return False return False
def can_send_eth(self): # pylint: disable=no-self-use def can_send_eth(self) -> bool: # pylint: disable=no-self-use
""" """
Must be called after slithIR analysis pass Must be called after slithIR analysis pass
:return: bool :return: bool

@ -63,14 +63,14 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
def call_id(self): def call_id(self):
return self._callid return self._callid
@property
def read(self):
return [self.called]
@call_id.setter @call_id.setter
def call_id(self, c): def call_id(self, c):
self._callid = c self._callid = c
@property
def read(self):
return [self.called]
@property @property
def called(self): def called(self):
return self._called return self._called

@ -1,21 +1,23 @@
from typing import List
from slither.slithir.operations.lvalue import OperationWithLValue from slither.slithir.operations.lvalue import OperationWithLValue
from slither.core.solidity_types.elementary_type import ElementaryType from slither.core.solidity_types.elementary_type import ElementaryType
class TmpNewElementaryType(OperationWithLValue): class TmpNewElementaryType(OperationWithLValue):
def __init__(self, new_type, lvalue): def __init__(self, new_type: ElementaryType, lvalue):
assert isinstance(new_type, ElementaryType) assert isinstance(new_type, ElementaryType)
super().__init__() super().__init__()
self._type = new_type self._type: ElementaryType = new_type
self._lvalue = lvalue self._lvalue = lvalue
@property @property
def read(self): def read(self) -> List:
return [] return []
@property @property
def type(self): def type(self) -> ElementaryType:
return self._type return self._type
def __str__(self): def __str__(self) -> str:
return f"{self.lvalue} = new {self._type}" return f"{self.lvalue} = new {self._type}"

@ -1,4 +1,3 @@
from decimal import Decimal
from functools import total_ordering from functools import total_ordering
from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint
@ -33,7 +32,7 @@ class Constant(SlithIRVariable):
else: else:
if val.isdigit(): if val.isdigit():
self._type = ElementaryType("uint256") self._type = ElementaryType("uint256")
self._val = int(Decimal(val)) self._val = convert_string_to_int(val)
else: else:
self._type = ElementaryType("string") self._type = ElementaryType("string")
self._val = val self._val = val
@ -75,3 +74,6 @@ class Constant(SlithIRVariable):
def __repr__(self): def __repr__(self):
return f"{str(self.value)}" return f"{str(self.value)}"
def __hash__(self) -> int:
return self._val.__hash__()

@ -157,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"]

@ -11,6 +11,7 @@ from slither.core.declarations.function import (
) )
from slither.core.declarations.function_contract import FunctionContract from slither.core.declarations.function_contract import FunctionContract
from slither.core.expressions import AssignmentOperation from slither.core.expressions import AssignmentOperation
from slither.core.source_mapping.source_mapping import Source
from slither.core.variables.local_variable import LocalVariable from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple from slither.core.variables.local_variable_init_from_tuple import LocalVariableInitFromTuple
from slither.solc_parsing.cfg.node import NodeSolc from slither.solc_parsing.cfg.node import NodeSolc
@ -319,7 +320,7 @@ class FunctionSolc(CallerContextExpression):
################################################################################### ###################################################################################
def _new_node( def _new_node(
self, node_type: NodeType, src: Union[str, Dict], scope: Union[Scope, "Function"] self, node_type: NodeType, src: Union[str, Source], scope: Union[Scope, "Function"]
) -> NodeSolc: ) -> NodeSolc:
node = self._function.new_node(node_type, src, scope) node = self._function.new_node(node_type, src, scope)
node_parser = NodeSolc(node) node_parser = NodeSolc(node)
@ -1347,7 +1348,7 @@ class FunctionSolc(CallerContextExpression):
condition = st.condition condition = st.condition
if not condition: if not condition:
raise ParsingError( raise ParsingError(
f"Incorrect ternary conversion {node.expression} {node.source_mapping_str}" f"Incorrect ternary conversion {node.expression} {node.source_mapping}"
) )
true_expr = st.true_expression true_expr = st.true_expression
false_expr = st.false_expression false_expr = st.false_expression

@ -62,7 +62,7 @@ class ModifierSolc(FunctionSolc):
self._content_was_analyzed = True self._content_was_analyzed = True
if self.is_compact_ast: if self.is_compact_ast:
body = self._functionNotParsed["body"] body = self._functionNotParsed.get("body", None)
if body and body[self.get_key()] == "Block": if body and body[self.get_key()] == "Block":
self._function.is_implemented = True self._function.is_implemented = True

@ -14,21 +14,24 @@ from slither.core.expressions.binary_operation import (
BinaryOperation, BinaryOperation,
BinaryOperationType, BinaryOperationType,
) )
from slither.core.expressions.call_expression import CallExpression from slither.core.expressions import (
from slither.core.expressions.conditional_expression import ConditionalExpression CallExpression,
from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression ConditionalExpression,
from slither.core.expressions.identifier import Identifier ElementaryTypeNameExpression,
from slither.core.expressions.index_access import IndexAccess Identifier,
from slither.core.expressions.literal import Literal IndexAccess,
from slither.core.expressions.member_access import MemberAccess Literal,
from slither.core.expressions.new_array import NewArray MemberAccess,
from slither.core.expressions.new_contract import NewContract NewArray,
from slither.core.expressions.new_elementary_type import NewElementaryType NewContract,
from slither.core.expressions.super_call_expression import SuperCallExpression NewElementaryType,
from slither.core.expressions.super_identifier import SuperIdentifier SuperCallExpression,
from slither.core.expressions.tuple_expression import TupleExpression SuperIdentifier,
from slither.core.expressions.type_conversion import TypeConversion TupleExpression,
from slither.core.expressions.unary_operation import UnaryOperation, UnaryOperationType TypeConversion,
UnaryOperation,
UnaryOperationType,
)
from slither.core.solidity_types import ( from slither.core.solidity_types import (
ArrayType, ArrayType,
ElementaryType, ElementaryType,
@ -445,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] + ")"
@ -455,13 +458,14 @@ 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)
identifier = Identifier(var) identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit) identifier.set_offset(src, caller_context.compilation_unit)
var.references.append(identifier.source_mapping)
return identifier return identifier
if name == "IndexAccess": if name == "IndexAccess":
@ -515,6 +519,9 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
var.set_offset(src, caller_context.compilation_unit) var.set_offset(src, caller_context.compilation_unit)
sup = SuperIdentifier(var) sup = SuperIdentifier(var)
sup.set_offset(src, caller_context.compilation_unit) sup.set_offset(src, caller_context.compilation_unit)
var.references.append(sup.source_mapping)
return sup return sup
member_access = MemberAccess(member_name, member_type, member_expression) member_access = MemberAccess(member_name, member_type, member_expression)
member_access.set_offset(src, caller_context.compilation_unit) member_access.set_offset(src, caller_context.compilation_unit)
@ -636,12 +643,17 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
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, is_identifier_path=True
)
if was_created: if was_created:
var.set_offset(src, caller_context.compilation_unit) var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var) identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit) identifier.set_offset(src, caller_context.compilation_unit)
var.references.append(identifier.source_mapping)
return identifier return identifier
raise ParsingError("IdentifierPath not currently supported for the legacy ast") raise ParsingError("IdentifierPath not currently supported for the legacy ast")

@ -98,7 +98,7 @@ def _find_variable_in_function_parser(
return None return None
def _find_top_level( def find_top_level(
var_name: str, scope: "FileScope" var_name: str, scope: "FileScope"
) -> Tuple[ ) -> Tuple[
Optional[Union[Enum, Structure, SolidityImportPlaceHolder, CustomError, TopLevelVariable]], bool Optional[Union[Enum, Structure, SolidityImportPlaceHolder, CustomError, TopLevelVariable]], bool
@ -155,6 +155,7 @@ def _find_in_contract(
contract: Optional[Contract], contract: Optional[Contract],
contract_declarer: Optional[Contract], contract_declarer: Optional[Contract],
is_super: bool, is_super: bool,
is_identifier_path: bool = False,
) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure, CustomError]]: ) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure, CustomError]]:
if contract is None or contract_declarer is None: if contract is None or contract_declarer is None:
return None return None
@ -197,6 +198,13 @@ def _find_in_contract(
if var_name in modifiers: if var_name in modifiers:
return modifiers[var_name] return modifiers[var_name]
if is_identifier_path:
for sig, modifier in modifiers.items():
if "(" in sig:
sig = sig[0 : sig.find("(")]
if sig == var_name:
return modifier
# structures are looked on the contract declarer # structures are looked on the contract declarer
structures = contract.structures_as_dict structures = contract.structures_as_dict
if var_name in structures: if var_name in structures:
@ -216,7 +224,7 @@ def _find_in_contract(
custom_errors = contract.custom_errors custom_errors = contract.custom_errors
try: try:
for custom_error in custom_errors: for custom_error in custom_errors:
if var_name == custom_error.solidity_signature: if var_name in [custom_error.solidity_signature, custom_error.full_name]:
return custom_error return custom_error
except ValueError: except ValueError:
# This can happen as custom error sol signature might not have been built # This can happen as custom error sol signature might not have been built
@ -294,6 +302,7 @@ def find_variable(
caller_context: CallerContextExpression, caller_context: CallerContextExpression,
referenced_declaration: Optional[int] = None, referenced_declaration: Optional[int] = None,
is_super: bool = False, is_super: bool = False,
is_identifier_path: bool = False,
) -> Tuple[ ) -> Tuple[
Union[ Union[
Variable, Variable,
@ -321,6 +330,8 @@ def find_variable(
:type referenced_declaration: :type referenced_declaration:
:param is_super: :param is_super:
:type is_super: :type is_super:
:param is_identifier_path:
:type is_identifier_path:
:return: :return:
:rtype: :rtype:
""" """
@ -381,7 +392,7 @@ def find_variable(
else: else:
assert isinstance(underlying_func, FunctionTopLevel) assert isinstance(underlying_func, FunctionTopLevel)
ret = _find_in_contract(var_name, contract, contract_declarer, is_super) ret = _find_in_contract(var_name, contract, contract_declarer, is_super, is_identifier_path)
if ret: if ret:
return ret, False return ret, False
@ -402,7 +413,7 @@ def find_variable(
return SolidityFunction(var_name), False return SolidityFunction(var_name), False
# Top level must be at the end, if nothing else was found # Top level must be at the end, if nothing else was found
ret, var_was_created = _find_top_level(var_name, current_scope) ret, var_was_created = find_top_level(var_name, current_scope)
if ret: if ret:
return ret, var_was_created return ret, var_was_created

@ -401,7 +401,7 @@ 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:
@ -418,7 +418,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:
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:
@ -432,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:
@ -446,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
@ -456,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()
@ -679,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()

@ -112,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
@ -159,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
@ -195,6 +195,12 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
return UserDefinedType(var_type) return UserDefinedType(var_type)
def _add_type_references(type_found: Type, src: str, sl: "SlitherCompilationUnit"):
if isinstance(type_found, UserDefinedType):
type_found.type.add_reference_from_raw_source(src, sl)
# TODO: since the add of FileScope, we can probably refactor this function and makes it a lot simpler # TODO: since the add of FileScope, we can probably refactor this function and makes it a lot simpler
def parse_type( def parse_type(
t: Union[Dict, UnknownType], t: Union[Dict, UnknownType],
@ -267,19 +273,25 @@ def parse_type(
assert isinstance(custom_error, CustomErrorContract) assert isinstance(custom_error, CustomErrorContract)
scope = custom_error.contract.file_scope scope = custom_error.contract.file_scope
sl = caller_context.compilation_unit
next_context = caller_context.slither_parser next_context = caller_context.slither_parser
structures_direct_access = list(scope.structures.values()) structures_direct_access = list(scope.structures.values())
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 renaming = scope.renaming
user_defined_types = scope.user_defined_types user_defined_types = scope.user_defined_types
elif isinstance(caller_context, (ContractSolc, FunctionSolc)): elif isinstance(caller_context, (ContractSolc, FunctionSolc)):
sl = caller_context.compilation_unit
if isinstance(caller_context, FunctionSolc): if isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function underlying_func = caller_context.underlying_function
# If contract_parser is set to None, then underlying_function is a functionContract # If contract_parser is set to None, then underlying_function is a functionContract
@ -345,7 +357,7 @@ def parse_type(
name = renaming[name] name = renaming[name]
if name in user_defined_types: if name in user_defined_types:
return user_defined_types[name] return user_defined_types[name]
return _find_from_type_name( type_found = _find_from_type_name(
name, name,
functions, functions,
contracts, contracts,
@ -354,6 +366,8 @@ def parse_type(
enums_direct_access, enums_direct_access,
all_enums, all_enums,
) )
_add_type_references(type_found, t["src"], sl)
return type_found
# 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
@ -363,7 +377,7 @@ def parse_type(
name = renaming[name] name = renaming[name]
if name in user_defined_types: if name in user_defined_types:
return user_defined_types[name] return user_defined_types[name]
return _find_from_type_name( type_found = _find_from_type_name(
name, name,
functions, functions,
contracts, contracts,
@ -372,6 +386,8 @@ def parse_type(
enums_direct_access, enums_direct_access,
all_enums, all_enums,
) )
_add_type_references(type_found, t["src"], sl)
return type_found
# Introduced with Solidity 0.8 # Introduced with Solidity 0.8
if t[key] == "IdentifierPath": if t[key] == "IdentifierPath":
@ -381,7 +397,7 @@ def parse_type(
name = renaming[name] name = renaming[name]
if name in user_defined_types: if name in user_defined_types:
return user_defined_types[name] return user_defined_types[name]
return _find_from_type_name( type_found = _find_from_type_name(
name, name,
functions, functions,
contracts, contracts,
@ -390,6 +406,8 @@ def parse_type(
enums_direct_access, enums_direct_access,
all_enums, all_enums,
) )
_add_type_references(type_found, t["src"], sl)
return type_found
raise SlitherError("Solidity 0.8 not supported with the legacy AST") raise SlitherError("Solidity 0.8 not supported with the legacy AST")

@ -35,6 +35,7 @@ from slither.solc_parsing.yul.evm_functions import (
unary_ops, unary_ops,
binary_ops, binary_ops,
) )
from slither.solc_parsing.expressions.find_variable import find_top_level
from slither.visitors.expression.find_calls import FindCalls from slither.visitors.expression.find_calls import FindCalls
from slither.visitors.expression.read_var import ReadVar from slither.visitors.expression.read_var import ReadVar
from slither.visitors.expression.write_var import WriteVar from slither.visitors.expression.write_var import WriteVar
@ -797,6 +798,10 @@ def parse_yul_identifier(root: YulScope, _node: YulNode, ast: Dict) -> Optional[
if magic_suffix: if magic_suffix:
return magic_suffix return magic_suffix
ret, _ = find_top_level(name, root.contract.file_scope)
if ret:
return Identifier(ret)
raise SlitherException(f"unresolved reference to identifier {name}") raise SlitherException(f"unresolved reference to identifier {name}")

@ -9,7 +9,7 @@ logging.getLogger("Slither").setLevel(logging.INFO)
logger = logging.getLogger("Slither-demo") logger = logging.getLogger("Slither-demo")
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -26,7 +26,7 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
args = parse_args() args = parse_args()
# Perform slither analysis on the given filename # Perform slither analysis on the given filename

@ -1,6 +1,7 @@
import argparse import argparse
import logging import logging
from collections import defaultdict from collections import defaultdict
from typing import Any, Dict, List
from crytic_compile import cryticparser from crytic_compile import cryticparser
from slither import Slither from slither import Slither
@ -26,7 +27,7 @@ logger.propagate = False
ADDITIONAL_CHECKS = {"ERC20": check_erc20, "ERC1155": check_erc1155} ADDITIONAL_CHECKS = {"ERC20": check_erc20, "ERC1155": check_erc1155}
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -63,20 +64,20 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def _log_error(err, args): def _log_error(err: Any, args: argparse.Namespace) -> None:
if args.json: if args.json:
output_to_json(args.json, str(err), {"upgradeability-check": []}) output_to_json(args.json, str(err), {"upgradeability-check": []})
logger.error(err) logger.error(err)
def main(): def main() -> None:
args = parse_args() args = parse_args()
# Perform slither analysis on the given filename # Perform slither analysis on the given filename
slither = Slither(args.project, **vars(args)) slither = Slither(args.project, **vars(args))
ret = defaultdict(list) ret: Dict[str, List] = defaultdict(list)
if args.erc.upper() in ERCS: if args.erc.upper() in ERCS:

@ -1,7 +1,10 @@
import logging import logging
from typing import Dict, List, Optional, Set
from slither.core.declarations import Contract
from slither.slithir.operations import EventCall from slither.slithir.operations import EventCall
from slither.utils import output from slither.utils import output
from slither.utils.erc import ERC, ERC_EVENT
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, export_return_type_from_variable,
@ -11,7 +14,7 @@ logger = logging.getLogger("Slither-conformance")
# pylint: disable=too-many-locals,too-many-branches,too-many-statements # pylint: disable=too-many-locals,too-many-branches,too-many-statements
def _check_signature(erc_function, contract, ret): def _check_signature(erc_function: ERC, contract: Contract, ret: Dict) -> None:
name = erc_function.name name = erc_function.name
parameters = erc_function.parameters parameters = erc_function.parameters
return_type = erc_function.return_type return_type = erc_function.return_type
@ -51,7 +54,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
@ -146,7 +149,7 @@ def _check_signature(erc_function, contract, ret):
ret["missing_event_emmited"].append(missing_event_emmited.data) ret["missing_event_emmited"].append(missing_event_emmited.data)
def _check_events(erc_event, contract, ret): def _check_events(erc_event: ERC_EVENT, contract: Contract, ret: Dict[str, List]) -> None:
name = erc_event.name name = erc_event.name
parameters = erc_event.parameters parameters = erc_event.parameters
indexes = erc_event.indexes indexes = erc_event.indexes
@ -180,7 +183,13 @@ def _check_events(erc_event, contract, ret):
ret["missing_event_index"].append(missing_event_index.data) ret["missing_event_index"].append(missing_event_index.data)
def generic_erc_checks(contract, erc_functions, erc_events, ret, explored=None): def generic_erc_checks(
contract: Contract,
erc_functions: List[ERC],
erc_events: List[ERC_EVENT],
ret: Dict[str, List],
explored: Optional[Set[Contract]] = None,
) -> None:
if explored is None: if explored is None:
explored = set() explored = set()
@ -192,6 +201,7 @@ 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)
if erc_events:
logger.info("\n## Check events") logger.info("\n## Check events")
for erc_event in erc_events: for erc_event in erc_events:
_check_events(erc_event, contract, ret) _check_events(erc_event, contract, ret)

@ -18,7 +18,7 @@ logger = logging.getLogger("Slither")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -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"
) )
@ -100,7 +106,7 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
args = parse_args() args = parse_args()
slither = Slither(args.filename, **vars(args)) slither = Slither(args.filename, **vars(args))
@ -111,6 +117,7 @@ def main():
compilation_unit, compilation_unit,
external_to_public=args.convert_external, external_to_public=args.convert_external,
remove_assert=args.remove_assert, remove_assert=args.remove_assert,
convert_library_to_internal=args.convert_library_to_internal,
private_to_internal=args.convert_private, private_to_internal=args.convert_private,
export_path=args.dir, export_path=args.dir,
pragma_solidity=args.pragma_solidity, pragma_solidity=args.pragma_solidity,

@ -51,6 +51,7 @@ class Flattening:
compilation_unit: SlitherCompilationUnit, 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,
@ -61,6 +62,7 @@ class Flattening:
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
@ -105,23 +107,23 @@ class Flattening:
:return: :return:
""" """
src_mapping = contract.source_mapping src_mapping = contract.source_mapping
content = self._compilation_unit.core.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:
continue continue
if f.visibility == "external": if f.visibility == "external":
attributes_start = ( attributes_start = (
f.parameters_src().source_mapping["start"] f.parameters_src().source_mapping.start
+ f.parameters_src().source_mapping["length"] + f.parameters_src().source_mapping.length
) )
attributes_end = f.returns_src().source_mapping["start"] attributes_end = f.returns_src().source_mapping.start
attributes = content[attributes_start:attributes_end] attributes = content[attributes_start:attributes_end]
regex = re.search(r"((\sexternal)\s+)|(\sexternal)$|(\)external)$", attributes) regex = re.search(r"((\sexternal)\s+)|(\sexternal)$|(\)external)$", attributes)
if regex: if regex:
@ -136,8 +138,8 @@ class Flattening:
for var in f.parameters: for var in f.parameters:
if var.location == "calldata": if var.location == "calldata":
calldata_start = var.source_mapping["start"] calldata_start = var.source_mapping.start
calldata_end = calldata_start + var.source_mapping["length"] calldata_end = calldata_start + var.source_mapping.length
calldata_idx = content[calldata_start:calldata_end].find(" calldata ") calldata_idx = content[calldata_start:calldata_end].find(" calldata ")
to_patch.append( to_patch.append(
Patch( Patch(
@ -146,11 +148,41 @@ 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":
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]
regex = re.search(r" private ", attributes) regex = re.search(r" private ", attributes)
if regex: if regex:
@ -172,9 +204,9 @@ class Flattening:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction( if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"assert(bool)" "assert(bool)"
): ):
to_patch.append(Patch(node.source_mapping["start"], "line_removal")) to_patch.append(Patch(node.source_mapping.start, "line_removal"))
logger.info( logger.info(
f"Code commented: {node.expression} ({node.source_mapping_str})" f"Code commented: {node.expression} ({node.source_mapping})"
) )
to_patch.sort(key=lambda x: x.index, reverse=True) to_patch.sort(key=lambda x: x.index, reverse=True)
@ -186,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":

@ -16,7 +16,7 @@ logger.handlers[0].setFormatter(formatter)
logger.propagate = False logger.propagate = False
def parse_args(): def parse_args() -> argparse.Namespace:
""" """
Parse the underlying arguments for the program. Parse the underlying arguments for the program.
:return: Returns the arguments for the program. :return: Returns the arguments for the program.
@ -56,7 +56,7 @@ def parse_args():
return parser.parse_args() return parser.parse_args()
def main(): def main() -> None:
# ------------------------------ # ------------------------------
# Usage: slither-kspec-coverage contract kspec # Usage: slither-kspec-coverage contract kspec
# Example: slither-kspec-coverage contract.sol kspec.md # Example: slither-kspec-coverage contract.sol kspec.md

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

Loading…
Cancel
Save