Merge branch 'dev' into update-pr-2034

pull/2384/head
alpharush 8 months ago
commit ee0afadf4b
  1. 6
      .github/actions/upload-coverage/action.yml
  2. 0
      .github/scripts/integration_test_runner.sh
  3. 0
      .github/scripts/tool_test_runner.sh
  4. 0
      .github/scripts/unit_test_runner.sh
  5. 4
      .github/workflows/black.yml
  6. 10
      .github/workflows/ci.yml
  7. 12
      .github/workflows/docker.yml
  8. 10
      .github/workflows/docs.yml
  9. 6
      .github/workflows/doctor.yml
  10. 40
      .github/workflows/issue-metrics.yml
  11. 19
      .github/workflows/linter.yml
  12. 32
      .github/workflows/matchers/pylint.json
  13. 22
      .github/workflows/matchers/yamllint.json
  14. 4
      .github/workflows/pip-audit.yml
  15. 13
      .github/workflows/publish.yml
  16. 12
      .github/workflows/pylint.yml
  17. 45
      .github/workflows/test.yml
  18. 64
      CITATION.cff
  19. 12
      CONTRIBUTING.md
  20. 2
      Dockerfile
  21. 295
      README.md
  22. 14
      examples/scripts/data_dependency.py
  23. 9
      examples/scripts/data_dependency.sol
  24. 3
      examples/scripts/possible_paths.py
  25. 4
      pyproject.toml
  26. 0
      scripts/ci_test_interface.sh
  27. 2
      scripts/ci_test_printers.sh
  28. 11
      setup.py
  29. 95
      slither/__main__.py
  30. 26
      slither/core/cfg/node.py
  31. 49
      slither/core/compilation_unit.py
  32. 5
      slither/core/declarations/__init__.py
  33. 114
      slither/core/declarations/contract.py
  34. 4
      slither/core/declarations/custom_error_contract.py
  35. 4
      slither/core/declarations/custom_error_top_level.py
  36. 24
      slither/core/declarations/event.py
  37. 25
      slither/core/declarations/event_contract.py
  38. 13
      slither/core/declarations/event_top_level.py
  39. 87
      slither/core/declarations/function.py
  40. 40
      slither/core/declarations/solidity_variables.py
  41. 13
      slither/core/dominators/utils.py
  42. 1
      slither/core/expressions/__init__.py
  43. 2
      slither/core/expressions/binary_operation.py
  44. 37
      slither/core/expressions/call_expression.py
  45. 1
      slither/core/expressions/identifier.py
  46. 6
      slither/core/expressions/self_identifier.py
  47. 2
      slither/core/expressions/unary_operation.py
  48. 13
      slither/core/scope/scope.py
  49. 103
      slither/core/slither_core.py
  50. 6
      slither/core/variables/__init__.py
  51. 4
      slither/core/variables/local_variable.py
  52. 10
      slither/core/variables/variable.py
  53. 24
      slither/detectors/abstract_detector.py
  54. 6
      slither/detectors/all_detectors.py
  55. 93
      slither/detectors/assembly/incorrect_return.py
  56. 68
      slither/detectors/assembly/return_instead_of_leave.py
  57. 11
      slither/detectors/assembly/shift_parameter_mixup.py
  58. 2
      slither/detectors/attributes/incorrect_solc.py
  59. 155
      slither/detectors/functions/out_of_order_retryable.py
  60. 2
      slither/detectors/functions/suicidal.py
  61. 21
      slither/detectors/naming_convention/naming_convention.py
  62. 7
      slither/detectors/operations/cache_array_length.py
  63. 93
      slither/detectors/operations/incorrect_exp.py
  64. 2
      slither/detectors/operations/unchecked_send_return_value.py
  65. 2
      slither/detectors/operations/unchecked_transfer.py
  66. 6
      slither/detectors/operations/unused_return_values.py
  67. 2
      slither/detectors/statements/deprecated_calls.py
  68. 10
      slither/detectors/statements/divide_before_multiply.py
  69. 24
      slither/detectors/statements/mapping_deletion.py
  70. 18
      slither/detectors/statements/msg_value_in_loop.py
  71. 123
      slither/detectors/statements/return_bomb.py
  72. 69
      slither/detectors/statements/tautological_compare.py
  73. 2
      slither/detectors/variables/predeclaration_usage_local.py
  74. 4
      slither/detectors/variables/unchanged_state_variables.py
  75. 2
      slither/formatters/naming_convention/naming_convention.py
  76. 2
      slither/printers/abstract_printer.py
  77. 3
      slither/printers/all_printers.py
  78. 119
      slither/printers/guidance/echidna.py
  79. 7
      slither/printers/inheritance/inheritance_graph.py
  80. 58
      slither/printers/summary/ck.py
  81. 12
      slither/printers/summary/declaration.py
  82. 49
      slither/printers/summary/halstead.py
  83. 32
      slither/printers/summary/martin.py
  84. 7
      slither/printers/summary/variable_order.py
  85. 49
      slither/slither.py
  86. 156
      slither/slithir/convert.py
  87. 21
      slither/slithir/operations/call.py
  88. 10
      slither/slithir/operations/high_level_call.py
  89. 2
      slither/slithir/operations/init_array.py
  90. 10
      slither/slithir/operations/internal_call.py
  91. 2
      slither/slithir/operations/new_array.py
  92. 25
      slither/slithir/operations/new_contract.py
  93. 11
      slither/slithir/operations/new_structure.py
  94. 5
      slither/slithir/operations/type_conversion.py
  95. 5
      slither/slithir/operations/unary.py
  96. 19
      slither/slithir/tmp_operations/tmp_call.py
  97. 21
      slither/slithir/utils/ssa.py
  98. 2
      slither/slithir/variables/constant.py
  99. 41
      slither/solc_parsing/declarations/contract.py
  100. 9
      slither/solc_parsing/declarations/custom_error.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -15,15 +15,15 @@ runs:
# This method has the limitation of 1 coverage file per run, limiting some coverage between online/offline tests.
- run: |
COVERAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid4())")
echo "COVERAGE_UUID=${COVERAGE_UUID}" >> $GITHUB_OUTPUT
echo "COVERAGE_UUID=${COVERAGE_UUID}" >> "$GITHUB_OUTPUT"
if [ -f .coverage ]; then
mv .coverage .coverage.${COVERAGE_UUID}
fi
id: coverage-uuid
shell: bash
- uses: actions/upload-artifact@v3.1.0
- uses: actions/upload-artifact@v4
with:
name: coverage-data
name: coverage-data-${{ steps.coverage-uuid.outputs.COVERAGE_UUID }}
path: |
.coverage.*
*.lcov

@ -26,13 +26,13 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.8

@ -26,7 +26,7 @@ jobs:
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"]') }}
python: ${{ (github.event_name == 'pull_request' && fromJSON('["3.8", "3.12"]')) || fromJSON('["3.8", "3.9", "3.10", "3.11", "3.12"]') }}
type: ["cli",
"dapp",
"data_dependency",
@ -53,9 +53,9 @@ jobs:
- os: windows-2022
type: truffle
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Install dependencies
@ -67,11 +67,11 @@ jobs:
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v22
uses: cachix/install-nix-action@v26
- name: Set up cachix
if: matrix.type == 'dapp'
uses: cachix/cachix-action@v12
uses: cachix/cachix-action@v14
with:
name: dapp

@ -17,20 +17,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
id: buildx
with:
install: true
- name: Set Docker metadata
id: metadata
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}
@ -40,14 +40,14 @@ jobs:
type=edge
- name: GitHub Container Registry Login
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Build and Push
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7
target: final

@ -28,19 +28,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Pages
uses: actions/configure-pages@v3
- uses: actions/setup-python@v4
uses: actions/configure-pages@v4
- uses: actions/setup-python@v5
with:
python-version: '3.8'
- run: pip install -e ".[doc]"
- run: pdoc -o html/ slither '!slither.tools' #TODO fix import errors on pdoc run
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@v3
with:
# Upload the doc
path: './html/'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
uses: actions/deploy-pages@v4

@ -23,16 +23,16 @@ 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
python: 3.8
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}

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

@ -9,8 +9,6 @@ defaults:
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
schedule:
# run CI every day even if no PRs/merges occur
@ -27,13 +25,13 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.8
@ -42,8 +40,12 @@ jobs:
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Register yamllint problem matcher
run: |
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
@ -55,8 +57,8 @@ jobs:
VALIDATE_PYTHON_PYLINT: false
VALIDATE_PYTHON_BLACK: false
VALIDATE_PYTHON_ISORT: false
# Always false
VALIDATE_JSON: false
VALIDATE_JAVASCRIPT_ES: false
VALIDATE_JAVASCRIPT_STANDARD: false
VALIDATE_PYTHON_FLAKE8: false
VALIDATE_DOCKERFILE: false
@ -64,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)

@ -0,0 +1,32 @@
{
"problemMatcher": [
{
"owner": "pylint-error",
"severity": "error",
"pattern": [
{
"regexp": "^(.+):(\\d+):(\\d+):\\s(([EF]\\d{4}):\\s.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"code": 5
}
]
},
{
"owner": "pylint-warning",
"severity": "warning",
"pattern": [
{
"regexp": "^(.+):(\\d+):(\\d+):\\s(([CRW]\\d{4}):\\s.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4,
"code": 5
}
]
}
]
}

@ -0,0 +1,22 @@
{
"problemMatcher": [
{
"owner": "yamllint",
"pattern": [
{
"regexp": "^(.*\\.ya?ml)$",
"file": 1
},
{
"regexp": "^\\s{2}(\\d+):(\\d+)\\s+(error|warning)\\s+(.*?)\\s+\\((.*)\\)$",
"line": 1,
"column": 2,
"severity": 3,
"message": 4,
"code": 5,
"loop": true
}
]
}
]
}

@ -18,10 +18,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.10"

@ -10,10 +10,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.x'
@ -23,7 +23,7 @@ jobs:
python -m pip install build
python -m build
- name: Upload distributions
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: slither-dists
path: dist/
@ -38,17 +38,16 @@ jobs:
- build-release
steps:
- name: fetch dists
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: slither-dists
path: dist/
- name: publish
uses: pypa/gh-action-pypi-publish@v1.8.7
uses: pypa/gh-action-pypi-publish@v1.8.14
- name: sign
uses: sigstore/gh-action-sigstore-python@v1.2.3
uses: sigstore/gh-action-sigstore-python@v2.1.1
with:
inputs: ./dist/*.tar.gz ./dist/*.whl
release-signing-artifacts: true
bundle-only: true

@ -9,6 +9,8 @@ defaults:
on:
pull_request:
branches: [master, dev]
paths:
- "**/*.py"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@ -21,13 +23,13 @@ jobs:
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
# Full git history is needed to get a proper list of changed files within `super-linter`
fetch-depth: 0
- name: Set up Python 3.8
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.8
@ -36,8 +38,12 @@ jobs:
mkdir -p .github/linters
cp pyproject.toml .github/linters
- name: Register pylint problem matcher
run: |
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,11 +25,11 @@ 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@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: "pip"
@ -40,7 +40,7 @@ jobs:
pip install ".[test]"
- name: Setup node
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: '16'
cache: 'npm'
@ -57,18 +57,34 @@ jobs:
npm install hardhat
popd || exit
fi
- name: Install Vyper
run: |
INSTALLDIR="$RUNNER_TEMP/vyper-install"
if [[ "$RUNNER_OS" = "Windows" ]]; then
URL="https://github.com/vyperlang/vyper/releases/download/v0.3.7/vyper.0.3.7+commit.6020b8bb.windows.exe"
FILENAME="vyper.exe"
elif [[ "$RUNNER_OS" = "Linux" ]]; then
URL="https://github.com/vyperlang/vyper/releases/download/v0.3.7/vyper.0.3.7+commit.6020b8bb.linux"
FILENAME="vyper"
else
echo "Unknown OS"
exit 1
fi
mkdir -p "$INSTALLDIR"
curl "$URL" -o "$INSTALLDIR/$FILENAME" -L
chmod 755 "$INSTALLDIR/$FILENAME"
echo "$INSTALLDIR" >> "$GITHUB_PATH"
- name: Run ${{ matrix.type }} tests
env:
TEST_TYPE: ${{ matrix.type }}
# 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
@ -84,23 +100,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python 3.8
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.8
- run: pip install coverage[toml]
- name: download coverage data
uses: actions/download-artifact@v3.0.2
uses: actions/download-artifact@v4
with:
name: coverage-data
pattern: coverage-data-*
merge-multiple: true
- name: combine coverage data
id: combinecoverage
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"

@ -0,0 +1,64 @@
cff-version: 1.2.0
title: Slither Analyzer
message: >-
If you use this software, please cite it using the
metadata from this file.
type: software
authors:
- given-names: Josselin
family-names: Feist
- given-names: Gustavo
family-names: Grieco
- given-names: Alex
family-names: Groce
identifiers:
- type: doi
value: 10.48550/arXiv.1908.09878
description: arXiv.1908.09878
- type: url
value: 'https://arxiv.org/abs/1908.09878'
description: arxiv
- type: doi
value: 10.1109/wetseb.2019.00008
repository-code: 'https://github.com/crytic/slither'
url: 'https://www.trailofbits.com/'
repository-artifact: 'https://github.com/crytic/slither/releases'
abstract: >-
Slither is a static analysis framework designed to provide
rich information about Ethereum smart contracts.
It works by converting Solidity smart contracts into an
intermediate representation called SlithIR.
SlithIR uses Static Single Assignment (SSA) form and a
reduced instruction set to ease implementation of analyses
while preserving semantic information that would be lost
in transforming Solidity to bytecode.
Slither allows for the application of commonly used
program analysis techniques like dataflow and taint
tracking.
Our framework has four main use cases:
(1) automated detection of vulnerabilities,
(2) automated detection of code optimization
opportunities,
(3) improvement of the user's understanding of the
contracts, and
(4) assistance with code review.
keywords:
- Ethereum
- Static Analysis
- Smart contracts
- EVM
- bug detection
- Software Engineering
license: AGPL-3.0-only
commit: 3d4f934d3228f072b7df2c5e7252c64df4601bc8
version: 0.9.5
date-released: '2023-06-28'

@ -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.
@ -96,8 +96,8 @@ For each new detector, at least one regression tests must be present.
#### Adding parsing tests
1. Create a test in `tests/e2e/solc_parsing/`
2. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git.
3. Update `ALL_TESTS` in `tests/e2e/solc_parsing/test_ast_parsing.py`.
2. Update `ALL_TESTS` in `tests/e2e/solc_parsing/test_ast_parsing.py`.
3. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git.
4. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git.
5. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked.

