Merge remote-tracking branch 'origin/dev' into dev-autocompletion

dev-autocompletion
Emilio López 4 weeks ago
commit 7b3566b4fa
  1. 25
      .coderabbit.yaml
  2. 10
      .github/ISSUE_TEMPLATE/bug_report.yml
  3. 3
      .github/actions/upload-coverage/action.yml
  4. 0
      .github/scripts/integration_test_runner.sh
  5. 4
      .github/scripts/tool_test_runner.sh
  6. 0
      .github/scripts/unit_test_runner.sh
  7. 43
      .github/workflows/black_auto.yml
  8. 27
      .github/workflows/ci.yml
  9. 4
      .github/workflows/docker.yml
  10. 2
      .github/workflows/docs.yml
  11. 2
      .github/workflows/doctor.yml
  12. 40
      .github/workflows/issue-metrics.yml
  13. 8
      .github/workflows/linter.yml
  14. 2
      .github/workflows/pip-audit.yml
  15. 4
      .github/workflows/publish.yml
  16. 2
      .github/workflows/pylint.yml
  17. 12
      .github/workflows/test.yml
  18. 5
      .gitignore
  19. 9
      .pre-commit-hooks.yaml
  20. 3
      CODEOWNERS
  21. 8
      CONTRIBUTING.md
  22. 1
      Dockerfile
  23. 12
      FUNDING.json
  24. 2
      Makefile
  25. 131
      README.md
  26. 2
      examples/printers/human_printer.sol
  27. 14
      examples/scripts/data_dependency.py
  28. 9
      examples/scripts/data_dependency.sol
  29. 4
      plugin_example/setup.py
  30. 0
      plugin_example/slither_my_plugin/detectors/__init__.py
  31. 10
      plugin_example/slither_my_plugin/detectors/example.py
  32. 4
      pyproject.toml
  33. 84
      scripts/ci_test.sh
  34. 0
      scripts/ci_test_interface.sh
  35. 2
      scripts/ci_test_printers.sh
  36. 31
      scripts/ci_test_upgradability.sh
  37. 27
      scripts/json_diff.py
  38. 28
      scripts/update_buggy_versions.py
  39. 10
      setup.py
  40. 145
      slither/__main__.py
  41. 8
      slither/analyses/evm/convert.py
  42. 86
      slither/core/cfg/node.py
  43. 63
      slither/core/compilation_unit.py
  44. 103
      slither/core/declarations/contract.py
  45. 1
      slither/core/declarations/custom_error.py
  46. 183
      slither/core/declarations/function.py
  47. 5
      slither/core/declarations/function_contract.py
  48. 17
      slither/core/declarations/function_top_level.py
  49. 4
      slither/core/declarations/import_directive.py
  50. 1
      slither/core/declarations/pragma_directive.py
  51. 10
      slither/core/declarations/solidity_variables.py
  52. 9
      slither/core/declarations/using_for_top_level.py
  53. 3
      slither/core/expressions/identifier.py
  54. 49
      slither/core/scope/scope.py
  55. 170
      slither/core/slither_core.py
  56. 2
      slither/core/solidity_types/array_type.py
  57. 2
      slither/core/solidity_types/elementary_type.py
  58. 2
      slither/core/solidity_types/function_type.py
  59. 1
      slither/core/solidity_types/type_alias.py
  60. 59
      slither/core/source_mapping/source_mapping.py
  61. 30
      slither/core/variables/state_variable.py
  62. 7
      slither/core/variables/variable.py
  63. 11
      slither/detectors/all_detectors.py
  64. 42
      slither/detectors/assembly/incorrect_return.py
  65. 14
      slither/detectors/assembly/return_instead_of_leave.py
  66. 11
      slither/detectors/assembly/shift_parameter_mixup.py
  67. 24
      slither/detectors/attributes/constant_pragma.py
  68. 94
      slither/detectors/attributes/incorrect_solc.py
  69. 2
      slither/detectors/attributes/locked_ether.py
  70. 41
      slither/detectors/compiler_bugs/array_by_reference.py
  71. 2
      slither/detectors/compiler_bugs/reused_base_constructor.py
  72. 87
      slither/detectors/erc/erc20/arbitrary_send_erc20.py
  73. 3
      slither/detectors/functions/arbitrary_send_eth.py
  74. 102
      slither/detectors/functions/chainlink_feed_registry.py
  75. 11
      slither/detectors/functions/dead_code.py
  76. 14
      slither/detectors/functions/external_function.py
  77. 78
      slither/detectors/functions/gelato_unprotected_randomness.py
  78. 4
      slither/detectors/functions/modifier.py
  79. 92
      slither/detectors/functions/optimism_deprecation.py
  80. 155
      slither/detectors/functions/out_of_order_retryable.py
  81. 4
      slither/detectors/functions/protected_variable.py
  82. 73
      slither/detectors/functions/pyth_deprecated_functions.py
  83. 2
      slither/detectors/functions/suicidal.py
  84. 2
      slither/detectors/naming_convention/naming_convention.py
  85. 37
      slither/detectors/operations/encode_packed.py
  86. 7
      slither/detectors/operations/low_level_calls.py
  87. 2
      slither/detectors/operations/unchecked_send_return_value.py
  88. 2
      slither/detectors/operations/unchecked_transfer.py
  89. 6
      slither/detectors/operations/unused_return_values.py
  90. 11
      slither/detectors/reentrancy/reentrancy.py
  91. 4
      slither/detectors/slither/name_reused.py
  92. 10
      slither/detectors/statements/assert_state_change.py
  93. 147
      slither/detectors/statements/chronicle_unchecked_price.py
  94. 16
      slither/detectors/statements/controlled_delegatecall.py
  95. 6
      slither/detectors/statements/divide_before_multiply.py
  96. 18
      slither/detectors/statements/msg_value_in_loop.py
  97. 79
      slither/detectors/statements/pyth_unchecked.py
  98. 50
      slither/detectors/statements/pyth_unchecked_confidence.py
  99. 52
      slither/detectors/statements/pyth_unchecked_publishtime.py
  100. 55
      slither/detectors/statements/return_bomb.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,25 @@
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "en"
early_access: false
knowledge_base:
learnings:
scope: auto
issues:
scope: global
reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
poem: false
review_status: true
collapse_walkthrough: true
auto_review:
enabled: true
ignore_title_keywords:
- "WIP"
- "DO NOT MERGE"
drafts: false
base_branches:
- dev
chat:
auto_reply: true

@ -3,9 +3,13 @@ body:
-
attributes:
value: |
Please check the issues tab to avoid duplicates.
Please check the issues tab to avoid duplicates, and
confirm that the bug exists on the latest release (upgrade
by running `python3 -m pip install --upgrade slither-analyzer`).
If you are having difficulty installing slither,
please head over to the "Discussions" page.
Thanks for taking the time to fill out this bug report!
type: markdown
-
@ -17,7 +21,7 @@ body:
required: true
-
attributes:
description: "It can be a github repo, etherscan link, or code snippet."
description: "It can be a github repo (preferred), etherscan link, or code snippet."
label: "Code example to reproduce the issue:"
placeholder: "`contract A {}`\n"
id: reproduce
@ -27,7 +31,7 @@ body:
-
attributes:
description: |
What version of slither are you running?
What version of slither are you running?
Run `slither --version`
label: "Version:"
id: version

@ -27,4 +27,5 @@ runs:
path: |
.coverage.*
*.lcov
if-no-files-found: ignore
if-no-files-found: ignore
include-hidden-files: true

@ -2,11 +2,11 @@
# used to pass --cov=$path and --cov-append to pytest
if [ "$1" != "" ]; then
pytest "$1" tests/tools/read-storage/test_read_storage.py
pytest "$1" tests/tools
status_code=$?
python -m coverage report
else
pytest tests/tools/read-storage/test_read_storage.py
pytest tests/tools
status_code=$?
fi

@ -0,0 +1,43 @@
---
name: Run black (auto)
defaults:
run:
# To load bashrc
shell: bash -ieo pipefail {0}
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Black
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: 3.8
- name: Run black
uses: psf/black@stable
with:
options: ""
summary: false
version: "~= 22.3.0"
- name: Annotate diff changes using reviewdog
uses: reviewdog/action-suggester@v1
with:
tool_name: blackfmt

@ -25,26 +25,9 @@ jobs:
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }}
type: ["cli",
"dapp",
"data_dependency",
"path_filtering",
# "embark",
"erc",
# "etherlime",
"etherscan",
"find_paths",
"flat",
"interface",
"kspec",
"printers",
# "prop"
"simil",
"slither_config",
"truffle",
"upgradability"]
os: ${{ (github.event_name == 'pull_request' && fromJSON('["ubuntu-latest"]')) || fromJSON('["ubuntu-latest", "windows-2022"]') }}
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
type: ${{ (github.event_name == 'pull_request' && fromJSON('["data_dependency", "path_filtering","erc","find_paths","flat","interface", "printers","slither_config","upgradability"]')) || fromJSON('["data_dependency", "path_filtering","erc","find_paths","flat","interface", "printers","slither_config","upgradability", "cli", "dapp", "etherscan", "kspec", "simil", "truffle"]') }}
exclude:
# Requires nix
- os: windows-2022
@ -67,11 +50,11 @@ jobs:
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v30
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v14
uses: cachix/cachix-action@v15
with:
name: dapp

@ -30,7 +30,7 @@ jobs:
- name: Set Docker metadata
id: metadata
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}
@ -47,7 +47,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Build and Push
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7
target: final

@ -30,7 +30,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v4
uses: actions/configure-pages@v5
- uses: actions/setup-python@v5
with:
python-version: '3.8'

@ -23,7 +23,7 @@ jobs:
fail-fast: false
matrix:
os: ["ubuntu-latest", "windows-2022"]
python: ["3.8", "3.9", "3.10", "3.11"]
python: ["3.8", "3.9", "3.10", "3.11", "3.12"]
exclude:
# strange failure
- os: windows-2022

@ -0,0 +1,40 @@
name: Monthly issue metrics
on:
workflow_dispatch:
schedule:
- cron: '3 2 1 * *'
permissions:
issues: write
pull-requests: read
jobs:
build:
name: issue metrics
runs-on: ubuntu-latest
steps:
- name: Get dates for last month
shell: bash
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-01)
# Calculate the last day of the previous month
last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d)
#Set an environment variable with the date range
echo "$first_day..$last_day"
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@v3
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:crytic/slither is:issue created:${{ env.last_month }} -reason:"not planned" -reason:"duplicate"'
- name: Create issue
uses: peter-evans/create-issue-from-file@v5
with:
title: Monthly issue metrics report
token: ${{ secrets.GITHUB_TOKEN }}
content-filepath: ./issue_metrics.md

@ -45,7 +45,7 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/yamllint.json"
- name: Lint everything else
uses: super-linter/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v6.1.1
if: always()
env:
# run linter on everything to catch preexisting problems
@ -58,6 +58,7 @@ jobs:
VALIDATE_PYTHON_BLACK: false
VALIDATE_PYTHON_ISORT: false
VALIDATE_JSON: false
VALIDATE_JAVASCRIPT_ES: false
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_PYTHON_FLAKE8: false
VALIDATE_DOCKERFILE: false
@ -65,7 +66,8 @@ jobs:
VALIDATE_EDITORCONFIG: false
VALIDATE_JSCPD: false
VALIDATE_PYTHON_MYPY: false
# Until we upgrade the super linter for actionlintÒ
VALIDATE_GITHUB_ACTIONS: false
VALIDATE_CHECKOV: false
# TODO: consider enabling
VALIDATE_SHELL_SHFMT: false
SHELLCHECK_OPTS: "-e SC1090"
FILTER_REGEX_EXCLUDE: .*tests/.*.(json|zip|sol)

@ -34,6 +34,6 @@ jobs:
python -m pip install .
- name: Run pip-audit
uses: pypa/gh-action-pip-audit@v1.0.8
uses: pypa/gh-action-pip-audit@v1.1.0
with:
virtual-environment: /tmp/pip-audit-env

@ -44,10 +44,10 @@ jobs:
path: dist/
- name: publish
uses: pypa/gh-action-pypi-publish@v1.8.11
uses: pypa/gh-action-pypi-publish@v1.10.3
- name: sign
uses: sigstore/gh-action-sigstore-python@v2.1.1
uses: sigstore/gh-action-sigstore-python@v3.0.0
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true

@ -43,7 +43,7 @@ jobs:
echo "::add-matcher::.github/workflows/matchers/pylint.json"
- name: Pylint
uses: super-linter/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v6.1.1
if: always()
env:
# Run linters only on new files for pylint to speed up the CI

@ -25,7 +25,7 @@ jobs:
matrix:
os: ["ubuntu-latest", "windows-2022"]
type: ["unit", "integration", "tool"]
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.11"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11"]') }}
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
@ -80,11 +80,11 @@ jobs:
# Only run coverage on ubuntu-latest.
run: |
if [ ${{ matrix.os }} = "ubuntu-latest" ]; then
TEST_ARGS="--cov=slither --cov-append"
TEST_ARGS=(--cov=slither --cov-append)
elif [ ${{ matrix.os }} = "windows-2022" ]; then
TEST_ARGS=""
TEST_ARGS=()
fi
bash "./.github/scripts/${TEST_TYPE}_test_runner.sh" $TEST_ARGS
bash "./.github/scripts/${TEST_TYPE}_test_runner.sh" "${TEST_ARGS[@]}"
- name: Upload coverage
@ -119,5 +119,5 @@ jobs:
run: |
set +e
python -m coverage combine
echo "## python coverage" >> $GITHUB_STEP_SUMMARY
python -m coverage report -m --format=markdown >> $GITHUB_STEP_SUMMARY
echo "## python coverage" >> "$GITHUB_STEP_SUMMARY"
python -m coverage report -m --format=markdown >> "$GITHUB_STEP_SUMMARY"

5
.gitignore vendored

@ -114,4 +114,7 @@ test_artifacts/
crytic-export/
# Auto-generated Github pages docs
docs/
docs/
# slither.db.json
slither.db.json

@ -0,0 +1,9 @@
- id: slither
name: Slither
description: Run Slither on your project
entry: slither
args:
- .
pass_filenames: false
language: python
files: \.sol$

@ -1,5 +1,4 @@
* @montyly @0xalpharush @smonicas
/slither/tools/read_storage/ @0xalpharush
* @montyly @smonicas
/slither/tools/doctor/ @elopez
/slither/slithir/ @montyly
/slither/analyses/ @montyly

