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. 29
      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. 292
      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. 5
      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. 24
      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
uses: actions/checkout@v2
- name: Set up Python 3.6
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
pip install ".[dev]"
solc-select install all
solc-select use 0.8.11

@ -22,16 +22,13 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters

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

@ -26,21 +26,15 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[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 use 0.7.3
- 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
uses: actions/checkout@v2
- name: Set up Python 3.6
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install ".[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 use 0.8.0
@ -51,5 +45,8 @@ jobs:
- name: Test with pytest
run: |
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/test_functions_ids.py
pytest tests/test_function.py
pytest tests/test_source_mapping.py

@ -22,16 +22,13 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters

@ -26,20 +26,14 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
python setup.py install
pip install deepdiff
pip install pytest==7.0.1
pip install typing_extensions==4.1.1
pip install importlib_metadata==4.8.3
pip install "solc-select>=v1.0.0b1"
pip install ".[dev]"
- name: Install solc
run: |

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

@ -22,16 +22,13 @@ jobs:
- name: Checkout Code
uses: actions/checkout@v2
- name: Set up Python 3.6
- name: Set up Python 3.8
uses: actions/setup-python@v3
with:
python-version: 3.6
python-version: 3.8
- name: Install dependencies
run: |
pip install .
pip install deepdiff numpy
mkdir -p .github/linters
cp pyproject.toml .github/linters

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

3
.gitignore vendored

@ -107,3 +107,6 @@ ENV/
# Test results
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.
- Title your pull request with a brief description of what it's changing. "Fixes #123" is a good comment to add to the description, but makes for an unclear title on its own.
## Directory Structure
Below is a rough outline of slither's design:
```text
.
├── analyses # Provides additional info such as data dependency
├── core # Ties everything together
├── detectors # Rules that define and identify issues
├── slither.py # Main entry point
├── slithir # Contains the semantics of slither's intermediate representation
├── solc_parsing # Responsible for parsing the solc AST
├── tools # Miscellaneous tools built on top of slither
├── visitors # Parses expressions and converts to slithir
└── ...
```
A code walkthrough is available [here](https://www.youtube.com/watch?v=EUl3UlYSluU).
## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).
To run the unit tests, you need
- `deepdiff` installed (`pip install deepdiff`).
- `pycov` installed (`pip install pytest-cov`).
- [`solc-select`](https://github.com/crytic/solc-select) installed.
To run the unit tests, you need to clone this repository and run `pip install ".[dev]"`.
### Linters
@ -46,6 +61,7 @@ For each new detector, at least one regression tests must be present.
- Create a test in `tests`
- Update `ALL_TEST` in `tests/test_detectors.py`
- Run `python ./tests/test_detectors.py --generate`. This will generate the json artifacts in `tests/expected_json`. Add the generated files to git.
- If updating an existing detector, identify the respective json artifacts and then delete them, or run `python ./tests/test_detectors.py --overwrite` instead.
- Run `pytest ./tests/test_detectors.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`

@ -1,4 +1,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 src="https://github.com/trailofbits/slither"
@ -6,23 +21,31 @@ LABEL creator=trailofbits
LABEL dockerfile_maintenance=trailofbits
LABEL desc="Static Analyzer for Solidity"
RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y git python3 python3-setuptools wget software-properties-common
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get update \
&& apt-get 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 \
&& chmod +x solc-static-linux \
&& mv solc-static-linux /usr/bin/solc
# improve compatibility with amd64 solc in non-amd64 environments (e.g. Docker Desktop on M1 Mac)
ENV QEMU_LD_PREFIX=/usr/x86_64-linux-gnu
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
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
WORKDIR /home/slither/slither
RUN python3 setup.py install --user
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

@ -13,6 +13,7 @@ Slither is a Solidity static analysis framework written in Python 3. It runs a s
- [Tools](#tools)
- [How to Install](#how-to-install)
- [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications)
## Features
@ -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
2 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#shift-parameter-mixup) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High
4 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
5 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
6 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
@ -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
71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-are-too-similar) | Informational | Medium
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
75 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
76 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
77 | `arbitrary-send-erc20` | [Detect when `msg.sender` is not used as `from` in transferFrom](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20)
78 | `arbitrary-send-erc20-permit` | [Detect when `msg.sender` is not used as `from` in transferFrom in conjuction with permit](https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit)
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
@ -146,6 +149,7 @@ For more information, see
- `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
- `function-summary`: [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary)
- `vars-and-auth`: [Print the state variables written and the authorization of the functions](https://github.com/crytic/slither/wiki/Printer-documentation#variables-written-and-authorization)
- `when-not-paused`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
To run a printer, use `--print` and a comma-separated list of printers.
@ -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-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance)
- `slither-format`: [Automatic patch generation](https://github.com/crytic/slither/wiki/Slither-format)
- `slither-read-storage`: [Read storage values from contracts](./slither/tools/read_storage/README.md)
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
@ -165,7 +170,7 @@ See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documen
## How to install
Slither requires Python 3.6+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
Slither requires Python 3.8+ and [solc](https://github.com/ethereum/solidity/), the Solidity compiler.
### Using Pip
@ -184,7 +189,7 @@ We recommend using a Python virtual environment, as detailed in the [Developer I
### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash
docker pull trailofbits/eth-security-toolbox
@ -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.
## FAQ
How do I exclude mocks or tests?
- View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
How do I fix "unknown file" or compilation issues?
- Because slither requires the solc AST, it must have all dependencies available.
If a contract has dependencies, `slither contract.sol` will fail.
Instead, use `slither .` in the parent directory of `contracts/` (you should see `contracts/` when you run `ls`).
If you have a `node_modules/` folder, it must be in the same directory as `contracts/`. To verify that this issue is related to slither,
run the compilation command for the framework you are using e.g `npx hardhat compile`. That must work successfully;
otherwise, slither's compilation engine, crytic-compile, cannot generate the AST.
## License
Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailto:opensource@trailofbits.com) if you're looking for an exception to the terms.
## Publications
### Trail of Bits publication
@ -232,4 +249,4 @@ Title | Usage | Authors | Venue
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

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

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

@ -1,8 +1,13 @@
from typing import Tuple, List, Type
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_printers = []
plugin_printers: List[Type[AbstractPrinter]] = []
return plugin_detectors, plugin_printers

@ -13,8 +13,7 @@ test_slither(){
expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.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 [ $? -eq 255 ]
if ! slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json";
then
echo "Slither crashed"
exit 255
@ -40,8 +39,7 @@ test_slither(){
fi
# 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 [ $? -eq 255 ]
if ! slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json";
then
echo "Slither crashed"
exit 255

@ -4,17 +4,17 @@
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"
exit 1
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"
exit 1
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"
exit 1
fi

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

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

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

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

@ -1,6 +1,6 @@
#!/usr/bin/env bash
### Test slither-check-upgradability
### Test slither-check-upgradeability
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")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 1 failed"
echo "slither-check-kspec 1 failed"
cat test_1.txt
echo ""
cat "$DIR_TESTS/test_1.txt"

@ -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
pip3.6 install pybind11
pip3.6 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip
pip3.8 install pybind11
pip3.8 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip
### Test slither-simil

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

@ -10,16 +10,28 @@ setup(
author="Trail of Bits",
version="0.8.3",
packages=find_packages(),
python_requires=">=3.6",
python_requires=">=3.8",
install_requires=[
"prettytable>=0.7.2",
"pysha3>=1.0.2",
# "crytic-compile>=0.2.3",
"crytic-compile",
],
extras_require={
"dev": [
"black==22.3.0",
"pylint==2.13.4",
"pytest",
"pytest-cov",
"deepdiff",
"numpy",
"solc-select>=v1.0.0b1",
]
},
dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0",
long_description=long_description,
long_description_content_type="text/markdown",
entry_points={
"console_scripts": [
"slither = slither.__main__:main",

@ -10,11 +10,11 @@ import os
import pstats
import sys
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 crytic_compile import cryticparser
from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export
from crytic_compile.platform.etherscan import SUPPORTED_NETWORK
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.
@ -72,7 +77,12 @@ def process_single(target, args, 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))
slither_instances = []
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:
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
def process_from_asts(filenames, args, detector_classes, printer_classes):
all_contracts = []
# TODO: delete me?
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:
with open(filename, encoding="utf8") as file_open:
@ -137,35 +157,21 @@ def process_from_asts(filenames, args, detector_classes, printer_classes):
# endregion
###################################################################################
###################################################################################
# region Exit
###################################################################################
###################################################################################
def my_exit(results):
if not results:
sys.exit(0)
sys.exit(len(results))
# endregion
###################################################################################
###################################################################################
# region Detectors and printers
###################################################################################
###################################################################################
def get_detectors_and_printers():
"""
NOTE: This contains just a few detectors and printers that we made public.
"""
def get_detectors_and_printers() -> Tuple[
List[Type[AbstractDetector]], List[Type[AbstractPrinter]]
]:
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_ = [getattr(all_detectors, name) for name in dir(all_detectors)]
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 = [p for p in printers if inspect.isclass(p) and issubclass(p, AbstractPrinter)]
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)]
# Handle plugins!
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
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
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)
return detectors_to_run
if args.exclude_optimization:
if args.exclude_optimization and not args.fail_pedantic:
detectors_to_run = [
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 = [
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]
if args.exclude_medium:
if args.exclude_medium and not args.fail_medium:
detectors_to_run = [
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]
if args.detectors_to_exclude:
detectors_to_run = [
@ -239,7 +247,9 @@ def choose_detectors(args, all_detector_classes):
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 = []
# 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:
return args.filter_paths.split(",")
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 += "\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"],
)
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(
"--show-ignored-findings",
help="Show all the findings",
@ -485,6 +534,14 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
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(
"--solc-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"],
)
parser.add_argument(
"--ignore-return-value",
help=argparse.SUPPRESS,
action="store_true",
default=defaults_flag_in_config["ignore_return_value"],
)
parser.add_argument(
"--perf",
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
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()
detector_types_json = output_detectors_json(detectors)
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
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()
output_printers(printers)
parser.exit()
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()
assert isinstance(values, str)
output_to_markdown(detectors, printers, values)
parser.exit()
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()
assert isinstance(values, str)
output_wiki(detectors, values)
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
sys.setrecursionlimit(1500)
@ -644,7 +712,10 @@ def main():
# 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_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()
# Set colorization option
set_colorization_enabled(not args.disable_color)
set_colorization_enabled(False if args.disable_color else sys.stdout.isatty())
# Define some variables for potential JSON output
json_results = {}
@ -710,8 +781,8 @@ def main_impl(all_detector_classes, all_printer_classes):
crytic_compile_error.propagate = False
crytic_compile_error.setLevel(logging.INFO)
results_detectors = []
results_printers = []
results_detectors: List[Dict] = []
results_printers: List[Dict] = []
try:
filename = args.filename
@ -760,6 +831,7 @@ def main_impl(all_detector_classes, all_printer_classes):
if "compilations" in args.json_types:
compilation_results = []
for slither_instance in slither_instances:
assert slither_instance.crytic_compile
compilation_results.append(
generate_standard_export(slither_instance.crytic_compile)
)
@ -800,8 +872,6 @@ def main_impl(all_detector_classes, all_printer_classes):
len(detector_classes),
len(results_detectors),
)
if args.ignore_return_value:
return
except SlitherException as 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
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(output_error)
@ -835,16 +905,31 @@ def main_impl(all_detector_classes, all_printer_classes):
if outputting_zip:
output_to_zip(args.zip, output_error, json_results, args.zip_type)
if args.perf:
if args.perf and cp:
cp.disable()
stats = pstats.Stats(cp).sort_stats("cumtime")
stats.print_stats()
# Exit with the appropriate status code
if output_error:
if args.fail_high:
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)
else:
my_exit(results_detectors)
sys.exit(0)
if __name__ == "__main__":

@ -128,17 +128,17 @@ def _get_evm_instructions_node(node_info):
node_info["cfg"].instructions,
node_info["srcmap"],
node_info["slither"],
node_info["contract"].source_mapping["filename_absolute"],
node_info["contract"].source_mapping.filename.absolute,
)
contract_file = (
node_info["slither"]
.source_code[node_info["contract"].source_mapping["filename_absolute"]]
.source_code[node_info["contract"].source_mapping.filename.absolute]
.encode("utf-8")
)
# Get evm instructions corresponding to node's source line number
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_ins = []

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

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

@ -28,4 +28,4 @@ class ChildNode:
@property
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,
ERC1155_signatures,
ERC2612_signatures,
ERC1363_signatures,
ERC4524_signatures,
ERC4626_signatures,
)
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._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
@ -144,6 +147,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_interface(self, is_interface: bool):
self._is_interface = is_interface
@property
def is_library(self) -> bool:
return self._is_library
@is_library.setter
def is_library(self, is_library: bool):
self._is_library = is_library
# endregion
###################################################################################
###################################################################################
@ -639,6 +650,21 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return [f for f in self.functions if f.is_writing(variable)]
def get_function_from_full_name(self, full_name: str) -> Optional["Function"]:
"""
Return a function from a full name
The full name differs from the solidity's signature are the type are conserved
For example contract type are kept, structure are not unrolled, etc
Args:
full_name (str): signature of the function (without return statement)
Returns:
Function
"""
return next(
(f for f in self.functions if f.full_name == full_name and not f.is_shadowed),
None,
)
def get_function_from_signature(self, function_signature: str) -> Optional["Function"]:
"""
Return a function from a signature
@ -648,7 +674,11 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Function
"""
return next(
(f for f in self.functions if f.full_name == function_signature and not f.is_shadowed),
(
f
for f in self.functions
if f.solidity_signature == function_signature and not f.is_shadowed
),
None,
)
@ -903,6 +933,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
("ERC721", self.is_erc721),
("ERC777", self.is_erc777),
("ERC2612", self.is_erc2612),
("ERC1363", self.is_erc1363),
("ERC4626", self.is_erc4626),
]
@ -998,6 +1029,26 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
full_names = self.functions_signatures
return all(s in full_names for s in ERC2612_signatures)
def is_erc1363(self) -> bool:
"""
Check if the contract is an erc1363
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc1363
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC1363_signatures)
def is_erc4524(self) -> bool:
"""
Check if the contract is an erc4524
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc4524
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC4524_signatures)
@property
def is_token(self) -> bool:
"""
@ -1061,7 +1112,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_from_dependency(self) -> bool:
return self.compilation_unit.core.crytic_compile.is_dependency(
self.source_mapping["filename_absolute"]
self.source_mapping.filename.absolute
)
# 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.name == "Migrations":
paths = Path(self.source_mapping["filename_absolute"]).parts
paths = Path(self.source_mapping.filename.absolute).parts
if len(paths) >= 2:
return paths[-2] == "contracts" and paths[-1] == "migrations.sol"
return False

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

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

@ -2,7 +2,7 @@
Function module
"""
import logging
from abc import ABCMeta, abstractmethod
from abc import abstractmethod, ABCMeta
from collections import namedtuple
from enum import Enum
from itertools import groupby
@ -20,11 +20,11 @@ from slither.core.expressions import (
MemberAccess,
UnaryOperation,
)
from slither.core.solidity_types import UserDefinedType
from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
from slither.core.variables.state_variable import StateVariable
from slither.utils.type import convert_type_for_solidity_signature_to_string
from slither.utils.utils import unroll
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
@ -265,6 +265,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
str: func_name(type1,type2)
Return the function signature without the return values
The difference between this function and solidity_function is that full_name does not translate the underlying
type (ex: structure, contract to address, ...)
"""
if self._full_name is None:
name, parameters, _ = self.signature
@ -538,7 +540,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._nodes = nodes
@property
def entry_point(self) -> "Node":
def entry_point(self) -> Optional["Node"]:
"""
Node: Entry point of the function
"""
@ -952,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
def solidity_signature(self) -> str:
"""
Return a signature following the Solidity Standard
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
"""
if self._solidity_signature is None:
parameters = [
self._convert_type_for_solidity_signature(x.type) for x in self.parameters
convert_type_for_solidity_signature_to_string(x.type) for x in self.parameters
]
self._solidity_signature = self.name + "(" + ",".join(parameters) + ")"
return self._solidity_signature

@ -1,9 +1,9 @@
# https://solidity.readthedocs.io/en/v0.4.24/units-and-global-variables.html
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.solidity_types import ElementaryType, TypeInformation
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.exceptions import SlitherException
if TYPE_CHECKING:
@ -96,7 +96,7 @@ def solidity_function_signature(name):
return name + f" returns({','.join(SOLIDITY_FUNCTIONS[name])})"
class SolidityVariable(Context):
class SolidityVariable(SourceMapping):
def __init__(self, name: str):
super().__init__()
self._check_name(name)
@ -155,13 +155,14 @@ class SolidityVariableComposed(SolidityVariable):
return hash(self.name)
class SolidityFunction:
class SolidityFunction(SourceMapping):
# Non standard handling of type(address). This function returns an undefined object
# The type is dynamic
# https://solidity.readthedocs.io/en/latest/units-and-global-variables.html#type-information
# As a result, we set return_type during the Ir conversion
def __init__(self, name: str):
super().__init__()
assert name in SOLIDITY_FUNCTIONS
self._name = name
# 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
@ -8,10 +8,10 @@ if TYPE_CHECKING:
class Structure(SourceMapping):
def __init__(self, compilation_unit: "SlitherCompilationUnit"):
def __init__(self, compilation_unit: "SlitherCompilationUnit") -> None:
super().__init__()
self._name = None
self._canonical_name = None
self._name: Optional[str] = None
self._canonical_name: Optional[str] = None
self._elems: Dict[str, "StructureVariable"] = {}
# Name of the elements in the order of declaration
self._elems_ordered: List[str] = []
@ -19,25 +19,27 @@ class Structure(SourceMapping):
@property
def canonical_name(self) -> str:
assert self._canonical_name
return self._canonical_name
@canonical_name.setter
def canonical_name(self, name: str):
def canonical_name(self, name: str) -> None:
self._canonical_name = name
@property
def name(self) -> str:
assert self._name
return self._name
@name.setter
def name(self, new_name: str):
def name(self, new_name: str) -> None:
self._name = new_name
@property
def elems(self) -> Dict[str, "StructureVariable"]:
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)
@property
@ -47,5 +49,5 @@ class Structure(SourceMapping):
ret.append(self._elems[e])
return ret
def __str__(self):
def __str__(self) -> str:
return self.name

@ -1,7 +1,9 @@
from typing import Optional, Union, TYPE_CHECKING
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.integer_conversion import convert_string_to_int
if TYPE_CHECKING:
from slither.core.solidity_types.type import Type
@ -29,5 +31,14 @@ class Literal(Expression):
def __str__(self):
if 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
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):
def __init__(self, expressions):
def __init__(self, expressions: List[Expression]) -> None:
assert all(isinstance(x, Expression) for x in expressions if x)
super().__init__()
self._expressions = expressions
@ -13,6 +13,6 @@ class TupleExpression(Expression):
def expressions(self) -> List[Expression]:
return self._expressions
def __str__(self):
def __str__(self) -> str:
expressions_str = [str(e) for e in self.expressions]
return "(" + ",".join(expressions_str) + ")"

@ -4,17 +4,24 @@
import json
import logging
import os
import pathlib
import posixpath
import re
from collections import defaultdict
from typing import Optional, Dict, List, Set, Union
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.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.utils.colors import red
from slither.utils.source_mapping import get_definition, get_references, get_implementation
logger = logging.getLogger("Slither")
logging.basicConfig()
@ -47,7 +54,7 @@ class SlitherCore(Context):
self._previous_results_ids: Set[str] = set()
# Every slither object has a list of result from detector
# Because of the multiple compilation support, we might analyze
# Multiple time the same result, so we remove dupplicate
# Multiple time the same result, so we remove duplicates
self._currently_seen_resuts: Set[str] = set()
self._paths_to_filter: Set[str] = set()
@ -69,6 +76,16 @@ class SlitherCore(Context):
self._contracts: 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
def compilation_units(self) -> List[SlitherCompilationUnit]:
return list(self._compilation_units)
@ -158,6 +175,108 @@ class SlitherCore(Context):
for f in c.functions:
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
###################################################################################
###################################################################################
@ -208,7 +327,7 @@ class SlitherCore(Context):
- There is an ignore comment on the preceding line
"""
# Remove dupplicate due to the multiple compilation support
# Remove duplicate due to the multiple compilation support
if r["id"] in self._currently_seen_resuts:
return False
self._currently_seen_resuts.add(r["id"])
@ -218,8 +337,12 @@ class SlitherCore(Context):
for elem in r["elements"]
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
@ -240,6 +363,7 @@ class SlitherCore(Context):
if r["elements"] and matching:
return False
if self._show_ignored_findings:
return True
if self.has_ignore_comment(r):
@ -247,9 +371,13 @@ class SlitherCore(Context):
if r["id"] in self._previous_results_ids:
return False
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
return not r["description"] in [pr["description"] for pr in self._previous_results]
if r["description"] in [pr["description"] for pr in self._previous_results]:
return False
return True
def load_previous_results(self):
filename = self._previous_results_filename

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

@ -1,5 +1,5 @@
import itertools
from typing import Optional, Tuple
from typing import Tuple
from slither.core.solidity_types.type import Type
@ -151,7 +151,7 @@ class NonElementaryType(Exception):
class ElementaryType(Type):
def __init__(self, t):
def __init__(self, t: str) -> None:
if t not in ElementaryTypeName:
raise NonElementaryType
super().__init__()
@ -163,6 +163,10 @@ class ElementaryType(Type):
t = "bytes1"
self._type = t
@property
def is_dynamic(self) -> bool:
return self._type in ("bytes", "string")
@property
def type(self) -> str:
return self._type
@ -172,7 +176,7 @@ class ElementaryType(Type):
return self.type
@property
def size(self) -> Optional[int]:
def size(self) -> int:
"""
Return the size in bits
Return None if the size is not known
@ -190,7 +194,7 @@ class ElementaryType(Type):
return int(160)
if t.startswith("bytes") and t != "bytes":
return int(t[len("bytes") :]) * 8
return None
raise SlitherException(f"{t} does not have a size")
@property
def storage_size(self) -> Tuple[int, bool]:

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

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

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

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

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

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

@ -1,5 +1,8 @@
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
@ -7,137 +10,182 @@ if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
class SourceMapping(Context):
def __init__(self) -> None:
super().__init__()
# TODO create a namedtuple for the source mapping rather than a dict
self._source_mapping: Optional[Dict] = None
# self._start: Optional[int] = None
# self._length: Optional[int] = None
# self._filename_used: Optional[str] = None
# self._filename_relative: Optional[str] = None
# self._filename_absolute: Optional[str] = None
# self._filename_short: Optional[str] = None
# self._is_dependency: Optional[bool] = None
# self._lines: Optional[List[int]] = None
# self._starting_column: Optional[int] = None
# self._ending_column: Optional[int] = None
@property
def source_mapping(self) -> Optional[Dict]:
return self._source_mapping
@staticmethod
def _compute_line(
compilation_unit: "SlitherCompilationUnit", filename, start: int, length: int
) -> Tuple[List[int], int, int]:
"""
Compute line(s) numbers and starting/ending columns
from a start/end offset. All numbers start from 1.
Not done in an efficient way
"""
start_line, starting_column = compilation_unit.core.crytic_compile.get_line_from_offset(
filename, start
)
end_line, ending_column = compilation_unit.core.crytic_compile.get_line_from_offset(
filename, start + length
)
return list(range(start_line, end_line + 1)), starting_column, ending_column
def _convert_source_mapping(
self, offset: str, compilation_unit: "SlitherCompilationUnit"
): # pylint: disable=too-many-locals
"""
Convert a text offset to a real offset
see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings
Returns:
(dict): {'start':0, 'length':0, 'filename': 'file.sol'}
"""
sourceUnits = compilation_unit.source_units
position = re.findall("([0-9]*):([0-9]*):([-]?[0-9]*)", offset)
if len(position) != 1:
return {}
s, l, f = position[0]
s = int(s)
l = int(l)
f = int(f)
if f not in sourceUnits:
return {"start": s, "length": l}
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 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:
(lines, starting_column, ending_column) = self._compute_line(
compilation_unit, filename, s, l
)
else:
(lines, starting_column, ending_column) = ([], None, None)
# 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:
self.start: int = 0
self.length: int = 0
self.filename: Filename = Filename("", "", "", "")
self.is_dependency: bool = False
self.lines: List[int] = []
self.starting_column: int = 0
self.ending_column: int = 0
self.end: int = 0
self.compilation_unit: Optional["SlitherCompilationUnit"] = None
def to_json(self) -> Dict:
return {
"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,
"start": self.start,
"length": self.length,
# TODO investigate filename_used usecase
# It creates non-deterministic result
# As it sometimes refer to the relative and sometimes to the absolute
# "filename_used": self.filename.used,
"filename_relative": self.filename.relative,
"filename_absolute": self.filename.absolute,
"filename_short": self.filename.short,
"is_dependency": self.is_dependency,
"lines": self.lines,
"starting_column": self.starting_column,
"ending_column": self.ending_column,
}
def set_offset(self, offset: Union[Dict, str], compilation_unit: "SlitherCompilationUnit"):
if isinstance(offset, dict):
self._source_mapping = offset
else:
self._source_mapping = self._convert_source_mapping(offset, compilation_unit)
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=""):
lines = self.source_mapping.get("lines", None)
# 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_descr}{lines[0]}"
lines = f"{line_prefix}{line_descr}{lines[0]}"
else:
lines = f"#{line_descr}{lines[0]}-{line_descr}{lines[-1]}"
lines = f"{line_prefix}{line_descr}{lines[0]}-{line_descr}{lines[-1]}"
return lines
def source_mapping_to_markdown(self, markdown_root: str) -> str:
lines = self._get_lines_str(line_descr="L")
return f'{markdown_root}{self.source_mapping.get("filename_relative", "")}{lines}'
@property
def source_mapping_str(self) -> str:
def __str__(self) -> str:
lines = self._get_lines_str()
return f'{self.source_mapping.get("filename_short", "")}{lines}'
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
from a start/end offset. All numbers start from 1.
Not done in an efficient way
"""
start_line, starting_column = compilation_unit.core.crytic_compile.get_line_from_offset(
filename, start
)
end_line, ending_column = compilation_unit.core.crytic_compile.get_line_from_offset(
filename, start + length
)
return list(range(start_line, end_line + 1)), starting_column, ending_column
def _convert_source_mapping(
offset: str, compilation_unit: "SlitherCompilationUnit"
) -> Source: # pylint: disable=too-many-locals
"""
Convert a text offset to a real offset
see https://solidity.readthedocs.io/en/develop/miscellaneous.html#source-mappings
Returns:
(dict): {'start':0, 'length':0, 'filename': 'file.sol'}
"""
sourceUnits = compilation_unit.source_units
position = re.findall("([0-9]*):([0-9]*):([-]?[0-9]*)", offset)
if len(position) != 1:
return Source()
s, l, f = position[0]
s = int(s)
l = int(l)
f = int(f)
if f not in sourceUnits:
new_source = Source()
new_source.start = s
new_source.length = l
return new_source
filename_used = sourceUnits[f]
# If possible, convert the filename to its absolute/relative version
assert compilation_unit.core.crytic_compile
filename: Filename = compilation_unit.core.crytic_compile.filename_lookup(filename_used)
is_dependency = compilation_unit.core.crytic_compile.is_dependency(filename.absolute)
(lines, starting_column, ending_column) = _compute_line(compilation_unit, filename, s, l)
new_source = Source()
new_source.start = s
new_source.length = l
new_source.filename = filename
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
class SourceMapping(Context, metaclass=ABCMeta):
def __init__(self) -> None:
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
def add_reference_from_raw_source(
self, offset: str, compilation_unit: "SlitherCompilationUnit"
) -> None:
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.utils.type import export_nested_types_from_variable
from slither.core.variables.variable import Variable
if TYPE_CHECKING:
from slither.core.cfg.node import Node
@ -22,33 +21,6 @@ class StateVariable(ChildContract, Variable):
"""
return self.contract == contract
###################################################################################
###################################################################################
# region Signature
###################################################################################
###################################################################################
@property
def signature(self) -> Tuple[str, List[str], str]:
"""
Return the signature of the state variable as a function signature
:return: (str, list(str), list(str)), as (name, list parameters type, list return values type)
"""
return (
self.name,
[str(x) for x in export_nested_types_from_variable(self)],
str(self.type),
)
@property
def signature_str(self) -> str:
"""
Return the signature of the state variable as a function signature
:return: str: func_name(type1,type2) returns(type3)
"""
name, parameters, returnVars = self.signature
return name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")"
# endregion
###################################################################################
###################################################################################

@ -1,7 +1,7 @@
"""
Variable module
"""
from typing import Optional, TYPE_CHECKING, List, Union
from typing import Optional, TYPE_CHECKING, List, Union, Tuple
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.solidity_types.type import Type
@ -44,7 +44,7 @@ class Variable(SourceMapping):
return self._initial_expression
@expression.setter
def expression(self, expr: "Expression"):
def expression(self, expr: "Expression") -> None:
self._initial_expression = expr
@property
@ -66,7 +66,7 @@ class Variable(SourceMapping):
return not self._initialized
@property
def name(self) -> str:
def name(self) -> Optional[str]:
"""
str: variable name
"""
@ -97,7 +97,7 @@ class Variable(SourceMapping):
return self._is_reentrant
@is_reentrant.setter
def is_reentrant(self, is_reentrant: bool):
def is_reentrant(self, is_reentrant: bool) -> None:
self._is_reentrant = is_reentrant
@property
@ -105,7 +105,7 @@ class Variable(SourceMapping):
return self._write_protection
@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
@property
@ -116,12 +116,13 @@ class Variable(SourceMapping):
return self._visibility
@visibility.setter
def visibility(self, v: str):
def visibility(self, v: str) -> None:
self._visibility = v
def set_type(self, t):
def set_type(self, t: Optional[Union[List, Type, str]]) -> None:
if isinstance(t, str):
t = ElementaryType(t)
self._type = ElementaryType(t)
return
assert isinstance(t, (Type, list)) or t is None
self._type = t
@ -135,27 +136,46 @@ class Variable(SourceMapping):
return self._is_immutable
@is_immutable.setter
def is_immutable(self, immutablility: bool):
def is_immutable(self, immutablility: bool) -> None:
self._is_immutable = immutablility
###################################################################################
###################################################################################
# region Signature
###################################################################################
###################################################################################
@property
def function_name(self):
def signature(self) -> Tuple[str, List[str], List[str]]:
"""
Return the name of the variable as a function signature
:return:
Return the signature of the state variable as a function signature
:return: (str, list(str), list(str)), as (name, list parameters type, list return values type)
"""
# pylint: disable=import-outside-toplevel
from slither.core.solidity_types import ArrayType, MappingType
from slither.utils.type import export_nested_types_from_variable
from slither.utils.type import (
export_nested_types_from_variable,
export_return_type_from_variable,
)
variable_getter_args = ""
return_type = self.type
assert return_type
return (
self.name,
[str(x) for x in export_nested_types_from_variable(self)],
[str(x) for x in export_return_type_from_variable(self)],
)
if isinstance(return_type, (ArrayType, MappingType)):
variable_getter_args = ",".join(map(str, export_nested_types_from_variable(self)))
@property
def signature_str(self) -> str:
"""
Return the signature of the state variable as a function signature
:return: str: func_name(type1,type2) returns(type3)
"""
name, parameters, returnVars = self.signature
return name + "(" + ",".join(parameters) + ") returns(" + ",".join(returnVars) + ")"
return f"{self.name}({variable_getter_args})"
@property
def solidity_signature(self) -> str:
name, parameters, _ = self.signature
return f'{name}({",".join(parameters)})'
def __str__(self):
def __str__(self) -> str:
return self._name

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

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

@ -14,7 +14,7 @@ from slither.formatters.attributes.incorrect_solc import custom_format
# 4: version number
# pylint: disable=anomalous-backslash-in-string
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
PATTERN = re.compile(r"(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
class IncorrectSolc(AbstractDetector):
@ -43,7 +43,14 @@ Deploy with any of the following Solidity versions:
- 0.5.16 - 0.5.17
- 0.6.11 - 0.6.12
- 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.
Consider using the latest version of Solidity for testing."""
# 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.
ALLOWED_VERSIONS = [
"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",
]
ALLOWED_VERSIONS = ["0.5.16", "0.5.17", "0.6.11", "0.6.12", "0.7.5", "0.7.6", "0.8.16"]
# Indicates the versions that should not be used.
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.slithir.operations import SolidityCall
from slither.slithir.operations import InternalCall, InternalDynamicCall
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):
@ -20,13 +28,11 @@ class ExternalFunction(AbstractDetector):
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_DESCRIPTION = "`public` functions that are never called by the contract should be declared `external` to save gas."
WIKI_RECOMMENDATION = (
"Use the `external` attribute for functions never called from the contract."
)
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 = "Use the `external` attribute for functions never called from the contract, and change the location of immutable parameters to `calldata` to save gas."
@staticmethod
def detect_functions_called(contract):
def detect_functions_called(contract: Contract) -> List[Function]:
"""Returns a list of InternallCall, SolidityCall
calls made in a function
@ -37,6 +43,8 @@ class ExternalFunction(AbstractDetector):
# Obtain all functions reachable by this contract.
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.
for node in func.nodes:
for ir in node.irs:
@ -45,7 +53,7 @@ class ExternalFunction(AbstractDetector):
return result
@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.
@ -53,6 +61,8 @@ class ExternalFunction(AbstractDetector):
(boolean): True if this contract contains a dynamic call (including through inheritance).
"""
for func in contract.all_functions_called:
if not isinstance(func, Function):
continue
for node in func.nodes:
for ir in node.irs:
if isinstance(ir, (InternalDynamicCall)):
@ -60,7 +70,7 @@ class ExternalFunction(AbstractDetector):
return False
@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
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.")
@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
overrides of that function.
@ -99,22 +111,45 @@ class ExternalFunction(AbstractDetector):
for derived_contract in base_most_function.contract.derived_contracts
for function in derived_contract.functions
if function.full_name == base_most_function.full_name
and isinstance(function, FunctionContract)
]
@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)
def _detect(self): # pylint: disable=too-many-locals,too-many-branches
results = []
@staticmethod
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
# 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
# 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
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.
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 function.is_constructor or function.visibility != "public":
continue
@ -189,10 +232,12 @@ class ExternalFunction(AbstractDetector):
# As we collect all shadowed functions in get_all_function_definitions
# Some function coming from a base might already been declared as external
all_function_definitions = [
all_function_definitions: List[FunctionContract] = [
f
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:
all_function_definitions = sorted(
@ -203,6 +248,12 @@ class ExternalFunction(AbstractDetector):
info = [f"{function_definition.full_name} should be declared external:\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:
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.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations import Contract
@ -22,7 +22,11 @@ def detect_call_in_loop(contract: Contract) -> List[Node]:
return ret
def call_in_loop(node: Node, in_loop_counter: int, visited: List[Node], ret: List[Node]) -> None:
def call_in_loop(
node: Optional[Node], in_loop_counter: int, visited: List[Node], ret: List[Node]
) -> None:
if node is None:
return
if node in visited:
return
# shared visited

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

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

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

@ -22,11 +22,20 @@ def _can_be_destroyed(contract: Contract) -> List[Function]:
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 m in f.modifiers:
if m.name == "initializer":
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
@ -82,7 +91,7 @@ class UnprotectedUpgradeable(AbstractDetector):
for contract in self.compilation_unit.contracts_derived:
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)
if functions_that_can_destroy:
initialize_functions = _initialize_functions(contract)
@ -102,7 +111,7 @@ class UnprotectedUpgradeable(AbstractDetector):
info = (
[
contract,
" is an upgradeable contract that does not protect its initiliaze functions: ",
" is an upgradeable contract that does not protect its initialize functions: ",
]
+ initialize_functions
+ [

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

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

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

@ -16,7 +16,7 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result):
element["type_specific_fields"]["parent"]["name"]
)
if target_contract:
function = target_contract.get_function_from_signature(
function = target_contract.get_function_from_full_name(
element["type_specific_fields"]["signature"]
)
if function:
@ -25,10 +25,10 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result):
result,
element["source_mapping"]["filename_absolute"],
int(
function.parameters_src().source_mapping["start"]
+ function.parameters_src().source_mapping["length"]
function.parameters_src().source_mapping.start
+ 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
# 3: version number
# 4: version number
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
PATTERN = re.compile(r"(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
def custom_format(slither, result):

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

@ -12,16 +12,16 @@ def custom_format(compilation_unit: SlitherCompilationUnit, result):
element["type_specific_fields"]["parent"]["name"]
)
if target_contract:
function = target_contract.get_function_from_signature(
function = target_contract.get_function_from_full_name(
element["type_specific_fields"]["signature"]
)
if function:
_patch(
compilation_unit,
file_scope,
result,
element["source_mapping"]["filename_absolute"],
int(function.parameters_src().source_mapping["start"]),
int(function.returns_src().source_mapping["start"]),
int(function.parameters_src().source_mapping.start),
int(function.returns_src().source_mapping.start),
)

@ -1,6 +1,8 @@
import re
import logging
from typing import List
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.slithir.operations import (
Send,
@ -10,6 +12,7 @@ from slither.slithir.operations import (
LowLevelCall,
InternalCall,
InternalDynamicCall,
Operation,
)
from slither.core.declarations import Modifier
from slither.core.solidity_types import UserDefinedType, MappingType
@ -254,7 +257,7 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
]
param_name = element["name"]
contract = scope.get_contract_from_name(contract_name)
function = contract.get_function_from_signature(function_sig)
function = contract.get_function_from_full_name(function_sig)
target = function.get_local_variable_from_name(param_name)
elif _target in ["variable", "variable_constant"]:
@ -268,7 +271,7 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
]
var_name = element["name"]
contract = scope.get_contract_from_name(contract_name)
function = contract.get_function_from_signature(function_sig)
function = contract.get_function_from_full_name(function_sig)
target = function.get_local_variable_from_name(var_name)
# State variable
else:
@ -300,10 +303,10 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
# group 2: beginning of the to type
# nested mapping are within the group 1
# RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)'
RE_MAPPING_FROM = b"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = b"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING_FROM = rb"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = rb"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING = (
b"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + b"\)"
rb"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + rb"\)"
)
@ -418,9 +421,9 @@ def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-ma
):
for variable in variables:
# First explore the type of the variable
filename_source_code = variable.source_mapping["filename_absolute"]
full_txt_start = variable.source_mapping["start"]
full_txt_end = full_txt_start + variable.source_mapping["length"]
filename_source_code = variable.source_mapping.filename.absolute
full_txt_start = variable.source_mapping.start
full_txt_end = full_txt_start + variable.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
@ -433,7 +436,7 @@ def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-ma
variable.type,
filename_source_code,
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
@ -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
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
end_line = func.source_mapping["lines"][0]
end_line = func.source_mapping.lines[0]
if variable in func.parameters:
idx = len(func.parameters) - func.parameters.index(variable) + 1
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
]
idx_beginning = func.source_mapping["start"]
idx_beginning += -func.source_mapping["starting_column"] + 1
idx_beginning = func.source_mapping.start
idx_beginning += -func.source_mapping.starting_column + 1
idx_beginning += -sum([len(c) for c in potential_comments])
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
new_str = convert(old_str, slither)
filename_source_code = st.source_mapping["filename_absolute"]
full_txt_start = st.source_mapping["start"]
full_txt_end = full_txt_start + st.source_mapping["length"]
filename_source_code = st.source_mapping.filename.absolute
full_txt_start = st.source_mapping.start
full_txt_end = full_txt_start + st.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[
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 event == target:
filename_source_code = event.source_mapping["filename_absolute"]
filename_source_code = event.source_mapping.filename.absolute
old_str = event.name
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)
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]
def _explore_irs(slither, irs, result, target, convert):
def _explore_irs(slither, irs: List[Operation], result, target, convert):
# pylint: disable=too-many-locals
if irs is None:
return
@ -562,9 +565,9 @@ def _explore_irs(slither, irs, result, target, convert):
and v.canonical_name == target.canonical_name
):
source_mapping = ir.expression.source_mapping
filename_source_code = source_mapping["filename_absolute"]
full_txt_start = source_mapping["start"]
full_txt_end = full_txt_start + source_mapping["length"]
filename_source_code = source_mapping.filename.absolute
full_txt_start = source_mapping.start
full_txt_end = full_txt_start + source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
@ -606,9 +609,9 @@ def _explore_functions(slither, functions, result, target, convert):
old_str = function.name
new_str = convert(old_str, slither)
filename_source_code = function.source_mapping["filename_absolute"]
full_txt_start = function.source_mapping["start"]
full_txt_end = full_txt_start + function.source_mapping["length"]
filename_source_code = function.source_mapping.filename.absolute
full_txt_start = function.source_mapping.start
full_txt_end = full_txt_start + function.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]
@ -631,9 +634,9 @@ def _explore_enums(slither, enums, result, target, convert):
old_str = enum.name
new_str = convert(old_str, slither)
filename_source_code = enum.source_mapping["filename_absolute"]
full_txt_start = enum.source_mapping["start"]
full_txt_end = full_txt_start + enum.source_mapping["length"]
filename_source_code = enum.source_mapping.filename.absolute
full_txt_start = enum.source_mapping.start
full_txt_end = full_txt_start + enum.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[
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)
if contract == target:
filename_source_code = contract.source_mapping["filename_absolute"]
full_txt_start = contract.source_mapping["start"]
full_txt_end = full_txt_start + contract.source_mapping["length"]
filename_source_code = contract.source_mapping.filename.absolute
full_txt_start = contract.source_mapping.start
full_txt_end = full_txt_start + contract.source_mapping.length
full_txt = slither.source_code[filename_source_code].encode("utf8")[
full_txt_start:full_txt_end
]

@ -18,3 +18,5 @@ from .summary.constructor_calls import ConstructorPrinter
from .guidance.echidna import Echidna
from .summary.evm import PrinterEVM
from .summary.when_not_paused import PrinterWhenNotPaused
from .summary.declaration import Declaration
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

@ -37,8 +37,7 @@ def _get_name(f: Union[Function, Variable]) -> str:
if isinstance(f, Function):
if f.is_fallback or f.is_receive:
return "()"
return f.solidity_signature
return f.function_name
return f.solidity_signature
def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]:
@ -117,7 +116,7 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
for contract in slither.contracts:
cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)]
cst_functions += [
v.function_name for v in contract.state_variables if v.visibility in ["public"]
v.solidity_signature for v in contract.state_variables if v.visibility in ["public"]
]
if cst_functions:
ret[contract.name] = cst_functions

@ -1,6 +1,7 @@
"""
Module printing summary of the contract
"""
from slither.core.source_mapping.source_mapping import Source
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils import output
@ -11,11 +12,11 @@ class ConstructorPrinter(AbstractPrinter):
HELP = "Print the constructors executed"
def _get_soruce_code(self, cst):
src_mapping = cst.source_mapping
content = self.slither.source_code[src_mapping["filename_absolute"]]
start = src_mapping["start"]
end = src_mapping["start"] + src_mapping["length"]
initial_space = src_mapping["starting_column"]
src_mapping: Source = cst.source_mapping
content = self.slither.source_code[src_mapping.filename.absolute]
start = src_mapping.start
end = src_mapping.start + src_mapping.length
initial_space = src_mapping.starting_column
return " " * initial_space + content[start:end]
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,
contract_srcmap_runtime,
slither,
contract.source_mapping["filename_absolute"],
contract.source_mapping.filename.absolute,
)
contract_bytecode_init = (
@ -51,7 +51,7 @@ def _extract_evm_info(slither):
cfg_init.instructions,
contract_srcmap_init,
slither,
contract.source_mapping["filename_absolute"],
contract.source_mapping.filename.absolute,
)
return evm_info
@ -83,9 +83,9 @@ class PrinterEVM(AbstractPrinter):
txt += blue(f"Contract {contract.name}\n")
contract_file = self.slither.source_code[
contract.source_mapping["filename_absolute"]
contract.source_mapping.filename.absolute
].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_pcs = {}
@ -105,8 +105,7 @@ class PrinterEVM(AbstractPrinter):
for node in function.nodes:
txt += green("\t\tNode: " + str(node) + "\n")
node_source_line = (
contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8"))
+ 1
contract_file[0 : node.source_mapping.start].count("\n".encode("utf-8")) + 1
)
txt += green(
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:
txt += green("\t\tNode: " + str(node) + "\n")
node_source_line = (
contract_file[0 : node.source_mapping["start"]].count("\n".encode("utf-8"))
+ 1
contract_file[0 : node.source_mapping.start].count("\n".encode("utf-8")) + 1
)
txt += green(
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"
table = MyPrettyTable(["Name", "ID"])
for function in contract.functions:
if function.is_shadowed or function.is_constructor_variables:
continue
if function.visibility in ["public", "external"]:
function_id = get_function_id(function.solidity_signature)
table.add_row([function.solidity_signature, f"{function_id:#0{10}x}"])
for variable in contract.state_variables:
if variable.visibility in ["public"]:
sig = variable.function_name
sig = variable.solidity_signature
function_id = get_function_id(sig)
table.add_row([sig, f"{function_id:#0{10}x}"])
txt += str(table) + "\n"

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

@ -1,5 +1,5 @@
import logging
from typing import Union, List, ValuesView
from typing import Union, List, ValuesView, Type, Dict
from crytic_compile import CryticCompile, InvalidCompilation
@ -19,7 +19,9 @@ logger_detector = logging.getLogger("Detectors")
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:
raise Exception(
@ -53,7 +55,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
Keyword Args:
solc (str): solc binary location (default 'solc')
disable_solc_warnings (bool): True to disable solc warnings (default false)
solc_arguments (str): solc arguments (default '')
solc_args (str): solc arguments (default '')
ast_format (str): ast format (default '--ast-compact-json')
filter_paths (list(str)): list of path to filter (default [])
triage_mode (bool): if true, switch to triage mode (default false)
@ -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_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__()
@ -76,6 +81,8 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
self._skip_assembly: bool = kwargs.get("skip_assembly", False)
self._show_ignored_findings: bool = kwargs.get("show_ignored_findings", False)
self.line_prefix = kwargs.get("change_line_prefix", "#")
self._parsers: List[SlitherCompilationUnitSolc] = []
try:
if isinstance(target, CryticCompile):
@ -173,7 +180,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
def detectors_optimization(self):
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`.
"""
@ -183,7 +190,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
instance = detector_class(compilation_unit, self, logger_detector)
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`.
"""
@ -192,7 +199,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
instance = printer_class(self, logger_printer)
self._printers.append(instance)
def run_detectors(self):
def run_detectors(self) -> List[Dict]:
"""
:return: List of registered detectors results.
"""

@ -33,6 +33,7 @@ from slither.core.solidity_types.elementary_type import (
MaxValues,
)
from slither.core.solidity_types.type import Type
from slither.core.solidity_types.type_alias import TypeAlias
from slither.core.variables.function_type_variable import FunctionTypeVariable
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.variable import Variable
@ -399,6 +400,13 @@ def propagate_type_and_convert_call(result, node):
ins = new_ins
result[idx] = ins
# If there's two consecutive type conversions, keep only the last
# ARG_CALL x call_data = [x]
# TMP_4 = CONVERT x to Fix call_data = []
# TMP_5 = CONVERT TMP_4 to uint192 call_data = [TMP_5]
if isinstance(ins, TypeConversion) and ins.variable in call_data:
call_data.remove(ins.variable)
if isinstance(ins, Argument):
# In case of dupplicate arguments we overwrite the value
# This can happen because of addr.call.value(1).value(2)
@ -531,7 +539,9 @@ def propagate_types(ir, node: "Node"): # pylint: disable=too-many-locals
return convert_type_of_high_and_internal_level_call(ir, contract)
# Convert HighLevelCall to LowLevelCall
if isinstance(t, ElementaryType) and t.name == "address":
if (isinstance(t, ElementaryType) and t.name == "address") or (
isinstance(t, TypeAlias) and t.underlying_type.name == "address"
):
# Cannot be a top level function with this.
assert isinstance(node_function, FunctionContract)
if ir.destination.name == "this":
@ -817,12 +827,28 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
# lib L { event E()}
# ...
# emit L.E();
if str(ins.ori.variable_right) in [f.name for f in ins.ori.variable_left.events]:
if str(ins.ori.variable_right) in ins.ori.variable_left.events_as_dict:
eventcall = EventCall(ins.ori.variable_right)
eventcall.set_expression(ins.expression)
eventcall.call_id = ins.call_id
return eventcall
# lib Lib { error Error()} ... revert Lib.Error()
if str(ins.ori.variable_right) in ins.ori.variable_left.custom_errors_as_dict:
custom_error = ins.ori.variable_left.custom_errors_as_dict[
str(ins.ori.variable_right)
]
assert isinstance(
custom_error,
CustomError,
)
sol_function = SolidityCustomRevert(custom_error)
solidity_call = SolidityCall(
sol_function, ins.nbr_arguments, ins.lvalue, ins.type_call
)
solidity_call.set_expression(ins.expression)
return solidity_call
libcall = LibraryCall(
ins.ori.variable_left,
ins.ori.variable_right,
@ -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
# 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(
Path(current_path, ins.ori.variable_left.import_directive.filename).absolute()
)
top_level_function_targets = [
f
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 len(f.parameters) == ins.nbr_arguments
]
@ -1369,7 +1395,11 @@ def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract):
if len(candidates) == 1:
func = candidates[0]
if func is None:
# We can discard if there are arguments here because libraries only support constant variables
# And constant variables cannot have non-value type
# i.e. "uint[2] constant arr = [1,2];" is not possible in Solidity
# If this were to change, the following condition might be broken
if func is None and not ir.arguments:
# TODO: handle collision with multiple state variables/functions
func = lib_contract.get_state_variable_from_name(ir.function_name)
if func is None and candidates:
@ -1657,7 +1687,10 @@ def convert_constant_types(irs):
if isinstance(ir.rvalue, TupleVariable):
# TODO: fix missing Unpack conversion
continue
if ir.rvalue.type.type not in ElementaryTypeInt:
if isinstance(ir.rvalue.type, TypeAlias):
ir.rvalue.set_type(ElementaryType(ir.lvalue.type.name))
was_changed = True
elif ir.rvalue.type.type not in ElementaryTypeInt:
ir.rvalue.set_type(ElementaryType(ir.lvalue.type.type))
was_changed = True
if isinstance(ir, Binary):
@ -1676,6 +1709,7 @@ def convert_constant_types(irs):
# TODO: add POP instruction
break
types = [p.type for p in func.parameters]
assert len(types) == len(ir.arguments)
for idx, arg in enumerate(ir.arguments):
t = types[idx]
if isinstance(t, ElementaryType):
@ -1723,6 +1757,21 @@ def convert_delete(irs):
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
###################################################################################
###################################################################################
@ -1744,4 +1793,6 @@ def apply_ir_heuristics(irs, node):
convert_constant_types(irs)
convert_delete(irs)
_find_source_mapping_references(irs)
return irs

@ -1,8 +1,10 @@
from typing import Optional, List
from slither.slithir.operations.operation import Operation
class Call(Operation):
def __init__(self):
def __init__(self) -> None:
super().__init__()
self._arguments = []
@ -14,14 +16,14 @@ class Call(Operation):
def arguments(self, 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
:return: bool
"""
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
:return: bool

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

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

@ -1,4 +1,3 @@
from decimal import Decimal
from functools import total_ordering
from slither.core.solidity_types.elementary_type import ElementaryType, Int, Uint
@ -33,7 +32,7 @@ class Constant(SlithIRVariable):
else:
if val.isdigit():
self._type = ElementaryType("uint256")
self._val = int(Decimal(val))
self._val = convert_string_to_int(val)
else:
self._type = ElementaryType("string")
self._val = val
@ -75,3 +74,6 @@ class Constant(SlithIRVariable):
def __repr__(self):
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 attributes["contractKind"] == "interface":
self._contract.is_interface = True
self._contract.kind = attributes["contractKind"]
elif attributes["contractKind"] == "library":
self._contract.is_library = True
self._contract.contract_kind = attributes["contractKind"]
self._linearized_base_contracts = attributes["linearizedBaseContracts"]
# self._contract.fullyImplemented = attributes["fullyImplemented"]

@ -11,6 +11,7 @@ from slither.core.declarations.function import (
)
from slither.core.declarations.function_contract import FunctionContract
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_init_from_tuple import LocalVariableInitFromTuple
from slither.solc_parsing.cfg.node import NodeSolc
@ -319,7 +320,7 @@ class FunctionSolc(CallerContextExpression):
###################################################################################
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:
node = self._function.new_node(node_type, src, scope)
node_parser = NodeSolc(node)
@ -1347,7 +1348,7 @@ class FunctionSolc(CallerContextExpression):
condition = st.condition
if not condition:
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
false_expr = st.false_expression

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

@ -14,21 +14,24 @@ from slither.core.expressions.binary_operation import (
BinaryOperation,
BinaryOperationType,
)
from slither.core.expressions.call_expression import CallExpression
from slither.core.expressions.conditional_expression import ConditionalExpression
from slither.core.expressions.elementary_type_name_expression import ElementaryTypeNameExpression
from slither.core.expressions.identifier import Identifier
from slither.core.expressions.index_access import IndexAccess
from slither.core.expressions.literal import Literal
from slither.core.expressions.member_access import MemberAccess
from slither.core.expressions.new_array import NewArray
from slither.core.expressions.new_contract import NewContract
from slither.core.expressions.new_elementary_type import NewElementaryType
from slither.core.expressions.super_call_expression import SuperCallExpression
from slither.core.expressions.super_identifier import SuperIdentifier
from slither.core.expressions.tuple_expression import TupleExpression
from slither.core.expressions.type_conversion import TypeConversion
from slither.core.expressions.unary_operation import UnaryOperation, UnaryOperationType
from slither.core.expressions import (
CallExpression,
ConditionalExpression,
ElementaryTypeNameExpression,
Identifier,
IndexAccess,
Literal,
MemberAccess,
NewArray,
NewContract,
NewElementaryType,
SuperCallExpression,
SuperIdentifier,
TupleExpression,
TypeConversion,
UnaryOperation,
UnaryOperationType,
)
from slither.core.solidity_types import (
ArrayType,
ElementaryType,
@ -445,7 +448,7 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
t = expression["attributes"]["type"]
if t:
found = re.findall("[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t)
found = re.findall(r"[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t)
assert len(found) <= 1
if found:
value = value + "(" + found[0] + ")"
@ -455,13 +458,14 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
referenced_declaration = expression["referencedDeclaration"]
else:
referenced_declaration = None
var, was_created = find_variable(value, caller_context, referenced_declaration)
if was_created:
var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit)
var.references.append(identifier.source_mapping)
return identifier
if name == "IndexAccess":
@ -515,6 +519,9 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
var.set_offset(src, caller_context.compilation_unit)
sup = SuperIdentifier(var)
sup.set_offset(src, caller_context.compilation_unit)
var.references.append(sup.source_mapping)
return sup
member_access = MemberAccess(member_name, member_type, member_expression)
member_access.set_offset(src, caller_context.compilation_unit)
@ -636,12 +643,17 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
else:
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:
var.set_offset(src, caller_context.compilation_unit)
identifier = Identifier(var)
identifier.set_offset(src, caller_context.compilation_unit)
var.references.append(identifier.source_mapping)
return identifier
raise ParsingError("IdentifierPath not currently supported for the legacy ast")

@ -98,7 +98,7 @@ def _find_variable_in_function_parser(
return None
def _find_top_level(
def find_top_level(
var_name: str, scope: "FileScope"
) -> Tuple[
Optional[Union[Enum, Structure, SolidityImportPlaceHolder, CustomError, TopLevelVariable]], bool
@ -155,6 +155,7 @@ def _find_in_contract(
contract: Optional[Contract],
contract_declarer: Optional[Contract],
is_super: bool,
is_identifier_path: bool = False,
) -> Optional[Union[Variable, Function, Contract, Event, Enum, Structure, CustomError]]:
if contract is None or contract_declarer is None:
return None
@ -197,6 +198,13 @@ def _find_in_contract(
if var_name in modifiers:
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 = contract.structures_as_dict
if var_name in structures:
@ -216,7 +224,7 @@ def _find_in_contract(
custom_errors = contract.custom_errors
try:
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
except ValueError:
# This can happen as custom error sol signature might not have been built
@ -294,6 +302,7 @@ def find_variable(
caller_context: CallerContextExpression,
referenced_declaration: Optional[int] = None,
is_super: bool = False,
is_identifier_path: bool = False,
) -> Tuple[
Union[
Variable,
@ -321,6 +330,8 @@ def find_variable(
:type referenced_declaration:
:param is_super:
:type is_super:
:param is_identifier_path:
:type is_identifier_path:
:return:
:rtype:
"""
@ -381,7 +392,7 @@ def find_variable(
else:
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:
return ret, False
@ -402,7 +413,7 @@ def find_variable(
return SolidityFunction(var_name), False
# 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:
return ret, var_was_created

@ -401,7 +401,7 @@ Please rename it, this name is reserved for Slither's internals"""
father_constructors = []
# try:
# Resolve linearized base contracts.
missing_inheritance = False
missing_inheritance = None
for i in contract_parser.linearized_base_contracts[1:]:
if i in contract_parser.remapping:
@ -418,7 +418,7 @@ Please rename it, this name is reserved for Slither's internals"""
elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i])
else:
missing_inheritance = True
missing_inheritance = i
# Resolve immediate base contracts
for i in contract_parser.baseContracts:
@ -432,7 +432,7 @@ Please rename it, this name is reserved for Slither's internals"""
elif i in self._contracts_by_id:
fathers.append(self._contracts_by_id[i])
else:
missing_inheritance = True
missing_inheritance = i
# Resolve immediate base constructor calls
for i in contract_parser.baseConstructorContractsCalled:
@ -446,7 +446,7 @@ Please rename it, this name is reserved for Slither's internals"""
elif i in self._contracts_by_id:
father_constructors.append(self._contracts_by_id[i])
else:
missing_inheritance = True
missing_inheritance = i
contract_parser.underlying_contract.set_inheritance(
ancestors, fathers, father_constructors
@ -456,7 +456,14 @@ Please rename it, this name is reserved for Slither's internals"""
self._compilation_unit.contracts_with_missing_inheritance.add(
contract_parser.underlying_contract
)
contract_parser.log_incorrect_parsing(f"Missing inheritance {contract_parser}")
txt = f"Missing inheritance {contract_parser.underlying_contract} ({contract_parser.compilation_unit.crytic_compile_compilation_unit.unique_id})\n"
txt += f"Missing inheritance ID: {missing_inheritance}\n"
if contract_parser.underlying_contract.inheritance:
txt += "Inheritance found:\n"
for contract_inherited in contract_parser.underlying_contract.inheritance:
txt += f"\t - {contract_inherited} (ID {contract_inherited.id})\n"
contract_parser.log_incorrect_parsing(txt)
contract_parser.set_is_analyzed(True)
contract_parser.delete_content()
@ -679,12 +686,12 @@ Please rename it, this name is reserved for Slither's internals"""
for func in contract.functions + contract.modifiers:
try:
func.generate_slithir_and_analyze()
except AttributeError:
except AttributeError as e:
# This can happens for example if there is a call to an interface
# And the interface is redefined due to contract's name reuse
# But the available version misses some functions
self._underlying_contract_to_parser[contract].log_incorrect_parsing(
f"Impossible to generate IR for {contract.name}.{func.name}"
f"Impossible to generate IR for {contract.name}.{func.name}:\n {e}"
)
contract.convert_expression_to_slithir_ssa()

@ -112,7 +112,7 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
if not var_type:
if name.startswith("function "):
found = re.findall(
"function \(([ ()\[\]a-zA-Z0-9\.,]*?)\)(?: payable)?(?: (?:external|internal|pure|view))?(?: returns \(([a-zA-Z0-9() \.,]*)\))?",
r"function \(([ ()\[\]a-zA-Z0-9\.,]*?)\)(?: payable)?(?: (?:external|internal|pure|view))?(?: returns \(([a-zA-Z0-9() \.,]*)\))?",
name,
)
assert len(found) == 1
@ -159,10 +159,10 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
if name.startswith("mapping("):
# nested mapping declared with var
if name.count("mapping(") == 1:
found = re.findall("mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name)
found = re.findall(r"mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name)
else:
found = re.findall(
"mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)",
r"mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)",
name,
)
assert len(found) == 1
@ -195,6 +195,12 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
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
def parse_type(
t: Union[Dict, UnknownType],
@ -267,19 +273,25 @@ def parse_type(
assert isinstance(custom_error, CustomErrorContract)
scope = custom_error.contract.file_scope
sl = caller_context.compilation_unit
next_context = caller_context.slither_parser
structures_direct_access = list(scope.structures.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 += structures_direct_access
enums_direct_access = []
all_enums = scope.enums.values()
all_enumss = [c.enums for c in scope.contracts.values()]
all_enums = [item for sublist in all_enumss for item in sublist]
all_enums += scope.enums.values()
contracts = scope.contracts.values()
functions = list(scope.functions)
renaming = scope.renaming
user_defined_types = scope.user_defined_types
elif isinstance(caller_context, (ContractSolc, FunctionSolc)):
sl = caller_context.compilation_unit
if isinstance(caller_context, FunctionSolc):
underlying_func = caller_context.underlying_function
# If contract_parser is set to None, then underlying_function is a functionContract
@ -345,7 +357,7 @@ def parse_type(
name = renaming[name]
if name in user_defined_types:
return user_defined_types[name]
return _find_from_type_name(
type_found = _find_from_type_name(
name,
functions,
contracts,
@ -354,6 +366,8 @@ def parse_type(
enums_direct_access,
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').
type_name_key = "type" if "type" in t["attributes"] else key
@ -363,7 +377,7 @@ def parse_type(
name = renaming[name]
if name in user_defined_types:
return user_defined_types[name]
return _find_from_type_name(
type_found = _find_from_type_name(
name,
functions,
contracts,
@ -372,6 +386,8 @@ def parse_type(
enums_direct_access,
all_enums,
)
_add_type_references(type_found, t["src"], sl)
return type_found
# Introduced with Solidity 0.8
if t[key] == "IdentifierPath":
@ -381,7 +397,7 @@ def parse_type(
name = renaming[name]
if name in user_defined_types:
return user_defined_types[name]
return _find_from_type_name(
type_found = _find_from_type_name(
name,
functions,
contracts,
@ -390,6 +406,8 @@ def parse_type(
enums_direct_access,
all_enums,
)
_add_type_references(type_found, t["src"], sl)
return type_found
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,
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.read_var import ReadVar
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:
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}")

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

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

@ -1,7 +1,10 @@
import logging
from typing import Dict, List, Optional, Set
from slither.core.declarations import Contract
from slither.slithir.operations import EventCall
from slither.utils import output
from slither.utils.erc import ERC, ERC_EVENT
from slither.utils.type import (
export_nested_types_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
def _check_signature(erc_function, contract, ret):
def _check_signature(erc_function: ERC, contract: Contract, ret: Dict) -> None:
name = erc_function.name
parameters = erc_function.parameters
return_type = erc_function.return_type
@ -51,7 +54,7 @@ def _check_signature(erc_function, contract, ret):
ret["missing_function"].append(missing_func.data)
return
function_return_type = [export_return_type_from_variable(state_variable_as_function)]
function_return_type = export_return_type_from_variable(state_variable_as_function)
function = state_variable_as_function
function_view = True
@ -146,7 +149,7 @@ def _check_signature(erc_function, contract, ret):
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
parameters = erc_event.parameters
indexes = erc_event.indexes
@ -180,7 +183,13 @@ def _check_events(erc_event, contract, ret):
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:
explored = set()
@ -192,9 +201,10 @@ def generic_erc_checks(contract, erc_functions, erc_events, ret, explored=None):
logger.info("## Check functions")
for erc_function in erc_functions:
_check_signature(erc_function, contract, ret)
logger.info("\n## Check events")
for erc_event in erc_events:
_check_events(erc_event, contract, ret)
if erc_events:
logger.info("\n## Check events")
for erc_event in erc_events:
_check_events(erc_event, contract, ret)
logger.info("\n")

@ -18,7 +18,7 @@ logger = logging.getLogger("Slither")
logger.setLevel(logging.INFO)
def parse_args():
def parse_args() -> argparse.Namespace:
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
@ -79,6 +79,12 @@ def parse_args():
action="store_true",
)
group_patching.add_argument(
"--convert-library-to-internal",
help="Convert external or public functions to internal in library.",
action="store_true",
)
group_patching.add_argument(
"--remove-assert", help="Remove call to assert().", action="store_true"
)
@ -100,7 +106,7 @@ def parse_args():
return parser.parse_args()
def main():
def main() -> None:
args = parse_args()
slither = Slither(args.filename, **vars(args))
@ -111,6 +117,7 @@ def main():
compilation_unit,
external_to_public=args.convert_external,
remove_assert=args.remove_assert,
convert_library_to_internal=args.convert_library_to_internal,
private_to_internal=args.convert_private,
export_path=args.dir,
pragma_solidity=args.pragma_solidity,

@ -51,6 +51,7 @@ class Flattening:
compilation_unit: SlitherCompilationUnit,
external_to_public=False,
remove_assert=False,
convert_library_to_internal=False,
private_to_internal=False,
export_path: Optional[str] = None,
pragma_solidity: Optional[str] = None,
@ -61,6 +62,7 @@ class Flattening:
self._external_to_public = external_to_public
self._remove_assert = remove_assert
self._use_abi_encoder_v2 = False
self._convert_library_to_internal = convert_library_to_internal
self._private_to_internal = private_to_internal
self._pragma_solidity = pragma_solidity
@ -105,23 +107,23 @@ class Flattening:
:return:
"""
src_mapping = contract.source_mapping
content = self._compilation_unit.core.source_code[src_mapping["filename_absolute"]]
start = src_mapping["start"]
end = src_mapping["start"] + src_mapping["length"]
content = self._compilation_unit.core.source_code[src_mapping.filename.absolute]
start = src_mapping.start
end = src_mapping.start + src_mapping.length
to_patch = []
# interface must use external
if self._external_to_public and contract.contract_kind != "interface":
if self._external_to_public and not contract.is_interface:
for f in contract.functions_declared:
# fallback must be external
if f.is_fallback or f.is_constructor_variables:
continue
if f.visibility == "external":
attributes_start = (
f.parameters_src().source_mapping["start"]
+ f.parameters_src().source_mapping["length"]
f.parameters_src().source_mapping.start
+ 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]
regex = re.search(r"((\sexternal)\s+)|(\sexternal)$|(\)external)$", attributes)
if regex:
@ -136,8 +138,8 @@ class Flattening:
for var in f.parameters:
if var.location == "calldata":
calldata_start = var.source_mapping["start"]
calldata_end = calldata_start + var.source_mapping["length"]
calldata_start = var.source_mapping.start
calldata_end = calldata_start + var.source_mapping.length
calldata_idx = content[calldata_start:calldata_end].find(" calldata ")
to_patch.append(
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:
for variable in contract.state_variables_declared:
if variable.visibility == "private":
attributes_start = variable.source_mapping["start"]
attributes_end = attributes_start + variable.source_mapping["length"]
attributes_start = variable.source_mapping.start
attributes_end = attributes_start + variable.source_mapping.length
attributes = content[attributes_start:attributes_end]
regex = re.search(r" private ", attributes)
if regex:
@ -172,9 +204,9 @@ class Flattening:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"assert(bool)"
):
to_patch.append(Patch(node.source_mapping["start"], "line_removal"))
to_patch.append(Patch(node.source_mapping.start, "line_removal"))
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)
@ -186,6 +218,10 @@ class Flattening:
index = index - start
if patch_type == "public_to_external":
content = content[:index] + "public" + content[index + len("external") :]
elif patch_type == "external_to_internal":
content = content[:index] + "internal" + content[index + len("external") :]
elif patch_type == "public_to_internal":
content = content[:index] + "internal" + content[index + len("public") :]
elif patch_type == "private_to_internal":
content = content[:index] + "internal" + content[index + len("private") :]
elif patch_type == "calldata_to_memory":

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