@ -47,6 +47,6 @@ ENV PATH="/home/slither/.local/bin:${PATH}"
RUN --mount=type=bind,target=/mnt,source=/wheels,from=python-wheels \
pip3 install --user --no-cache-dir --upgrade --no-index --find-links /mnt --no-deps /mnt/*.whl
RUN solc-select install 0.4.25 && solc-select use 0.4.25
RUN solc-select use latest --always-install
CMD /bin/bash

@ -1,42 +1,58 @@
# Slither, the Solidity source analyzer
# [Slither, the smart contract static analyzer](https://crytic.github.io/slither/slither.html)
<img src="https://raw.githubusercontent.com/crytic/slither/master/logo.png" alt="Logo" width="500"/>
<img src="https://raw.githubusercontent.com/crytic/slither/master/logo.png" alt="Slither Static Analysis Framework Logo" width="500" />
[![Build Status](https://img.shields.io/github/actions/workflow/status/crytic/slither/ci.yml?branch=master)](https://github.com/crytic/slither/actions?query=workflow%3ACI)
[![Slack Status](https://empireslacking.herokuapp.com/badge.svg)](https://empireslacking.herokuapp.com)
[![PyPI version](https://badge.fury.io/py/slither-analyzer.svg)](https://badge.fury.io/py/slither-analyzer)
Slither is a Solidity static analysis framework written in Python3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.
- [Features](#features)
- [Usage](#usage)
- [How to Install](#how-to-install)
- [Detectors](#detectors)
- [Printers](#printers)
- [Tools](#tools)
- [API Documentation](#api-documentation)
- [Getting Help](#getting-help)
- [FAQ](#faq)
- [Publications](#publications)
![PyPI](https://img.shields.io/pypi/v/slither-analyzer?logo=python&logoColor=white&label=slither-analyzer)
[![Slither - Read the Docs](https://img.shields.io/badge/Slither-Read_the_Docs-2ea44f)](https://crytic.github.io/slither/slither.html)
[![Slither - Wiki](https://img.shields.io/badge/Slither-Wiki-2ea44f)](https://github.com/crytic/slither/wiki/SlithIR)
> Join the Empire Hacking Slack
>
> [![Slack Status](https://slack.empirehacking.nyc/badge.svg)](https://slack.empirehacking.nyc/)
> > <sub><i>- Discussions and Support </i></sub>
**Slither** is a Solidity & Vyper static analysis framework written in Python3. It runs a suite of vulnerability detectors, prints visual information about contract details, and provides an API to easily write custom analyses. Slither enables developers to find vulnerabilities, enhance their code comprehension, and quickly prototype custom analyses.
* [Features](#features)
* [Usage](#usage)
* [How to install](#how-to-install)
* [Using Pip](#using-pip)
* [Using Git](#using-git)
* [Using Docker](#using-docker)
* [Integration](#integration)
* [Detectors](#detectors)
* [Printers](#printers)
* [Quick Review Printers](#quick-review-printers)
* [In-Depth Review Printers](#in-depth-review-printers)
* [Tools](#tools)
* [API Documentation](#api-documentation)
* [Getting Help](#getting-help)
* [FAQ](#faq)
* [License](#license)
* [Publications](#publications)
* [Trail of Bits publication](#trail-of-bits-publication)
* [External publications](#external-publications)
## Features
- Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md))
- Identifies where the error condition occurs in the source code
- Easily integrates into continuous integration and Hardhat/Foundry builds
- Built-in 'printers' quickly report crucial contract information
- Detector API to write custom analyses in Python
- Ability to analyze contracts written with Solidity >= 0.4
- Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses
- Correctly parses 99.9% of all public Solidity code
- Average execution time of less than 1 second per contract
- Integrates with Github's code scanning in [CI](https://github.com/marketplace/actions/slither-action)
* Detects vulnerable Solidity code with low false positives (see the list of [trophies](./trophies.md))
* Identifies where the error condition occurs in the source code
* Easily integrates into continuous integration and Hardhat/Foundry builds
* Built-in 'printers' quickly report crucial contract information
* Detector API to write custom analyses in Python
* Ability to analyze contracts written with Solidity >= 0.4
* Intermediate representation ([SlithIR](https://github.com/trailofbits/slither/wiki/SlithIR)) enables simple, high-precision analyses
* Correctly parses 99.9% of all public Solidity code
* Average execution time of less than 1 second per contract
* Integrates with Github's code scanning in [CI](https://github.com/marketplace/actions/slither-action)
* Support for Vyper smart contracts
## Usage
Run Slither on a Hardhat/Foundry/Dapp/Brownie application:
```bash
```console
slither .
```
@ -44,26 +60,27 @@ This is the preferred option if your project has dependencies as Slither relies
However, you can run Slither on a single file that does not import dependencies:
```bash
```console
slither tests/uninitialized.sol
```
## How to install
Slither requires Python 3.8+.
> **Note** <br />
> Slither requires Python 3.8+.
If you're **not** going to use one of the [supported compilation frameworks](https://github.com/crytic/crytic-compile), you need [solc](https://github.com/ethereum/solidity/), the Solidity compiler; we recommend using [solc-select](https://github.com/crytic/solc-select) to conveniently switch between solc versions.
### Using Pip
```bash
pip3 install slither-analyzer
```console
python3 -m pip install slither-analyzer
```
### Using Git
```bash
git clone https://github.com/crytic/slither.git && cd slither
python3 setup.py install
python3 -m pip install .
```
We recommend using a Python virtual environment, as detailed in the [Developer Installation Instructions](https://github.com/trailofbits/slither/wiki/Developer-installation), if you prefer to install Slither via git.
@ -84,9 +101,9 @@ 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).
- 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`)
* For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action).
* To generate a Markdown report, use `slither [target] --checklist`.
* To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`)
## Detectors
@ -113,92 +130,100 @@ Num | Detector | What it Detects | Impact | Confidence
19 | `controlled-array-length` | [Tainted array length assignment](https://github.com/crytic/slither/wiki/Detector-Documentation#array-length-assignment) | High | Medium
20 | `controlled-delegatecall` | [Controlled delegatecall destination](https://github.com/crytic/slither/wiki/Detector-Documentation#controlled-delegatecall) | High | Medium
21 | `delegatecall-loop` | [Payable functions using `delegatecall` inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#payable-functions-using-delegatecall-inside-a-loop) | High | Medium
22 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
23 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
24 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
25 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
26 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
27 | `domain-separator-collision` | [Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()](https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision) | Medium | High
28 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
29 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
30 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
31 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
32 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
33 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
34 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
35 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
36 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
37 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
38 | `constant-function-asm` | [Constant functions using assembly code](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-using-assembly-code) | Medium | Medium
39 | `constant-function-state` | [Constant functions changing the state](https://github.com/crytic/slither/wiki/Detector-Documentation#constant-functions-changing-the-state) | Medium | Medium
40 | `divide-before-multiply` | [Imprecise arithmetic operations order](https://github.com/crytic/slither/wiki/Detector-Documentation#divide-before-multiply) | Medium | Medium
41 | `reentrancy-no-eth` | [Reentrancy vulnerabilities (no theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-1) | Medium | Medium
42 | `reused-constructor` | [Reused base constructor](https://github.com/crytic/slither/wiki/Detector-Documentation#reused-base-constructors) | Medium | Medium
43 | `tx-origin` | [Dangerous usage of `tx.origin`](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-usage-of-txorigin) | Medium | Medium
44 | `unchecked-lowlevel` | [Unchecked low-level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-low-level-calls) | Medium | Medium
45 | `unchecked-send` | [Unchecked send](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-send) | Medium | Medium
46 | `uninitialized-local` | [Uninitialized local variables](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-local-variables) | Medium | Medium
47 | `unused-return` | [Unused return values](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-return) | Medium | Medium
48 | `incorrect-modifier` | [Modifiers that can return the default value](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-modifier) | Low | High
49 | `shadowing-builtin` | [Built-in symbol shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#builtin-symbol-shadowing) | Low | High
50 | `shadowing-local` | [Local variables shadowing](https://github.com/crytic/slither/wiki/Detector-Documentation#local-variable-shadowing) | Low | High
51 | `uninitialized-fptr-cst` | [Uninitialized function pointer calls in constructors](https://github.com/crytic/slither/wiki/Detector-Documentation#uninitialized-function-pointers-in-constructors) | Low | High
52 | `variable-scope` | [Local variables used prior their declaration](https://github.com/crytic/slither/wiki/Detector-Documentation#pre-declaration-usage-of-local-variables) | Low | High
53 | `void-cst` | [Constructor called not implemented](https://github.com/crytic/slither/wiki/Detector-Documentation#void-constructor) | Low | High
54 | `calls-loop` | [Multiple calls in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#calls-inside-a-loop) | Low | Medium
55 | `events-access` | [Missing Events Access Control](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-access-control) | Low | Medium
56 | `events-maths` | [Missing Events Arithmetic](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-events-arithmetic) | Low | Medium
57 | `incorrect-unary` | [Dangerous unary expressions](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-unary-expressions) | Low | Medium
58 | `missing-zero-check` | [Missing Zero Address Validation](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-zero-address-validation) | Low | Medium
59 | `reentrancy-benign` | [Benign reentrancy vulnerabilities](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-2) | Low | Medium
60 | `reentrancy-events` | [Reentrancy vulnerabilities leading to out-of-order Events](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-3) | Low | Medium
61 | `timestamp` | [Dangerous usage of `block.timestamp`](https://github.com/crytic/slither/wiki/Detector-Documentation#block-timestamp) | Low | Medium
62 | `assembly` | [Assembly usage](https://github.com/crytic/slither/wiki/Detector-Documentation#assembly-usage) | Informational | High
63 | `assert-state-change` | [Assert state change](https://github.com/crytic/slither/wiki/Detector-Documentation#assert-state-change) | Informational | High
64 | `boolean-equal` | [Comparison to boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#boolean-equality) | Informational | High
65 | `cyclomatic-complexity` | [Detects functions with high (> 11) cyclomatic complexity](https://github.com/crytic/slither/wiki/Detector-Documentation#cyclomatic-complexity) | Informational | High
66 | `deprecated-standards` | [Deprecated Solidity Standards](https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards) | Informational | High
67 | `erc20-indexed` | [Un-indexed ERC20 event parameters](https://github.com/crytic/slither/wiki/Detector-Documentation#unindexed-erc20-event-parameters) | Informational | High
68 | `function-init-state` | [Function initializing state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#function-initializing-state) | Informational | High
69 | `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
70 | `low-level-calls` | [Low level calls](https://github.com/crytic/slither/wiki/Detector-Documentation#low-level-calls) | Informational | High
71 | `missing-inheritance` | [Missing inheritance](https://github.com/crytic/slither/wiki/Detector-Documentation#missing-inheritance) | Informational | High
72 | `naming-convention` | [Conformity to Solidity naming conventions](https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions) | Informational | High
73 | `pragma` | [If different pragma directives are used](https://github.com/crytic/slither/wiki/Detector-Documentation#different-pragma-directives-are-used) | Informational | High
74 | `redundant-statements` | [Redundant statements](https://github.com/crytic/slither/wiki/Detector-Documentation#redundant-statements) | Informational | High
75 | `solc-version` | [Incorrect Solidity version](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity) | Informational | High
76 | `unimplemented-functions` | [Unimplemented functions](https://github.com/crytic/slither/wiki/Detector-Documentation#unimplemented-functions) | Informational | High
77 | `unused-state` | [Unused state variables](https://github.com/crytic/slither/wiki/Detector-Documentation#unused-state-variable) | Informational | High
78 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
79 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
80 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
81 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
82 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
83 | `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
84 | `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
85 | `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
86 | `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
87 | `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
22 | `incorrect-exp` | [Incorrect exponentiation](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-exponentiation) | High | Medium
23 | `incorrect-return` | [If a `return` is incorrectly used in assembly mode.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-return-in-assembly) | High | Medium
24 | `msg-value-loop` | [msg.value inside a loop](https://github.com/crytic/slither/wiki/Detector-Documentation/#msgvalue-inside-a-loop) | High | Medium
25 | `reentrancy-eth` | [Reentrancy vulnerabilities (theft of ethers)](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities) | High | Medium
26 | `return-leave` | [If a `return` is used instead of a `leave`.](https://github.com/crytic/slither/wiki/Detector-Documentation#return-instead-of-leave-in-assembly) | High | Medium
27 | `storage-array` | [Signed storage integer array compiler bug](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-signed-integer-array) | High | Medium
28 | `unchecked-transfer` | [Unchecked tokens transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer) | High | Medium
29 | `weak-prng` | [Weak PRNG](https://github.com/crytic/slither/wiki/Detector-Documentation#weak-PRNG) | High | Medium
30 | `domain-separator-collision` | [Detects ERC20 tokens that have a function whose signature collides with EIP-2612's DOMAIN_SEPARATOR()](https://github.com/crytic/slither/wiki/Detector-Documentation#domain-separator-collision) | Medium | High
31 | `enum-conversion` | [Detect dangerous enum conversion](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-enum-conversion) | Medium | High
32 | `erc20-interface` | [Incorrect ERC20 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc20-interface) | Medium | High
33 | `erc721-interface` | [Incorrect ERC721 interfaces](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-erc721-interface) | Medium | High
34 | `incorrect-equality` | [Dangerous strict equalities](https://github.com/crytic/slither/wiki/Detector-Documentation#dangerous-strict-equalities) | Medium | High
35 | `locked-ether` | [Contracts that lock ether](https://github.com/crytic/slither/wiki/Detector-Documentation#contracts-that-lock-ether) | Medium | High
36 | `mapping-deletion` | [Deletion on mapping containing a structure](https://github.com/crytic/slither/wiki/Detector-Documentation#deletion-on-mapping-containing-a-structure) | Medium | High
37 | `shadowing-abstract` | [State variables shadowing from abstract contracts](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variable-shadowing-from-abstract-contracts) | Medium | High
38 | `tautological-compare` | [Comparing a variable to itself always returns true or false, depending on comparison](https://github.com/crytic/slither/wiki/Detector-Documentation#tautological-compare) | Medium | High
39 | `tautology` | [Tautology or contradiction](https://github.com/crytic/slither/wiki/Detector-Documentation#tautology-or-contradiction) | Medium | High
40 | `write-after-write` | [Unused write](https://github.com/crytic/slither/wiki/Detector-Documentation#write-after-write) | Medium | High
41 | `boolean-cst` | [Misuse of Boolean constant](https://github.com/crytic/slither/wiki/Detector-Documentation#misuse-of-a-boolean-constant) | Medium | Medium
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
For more information, see
- The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
- The [Detection Selection](https://github.com/crytic/slither/wiki/Usage#detector-selection) to run only selected detectors. By default, all the detectors are run.
- The [Triage Mode](https://github.com/crytic/slither/wiki/Usage#triage-mode) to filter individual results
* The [Detector Documentation](https://github.com/crytic/slither/wiki/Detector-Documentation) for details on each detector
* The [Detection Selection](https://github.com/crytic/slither/wiki/Usage#detector-selection) to run only selected detectors. By default, all the detectors are run.
* The [Triage Mode](https://github.com/crytic/slither/wiki/Usage#triage-mode) to filter individual results
## Printers
### Quick Review Printers
- `human-summary`: [Print a human-readable summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary)
- `inheritance-graph`: [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph)
- `contract-summary`: [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary)
- `loc`: [Count the total number lines of code (LOC), source lines of code (SLOC), and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), and test files (TEST).](https://github.com/trailofbits/slither/wiki/Printer-documentation#loc)
* `human-summary`: [Print a human-readable summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#human-summary)
* `inheritance-graph`: [Export the inheritance graph of each contract to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#inheritance-graph)
* `contract-summary`: [Print a summary of the contracts](https://github.com/trailofbits/slither/wiki/Printer-documentation#contract-summary)
* `loc`: [Count the total number lines of code (LOC), source lines of code (SLOC), and comment lines of code (CLOC) found in source files (SRC), dependencies (DEP), and test files (TEST).](https://github.com/trailofbits/slither/wiki/Printer-documentation#loc)
### In-Depth Review Printers
- `call-graph`: [Export the call-graph of the contracts to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph)
- `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
- `function-summary`: [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary)
- `vars-and-auth`: [Print the state variables written and the authorization of the functions](https://github.com/crytic/slither/wiki/Printer-documentation#variables-written-and-authorization)
- `not-pausable`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
* `call-graph`: [Export the call-graph of the contracts to a dot file](https://github.com/trailofbits/slither/wiki/Printer-documentation#call-graph)
* `cfg`: [Export the CFG of each functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#cfg)
* `function-summary`: [Print a summary of the functions](https://github.com/trailofbits/slither/wiki/Printer-documentation#function-summary)
* `vars-and-auth`: [Print the state variables written and the authorization of the functions](https://github.com/crytic/slither/wiki/Printer-documentation#variables-written-and-authorization)
* `not-pausable`: [Print functions that do not use `whenNotPaused` modifier](https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused).
To run a printer, use `--print` and a comma-separated list of printers.
@ -206,13 +231,13 @@ See the [Printer documentation](https://github.com/crytic/slither/wiki/Printer-d
## Tools
- `slither-check-upgradeability`: [Review `delegatecall`-based upgradeability](https://github.com/crytic/slither/wiki/Upgradeability-Checks)
- `slither-prop`: [Automatic unit test and property generation](https://github.com/crytic/slither/wiki/Property-generation)
- `slither-flat`: [Flatten a codebase](https://github.com/crytic/slither/wiki/Contract-Flattening)
- `slither-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance)
- `slither-format`: [Automatic patch generation](https://github.com/crytic/slither/wiki/Slither-format)
- `slither-read-storage`: [Read storage values from contracts](./slither/tools/read_storage/README.md)
- `slither-interface`: [Generate an interface for a contract](./slither/tools/interface/README.md)
* `slither-check-upgradeability`: [Review `delegatecall`-based upgradeability](https://github.com/crytic/slither/wiki/Upgradeability-Checks)
* `slither-prop`: [Automatic unit test and property generation](https://github.com/crytic/slither/wiki/Property-generation)
* `slither-flat`: [Flatten a codebase](https://github.com/crytic/slither/wiki/Contract-Flattening)
* `slither-check-erc`: [Check the ERC's conformance](https://github.com/crytic/slither/wiki/ERC-Conformance)
* `slither-format`: [Automatic patch generation](https://github.com/crytic/slither/wiki/Slither-format)
* `slither-read-storage`: [Read storage values from contracts](./slither/tools/read_storage/README.md)
* `slither-interface`: [Generate an interface for a contract](./slither/tools/interface/README.md)
See the [Tool documentation](https://github.com/crytic/slither/wiki/Tool-Documentation) for additional tools.
@ -226,23 +251,23 @@ Documentation on Slither's internals is available [here](https://crytic.github.i
Feel free to stop by our [Slack channel](https://empireslacking.herokuapp.com) (#ethereum) for help using or extending Slither.
- The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract.
* The [Printer documentation](https://github.com/trailofbits/slither/wiki/Printer-documentation) describes the information Slither is capable of visualizing for each contract.
- The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses.
* The [Detector documentation](https://github.com/trailofbits/slither/wiki/Adding-a-new-detector) describes how to write a new vulnerability analyses.
- The [API documentation](https://github.com/crytic/slither/wiki/Python-API) describes the methods and objects available for custom analyses.
* The [API documentation](https://github.com/crytic/slither/wiki/Python-API) describes the methods and objects available for custom analyses.
- The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
* The [SlithIR documentation](https://github.com/trailofbits/slither/wiki/SlithIR) describes the SlithIR intermediate representation.
## FAQ
How do I exclude mocks or tests?
- View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
* View our documentation on [path filtering](https://github.com/crytic/slither/wiki/Usage#path-filtering).
How do I fix "unknown file" or compilation issues?
- Because slither requires the solc AST, it must have all dependencies available.
* Because slither requires the solc AST, it must have all dependencies available.
If a contract has dependencies, `slither contract.sol` will fail.
Instead, use `slither .` in the parent directory of `contracts/` (you should see `contracts/` when you run `ls`).
If you have a `node_modules/` folder, it must be in the same directory as `contracts/`. To verify that this issue is related to slither,
@ -257,22 +282,22 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
### Trail of Bits publication
- [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
* [Slither: A Static Analysis Framework For Smart Contracts](https://arxiv.org/abs/1908.09878), Josselin Feist, Gustavo Grieco, Alex Groce - WETSEB '19
### External publications
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)

@ -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);
_;
}
}

@ -19,7 +19,8 @@ def resolve_function(contract_name, function_name):
contract = contracts[0]
# Obtain the target function
target_function = next(
(function for function in contract.functions if function.name == function_name), None
(function for function in contract.functions_declared if function.name == function_name),
None,
)
# Verify we have resolved the function specified.

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

@ -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,human-summary,inheritance,inheritance-graph,loc,slithir,slithir-ssa,vars-and-auth,require,variable-order,declaration"
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"
# Only test 0.5.17 to limit test time
for file in *0.5.17-compact.zip; do

@ -5,18 +5,18 @@ with open("README.md", "r", encoding="utf-8") as f:
setup(
name="slither-analyzer",
description="Slither is a Solidity static analysis framework written in Python 3.",
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.9.3",
version="0.10.0",
packages=find_packages(),
python_requires=">=3.8",
install_requires=[
"packaging",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
# "crytic-compile>=0.3.1,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile",
# "crytic-compile>=0.3.5,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
@ -25,7 +25,7 @@ setup(
extras_require={
"lint": [
"black==22.3.0",
"pylint==2.13.4",
"pylint==3.0.3",
],
"test": [
"pytest",
@ -36,7 +36,6 @@ setup(
"coverage[toml]",
"filelock",
"pytest-insta",
"solc-select@git+https://github.com/crytic/solc-select.git@query-artifact-path#egg=solc-select",
],
"doc": [
"pdoc",

@ -10,9 +10,9 @@ import os
import pstats
import sys
import traceback
from importlib import metadata
from typing import Tuple, Optional, List, Dict, Type, Union, Any, Sequence
from pkg_resources import iter_entry_points, require
from crytic_compile import cryticparser, CryticCompile
from crytic_compile.platform.standard import generate_standard_export
@ -79,6 +79,11 @@ def process_single(
ast = "--ast-json"
slither = Slither(target, ast_format=ast, **vars(args))
if args.sarif_input:
slither.sarif_input = args.sarif_input
if args.sarif_triage:
slither.sarif_triage = args.sarif_triage
return _process(slither, detector_classes, printer_classes)
@ -161,19 +166,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)
@ -203,7 +215,7 @@ def choose_detectors(
if detector in detectors:
detectors_to_run.append(detectors[detector])
else:
raise Exception(f"Error: {detector} is not a detector")
raise ValueError(f"Error: {detector} is not a detector")
detectors_to_run = sorted(detectors_to_run, key=lambda x: x.IMPACT)
return detectors_to_run
@ -251,7 +263,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
@ -263,9 +275,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 []
@ -292,7 +305,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",
)
@ -302,6 +315,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",
@ -442,7 +456,7 @@ def parse_args(
group_checklist.add_argument(
"--checklist-limit",
help="Limite the number of results per detector in the markdown file",
help="Limit the number of results per detector in the markdown file",
action="store",
default="",
)
@ -469,6 +483,20 @@ def parse_args(
default=defaults_flag_in_config["sarif"],
)
group_misc.add_argument(
"--sarif-input",
help="Sarif input (beta)",
action="store",
default=defaults_flag_in_config["sarif_input"],
)
group_misc.add_argument(
"--sarif-triage",
help="Sarif triage (beta)",
action="store",
default=defaults_flag_in_config["sarif_triage"],
)
group_misc.add_argument(
"--json-types",
help="Comma-separated list of result types to output to JSON, defaults to "
@ -499,22 +527,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)",
@ -552,6 +580,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
@ -604,13 +648,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
@ -870,12 +915,6 @@ def main_impl(
logging.error(red(output_error))
logging.error("Please report an issue to https://github.com/crytic/slither/issues")
except Exception: # pylint: disable=broad-except
output_error = traceback.format_exc()
traceback.print_exc()
logging.error(f"Error in {args.filename}") # pylint: disable=logging-fstring-interpolation
logging.error(output_error)
# If we are outputting JSON, capture the redirected output and disable the redirect to output the final JSON.
if outputting_json:
if "console" in args.json_types:

@ -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
@ -74,6 +75,7 @@ class NodeType(Enum):
IF = "IF"
VARIABLE = "NEW VARIABLE" # Variable declaration
ASSEMBLY = "INLINE ASM"
ENDASSEMBLY = "END INLINE ASM"
IFLOOP = "IF_LOOP"
# Nodes where control flow merges
@ -193,6 +195,8 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
self.file_scope: "FileScope" = file_scope
self._function: Optional["Function"] = None
self._is_reachable: bool = False
###################################################################################
###################################################################################
# region General's properties
@ -234,6 +238,13 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
def function(self) -> "Function":
return self._function
@property
def is_reachable(self) -> bool:
return self._is_reachable
def set_is_reachable(self, new_is_reachable: bool) -> None:
self._is_reachable = new_is_reachable
# endregion
###################################################################################
###################################################################################
@ -888,6 +899,21 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
# TODO: consider removing dependancy of solidity_call to internal_call
self._solidity_calls.append(ir.function)
self._internal_calls.append(ir.function)
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)))

@ -1,4 +1,5 @@
import math
from enum import Enum
from typing import Optional, Dict, List, Set, Union, TYPE_CHECKING, Tuple
from crytic_compile import CompilationUnit, CryticCompile
@ -15,6 +16,7 @@ from slither.core.declarations import (
)
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
@ -29,6 +31,20 @@ if TYPE_CHECKING:
from slither.core.slither_core import SlitherCore
class Language(Enum):
SOLIDITY = "solidity"
VYPER = "vyper"
@staticmethod
def from_str(label: str):
if label == "solc":
return Language.SOLIDITY
if label == "vyper":
return Language.VYPER
raise ValueError(f"Unknown language: {label}")
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class SlitherCompilationUnit(Context):
def __init__(self, core: "SlitherCore", crytic_compilation_unit: CompilationUnit) -> None:
@ -36,18 +52,20 @@ class SlitherCompilationUnit(Context):
self._core = core
self._crytic_compile_compilation_unit = crytic_compilation_unit
self._language = Language.from_str(crytic_compilation_unit.compiler_version.compiler)
# Top level object
self.contracts: List[Contract] = []
self._structures_top_level: List[StructureTopLevel] = []
self._enums_top_level: List[EnumTopLevel] = []
self._events_top_level: List[EventTopLevel] = []
self._variables_top_level: List[TopLevelVariable] = []
self._functions_top_level: List[FunctionTopLevel] = []
self._using_for_top_level: List[UsingForTopLevel] = []
self._pragma_directives: List[Pragma] = []
self._import_directives: List[Import] = []
self._custom_errors: List[CustomErrorTopLevel] = []
self._user_defined_value_types: Dict[str, TypeAliasTopLevel] = {}
self._type_aliases: Dict[str, TypeAliasTopLevel] = {}
self._all_functions: Set[Function] = set()
self._all_modifiers: Set[Modifier] = set()
@ -81,6 +99,17 @@ class SlitherCompilationUnit(Context):
# region Compiler
###################################################################################
###################################################################################
@property
def language(self) -> Language:
return self._language
@property
def is_vyper(self) -> bool:
return self._language == Language.VYPER
@property
def is_solidity(self) -> bool:
return self._language == Language.SOLIDITY
@property
def compiler_version(self) -> CompilerVersion:
@ -166,6 +195,10 @@ class SlitherCompilationUnit(Context):
return self.functions + list(self.modifiers)
def propagate_function_calls(self) -> None:
"""This info is used to compute the rvalues of Phi operations in `fix_phi` and ultimately
is responsible for the `read` property of Phi operations which is vital to
propagating taints inter-procedurally
"""
for f in self.functions_and_modifiers:
for node in f.nodes:
for ir in node.irs_ssa:
@ -203,6 +236,10 @@ class SlitherCompilationUnit(Context):
def enums_top_level(self) -> List[EnumTopLevel]:
return self._enums_top_level
@property
def events_top_level(self) -> List[EventTopLevel]:
return self._events_top_level
@property
def variables_top_level(self) -> List[TopLevelVariable]:
return self._variables_top_level
@ -220,8 +257,8 @@ class SlitherCompilationUnit(Context):
return self._custom_errors
@property
def user_defined_value_types(self) -> Dict[str, TypeAliasTopLevel]:
return self._user_defined_value_types
def type_aliases(self) -> Dict[str, TypeAliasTopLevel]:
return self._type_aliases
# endregion
###################################################################################
@ -259,15 +296,13 @@ 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
for var in contract.stored_state_variables_ordered:
assert var.type
size, new_slot = var.type.storage_size

@ -1,6 +1,8 @@
from .contract import Contract
from .enum import Enum
from .event import Event
from .event_contract import EventContract
from .event_top_level import EventTopLevel
from .function import Function
from .import_directive import Import
from .modifier import Modifier
@ -18,3 +20,6 @@ from .structure_top_level import StructureTopLevel
from .function_contract import FunctionContract
from .function_top_level import FunctionTopLevel
from .custom_error_contract import CustomErrorContract
from .custom_error_top_level import CustomErrorTopLevel
from .custom_error import CustomError
from .solidity_import_placeholder import SolidityImportPlaceHolder

@ -33,7 +33,7 @@ if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import (
Enum,
Event,
EventContract,
Modifier,
EnumContract,
StructureContract,
@ -45,6 +45,7 @@ if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.scope.scope import FileScope
from slither.core.cfg.node import Node
from slither.core.solidity_types import TypeAliasContract
LOGGER = logging.getLogger("Contract")
@ -72,15 +73,18 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._enums: Dict[str, "EnumContract"] = {}
self._structures: Dict[str, "StructureContract"] = {}
self._events: Dict[str, "Event"] = {}
self._events: Dict[str, "EventContract"] = {}
# map accessible variable from name -> variable
# do not contain private variables inherited from contract
self._variables: Dict[str, "StateVariable"] = {}
self._variables_ordered: List["StateVariable"] = []
# Reference id -> variable declaration (only available for compact AST)
self._state_variables_by_ref_id: Dict[int, "StateVariable"] = {}
self._modifiers: Dict[str, "Modifier"] = {}
self._functions: Dict[str, "FunctionContract"] = {}
self._linearizedBaseContracts: List[int] = []
self._custom_errors: Dict[str, "CustomErrorContract"] = {}
self._type_aliases: Dict[str, "TypeAliasContract"] = {}
# The only str is "*"
self._using_for: Dict[USING_FOR_KEY, USING_FOR_ITEM] = {}
@ -89,6 +93,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
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
@ -136,7 +141,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def id(self) -> int:
"""Unique id."""
assert self._id
assert self._id is not None
return self._id
@id.setter
@ -199,12 +204,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
###################################################################################
###################################################################################
@ -274,28 +301,28 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def events(self) -> List["Event"]:
def events(self) -> List["EventContract"]:
"""
list(Event): List of the events
"""
return list(self._events.values())
@property
def events_inherited(self) -> List["Event"]:
def events_inherited(self) -> List["EventContract"]:
"""
list(Event): List of the inherited events
"""
return [e for e in self.events if e.contract != self]
@property
def events_declared(self) -> List["Event"]:
def events_declared(self) -> List["EventContract"]:
"""
list(Event): List of the events declared within the contract (not inherited)
"""
return [e for e in self.events if e.contract == self]
@property
def events_as_dict(self) -> Dict[str, "Event"]:
def events_as_dict(self) -> Dict[str, "EventContract"]:
return self._events
# endregion
@ -364,12 +391,50 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
def custom_errors_as_dict(self) -> Dict[str, "CustomErrorContract"]:
return self._custom_errors
# endregion
###################################################################################
###################################################################################
# region Custom Errors
###################################################################################
###################################################################################
@property
def type_aliases(self) -> List["TypeAliasContract"]:
"""
list(TypeAliasContract): List of the contract's custom errors
"""
return list(self._type_aliases.values())
@property
def type_aliases_inherited(self) -> List["TypeAliasContract"]:
"""
list(TypeAliasContract): List of the inherited custom errors
"""
return [s for s in self.type_aliases if s.contract != self]
@property
def type_aliases_declared(self) -> List["TypeAliasContract"]:
"""
list(TypeAliasContract): List of the custom errors declared within the contract (not inherited)
"""
return [s for s in self.type_aliases if s.contract == self]
@property
def type_aliases_as_dict(self) -> Dict[str, "TypeAliasContract"]:
return self._type_aliases
# endregion
###################################################################################
###################################################################################
# region Variables
###################################################################################
###################################################################################
@property
def state_variables_by_ref_id(self) -> Dict[int, "StateVariable"]:
"""
Returns the state variables by reference id (only available for compact AST).
"""
return self._state_variables_by_ref_id
@property
def variables(self) -> List["StateVariable"]:
@ -394,6 +459,33 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
return list(self._variables.values())
@property
def stored_state_variables(self) -> List["StateVariable"]:
"""
Returns state variables with storage locations, excluding private variables from inherited contracts.
Use stored_state_variables_ordered to access variables with storage locations in their declaration order.
This implementation filters out state variables if they are constant or immutable. It will be
updated to accommodate any new non-storage keywords that might replace 'constant' and 'immutable' in the future.
Returns:
List[StateVariable]: A list of state variables with storage locations.
"""
return [variable for variable in self.state_variables if variable.is_stored]
@property
def stored_state_variables_ordered(self) -> List["StateVariable"]:
"""
list(StateVariable): List of the state variables with storage locations by order of declaration.
This implementation filters out state variables if they are constant or immutable. It will be
updated to accommodate any new non-storage keywords that might replace 'constant' and 'immutable' in the future.
Returns:
List[StateVariable]: A list of state variables with storage locations ordered by declaration.
"""
return [variable for variable in self.state_variables_ordered if variable.is_stored]
@property
def state_variables_entry_points(self) -> List["StateVariable"]:
"""
@ -861,7 +953,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
Returns:
StateVariable
"""
return next((v for v in self.state_variables if v.name == canonical_name), None)
return next((v for v in self.state_variables if v.canonical_name == canonical_name), None)
def get_structure_from_name(self, structure_name: str) -> Optional["StructureContract"]:
"""
@ -927,16 +1019,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
###################################################################################

@ -16,3 +16,7 @@ class CustomErrorContract(CustomError, ContractLevel):
:return:
"""
return self.contract == contract
@property
def canonical_name(self) -> str:
return self.contract.name + "." + self.full_name

@ -12,3 +12,7 @@ class CustomErrorTopLevel(CustomError, TopLevel):
def __init__(self, compilation_unit: "SlitherCompilationUnit", scope: "FileScope") -> None:
super().__init__(compilation_unit)
self.file_scope: "FileScope" = scope
@property
def canonical_name(self) -> str:
return self.full_name

@ -1,14 +1,10 @@
from typing import List, Tuple, TYPE_CHECKING
from typing import List, Tuple
from slither.core.declarations.contract_level import ContractLevel
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.event_variable import EventVariable
if TYPE_CHECKING:
from slither.core.declarations import Contract
class Event(ContractLevel, SourceMapping):
class Event(SourceMapping):
def __init__(self) -> None:
super().__init__()
self._name = None
@ -39,25 +35,9 @@ class Event(ContractLevel, SourceMapping):
name, parameters = self.signature
return name + "(" + ",".join(parameters) + ")"
@property
def canonical_name(self) -> str:
"""Return the function signature as a str
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + "." + self.full_name
@property
def elems(self) -> List["EventVariable"]:
return self._elems
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract
def __str__(self) -> str:
return self.name

@ -0,0 +1,25 @@
from typing import TYPE_CHECKING
from slither.core.declarations.contract_level import ContractLevel
from slither.core.declarations import Event
if TYPE_CHECKING:
from slither.core.declarations import Contract
class EventContract(Event, ContractLevel):
def is_declared_by(self, contract: "Contract") -> bool:
"""
Check if the element is declared by the contract
:param contract:
:return:
"""
return self.contract == contract
@property
def canonical_name(self) -> str:
"""Return the function signature as a str
Returns:
str: contract.func_name(type1,type2)
"""
return self.contract.name + "." + self.full_name

@ -0,0 +1,13 @@
from typing import TYPE_CHECKING
from slither.core.declarations import Event
from slither.core.declarations.top_level import TopLevel
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
class EventTopLevel(Event, TopLevel):
def __init__(self, scope: "FileScope") -> None:
super().__init__()
self.file_scope: "FileScope" = scope

@ -37,7 +37,7 @@ if TYPE_CHECKING:
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
@ -46,7 +46,6 @@ if TYPE_CHECKING:
from slither.slithir.operations import 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"])
@ -126,6 +125,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
@ -137,6 +139,8 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self._parameters: List["LocalVariable"] = []
self._parameters_ssa: List["LocalIRVariable"] = []
self._parameters_src: SourceMapping = SourceMapping()
# This is used for vyper calls with default arguments
self._default_args_as_expressions: List["Expression"] = []
self._returns: List["LocalVariable"] = []
self._returns_ssa: List["LocalIRVariable"] = []
self._returns_src: SourceMapping = SourceMapping()
@ -217,8 +221,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
self.compilation_unit: "SlitherCompilationUnit" = compilation_unit
# Assume we are analyzing Solidity by default
self.function_language: FunctionLanguage = FunctionLanguage.Solidity
self.function_language: FunctionLanguage = (
FunctionLanguage.Solidity if compilation_unit.is_solidity else FunctionLanguage.Vyper
)
self._id: Optional[str] = None
@ -238,7 +243,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
if self._name == "" and self._function_type == FunctionType.CONSTRUCTOR:
return "constructor"
if self._function_type == FunctionType.FALLBACK:
if self._name == "" and self._function_type == FunctionType.FALLBACK:
return "fallback"
if self._function_type == FunctionType.RECEIVE:
return "receive"
@ -438,6 +443,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
###################################################################################
###################################################################################
@ -985,14 +1033,15 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
(str, list(str), list(str)): Function signature as
(name, list parameters type, list return values type)
"""
if self._signature is None:
signature = (
self.name,
[str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns],
)
self._signature = signature
return self._signature
# FIXME memoizing this function is not working properly for vyper
# if self._signature is None:
return (
self.name,
[str(x.type) for x in self.parameters],
[str(x.type) for x in self.returns],
)
# self._signature = signature
# return self._signature
@property
def signature_str(self) -> str:
@ -1496,8 +1545,13 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
"""
Determine if the function can be re-entered
"""
reentrancy_modifier = "nonReentrant"
if self.function_language == FunctionLanguage.Vyper:
reentrancy_modifier = "nonreentrant(lock)"
# TODO: compare with hash of known nonReentrant modifier instead of the name
if "nonReentrant" in [m.name for m in self.modifiers]:
if reentrancy_modifier in [m.name for m in self.modifiers]:
return False
if self.visibility in ["public", "external"]:
@ -1509,7 +1563,9 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
]
if not all_entry_points:
return True
return not all(("nonReentrant" in [m.name for m in f.modifiers] for f in all_entry_points))
return not all(
(reentrancy_modifier in [m.name for m in f.modifiers] for f in all_entry_points)
)
# endregion
###################################################################################
@ -1756,6 +1812,7 @@ class Function(SourceMapping, metaclass=ABCMeta): # pylint: disable=too-many-pu
node.irs_ssa = [ir for ir in node.irs_ssa if not self._unchange_phi(ir)]
def generate_slithir_and_analyze(self) -> None:
for node in self.nodes:
node.slithir_generation()

@ -10,15 +10,19 @@ from slither.exceptions import SlitherException
SOLIDITY_VARIABLES = {
"now": "uint256",
"this": "address",
"self": "address",
"abi": "address", # to simplify the conversion, assume that abi return an address
"msg": "",
"tx": "",
"block": "",
"super": "",
"chain": "",
"ZERO_ADDRESS": "address",
}
SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint",
"block.basefee": "uint256",
"block.blobbasefee": "uint256",
"block.coinbase": "address",
"block.difficulty": "uint256",
"block.prevrandao": "uint256",
@ -34,9 +38,14 @@ SOLIDITY_VARIABLES_COMPOSED = {
"msg.value": "uint256",
"tx.gasprice": "uint256",
"tx.origin": "address",
# Vyper
"chain.id": "uint256",
"block.prevhash": "bytes32",
"self.balance": "uint256",
}
SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"blobhash(uint256)": ["bytes32"],
"gasleft()": ["uint256"],
"assert(bool)": [],
"require(bool)": [],
@ -81,6 +90,33 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"balance(address)": ["uint256"],
"code(address)": ["bytes"],
"codehash(address)": ["bytes32"],
# Vyper
"create_from_blueprint()": [],
"create_minimal_proxy_to()": [],
"empty()": [],
"convert()": [],
"len()": ["uint256"],
"method_id()": [],
"unsafe_sub()": [],
"unsafe_add()": [],
"unsafe_div()": [],
"unsafe_mul()": [],
"pow_mod256()": [],
"max_value()": [],
"min_value()": [],
"concat()": [],
"ecrecover()": [],
"isqrt()": [],
"range()": [],
"min()": [],
"max()": [],
"shift()": [],
"abs()": [],
"raw_call()": ["bool", "bytes32"],
"_abi_encode()": [],
"slice()": [],
"uint2str()": ["string"],
"send()": [],
}
@ -104,7 +140,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

@ -9,9 +9,20 @@ if TYPE_CHECKING:
def intersection_predecessor(node: "Node") -> Set["Node"]:
if not node.fathers:
return set()
ret = node.fathers[0].dominators
for pred in node.fathers[1:]:
ret = ret.intersection(pred.dominators)
if not any(father.is_reachable for father in node.fathers):
return set()
ret = set()
for pred in node.fathers:
ret = ret.union(pred.dominators)
for pred in node.fathers:
if pred.is_reachable:
ret = ret.intersection(pred.dominators)
return ret
@ -84,6 +95,8 @@ def compute_dominance_frontier(nodes: List["Node"]) -> None:
for node in nodes:
if len(node.fathers) >= 2:
for father in node.fathers:
if not father.is_reachable:
continue
runner = father
# Corner case: if there is a if without else
# we need to add update the conditional node

@ -12,6 +12,7 @@ from .new_contract import NewContract
from .new_elementary_type import NewElementaryType
from .super_call_expression import SuperCallExpression
from .super_identifier import SuperIdentifier
from .self_identifier import SelfIdentifier
from .tuple_expression import TupleExpression
from .type_conversion import TypeConversion
from .unary_operation import UnaryOperation, UnaryOperationType

@ -42,7 +42,7 @@ class BinaryOperationType(Enum):
# pylint: disable=too-many-branches
@staticmethod
def get_type(
operation_type: "BinaryOperation",
operation_type: "str",
) -> "BinaryOperationType":
if operation_type == "**":
return BinaryOperationType.POWER

@ -4,12 +4,32 @@ from slither.core.expressions.expression import Expression
class CallExpression(Expression): # pylint: disable=too-many-instance-attributes
def __init__(self, called: Expression, arguments: List[Any], type_call: str) -> None:
def __init__(
self,
called: Expression,
arguments: List[Any],
type_call: str,
names: Optional[List[str]] = None,
) -> None:
"""
#### Parameters
called -
The expression denoting the function to be called
arguments -
List of argument expressions
type_call -
A string formatting of the called function's return type
names -
For calls with named fields, list fields in call order.
For calls without named fields, None.
"""
assert isinstance(called, Expression)
assert (names is None) or isinstance(names, list)
super().__init__()
self._called: Expression = called
self._arguments: List[Expression] = arguments
self._type_call: str = type_call
self._names: Optional[List[str]] = names
# gas and value are only available if the syntax is {gas: , value: }
# For the .gas().value(), the member are considered as function call
# And converted later to the correct info (convert.py)
@ -17,6 +37,14 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
self._value: Optional[Expression] = None
self._salt: Optional[Expression] = None
@property
def names(self) -> Optional[List[str]]:
"""
For calls with named fields, list fields in call order.
For calls without named fields, None.
"""
return self._names
@property
def call_value(self) -> Optional[Expression]:
return self._value
@ -62,4 +90,9 @@ class CallExpression(Expression): # pylint: disable=too-many-instance-attribute
if gas or value or salt:
options = [gas, value, salt]
txt += "{" + ",".join([o for o in options if o != ""]) + "}"
return txt + "(" + ",".join([str(a) for a in self._arguments]) + ")"
args = (
"{" + ",".join([f"{n}:{str(a)}" for (a, n) in zip(self._arguments, self._names)]) + "}"
if self._names is not None
else ",".join([str(a) for a in self._arguments])
)
return txt + "(" + args + ")"

@ -26,7 +26,6 @@ class Identifier(Expression):
],
) -> None:
super().__init__()
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract, SolidityVariable, SolidityFunction
from slither.solc_parsing.yul.evm_functions import YulBuiltin

@ -0,0 +1,6 @@
from slither.core.expressions.identifier import Identifier
class SelfIdentifier(Identifier):
def __str__(self):
return "self." + str(self._value)

@ -106,8 +106,6 @@ class UnaryOperation(Expression):
UnaryOperationType.MINUSMINUS_PRE,
UnaryOperationType.PLUSPLUS_POST,
UnaryOperationType.MINUSMINUS_POST,
UnaryOperationType.PLUS_PRE,
UnaryOperationType.MINUS_PRE,
]:
expression.set_lvalue()

@ -7,6 +7,7 @@ from crytic_compile.utils.naming import Filename
from slither.core.declarations import Contract, Import, Pragma
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
@ -39,6 +40,7 @@ class FileScope:
# 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()
@ -52,9 +54,9 @@ class FileScope:
# User defined types
# Name -> type alias
self.user_defined_types: Dict[str, TypeAlias] = {}
self.type_aliases: Dict[str, TypeAlias] = {}
def add_accesible_scopes(self) -> bool:
def add_accesible_scopes(self) -> bool: # pylint: disable=too-many-branches
"""
Add information from accessible scopes. Return true if new information was obtained
@ -74,6 +76,9 @@ class FileScope:
if not _dict_contain(new_scope.enums, self.enums):
self.enums.update(new_scope.enums)
learn_something = True
if not new_scope.events.issubset(self.events):
self.events |= new_scope.events
learn_something = True
if not new_scope.functions.issubset(self.functions):
self.functions |= new_scope.functions
learn_something = True
@ -95,8 +100,8 @@ class FileScope:
if not _dict_contain(new_scope.renaming, self.renaming):
self.renaming.update(new_scope.renaming)
learn_something = True
if not _dict_contain(new_scope.user_defined_types, self.user_defined_types):
self.user_defined_types.update(new_scope.user_defined_types)
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

@ -21,7 +21,8 @@ from slither.core.declarations.top_level import TopLevel
from slither.core.source_mapping.source_mapping import SourceMapping, Source
from slither.slithir.variables import Constant
from slither.utils.colors import red
from slither.utils.source_mapping import get_definition, get_references, get_implementation
from slither.utils.sarif import read_triage_info
from slither.utils.source_mapping import get_definition, get_references, get_all_implementations
logger = logging.getLogger("Slither")
logging.basicConfig()
@ -48,6 +49,10 @@ class SlitherCore(Context):
self._source_code_to_line: Optional[Dict[str, List[str]]] = None
self._previous_results_filename: str = "slither.db.json"
# TODO: add cli flag to set these variables
self.sarif_input: str = "export.sarif"
self.sarif_triage: str = "export.sarif.sarifexplorer"
self._results_to_hide: List = []
self._previous_results: List = []
# From triaged result
@ -57,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
@ -96,6 +102,8 @@ class SlitherCore(Context):
# If true, partial analysis is allowed
self.no_fail = False
self.skip_data_dependency = False
@property
def compilation_units(self) -> List[SlitherCompilationUnit]:
return list(self._compilation_units)
@ -196,41 +204,53 @@ class SlitherCore(Context):
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)
for offset in range(definition.start, definition.end + 1):
if (
isinstance(thing, TopLevel)
isinstance(thing, (TopLevel, Contract))
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)
self._offset_to_definitions[definition.filename][offset].add(definition)
self._offset_to_implementations[definition.filename][offset].add(implementation)
self._offset_to_implementations[definition.filename][offset].update(implementations)
self._offset_to_references[definition.filename][offset] |= set(references)
for ref in references:
for offset in range(ref.start, ref.end + 1):
is_declared_function = (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
if (
isinstance(thing, TopLevel)
or (
isinstance(thing, FunctionContract)
and thing.contract_declarer == thing.contract
)
or is_declared_function
or (
isinstance(thing, ContractLevel) and not isinstance(thing, FunctionContract)
)
):
self._offset_to_objects[definition.filename][offset].add(thing)
self._offset_to_definitions[ref.filename][offset].add(definition)
self._offset_to_implementations[ref.filename][offset].add(implementation)
if is_declared_function:
# Only show the nearest lexical definition for declared contract-level functions
if (
thing.contract.source_mapping.start
< offset
< thing.contract.source_mapping.end
):
self._offset_to_definitions[ref.filename][offset].add(definition)
else:
self._offset_to_definitions[ref.filename][offset].add(definition)
self._offset_to_implementations[ref.filename][offset].update(implementations)
self._offset_to_references[ref.filename][offset] |= set(references)
def _compute_offsets_to_ref_impl_decl(self): # pylint: disable=too-many-branches
@ -243,15 +263,18 @@ class SlitherCore(Context):
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)
@ -260,12 +283,26 @@ 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:
self._compute_offsets_from_thing(event)
for function in compilation_unit.functions_top_level:
self._compute_offsets_from_thing(function)
for st in compilation_unit.structures_top_level:
self._compute_offsets_from_thing(st)
for 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:
@ -402,25 +439,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"
)
@ -444,6 +485,8 @@ class SlitherCore(Context):
return True
def load_previous_results(self) -> None:
self.load_previous_results_from_sarif()
filename = self._previous_results_filename
try:
if os.path.isfile(filename):
@ -453,9 +496,24 @@ class SlitherCore(Context):
for r in self._previous_results:
if "id" in r:
self._previous_results_ids.add(r["id"])
except json.decoder.JSONDecodeError:
logger.error(red(f"Impossible to decode {filename}. Consider removing the file"))
def load_previous_results_from_sarif(self) -> None:
sarif = pathlib.Path(self.sarif_input)
triage = pathlib.Path(self.sarif_triage)
if not sarif.exists():
return
if not triage.exists():
return
triaged = read_triage_info(sarif, triage)
for id_triaged in triaged:
self._previous_results_ids.add(id_triaged)
def write_results_to_hide(self) -> None:
if not self._results_to_hide:
return
@ -474,6 +532,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
###################################################################################
###################################################################################

@ -1,2 +1,8 @@
from .state_variable import StateVariable
from .variable import Variable
from .local_variable_init_from_tuple import LocalVariableInitFromTuple
from .local_variable import LocalVariable
from .top_level_variable import TopLevelVariable
from .event_variable import EventVariable
from .function_type_variable import FunctionTypeVariable
from .structure_variable import StructureVariable

@ -2,7 +2,6 @@ from typing import Optional, TYPE_CHECKING
from slither.core.variables.variable import Variable
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.solidity_types.mapping_type import MappingType
from slither.core.solidity_types.elementary_type import ElementaryType
@ -51,6 +50,9 @@ class LocalVariable(Variable):
Returns:
(bool)
"""
# pylint: disable=import-outside-toplevel
from slither.core.solidity_types.array_type import ArrayType
if self.location == "memory":
return False
if self.location == "calldata":

@ -93,6 +93,13 @@ class Variable(SourceMapping):
def is_constant(self, is_cst: bool) -> None:
self._is_constant = is_cst
@property
def is_stored(self) -> bool:
"""
Checks if a variable is stored, based on it not being constant or immutable. Future updates may adjust for new non-storage keywords.
"""
return not self._is_constant and not self._is_immutable
@property
def is_reentrant(self) -> bool:
return self._is_reentrant
@ -179,5 +186,6 @@ class Variable(SourceMapping):
return f'{name}({",".join(parameters)})'
def __str__(self) -> str:
assert self._name
if self._name is None:
return ""
return self._name

@ -3,7 +3,7 @@ import re
from logging import Logger
from typing import Optional, List, TYPE_CHECKING, Dict, Union, Callable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.compilation_unit import SlitherCompilationUnit, Language
from slither.core.declarations import Contract
from slither.formatters.exceptions import FormatImpossible
from slither.formatters.utils.patches import apply_patch, create_diff
@ -80,6 +80,9 @@ class AbstractDetector(metaclass=abc.ABCMeta):
# list of vulnerable solc versions as strings (e.g. ["0.4.25", "0.5.0"])
# If the detector is meant to run on all versions, use None
VULNERABLE_SOLC_VERSIONS: Optional[List[str]] = None
# If the detector is meant to run on all languages, use None
# Otherwise, use `solidity` or `vyper`
LANGUAGE: Optional[str] = None
def __init__(
self, compilation_unit: SlitherCompilationUnit, slither: "Slither", logger: Logger
@ -133,6 +136,14 @@ class AbstractDetector(metaclass=abc.ABCMeta):
f"VULNERABLE_SOLC_VERSIONS should not be an empty list {self.__class__.__name__}"
)
if self.LANGUAGE is not None and self.LANGUAGE not in [
Language.SOLIDITY.value,
Language.VYPER.value,
]:
raise IncorrectDetectorInitialization(
f"LANGUAGE should not be either 'solidity' or 'vyper' {self.__class__.__name__}"
)
if re.match("^[a-zA-Z0-9_-]*$", self.ARGUMENT) is None:
raise IncorrectDetectorInitialization(
f"ARGUMENT has illegal character {self.__class__.__name__}"
@ -164,9 +175,14 @@ class AbstractDetector(metaclass=abc.ABCMeta):
if self.logger:
self.logger.info(self.color(info))
def _uses_vulnerable_solc_version(self) -> bool:
def _is_applicable_detector(self) -> bool:
if self.VULNERABLE_SOLC_VERSIONS:
return self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
return (
self.compilation_unit.is_solidity
and self.compilation_unit.solc_version in self.VULNERABLE_SOLC_VERSIONS
)
if self.LANGUAGE:
return self.compilation_unit.language.value == self.LANGUAGE
return True
@abc.abstractmethod
@ -179,7 +195,7 @@ class AbstractDetector(metaclass=abc.ABCMeta):
results: List[Dict] = []
# check solc version
if not self._uses_vulnerable_solc_version():
if not self._is_applicable_detector():
return results
# only keep valid result, and remove duplicate

@ -92,3 +92,9 @@ from .functions.cyclomatic_complexity import CyclomaticComplexity
from .operations.cache_array_length import CacheArrayLength
from .statements.incorrect_using_for import IncorrectUsingFor
from .operations.encode_packed import EncodePackedCollision
from .assembly.incorrect_return import IncorrectReturn
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

@ -0,0 +1,93 @@
from typing import List, Optional
from slither.core.declarations import SolidityFunction, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output
def _assembly_node(function: Function) -> Optional[SolidityCall]:
"""
Check if there is a node that use return in assembly
Args:
function:
Returns:
"""
for ir in function.all_slithir_operations():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"return(uint256,uint256)"
):
return ir
return None
class IncorrectReturn(AbstractDetector):
"""
Check for cases where a return(a,b) is used in an assembly function
"""
ARGUMENT = "incorrect-return"
HELP = "If a `return` is incorrectly used in assembly mode."
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-return-in-assembly"
)
WIKI_TITLE = "Incorrect return in assembly"
WIKI_DESCRIPTION = "Detect if `return` in an assembly block halts unexpectedly the execution."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
function f() internal returns (uint a, uint b) {
assembly {
return (5, 6)
}
}
function g() returns (bool){
f();
return true;
}
}
```
The return statement in `f` will cause execution in `g` to halt.
The function will return 6 bytes starting from offset 5, instead of returning a boolean."""
WIKI_RECOMMENDATION = "Use the `leave` statement."
# pylint: disable=too-many-nested-blocks
def _detect(self) -> List[Output]:
results: List[Output] = []
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)
return results

@ -0,0 +1,68 @@
from typing import List
from slither.core.declarations import SolidityFunction, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import SolidityCall
from slither.utils.output import Output
class ReturnInsteadOfLeave(AbstractDetector):
"""
Check for cases where a return(a,b) is used in an assembly function that also returns two variables
"""
ARGUMENT = "return-leave"
HELP = "If a `return` is used instead of a `leave`."
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#return-instead-of-leave-in-assembly"
WIKI_TITLE = "Return instead of leave in assembly"
WIKI_DESCRIPTION = "Detect if a `return` is used where a `leave` should be used."
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract C {
function f() internal returns (uint a, uint b) {
assembly {
return (5, 6)
}
}
}
```
The function will halt the execution, instead of returning a two uint."""
WIKI_RECOMMENDATION = "Use the `leave` statement."
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)
results.append(json)
return results
def _detect(self) -> List[Output]:
results: List[Output] = []
for c in self.contracts:
for f in c.functions_declared:
if (
len(f.returns) == 2
and f.contains_assembly
and f.visibility not in ["public", "external"]
):
results += self._check_function(f)
return results

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

@ -33,7 +33,7 @@ class IncorrectSolc(AbstractDetector):
HELP = "Incorrect Solidity version"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-versions-of-solidity"
WIKI_TITLE = "Incorrect versions of Solidity"

@ -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 internal_call in node.internal_calls:
if isinstance(internal_call, Function):
internal_ops += internal_call.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

@ -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.internal_calls]
calls = [c.name for c in func.all_internal_calls()]
if not ("suicide(address)" in calls or "selfdestruct(address)" in calls):
return False

@ -24,7 +24,7 @@ class NamingConvention(AbstractDetector):
HELP = "Conformity to Solidity naming conventions"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#conformance-to-solidity-naming-conventions"
WIKI_TITLE = "Conformance to Solidity naming conventions"
@ -45,6 +45,14 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
def is_cap_words(name: str) -> bool:
return re.search("^[A-Z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_immutable_naming(name: str) -> bool:
return re.search("^i_[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_state_naming(name: str) -> bool:
return re.search("^s_[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@staticmethod
def is_mixed_case(name: str) -> bool:
return re.search("^[a-z]([A-Za-z0-9]+)?_?$", name) is not None
@ -167,10 +175,17 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
results.append(res)
else:
if var.visibility == "private":
correct_naming = self.is_mixed_case_with_underscore(var.name)
if var.visibility in ["private", "internal"]:
correct_naming = self.is_mixed_case_with_underscore(
var.name
) or self.is_state_naming(var.name)
if not correct_naming and var.is_immutable:
correct_naming = self.is_immutable_naming(var.name)
else:
correct_naming = self.is_mixed_case(var.name)
if not correct_naming:
info = ["Variable ", var, " is not in mixedCase\n"]

@ -17,7 +17,7 @@ class CacheArrayLength(AbstractDetector):
ARGUMENT = "cache-array-length"
HELP = (
"Detects `for` loops that use `length` member of some storage array in their loop condition and don't "
"modify it. "
"modify it."
)
IMPACT = DetectorClassification.OPTIMIZATION
CONFIDENCE = DetectorClassification.HIGH
@ -216,9 +216,8 @@ contract C
for usage in non_optimal_array_len_usages:
info = [
"Loop condition ",
f"`{usage.source_mapping.content}` ",
f"({usage.source_mapping}) ",
"should use cached array length instead of referencing `length` member "
usage,
" should use cached array length instead of referencing `length` member "
"of the storage array.\n ",
]
res = self.generate_result(info)

@ -0,0 +1,93 @@
"""
Module detecting incorrect operator usage for exponentiation where bitwise xor '^' is used instead of '**'
"""
from typing import Tuple, List, Union
from slither.core.cfg.node import Node
from slither.core.declarations import Contract, Function
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import Binary, BinaryType, Operation
from slither.slithir.utils.utils import RVALUE
from slither.slithir.variables.constant import Constant
from slither.utils.output import Output
def _is_constant_candidate(var: Union[RVALUE, Function]) -> bool:
"""
Check if the variable is a constant.
Do not consider variable that are expressed with hexadecimal.
Something like 2^0xf is likely to be a correct bitwise operator
:param var:
:return:
"""
return isinstance(var, Constant) and not var.original_value.startswith("0x")
def _is_bitwise_xor_on_constant(ir: Operation) -> bool:
return (
isinstance(ir, Binary)
and ir.type == BinaryType.CARET
and (_is_constant_candidate(ir.variable_left) or _is_constant_candidate(ir.variable_right))
)
def _detect_incorrect_operator(contract: Contract) -> List[Tuple[Function, Node]]:
ret: List[Tuple[Function, Node]] = []
f: Function
for f in contract.functions + contract.modifiers: # type:ignore
# Heuristic: look for binary expressions with ^ operator where at least one of the operands is a constant, and
# the constant is not in hex, because hex typically is used with bitwise xor and not exponentiation
nodes = [node for node in f.nodes for ir in node.irs if _is_bitwise_xor_on_constant(ir)]
for node in nodes:
ret.append((f, node))
return ret
# pylint: disable=too-few-public-methods
class IncorrectOperatorExponentiation(AbstractDetector):
"""
Incorrect operator usage of bitwise xor mistaking it for exponentiation
"""
ARGUMENT = "incorrect-exp"
HELP = "Incorrect exponentiation"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-exponentiation"
WIKI_TITLE = "Incorrect exponentiation"
WIKI_DESCRIPTION = "Detect use of bitwise `xor ^` instead of exponential `**`"
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Bug{
uint UINT_MAX = 2^256 - 1;
...
}
```
Alice deploys a contract in which `UINT_MAX` incorrectly uses `^` operator instead of `**` for exponentiation"""
WIKI_RECOMMENDATION = "Use the correct operator `**` for exponentiation."
def _detect(self) -> List[Output]:
"""Detect the incorrect operator usage for exponentiation where bitwise xor ^ is used instead of **
Returns:
list: (function, node)
"""
results: List[Output] = []
for c in self.compilation_unit.contracts_derived:
res = _detect_incorrect_operator(c)
for (func, node) in res:
info: DETECTOR_INFO = [
func,
" has bitwise-xor operator ^ instead of the exponentiation operator **: \n",
]
info += ["\t - ", node, "\n"]
results.append(self.generate_result(info))
return results

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

@ -31,7 +31,7 @@ class DeprecatedStandards(AbstractDetector):
HELP = "Deprecated Solidity Standards"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
LANGUAGE = "solidity"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#deprecated-standards"
WIKI_TITLE = "Deprecated standards"

@ -2,7 +2,7 @@
Module detecting possible loss of precision due to divide before multiple
"""
from collections import defaultdict
from typing import DefaultDict, List, Set, Tuple
from typing import DefaultDict, List, Tuple
from slither.core.cfg.node import Node
from slither.core.declarations.contract import Contract
@ -63,7 +63,7 @@ def is_assert(node: Node) -> bool:
# pylint: disable=too-many-branches
def _explore(
to_explore: Set[Node], f_results: List[List[Node]], divisions: DefaultDict[LVALUE, List[Node]]
to_explore: List[Node], f_results: List[List[Node]], divisions: DefaultDict[LVALUE, List[Node]]
) -> None:
explored = set()
while to_explore: # pylint: disable=too-many-nested-blocks
@ -114,7 +114,7 @@ def _explore(
f_results.append(node_results)
for son in node.sons:
to_explore.add(son)
to_explore.append(son)
def detect_divide_before_multiply(
@ -133,7 +133,7 @@ def detect_divide_before_multiply(
results: List[Tuple[FunctionContract, List[Node]]] = []
# Loop for each function and modifier.
for function in contract.functions_declared:
for function in contract.functions_declared + contract.modifiers_declared:
if not function.entry_point:
continue
@ -145,7 +145,7 @@ def detect_divide_before_multiply(
# track all the division results (and the assignment of the division results)
divisions: DefaultDict[LVALUE, List[Node]] = defaultdict(list)
_explore({function.entry_point}, f_results, divisions)
_explore([function.entry_point], f_results, divisions)
for f_result in f_results:
results.append((function, f_result))

@ -6,6 +6,7 @@ from typing import List, Tuple
from slither.core.cfg.node import Node
from slither.core.declarations import Structure
from slither.core.declarations.contract import Contract
from slither.core.variables.variable import Variable
from slither.core.declarations.function_contract import FunctionContract
from slither.core.solidity_types import MappingType, UserDefinedType
from slither.detectors.abstract_detector import (
@ -69,14 +70,25 @@ The mapping `balances` is never deleted, so `remove` does not work as intended."
for ir in node.irs:
if isinstance(ir, Delete):
value = ir.variable
if isinstance(value.type, UserDefinedType) and isinstance(
value.type.type, Structure
):
st = value.type.type
if any(isinstance(e.type, MappingType) for e in st.elems.values()):
ret.append((f, st, node))
MappingDeletionDetection.check_if_mapping(value, ret, f, node)
return ret
@staticmethod
def check_if_mapping(
value: Variable,
ret: List[Tuple[FunctionContract, Structure, Node]],
f: FunctionContract,
node: Node,
):
if isinstance(value.type, UserDefinedType) and isinstance(value.type.type, Structure):
st = value.type.type
if any(isinstance(e.type, MappingType) for e in st.elems.values()):
ret.append((f, st, node))
return
for e in st.elems.values():
MappingDeletionDetection.check_if_mapping(e, ret, f, node)
def _detect(self) -> List[Output]:
"""Detect mapping deletion

@ -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,123 @@
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations import Contract
from slither.core.declarations.function import Function
from slither.core.solidity_types import Type
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import LowLevelCall, HighLevelCall
from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.utils.output import Output
class ReturnBomb(AbstractDetector):
ARGUMENT = "return-bomb"
HELP = "A low level callee may consume all callers gas unexpectedly."
IMPACT = DetectorClassification.LOW
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#return-bomb"
WIKI_TITLE = "Return Bomb"
WIKI_DESCRIPTION = "A low level callee may consume all callers gas unexpectedly."
WIKI_EXPLOIT_SCENARIO = """
```solidity
//Modified from https://github.com/nomad-xyz/ExcessivelySafeCall
contract BadGuy {
function youveActivateMyTrapCard() external pure returns (bytes memory) {
assembly{
revert(0, 1000000)
}
}
}
contract Mark {
function oops(address badGuy) public{
bool success;
bytes memory ret;
// Mark pays a lot of gas for this copy
//(success, ret) = badGuy.call{gas:10000}(
(success, ret) = badGuy.call(
abi.encodeWithSelector(
BadGuy.youveActivateMyTrapCard.selector
)
);
// Mark may OOG here, preventing local state changes
//importantCleanup();
}
}
```
After Mark calls BadGuy bytes are copied from returndata to memory, the memory expansion cost is paid. This means that when using a standard solidity call, the callee can "returnbomb" the caller, imposing an arbitrary gas cost.
Callee unexpectedly makes the caller OOG.
"""
WIKI_RECOMMENDATION = "Avoid unlimited implicit decoding of returndata."
@staticmethod
def is_dynamic_type(ty: Type) -> bool:
# ty.is_dynamic ?
name = str(ty)
if "[]" in name or name in ("bytes", "string"):
return True
return False
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
return nodes
def _detect(self) -> List[Output]:
results = []
for contract in self.compilation_unit.contracts:
for function in contract.functions_declared:
nodes = self.get_nodes_for_function(function, contract)
if nodes:
info: DETECTOR_INFO = [
function,
" tries to limit the gas of an external call that controls implicit decoding\n",
]
for node in sorted(nodes, key=lambda x: x.node_id):
info += ["\t", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,69 @@
from typing import List
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import (
Binary,
BinaryType,
)
from slither.core.declarations import Function
from slither.utils.output import Output
class TautologicalCompare(AbstractDetector):
"""
Same variable comparison detector
"""
ARGUMENT = "tautological-compare"
HELP = "Comparing a variable to itself always returns true or false, depending on comparison"
IMPACT = DetectorClassification.MEDIUM
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#tautological-compare"
WIKI_TITLE = "Tautological compare"
WIKI_DESCRIPTION = "A variable compared to itself is probably an error as it will always return `true` for `==`, `>=`, `<=` and always `false` for `<`, `>` and `!=`."
WIKI_EXPLOIT_SCENARIO = """
```solidity
function check(uint a) external returns(bool){
return (a >= a);
}
```
`check` always return true."""
WIKI_RECOMMENDATION = "Remove comparison or compare to different value."
def _check_function(self, f: Function) -> List[Output]:
affected_nodes = set()
for node in f.nodes:
for ir in node.irs:
if isinstance(ir, Binary):
if ir.type in [
BinaryType.GREATER,
BinaryType.GREATER_EQUAL,
BinaryType.LESS,
BinaryType.LESS_EQUAL,
BinaryType.EQUAL,
BinaryType.NOT_EQUAL,
]:
if ir.variable_left == ir.variable_right:
affected_nodes.add(node)
results = []
for n in affected_nodes:
info: DETECTOR_INFO = [f, " compares a variable to itself:\n\t", n, "\n"]
res = self.generate_result(info)
results.append(res)
return results
def _detect(self):
results = []
for f in self.compilation_unit.functions_and_modifiers:
results.extend(self._check_function(f))
return results

@ -36,7 +36,7 @@ class PredeclarationUsageLocal(AbstractDetector):
```solidity
contract C {
function f(uint z) public returns (uint) {
uint y = x + 9 + z; // 'z' is used pre-declaration
uint y = x + 9 + z; // 'x' is used pre-declaration
uint x = 7;
if (z % 2 == 0) {

@ -25,7 +25,7 @@ def _is_valid_type(v: StateVariable) -> bool:
def _valid_candidate(v: StateVariable) -> bool:
return _is_valid_type(v) and not (v.is_constant or v.is_immutable)
return _is_valid_type(v)
def _is_constant_var(v: Variable) -> bool:
@ -92,7 +92,7 @@ class UnchangedStateVariables:
variables = []
functions = []
variables.append(c.state_variables)
variables.append(c.stored_state_variables)
functions.append(c.all_functions_called)
valid_candidates: Set[StateVariable] = {

@ -501,7 +501,7 @@ def _explore_variables_declaration( # pylint: disable=too-many-arguments,too-ma
idx_beginning = func.source_mapping.start
idx_beginning += -func.source_mapping.starting_column + 1
idx_beginning += -sum([len(c) for c in potential_comments])
idx_beginning += -sum(len(c) for c in potential_comments)
old_comment = f"@param {old_str}".encode("utf8")

@ -20,7 +20,7 @@ class AbstractPrinter(metaclass=abc.ABCMeta):
WIKI = ""
def __init__(self, slither: "Slither", logger: Logger) -> None:
def __init__(self, slither: "Slither", logger: Optional[Logger]) -> None:
self.slither = slither
self.contracts = slither.contracts
self.filename = slither.filename

@ -9,6 +9,8 @@ from .functions.authorization import PrinterWrittenVariablesAndAuthorization
from .summary.slithir import PrinterSlithIR
from .summary.slithir_ssa import PrinterSlithIRSSA
from .summary.human_summary import PrinterHumanSummary
from .summary.ck import CK
from .summary.halstead import Halstead
from .functions.cfg import CFG
from .summary.function_ids import FunctionIds
from .summary.variable_order import VariableOrder
@ -21,3 +23,4 @@ from .summary.evm import PrinterEVM
from .summary.when_not_paused import PrinterWhenNotPaused
from .summary.declaration import Declaration
from .functions.dominator import Dominator
from .summary.martin import Martin

@ -4,7 +4,7 @@ from typing import Dict, List, Set, Tuple, NamedTuple, Union
from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.cfg.node import Node
from slither.core.declarations import Enum, Function
from slither.core.declarations import Enum, Function, Contract
from slither.core.declarations.solidity_variables import (
SolidityVariableComposed,
SolidityFunction,
@ -30,7 +30,7 @@ from slither.slithir.operations import (
TypeConversion,
)
from slither.slithir.operations.binary import Binary
from slither.slithir.variables import Constant
from slither.slithir.variables import Constant, ReferenceVariable
from slither.utils.output import Output
from slither.visitors.expression.constants_folding import ConstantFolding, NotConstant
@ -43,9 +43,9 @@ def _get_name(f: Union[Function, Variable]) -> str:
return f.solidity_signature
def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_payable(contracts: List[Contract]) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
payable_functions = [_get_name(f) for f in contract.functions_entry_points if f.payable]
if payable_functions:
ret[contract.name] = payable_functions
@ -53,10 +53,10 @@ def _extract_payable(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_solidity_variable_usage(
slither: SlitherCore, sol_var: SolidityVariable
contracts: List[Contract], sol_var: SolidityVariable
) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
functions_using_sol_var = []
for f in contract.functions_entry_points:
for v in f.all_solidity_variables_read():
@ -114,9 +114,9 @@ def _is_constant(f: Function) -> bool: # pylint: disable=too-many-branches
return True
def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
def _extract_constant_functions(contracts: List[Contract]) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
for contract in contracts:
cst_functions = [_get_name(f) for f in contract.functions_entry_points if _is_constant(f)]
cst_functions += [
v.solidity_signature for v in contract.state_variables if v.visibility in ["public"]
@ -126,15 +126,29 @@ def _extract_constant_functions(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _extract_assert(slither: SlitherCore) -> Dict[str, List[str]]:
ret: Dict[str, List[str]] = {}
for contract in slither.contracts:
functions_using_assert = []
def _extract_assert(contracts: List[Contract]) -> Dict[str, Dict[str, List[Dict]]]:
"""
Return the list of contract -> function name -> List(source mapping of the assert))
Args:
contracts: list of contracts
Returns:
"""
ret: Dict[str, Dict[str, List[Dict]]] = {}
for contract in contracts:
functions_using_assert = [] # Dict[str, List[Dict]] = defaultdict(list)
for f in contract.functions_entry_points:
for v in f.all_solidity_calls():
if v == SolidityFunction("assert(bool)"):
functions_using_assert.append(_get_name(f))
break
# Revert https://github.com/crytic/slither/pull/2105 until format is supported by echidna.
# for node in f.all_nodes():
# if SolidityFunction("assert(bool)") in node.solidity_calls and node.source_mapping:
# func_name = _get_name(f)
# functions_using_assert[func_name].append(node.source_mapping.to_json())
if functions_using_assert:
ret[contract.name] = functions_using_assert
return ret
@ -208,19 +222,20 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
except ValueError: # index could fail; should never happen in working solidity code
pass
for r in ir.read:
var_read = r.points_to_origin if isinstance(r, ReferenceVariable) else r
# Do not report struct_name in a.struct_name
if isinstance(ir, Member):
continue
if isinstance(r, Constant):
all_cst_used.append(ConstantValue(str(r.value), str(r.type)))
if isinstance(r, StateVariable):
if r.node_initialization:
if r.node_initialization.irs:
if r.node_initialization in context_explored:
if isinstance(var_read, Constant):
all_cst_used.append(ConstantValue(str(var_read.value), str(var_read.type)))
if isinstance(var_read, StateVariable):
if var_read.node_initialization:
if var_read.node_initialization.irs:
if var_read.node_initialization in context_explored:
continue
context_explored.add(r.node_initialization)
context_explored.add(var_read.node_initialization)
_extract_constants_from_irs(
r.node_initialization.irs,
var_read.node_initialization.irs,
all_cst_used,
all_cst_used_in_binary,
context_explored,
@ -228,13 +243,13 @@ def _extract_constants_from_irs( # pylint: disable=too-many-branches,too-many-n
def _extract_constants(
slither: SlitherCore,
contracts: List[Contract],
) -> Tuple[Dict[str, Dict[str, List]], Dict[str, Dict[str, Dict]]]:
# contract -> function -> [ {"value": value, "type": type} ]
ret_cst_used: Dict[str, Dict[str, List[ConstantValue]]] = defaultdict(dict)
# contract -> function -> binary_operand -> [ {"value": value, "type": type ]
ret_cst_used_in_binary: Dict[str, Dict[str, Dict[str, List[ConstantValue]]]] = defaultdict(dict)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
all_cst_used: List = []
all_cst_used_in_binary: Dict = defaultdict(list)
@ -260,11 +275,11 @@ def _extract_constants(
def _extract_function_relations(
slither: SlitherCore,
contracts: List[Contract],
) -> Dict[str, Dict[str, Dict[str, List[str]]]]:
# contract -> function -> [functions]
ret: Dict[str, Dict[str, Dict[str, List[str]]]] = defaultdict(dict)
for contract in slither.contracts:
for contract in contracts:
ret[contract.name] = defaultdict(dict)
written = {
_get_name(function): function.all_state_variables_written()
@ -288,14 +303,14 @@ def _extract_function_relations(
return ret
def _have_external_calls(slither: SlitherCore) -> Dict[str, List[str]]:
def _have_external_calls(contracts: List[Contract]) -> Dict[str, List[str]]:
"""
Detect the functions with external calls
:param slither:
:return:
"""
ret: Dict[str, List[str]] = defaultdict(list)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.all_high_level_calls() or function.all_low_level_calls():
ret[contract.name].append(_get_name(function))
@ -304,14 +319,14 @@ def _have_external_calls(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
def _use_balance(contracts: List[Contract]) -> Dict[str, List[str]]:
"""
Detect the functions with external calls
:param slither:
:return:
"""
ret: Dict[str, List[str]] = defaultdict(list)
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
for ir in function.all_slithir_operations():
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
@ -323,25 +338,25 @@ def _use_balance(slither: SlitherCore) -> Dict[str, List[str]]:
return ret
def _with_fallback(slither: SlitherCore) -> Set[str]:
def _with_fallback(contracts: List[Contract]) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.is_fallback:
ret.add(contract.name)
return ret
def _with_receive(slither: SlitherCore) -> Set[str]:
def _with_receive(contracts: List[Contract]) -> Set[str]:
ret: Set[str] = set()
for contract in slither.contracts:
for contract in contracts:
for function in contract.functions_entry_points:
if function.is_receive:
ret.add(contract.name)
return ret
def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]:
def _call_a_parameter(slither: SlitherCore, contracts: List[Contract]) -> Dict[str, List[Dict]]:
"""
Detect the functions with external calls
:param slither:
@ -349,7 +364,7 @@ def _call_a_parameter(slither: SlitherCore) -> Dict[str, List[Dict]]:
"""
# contract -> [ (function, idx, interface_called) ]
ret: Dict[str, List[Dict]] = defaultdict(list)
for contract in slither.contracts: # pylint: disable=too-many-nested-blocks
for contract in contracts: # pylint: disable=too-many-nested-blocks
for function in contract.functions_entry_points:
try:
for ir in function.all_slithir_operations():
@ -395,40 +410,40 @@ class Echidna(AbstractPrinter):
_filename(string)
"""
payable = _extract_payable(self.slither)
contracts = self.slither.contracts
payable = _extract_payable(contracts)
timestamp = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("block.timestamp")
contracts, SolidityVariableComposed("block.timestamp")
)
block_number = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("block.number")
contracts, SolidityVariableComposed("block.number")
)
msg_sender = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.sender")
)
msg_gas = _extract_solidity_variable_usage(
self.slither, SolidityVariableComposed("msg.gas")
contracts, SolidityVariableComposed("msg.sender")
)
assert_usage = _extract_assert(self.slither)
cst_functions = _extract_constant_functions(self.slither)
(cst_used, cst_used_in_binary) = _extract_constants(self.slither)
msg_gas = _extract_solidity_variable_usage(contracts, SolidityVariableComposed("msg.gas"))
assert_usage = _extract_assert(contracts)
cst_functions = _extract_constant_functions(contracts)
(cst_used, cst_used_in_binary) = _extract_constants(contracts)
functions_relations = _extract_function_relations(self.slither)
functions_relations = _extract_function_relations(contracts)
constructors = {
contract.name: contract.constructor.full_name
for contract in self.slither.contracts
for contract in contracts
if contract.constructor
}
external_calls = _have_external_calls(self.slither)
external_calls = _have_external_calls(contracts)
call_parameters = _call_a_parameter(self.slither)
# call_parameters = _call_a_parameter(self.slither, contracts)
use_balance = _use_balance(self.slither)
use_balance = _use_balance(contracts)
with_fallback = list(_with_fallback(self.slither))
with_fallback = list(_with_fallback(contracts))
with_receive = list(_with_receive(self.slither))
with_receive = list(_with_receive(contracts))
d = {
"payable": payable,
@ -443,7 +458,7 @@ class Echidna(AbstractPrinter):
"functions_relations": functions_relations,
"constructors": constructors,
"have_external_calls": external_calls,
"call_a_parameter": call_parameters,
# "call_a_parameter": call_parameters,
"use_balance": use_balance,
"solc_versions": [unit.solc_version for unit in self.slither.compilation_units],
"with_fallback": with_fallback,

@ -100,10 +100,11 @@ class PrinterInheritanceGraph(AbstractPrinter):
# Add arrows (number them if there is more than one path so we know order of declaration for inheritance).
if len(contract.immediate_inheritance) == 1:
ret += f"{contract.name} -> {contract.immediate_inheritance[0]};\n"
immediate_inheritance = contract.immediate_inheritance[0]
ret += f"c{contract.id}_{contract.name} -> c{immediate_inheritance.id}_{immediate_inheritance};\n"
else:
for i, immediate_inheritance in enumerate(contract.immediate_inheritance):
ret += f'{contract.name} -> {immediate_inheritance} [ label="{i + 1}" ];\n'
ret += f'c{contract.id}_{contract.name} -> c{immediate_inheritance.id}_{immediate_inheritance} [ label="{i + 1}" ];\n'
# Functions
visibilities = ["public", "external"]
@ -151,7 +152,7 @@ class PrinterInheritanceGraph(AbstractPrinter):
indirect_shadowing_information = self._get_indirect_shadowing_information(contract)
# Build the node label
ret += f'{contract.name}[shape="box"'
ret += f'c{contract.id}_{contract.name}[shape="box"'
ret += 'label=< <TABLE border="0">'
ret += f'<TR><TD align="center"><B>{contract.name}</B></TD></TR>'
if public_functions:

@ -0,0 +1,58 @@
"""
CK Metrics are a suite of six software metrics proposed by Chidamber and Kemerer in 1994.
These metrics are used to measure the complexity of a class.
https://en.wikipedia.org/wiki/Programming_complexity
- Response For a Class (RFC) is a metric that measures the number of unique method calls within a class.
- Number of Children (NOC) is a metric that measures the number of children a class has.
- Depth of Inheritance Tree (DIT) is a metric that measures the number of parent classes a class has.
- Coupling Between Object Classes (CBO) is a metric that measures the number of classes a class is coupled to.
Not implemented:
- Lack of Cohesion of Methods (LCOM) is a metric that measures the lack of cohesion in methods.
- Weighted Methods per Class (WMC) is a metric that measures the complexity of a class.
During the calculation of the metrics above, there are a number of other intermediate metrics that are calculated.
These are also included in the output:
- State variables: total number of state variables
- Constants: total number of constants
- Immutables: total number of immutables
- Public: total number of public functions
- External: total number of external functions
- Internal: total number of internal functions
- Private: total number of private functions
- Mutating: total number of state mutating functions
- View: total number of view functions
- Pure: total number of pure functions
- External mutating: total number of external mutating functions
- No auth or onlyOwner: total number of functions without auth or onlyOwner modifiers
- No modifiers: total number of functions without modifiers
- Ext calls: total number of external calls
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.ck import CKMetrics
from slither.utils.output import Output
class CK(AbstractPrinter):
ARGUMENT = "ck"
HELP = "Chidamber and Kemerer (CK) complexity metrics and related function attributes"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#ck"
def output(self, _filename: str) -> Output:
if len(self.contracts) == 0:
return self.generate_output("No contract found")
ck = CKMetrics(self.contracts)
res = self.generate_output(ck.full_text)
res.add_pretty_table(ck.auxiliary1.pretty_table, ck.auxiliary1.title)
res.add_pretty_table(ck.auxiliary2.pretty_table, ck.auxiliary2.title)
res.add_pretty_table(ck.auxiliary3.pretty_table, ck.auxiliary3.title)
res.add_pretty_table(ck.auxiliary4.pretty_table, ck.auxiliary4.title)
res.add_pretty_table(ck.core.pretty_table, ck.core.title)
self.info(ck.full_text)
return res

@ -21,18 +21,20 @@ class Declaration(AbstractPrinter):
txt += "\n# Contracts\n"
for contract in compilation_unit.contracts:
txt += f"# {contract.name}\n"
txt += f"\t- Declaration: {get_definition(contract, compilation_unit.core.crytic_compile).to_detailed_str()}\n"
txt += f"\t- Implementation: {get_implementation(contract).to_detailed_str()}\n"
contract_def = get_definition(contract, compilation_unit.core.crytic_compile)
txt += f"\t- Declaration: {contract_def.to_detailed_str()}\n"
txt += f"\t- Implementation(s): {[x.to_detailed_str() for x in list(self.slither.offset_to_implementations(contract.source_mapping.filename.absolute, contract_def.start))]}\n"
txt += (
f"\t- References: {[x.to_detailed_str() for x in get_references(contract)]}\n"
)
txt += "\n\t## Function\n"
for func in contract.functions:
for func in contract.functions_declared:
txt += f"\t\t- {func.canonical_name}\n"
txt += f"\t\t\t- Declaration: {get_definition(func, compilation_unit.core.crytic_compile).to_detailed_str()}\n"
txt += f"\t\t\t- Implementation: {get_implementation(func).to_detailed_str()}\n"
function_def = get_definition(func, compilation_unit.core.crytic_compile)
txt += f"\t\t\t- Declaration: {function_def.to_detailed_str()}\n"
txt += f"\t\t\t- Implementation(s): {[x.to_detailed_str() for x in list(self.slither.offset_to_implementations(func.source_mapping.filename.absolute, function_def.start))]}\n"
txt += f"\t\t\t- References: {[x.to_detailed_str() for x in get_references(func)]}\n"
txt += "\n\t## State variables\n"

@ -0,0 +1,49 @@
"""
Halstead complexity metrics
https://en.wikipedia.org/wiki/Halstead_complexity_measures
12 metrics based on the number of unique operators and operands:
Core metrics:
n1 = the number of distinct operators
n2 = the number of distinct operands
N1 = the total number of operators
N2 = the total number of operands
Extended metrics1:
n = n1 + n2 # Program vocabulary
N = N1 + N2 # Program length
S = n1 * log2(n1) + n2 * log2(n2) # Estimated program length
V = N * log2(n) # Volume
Extended metrics2:
D = (n1 / 2) * (N2 / n2) # Difficulty
E = D * V # Effort
T = E / 18 seconds # Time required to program
B = (E^(2/3)) / 3000 # Number of delivered bugs
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.halstead import HalsteadMetrics
from slither.utils.output import Output
class Halstead(AbstractPrinter):
ARGUMENT = "halstead"
HELP = "Computes the Halstead complexity metrics for each contract"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#halstead"
def output(self, _filename: str) -> Output:
if len(self.contracts) == 0:
return self.generate_output("No contract found")
halstead = HalsteadMetrics(self.contracts)
res = self.generate_output(halstead.full_text)
res.add_pretty_table(halstead.core.pretty_table, halstead.core.title)
res.add_pretty_table(halstead.extended1.pretty_table, halstead.extended1.title)
res.add_pretty_table(halstead.extended2.pretty_table, halstead.extended2.title)
self.info(halstead.full_text)
return res

@ -0,0 +1,32 @@
"""
Robert "Uncle Bob" Martin - Agile software metrics
https://en.wikipedia.org/wiki/Software_package_metrics
Efferent Coupling (Ce): Number of contracts that the contract depends on
Afferent Coupling (Ca): Number of contracts that depend on a contract
Instability (I): Ratio of efferent coupling to total coupling (Ce / (Ce + Ca))
Abstractness (A): Number of abstract contracts / total number of contracts
Distance from the Main Sequence (D): abs(A + I - 1)
"""
from slither.printers.abstract_printer import AbstractPrinter
from slither.utils.martin import MartinMetrics
from slither.utils.output import Output
class Martin(AbstractPrinter):
ARGUMENT = "martin"
HELP = "Martin agile software metrics (Ca, Ce, I, A, D)"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#martin"
def output(self, _filename: str) -> Output:
if len(self.contracts) == 0:
return self.generate_output("No contract found")
martin = MartinMetrics(self.contracts)
res = self.generate_output(martin.full_text)
res.add_pretty_table(martin.core.pretty_table, martin.core.title)
self.info(martin.full_text)
return res

@ -28,10 +28,9 @@ class VariableOrder(AbstractPrinter):
for contract in self.slither.contracts_derived:
txt += f"\n{contract.name}:\n"
table = MyPrettyTable(["Name", "Type", "Slot", "Offset"])
for variable in contract.state_variables_ordered:
if not variable.is_constant and not variable.is_immutable:
slot, offset = contract.compilation_unit.storage_layout_of(contract, variable)
table.add_row([variable.canonical_name, str(variable.type), slot, offset])
for variable in contract.stored_state_variables_ordered:
slot, offset = contract.compilation_unit.storage_layout_of(contract, variable)
table.add_row([variable.canonical_name, str(variable.type), slot, offset])
all_tables.append((contract.name, table))
txt += str(table) + "\n"

@ -11,7 +11,9 @@ from slither.detectors.abstract_detector import AbstractDetector, DetectorClassi
from slither.exceptions import SlitherError
from slither.printers.abstract_printer import AbstractPrinter
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.vyper_parsing.vyper_compilation_unit import VyperCompilationUnit
from slither.utils.output import Output
from slither.vyper_parsing.ast.ast import parse
logger = logging.getLogger("Slither")
logging.basicConfig()
@ -48,7 +50,9 @@ def _update_file_scopes(candidates: ValuesView[FileScope]):
learned_something = False
class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
class Slither(
SlitherCore
): # pylint: disable=too-many-instance-attributes,too-many-locals,too-many-statements
def __init__(self, target: Union[str, CryticCompile], **kwargs) -> None:
"""
Args:
@ -62,16 +66,6 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
triage_mode (bool): if true, switch to triage mode (default false)
exclude_dependencies (bool): if true, exclude results that are only related to dependencies
generate_patches (bool): if true, patches are generated (json output only)
truffle_ignore (bool): ignore truffle.js presence (default false)
truffle_build_directory (str): build truffle directory (default 'build/contracts')
truffle_ignore_compile (bool): do not run truffle compile (default False)
truffle_version (str): use a specific truffle version (default None)
embark_ignore (bool): ignore embark.js presence (default false)
embark_ignore_compile (bool): do not run embark build (default False)
embark_overwrite_config (bool): overwrite original config file (default false)
change_line_prefix (str): Change the line prefix (default #)
for the displayed source codes (i.e. file.sol#1).
@ -108,13 +102,23 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
for compilation_unit in crytic_compile.compilation_units.values():
compilation_unit_slither = SlitherCompilationUnit(self, compilation_unit)
self._compilation_units.append(compilation_unit_slither)
parser = SlitherCompilationUnitSolc(compilation_unit_slither)
self._parsers.append(parser)
for path, ast in compilation_unit.asts.items():
parser.parse_top_level_from_loaded_json(ast, path)
self.add_source_code(path)
_update_file_scopes(compilation_unit_slither.scopes.values())
if compilation_unit_slither.is_vyper:
vyper_parser = VyperCompilationUnit(compilation_unit_slither)
for path, ast in compilation_unit.asts.items():
ast_nodes = parse(ast["ast"])
vyper_parser.parse_module(ast_nodes, path)
self._parsers.append(vyper_parser)
else:
# Solidity specific
assert compilation_unit_slither.is_solidity
sol_parser = SlitherCompilationUnitSolc(compilation_unit_slither)
self._parsers.append(sol_parser)
for path, ast in compilation_unit.asts.items():
sol_parser.parse_top_level_items(ast, path)
self.add_source_code(path)
_update_file_scopes(compilation_unit_slither.scopes.values())
if kwargs.get("generate_patches", False):
self.generate_patches = True
@ -128,10 +132,21 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
for p in filter_paths:
self.add_path_to_filter(p)
include_paths = kwargs.get("include_paths", [])
for p in include_paths:
self.add_path_to_include(p)
self._exclude_dependencies = kwargs.get("exclude_dependencies", False)
triage_mode = kwargs.get("triage_mode", False)
triage_database = kwargs.get("triage_database", "slither.db.json")
self._triage_mode = triage_mode
self._previous_results_filename = triage_database
printers_to_run = kwargs.get("printers_to_run", "")
if printers_to_run == "echidna":
self.skip_data_dependency = True
self._init_parsing_and_analyses(kwargs.get("skip_analyze", False))
def _init_parsing_and_analyses(self, skip_analyze: bool) -> None:

@ -114,8 +114,8 @@ def convert_expression(expression: Expression, node: "Node") -> List[Operation]:
visitor = ExpressionToSlithIR(expression, node)
result = visitor.result()
result = apply_ir_heuristics(result, node)
is_solidity = node.compilation_unit.is_solidity
result = apply_ir_heuristics(result, node, is_solidity)
if result:
if node.type in [NodeType.IF, NodeType.IFLOOP]:
@ -385,6 +385,70 @@ def integrate_value_gas(result: List[Operation]) -> List[Operation]:
###################################################################################
def get_declared_param_names(
ins: Union[
NewStructure,
NewContract,
InternalCall,
LibraryCall,
HighLevelCall,
InternalDynamicCall,
EventCall,
]
) -> Optional[List[str]]:
"""
Given a call operation, return the list of parameter names, in the order
listed in the function declaration.
#### Parameters
ins -
The call instruction
#### Possible Returns
List[str] -
A list of the parameters in declaration order
None -
Workaround: Unable to obtain list of parameters in declaration order
"""
if isinstance(ins, NewStructure):
return [x.name for x in ins.structure.elems_ordered if not isinstance(x.type, MappingType)]
if isinstance(ins, (InternalCall, LibraryCall, HighLevelCall)):
if isinstance(ins.function, Function):
return [p.name for p in ins.function.parameters]
return None
if isinstance(ins, InternalDynamicCall):
return [p.name for p in ins.function_type.params]
assert isinstance(ins, (EventCall, NewContract))
return None
def reorder_arguments(
args: List[Variable], call_names: List[str], decl_names: List[str]
) -> List[Variable]:
"""
Reorder named struct constructor arguments so that they match struct declaration ordering rather
than call ordering
E.g. for `struct S { int x; int y; }` we reorder `S({y : 2, x : 3})` to `S(3, 2)`
#### Parameters
args -
Arguments to constructor call, in call order
names -
Parameter names in call order
decl_names -
Parameter names in declaration order
#### Returns
Reordered arguments to constructor call, now in declaration order
"""
assert len(args) == len(call_names)
assert len(call_names) == len(decl_names)
args_ret = []
for n in decl_names:
ind = call_names.index(n)
args_ret.append(args[ind])
return args_ret
def propagate_type_and_convert_call(result: List[Operation], node: "Node") -> List[Operation]:
"""
Propagate the types variables and convert tmp call to real call operation
@ -434,6 +498,23 @@ def propagate_type_and_convert_call(result: List[Operation], node: "Node") -> Li
if ins.call_id in calls_gas and isinstance(ins, (HighLevelCall, InternalDynamicCall)):
ins.call_gas = calls_gas[ins.call_id]
if isinstance(ins, Call) and (ins.names is not None):
assert isinstance(
ins,
(
NewStructure,
NewContract,
InternalCall,
LibraryCall,
HighLevelCall,
InternalDynamicCall,
EventCall,
),
)
decl_param_names = get_declared_param_names(ins)
if decl_param_names is not None:
call_data = reorder_arguments(call_data, ins.names, decl_param_names)
if isinstance(ins, (Call, NewContract, NewStructure)):
# We might have stored some arguments for libraries
if ins.arguments:
@ -549,6 +630,17 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
if new_ir:
return new_ir
# convert library function when used with "this"
if (
isinstance(t, ElementaryType)
and t.name == "address"
and ir.destination.name == "this"
and UserDefinedType(node_function.contract) in using_for
):
new_ir = convert_to_library_or_top_level(ir, node, using_for)
if new_ir:
return new_ir
if isinstance(t, UserDefinedType):
# UserdefinedType
t_type = t.type
@ -576,7 +668,9 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
if isinstance(t, ArrayType) or (
isinstance(t, ElementaryType) and t.type == "bytes"
):
if ir.function_name == "push" and len(ir.arguments) <= 1:
# Solidity uses push
# Vyper uses append
if ir.function_name in ["push", "append"] and len(ir.arguments) <= 1:
return convert_to_push(ir, node)
if ir.function_name == "pop" and len(ir.arguments) == 0:
return convert_to_pop(ir, node)
@ -777,9 +871,7 @@ def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-lo
elif isinstance(ir, NewArray):
ir.lvalue.set_type(ir.array_type)
elif isinstance(ir, NewContract):
contract = node.file_scope.get_contract_from_name(ir.contract_name)
assert contract
ir.lvalue.set_type(UserDefinedType(contract))
ir.lvalue.set_type(ir.contract_name)
elif isinstance(ir, NewElementaryType):
ir.lvalue.set_type(ir.type)
elif isinstance(ir, NewStructure):
@ -855,7 +947,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
if isinstance(ins.ori.variable_left, Contract):
st = ins.ori.variable_left.get_structure_from_name(ins.ori.variable_right)
if st:
op = NewStructure(st, ins.lvalue)
op = NewStructure(st, ins.lvalue, names=ins.names)
op.set_expression(ins.expression)
op.call_id = ins.call_id
return op
@ -892,6 +984,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
names=ins.names,
)
libcall.set_expression(ins.expression)
libcall.call_id = ins.call_id
@ -950,6 +1043,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
len(lib_func.parameters),
ins.lvalue,
"d",
names=ins.names,
)
lib_call.set_expression(ins.expression)
lib_call.set_node(ins.node)
@ -1031,6 +1125,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
ins.nbr_arguments,
ins.lvalue,
ins.type_call,
names=ins.names,
)
msgcall.call_id = ins.call_id
@ -1067,7 +1162,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
return n
if isinstance(ins.ori, TmpNewContract):
op = NewContract(Constant(ins.ori.contract_name), ins.lvalue)
op = NewContract(ins.ori.contract_name, ins.lvalue)
op.set_expression(ins.expression)
op.call_id = ins.call_id
if ins.call_value:
@ -1082,7 +1177,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
return n
if isinstance(ins.called, Structure):
op = NewStructure(ins.called, ins.lvalue)
op = NewStructure(ins.called, ins.lvalue, names=ins.names)
op.set_expression(ins.expression)
op.call_id = ins.call_id
op.set_expression(ins.expression)
@ -1106,13 +1201,13 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
if len(ins.called.constructor.parameters) != ins.nbr_arguments:
return Nop()
internalcall = InternalCall(
ins.called.constructor, ins.nbr_arguments, ins.lvalue, ins.type_call
ins.called.constructor, ins.nbr_arguments, ins.lvalue, ins.type_call, ins.names
)
internalcall.call_id = ins.call_id
internalcall.set_expression(ins.expression)
return internalcall
raise Exception(f"Not extracted {type(ins.called)} {ins}")
raise SlithIRError(f"Not extracted {type(ins.called)} {ins}")
# endregion
@ -1131,6 +1226,7 @@ def can_be_low_level(ir: HighLevelCall) -> bool:
"delegatecall",
"callcode",
"staticcall",
"raw_call",
]
@ -1159,13 +1255,14 @@ def convert_to_low_level(
ir.set_node(prev_ir.node)
ir.lvalue.set_type(ElementaryType("bool"))
return ir
if ir.function_name in ["call", "delegatecall", "callcode", "staticcall"]:
if ir.function_name in ["call", "delegatecall", "callcode", "staticcall", "raw_call"]:
new_ir = LowLevelCall(
ir.destination, ir.function_name, ir.nbr_arguments, ir.lvalue, ir.type_call
)
new_ir.call_gas = ir.call_gas
new_ir.call_value = ir.call_value
new_ir.arguments = ir.arguments
# TODO fix this for Vyper
if ir.node.compilation_unit.solc_version >= "0.5":
new_ir.lvalue.set_type([ElementaryType("bool"), ElementaryType("bytes")])
else:
@ -1210,7 +1307,12 @@ def convert_to_solidity_func(
and len(new_ir.arguments) == 2
and isinstance(new_ir.arguments[1], list)
):
types = list(new_ir.arguments[1])
types = []
for arg_type in new_ir.arguments[1]:
decode_type = arg_type
if isinstance(decode_type, (Structure, Enum, Contract)):
decode_type = UserDefinedType(decode_type)
types.append(decode_type)
new_ir.lvalue.set_type(types)
# abi.decode where the type to decode is a singleton
# abi.decode(a, (uint))
@ -1440,6 +1542,7 @@ def look_for_library_or_top_level(
ir.nbr_arguments,
ir.lvalue,
ir.type_call,
names=ir.names,
)
lib_call.set_expression(ir.expression)
lib_call.set_node(ir.node)
@ -1470,6 +1573,18 @@ def convert_to_library_or_top_level(
if new_ir:
return new_ir
if (
isinstance(t, ElementaryType)
and t.name == "address"
and ir.destination.name == "this"
and UserDefinedType(node.function.contract) in using_for
):
new_ir = look_for_library_or_top_level(
contract, ir, using_for, UserDefinedType(node.function.contract)
)
if new_ir:
return new_ir
return None
@ -1602,6 +1717,7 @@ def convert_type_of_high_and_internal_level_call(
Returns:
Potential new IR
"""
func = None
if isinstance(ir, InternalCall):
candidates: List[Function]
@ -1857,7 +1973,7 @@ def convert_constant_types(irs: List[Operation]) -> None:
if isinstance(ir.lvalue.type.type, ElementaryType):
if ir.lvalue.type.type.type in ElementaryTypeInt:
for r in ir.read:
if r.type.type not in ElementaryTypeInt:
if r.type.type.type not in ElementaryTypeInt:
r.set_type(ElementaryType(ir.lvalue.type.type.type))
was_changed = True
@ -1897,6 +2013,9 @@ def _find_source_mapping_references(irs: List[Operation]) -> None:
if isinstance(ir, NewContract):
ir.contract_created.references.append(ir.expression.source_mapping)
if isinstance(ir, HighLevelCall):
ir.function.references.append(ir.expression.source_mapping)
# endregion
###################################################################################
@ -1906,7 +2025,7 @@ def _find_source_mapping_references(irs: List[Operation]) -> None:
###################################################################################
def apply_ir_heuristics(irs: List[Operation], node: "Node") -> List[Operation]:
def apply_ir_heuristics(irs: List[Operation], node: "Node", is_solidity: bool) -> List[Operation]:
"""
Apply a set of heuristic to improve slithIR
"""
@ -1916,8 +2035,11 @@ def apply_ir_heuristics(irs: List[Operation], node: "Node") -> List[Operation]:
irs = propagate_type_and_convert_call(irs, node)
irs = remove_unused(irs)
find_references_origin(irs)
convert_constant_types(irs)
convert_delete(irs)
# These are heuristics that are only applied to Solidity
if is_solidity:
convert_constant_types(irs)
convert_delete(irs)
_find_source_mapping_references(irs)

@ -6,9 +6,25 @@ from slither.slithir.operations.operation import Operation
class Call(Operation):
def __init__(self) -> None:
def __init__(self, names: Optional[List[str]] = None) -> None:
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert (names is None) or isinstance(names, list)
super().__init__()
self._arguments: List[Variable] = []
self._names = names
@property
def names(self) -> Optional[List[str]]:
"""
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
return self._names
@property
def arguments(self) -> List[Variable]:
@ -18,7 +34,6 @@ class Call(Operation):
def arguments(self, v: List[Variable]) -> None:
self._arguments = v
# pylint: disable=no-self-use
def can_reenter(self, _callstack: Optional[List[Union[Function, Variable]]] = None) -> bool:
"""
Must be called after slithIR analysis pass
@ -26,7 +41,7 @@ class Call(Operation):
"""
return False
def can_send_eth(self) -> bool: # pylint: disable=no-self-use
def can_send_eth(self) -> bool:
"""
Must be called after slithIR analysis pass
:return: bool

@ -28,11 +28,18 @@ class HighLevelCall(Call, OperationWithLValue):
nbr_arguments: int,
result: Optional[Union[TemporaryVariable, TupleVariable, TemporaryVariableSSA]],
type_call: str,
names: Optional[List[str]] = None,
) -> None:
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(function_name, Constant)
assert is_valid_lvalue(result) or result is None
self._check_destination(destination)
super().__init__()
super().__init__(names=names)
# Contract is only possible for library call, which inherits from highlevelcall
self._destination: Union[Variable, SolidityVariable, Contract] = destination # type: ignore
self._function_name = function_name
@ -47,7 +54,6 @@ class HighLevelCall(Call, OperationWithLValue):
# Development function, to be removed once the code is stable
# It is overridden by LibraryCall
# pylint: disable=no-self-use
def _check_destination(self, destination: Union[Variable, SolidityVariable, Contract]) -> None:
assert isinstance(destination, (Variable, SolidityVariable))

@ -44,4 +44,4 @@ class InitArray(OperationWithLValue):
return f"{elem}({elem.type})"
init_values = convert(self.init_values)
return f"{self.lvalue}({self.lvalue.type}) = {init_values}"
return f"{self.lvalue}({self.lvalue.type}) = {init_values}"

@ -20,8 +20,16 @@ class InternalCall(Call, OperationWithLValue): # pylint: disable=too-many-insta
Union[TupleVariableSSA, TemporaryVariableSSA, TupleVariable, TemporaryVariable]
],
type_call: str,
names: Optional[List[str]] = None,
) -> None:
super().__init__()
# pylint: disable=too-many-arguments
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
super().__init__(names=names)
self._contract_name = ""
if isinstance(function, Function):
self._function: Optional[Function] = function

@ -33,4 +33,4 @@ class NewArray(Call, OperationWithLValue):
def __str__(self):
args = [str(a) for a in self.arguments]
lvalue = self.lvalue
return f"{lvalue}{lvalue.type}) = new {self.array_type}({','.join(args)})"
return f"{lvalue}({lvalue.type}) = new {self.array_type}({','.join(args)})"

@ -3,6 +3,7 @@ from typing import Optional, Any, List, Union
from slither.core.declarations import Function
from slither.core.declarations.contract import Contract
from slither.core.variables import Variable
from slither.core.solidity_types import UserDefinedType
from slither.slithir.operations import Call, OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue
from slither.slithir.variables.constant import Constant
@ -12,11 +13,22 @@ from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes
def __init__(
self, contract_name: Constant, lvalue: Union[TemporaryVariableSSA, TemporaryVariable]
self,
contract_name: UserDefinedType,
lvalue: Union[TemporaryVariableSSA, TemporaryVariable],
names: Optional[List[str]] = None,
) -> None:
assert isinstance(contract_name, Constant)
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(
contract_name.type, Contract
), f"contract_name is {contract_name} of type {type(contract_name)}"
assert is_valid_lvalue(lvalue)
super().__init__()
super().__init__(names=names)
self._contract_name = contract_name
# todo create analyze to add the contract instance
self._lvalue = lvalue
@ -49,7 +61,7 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
self._call_salt = s
@property
def contract_name(self) -> Constant:
def contract_name(self) -> UserDefinedType:
return self._contract_name
@property
@ -60,10 +72,7 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
@property
def contract_created(self) -> Contract:
contract_name = self.contract_name
contract_instance = self.node.file_scope.get_contract_from_name(contract_name)
assert contract_instance
return contract_instance
return self.contract_name.type
###################################################################################
###################################################################################

@ -1,4 +1,4 @@
from typing import List, Union
from typing import List, Optional, Union
from slither.slithir.operations.call import Call
from slither.slithir.operations.lvalue import OperationWithLValue
@ -17,8 +17,15 @@ class NewStructure(Call, OperationWithLValue):
self,
structure: StructureContract,
lvalue: Union[TemporaryVariableSSA, TemporaryVariable],
names: Optional[List[str]] = None,
) -> None:
super().__init__()
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
super().__init__(names=names)
assert isinstance(structure, Structure)
assert is_valid_lvalue(lvalue)
self._structure = structure

@ -4,6 +4,7 @@ from slither.core.declarations import Contract
from slither.core.solidity_types.elementary_type import ElementaryType
from slither.core.solidity_types.type_alias import TypeAlias
from slither.core.solidity_types.user_defined_type import UserDefinedType
from slither.core.solidity_types.array_type import ArrayType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
@ -21,10 +22,10 @@ class TypeConversion(OperationWithLValue):
super().__init__()
assert is_valid_rvalue(variable) or isinstance(variable, Contract)
assert is_valid_lvalue(result)
assert isinstance(variable_type, (TypeAlias, UserDefinedType, ElementaryType))
assert isinstance(variable_type, (TypeAlias, UserDefinedType, ElementaryType, ArrayType))
self._variable = variable
self._type: Union[TypeAlias, UserDefinedType, ElementaryType] = variable_type
self._type: Union[TypeAlias, UserDefinedType, ElementaryType, ArrayType] = variable_type
self._lvalue = result
@property

@ -5,7 +5,6 @@ from enum import Enum
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_lvalue, is_valid_rvalue
from slither.slithir.exceptions import SlithIRError
from slither.core.expressions.unary_operation import UnaryOperationType
from slither.core.variables.local_variable import LocalVariable
from slither.slithir.variables.constant import Constant
from slither.slithir.variables.local_variable import LocalIRVariable
@ -35,7 +34,7 @@ class Unary(OperationWithLValue):
self,
result: Union[TemporaryVariableSSA, TemporaryVariable],
variable: Union[Constant, LocalIRVariable, LocalVariable],
operation_type: UnaryOperationType,
operation_type: UnaryType,
) -> None:
assert is_valid_rvalue(variable)
assert is_valid_lvalue(result)
@ -53,7 +52,7 @@ class Unary(OperationWithLValue):
return self._variable
@property
def type(self) -> UnaryOperationType:
def type(self) -> UnaryType:
return self._type
@property

@ -1,4 +1,4 @@
from typing import Optional, Union
from typing import List, Optional, Union
from slither.core.declarations import (
Event,
@ -25,7 +25,15 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
nbr_arguments: int,
result: Union[TupleVariable, TemporaryVariable],
type_call: str,
names: Optional[List[str]] = None,
) -> None:
# pylint: disable=too-many-arguments
"""
#### Parameters
names -
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(
called,
(
@ -42,6 +50,7 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
self._called = called
self._nbr_arguments = nbr_arguments
self._type_call = type_call
self._names = names
self._lvalue = result
self._ori = None #
self._callid = None
@ -49,6 +58,14 @@ class TmpCall(OperationWithLValue): # pylint: disable=too-many-instance-attribu
self._value = None
self._salt = None
@property
def names(self) -> Optional[List[str]]:
"""
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
return self._names
@property
def call_value(self):
return self._value

@ -90,13 +90,13 @@ def transform_slithir_vars_to_ssa(
tmp_variables = [v for v in variables if isinstance(v, TemporaryVariable)]
for idx, _ in enumerate(tmp_variables):
tmp_variables[idx].index = idx
tmp_variables[idx].index = idx # pylint: disable=unnecessary-list-index-lookup
ref_variables = [v for v in variables if isinstance(v, ReferenceVariable)]
for idx, _ in enumerate(ref_variables):
ref_variables[idx].index = idx
ref_variables[idx].index = idx # pylint: disable=unnecessary-list-index-lookup
tuple_variables = [v for v in variables if isinstance(v, TupleVariable)]
for idx, _ in enumerate(tuple_variables):
tuple_variables[idx].index = idx
tuple_variables[idx].index = idx # pylint: disable=unnecessary-list-index-lookup
###################################################################################
@ -735,12 +735,17 @@ def copy_ir(ir: Operation, *instances) -> Operation:
destination = get_variable(ir, lambda x: x.destination, *instances)
function_name = ir.function_name
nbr_arguments = ir.nbr_arguments
names = ir.names
lvalue = get_variable(ir, lambda x: x.lvalue, *instances)
type_call = ir.type_call
if isinstance(ir, LibraryCall):
new_ir = LibraryCall(destination, function_name, nbr_arguments, lvalue, type_call)
new_ir = LibraryCall(
destination, function_name, nbr_arguments, lvalue, type_call, names=names
)
else:
new_ir = HighLevelCall(destination, function_name, nbr_arguments, lvalue, type_call)
new_ir = HighLevelCall(
destination, function_name, nbr_arguments, lvalue, type_call, names=names
)
new_ir.call_id = ir.call_id
new_ir.call_value = get_variable(ir, lambda x: x.call_value, *instances)
new_ir.call_gas = get_variable(ir, lambda x: x.call_gas, *instances)
@ -761,7 +766,8 @@ def copy_ir(ir: Operation, *instances) -> Operation:
nbr_arguments = ir.nbr_arguments
lvalue = get_variable(ir, lambda x: x.lvalue, *instances)
type_call = ir.type_call
new_ir = InternalCall(function, nbr_arguments, lvalue, type_call)
names = ir.names
new_ir = InternalCall(function, nbr_arguments, lvalue, type_call, names=names)
new_ir.arguments = get_arguments(ir, *instances)
return new_ir
if isinstance(ir, InternalDynamicCall):
@ -811,7 +817,8 @@ def copy_ir(ir: Operation, *instances) -> Operation:
if isinstance(ir, NewStructure):
structure = ir.structure
lvalue = get_variable(ir, lambda x: x.lvalue, *instances)
new_ir = NewStructure(structure, lvalue)
names = ir.names
new_ir = NewStructure(structure, lvalue, names=names)
new_ir.arguments = get_arguments(ir, *instances)
return new_ir
if isinstance(ir, Nop):

@ -11,7 +11,7 @@ from slither.utils.integer_conversion import convert_string_to_int
class Constant(SlithIRVariable):
def __init__(
self,
val: Union[int, str],
val: str,
constant_type: Optional[ElementaryType] = None,
subdenomination: Optional[str] = None,
) -> None: # pylint: disable=too-many-branches

@ -1,10 +1,10 @@
import logging
import re
from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequence
from typing import Any, List, Dict, Callable, TYPE_CHECKING, Union, Set, Sequence, Tuple
from slither.core.declarations import (
Modifier,
Event,
EventContract,
EnumContract,
StructureContract,
Function,
@ -16,7 +16,7 @@ from slither.core.solidity_types import ElementaryType, TypeAliasContract
from slither.core.variables.state_variable import StateVariable
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.event import EventSolc
from slither.solc_parsing.declarations.event_contract import EventContractSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.modifier import ModifierSolc
from slither.solc_parsing.declarations.structure_contract import StructureContractSolc
@ -64,7 +64,8 @@ class ContractSolc(CallerContextExpression):
# use to remap inheritance id
self._remapping: Dict[str, str] = {}
self.baseContracts: List[str] = []
# (referencedDeclaration, offset)
self.baseContracts: List[Tuple[int, str]] = []
self.baseConstructorContractsCalled: List[str] = []
self._linearized_base_contracts: List[int]
@ -174,6 +175,9 @@ class ContractSolc(CallerContextExpression):
self._contract.is_fully_implemented = attributes["fullyImplemented"]
self._linearized_base_contracts = attributes["linearizedBaseContracts"]
if "abstract" in attributes:
self._contract.is_abstract = attributes["abstract"]
# Parse base contract information
self._parse_base_contract_info()
@ -201,7 +205,9 @@ class ContractSolc(CallerContextExpression):
# Obtain our contract reference and add it to our base contract list
referencedDeclaration = base_contract["baseName"]["referencedDeclaration"]
self.baseContracts.append(referencedDeclaration)
self.baseContracts.append(
(referencedDeclaration, base_contract["baseName"]["src"])
)
# If we have defined arguments in our arguments object, this is a constructor invocation.
# (note: 'arguments' can be [], which is not the same as None. [] implies a constructor was
@ -233,7 +239,10 @@ class ContractSolc(CallerContextExpression):
referencedDeclaration = base_contract_items[0]["attributes"][
"referencedDeclaration"
]
self.baseContracts.append(referencedDeclaration)
self.baseContracts.append(
(referencedDeclaration, base_contract_items[0]["src"])
)
# If we have an 'attributes'->'arguments' which is None, this is not a constructor call.
if (
@ -291,10 +300,10 @@ class ContractSolc(CallerContextExpression):
alias = item["name"]
alias_canonical = self._contract.name + "." + item["name"]
user_defined_type = TypeAliasContract(original_type, alias, self.underlying_contract)
user_defined_type.set_offset(item["src"], self.compilation_unit)
self._contract.file_scope.user_defined_types[alias] = user_defined_type
self._contract.file_scope.user_defined_types[alias_canonical] = user_defined_type
type_alias = TypeAliasContract(original_type, alias, self.underlying_contract)
type_alias.set_offset(item["src"], self.compilation_unit)
self._contract.type_aliases_as_dict[alias] = type_alias
self._contract.file_scope.type_aliases[alias_canonical] = type_alias
def _parse_struct(self, struct: Dict) -> None:
@ -319,7 +328,7 @@ class ContractSolc(CallerContextExpression):
ce.set_contract(self._contract)
ce.set_offset(custom_error["src"], self.compilation_unit)
ce_parser = CustomErrorSolc(ce, custom_error, self._slither_parser)
ce_parser = CustomErrorSolc(ce, custom_error, self, self._slither_parser)
self._contract.custom_errors_as_dict[ce.name] = ce
self._custom_errors_parser.append(ce_parser)
@ -357,6 +366,8 @@ class ContractSolc(CallerContextExpression):
self._variables_parser.append(var_parser)
assert var.name
if var_parser.reference_id is not None:
self._contract.state_variables_by_ref_id[var_parser.reference_id] = var
self._contract.variables_as_dict[var.name] = var
self._contract.add_variables_ordered([var])
@ -725,7 +736,7 @@ class ContractSolc(CallerContextExpression):
new_enum.set_offset(enum["src"], self._contract.compilation_unit)
self._contract.enums_as_dict[canonicalName] = new_enum
def _analyze_struct(self, struct: StructureContractSolc) -> None: # pylint: disable=no-self-use
def _analyze_struct(self, struct: StructureContractSolc) -> None:
struct.analyze()
def analyze_structs(self) -> None:
@ -745,12 +756,12 @@ class ContractSolc(CallerContextExpression):
self._contract.events_as_dict.update(father.events_as_dict)
for event_to_parse in self._eventsNotParsed:
event = Event()
event = EventContract()
event.set_contract(self._contract)
event.set_offset(event_to_parse["src"], self._contract.compilation_unit)
event_parser = EventSolc(event, event_to_parse, self) # type: ignore
event_parser.analyze(self) # type: ignore
event_parser = EventContractSolc(event, event_to_parse, self) # type: ignore
event_parser.analyze() # type: ignore
self._contract.events_as_dict[event.full_name] = event
except (VariableNotFound, KeyError) as e:
self.log_incorrect_parsing(f"Missing event {e}")

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Dict
from typing import TYPE_CHECKING, Dict, Optional
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.custom_error_contract import CustomErrorContract
@ -10,6 +10,7 @@ from slither.solc_parsing.variables.local_variable import LocalVariableSolc
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.solc_parsing.declarations.contract import ContractSolc
# Part of the code was copied from the function parsing
@ -21,11 +22,13 @@ class CustomErrorSolc(CallerContextExpression):
self,
custom_error: CustomError,
custom_error_data: dict,
contract_parser: Optional["ContractSolc"],
slither_parser: "SlitherCompilationUnitSolc",
) -> None:
self._slither_parser: "SlitherCompilationUnitSolc" = slither_parser
self._custom_error = custom_error
custom_error.name = custom_error_data["name"]
self._contract_parser = contract_parser
self._params_was_analyzed = False
if not self._slither_parser.is_compact_ast:
@ -56,6 +59,10 @@ class CustomErrorSolc(CallerContextExpression):
if params:
self._parse_params(params)
@property
def contract_parser(self) -> Optional["ContractSolc"]:
return self._contract_parser
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast

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

Loading…
Cancel
Save