@ -6,7 +6,7 @@ If you're unsure where to start, we recommend our [`good first issue`](https://g
## Bug reports and feature suggestions
Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email opensource@trailofbits.com instead.
Bug reports and feature suggestions can be submitted to our issue tracker. For bug reports, attaching the contract that caused the bug will help us in debugging and resolving the issue quickly. If you find a security vulnerability, do not open an issue; email <opensource@trailofbits.com> instead.
## Questions
@ -14,7 +14,7 @@ Questions can be submitted to the "Discussions" page, and you may also join our
## Code
Slither uses the pull request contribution model. Please make an account on Github, fork this repo, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/).
Slither uses the pull request contribution model. Please make an account on GitHub, fork this repository, and submit code contributions via pull request. For more documentation, look [here](https://guides.github.com/activities/forking/).
Some pull request guidelines:
@ -63,7 +63,7 @@ To automatically reformat the code:
- `make reformat`
We use pylint `2.13.4`, black `22.3.0`.
We use pylint `3.0.3`, black `22.3.0`.
### Testing
@ -82,7 +82,7 @@ For each new detector, at least one regression tests must be present.
1. Create a folder in `tests/e2e/detectors/test_data` with the detector's argument name.
2. Create a test contract in `tests/e2e/detectors/test_data/<detector_name>/`.
3. Update `ALL_TESTS` in `tests/e2e/detectors/test_detectors.py`.
4. Run `python tests/e2e/detectors/test_detectors.py --compile` to create a zip file of the compilation artifacts.
4. Run `python tests/e2e/detectors/test_detectors.py --compile` to create a ZIP file of the compilation artifacts.
5. `pytest tests/e2e/detectors/test_detectors.py --insta update-new`. This will generate a snapshot of the detector output in `tests/e2e/detectors/snapshots/`. If updating an existing detector, run `pytest tests/e2e/detectors/test_detectors.py --insta review` and accept or reject the updates.
6. Run `pytest tests/e2e/detectors/test_detectors.py` to ensure everything worked. Then, add and commit the files to git.

@ -3,6 +3,7 @@ FROM ubuntu:jammy AS python-wheels
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gcc \
git \
make \
python3-dev \
python3-pip \
&& rm -rf /var/lib/apt/lists/*

@ -0,0 +1,12 @@
{
"Optimism": {
"op-mainnet": {
"ownedBy": "0xc44F30Be3eBBEfdDBB5a85168710b4f0e18f4Ff0"
}
},
"drips": {
"ethereum": {
"ownedBy": "0xc44F30Be3eBBEfdDBB5a85168710b4f0e18f4Ff0"
}
}
}

@ -6,7 +6,7 @@ TEST_MODULE := tests
ALL_PY_SRCS := $(shell find $(PY_MODULE) -name '*.py') \
$(shell find test -name '*.py')
# Optionally overriden by the user, if they're using a virtual environment manager.
# Optionally overridden by the user, if they're using a virtual environment manager.
VENV ?= env
# On Windows, venv scripts/shims are under `Scripts` instead of `bin`.

@ -76,6 +76,12 @@ If you're **not** going to use one of the [supported compilation frameworks](htt
python3 -m pip install slither-analyzer
```
#### How to upgrade
```console
python3 -m pip install --upgrade slither-analyzer
```
### Using Git
```bash
@ -102,6 +108,13 @@ docker run -it -v /home/share:/share trailofbits/eth-security-toolbox
### Integration
* For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action).
* For pre-commit integration, use (replace `$GIT_TAG` with real tag)
```YAML
- repo: https://github.com/crytic/slither
rev: $GIT_TAG
hooks:
- id: slither
```
* To generate a Markdown report, use `slither [target] --checklist`.
* To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`)
@ -153,54 +166,55 @@ Num | Detector | What it Detects | Impact | Confidence
42 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
43 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
44 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
45 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
46 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
47 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
48 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
49 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
50 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
51 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
52 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
53 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
54 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
55 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
56 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
57 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
58 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
59 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
60 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
61 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
62 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
63 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
64 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
65 | `return-bomb` | [A low level callee may consume all callers gas unexpectedly.](https://github.com/crytic/slither/wiki/Detector-Documentation#return-bomb) | Low | Medium
66 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
67 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
68 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
69 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
70 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
71 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
72 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
73 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
74 | `incorrect-using-for` | [Detects using-for statement usage when no function from a given library matches a given type](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage) | Informational | High
75 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
76 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
77 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
78 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
79 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
80 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
81 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
82 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
83 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
84 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
85 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
86 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
87 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
88 | `cache-array-length` | [Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it.](https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length) | Optimization | High
89 | `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
90 | `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
91 | `immutable-states` | [State variables that could be declared immutable](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-immutable) | Optimization | High
92 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Detector-Documentation#public-variable-read-in-external-context) | Optimization | High
45 | `out-of-order-retryable` | [Out-of-order retryable transactions](https://github.com/crytic/slither/wiki/Detector-Documentation#out-of-order-retryable-transactions) | Medium | Medium
46 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
47 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
48 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
49 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
50 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
51 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
52 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
53 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
54 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
55 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
56 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
57 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
58 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
59 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
60 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
61 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
62 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
63 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
64 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
65 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
66 | `return-bomb` | [A low level callee may consume all callers gas unexpectedly.](https://github.com/crytic/slither/wiki/Detector-Documentation#return-bomb) | Low | Medium
67 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
68 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
69 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
70 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
71 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
72 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
73 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
74 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
75 | `incorrect-using-for` | [Detects using-for statement usage when no function from a given library matches a given type](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage) | Informational | High
76 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
77 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
78 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
79 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
80 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
81 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
82 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
83 | `unused-import` | [Detects unused imports](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-imports) | Informational | High
84 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
85 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
86 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
87 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
88 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
89 | `cache-array-length` | [Detects `for` loops that use `length` member of some storage array in their loop condition and don't modify it.](https://github.com/crytic/slither/wiki/Detector-Documentation#cache-array-length) | Optimization | High
90 | `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
91 | `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
92 | `immutable-states` | [State variables that could be declared immutable](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-immutable) | Optimization | High
93 | `var-read-using-this` | [Contract reads its own variable using `this`](https://github.com/crytic/slither/wiki/Detector-Documentation#public-variable-read-in-external-context) | Optimization | High
For more information, see
@ -288,17 +302,24 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
Title | Usage | Authors | Venue | Code
--- | --- | --- | --- | ---
[ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method) | AST-based analysis built on top of Slither | Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen | CTCIS 19
[ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method) | AST-based analysis built on top of Slither | Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen | CTCIS 19 | -
[MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf) | Leverage data dependency through Slither | William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh | ISSRE 2019 | [MPro](https://github.com/QuanZhang-William/M-Pro)
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20 | -
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20 | -
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20 | [SCRepair](https://github.com/xiaoly8/SCRepair/)
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20 | -
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020) | -
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22 | [Sailfish](https://github.com/ucsb-seclab/sailfish)
[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)
[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) | -
[MANDO: Multi-Level Heterogeneous Graph Embeddings for Fine-Grained Detection of Smart Contract Vulnerabilities](https://arxiv.org/abs/2208.13252) | Use Slither to extract the CFG and call graph | Hoang Nguyen, Nhat-Minh Nguyen, Chunyao Xie, Zahra Ahmadi, Daniel Kudendo, Thanh-Nam Doan and Lingxiao Jiang| IEEE 9th International Conference on Data Science and Advanced Analytics (DSAA, 2022) | [ge-sc](https://github.com/MANDO-Project/ge-sc)
[Automated Auditing of Price Gouging TOD Vulnerabilities in Smart Contracts](https://www.cs.toronto.edu/~fanl/papers/price-icbc22.pdf) | Use Slither to extract the CFG and data dependencies| Sidi Mohamed Beillahi, Eric Keilty, Keerthi Nelaturu, Andreas Veneris, and Fan Long | 2022 IEEE International Conference on Blockchain and Cryptocurrency (ICBC) | [Smart-Contract-Repair](https://github.com/Veneris-Group/TOD-Location-Rectification)
[Modeling and Enforcing Access Control Policies for Smart Contracts](https://publikationen.bibliothek.kit.edu/1000152805/151859658) | Extend Slither's data dependencies | Jan-Philipp Toberg, Jonas Schiffl, Frederik Reiche, Bernhard Beckert, Robert Heinrich, Ralf Reussner | IEEE International Conference on Decentralized Applications and Infrastructures (DAPPS), 2022 | [SolidityAccessControlEnforcement](https://github.com/KASTEL-CSSDA/SolidityAccessControlEnforcement)
[Smart Contract Vulnerability Detection Based on Deep Learning and Multimodal Decision Fusion](https://www.mdpi.com/1424-8220/23/16/7246) | Use Slither to extract the CFG | Weichu Deng, Huanchun Wei, Teng Huang, Cong Cao, Yun Peng, and Xuan Hu | Sensors 2023, 23, 7246 | -
[Semantic-enriched Code Knowledge Graph to Reveal Unknowns in Smart Contract Code Reuse](https://www.researchgate.net/profile/Qing-Huang-26/publication/370638129_Semantic-enriched_Code_Knowledge_Graph_to_Reveal_Unknowns_in_Smart_Contract_Code_Reuse/links/645b7b8639c408339b3a54da/Semantic-Enriched-Code-Knowledge-Graph-to-Reveal-Unknowns-in-Smart-Contract-Code-Reuse.pdf) | Use Slither to extract the code features (CFG, function, parameters types, ..) | Qing Huang, Dianshu Liao, Zhenchang Xing, Zhengkang Zuo, Changjing Wang, Xin Xia | ACM Transactions on Software Engineering and Methodology, 2023 | -
[Smart Contract Parallel Execution with Fine-Grained State Accesses](https://personal.ntu.edu.sg/yi_li/files/Qi2023SCP.pdf) | Use Slither to build state access graphs | Xiaodong Qi, Jiao Jiao, Yi Li | International Conference on Distributed Computing Systems (ICDCS), 2023 | -
[Bad Apples: Understanding the Centralized Security Risks in Decentralized Ecosystems](https://diaowenrui.github.io/paper/www23-yan.pdf) | Implement an internal analysis on top of Slither | Kailun Yan , Jilian Zhang , Xiangyu Liu , Wenrui Diao , Shanqing Guo | ACM Web Conference April 2023 | -
[Identifying Vulnerabilities in Smart Contracts using Interval Analysis](https://arxiv.org/pdf/2309.13805.pdf) | Create 4 detectors on top of Slither | Ştefan-Claudiu Susan, Andrei Arusoaie | FROM 2023 | -
Storage State Analysis and Extraction of Ethereum Blockchain Smart Contracts (no PDF in open access) | Rely on Slither's CFG and AST | Maha Ayub , Tania Saleem , Muhammad Janjua , Talha Ahmad | TOSEM 2023 | [SmartMuv](https://github.com/WaizKhan7/SmartMuv)
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/).

@ -65,7 +65,7 @@ contract SimpleVulnerableToken{
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @param _owner The address to query the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public view returns (uint256) {

@ -126,3 +126,17 @@ print(f"{var_tainted} is tainted: {is_tainted(var_tainted, contract)}")
assert is_tainted(var_tainted, contract)
print(f"{var_not_tainted} is tainted: {is_tainted(var_not_tainted, contract)}")
assert not is_tainted(var_not_tainted, contract)
print("SimpleModifier contract")
contracts = slither.get_contract_from_name("SimpleModifier")
assert len(contracts) == 1
contract = contracts[0]
dependent_state_var = contract.get_state_variable_from_name("owner")
assert dependent_state_var
baz = contract.get_modifier_from_signature("baz()")
intermediate = baz.get_local_variable_from_name("intermediate")
assert intermediate
print(
f"{intermediate} depends on msg.sender: {is_dependent(intermediate, SolidityVariableComposed('msg.sender'), baz)}"
)
assert is_dependent(intermediate, SolidityVariableComposed("msg.sender"), baz)

@ -115,3 +115,12 @@ contract PropagateThroughReturnValue {
return (var_state);
}
}
contract SimpleModifier {
address owner;
modifier baz {
bool intermediate = msg.sender == owner;
require(intermediate);
_;
}
}

@ -1,14 +1,14 @@
from setuptools import setup, find_packages
setup(
name="slither-my-plugins",
name="slither_my_plugin",
description="This is an example of detectors and printers to Slither.",
url="https://github.com/trailofbits/slither-plugins",
author="Trail of Bits",
version="0.0",
packages=find_packages(),
python_requires=">=3.8",
install_requires=["slither-analyzer==0.1"],
install_requires=["slither-analyzer>=0.6.0"],
entry_points={
"slither_analyzer.plugin": "slither my-plugin=slither_my_plugin:make_plugin",
},

@ -11,12 +11,12 @@ class Example(AbstractDetector): # pylint: disable=too-few-public-methods
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = ""
WIKI = "https://www.example.com/#example-detector"
WIKI_TITLE = ""
WIKI_DESCRIPTION = ""
WIKI_EXPLOIT_SCENARIO = ""
WIKI_RECOMMENDATION = ""
WIKI_TITLE = "example detector"
WIKI_DESCRIPTION = "This is an example detector that always generates a finding"
WIKI_EXPLOIT_SCENARIO = "Scenario goes here"
WIKI_RECOMMENDATION = "Customize the detector"
def _detect(self):

@ -7,7 +7,6 @@ missing-module-docstring,
missing-class-docstring,
missing-function-docstring,
unnecessary-lambda,
bad-continuation,
cyclic-import,
line-too-long,
invalid-name,
@ -18,5 +17,6 @@ logging-fstring-interpolation,
logging-not-lazy,
duplicate-code,
import-error,
unsubscriptable-object
unsubscriptable-object,
unnecessary-lambda-assignment
"""

@ -1,84 +0,0 @@
#!/usr/bin/env bash
### Test Detectors
DIR="$(cd "$(dirname "$0")" && pwd)"
CURRENT_PATH=$(pwd)
TRAVIS_PATH='/home/travis/build/crytic/slither'
# test_slither file.sol detectors
test_slither(){
expected="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json"
# run slither detector on input file and save output as json
if ! slither "$1" --solc-disable-warnings --detect "$2" --json "$DIR/tmp-test.json";
then
echo "Slither crashed"
exit 255
fi
if [ ! -f "$DIR/tmp-test.json" ]; then
echo ""
echo "Missing generated file"
echo ""
exit 1
fi
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i
result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json")
rm "$DIR/tmp-test.json"
if [ "$result" != "{}" ]; then
echo ""
echo "failed test of file: $1, detector: $2"
echo ""
echo "$result"
echo ""
exit 1
fi
# run slither detector on input file and save output as json
if ! slither "$1" --solc-disable-warnings --detect "$2" --legacy-ast --json "$DIR/tmp-test.json";
then
echo "Slither crashed"
exit 255
fi
if [ ! -f "$DIR/tmp-test.json" ]; then
echo ""
echo "Missing generated file"
echo ""
exit 1
fi
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$DIR/tmp-test.json" -i
result=$(python "$DIR/json_diff.py" "$expected" "$DIR/tmp-test.json")
rm "$DIR/tmp-test.json"
if [ "$result" != "{}" ]; then
echo ""
echo "failed test of file: $1, detector: $2"
echo ""
echo "$result"
echo ""
exit 1
fi
}
# generate_expected_json file.sol detectors
generate_expected_json(){
# generate output filename
# e.g. file: uninitialized.sol detector: uninitialized-state
# ---> uninitialized.uninitialized-state.json
output_filename="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.json"
output_filename_txt="$DIR/../tests/expected_json/$(basename "$1" .sol).$2.txt"
# run slither detector on input file and save output as json
slither "$1" --solc-disable-warnings --detect "$2" --json "$output_filename" > "$output_filename_txt" 2>&1
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename" -i
sed "s|$CURRENT_PATH|$TRAVIS_PATH|g" "$output_filename_txt" -i
}

@ -5,7 +5,7 @@
cd tests/e2e/solc_parsing/test_data/compile/ || exit
# Do not test the evm printer,as it needs a refactoring
ALL_PRINTERS="cfg,constructor-calls,contract-summary,data-dependency,echidna,function-id,function-summary,modifiers,call-graph,halstead,human-summary,inheritance,inheritance-graph,loc,martin,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration,ck"
ALL_PRINTERS="cfg,constructor-calls,contract-summary,data-dependency,echidna,function-id,function-summary,modifiers,call-graph,halstead,human-summary,inheritance,inheritance-graph,loc,martin,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration,ck,cheatcode"
# Only test 0.5.17 to limit test time
for file in *0.5.17-compact.zip; do

@ -2,7 +2,8 @@
### Test slither-check-upgradeability
DIR_TESTS="tests/check-upgradeability"
DIR_TESTS="tests/tools/check_upgradeability"
solc-select install "0.5.0"
solc-select use "0.5.0"
slither-check-upgradeability "$DIR_TESTS/contractV1.sol" ContractV1 --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_1.txt 2>&1
@ -181,6 +182,32 @@ then
exit 255
fi
slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_no_bug_reinitializer --proxy-filename "$DIR_TESTS/proxy.sol" --proxy-name Proxy > test_14.txt 2>&1
DIFF=$(diff test_14.txt "$DIR_TESTS/test_14.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 14 failed"
cat test_14.txt
echo ""
cat "$DIR_TESTS/test_14.txt"
echo ""
echo "$DIFF"
exit 255
fi
slither-check-upgradeability "$DIR_TESTS/contract_initialization.sol" Contract_reinitializer_V2 --new-contract-name Counter_reinitializer_V3_V4 > test_15.txt 2>&1
DIFF=$(diff test_15.txt "$DIR_TESTS/test_15.txt")
if [ "$DIFF" != "" ]
then
echo "slither-check-upgradeability 14 failed"
cat test_15.txt
echo ""
cat "$DIR_TESTS/test_15.txt"
echo ""
echo "$DIFF"
exit 255
fi
rm test_1.txt
rm test_2.txt
rm test_3.txt
@ -194,3 +221,5 @@ rm test_10.txt
rm test_11.txt
rm test_12.txt
rm test_13.txt
rm test_14.txt
rm test_15.txt

@ -1,27 +0,0 @@
import sys
import json
from pprint import pprint
from deepdiff import DeepDiff # pip install deepdiff
if len(sys.argv) != 3:
print("Usage: python json_diff.py 1.json 2.json")
sys.exit(-1)
with open(sys.argv[1], encoding="utf8") as f:
d1 = json.load(f)
with open(sys.argv[2], encoding="utf8") as f:
d2 = json.load(f)
# Remove description field to allow non deterministic print
for elem in d1:
if "description" in elem:
del elem["description"]
for elem in d2:
if "description" in elem:
del elem["description"]
pprint(DeepDiff(d1, d2, ignore_order=True, verbose_level=2))

@ -0,0 +1,28 @@
import json
from pathlib import Path
import urllib.request
def retrieve_json(url):
with urllib.request.urlopen(url) as response:
data = response.read().decode("utf-8")
return json.loads(data)
def organize_data(json_data):
version_bugs = {}
for version, info in json_data.items():
version_bugs[version] = info["bugs"]
return version_bugs
if __name__ == "__main__":
bug_list_url = (
"https://raw.githubusercontent.com/ethereum/solidity/develop/docs/bugs_by_version.json"
)
bug_data = retrieve_json(bug_list_url)
bugs_by_version = organize_data(bug_data)
with open(Path.cwd() / Path("slither/utils/buggy_versions.py"), "w", encoding="utf-8") as file:
file.write("# pylint: disable=too-many-lines\n")
file.write(f"bugs_by_version = {bugs_by_version}")

@ -8,16 +8,16 @@ setup(
description="Slither is a Solidity and Vyper static analysis framework written in Python 3.",
url="https://github.com/crytic/slither",
author="Trail of Bits",
version="0.10.0",
version="0.10.4",
packages=find_packages(),
python_requires=">=3.8",
install_requires=[
"packaging",
"prettytable>=3.3.0",
"prettytable>=3.10.2",
"pycryptodome>=3.4.6",
"crytic-compile>=0.3.5,<0.4.0",
"crytic-compile>=0.3.7,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"web3>=6.0.0",
"web3>=6.20.2, <7",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
"eth-utils>=2.1.0",
@ -26,7 +26,7 @@ setup(
extras_require={
"lint": [
"black==22.3.0",
"pylint==2.13.4",
"pylint==3.0.3",
],
"test": [
"pytest",

@ -10,9 +10,9 @@ import os
import pstats
import sys
import traceback
from typing import Tuple, Optional, List, Dict, Type, Union, Any, Sequence
from importlib import metadata
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union
from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export
@ -167,19 +167,26 @@ def get_detectors_and_printers() -> Tuple[
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):
if sys.version_info >= (3, 10):
entry_points = metadata.entry_points(group="slither_analyzer.plugin")
else:
from pkg_resources import iter_entry_points # pylint: disable=import-outside-toplevel
entry_points = iter_entry_points(group="slither_analyzer.plugin", name=None)
for entry_point in entry_points:
make_plugin = entry_point.load()
plugin_detectors, plugin_printers = make_plugin()
detector = None
if not all(issubclass(detector, AbstractDetector) for detector in plugin_detectors):
raise Exception(
raise ValueError(
f"Error when loading plugin {entry_point}, {detector} is not a detector"
)
printer = None
if not all(issubclass(printer, AbstractPrinter) for printer in plugin_printers):
raise Exception(f"Error when loading plugin {entry_point}, {printer} is not a printer")
raise ValueError(f"Error when loading plugin {entry_point}, {printer} is not a printer")
# We convert those to lists in case someone returns a tuple
detectors += list(plugin_detectors)
@ -199,43 +206,50 @@ def choose_detectors(
if args.detectors_to_run == "all":
detectors_to_run = all_detector_classes
if args.detectors_to_exclude:
detectors_excluded = args.detectors_to_exclude.split(",")
for detector in detectors:
if detector in detectors_excluded:
detectors_to_run.remove(detectors[detector])
else:
for detector in args.detectors_to_run.split(","):
if detector in detectors:
detectors_to_run.append(detectors[detector])
else:
raise Exception(f"Error: {detector} is not a detector")
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
detectors_to_run = __include_detectors(
set(detectors_to_run), args.detectors_to_run, detectors
)
return detectors_to_run
if args.exclude_optimization:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.OPTIMIZATION
]
classification_map = {
DetectorClassification.HIGH: args.exclude_high,
DetectorClassification.MEDIUM: args.exclude_medium,
DetectorClassification.LOW: args.exclude_low,
DetectorClassification.INFORMATIONAL: args.exclude_informational,
DetectorClassification.OPTIMIZATION: args.exclude_optimization,
}
excluded_classification = [
classification for classification, included in classification_map.items() if included
]
detectors_to_run = [d for d in detectors_to_run if d.IMPACT not in excluded_classification]
if args.exclude_informational:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.INFORMATIONAL
]
if args.exclude_low:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.LOW]
if args.exclude_medium:
detectors_to_run = [
d for d in detectors_to_run if d.IMPACT != DetectorClassification.MEDIUM
]
if args.exclude_high:
detectors_to_run = [d for d in detectors_to_run if d.IMPACT != DetectorClassification.HIGH]
if args.detectors_to_exclude:
detectors_to_run = [
d for d in detectors_to_run if d.ARGUMENT not in args.detectors_to_exclude
]
if args.detectors_to_include:
detectors_to_run = __include_detectors(
set(detectors_to_run), args.detectors_to_include, detectors
)
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
def __include_detectors(
detectors_to_run: Set[Type[AbstractDetector]],
detectors_to_include: str,
detectors: Dict[str, Type[AbstractDetector]],
) -> List[Type[AbstractDetector]]:
include_detectors = detectors_to_include.split(",")
for detector in include_detectors:
if detector in detectors:
detectors_to_run.add(detectors[detector])
else:
raise ValueError(f"Error: {detector} is not a detector")
return detectors_to_run
@ -257,7 +271,7 @@ def choose_printers(
if printer in printers:
printers_to_run.append(printers[printer])
else:
raise Exception(f"Error: {printer} is not a printer")
raise ValueError(f"Error: {printer} is not a printer")
return printers_to_run
@ -269,9 +283,10 @@ def choose_printers(
###################################################################################
def parse_filter_paths(args: argparse.Namespace) -> List[str]:
if args.filter_paths:
return args.filter_paths.split(",")
def parse_filter_paths(args: argparse.Namespace, filter_path: bool) -> List[str]:
paths = args.filter_paths if filter_path else args.include_paths
if paths:
return paths.split(",")
return []
@ -300,7 +315,7 @@ def parse_args(
parser.add_argument(
"--version",
help="displays the current version",
version=require("slither-analyzer")[0].version,
version=metadata.version("slither-analyzer"),
action="version",
)
@ -310,6 +325,7 @@ def parse_args(
"Checklist (consider using https://github.com/crytic/slither-action)"
)
group_misc = parser.add_argument_group("Additional options")
group_filters = parser.add_mutually_exclusive_group()
group_detector.add_argument(
"--detect",
@ -329,6 +345,14 @@ def parse_args(
default=defaults_flag_in_config["printers_to_run"],
)
group_printer.add_argument(
"--include-interfaces",
help="Include interfaces from inheritance-graph printer",
action="store_true",
dest="include_interfaces",
default=False,
)
group_detector.add_argument(
"--list-detectors",
help="List available detectors",
@ -395,6 +419,14 @@ def parse_args(
default=defaults_flag_in_config["exclude_high"],
)
group_detector.add_argument(
"--include-detectors",
help="Comma-separated list of detectors that should be included",
action="store",
dest="detectors_to_include",
default=defaults_flag_in_config["detectors_to_include"],
)
fail_on_group = group_detector.add_mutually_exclusive_group()
fail_on_group.add_argument(
"--fail-pedantic",
@ -522,22 +554,22 @@ def parse_args(
default=defaults_flag_in_config["disable_color"],
)
group_misc.add_argument(
"--filter-paths",
help="Regex filter to exclude detector results matching file path e.g. (mocks/|test/)",
action="store",
dest="filter_paths",
default=defaults_flag_in_config["filter_paths"],
)
group_misc.add_argument(
"--triage-mode",
help="Run triage mode (save results in slither.db.json)",
help="Run triage mode (save results in triage database)",
action="store_true",
dest="triage_mode",
default=False,
)
group_misc.add_argument(
"--triage-database",
help="File path to the triage database (default: slither.db.json)",
action="store",
dest="triage_database",
default=defaults_flag_in_config["triage_database"],
)
group_misc.add_argument(
"--config-file",
help="Provide a config file (default: slither.config.json)",
@ -575,6 +607,22 @@ def parse_args(
default=defaults_flag_in_config["no_fail"],
)
group_filters.add_argument(
"--filter-paths",
help="Regex filter to exclude detector results matching file path e.g. (mocks/|test/)",
action="store",
dest="filter_paths",
default=defaults_flag_in_config["filter_paths"],
)
group_filters.add_argument(
"--include-paths",
help="Regex filter to include detector results matching file path e.g. (src/|contracts/). Opposite of --filter-paths",
action="store",
dest="include_paths",
default=defaults_flag_in_config["include_paths"],
)
codex.init_parser(parser)
# debugger command
@ -627,13 +675,14 @@ def parse_args(
args = parser.parse_args()
read_config_file(args)
args.filter_paths = parse_filter_paths(args)
args.filter_paths = parse_filter_paths(args, True)
args.include_paths = parse_filter_paths(args, False)
# Verify our json-type output is valid
args.json_types = set(args.json_types.split(",")) # type:ignore
for json_type in args.json_types:
if json_type not in JSON_OUTPUT_TYPES:
raise Exception(f'Error: "{json_type}" is not a valid JSON result output type.')
raise ValueError(f'Error: "{json_type}" is not a valid JSON result output type.')
return args

@ -178,15 +178,14 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither
# In order to compress these source mappings especially for bytecode, the following rules are used:
# If a field is empty, the value of the preceding element is used.
# If a : is missing, all following fields are considered empty.
mapping_item = mapping.split(":")
mapping_item += prev_mapping[len(mapping_item) :]
for i, _ in enumerate(mapping_item):
if mapping_item[i] == "":
mapping_item[i] = int(prev_mapping[i])
mapping_item[i] = prev_mapping[i]
offset, _length, file_id, *_ = mapping_item
offset, _, file_id, *_ = mapping_item
prev_mapping = mapping_item
if file_id == "-1":
@ -194,8 +193,7 @@ def generate_source_to_evm_ins_mapping(evm_instructions, srcmap_runtime, slither
# See https://github.com/ethereum/solidity/issues/6119#issuecomment-467797635
continue
offset = int(offset)
line_number = file_source[0:offset].count("\n".encode("utf-8")) + 1
line_number = file_source[0 : int(offset)].count("\n".encode("utf-8")) + 1
# Append evm instructions to the corresponding source line number
# Note: Some evm instructions in mapping are not necessarily in program execution order

@ -11,6 +11,7 @@ from slither.core.declarations.solidity_variables import (
SolidityFunction,
)
from slither.core.expressions.expression import Expression
from slither.core.expressions import CallExpression, Identifier, AssignmentOperation
from slither.core.solidity_types import ElementaryType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
@ -45,12 +46,6 @@ from slither.slithir.variables import (
if TYPE_CHECKING:
from slither.slithir.variables.variable import SlithIRVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.utils.type_helpers import (
InternalCallType,
HighLevelCallType,
LibraryCallType,
LowLevelCallType,
)
from slither.core.cfg.scope import Scope
from slither.core.scope.scope import FileScope
@ -152,11 +147,11 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
self._ssa_vars_written: List["SlithIRVariable"] = []
self._ssa_vars_read: List["SlithIRVariable"] = []
self._internal_calls: List[Union["Function", "SolidityFunction"]] = []
self._solidity_calls: List[SolidityFunction] = []
self._high_level_calls: List["HighLevelCallType"] = [] # contains library calls
self._library_calls: List["LibraryCallType"] = []
self._low_level_calls: List["LowLevelCallType"] = []
self._internal_calls: List[InternalCall] = [] # contains solidity calls
self._solidity_calls: List[SolidityCall] = []
self._high_level_calls: List[Tuple[Contract, HighLevelCall]] = [] # contains library calls
self._library_calls: List[LibraryCall] = []
self._low_level_calls: List[LowLevelCall] = []
self._external_calls_as_expressions: List[Expression] = []
self._internal_calls_as_expressions: List[Expression] = []
self._irs: List[Operation] = []
@ -225,8 +220,9 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
@property
def will_return(self) -> bool:
if not self.sons and self.type != NodeType.THROW:
if SolidityFunction("revert()") not in self.solidity_calls:
if SolidityFunction("revert(string)") not in self.solidity_calls:
solidity_calls = [ir.function for ir in self.solidity_calls]
if SolidityFunction("revert()") not in solidity_calls:
if SolidityFunction("revert(string)") not in solidity_calls:
return True
return False
@ -372,44 +368,38 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def internal_calls(self) -> List["InternalCallType"]:
def internal_calls(self) -> List[InternalCall]:
"""
list(Function or SolidityFunction): List of internal/soldiity function calls
list(InternalCall): List of IR operations with internal/solidity function calls
"""
return list(self._internal_calls)
@property
def solidity_calls(self) -> List[SolidityFunction]:
def solidity_calls(self) -> List[SolidityCall]:
"""
list(SolidityFunction): List of Soldity calls
list(SolidityCall): List of IR operations with solidity calls
"""
return list(self._solidity_calls)
@property
def high_level_calls(self) -> List["HighLevelCallType"]:
def high_level_calls(self) -> List[HighLevelCall]:
"""
list((Contract, Function|Variable)):
List of high level calls (external calls).
A variable is called in case of call to a public state variable
list(HighLevelCall): List of IR operations with high level calls (external calls).
Include library calls
"""
return list(self._high_level_calls)
@property
def library_calls(self) -> List["LibraryCallType"]:
def library_calls(self) -> List[LibraryCall]:
"""
list((Contract, Function)):
Include library calls
list(LibraryCall): List of IR operations with library calls.
"""
return list(self._library_calls)
@property
def low_level_calls(self) -> List["LowLevelCallType"]:
def low_level_calls(self) -> List[LowLevelCall]:
"""
list((Variable|SolidityVariable, str)): List of low_level call
A low level call is defined by
- the variable called
- the name of the function (call/delegatecall/codecall)
list(LowLevelCall): List of IR operations with low_level call
"""
return list(self._low_level_calls)
@ -528,8 +518,9 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
bool: True if the node has a require or assert call
"""
return any(
c.name in ["require(bool)", "require(bool,string)", "assert(bool)"]
for c in self.internal_calls
ir.function.name
in ["require(bool)", "require(bool,string)", "require(bool,error)", "assert(bool)"]
for ir in self.internal_calls
)
def contains_if(self, include_loop: bool = True) -> bool:
@ -893,29 +884,44 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
self._vars_written.append(var)
if isinstance(ir, InternalCall):
self._internal_calls.append(ir.function)
self._internal_calls.append(ir)
if isinstance(ir, SolidityCall):
# TODO: consider removing dependancy of solidity_call to internal_call
self._solidity_calls.append(ir.function)
self._internal_calls.append(ir.function)
self._solidity_calls.append(ir)
self._internal_calls.append(ir)
if (
isinstance(ir, SolidityCall)
and ir.function == SolidityFunction("sstore(uint256,uint256)")
and isinstance(ir.node.expression, CallExpression)
and isinstance(ir.node.expression.arguments[0], Identifier)
):
self._vars_written.append(ir.arguments[0])
if (
isinstance(ir, SolidityCall)
and ir.function == SolidityFunction("sload(uint256)")
and isinstance(ir.node.expression, AssignmentOperation)
and isinstance(ir.node.expression.expression_right, CallExpression)
and isinstance(ir.node.expression.expression_right.arguments[0], Identifier)
):
self._vars_read.append(ir.arguments[0])
if isinstance(ir, LowLevelCall):
assert isinstance(ir.destination, (Variable, SolidityVariable))
self._low_level_calls.append((ir.destination, str(ir.function_name.value)))
self._low_level_calls.append(ir)
elif isinstance(ir, HighLevelCall) and not isinstance(ir, LibraryCall):
# Todo investigate this if condition
# It does seem right to compare against a contract
# This might need a refactoring
if isinstance(ir.destination.type, Contract):
self._high_level_calls.append((ir.destination.type, ir.function))
self._high_level_calls.append((ir.destination.type, ir))
elif ir.destination == SolidityVariable("this"):
func = self.function
# Can't use this in a top level function
assert isinstance(func, FunctionContract)
self._high_level_calls.append((func.contract, ir.function))
self._high_level_calls.append((func.contract, ir))
else:
try:
# Todo this part needs more tests and documentation
self._high_level_calls.append((ir.destination.type.type, ir.function))
self._high_level_calls.append((ir.destination.type.type, ir))
except AttributeError as error:
# pylint: disable=raise-missing-from
raise SlitherException(
@ -924,8 +930,8 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
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))
self._high_level_calls.append((ir.destination, ir))
self._library_calls.append(ir)
self._vars_read = list(set(self._vars_read))
self._state_vars_read = [v for v in self._vars_read if isinstance(v, StateVariable)]

@ -73,7 +73,8 @@ class SlitherCompilationUnit(Context):
# Memoize
self._all_state_variables: Optional[Set[StateVariable]] = None
self._storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
self._persistent_storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
self._transient_storage_layouts: Dict[str, Dict[str, Tuple[int, int]]] = {}
self._contract_with_missing_inheritance: Set[Contract] = set()
@ -297,36 +298,52 @@ class SlitherCompilationUnit(Context):
def compute_storage_layout(self) -> None:
assert self.is_solidity
for contract in self.contracts_derived:
self._storage_layouts[contract.name] = {}
slot = 0
offset = 0
for var in contract.state_variables_ordered:
if var.is_constant or var.is_immutable:
continue
assert var.type
size, new_slot = var.type.storage_size
if new_slot:
if offset > 0:
slot += 1
offset = 0
elif size + offset > 32:
self._compute_storage_layout(contract.name, contract.storage_variables_ordered, False)
self._compute_storage_layout(contract.name, contract.transient_variables_ordered, True)
def _compute_storage_layout(
self, contract_name: str, state_variables_ordered: List[StateVariable], is_transient: bool
):
if is_transient:
self._transient_storage_layouts[contract_name] = {}
else:
self._persistent_storage_layouts[contract_name] = {}
slot = 0
offset = 0
for var in state_variables_ordered:
assert var.type
size, new_slot = var.type.storage_size
if new_slot:
if offset > 0:
slot += 1
offset = 0
elif size + offset > 32:
slot += 1
offset = 0
self._storage_layouts[contract.name][var.canonical_name] = (
if is_transient:
self._transient_storage_layouts[contract_name][var.canonical_name] = (
slot,
offset,
)
if new_slot:
slot += math.ceil(size / 32)
else:
offset += size
else:
self._persistent_storage_layouts[contract_name][var.canonical_name] = (
slot,
offset,
)
if new_slot:
slot += math.ceil(size / 32)
else:
offset += size
def storage_layout_of(self, contract: Contract, var: StateVariable) -> Tuple[int, int]:
return self._storage_layouts[contract.name][var.canonical_name]
if var.is_stored:
return self._persistent_storage_layouts[contract.name][var.canonical_name]
return self._transient_storage_layouts[contract.name][var.canonical_name]
# endregion

@ -9,9 +9,8 @@ from typing import Optional, List, Dict, Callable, Tuple, TYPE_CHECKING, Union,
from crytic_compile.platform import Type as PlatformType
from slither.core.cfg.scope import Scope
from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.utils.using_for import USING_FOR, merge_using_for
from slither.core.declarations.function import Function, FunctionType, FunctionLanguage
from slither.utils.erc import (
ERC20_signatures,
@ -30,7 +29,6 @@ from slither.utils.tests_pattern import is_test_contract
# pylint: disable=too-many-lines,too-many-instance-attributes,import-outside-toplevel,too-many-nested-blocks
if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import (
Enum,
EventContract,
@ -40,6 +38,7 @@ if TYPE_CHECKING:
FunctionContract,
CustomErrorContract,
)
from slither.slithir.operations import HighLevelCall, LibraryCall
from slither.slithir.variables.variable import SlithIRVariable
from slither.core.variables import Variable, StateVariable
from slither.core.compilation_unit import SlitherCompilationUnit
@ -50,9 +49,6 @@ if TYPE_CHECKING:
LOGGER = logging.getLogger("Contract")
USING_FOR_KEY = Union[str, Type]
USING_FOR_ITEM = List[Union[Type, Function]]
class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
@ -87,12 +83,13 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._type_aliases: Dict[str, "TypeAliasContract"] = {}
# The only str is "*"
self._using_for: Dict[USING_FOR_KEY, USING_FOR_ITEM] = {}
self._using_for_complete: Optional[Dict[USING_FOR_KEY, USING_FOR_ITEM]] = None
self._using_for: USING_FOR = {}
self._using_for_complete: Optional[USING_FOR] = None
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._is_fully_implemented: bool = False
self._is_abstract: bool = False
self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
@ -109,7 +106,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._is_incorrectly_parsed: bool = False
self._available_functions_as_dict: Optional[Dict[str, "Function"]] = None
self._all_functions_called: Optional[List["InternalCallType"]] = None
self._all_functions_called: Optional[List["Function"]] = None
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
self.file_scope: "FileScope" = scope
@ -203,12 +200,34 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def is_fully_implemented(self) -> bool:
"""
bool: True if the contract defines all functions.
In modern Solidity, virtual functions can lack an implementation.
Prior to Solidity 0.6.0, functions like the following would be not fully implemented:
```solidity
contract ImplicitAbstract{
function f() public;
}
```
"""
return self._is_fully_implemented
@is_fully_implemented.setter
def is_fully_implemented(self, is_fully_implemented: bool):
self._is_fully_implemented = is_fully_implemented
@property
def is_abstract(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the contract is abstract.
"""
return self._is_abstract
@is_abstract.setter
def is_abstract(self, is_abstract: bool):
self._is_abstract = is_abstract
# endregion
###################################################################################
###################################################################################
@ -310,29 +329,20 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for(self) -> USING_FOR:
return self._using_for
@property
def using_for_complete(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for_complete(self) -> USING_FOR:
"""
Dict[Union[str, Type], List[Type]]: Dict of merged local using for directive with top level directive
USING_FOR: Dict of merged local using for directive with top level directive
"""
def _merge_using_for(
uf1: Dict[USING_FOR_KEY, USING_FOR_ITEM], uf2: Dict[USING_FOR_KEY, USING_FOR_ITEM]
) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
result = {**uf1, **uf2}
for key, value in result.items():
if key in uf1 and key in uf2:
result[key] = value + uf1[key]
return result
if self._using_for_complete is None:
result = self.using_for
top_level_using_for = self.file_scope.using_for_directives
for uftl in top_level_using_for:
result = _merge_using_for(result, uftl.using_for)
result = merge_using_for(result, uftl.using_for)
self._using_for_complete = result
return self._using_for_complete
@ -430,7 +440,8 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def state_variables(self) -> List["StateVariable"]:
"""
Returns all the accessible variables (do not include private variable from inherited contract).
Use state_variables_ordered for all the variables following the storage order
Use stored_state_variables_ordered for all the storage variables following the storage order
Use transient_state_variables_ordered for all the transient variables following the storage order
list(StateVariable): List of the state variables.
"""
@ -448,11 +459,25 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
list(StateVariable): List of the state variables by order of declaration.
"""
return list(self._variables_ordered)
return self._variables_ordered
def add_variables_ordered(self, new_vars: List["StateVariable"]) -> None:
def add_state_variables_ordered(self, new_vars: List["StateVariable"]) -> None:
self._variables_ordered += new_vars
@property
def storage_variables_ordered(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables in storage location by order of declaration.
"""
return [v for v in self._variables_ordered if v.is_stored]
@property
def transient_variables_ordered(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables in transient location by order of declaration.
"""
return [v for v in self._variables_ordered if v.is_transient]
@property
def state_variables_inherited(self) -> List["StateVariable"]:
"""
@ -867,7 +892,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def get_function_from_canonical_name(self, canonical_name: str) -> Optional["Function"]:
"""
Return a function from a a canonical name (contract.signature())
Return a function from a canonical name (contract.signature())
Args:
canonical_name (str): canonical name of the function (without return statement)
Returns:
@ -969,16 +994,14 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def get_functions_overridden_by(self, function: "Function") -> List["Function"]:
"""
Return the list of functions overriden by the function
Return the list of functions overridden by the function
Args:
(core.Function)
Returns:
list(core.Function)
"""
candidatess = [c.functions_declared for c in self.inheritance]
candidates = [candidate for sublist in candidatess for candidate in sublist]
return [f for f in candidates if f.full_name == function.full_name]
return function.overrides
# endregion
###################################################################################
@ -988,15 +1011,21 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def all_functions_called(self) -> List["InternalCallType"]:
def all_functions_called(self) -> List["Function"]:
"""
list(Function): List of functions reachable from the contract
Includes super, and private/internal functions not shadowed
"""
from slither.slithir.operations import Operation
if self._all_functions_called is None:
all_functions = [f for f in self.functions + self.modifiers if not f.is_shadowed] # type: ignore
all_callss = [f.all_internal_calls() for f in all_functions] + [list(all_functions)]
all_calls = [item for sublist in all_callss for item in sublist]
all_calls = [
item.function if isinstance(item, Operation) else item
for sublist in all_callss
for item in sublist
]
all_calls = list(set(all_calls))
all_constructors = [c.constructor for c in self.inheritance if c.constructor]
@ -1034,18 +1063,18 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
return list(set(all_state_variables_read))
@property
def all_library_calls(self) -> List["LibraryCallType"]:
def all_library_calls(self) -> List["LibraryCall"]:
"""
list((Contract, Function): List all of the libraries func called
list(LibraryCall): List all of the libraries func called
"""
all_high_level_callss = [f.all_library_calls() for f in self.functions + self.modifiers] # type: ignore
all_high_level_calls = [item for sublist in all_high_level_callss for item in sublist]
return list(set(all_high_level_calls))
@property
def all_high_level_calls(self) -> List["HighLevelCallType"]:
def all_high_level_calls(self) -> List[Tuple["Contract", "HighLevelCall"]]:
"""
list((Contract, Function|Variable)): List all of the external high level calls
list(Tuple("Contract", "HighLevelCall")): List all of the external high level calls
"""
all_high_level_callss = [f.all_high_level_calls() for f in self.functions + self.modifiers] # type: ignore
all_high_level_calls = [item for sublist in all_high_level_callss for item in sublist]
@ -1337,8 +1366,6 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def is_upgradeable(self) -> bool:
if self._is_upgradeable is None:
self._is_upgradeable = False
if self.is_upgradeable_proxy:
return False
initializable = self.file_scope.get_contract_from_name("Initializable")
if initializable:
if initializable in self.inheritance:

@ -17,6 +17,7 @@ class CustomError(SourceMapping):
self._solidity_signature: Optional[str] = None
self._full_name: Optional[str] = None
self._pattern = "error"
@property
def name(self) -> str:

@ -31,22 +31,22 @@ from slither.utils.utils import unroll
# pylint: disable=import-outside-toplevel,too-many-instance-attributes,too-many-statements,too-many-lines
if TYPE_CHECKING:
from slither.utils.type_helpers import (
InternalCallType,
LowLevelCallType,
HighLevelCallType,
LibraryCallType,
)
from slither.core.declarations import Contract
from slither.core.declarations import Contract, FunctionContract
from slither.core.cfg.node import Node, NodeType
from slither.core.variables.variable import Variable
from slither.slithir.variables.variable import SlithIRVariable
from slither.slithir.variables import LocalIRVariable
from slither.core.expressions.expression import Expression
from slither.slithir.operations import Operation
from slither.slithir.operations import (
HighLevelCall,
InternalCall,
LibraryCall,
LowLevelCall,
SolidityCall,
Operation,
)
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.slithir.variables.state_variable import StateIRVariable
LOGGER = logging.getLogger("Function")
ReacheableNode = namedtuple("ReacheableNode", ["node", "ir"])
@ -95,14 +95,11 @@ class FunctionType(Enum):
def _filter_state_variables_written(expressions: List["Expression"]):
ret = []
for expression in expressions:
if isinstance(expression, Identifier):
ret.append(expression)
if isinstance(expression, UnaryOperation):
ret.append(expression.expression)
if isinstance(expression, MemberAccess):
if isinstance(expression, (Identifier, UnaryOperation, MemberAccess)):
ret.append(expression.expression)
if isinstance(expression, IndexAccess):
elif isinstance(expression, IndexAccess):
ret.append(expression.expression_left)
return ret
@ -126,6 +123,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._pure: bool = False
self._payable: bool = False
self._visibility: Optional[str] = None
self._virtual: bool = False
self._overrides: List["FunctionContract"] = []
self._overridden_by: List["FunctionContract"] = []
self._is_implemented: Optional[bool] = None
self._is_empty: Optional[bool] = None
@ -150,11 +150,11 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._vars_read_or_written: List["Variable"] = []
self._solidity_vars_read: List["SolidityVariable"] = []
self._state_vars_written: List["StateVariable"] = []
self._internal_calls: List["InternalCallType"] = []
self._solidity_calls: List["SolidityFunction"] = []
self._low_level_calls: List["LowLevelCallType"] = []
self._high_level_calls: List["HighLevelCallType"] = []
self._library_calls: List["LibraryCallType"] = []
self._internal_calls: List["InternalCall"] = []
self._solidity_calls: List["SolidityCall"] = []
self._low_level_calls: List["LowLevelCall"] = []
self._high_level_calls: List[Tuple["Contract", "HighLevelCall"]] = []
self._library_calls: List["LibraryCall"] = []
self._external_calls_as_expressions: List["Expression"] = []
self._expression_vars_read: List["Expression"] = []
self._expression_vars_written: List["Expression"] = []
@ -170,11 +170,13 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._all_expressions: Optional[List["Expression"]] = None
self._all_slithir_operations: Optional[List["Operation"]] = None
self._all_internals_calls: Optional[List["InternalCallType"]] = None
self._all_high_level_calls: Optional[List["HighLevelCallType"]] = None
self._all_library_calls: Optional[List["LibraryCallType"]] = None
self._all_low_level_calls: Optional[List["LowLevelCallType"]] = None
self._all_solidity_calls: Optional[List["SolidityFunction"]] = None
self._all_internals_calls: Optional[List["InternalCall"]] = None
self._all_high_level_calls: Optional[List[Tuple["Contract", "HighLevelCall"]]] = None
self._all_library_calls: Optional[List["LibraryCall"]] = None
self._all_low_level_calls: Optional[List["LowLevelCall"]] = None
self._all_solidity_calls: Optional[List["SolidityCall"]] = None
self._all_variables_read: Optional[List["Variable"]] = None
self._all_variables_written: Optional[List["Variable"]] = None
self._all_state_variables_read: Optional[List["StateVariable"]] = None
self._all_solidity_variables_read: Optional[List["SolidityVariable"]] = None
self._all_state_variables_written: Optional[List["StateVariable"]] = None
@ -351,8 +353,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
@property
def id(self) -> Optional[str]:
"""
Return the ID of the funciton. For Solidity with compact-AST the ID is the reference ID
For other, the ID is None
Return the reference ID of the function, if available.
:return:
:rtype:
@ -441,6 +442,49 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def payable(self, p: bool):
self._payable = p
# endregion
###################################################################################
###################################################################################
# region Virtual
###################################################################################
###################################################################################
@property
def is_virtual(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the function is virtual
"""
return self._virtual
@is_virtual.setter
def is_virtual(self, v: bool):
self._virtual = v
@property
def is_override(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the function overrides a base function
"""
return len(self._overrides) > 0
@property
def overridden_by(self) -> List["FunctionContract"]:
"""
List["FunctionContract"]: List of functions in child contracts that override this function
This may include distinct instances of the same function due to inheritance
"""
return self._overridden_by
@property
def overrides(self) -> List["FunctionContract"]:
"""
List["FunctionContract"]: List of functions in parent contracts that this function overrides
This may include distinct instances of the same function due to inheritance
"""
return self._overrides
# endregion
###################################################################################
###################################################################################
@ -733,14 +777,14 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
def variables(self) -> List[LocalVariable]:
"""
Return all local variables
Include paramters and return values
Include parameters and return values
"""
return list(self._variables.values())
@property
def local_variables(self) -> List[LocalVariable]:
"""
Return all local variables (dont include paramters and return values)
Return all local variables (dont include parameters and return values)
"""
return list(set(self.variables) - set(self.returns) - set(self.parameters))
@ -814,43 +858,42 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
###################################################################################
@property
def internal_calls(self) -> List["InternalCallType"]:
def internal_calls(self) -> List["InternalCall"]:
"""
list(Function or SolidityFunction): List of function calls (that does not create a transaction)
list(InternalCall): List of IR operations for internal calls
"""
return list(self._internal_calls)
@property
def solidity_calls(self) -> List[SolidityFunction]:
def solidity_calls(self) -> List["SolidityCall"]:
"""
list(SolidityFunction): List of Soldity calls
list(SolidityCall): List of IR operations for Solidity calls
"""
return list(self._solidity_calls)
@property
def high_level_calls(self) -> List["HighLevelCallType"]:
def high_level_calls(self) -> List[Tuple["Contract", "HighLevelCall"]]:
"""
list((Contract, Function|Variable)):
List of high level calls (external calls).
list(Tuple(Contract, "HighLevelCall")): List of call target contract and IR of the high level call
A variable is called in case of call to a public state variable
Include library calls
"""
return list(self._high_level_calls)
@property
def library_calls(self) -> List["LibraryCallType"]:
def library_calls(self) -> List["LibraryCall"]:
"""
list((Contract, Function)):
list(LibraryCall): List of IR operations for library calls
"""
return list(self._library_calls)
@property
def low_level_calls(self) -> List["LowLevelCallType"]:
def low_level_calls(self) -> List["LowLevelCall"]:
"""
list((Variable|SolidityVariable, str)): List of low_level call
list(LowLevelCall): List of IR operations for low level calls
A low level call is defined by
- the variable called
- the name of the function (call/delegatecall/codecall)
- the name of the function (call/delegatecall/callcode)
"""
return list(self._low_level_calls)
@ -1078,10 +1121,14 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
values = f_new_values(self)
explored = [self]
to_explore = [
c for c in self.internal_calls if isinstance(c, Function) and c not in explored
ir.function
for ir in self.internal_calls
if isinstance(ir.function, Function) and ir.function not in explored
]
to_explore += [
c for (_, c) in self.library_calls if isinstance(c, Function) and c not in explored
ir.function
for ir in self.library_calls
if isinstance(ir.function, Function) and ir.function not in explored
]
to_explore += [m for m in self.modifiers if m not in explored]
@ -1095,19 +1142,35 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
values += f_new_values(f)
to_explore += [
c
for c in f.internal_calls
if isinstance(c, Function) and c not in explored and c not in to_explore
ir.function
for ir in f.internal_calls
if isinstance(ir.function, Function)
and ir.function not in explored
and ir.function not in to_explore
]
to_explore += [
c
for (_, c) in f.library_calls
if isinstance(c, Function) and c not in explored and c not in to_explore
ir.function
for ir in f.library_calls
if isinstance(ir.function, Function)
and ir.function not in explored
and ir.function not in to_explore
]
to_explore += [m for m in f.modifiers if m not in explored and m not in to_explore]
return list(set(values))
def all_variables_read(self) -> List["Variable"]:
"""recursive version of variables_read"""
if self._all_variables_read is None:
self._all_variables_read = self._explore_functions(lambda x: x.variables_read)
return self._all_variables_read
def all_variables_written(self) -> List["Variable"]:
"""recursive version of variables_written"""
if self._all_variables_written is None:
self._all_variables_written = self._explore_functions(lambda x: x.variables_written)
return self._all_variables_written
def all_state_variables_read(self) -> List["StateVariable"]:
"""recursive version of variables_read"""
if self._all_state_variables_read is None:
@ -1155,31 +1218,31 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
)
return self._all_state_variables_written
def all_internal_calls(self) -> List["InternalCallType"]:
def all_internal_calls(self) -> List["InternalCall"]:
"""recursive version of internal_calls"""
if self._all_internals_calls is None:
self._all_internals_calls = self._explore_functions(lambda x: x.internal_calls)
return self._all_internals_calls
def all_low_level_calls(self) -> List["LowLevelCallType"]:
def all_low_level_calls(self) -> List["LowLevelCall"]:
"""recursive version of low_level calls"""
if self._all_low_level_calls is None:
self._all_low_level_calls = self._explore_functions(lambda x: x.low_level_calls)
return self._all_low_level_calls
def all_high_level_calls(self) -> List["HighLevelCallType"]:
def all_high_level_calls(self) -> List[Tuple["Contract", "HighLevelCall"]]:
"""recursive version of high_level calls"""
if self._all_high_level_calls is None:
self._all_high_level_calls = self._explore_functions(lambda x: x.high_level_calls)
return self._all_high_level_calls
def all_library_calls(self) -> List["LibraryCallType"]:
def all_library_calls(self) -> List["LibraryCall"]:
"""recursive version of library calls"""
if self._all_library_calls is None:
self._all_library_calls = self._explore_functions(lambda x: x.library_calls)
return self._all_library_calls
def all_solidity_calls(self) -> List[SolidityFunction]:
def all_solidity_calls(self) -> List["SolidityCall"]:
"""recursive version of solidity calls"""
if self._all_solidity_calls is None:
self._all_solidity_calls = self._explore_functions(lambda x: x.solidity_calls)
@ -1535,7 +1598,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
write_var = [x for x in write_var if x]
write_var = [item for sublist in write_var for item in sublist]
write_var = list(set(write_var))
# Remove dupplicate if they share the same string representation
# Remove duplicate if they share the same string representation
write_var = [
next(obj)
for i, obj in groupby(sorted(write_var, key=lambda x: str(x)), lambda x: str(x))
@ -1546,7 +1609,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
write_var = [x for x in write_var if x]
write_var = [item for sublist in write_var for item in sublist]
write_var = list(set(write_var))
# Remove dupplicate if they share the same string representation
# Remove duplicate if they share the same string representation
write_var = [
next(obj)
for i, obj in groupby(sorted(write_var, key=lambda x: str(x)), lambda x: str(x))
@ -1556,7 +1619,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
read_var = [x.variables_read_as_expression for x in self.nodes]
read_var = [x for x in read_var if x]
read_var = [item for sublist in read_var for item in sublist]
# Remove dupplicate if they share the same string representation
# Remove duplicate if they share the same string representation
read_var = [
next(obj)
for i, obj in groupby(sorted(read_var, key=lambda x: str(x)), lambda x: str(x))
@ -1566,7 +1629,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
read_var = [x.variables_read for x in self.nodes]
read_var = [x for x in read_var if x]
read_var = [item for sublist in read_var for item in sublist]
# Remove dupplicate if they share the same string representation
# Remove duplicate if they share the same string representation
read_var = [
next(obj)
for i, obj in groupby(sorted(read_var, key=lambda x: str(x)), lambda x: str(x))
@ -1598,7 +1661,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
internal_calls = [item for sublist in internal_calls for item in sublist]
self._internal_calls = list(set(internal_calls))
self._solidity_calls = [c for c in internal_calls if isinstance(c, SolidityFunction)]
self._solidity_calls = [
ir for ir in internal_calls if isinstance(ir.function, SolidityFunction)
]
low_level_calls = [x.low_level_calls for x in self.nodes]
low_level_calls = [x for x in low_level_calls if x]

@ -65,7 +65,10 @@ class FunctionContract(Function, ContractLevel):
@property
def file_scope(self) -> "FileScope":
return self.contract.file_scope
# This is the contract declarer's file scope because inherited functions have access
# to the file scope which their declared in. This scope may contain references not
# available in the child contract's scope. See inherited_function_scope.sol for an example.
return self.contract_declarer.file_scope
# endregion
###################################################################################

@ -1,10 +1,11 @@
"""
Function module
"""
from typing import Dict, List, Tuple, TYPE_CHECKING
from typing import Dict, List, Tuple, TYPE_CHECKING, Optional
from slither.core.declarations import Function
from slither.core.declarations.top_level import TopLevel
from slither.utils.using_for import USING_FOR, merge_using_for
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
@ -16,11 +17,25 @@ class FunctionTopLevel(Function, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__(compilation_unit)
self._scope: "FileScope" = scope
self._using_for_complete: Optional[USING_FOR] = None
@property
def file_scope(self) -> "FileScope":
return self._scope
@property
def using_for_complete(self) -> USING_FOR:
"""
USING_FOR: Dict of top level directive
"""
if self._using_for_complete is None:
result = {}
for uftl in self.file_scope.using_for_directives:
result = merge_using_for(result, uftl.using_for)
self._using_for_complete = result
return self._using_for_complete
@property
def canonical_name(self) -> str:
"""

@ -11,11 +11,13 @@ class Import(SourceMapping):
def __init__(self, filename: Path, scope: "FileScope") -> None:
super().__init__()
self._filename: Path = filename
self._alias: Optional[str] = None
self.scope: "FileScope" = scope
self._alias: Optional[str] = None
# Map local name -> original name
self.renaming: Dict[str, str] = {}
self._pattern = "import"
@property
def filename(self) -> str:
"""

@ -11,6 +11,7 @@ class Pragma(SourceMapping):
super().__init__()
self._directive = directive
self.scope: "FileScope" = scope
self._pattern = "pragma"
@property
def directive(self) -> List[str]:

@ -21,7 +21,8 @@ SOLIDITY_VARIABLES = {
}
SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint",
"block.basefee": "uint256",
"block.blobbasefee": "uint256",
"block.coinbase": "address",
"block.difficulty": "uint256",
"block.prevrandao": "uint256",
@ -44,10 +45,12 @@ SOLIDITY_VARIABLES_COMPOSED = {
}
SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"blobhash(uint256)": ["bytes32"],
"gasleft()": ["uint256"],
"assert(bool)": [],
"require(bool)": [],
"require(bool,string)": [],
"require(bool,error)": [], # Solidity 0.8.26 via-ir and Solidity >= 0.8.27
"revert()": [],
"revert(string)": [],
"revert ": [],
@ -80,7 +83,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"abi.encodeCall()": ["bytes"],
"bytes.concat()": ["bytes"],
"string.concat()": ["string"],
# abi.decode returns an a list arbitrary types
# abi.decode returns a list arbitrary types
"abi.decode()": [],
"type(address)": [],
"type()": [], # 0.6.8 changed type(address) to type()
@ -114,6 +117,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"_abi_encode()": [],
"slice()": [],
"uint2str()": ["string"],
"send()": [],
}
@ -137,7 +141,7 @@ class SolidityVariable(SourceMapping):
self._name = name
# dev function, will be removed once the code is stable
def _check_name(self, name: str) -> None: # pylint: disable=no-self-use
def _check_name(self, name: str) -> None:
assert name in SOLIDITY_VARIABLES or name.endswith(("_slot", "_offset"))
@property

@ -1,8 +1,7 @@
from typing import TYPE_CHECKING, List, Dict, Union
from typing import TYPE_CHECKING
from slither.core.declarations.contract import USING_FOR_KEY, USING_FOR_ITEM
from slither.core.solidity_types.type import Type
from slither.core.declarations.top_level import TopLevel
from slither.utils.using_for import USING_FOR
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
@ -11,9 +10,9 @@ if TYPE_CHECKING:
class UsingForTopLevel(TopLevel):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self._using_for: Dict[Union[str, Type], List[Type]] = {}
self._using_for: USING_FOR = {}
self.file_scope: "FileScope" = scope
@property
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for(self) -> USING_FOR:
return self._using_for

@ -78,3 +78,6 @@ class Identifier(Expression):
def __str__(self) -> str:
return str(self._value)
def expression(self):
return self

@ -29,6 +29,7 @@ class FileScope:
def __init__(self, filename: Filename) -> None:
self.filename = filename
self.accessible_scopes: List[FileScope] = []
self.exported_symbols: Set[int] = set()
self.contracts: Dict[str, Contract] = {}
# Custom error are a list instead of a dict
@ -36,11 +37,11 @@ class FileScope:
# So we simplify the logic and have the scope fields all populated
self.custom_errors: Set[CustomErrorTopLevel] = set()
self.enums: Dict[str, EnumTopLevel] = {}
self.events: Dict[str, EventTopLevel] = {}
# Functions is a list instead of a dict
# Because we parse the function signature later on
# So we simplify the logic and have the scope fields all populated
self.functions: Set[FunctionTopLevel] = set()
self.events: Set[EventTopLevel] = set()
self.using_for_directives: Set[UsingForTopLevel] = set()
self.imports: Set[Import] = set()
self.pragmas: Set[Pragma] = set()
@ -56,53 +57,39 @@ class FileScope:
# Name -> type alias
self.type_aliases: Dict[str, TypeAlias] = {}
def add_accesible_scopes(self) -> bool: # pylint: disable=too-many-branches
def add_accessible_scopes(self) -> bool: # pylint: disable=too-many-branches
"""
Add information from accessible scopes. Return true if new information was obtained
:return:
:rtype:
"""
learn_something = False
for new_scope in self.accessible_scopes:
if not _dict_contain(new_scope.contracts, self.contracts):
self.contracts.update(new_scope.contracts)
learn_something = True
if not new_scope.custom_errors.issubset(self.custom_errors):
self.custom_errors |= new_scope.custom_errors
learn_something = True
if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums)
learn_something = True
if not _dict_contain(new_scope.events, self.events):
self.events.update(new_scope.events)
learn_something = True
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
# To support using for directives on user defined types and user defined functions,
# we need to propagate the using for directives from the imported file to the importing file
# since it is not reflected in the "exportedSymbols" field of the AST.
if not new_scope.using_for_directives.issubset(self.using_for_directives):
self.using_for_directives |= new_scope.using_for_directives
learn_something = True
if not new_scope.imports.issubset(self.imports):
self.imports |= new_scope.imports
learn_something = True
if not new_scope.pragmas.issubset(self.pragmas):
self.pragmas |= new_scope.pragmas
if not _dict_contain(new_scope.type_aliases, self.type_aliases):
self.type_aliases.update(new_scope.type_aliases)
learn_something = True
if not _dict_contain(new_scope.structures, self.structures):
self.structures.update(new_scope.structures)
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
if not _dict_contain(new_scope.variables, self.variables):
self.variables.update(new_scope.variables)
# To get around this bug for aliases https://github.com/ethereum/solidity/pull/11881,
# we propagate the exported_symbols from the imported file to the importing file
# See tests/e2e/solc_parsing/test_data/top-level-nested-import-0.7.1.sol
if not new_scope.exported_symbols.issubset(self.exported_symbols):
self.exported_symbols |= new_scope.exported_symbols
learn_something = True
# This is need to support aliasing when we do a late lookup using SolidityImportPlaceholder
if not _dict_contain(new_scope.renaming, self.renaming):
self.renaming.update(new_scope.renaming)
learn_something = True
if not _dict_contain(new_scope.type_aliases, self.type_aliases):
self.type_aliases.update(new_scope.type_aliases)
learn_something = True
return learn_something

@ -8,7 +8,7 @@ import pathlib
import posixpath
import re
from collections import defaultdict
from typing import Optional, Dict, List, Set, Union, Tuple
from typing import Optional, Dict, List, Set, Union, Tuple, TypeVar
from crytic_compile import CryticCompile
from crytic_compile.utils.naming import Filename
@ -22,7 +22,7 @@ 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.sarif import read_triage_info
from slither.utils.source_mapping import get_definition, get_references, get_implementation
from slither.utils.source_mapping import get_definition, get_references, get_all_implementations
logger = logging.getLogger("Slither")
logging.basicConfig()
@ -62,6 +62,7 @@ class SlitherCore(Context):
# Multiple time the same result, so we remove duplicates
self._currently_seen_resuts: Set[str] = set()
self._paths_to_filter: Set[str] = set()
self._paths_to_include: Set[str] = set()
self._crytic_compile: Optional[CryticCompile] = None
@ -87,6 +88,7 @@ class SlitherCore(Context):
self._contracts: List[Contract] = []
self._contracts_derived: List[Contract] = []
self._offset_to_min_offset: Optional[Dict[Filename, Dict[int, Set[int]]]] = None
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
@ -194,71 +196,87 @@ 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)
implementations = get_all_implementations(thing, self.contracts)
# Create the offset mapping
for offset in range(definition.start, definition.end + 1):
self._offset_to_min_offset[definition.filename][offset].add(definition.start)
if (
isinstance(thing, TopLevel)
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
):
self._offset_to_objects[definition.filename][offset].add(thing)
is_declared_function = (
isinstance(thing, FunctionContract) and thing.contract_declarer == thing.contract
)
should_add_to_objects = (
isinstance(thing, (TopLevel, Contract))
or is_declared_function
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
)
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)
if should_add_to_objects:
self._offset_to_objects[definition.filename][definition.start].add(thing)
self._offset_to_definitions[definition.filename][definition.start].add(definition)
self._offset_to_implementations[definition.filename][definition.start].update(
implementations
)
self._offset_to_references[definition.filename][definition.start] |= set(references)
# For references
should_add_to_objects = (
isinstance(thing, TopLevel)
or is_declared_function
or (isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract))
)
for ref in references:
for offset in range(ref.start, ref.end + 1):
self._offset_to_min_offset[definition.filename][offset].add(ref.start)
if should_add_to_objects:
self._offset_to_objects[definition.filename][ref.start].add(thing)
if is_declared_function:
# Only show the nearest lexical definition for declared contract-level functions
if (
isinstance(thing, TopLevel)
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or (
isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract)
)
thing.contract.source_mapping.start
< ref.start
< thing.contract.source_mapping.end
):
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)
self._offset_to_definitions[ref.filename][ref.start].add(definition)
else:
self._offset_to_definitions[ref.filename][ref.start].add(definition)
self._offset_to_implementations[ref.filename][ref.start].update(implementations)
self._offset_to_references[ref.filename][ref.start] |= 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()))
self._offset_to_min_offset = 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:
for function in contract.functions_declared:
self._compute_offsets_from_thing(function)
for variable in function.local_variables:
self._compute_offsets_from_thing(variable)
for modifier in contract.modifiers:
for modifier in contract.modifiers_declared:
self._compute_offsets_from_thing(modifier)
for variable in modifier.local_variables:
self._compute_offsets_from_thing(variable)
for var in contract.state_variables:
self._compute_offsets_from_thing(var)
for st in contract.structures:
self._compute_offsets_from_thing(st)
@ -267,6 +285,10 @@ class SlitherCore(Context):
for event in contract.events:
self._compute_offsets_from_thing(event)
for typ in contract.type_aliases:
self._compute_offsets_from_thing(typ)
for enum in compilation_unit.enums_top_level:
self._compute_offsets_from_thing(enum)
for event in compilation_unit.events_top_level:
@ -275,28 +297,72 @@ class SlitherCore(Context):
self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level:
self._compute_offsets_from_thing(st)
for var in compilation_unit.variables_top_level:
self._compute_offsets_from_thing(var)
for typ in compilation_unit.type_aliases.values():
self._compute_offsets_from_thing(typ)
for err in compilation_unit.custom_errors:
self._compute_offsets_from_thing(err)
for event in compilation_unit.events_top_level:
self._compute_offsets_from_thing(event)
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)
T = TypeVar("T", Source, SourceMapping)
def _get_offset(
self, mapping: Dict[Filename, Dict[int, Set[T]]], filename_str: str, offset: int
) -> Set[T]:
"""Get the Source/SourceMapping referenced by the offset.
For performance reasons, references are only stored once at the lowest offset.
It uses the _offset_to_min_offset mapping to retrieve the correct offsets.
As multiple definitions can be related to the same offset, we retrieve all of them.
:param mapping: Mapping to search for (objects. references, ...)
:param filename_str: Filename to consider
:param offset: Look-up offset
:raises IndexError: When the start offset is not found
:return: The corresponding set of Source/SourceMapping
"""
filename: Filename = self.crytic_compile.filename_lookup(filename_str)
start_offsets = self._offset_to_min_offset[filename][offset]
if not start_offsets:
msg = f"Unable to find reference for offset {offset}"
raise IndexError(msg)
results = set()
for start_offset in start_offsets:
results |= mapping[filename][start_offset]
return results
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]
return self._get_offset(self._offset_to_references, filename_str, 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]
return self._get_offset(self._offset_to_implementations, filename_str, 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]
return self._get_offset(self._offset_to_definitions, filename_str, offset)
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()
return self._get_offset(self._offset_to_objects, filename_str, offset)
# endregion
###################################################################################
@ -411,25 +477,29 @@ class SlitherCore(Context):
if "source_mapping" in elem
]
# Use POSIX-style paths so that filter_paths works across different
# Use POSIX-style paths so that filter_paths|include_paths works across different
# OSes. Convert to a list so elements don't get consumed and are lost
# while evaluating the first pattern
source_mapping_elements = list(
map(lambda x: pathlib.Path(x).resolve().as_posix() if x else x, source_mapping_elements)
)
matching = False
(matching, paths, msg_err) = (
(True, self._paths_to_include, "--include-paths")
if self._paths_to_include
else (False, self._paths_to_filter, "--filter-paths")
)
for path in self._paths_to_filter:
for path in paths:
try:
if any(
bool(re.search(_relative_path_format(path), src_mapping))
for src_mapping in source_mapping_elements
):
matching = True
matching = not matching
break
except re.error:
logger.error(
f"Incorrect regular expression for --filter-paths {path}."
f"Incorrect regular expression for {msg_err} {path}."
"\nSlither supports the Python re format"
": https://docs.python.org/3/library/re.html"
)
@ -492,6 +562,7 @@ class SlitherCore(Context):
def save_results_to_hide(self, results: List[Dict]) -> None:
self._results_to_hide += results
self.write_results_to_hide()
def add_path_to_filter(self, path: str):
"""
@ -500,6 +571,13 @@ class SlitherCore(Context):
"""
self._paths_to_filter.add(path)
def add_path_to_include(self, path: str):
"""
Add path to include
Path are used through direct comparison (no regex)
"""
self._paths_to_include.add(path)
# endregion
###################################################################################
###################################################################################

@ -74,7 +74,7 @@ class ArrayType(Type):
def __eq__(self, other: Any) -> bool:
if not isinstance(other, ArrayType):
return False
return self._type == other.type and self.length == other.length
return self._type == other.type and self._length_value == other.length_value
def __hash__(self) -> int:
return hash(str(self))

@ -225,4 +225,4 @@ class ElementaryType(Type):
return self.type == other.type
def __hash__(self) -> int:
return hash(str(self))
return hash(self._type)

@ -36,7 +36,7 @@ class FunctionType(Type):
def is_dynamic(self) -> bool:
return False
def __str__(self):
def __str__(self) -> str:
# Use x.type
# x.name may be empty
params = ",".join([str(x.type) for x in self._params])

@ -15,6 +15,7 @@ class TypeAlias(Type):
super().__init__()
self.name = name
self.underlying_type = underlying_type
self._pattern = "type"
@property
def type(self) -> ElementaryType:

@ -1,10 +1,10 @@
import re
from abc import ABCMeta
from typing import Dict, Union, List, Tuple, TYPE_CHECKING, Optional, Any
from Crypto.Hash import SHA1
from crytic_compile.utils.naming import Filename
from slither.core.context.context import Context
from slither.exceptions import SlitherException
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
@ -99,21 +99,25 @@ class Source:
return f"{filename_short}{lines}"
def __hash__(self) -> int:
return hash(str(self))
return hash(
(
self.start,
self.length,
self.filename.relative,
self.end,
)
)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, type(self)):
try:
return (
self.start == other.start
and self.filename.relative == other.filename.relative
and self.is_dependency == other.is_dependency
and self.end == other.end
)
except AttributeError:
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(
@ -129,9 +133,20 @@ def _compute_line(
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
)
try:
end_line, ending_column = compilation_unit.core.crytic_compile.get_line_from_offset(
filename, start + length
)
except KeyError:
# This error may occur when the build is not synchronised with the source code on disk.
# See the GitHub issue https://github.com/crytic/slither/issues/2296
msg = f"""The source code appears to be out of sync with the build artifacts on disk.
This discrepancy can occur after recent modifications to {filename.short}. To resolve this
issue, consider executing the clean command of the build system (e.g. forge clean).
"""
# We still re-raise the exception as a SlitherException here
raise SlitherException(msg) from None
return list(range(start_line, end_line + 1)), starting_column, ending_column
@ -183,12 +198,14 @@ def _convert_source_mapping(
return new_source
class SourceMapping(Context, metaclass=ABCMeta):
class SourceMapping(Context):
def __init__(self) -> None:
super().__init__()
self.source_mapping: Optional[Source] = None
self.references: List[Source] = []
self._pattern: Union[str, None] = None
def set_offset(
self, offset: Union["Source", str], compilation_unit: "SlitherCompilationUnit"
) -> None:
@ -204,3 +221,11 @@ class SourceMapping(Context, metaclass=ABCMeta):
) -> None:
s = _convert_source_mapping(offset, compilation_unit)
self.references.append(s)
@property
def pattern(self) -> str:
if self._pattern is None:
# Add " " to look after the first solidity keyword
return f" {self.name}" # pylint: disable=no-member
return self._pattern

@ -12,6 +12,7 @@ class StateVariable(ContractLevel, Variable):
def __init__(self) -> None:
super().__init__()
self._node_initialization: Optional["Node"] = None
self._location: Optional[str] = None
def is_declared_by(self, contract: "Contract") -> bool:
"""
@ -21,6 +22,35 @@ class StateVariable(ContractLevel, Variable):
"""
return self.contract == contract
def set_location(self, loc: str) -> None:
self._location = loc
@property
def location(self) -> Optional[str]:
"""
Variable Location
Can be default or transient
Returns:
(str)
"""
return self._location
@property
def is_stored(self) -> bool:
"""
Checks if the state variable is stored, based on it not being constant or immutable or transient.
"""
return (
not self._is_constant and not self._is_immutable and not self._location == "transient"
)
@property
def is_transient(self) -> bool:
"""
Checks if the state variable is transient. A transient variable can not be constant or immutable.
"""
return self._location == "transient"
# endregion
###################################################################################
###################################################################################

@ -9,6 +9,7 @@ from slither.core.solidity_types.elementary_type import ElementaryType
if TYPE_CHECKING:
from slither.core.expressions.expression import Expression
from slither.core.declarations import Function
# pylint: disable=too-many-instance-attributes
class Variable(SourceMapping):
@ -16,7 +17,7 @@ class Variable(SourceMapping):
super().__init__()
self._name: Optional[str] = None
self._initial_expression: Optional["Expression"] = None
self._type: Optional[Type] = None
self._type: Optional[Union[List, Type, "Function", str]] = None
self._initialized: Optional[bool] = None
self._visibility: Optional[str] = None
self._is_constant = False
@ -77,7 +78,7 @@ class Variable(SourceMapping):
self._name = name
@property
def type(self) -> Optional[Type]:
def type(self) -> Optional[Union[List, Type, "Function", str]]:
return self._type
@type.setter
@ -120,7 +121,7 @@ class Variable(SourceMapping):
def visibility(self, v: str) -> None:
self._visibility = v
def set_type(self, t: Optional[Union[List, Type, str]]) -> None:
def set_type(self, t: Optional[Union[List, Type, "Function", str]]) -> None:
if isinstance(t, str):
self._type = ElementaryType(t)
return

@ -57,7 +57,6 @@ from .slither.name_reused import NameReused
from .functions.unimplemented import UnimplementedFunctionDetection
from .statements.mapping_deletion import MappingDeletionDetection
from .statements.array_length_assignment import ArrayLengthAssignment
from .variables.similar_variables import SimilarVarsDetection
from .variables.function_init_state_variables import FunctionInitializedState
from .statements.redundant_statements import RedundantStatements
from .operations.bad_prng import BadPRNG
@ -97,3 +96,13 @@ from .assembly.return_instead_of_leave import ReturnInsteadOfLeave
from .operations.incorrect_exp import IncorrectOperatorExponentiation
from .statements.tautological_compare import TautologicalCompare
from .statements.return_bomb import ReturnBomb
from .functions.out_of_order_retryable import OutOfOrderRetryable
from .functions.gelato_unprotected_randomness import GelatoUnprotectedRandomness
from .statements.chronicle_unchecked_price import ChronicleUncheckedPrice
from .statements.pyth_unchecked_confidence import PythUncheckedConfidence
from .statements.pyth_unchecked_publishtime import PythUncheckedPublishTime
from .functions.chainlink_feed_registry import ChainlinkFeedRegistry
from .functions.pyth_deprecated_functions import PythDeprecatedFunctions
from .functions.optimism_deprecation import OptimismDeprecation
# from .statements.unused_import import UnusedImport

@ -21,10 +21,8 @@ def _assembly_node(function: Function) -> Optional[SolidityCall]:
"""
for ir in function.all_slithir_operations():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
for ir in function.all_solidity_calls():
if ir.function == SolidityFunction("return(uint256,uint256)"):
return ir
return None
@ -71,23 +69,23 @@ The function will return 6 bytes starting from offset 5, instead of returning a
for c in self.contracts:
for f in c.functions_and_modifiers_declared:
for node in f.nodes:
if node.sons:
for function_called in node.internal_calls:
if isinstance(function_called, Function):
found = _assembly_node(function_called)
if found:
info: DETECTOR_INFO = [
f,
" calls ",
function_called,
" which halt the execution ",
found.node,
"\n",
]
json = self.generate_result(info)
results.append(json)
for ir in f.internal_calls:
if ir.node.sons:
function_called = ir.function
if isinstance(function_called, Function):
found = _assembly_node(function_called)
if found:
info: DETECTOR_INFO = [
f,
" calls ",
function_called,
" which halt the execution ",
found.node,
"\n",
]
json = self.generate_result(info)
results.append(json)
return results

@ -6,7 +6,6 @@ from slither.detectors.abstract_detector import (
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output
@ -42,15 +41,12 @@ The function will halt the execution, instead of returning a two uint."""
def _check_function(self, f: Function) -> List[Output]:
results: List[Output] = []
for node in f.nodes:
for ir in node.irs:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", node, "\n"]
json = self.generate_result(info)
for ir in f.solidity_calls:
if ir.function == SolidityFunction("return(uint256,uint256)"):
info: DETECTOR_INFO = [f, " contains an incorrect call to return: ", ir.node, "\n"]
json = self.generate_result(info)
results.append(json)
results.append(json)
return results
def _detect(self) -> List[Output]:

@ -8,6 +8,7 @@ from slither.slithir.operations import Binary, BinaryType
from slither.slithir.variables import Constant
from slither.core.declarations.function_contract import FunctionContract
from slither.utils.output import Output
from slither.core.cfg.node import NodeType
class ShiftParameterMixup(AbstractDetector):
@ -45,8 +46,18 @@ The shift statement will right-shift the constant 8 by `a` bits"""
def _check_function(self, f: FunctionContract) -> List[Output]:
results = []
in_assembly = False
for node in f.nodes:
if node.type == NodeType.ASSEMBLY:
in_assembly = True
continue
if node.type == NodeType.ENDASSEMBLY:
in_assembly = False
continue
if not in_assembly:
continue
for ir in node.irs:
if isinstance(ir, Binary) and ir.type in [
BinaryType.LEFT_SHIFT,

@ -1,6 +1,7 @@
"""
Check that the same pragma is used in all the files
"""
from collections import OrderedDict
from typing import List, Dict
from slither.core.compilation_unit import SlitherCompilationUnit
@ -31,16 +32,23 @@ class ConstantPragma(AbstractDetector):
def _detect(self) -> List[Output]:
results = []
pragma = self.compilation_unit.pragma_directives
versions = [p.version for p in pragma if p.is_solidity_version]
versions = sorted(list(set(versions)))
pragma_directives_by_version = OrderedDict()
for pragma in self.compilation_unit.pragma_directives:
if pragma.is_solidity_version:
if pragma.version not in pragma_directives_by_version:
pragma_directives_by_version[pragma.version] = [pragma]
else:
pragma_directives_by_version[pragma.version].append(pragma)
versions = list(pragma_directives_by_version.keys())
if len(versions) > 1:
info: DETECTOR_INFO = ["Different versions of Solidity are used:\n"]
info += [f"\t- Version used: {[str(v) for v in versions]}\n"]
info: DETECTOR_INFO = [f"{len(versions)} different versions of Solidity are used:\n"]
for p in sorted(pragma, key=lambda x: x.version):
info += ["\t- ", p, "\n"]
for version in versions:
pragmas = pragma_directives_by_version[version]
info += [f"\t- Version constraint {version} is used by:\n"]
for pragma in pragmas:
info += ["\t\t-", pragma, "\n"]
res = self.generate_result(info)

@ -12,6 +12,7 @@ from slither.detectors.abstract_detector import (
)
from slither.formatters.attributes.incorrect_solc import custom_format
from slither.utils.output import Output
from slither.utils.buggy_versions import bugs_by_version
# group:
# 0: ^ > >= < <= (optional)
@ -46,64 +47,36 @@ We also recommend avoiding complex `pragma` statement."""
# region wiki_recommendation
WIKI_RECOMMENDATION = """
Deploy with any of the following Solidity versions:
- 0.8.18
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
Deploy with a recent version of Solidity (at least 0.8.0) with no known severe issues.
Use a simple pragma version that allows any of these versions.
Consider using the latest version of Solidity for testing."""
# endregion wiki_recommendation
COMPLEX_PRAGMA_TXT = "is too complex"
OLD_VERSION_TXT = "allows old versions"
OLD_VERSION_TXT = (
"is an outdated solc version. Use a more recent version (at least 0.8.0), if possible."
)
LESS_THAN_TXT = "uses lesser than"
BUGGY_VERSION_TXT = (
"is known to contain severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
"contains known severe issues (https://solidity.readthedocs.io/en/latest/bugs.html)"
)
# Indicates the allowed versions. Must be formatted in increasing order.
ALLOWED_VERSIONS = ["0.8.18"]
TOO_RECENT_VERSION_TXT = f"necessitates a version too recent to be trusted. Consider deploying with {'/'.join(ALLOWED_VERSIONS)}."
# Indicates the versions that should not be used.
BUGGY_VERSIONS = [
"0.4.22",
"^0.4.22",
"0.5.5",
"^0.5.5",
"0.5.6",
"^0.5.6",
"0.5.14",
"^0.5.14",
"0.6.9",
"^0.6.9",
"0.8.8",
"^0.8.8",
]
ALLOWED_VERSIONS = ["0.8.0"]
def _check_version(self, version: Tuple[str, str, str, str, str]) -> Optional[str]:
op = version[0]
if op and op not in [">", ">=", "^"]:
return self.LESS_THAN_TXT
version_number = ".".join(version[2:])
if version_number in self.BUGGY_VERSIONS:
return self.BUGGY_VERSION_TXT
if version_number not in self.ALLOWED_VERSIONS:
if list(map(int, version[2:])) > list(map(int, self.ALLOWED_VERSIONS[-1].split("."))):
return self.TOO_RECENT_VERSION_TXT
return self.OLD_VERSION_TXT
if version_number in bugs_by_version and len(bugs_by_version[version_number]):
bugs = "\n".join([f"\t- {bug}" for bug in bugs_by_version[version_number]])
return self.BUGGY_VERSION_TXT + f"\n{bugs}"
return None
def _check_pragma(self, version: str) -> Optional[str]:
if version in self.BUGGY_VERSIONS:
return self.BUGGY_VERSION_TXT
versions = PATTERN.findall(version)
if len(versions) == 1:
version = versions[0]
@ -130,42 +103,45 @@ Consider using the latest version of Solidity for testing."""
# Detect all version related pragmas and check if they are disallowed.
results = []
pragma = self.compilation_unit.pragma_directives
disallowed_pragmas = []
disallowed_pragmas = {}
for p in pragma:
# Skip any pragma directives which do not refer to version
if len(p.directive) < 1 or p.directive[0] != "solidity":
continue
# This is version, so we test if this is disallowed.
reason = self._check_pragma(p.version)
if reason:
disallowed_pragmas.append((reason, p))
if reason is None:
continue
if p.version in disallowed_pragmas and reason in disallowed_pragmas[p.version]:
disallowed_pragmas[p.version][reason].append(p)
else:
disallowed_pragmas[p.version] = {reason: [p]}
# If we found any disallowed pragmas, we output our findings.
if disallowed_pragmas:
for (reason, p) in disallowed_pragmas:
info: DETECTOR_INFO = ["Pragma version", p, f" {reason}\n"]
if len(disallowed_pragmas.keys()):
for p, reasons in disallowed_pragmas.items():
info: DETECTOR_INFO = []
for r, vers in reasons.items():
info += [f"Version constraint {p} {r}.\nIt is used by:\n"]
for ver in vers:
info += ["\t- ", ver, "\n"]
json = self.generate_result(info)
results.append(json)
if self.compilation_unit.solc_version not in self.ALLOWED_VERSIONS:
if self.compilation_unit.solc_version in self.BUGGY_VERSIONS:
info = [
"solc-",
self.compilation_unit.solc_version,
" ",
self.BUGGY_VERSION_TXT,
]
else:
info = [
"solc-",
self.compilation_unit.solc_version,
" is not recommended for deployment\n",
]
if list(map(int, self.compilation_unit.solc_version.split("."))) < list(
map(int, self.ALLOWED_VERSIONS[-1].split("."))
):
info = [
"solc-",
self.compilation_unit.solc_version,
" ",
self.OLD_VERSION_TXT,
"\n",
]
json = self.generate_result(info)

@ -59,7 +59,7 @@ Every Ether sent to `Locked` will be lost."""
explored += to_explore
to_explore = []
for function in functions:
calls = [c.name for c in function.internal_calls]
calls = [ir.function.name for ir in function.internal_calls]
if "suicide(address)" in calls or "selfdestruct(address)" in calls:
return False
for node in function.nodes:

@ -13,8 +13,6 @@ from slither.detectors.abstract_detector import (
from slither.core.solidity_types.array_type import ArrayType
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.local_variable import LocalVariable
from slither.slithir.operations.high_level_call import HighLevelCall
from slither.slithir.operations.internal_call import InternalCall
from slither.core.cfg.node import Node
from slither.core.declarations.contract import Contract
from slither.core.declarations.function_contract import FunctionContract
@ -117,37 +115,26 @@ As a result, Bob's usage of the contract is incorrect."""
# pylint: disable=too-many-nested-blocks
for contract in contracts:
for function in contract.functions_and_modifiers_declared:
for node in function.nodes:
for ir in [ir for _, ir in function.high_level_calls] + function.internal_calls:
# If this node has no expression, skip it.
if not node.expression:
# Verify this references a function in our array modifying functions collection.
if ir.function not in array_modifying_funcs:
continue
for ir in node.irs:
# Verify this is a high level call.
if not isinstance(ir, (HighLevelCall, InternalCall)):
# Verify one of these parameters is an array in storage.
for (param, arg) in zip(ir.function.parameters, ir.arguments):
# Verify this argument is a variable that is an array type.
if not isinstance(arg, (StateVariable, LocalVariable)):
continue
# Verify this references a function in our array modifying functions collection.
if ir.function not in array_modifying_funcs:
if not isinstance(arg.type, ArrayType):
continue
# Verify one of these parameters is an array in storage.
for (param, arg) in zip(ir.function.parameters, ir.arguments):
# Verify this argument is a variable that is an array type.
if not isinstance(arg, (StateVariable, LocalVariable)):
continue
if not isinstance(arg.type, ArrayType):
continue
# If it is a state variable OR a local variable referencing storage, we add it to the list.
if (
isinstance(arg, StateVariable)
or (isinstance(arg, LocalVariable) and arg.location == "storage")
) and (
isinstance(param.type, ArrayType) and param.location != "storage"
):
results.append((node, arg, ir.function))
# If it is a state variable OR a local variable referencing storage, we add it to the list.
if (
isinstance(arg, StateVariable)
or (isinstance(arg, LocalVariable) and arg.location == "storage")
) and (isinstance(param.type, ArrayType) and param.location != "storage"):
results.append((ir.node, arg, ir.function))
return results
def _detect(self) -> List[Output]:

@ -35,7 +35,7 @@ class ReusedBaseConstructor(AbstractDetector):
ARGUMENT = "reused-constructor"
HELP = "Reused base constructor"
IMPACT = DetectorClassification.MEDIUM
# The confidence is medium, because prior Solidity 0.4.22, we cant differentiate
# The confidence is medium, because prior Solidity 0.4.22, we can't differentiate
# contract C is A() {
# to
# contract C is A {

@ -3,7 +3,7 @@ from typing import List
from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.cfg.node import Node
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract, Function, SolidityVariableComposed
from slither.core.declarations import Contract, Function, SolidityVariableComposed, FunctionContract
from slither.core.declarations.solidity_variables import SolidityVariable
from slither.slithir.operations import HighLevelCall, LibraryCall
@ -31,11 +31,11 @@ class ArbitrarySendErc20:
def _detect_arbitrary_from(self, contract: Contract) -> None:
for f in contract.functions:
all_high_level_calls = [
f_called[1].solidity_signature
for f_called in f.high_level_calls
if isinstance(f_called[1], Function)
ir.function.solidity_signature
for _, ir in f.high_level_calls
if isinstance(ir.function, Function)
]
all_library_calls = [f_called[1].solidity_signature for f_called in f.library_calls]
all_library_calls = [ir.function.solidity_signature for ir in f.library_calls]
if (
"transferFrom(address,address,uint256)" in all_high_level_calls
or "safeTransferFrom(address,address,address,uint256)" in all_library_calls
@ -44,51 +44,50 @@ class ArbitrarySendErc20:
"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"
in all_high_level_calls
):
ArbitrarySendErc20._arbitrary_from(f.nodes, self._permit_results)
ArbitrarySendErc20._arbitrary_from(f, self._permit_results)
else:
ArbitrarySendErc20._arbitrary_from(f.nodes, self._no_permit_results)
ArbitrarySendErc20._arbitrary_from(f, self._no_permit_results)
@staticmethod
def _arbitrary_from(nodes: List[Node], results: List[Node]) -> None:
def _arbitrary_from(function: FunctionContract, results: List[Node]) -> None:
"""Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter."""
for node in nodes:
for ir in node.irs:
if (
isinstance(ir, HighLevelCall)
and isinstance(ir.function, Function)
and ir.function.solidity_signature == "transferFrom(address,address,uint256)"
and not (
is_dependent(
ir.arguments[0],
SolidityVariableComposed("msg.sender"),
node,
)
or is_dependent(
ir.arguments[0],
SolidityVariable("this"),
node,
)
for _, ir in function.high_level_calls:
if (
isinstance(ir, LibraryCall)
and ir.function.solidity_signature
== "safeTransferFrom(address,address,address,uint256)"
and not (
is_dependent(
ir.arguments[1],
SolidityVariableComposed("msg.sender"),
ir.node,
)
):
results.append(ir.node)
elif (
isinstance(ir, LibraryCall)
and ir.function.solidity_signature
== "safeTransferFrom(address,address,address,uint256)"
and not (
is_dependent(
ir.arguments[1],
SolidityVariableComposed("msg.sender"),
node,
)
or is_dependent(
ir.arguments[1],
SolidityVariable("this"),
node,
)
or is_dependent(
ir.arguments[1],
SolidityVariable("this"),
ir.node,
)
):
results.append(ir.node)
)
):
results.append(ir.node)
elif (
isinstance(ir, HighLevelCall)
and isinstance(ir.function, Function)
and ir.function.solidity_signature == "transferFrom(address,address,uint256)"
and not (
is_dependent(
ir.arguments[0],
SolidityVariableComposed("msg.sender"),
ir.node,
)
or is_dependent(
ir.arguments[0],
SolidityVariable("this"),
ir.node,
)
)
):
results.append(ir.node)
def detect(self) -> None:
"""Detect transfers that use arbitrary `from` parameter."""

@ -30,6 +30,7 @@ from slither.slithir.operations import (
SolidityCall,
Transfer,
)
from slither.core.variables.state_variable import StateVariable
# pylint: disable=too-many-nested-blocks,too-many-branches
from slither.utils.output import Output
@ -67,6 +68,8 @@ def arbitrary_send(func: Function) -> Union[bool, List[Node]]:
continue
if ir.call_value == SolidityVariableComposed("msg.value"):
continue
if isinstance(ir.destination, StateVariable) and ir.destination.is_immutable:
continue
if is_dependent(
ir.call_value,
SolidityVariableComposed("msg.value"),

@ -0,0 +1,102 @@
from typing import List
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
class ChainlinkFeedRegistry(AbstractDetector):
ARGUMENT = "chainlink-feed-registry"
HELP = "Detect when chainlink feed registry is used"
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#chainlink-feed-registry"
WIKI_TITLE = "Chainlink Feed Registry usage"
WIKI_DESCRIPTION = "Detect when Chainlink Feed Registry is used. At the moment is only available on Ethereum Mainnet."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
import "chainlink/contracts/src/v0.8/interfaces/FeedRegistryInteface.sol"
contract A {
FeedRegistryInterface public immutable registry;
constructor(address _registry) {
registry = _registry;
}
function getPrice(address base, address quote) public return(uint256) {
(, int256 price,,,) = registry.latestRoundData(base, quote);
// Do price validation
return uint256(price);
}
}
```
If the contract is deployed on a different chain than Ethereum Mainnet the `getPrice` function will revert.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Do not use Chainlink Feed Registry outside of Ethereum Mainnet."
def _detect(self) -> List[Output]:
# https://github.com/smartcontractkit/chainlink/blob/8ca41fc8f722accfccccb4b1778db2df8fef5437/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol
registry_functions = [
"decimals",
"description",
"versiom",
"latestRoundData",
"getRoundData",
"latestAnswer",
"latestTimestamp",
"latestRound",
"getAnswer",
"getTimestamp",
"getFeed",
"getPhaseFeed",
"isFeedEnabled",
"getPhase",
"getRoundFeed",
"getPhaseRange",
"getPreviousRoundId",
"getNextRoundId",
"proposeFeed",
"confirmFeed",
"getProposedFeed",
"proposedGetRoundData",
"proposedLatestRoundData",
"getCurrentPhaseId",
]
results = []
for contract in self.compilation_unit.contracts_derived:
nodes = []
for target, ir in contract.all_high_level_calls:
if (
target.name == "FeedRegistryInterface"
and ir.function_name in registry_functions
):
nodes.append(ir.node)
# Sort so output is deterministic
nodes.sort(key=lambda x: (x.node_id, x.function.full_name))
if len(nodes) > 0:
info: DETECTOR_INFO = [
"The Chainlink Feed Registry is used in the ",
contract.name,
" contract. It's only available on Ethereum Mainnet, consider to not use it if the contract needs to be deployed on other chains.\n",
]
for node in nodes:
info.extend(["\t - ", node, "\n"])
res = self.generate_result(info)
results.append(res)
return results

@ -25,7 +25,7 @@ class DeadCode(AbstractDetector):
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code"
WIKI_TITLE = "Dead-code"
WIKI_DESCRIPTION = "Functions that are not sued."
WIKI_DESCRIPTION = "Functions that are not used."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
@ -48,13 +48,15 @@ contract Contract{
all_functionss_called = [
f.all_internal_calls() for f in contract.functions_entry_points
]
all_functions_called = [item for sublist in all_functionss_called for item in sublist]
all_functions_called = [
item.function for sublist in all_functionss_called for item in sublist
]
functions_used |= {
f.canonical_name for f in all_functions_called if isinstance(f, Function)
}
all_libss_called = [f.all_library_calls() for f in contract.functions_entry_points]
all_libs_called: List[Tuple[Contract, Function]] = [
item for sublist in all_libss_called for item in sublist
item.function for sublist in all_libss_called for item in sublist
]
functions_used |= {
lib[1].canonical_name for lib in all_libs_called if isinstance(lib, tuple)
@ -71,9 +73,10 @@ contract Contract{
continue
if isinstance(function, FunctionContract) and (
function.contract_declarer.is_from_dependency()
or function.contract_declarer.is_library
):
continue
# Continue if the functon is not implemented because it means the contract is abstract
# Continue if the function is not implemented because it means the contract is abstract
if not function.is_implemented:
continue
info: DETECTOR_INFO = [function, " is never used and should be removed\n"]

@ -13,8 +13,7 @@ from slither.detectors.abstract_detector import (
make_solc_versions,
)
from slither.formatters.functions.external_function import custom_format
from slither.slithir.operations import InternalCall, InternalDynamicCall
from slither.slithir.operations import SolidityCall
from slither.slithir.operations import InternalDynamicCall
from slither.utils.output import Output
@ -55,11 +54,11 @@ class ExternalFunction(AbstractDetector):
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:
if isinstance(ir, (InternalCall, SolidityCall)):
result.append(ir.function)
# Loop through all internal and solidity calls in the function, add them to a list.
for ir in func.internal_calls + func.solidity_calls:
result.append(ir.function)
return result
@staticmethod
@ -101,6 +100,7 @@ class ExternalFunction(AbstractDetector):
# Somehow we couldn't resolve it, which shouldn't happen, as the provided function should be found if we could
# not find some any more basic.
# pylint: disable=broad-exception-raised
raise Exception("Could not resolve the base-most function for the provided function.")
@staticmethod

@ -0,0 +1,78 @@
from typing import List
from slither.slithir.operations.internal_call import InternalCall
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
class GelatoUnprotectedRandomness(AbstractDetector):
"""
Unprotected Gelato VRF requests
"""
ARGUMENT = "gelato-unprotected-randomness"
HELP = "Call to _requestRandomness within an unprotected function"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#gelato-unprotected-randomness"
WIKI_TITLE = "Gelato unprotected randomness"
WIKI_DESCRIPTION = "Detect calls to `_requestRandomness` within an unprotected function."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C is GelatoVRFConsumerBase {
function _fulfillRandomness(
uint256 randomness,
uint256,
bytes memory extraData
) internal override {
// Do something with the random number
}
function bad() public {
_requestRandomness(abi.encode(msg.sender));
}
}
```
The function `bad` is uprotected and requests randomness."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Function that request randomness should be allowed only to authorized users."
)
def _detect(self) -> List[Output]:
results = []
for contract in self.compilation_unit.contracts_derived:
if "GelatoVRFConsumerBase" in [c.name for c in contract.inheritance]:
for function in contract.functions_entry_points:
if not function.is_protected() and (
nodes_request := [
ir.node
for ir in function.all_internal_calls()
if isinstance(ir, InternalCall)
and ir.function_name == "_requestRandomness"
]
):
# Sort so output is deterministic
nodes_request.sort(key=lambda x: (x.node_id, x.function.full_name))
for node in nodes_request:
info: DETECTOR_INFO = [
function,
" is unprotected and request randomness from Gelato VRF\n\t- ",
node,
"\n",
]
res = self.generate_result(info)
results.append(res)
return results

@ -17,7 +17,7 @@ from slither.utils.output import Output
def is_revert(node: Node) -> bool:
return node.type == NodeType.THROW or any(
c.name in ["revert()", "revert(string"] for c in node.internal_calls
ir.function.name in ["revert()", "revert(string"] for ir in node.internal_calls
)
@ -89,7 +89,7 @@ If the condition in `myModif` is false, the execution of `get()` will return 0."
info: DETECTOR_INFO = [
"Modifier ",
mod,
" does not always execute _; or revert",
" does not always execute _; or revert\n",
]
res = self.generate_result(info)

@ -0,0 +1,92 @@
from typing import List
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.core.cfg.node import Node
from slither.core.variables.variable import Variable
from slither.core.expressions import TypeConversion, Literal
from slither.utils.output import Output
class OptimismDeprecation(AbstractDetector):
ARGUMENT = "optimism-deprecation"
HELP = "Detect when deprecated Optimism predeploy or function is used."
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#optimism-deprecation"
WIKI_TITLE = "Optimism deprecated predeploy or function"
WIKI_DESCRIPTION = "Detect when deprecated Optimism predeploy or function is used."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
interface GasPriceOracle {
function scalar() external view returns (uint256);
}
contract Test {
GasPriceOracle constant OPT_GAS = GasPriceOracle(0x420000000000000000000000000000000000000F);
function a() public {
OPT_GAS.scalar();
}
}
```
The call to the `scalar` function of the Optimism GasPriceOracle predeploy always revert.
"""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Do not use the deprecated components."
def _detect(self) -> List[Output]:
results = []
deprecated_predeploys = [
"0x4200000000000000000000000000000000000000", # LegacyMessagePasser
"0x4200000000000000000000000000000000000001", # L1MessageSender
"0x4200000000000000000000000000000000000002", # DeployerWhitelist
"0x4200000000000000000000000000000000000013", # L1BlockNumber
]
for contract in self.compilation_unit.contracts_derived:
use_deprecated: List[Node] = []
for _, ir in contract.all_high_level_calls:
# To avoid FPs we assume predeploy contracts are always assigned to a constant and typecasted to an interface
# and we check the target address of a high level call.
if (
isinstance(ir.destination, Variable)
and isinstance(ir.destination.expression, TypeConversion)
and isinstance(ir.destination.expression.expression, Literal)
):
if ir.destination.expression.expression.value in deprecated_predeploys:
use_deprecated.append(ir.node)
if (
ir.destination.expression.expression.value
== "0x420000000000000000000000000000000000000F"
and ir.function_name in ("overhead", "scalar", "getL1GasUsed")
):
use_deprecated.append(ir.node)
# Sort so output is deterministic
use_deprecated.sort(key=lambda x: (x.node_id, x.function.full_name))
if len(use_deprecated) > 0:
info: DETECTOR_INFO = [
"A deprecated Optimism predeploy or function is used in the ",
contract.name,
" contract.\n",
]
for node in use_deprecated:
info.extend(["\t - ", node, "\n"])
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,155 @@
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations import Function, FunctionContract
from slither.slithir.operations import HighLevelCall
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output
class OutOfOrderRetryable(AbstractDetector):
ARGUMENT = "out-of-order-retryable"
HELP = "Out-of-order retryable transactions"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#out-of-order-retryable-transactions"
WIKI_TITLE = "Out-of-order retryable transactions"
WIKI_DESCRIPTION = "Out-of-order retryable transactions"
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract L1 {
function doStuffOnL2() external {
// Retryable A
IInbox(inbox).createRetryableTicket({
to: l2contract,
l2CallValue: 0,
maxSubmissionCost: maxSubmissionCost,
excessFeeRefundAddress: msg.sender,
callValueRefundAddress: msg.sender,
gasLimit: gasLimit,
maxFeePerGas: maxFeePerGas,
data: abi.encodeCall(l2contract.claim_rewards, ())
});
// Retryable B
IInbox(inbox).createRetryableTicket({
to: l2contract,
l2CallValue: 0,
maxSubmissionCost: maxSubmissionCost,
excessFeeRefundAddress: msg.sender,
callValueRefundAddress: msg.sender,
gasLimit: gas,
maxFeePerGas: maxFeePerGas,
data: abi.encodeCall(l2contract.unstake, ())
});
}
}
contract L2 {
function claim_rewards() public {
// rewards is computed based on balance and staking period
uint unclaimed_rewards = _compute_and_update_rewards();
token.safeTransfer(msg.sender, unclaimed_rewards);
}
// Call claim_rewards before unstaking, otherwise you lose your rewards
function unstake() public {
_free_rewards(); // clean up rewards related variables
balance = balance[msg.sender];
balance[msg.sender] = 0;
staked_token.safeTransfer(msg.sender, balance);
}
}
```
Bob calls `doStuffOnL2` but the first retryable ticket calling `claim_rewards` fails. The second retryable ticket calling `unstake` is executed successfully. As a result, Bob loses his rewards."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Do not rely on the order or successful execution of retryable tickets."
key = "OUTOFORDERRETRYABLE"
# pylint: disable=too-many-branches
def _detect_multiple_tickets(
self, function: FunctionContract, node: Node, visited: List[Node]
) -> None:
if node in visited:
return
visited = visited + [node]
fathers_context = []
for father in node.fathers:
if self.key in father.context:
fathers_context += father.context[self.key]
# Exclude path that dont bring further information
if node in self.visited_all_paths:
if all(f_c in self.visited_all_paths[node] for f_c in fathers_context):
return
else:
self.visited_all_paths[node] = []
self.visited_all_paths[node] = self.visited_all_paths[node] + fathers_context
if self.key not in node.context:
node.context[self.key] = fathers_context
# include ops from internal function calls
internal_ops = []
for ir in node.internal_calls:
if isinstance(ir.function, Function):
internal_ops += ir.function.all_slithir_operations()
# analyze node for retryable tickets
for ir in node.irs + internal_ops:
if (
isinstance(ir, HighLevelCall)
and isinstance(ir.function, Function)
and ir.function.name
in [
"createRetryableTicket",
"outboundTransferCustomRefund",
"unsafeCreateRetryableTicket",
]
):
node.context[self.key].append(node)
if len(node.context[self.key]) > 1:
self.results.append(node.context[self.key])
return
for son in node.sons:
self._detect_multiple_tickets(function, son, visited)
def _detect(self) -> List[Output]:
results = []
# pylint: disable=attribute-defined-outside-init
self.results = []
self.visited_all_paths = {}
for contract in self.compilation_unit.contracts:
for function in contract.functions:
if (
function.is_implemented
and function.contract_declarer == contract
and function.entry_point
):
function.entry_point.context[self.key] = []
self._detect_multiple_tickets(function, function.entry_point, [])
for multiple_tickets in self.results:
info = ["Multiple retryable tickets created in the same function:\n"]
for x in multiple_tickets:
info += ["\t -", x, "\n"]
json = self.generate_result(info)
results.append(json)
return results

@ -61,7 +61,9 @@ contract Buggy{
if not function_protection:
self.logger.error(f"{function_sig} not found")
continue
if function_protection not in function.all_internal_calls():
if function_protection not in [
ir.function for ir in function.all_internal_calls()
]:
info: DETECTOR_INFO = [
function,
" should have ",

@ -0,0 +1,73 @@
from typing import List
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
class PythDeprecatedFunctions(AbstractDetector):
"""
Documentation: This detector finds deprecated Pyth function calls
"""
ARGUMENT = "pyth-deprecated-functions"
HELP = "Detect Pyth deprecated functions"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#pyth-deprecated-functions"
WIKI_TITLE = "Pyth deprecated functions"
WIKI_DESCRIPTION = "Detect when a Pyth deprecated function is used"
WIKI_RECOMMENDATION = (
"Do not use deprecated Pyth functions. Visit https://api-reference.pyth.network/."
)
WIKI_EXPLOIT_SCENARIO = """
```solidity
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
contract C {
IPyth pyth;
constructor(IPyth _pyth) {
pyth = _pyth;
}
function A(bytes32 priceId) public {
PythStructs.Price memory price = pyth.getPrice(priceId);
...
}
}
```
The function `A` uses the deprecated `getPrice` Pyth function.
"""
def _detect(self):
DEPRECATED_PYTH_FUNCTIONS = [
"getValidTimePeriod",
"getEmaPrice",
"getPrice",
]
results: List[Output] = []
for contract in self.compilation_unit.contracts_derived:
for target_contract, ir in contract.all_high_level_calls:
if (
target_contract.name == "IPyth"
and ir.function_name in DEPRECATED_PYTH_FUNCTIONS
):
info: DETECTOR_INFO = [
"The following Pyth deprecated function is used\n\t- ",
ir.node,
"\n",
]
res = self.generate_result(info)
results.append(res)
return results

@ -59,7 +59,7 @@ Bob calls `kill` and destructs the contract."""
if func.visibility not in ["public", "external"]:
return False
calls = [c.name for c in func.all_internal_calls()]
calls = [ir.function.name for ir in func.all_internal_calls()]
if not ("suicide(address)" in calls or "selfdestruct(address)" in calls):
return False

@ -16,7 +16,7 @@ class NamingConvention(AbstractDetector):
Exceptions:
- Allow constant variables name/symbol/decimals to be lowercase (ERC20)
- Allow '_' at the beggining of the mixed_case match for private variables and unused parameters
- Allow '_' at the beginning of the mixed_case match for private variables and unused parameters
- Ignore echidna properties (functions with names starting 'echidna_' or 'crytic_'
"""

@ -3,14 +3,14 @@ Module detecting usage of more than one dynamic type in abi.encodePacked() argum
"""
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations.solidity_variables import SolidityFunction
from slither.slithir.operations import SolidityCall
from slither.core.declarations import Contract, SolidityFunction
from slither.core.variables import Variable
from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.core.solidity_types import ElementaryType
from slither.core.solidity_types import ArrayType
def _is_dynamic_type(arg):
def _is_dynamic_type(arg: Variable):
"""
Args:
arg (function argument)
@ -25,7 +25,7 @@ def _is_dynamic_type(arg):
return False
def _detect_abi_encodePacked_collision(contract):
def _detect_abi_encodePacked_collision(contract: Contract):
"""
Args:
contract (Contract)
@ -35,22 +35,19 @@ def _detect_abi_encodePacked_collision(contract):
ret = []
# pylint: disable=too-many-nested-blocks
for f in contract.functions_and_modifiers_declared:
for n in f.nodes:
for ir in n.irs:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"abi.encodePacked()"
):
dynamic_type_count = 0
for arg in ir.arguments:
if is_tainted(arg, contract) and _is_dynamic_type(arg):
dynamic_type_count += 1
elif dynamic_type_count > 1:
ret.append((f, n))
dynamic_type_count = 0
else:
dynamic_type_count = 0
if dynamic_type_count > 1:
ret.append((f, n))
for ir in f.solidity_calls:
if ir.function == SolidityFunction("abi.encodePacked()"):
dynamic_type_count = 0
for arg in ir.arguments:
if is_tainted(arg, contract) and _is_dynamic_type(arg):
dynamic_type_count += 1
elif dynamic_type_count > 1:
ret.append((f, ir.node))
dynamic_type_count = 0
else:
dynamic_type_count = 0
if dynamic_type_count > 1:
ret.append((f, ir.node))
return ret

@ -44,10 +44,9 @@ class LowLevelCalls(AbstractDetector):
) -> List[Tuple[FunctionContract, List[Node]]]:
ret = []
for f in [f for f in contract.functions if contract == f.contract_declarer]:
nodes = f.nodes
assembly_nodes = [n for n in nodes if self._contains_low_level_calls(n)]
if assembly_nodes:
ret.append((f, assembly_nodes))
low_level_nodes = [ir.node for ir in f.low_level_calls]
if low_level_nodes:
ret.append((f, low_level_nodes))
return ret
def _detect(self) -> List[Output]:

@ -39,5 +39,5 @@ If `send` is used to prevent blocking operations, consider logging the failed `s
WIKI_RECOMMENDATION = "Ensure that the return value of `send` is checked or logged."
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use
def _is_instance(self, ir: Operation) -> bool:
return isinstance(ir, Send)

@ -46,7 +46,7 @@ Several tokens do not revert in case of failure and return false. If one of thes
"Use `SafeERC20`, or ensure that the transfer/transferFrom return value is checked."
)
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use
def _is_instance(self, ir: Operation) -> bool:
return (
isinstance(ir, HighLevelCall)
and isinstance(ir.function, Function)

@ -49,7 +49,7 @@ contract MyConc{
WIKI_RECOMMENDATION = "Ensure that all the return values of the function calls are used."
def _is_instance(self, ir: Operation) -> bool: # pylint: disable=no-self-use
def _is_instance(self, ir: Operation) -> bool:
return (
isinstance(ir, HighLevelCall)
and (
@ -64,9 +64,7 @@ contract MyConc{
and isinstance(ir, (Assignment, Unpack))
)
def detect_unused_return_values(
self, f: FunctionContract
) -> List[Node]: # pylint: disable=no-self-use
def detect_unused_return_values(self, f: FunctionContract) -> List[Node]:
"""
Return the nodes where the return value of a call is unused
Args:

@ -145,15 +145,16 @@ class AbstractState:
)
slithir_operations = []
# Add the state variables written in internal calls
for internal_call in node.internal_calls:
for ir in node.internal_calls:
# Filter to Function, as internal_call can be a solidity call
if isinstance(internal_call, Function):
for internal_node in internal_call.all_nodes():
function = ir.function
if isinstance(function, Function):
for internal_node in function.all_nodes():
for read in internal_node.state_variables_read:
state_vars_read[read].add(internal_node)
for write in internal_node.state_variables_written:
state_vars_written[write].add(internal_node)
slithir_operations += internal_call.all_slithir_operations()
slithir_operations += function.all_slithir_operations()
contains_call = False
@ -195,7 +196,7 @@ class AbstractState:
def _filter_if(node: Node) -> bool:
"""
Check if the node is a condtional node where
Check if the node is a conditional node where
there is an external call checked
Heuristic:
- The call is a IF node

@ -1,6 +1,8 @@
from collections import defaultdict
from typing import List
from crytic_compile.platform import Type as PlatformType
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract
from slither.detectors.abstract_detector import (
@ -61,6 +63,8 @@ As a result, the second contract cannot be analyzed.
def _detect(self) -> List[Output]:
results = []
compilation_unit = self.compilation_unit
if compilation_unit.core.crytic_compile.platform != PlatformType.TRUFFLE:
return []
all_contracts = compilation_unit.contracts
all_contracts_name = [c.name for c in all_contracts]

@ -30,22 +30,22 @@ def detect_assert_state_change(
# Loop for each function and modifier.
for function in contract.functions_declared + list(contract.modifiers_declared):
for node in function.nodes:
for ir_call in function.internal_calls:
# Detect assert() calls
if any(c.name == "assert(bool)" for c in node.internal_calls) and (
if ir_call.function.name == "assert(bool)" and (
# Detect direct changes to state
node.state_variables_written
ir_call.node.state_variables_written
or
# Detect changes to state via function calls
any(
ir
for ir in node.irs
for ir in ir_call.node.irs
if isinstance(ir, InternalCall)
and ir.function
and ir.function.state_variables_written
)
):
results.append((function, node))
results.append((function, ir_call.node))
# Return the resulting set of nodes
return results

@ -0,0 +1,147 @@
from typing import List
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
from slither.slithir.operations import Binary, Assignment, Unpack, SolidityCall
from slither.core.variables import Variable
from slither.core.declarations.solidity_variables import SolidityFunction
from slither.core.cfg.node import Node
class ChronicleUncheckedPrice(AbstractDetector):
"""
Documentation: This detector finds calls to Chronicle oracle where the returned price is not checked
https://docs.chroniclelabs.org/Resources/FAQ/Oracles#how-do-i-check-if-an-oracle-becomes-inactive-gets-deprecated
"""
ARGUMENT = "chronicle-unchecked-price"
HELP = "Detect when Chronicle price is not checked."
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#chronicle-unchecked-price"
WIKI_TITLE = "Chronicle unchecked price"
WIKI_DESCRIPTION = "Chronicle oracle is used and the price returned is not checked to be valid. For more information https://docs.chroniclelabs.org/Resources/FAQ/Oracles#how-do-i-check-if-an-oracle-becomes-inactive-gets-deprecated."
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
IChronicle chronicle;
constructor(address a) {
chronicle = IChronicle(a);
}
function bad() public {
uint256 price = chronicle.read();
}
```
The `bad` function gets the price from Chronicle by calling the read function however it does not check if the price is valid."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Validate that the price returned by the oracle is valid."
def _var_is_checked(self, nodes: List[Node], var_to_check: Variable) -> bool:
visited = set()
checked = False
while nodes:
if checked:
break
next_node = nodes[0]
nodes = nodes[1:]
for node_ir in next_node.all_slithir_operations():
if isinstance(node_ir, Binary) and var_to_check in node_ir.read:
checked = True
break
# This case is for tryRead and tryReadWithAge
# if the isValid boolean is checked inside a require(isValid)
if (
isinstance(node_ir, SolidityCall)
and node_ir.function
in (
SolidityFunction("require(bool)"),
SolidityFunction("require(bool,string)"),
SolidityFunction("require(bool,error)"),
)
and var_to_check in node_ir.read
):
checked = True
break
if next_node not in visited:
visited.add(next_node)
for son in next_node.sons:
if son not in visited:
nodes.append(son)
return checked
# pylint: disable=too-many-nested-blocks,too-many-branches
def _detect(self) -> List[Output]:
results: List[Output] = []
for contract in self.compilation_unit.contracts_derived:
for target_contract, ir in sorted(
contract.all_high_level_calls,
key=lambda x: (x[1].node.node_id, x[1].node.function.full_name),
):
if target_contract.name in ("IScribe", "IChronicle") and ir.function_name in (
"read",
"tryRead",
"readWithAge",
"tryReadWithAge",
"latestAnswer",
"latestRoundData",
):
found = False
if ir.function_name in ("read", "latestAnswer"):
# We need to iterate the IRs as we are not always sure that the following IR is the assignment
# for example in case of type conversion it isn't
for node_ir in ir.node.irs:
if isinstance(node_ir, Assignment):
possible_unchecked_variable_ir = node_ir.lvalue
found = True
break
elif ir.function_name in ("readWithAge", "tryRead", "tryReadWithAge"):
# We are interested in the first item of the tuple
# readWithAge : value
# tryRead/tryReadWithAge : isValid
for node_ir in ir.node.irs:
if isinstance(node_ir, Unpack) and node_ir.index == 0:
possible_unchecked_variable_ir = node_ir.lvalue
found = True
break
elif ir.function_name == "latestRoundData":
found = False
for node_ir in ir.node.irs:
if isinstance(node_ir, Unpack) and node_ir.index == 1:
possible_unchecked_variable_ir = node_ir.lvalue
found = True
break
# If we did not find the variable assignment we know it's not checked
checked = (
self._var_is_checked(ir.node.sons, possible_unchecked_variable_ir)
if found
else False
)
if not checked:
info: DETECTOR_INFO = [
"Chronicle price is not checked to be valid in ",
ir.node.function,
"\n\t- ",
ir.node,
"\n",
]
res = self.generate_result(info)
results.append(res)
return results

@ -8,20 +8,18 @@ from slither.detectors.abstract_detector import (
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import LowLevelCall
from slither.utils.output import Output
def controlled_delegatecall(function: FunctionContract) -> List[Node]:
ret = []
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, LowLevelCall) and ir.function_name in [
"delegatecall",
"callcode",
]:
if is_tainted(ir.destination, function.contract):
ret.append(node)
for ir in function.low_level_calls:
if ir.function_name in [
"delegatecall",
"callcode",
]:
if is_tainted(ir.destination, function.contract):
ret.append(ir.node)
return ret

@ -56,7 +56,7 @@ def is_assert(node: Node) -> bool:
# Old Solidity code where using an internal 'assert(bool)' function
# While we dont check that this function is correct, we assume it is
# To avoid too many FP
if "assert(bool)" in [c.full_name for c in node.internal_calls]:
if "assert(bool)" in [ir.function.full_name for ir in node.internal_calls]:
return True
return False
@ -80,7 +80,7 @@ def _explore(
for ir in node.irs:
if isinstance(ir, Assignment):
if ir.rvalue in divisions:
# Avoid dupplicate. We dont use set so we keep the order of the nodes
# Avoid duplicate. We dont use set so we keep the order of the nodes
if node not in divisions[ir.rvalue]: # type: ignore
divisions[ir.lvalue] = divisions[ir.rvalue] + [node] # type: ignore
else:
@ -94,7 +94,7 @@ def _explore(
nodes = []
for r in mul_arguments:
if not isinstance(r, Constant) and (r in divisions):
# Dont add node already present to avoid dupplicate
# Dont add node already present to avoid duplicate
# We dont use set to keep the order of the nodes
if node in divisions[r]:
nodes += [n for n in divisions[r] if n not in nodes]

@ -8,6 +8,9 @@ from slither.detectors.abstract_detector import (
from slither.slithir.operations import InternalCall
from slither.core.declarations import SolidityVariableComposed, Contract
from slither.utils.output import Output
from slither.slithir.variables.constant import Constant
from slither.core.variables import Variable
from slither.core.expressions.literal import Literal
def detect_msg_value_in_loop(contract: Contract) -> List[Node]:
@ -37,6 +40,21 @@ def msg_value_in_loop(
for ir in node.all_slithir_operations():
if in_loop_counter > 0 and SolidityVariableComposed("msg.value") in ir.read:
# If we find a conditional expression with msg.value and is compared to 0 we don't report it
if ir.node.is_conditional() and SolidityVariableComposed("msg.value") in ir.read:
compared_to = (
ir.read[1]
if ir.read[0] == SolidityVariableComposed("msg.value")
else ir.read[0]
)
if (
isinstance(compared_to, Constant)
and compared_to.value == 0
or isinstance(compared_to, Variable)
and isinstance(compared_to.expression, Literal)
and str(compared_to.expression.value) == "0"
):
continue
results.append(ir.node)
if isinstance(ir, (InternalCall)):
msg_value_in_loop(ir.function.entry_point, in_loop_counter, visited, results)

@ -0,0 +1,79 @@
from typing import List
from slither.detectors.abstract_detector import (
AbstractDetector,
DETECTOR_INFO,
)
from slither.utils.output import Output
from slither.slithir.operations import Member, Binary, Assignment
class PythUnchecked(AbstractDetector):
"""
Documentation: This detector finds deprecated Pyth function calls
"""
# To be overriden in the derived class
PYTH_FUNCTIONS = []
PYTH_FIELD = ""
# pylint: disable=too-many-nested-blocks
def _detect(self) -> List[Output]:
results: List[Output] = []
for contract in self.compilation_unit.contracts_derived:
for target_contract, ir in contract.all_high_level_calls:
if target_contract.name == "IPyth" and ir.function_name in self.PYTH_FUNCTIONS:
# We know for sure the second IR in the node is an Assignment operation of the TMP variable. Example:
# Expression: price = pyth.getEmaPriceNoOlderThan(id,age)
# IRs:
# TMP_0(PythStructs.Price) = HIGH_LEVEL_CALL, dest:pyth(IPyth), function:getEmaPriceNoOlderThan, arguments:['id', 'age']
# price(PythStructs.Price) := TMP_0(PythStructs.Price)
assert isinstance(ir.node.irs[1], Assignment)
return_variable = ir.node.irs[1].lvalue
checked = False
possible_unchecked_variable_ir = None
nodes = ir.node.sons
visited = set()
while nodes:
if checked:
break
next_node = nodes[0]
nodes = nodes[1:]
for node_ir in next_node.all_slithir_operations():
# We are accessing the unchecked_var field of the returned Price struct
if (
isinstance(node_ir, Member)
and node_ir.variable_left == return_variable
and node_ir.variable_right.name == self.PYTH_FIELD
):
possible_unchecked_variable_ir = node_ir.lvalue
# We assume that if unchecked_var happens to be inside a binary operation is checked
if (
isinstance(node_ir, Binary)
and possible_unchecked_variable_ir is not None
and possible_unchecked_variable_ir in node_ir.read
):
checked = True
break
if next_node not in visited:
visited.add(next_node)
for son in next_node.sons:
if son not in visited:
nodes.append(son)
if not checked:
info: DETECTOR_INFO = [
f"Pyth price {self.PYTH_FIELD} field is not checked in ",
ir.node.function,
"\n\t- ",
ir.node,
"\n",
]
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,50 @@
from slither.detectors.abstract_detector import DetectorClassification
from slither.detectors.statements.pyth_unchecked import PythUnchecked
class PythUncheckedConfidence(PythUnchecked):
"""
Documentation: This detector finds when the confidence level of a Pyth price is not checked
"""
ARGUMENT = "pyth-unchecked-confidence"
HELP = "Detect when the confidence level of a Pyth price is not checked"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#pyth-unchecked-confidence"
WIKI_TITLE = "Pyth unchecked confidence level"
WIKI_DESCRIPTION = "Detect when the confidence level of a Pyth price is not checked"
WIKI_RECOMMENDATION = "Check the confidence level of a Pyth price. Visit https://docs.pyth.network/price-feeds/best-practices#confidence-intervals for more information."
WIKI_EXPLOIT_SCENARIO = """
```solidity
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
contract C {
IPyth pyth;
constructor(IPyth _pyth) {
pyth = _pyth;
}
function bad(bytes32 id, uint256 age) public {
PythStructs.Price memory price = pyth.getEmaPriceNoOlderThan(id, age);
// Use price
}
}
```
The function `A` uses the price without checking its confidence level.
"""
PYTH_FUNCTIONS = [
"getEmaPrice",
"getEmaPriceNoOlderThan",
"getEmaPriceUnsafe",
"getPrice",
"getPriceNoOlderThan",
"getPriceUnsafe",
]
PYTH_FIELD = "conf"

@ -0,0 +1,52 @@
from slither.detectors.abstract_detector import DetectorClassification
from slither.detectors.statements.pyth_unchecked import PythUnchecked
class PythUncheckedPublishTime(PythUnchecked):
"""
Documentation: This detector finds when the publishTime of a Pyth price is not checked
"""
ARGUMENT = "pyth-unchecked-publishtime"
HELP = "Detect when the publishTime of a Pyth price is not checked"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#pyth-unchecked-publishtime"
)
WIKI_TITLE = "Pyth unchecked publishTime"
WIKI_DESCRIPTION = "Detect when the publishTime of a Pyth price is not checked"
WIKI_RECOMMENDATION = "Check the publishTime of a Pyth price."
WIKI_EXPLOIT_SCENARIO = """
```solidity
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
contract C {
IPyth pyth;
constructor(IPyth _pyth) {
pyth = _pyth;
}
function bad(bytes32 id) public {
PythStructs.Price memory price = pyth.getEmaPriceUnsafe(id);
// Use price
}
}
```
The function `A` uses the price without checking its `publishTime` coming from the `getEmaPriceUnsafe` function.
"""
PYTH_FUNCTIONS = [
"getEmaPrice",
# "getEmaPriceNoOlderThan",
"getEmaPriceUnsafe",
"getPrice",
# "getPriceNoOlderThan",
"getPriceUnsafe",
]
PYTH_FIELD = "publishTime"

@ -9,7 +9,7 @@ from slither.detectors.abstract_detector import (
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import LowLevelCall, HighLevelCall
from slither.slithir.operations import HighLevelCall
from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.utils.output import Output
@ -71,34 +71,31 @@ Callee unexpectedly makes the caller OOG.
def get_nodes_for_function(self, function: Function, contract: Contract) -> List[Node]:
nodes = []
for node in function.nodes:
for ir in node.irs:
if isinstance(ir, (HighLevelCall, LowLevelCall)):
if not is_tainted(ir.destination, contract): # type:ignore
# Only interested if the target address is controlled/tainted
continue
if isinstance(ir, HighLevelCall) and isinstance(ir.function, Function):
# in normal highlevel calls return bombs are _possible_
# if the return type is dynamic and the caller tries to copy and decode large data
has_dyn = False
if ir.function.return_type:
has_dyn = any(
self.is_dynamic_type(ty) for ty in ir.function.return_type
)
if not has_dyn:
continue
# If a gas budget was specified then the
# user may not know about the return bomb
if ir.call_gas is None:
# if a gas budget was NOT specified then the caller
# may already suspect the call may spend all gas?
continue
nodes.append(node)
# TODO: check that there is some state change after the call
for ir in [ir for _, ir in function.high_level_calls] + function.low_level_calls:
if not is_tainted(ir.destination, contract): # type:ignore
# Only interested if the target address is controlled/tainted
continue
if isinstance(ir, HighLevelCall) and isinstance(ir.function, Function):
# in normal highlevel calls return bombs are _possible_
# if the return type is dynamic and the caller tries to copy and decode large data
has_dyn = False
if ir.function.return_type:
has_dyn = any(self.is_dynamic_type(ty) for ty in ir.function.return_type)
if not has_dyn:
continue
# If a gas budget was specified then the
# user may not know about the return bomb
if ir.call_gas is None:
# if a gas budget was NOT specified then the caller
# may already suspect the call may spend all gas?
continue
nodes.append(ir.node)
# TODO: check that there is some state change after the call
return nodes

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

Loading…
Cancel
Save