Merge branch 'dev' into mutator/fit-and-finish

pull/2302/head
bohendo 8 months ago
commit 1907ace9ca
No known key found for this signature in database
  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/ci.yml
  6. 2
      .github/workflows/docker.yml
  7. 2
      .github/workflows/doctor.yml
  8. 40
      .github/workflows/issue-metrics.yml
  9. 8
      .github/workflows/linter.yml
  10. 4
      .github/workflows/publish.yml
  11. 2
      .github/workflows/pylint.yml
  12. 17
      .github/workflows/test.yml
  13. 8
      CONTRIBUTING.md
  14. 14
      README.md
  15. 14
      examples/scripts/data_dependency.py
  16. 9
      examples/scripts/data_dependency.sol
  17. 3
      examples/scripts/possible_paths.py
  18. 4
      pyproject.toml
  19. 0
      scripts/ci_test_interface.sh
  20. 6
      setup.py
  21. 68
      slither/__main__.py
  22. 16
      slither/core/cfg/node.py
  23. 11
      slither/core/compilation_unit.py
  24. 2
      slither/core/declarations/__init__.py
  25. 93
      slither/core/declarations/contract.py
  26. 24
      slither/core/declarations/event.py
  27. 25
      slither/core/declarations/event_contract.py
  28. 13
      slither/core/declarations/event_top_level.py
  29. 49
      slither/core/declarations/function.py
  30. 17
      slither/core/declarations/function_top_level.py
  31. 7
      slither/core/declarations/solidity_variables.py
  32. 4
      slither/core/declarations/using_for_top_level.py
  33. 7
      slither/core/scope/scope.py
  34. 79
      slither/core/slither_core.py
  35. 7
      slither/core/variables/variable.py
  36. 1
      slither/detectors/all_detectors.py
  37. 11
      slither/detectors/assembly/shift_parameter_mixup.py
  38. 2
      slither/detectors/functions/modifier.py
  39. 155
      slither/detectors/functions/out_of_order_retryable.py
  40. 2
      slither/detectors/operations/unchecked_send_return_value.py
  41. 2
      slither/detectors/operations/unchecked_transfer.py
  42. 6
      slither/detectors/operations/unused_return_values.py
  43. 18
      slither/detectors/statements/msg_value_in_loop.py
  44. 2
      slither/detectors/statements/too_many_digits.py
  45. 4
      slither/detectors/variables/unchanged_state_variables.py
  46. 2
      slither/formatters/naming_convention/naming_convention.py
  47. 2
      slither/printers/abstract_printer.py
  48. 7
      slither/printers/inheritance/inheritance_graph.py
  49. 12
      slither/printers/summary/declaration.py
  50. 7
      slither/printers/summary/variable_order.py
  51. 6
      slither/slither.py
  52. 80
      slither/slithir/convert.py
  53. 3
      slither/slithir/operations/call.py
  54. 1
      slither/slithir/operations/high_level_call.py
  55. 9
      slither/slithir/operations/init_array.py
  56. 4
      slither/slithir/operations/new_array.py
  57. 14
      slither/slithir/operations/new_contract.py
  58. 6
      slither/slithir/utils/ssa.py
  59. 32
      slither/solc_parsing/declarations/contract.py
  60. 25
      slither/solc_parsing/declarations/event_contract.py
  61. 75
      slither/solc_parsing/declarations/event_top_level.py
  62. 254
      slither/solc_parsing/declarations/function.py
  63. 2
      slither/solc_parsing/declarations/modifier.py
  64. 17
      slither/solc_parsing/expressions/expression_parsing.py
  65. 9
      slither/solc_parsing/expressions/find_variable.py
  66. 113
      slither/solc_parsing/slither_compilation_unit_solc.py
  67. 11
      slither/solc_parsing/solidity_types/type_parsing.py
  68. 14
      slither/solc_parsing/yul/evm_functions.py
  69. 4
      slither/solc_parsing/yul/parse_yul.py
  70. 5
      slither/tools/documentation/__main__.py
  71. 1
      slither/tools/flattening/flattening.py
  72. 6
      slither/tools/properties/__main__.py
  73. 2
      slither/tools/read_storage/read_storage.py
  74. 4
      slither/tools/upgradeability/checks/variable_initialization.py
  75. 24
      slither/tools/upgradeability/checks/variables_order.py
  76. 2
      slither/utils/command_line.py
  77. 2
      slither/utils/encoding.py
  78. 11
      slither/utils/myprettytable.py
  79. 4
      slither/utils/output.py
  80. 49
      slither/utils/source_mapping.py
  81. 23
      slither/utils/upgradeability.py
  82. 17
      slither/utils/using_for.py
  83. 18
      slither/visitors/slithir/expression_to_slithir.py
  84. 4
      slither/vyper_parsing/declarations/contract.py
  85. 15
      slither/vyper_parsing/expressions/expression_parsing.py
  86. 4
      tests/conftest.py
  87. 3
      tests/e2e/detectors/snapshots/detectors__detector_ModifierDefaultDetection_0_4_25_modifier_default_sol__0.txt
  88. 3
      tests/e2e/detectors/snapshots/detectors__detector_ModifierDefaultDetection_0_5_16_modifier_default_sol__0.txt
  89. 3
      tests/e2e/detectors/snapshots/detectors__detector_ModifierDefaultDetection_0_6_11_modifier_default_sol__0.txt
  90. 3
      tests/e2e/detectors/snapshots/detectors__detector_ModifierDefaultDetection_0_7_6_modifier_default_sol__0.txt
  91. 16
      tests/e2e/detectors/snapshots/detectors__detector_OutOfOrderRetryable_0_8_20_out_of_order_retryable_sol__0.txt
  92. 20
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyBenign_0_5_16_reentrancy_benign_sol__0.txt
  93. 32
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyBenign_0_6_11_reentrancy_benign_sol__0.txt
  94. 36
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyBenign_0_7_6_reentrancy_benign_sol__0.txt
  95. 6
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyReadBeforeWritten_0_5_16_reentrancy_write_sol__0.txt
  96. 18
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyReadBeforeWritten_0_6_11_reentrancy_write_sol__0.txt
  97. 6
      tests/e2e/detectors/snapshots/detectors__detector_ReentrancyReadBeforeWritten_0_7_6_reentrancy_write_sol__0.txt
  98. 2
      tests/e2e/detectors/snapshots/detectors__detector_ShiftParameterMixup_0_6_11_shift_parameter_mixup_sol__0.txt
  99. 2
      tests/e2e/detectors/snapshots/detectors__detector_ShiftParameterMixup_0_7_6_shift_parameter_mixup_sol__0.txt
  100. 4
      tests/e2e/detectors/snapshots/detectors__detector_UnusedReturnValues_0_4_25_unused_return_sol__0.txt
  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,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",
@ -67,7 +67,7 @@ jobs:
- name: Set up nix
if: matrix.type == 'dapp'
uses: cachix/install-nix-action@v25
uses: cachix/install-nix-action@v26
- name: Set up cachix
if: matrix.type == 'dapp'

@ -30,7 +30,7 @@ jobs:
- name: Set Docker metadata
id: metadata
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ github.repository }}

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

@ -0,0 +1,40 @@
name: Monthly issue metrics
on:
workflow_dispatch:
schedule:
- cron: '3 2 1 * *'
permissions:
issues: write
pull-requests: read
jobs:
build:
name: issue metrics
runs-on: ubuntu-latest
steps:
- name: Get dates for last month
shell: bash
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-01)
# Calculate the last day of the previous month
last_day=$(date -d "$first_day +1 month -1 day" +%Y-%m-%d)
#Set an environment variable with the date range
echo "$first_day..$last_day"
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@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

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

@ -38,13 +38,13 @@ 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.11
uses: pypa/gh-action-pypi-publish@v1.8.14
- name: sign
uses: sigstore/gh-action-sigstore-python@v2.1.1

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

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

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

@ -288,16 +288,16 @@ Slither is licensed and distributed under the AGPLv3 license. [Contact us](mailt
Title | Usage | Authors | Venue | Code
--- | --- | --- | --- | ---
[ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method) | AST-based analysis built on top of Slither | Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen | CTCIS 19
[ReJection: A AST-Based Reentrancy Vulnerability Detection Method](https://www.researchgate.net/publication/339354823_ReJection_A_AST-Based_Reentrancy_Vulnerability_Detection_Method) | AST-based analysis built on top of Slither | Rui Ma, Zefeng Jian, Guangyuan Chen, Ke Ma, Yujia Chen | CTCIS 19 | -
[MPro: Combining Static and Symbolic Analysis forScalable Testing of Smart Contract](https://arxiv.org/pdf/1911.00570.pdf) | Leverage data dependency through Slither | William Zhang, Sebastian Banescu, Leodardo Pasos, Steven Stewart, Vijay Ganesh | ISSRE 2019 | [MPro](https://github.com/QuanZhang-William/M-Pro)
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20
[ETHPLOIT: From Fuzzing to Efficient Exploit Generation against Smart Contracts](https://wcventure.github.io/FuzzingPaper/Paper/SANER20_ETHPLOIT.pdf) | Leverage data dependency through Slither | Qingzhao Zhang, Yizhuo Wang, Juanru Li, Siqi Ma | SANER 20 | -
[Verification of Ethereum Smart Contracts: A Model Checking Approach](http://www.ijmlc.org/vol10/977-AM0059.pdf) | Symbolic execution built on top of Slither’s CFG | Tam Bang, Hoang H Nguyen, Dung Nguyen, Toan Trieu, Tho Quan | IJMLC 20 | -
[Smart Contract Repair](https://arxiv.org/pdf/1912.05823.pdf) | Rely on Slither’s vulnerabilities detectors | Xiao Liang Yu, Omar Al-Bataineh, David Lo, Abhik Roychoudhury | TOSEM 20 | [SCRepair](https://github.com/xiaoly8/SCRepair/)
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020)
[Demystifying Loops in Smart Contracts](https://www.microsoft.com/en-us/research/uploads/prod/2020/08/loops_solidity__camera_ready-5f3fec3f15c69.pdf) | Leverage data dependency through Slither | Ben Mariano, Yanju Chen, Yu Feng, Shuvendu Lahiri, Isil Dillig | ASE 20 | -
[Trace-Based Dynamic Gas Estimation of Loops in Smart Contracts](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=9268144) | Use Slither’s CFG to detect loops | Chunmiao Li, Shijie Nie, Yang Cao, Yijun Yu, Zhenjiang Hu | IEEE Open J. Comput. Soc. 1 (2020) | -
[SAILFISH: Vetting Smart Contract State-Inconsistency Bugs in Seconds](https://arxiv.org/pdf/2104.08638.pdf) | Rely on SlithIR to build a *storage dependency graph* | Priyanka Bose, Dipanjan Das, Yanju Chen, Yu Feng, Christopher Kruegel, and Giovanni Vigna | S&P 22 | [Sailfish](https://github.com/ucsb-seclab/sailfish)
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22 | -
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022) | -
[MANDO: Multi-Level Heterogeneous Graph Embeddings for Fine-Grained Detection of Smart Contract Vulnerabilities](https://arxiv.org/abs/2208.13252) | Use Slither to extract the CFG and call graph | Hoang Nguyen, Nhat-Minh Nguyen, Chunyao Xie, Zahra Ahmadi, Daniel Kudendo, Thanh-Nam Doan and Lingxiao Jiang| IEEE 9th International Conference on Data Science and Advanced Analytics (DSAA, 2022) | [ge-sc](https://github.com/MANDO-Project/ge-sc)
[Automated Auditing of Price Gouging TOD Vulnerabilities in Smart Contracts](https://www.cs.toronto.edu/~fanl/papers/price-icbc22.pdf) | Use Slither to extract the CFG and data dependencies| Sidi Mohamed Beillahi, Eric Keilty, Keerthi Nelaturu, Andreas Veneris, and Fan Long | 2022 IEEE International Conference on Blockchain and Cryptocurrency (ICBC) | [Smart-Contract-Repair](https://github.com/Veneris-Group/TOD-Location-Rectification)

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

@ -15,8 +15,8 @@ setup(
"packaging",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
"crytic-compile>=0.3.5,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#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",

@ -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
@ -166,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)
@ -208,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
@ -256,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
@ -268,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 []
@ -297,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",
)
@ -307,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",
@ -518,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)",
@ -571,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
@ -623,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

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

@ -16,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
@ -57,6 +58,7 @@ class SlitherCompilationUnit(Context):
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] = []
@ -234,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
@ -296,10 +302,7 @@ class SlitherCompilationUnit(Context):
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

@ -9,9 +9,8 @@ from typing import Optional, List, Dict, Callable, Tuple, TYPE_CHECKING, Union,
from crytic_compile.platform import Type as PlatformType
from slither.core.cfg.scope import Scope
from slither.core.solidity_types.type import Type
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.utils.using_for import USING_FOR, _merge_using_for
from slither.core.declarations.function import Function, FunctionType, FunctionLanguage
from slither.utils.erc import (
ERC20_signatures,
@ -33,7 +32,7 @@ if TYPE_CHECKING:
from slither.utils.type_helpers import LibraryCallType, HighLevelCallType, InternalCallType
from slither.core.declarations import (
Enum,
Event,
EventContract,
Modifier,
EnumContract,
StructureContract,
@ -50,9 +49,6 @@ if TYPE_CHECKING:
LOGGER = logging.getLogger("Contract")
USING_FOR_KEY = Union[str, Type]
USING_FOR_ITEM = List[Union[Type, Function]]
class Contract(SourceMapping): # pylint: disable=too-many-public-methods
"""
@ -73,7 +69,7 @@ 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"] = {}
@ -87,12 +83,13 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._type_aliases: Dict[str, "TypeAliasContract"] = {}
# The only str is "*"
self._using_for: Dict[USING_FOR_KEY, USING_FOR_ITEM] = {}
self._using_for_complete: Optional[Dict[USING_FOR_KEY, USING_FOR_ITEM]] = None
self._using_for: USING_FOR = {}
self._using_for_complete: Optional[USING_FOR] = None
self._kind: Optional[str] = None
self._is_interface: bool = False
self._is_library: bool = False
self._is_fully_implemented: bool = False
self._is_abstract: bool = False
self._signatures: Optional[List[str]] = None
self._signatures_declared: Optional[List[str]] = None
@ -203,12 +200,34 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
@property
def is_fully_implemented(self) -> bool:
"""
bool: True if the contract defines all functions.
In modern Solidity, virtual functions can lack an implementation.
Prior to Solidity 0.6.0, functions like the following would be not fully implemented:
```solidity
contract ImplicitAbstract{
function f() public;
}
```
"""
return self._is_fully_implemented
@is_fully_implemented.setter
def is_fully_implemented(self, is_fully_implemented: bool):
self._is_fully_implemented = is_fully_implemented
@property
def is_abstract(self) -> bool:
"""
Note for Solidity < 0.6.0 it will always be false
bool: True if the contract is abstract.
"""
return self._is_abstract
@is_abstract.setter
def is_abstract(self, is_abstract: bool):
self._is_abstract = is_abstract
# endregion
###################################################################################
###################################################################################
@ -278,28 +297,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
@ -310,24 +329,15 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
###################################################################################
@property
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for(self) -> USING_FOR:
return self._using_for
@property
def using_for_complete(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for_complete(self) -> USING_FOR:
"""
Dict[Union[str, Type], List[Type]]: Dict of merged local using for directive with top level directive
USING_FOR: Dict of merged local using for directive with top level directive
"""
def _merge_using_for(
uf1: Dict[USING_FOR_KEY, USING_FOR_ITEM], uf2: Dict[USING_FOR_KEY, USING_FOR_ITEM]
) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
result = {**uf1, **uf2}
for key, value in result.items():
if key in uf1 and key in uf2:
result[key] = value + uf1[key]
return result
if self._using_for_complete is None:
result = self.using_for
top_level_using_for = self.file_scope.using_for_directives
@ -436,6 +446,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"]:
"""
@ -969,16 +1006,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
###################################################################################

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

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

@ -21,7 +21,8 @@ SOLIDITY_VARIABLES = {
}
SOLIDITY_VARIABLES_COMPOSED = {
"block.basefee": "uint",
"block.basefee": "uint256",
"block.blobbasefee": "uint256",
"block.coinbase": "address",
"block.difficulty": "uint256",
"block.prevrandao": "uint256",
@ -44,6 +45,7 @@ SOLIDITY_VARIABLES_COMPOSED = {
}
SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"blobhash(uint256)": ["bytes32"],
"gasleft()": ["uint256"],
"assert(bool)": [],
"require(bool)": [],
@ -114,6 +116,7 @@ SOLIDITY_FUNCTIONS: Dict[str, List[str]] = {
"_abi_encode()": [],
"slice()": [],
"uint2str()": ["string"],
"send()": [],
}
@ -137,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

@ -1,8 +1,8 @@
from typing import TYPE_CHECKING, List, Dict, Union
from slither.core.declarations.contract import USING_FOR_KEY, USING_FOR_ITEM
from slither.core.solidity_types.type import Type
from slither.core.declarations.top_level import TopLevel
from slither.utils.using_for import USING_FOR
if TYPE_CHECKING:
from slither.core.scope.scope import FileScope
@ -15,5 +15,5 @@ class UsingForTopLevel(TopLevel):
self.file_scope: "FileScope" = scope
@property
def using_for(self) -> Dict[USING_FOR_KEY, USING_FOR_ITEM]:
def using_for(self) -> USING_FOR:
return self._using_for

@ -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()
@ -54,7 +56,7 @@ class FileScope:
# Name -> type alias
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

@ -22,7 +22,7 @@ from slither.core.source_mapping.source_mapping import SourceMapping, Source
from slither.slithir.variables import Constant
from slither.utils.colors import red
from slither.utils.sarif import read_triage_info
from slither.utils.source_mapping import get_definition, get_references, get_implementation
from slither.utils.source_mapping import get_definition, get_references, get_all_implementations
logger = logging.getLogger("Slither")
logging.basicConfig()
@ -62,6 +62,7 @@ class SlitherCore(Context):
# Multiple time the same result, so we remove duplicates
self._currently_seen_resuts: Set[str] = set()
self._paths_to_filter: Set[str] = set()
self._paths_to_include: Set[str] = set()
self._crytic_compile: Optional[CryticCompile] = None
@ -203,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
@ -250,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)
@ -267,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:
@ -409,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"
)
@ -498,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
###################################################################################
###################################################################################

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

@ -97,3 +97,4 @@ 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

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

@ -89,7 +89,7 @@ If the condition in `myModif` is false, the execution of `get()` will return 0."
info: DETECTOR_INFO = [
"Modifier ",
mod,
" does not always execute _; or revert",
" does not always execute _; or revert\n",
]
res = self.generate_result(info)

@ -0,0 +1,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

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

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

@ -40,7 +40,7 @@ class TooManyDigits(AbstractDetector):
# region wiki_description
WIKI_DESCRIPTION = """
Literals with many digits are difficult to read and review.
Literals with many digits are difficult to read and review. Use scientific notation or suffixes to make the code more readable.
"""
# endregion wiki_description

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

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

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

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

@ -132,10 +132,16 @@ class Slither(
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":

@ -1,6 +1,6 @@
import logging
from pathlib import Path
from typing import Any, List, TYPE_CHECKING, Union, Optional, Dict
from typing import Any, List, TYPE_CHECKING, Union, Optional
# pylint: disable= too-many-lines,import-outside-toplevel,too-many-branches,too-many-statements,too-many-nested-blocks
from slither.core.declarations import (
@ -13,7 +13,6 @@ from slither.core.declarations import (
SolidityVariableComposed,
Structure,
)
from slither.core.declarations.contract import USING_FOR_KEY, USING_FOR_ITEM
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations.function_top_level import FunctionTopLevel
@ -84,6 +83,7 @@ from slither.slithir.variables import Constant, ReferenceVariable, TemporaryVari
from slither.slithir.variables import TupleVariable
from slither.utils.function import get_function_id
from slither.utils.type import export_nested_types_from_variable
from slither.utils.using_for import USING_FOR
from slither.visitors.slithir.expression_to_slithir import ExpressionToSlithIR
if TYPE_CHECKING:
@ -594,11 +594,13 @@ def _convert_type_contract(ir: Member) -> Assignment:
def propagate_types(ir: Operation, node: "Node"): # pylint: disable=too-many-locals
# propagate the type
node_function = node.function
using_for: Dict[USING_FOR_KEY, USING_FOR_ITEM] = (
node_function.contract.using_for_complete
if isinstance(node_function, FunctionContract)
else {}
)
using_for: USING_FOR = {}
if isinstance(node_function, FunctionContract):
using_for = node_function.contract.using_for_complete
elif isinstance(node_function, FunctionTopLevel):
using_for = node_function.using_for_complete
if isinstance(ir, OperationWithLValue) and ir.lvalue:
# Force assignment in case of missing previous correct type
if not ir.lvalue.type:
@ -871,9 +873,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):
@ -1164,7 +1164,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:
@ -1209,7 +1209,7 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]) -> Union[Call,
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
@ -1533,9 +1533,10 @@ def look_for_library_or_top_level(
internalcall.lvalue = None
return internalcall
lib_contract = None
if isinstance(destination, FunctionContract) and destination.contract.is_library:
lib_contract = destination.contract
else:
elif not isinstance(destination, FunctionTopLevel):
lib_contract = contract.file_scope.get_contract_from_name(str(destination))
if lib_contract:
lib_call = LibraryCall(
@ -1563,7 +1564,9 @@ def convert_to_library_or_top_level(
# We use contract_declarer, because Solidity resolve the library
# before resolving the inheritance.
# Though we could use .contract as libraries cannot be shadowed
contract = node.function.contract_declarer
contract = (
node.function.contract_declarer if isinstance(node.function, FunctionContract) else None
)
t = ir.destination.type
if t in using_for:
new_ir = look_for_library_or_top_level(contract, ir, using_for, t)
@ -1719,6 +1722,7 @@ def convert_type_of_high_and_internal_level_call(
Returns:
Potential new IR
"""
func = None
if isinstance(ir, InternalCall):
candidates: List[Function]
@ -1922,35 +1926,21 @@ def convert_constant_types(irs: List[Operation]) -> None:
while was_changed:
was_changed = False
for ir in irs:
if isinstance(ir, Assignment):
if isinstance(ir.lvalue.type, ElementaryType):
if ir.lvalue.type.type in ElementaryTypeInt:
if isinstance(ir.rvalue, Function):
continue
if isinstance(ir.rvalue, TupleVariable):
# TODO: fix missing Unpack conversion
continue
if isinstance(ir.rvalue.type, TypeAlias):
ir.rvalue.set_type(ElementaryType(ir.lvalue.type.name))
was_changed = True
elif ir.rvalue.type.type not in ElementaryTypeInt:
ir.rvalue.set_type(ElementaryType(ir.lvalue.type.type))
if isinstance(ir, (Assignment, Binary)):
if (
isinstance(ir.lvalue.type, ElementaryType)
and ir.lvalue.type.type in ElementaryTypeInt
):
for r in ir.read:
if isinstance(r, Constant) and r.type.type not in ElementaryTypeInt:
r.set_type(ElementaryType(ir.lvalue.type.type))
was_changed = True
if isinstance(ir, Binary):
if isinstance(ir.lvalue.type, ElementaryType):
if ir.lvalue.type.type in ElementaryTypeInt:
for r in ir.read:
if r.type.type not in ElementaryTypeInt:
r.set_type(ElementaryType(ir.lvalue.type.type))
was_changed = True
if isinstance(ir, (HighLevelCall, InternalCall)):
func = ir.function
if isinstance(func, StateVariable):
types = export_nested_types_from_variable(func)
else:
if func is None:
# TODO: add POP instruction
break
types = [p.type for p in func.parameters]
assert len(types) == len(ir.arguments)
for idx, arg in enumerate(ir.arguments):
@ -1960,6 +1950,7 @@ def convert_constant_types(irs: List[Operation]) -> None:
if arg.type.type not in ElementaryTypeInt:
arg.set_type(ElementaryType(t.type))
was_changed = True
if isinstance(ir, NewStructure):
st = ir.structure
for idx, arg in enumerate(ir.arguments):
@ -1969,11 +1960,15 @@ def convert_constant_types(irs: List[Operation]) -> None:
if arg.type.type not in ElementaryTypeInt:
arg.set_type(ElementaryType(e.type.type))
was_changed = True
def is_elementary_array(t):
return isinstance(t, ArrayType) and isinstance(t.type, ElementaryType)
if isinstance(ir, InitArray):
if isinstance(ir.lvalue.type, ArrayType):
if isinstance(ir.lvalue.type.type, ElementaryType):
if ir.lvalue.type.type.type in ElementaryTypeInt:
for r in ir.read:
if is_elementary_array(ir.lvalue.type):
if ir.lvalue.type.type.type in ElementaryTypeInt:
for r in ir.read:
if isinstance(r, Constant) and is_elementary_array(r.type):
if r.type.type.type not in ElementaryTypeInt:
r.set_type(ElementaryType(ir.lvalue.type.type.type))
was_changed = True
@ -2014,6 +2009,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
###################################################################################

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

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

@ -1,14 +1,13 @@
from typing import List, Union
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import is_valid_rvalue
from slither.slithir.variables.constant import Constant
from slither.slithir.utils.utils import is_valid_rvalue, RVALUE
from slither.slithir.variables.temporary import TemporaryVariable
from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
class InitArray(OperationWithLValue):
def __init__(
self, init_values: List[Constant], lvalue: Union[TemporaryVariableSSA, TemporaryVariable]
self, init_values: List[RVALUE], lvalue: Union[TemporaryVariableSSA, TemporaryVariable]
) -> None:
# init_values can be an array of n dimension
# reduce was removed in py3
@ -30,11 +29,11 @@ class InitArray(OperationWithLValue):
self._lvalue = lvalue
@property
def read(self) -> List[Constant]:
def read(self) -> List[RVALUE]:
return self._unroll(self.init_values)
@property
def init_values(self) -> List[Constant]:
def init_values(self) -> List[RVALUE]:
return list(self._init_values)
def __str__(self):

@ -3,9 +3,9 @@ from typing import List, Union, TYPE_CHECKING
from slither.core.solidity_types.array_type import ArrayType
from slither.slithir.operations.call import Call
from slither.slithir.operations.lvalue import OperationWithLValue
from slither.slithir.utils.utils import RVALUE
if TYPE_CHECKING:
from slither.slithir.variables.constant import Constant
from slither.slithir.variables.temporary import TemporaryVariable
from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
@ -27,7 +27,7 @@ class NewArray(Call, OperationWithLValue):
return self._array_type
@property
def read(self) -> List["Constant"]:
def read(self) -> List[RVALUE]:
return self._unroll(self.arguments)
def __str__(self):

@ -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
@ -13,7 +14,7 @@ from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instance-attributes
def __init__(
self,
contract_name: Constant,
contract_name: UserDefinedType,
lvalue: Union[TemporaryVariableSSA, TemporaryVariable],
names: Optional[List[str]] = None,
) -> None:
@ -23,7 +24,9 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
For calls of the form f({argName1 : arg1, ...}), the names of parameters listed in call order.
Otherwise, None.
"""
assert isinstance(contract_name, Constant)
assert isinstance(
contract_name.type, Contract
), f"contract_name is {contract_name} of type {type(contract_name)}"
assert is_valid_lvalue(lvalue)
super().__init__(names=names)
self._contract_name = contract_name
@ -58,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
@ -69,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
###################################################################################
###################################################################################

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

@ -1,28 +1,29 @@
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,
)
from slither.core.declarations.contract import Contract, USING_FOR_KEY
from slither.core.declarations.contract import Contract
from slither.core.declarations.custom_error_contract import CustomErrorContract
from slither.core.declarations.function_contract import FunctionContract
from slither.core.solidity_types import ElementaryType, TypeAliasContract
from slither.core.variables.state_variable import StateVariable
from slither.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
from slither.solc_parsing.exceptions import ParsingError, VariableNotFound
from slither.solc_parsing.solidity_types.type_parsing import parse_type
from slither.solc_parsing.variables.state_variable import StateVariableSolc
from slither.utils.using_for import USING_FOR_KEY
LOGGER = logging.getLogger("ContractSolcParsing")
@ -64,7 +65,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 +176,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 +206,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 +240,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 (
@ -727,7 +737,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:
@ -747,12 +757,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,26 +1,27 @@
"""
Event module
EventContract module
"""
from typing import TYPE_CHECKING, Dict
from slither.core.variables.event_variable import EventVariable
from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.core.declarations.event import Event
from slither.core.declarations.event_contract import EventContract
if TYPE_CHECKING:
from slither.solc_parsing.declarations.contract import ContractSolc
class EventSolc:
class EventContractSolc:
"""
Event class
EventContract class
"""
def __init__(self, event: Event, event_data: Dict, contract_parser: "ContractSolc") -> None:
def __init__(
self, event: EventContract, event_data: Dict, contract_parser: "ContractSolc"
) -> None:
self._event = event
event.set_contract(contract_parser.underlying_contract)
self._parser_contract = contract_parser
self._contract_parser = contract_parser
if self.is_compact_ast:
self._event.name = event_data["name"]
@ -41,18 +42,16 @@ class EventSolc:
@property
def is_compact_ast(self) -> bool:
return self._parser_contract.is_compact_ast
return self._contract_parser.is_compact_ast
def analyze(self, contract: "ContractSolc") -> None:
def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed:
elem = EventVariable()
# Todo: check if the source offset is always here
if "src" in elem_to_parse:
elem.set_offset(
elem_to_parse["src"], self._parser_contract.underlying_contract.compilation_unit
)
elem.set_offset(elem_to_parse["src"], self._contract_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(contract)
elem_parser.analyze(self._contract_parser)
self._event.elems.append(elem)

@ -0,0 +1,75 @@
"""
EventTopLevel module
"""
from typing import TYPE_CHECKING, Dict
from slither.core.declarations.event_top_level import EventTopLevel
from slither.core.variables.event_variable import EventVariable
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.solc_parsing.variables.event_variable import EventVariableSolc
from slither.solc_parsing.declarations.caller_context import CallerContextExpression
if TYPE_CHECKING:
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
class EventTopLevelSolc(CallerContextExpression):
"""
EventTopLevel class
"""
def __init__(
self, event: EventTopLevel, event_data: Dict, slither_parser: "SlitherCompilationUnitSolc"
) -> None:
self._event = event
self._slither_parser = slither_parser
if self.is_compact_ast:
self._event.name = event_data["name"]
elems = event_data["parameters"]
assert elems["nodeType"] == "ParameterList"
self._elemsNotParsed = elems["parameters"]
else:
self._event.name = event_data["attributes"]["name"]
for elem in event_data["children"]:
# From Solidity 0.6.3 to 0.6.10 (included)
# Comment above a event might be added in the children
# of an event for the legacy ast
if elem["name"] == "ParameterList":
if "children" in elem:
self._elemsNotParsed = elem["children"]
else:
self._elemsNotParsed = []
def analyze(self) -> None:
for elem_to_parse in self._elemsNotParsed:
elem = EventVariable()
# Todo: check if the source offset is always here
if "src" in elem_to_parse:
elem.set_offset(elem_to_parse["src"], self._slither_parser.compilation_unit)
elem_parser = EventVariableSolc(elem, elem_to_parse)
elem_parser.analyze(self)
self._event.elems.append(elem)
self._elemsNotParsed = []
@property
def is_compact_ast(self) -> bool:
return self._slither_parser.is_compact_ast
@property
def compilation_unit(self) -> SlitherCompilationUnit:
return self._slither_parser.compilation_unit
def get_key(self) -> str:
return self._slither_parser.get_key()
@property
def slither_parser(self) -> "SlitherCompilationUnitSolc":
return self._slither_parser
@property
def underlying_event(self) -> EventTopLevel:
return self._event

@ -68,6 +68,7 @@ class FunctionSolc(CallerContextExpression):
else:
self._function.name = function_data["attributes"][self.get_key()]
self._functionNotParsed = function_data
self._returnsNotParsed: List[dict] = []
self._params_was_analyzed = False
self._content_was_analyzed = False
@ -207,8 +208,6 @@ class FunctionSolc(CallerContextExpression):
else:
attributes = self._functionNotParsed["attributes"]
if "payable" in attributes:
self._function.payable = attributes["payable"]
if "stateMutability" in attributes:
if attributes["stateMutability"] == "payable":
self._function.payable = True
@ -242,6 +241,34 @@ class FunctionSolc(CallerContextExpression):
if "payable" in attributes:
self._function.payable = attributes["payable"]
if "baseFunctions" in attributes:
overrides_ids = attributes["baseFunctions"]
if len(overrides_ids) > 0:
for f_id in overrides_ids:
funcs = self.slither_parser.functions_by_id[f_id]
for f in funcs:
# Do not consider leaf contracts as overrides.
# B is A { function a() override {} } and C is A { function a() override {} } override A.a(), not each other.
if (
f.contract == self._function.contract
or f.contract in self._function.contract.inheritance
):
self._function.overrides.append(f)
f.overridden_by.append(self._function)
# Attaches reference to override specifier e.g. X is referenced by `function a() override(X)`
if "overrides" in attributes and isinstance(attributes["overrides"], dict):
for override in attributes["overrides"].get("overrides", []):
refId = override["referencedDeclaration"]
overridden_contract = self.slither_parser.contracts_by_id.get(refId, None)
if overridden_contract:
overridden_contract.add_reference_from_raw_source(
override["src"], self.compilation_unit
)
if "virtual" in attributes:
self._function.is_virtual = attributes["virtual"]
def analyze_params(self) -> None:
# Can be re-analyzed due to inheritance
if self._params_was_analyzed:
@ -280,6 +307,7 @@ class FunctionSolc(CallerContextExpression):
if self.is_compact_ast:
body = self._functionNotParsed.get("body", None)
return_params = self._functionNotParsed.get("returnParameters", None)
if body and body[self.get_key()] == "Block":
self._function.is_implemented = True
@ -290,6 +318,7 @@ class FunctionSolc(CallerContextExpression):
else:
children = self._functionNotParsed[self.get_children("children")]
return_params = children[1]
self._function.is_implemented = False
for child in children[2:]:
if child[self.get_key()] == "Block":
@ -315,6 +344,9 @@ class FunctionSolc(CallerContextExpression):
self._remove_alone_endif()
if return_params:
self._fix_implicit_return(return_params)
if self._function.entry_point:
self._update_reachability(self._function.entry_point)
@ -428,7 +460,7 @@ class FunctionSolc(CallerContextExpression):
return node_endWhile
def _parse_for_compact_ast( # pylint: disable=no-self-use
def _parse_for_compact_ast(
self, statement: Dict
) -> Tuple[Optional[Dict], Optional[Dict], Optional[Dict], Dict]:
body = statement["body"]
@ -637,7 +669,6 @@ class FunctionSolc(CallerContextExpression):
link_underlying_nodes(node_condition, node_endDoWhile)
return node_endDoWhile
# pylint: disable=no-self-use
def _construct_try_expression(self, externalCall: Dict, parameters_list: Dict) -> Dict:
# if the parameters are more than 1 we make the leftHandSide of the Assignment node
# a TupleExpression otherwise an Identifier
@ -818,19 +849,26 @@ class FunctionSolc(CallerContextExpression):
new_node = self._parse_variable_definition_init_tuple(
new_statement, i, new_node, scope
)
else:
variables.append(None)
i = i + 1
var_identifiers = []
# craft of the expression doing the assignement
for v in variables:
identifier = {
"nodeType": "Identifier",
"src": v["src"],
"name": v["name"],
"referencedDeclaration": v["id"],
"typeDescriptions": {"typeString": v["typeDescriptions"]["typeString"]},
}
var_identifiers.append(identifier)
if v is not None:
identifier = {
"nodeType": "Identifier",
"src": v["src"],
"name": v["name"],
"referencedDeclaration": v["id"],
"typeDescriptions": {
"typeString": v["typeDescriptions"]["typeString"]
},
}
var_identifiers.append(identifier)
else:
var_identifiers.append(None)
tuple_expression = {
"nodeType": "TupleExpression",
@ -996,7 +1034,9 @@ class FunctionSolc(CallerContextExpression):
if "operations" in statement:
asm_node.underlying_node.add_inline_asm(statement["operations"])
link_underlying_nodes(node, asm_node)
node = asm_node
end_assembly = self._new_node(NodeType.ENDASSEMBLY, statement["src"], scope)
link_underlying_nodes(asm_node, end_assembly)
node = end_assembly
elif name == "DoWhileStatement":
node = self._parse_dowhile(statement, node, scope)
# For Continue / Break / Return / Throw
@ -1163,16 +1203,38 @@ class FunctionSolc(CallerContextExpression):
return None
def _find_start_loop(self, node: Node, visited: List[Node]) -> Optional[Node]:
def _find_if_loop(self, node: Node, visited: List[Node], skip_if_loop: int) -> Optional[Node]:
if node in visited:
return None
# If skip_if_loop is not 0 it means we are in a nested situation
# and we have to skip the closer n loop headers.
# If in the fathers there is an EXPRESSION node it's a for loop
# and it's the index increment expression
if node.type == NodeType.IFLOOP:
if skip_if_loop == 0:
for father in node.fathers:
if father.type == NodeType.EXPRESSION:
return father
return node
# skip_if_loop works as explained above.
# This handle when a for loop doesn't have a condition
# e.g. for (;;) {}
# and decrement skip_if_loop since we are out of the nested loop
if node.type == NodeType.STARTLOOP:
return node
if skip_if_loop == 0:
return node
skip_if_loop -= 1
# If we find an ENDLOOP means we are in a nested loop
# we increment skip_if_loop counter
if node.type == NodeType.ENDLOOP:
skip_if_loop += 1
visited = visited + [node]
for father in node.fathers:
ret = self._find_start_loop(father, visited)
ret = self._find_if_loop(father, visited, skip_if_loop)
if ret:
return ret
@ -1195,15 +1257,22 @@ class FunctionSolc(CallerContextExpression):
end_node.add_father(node)
def _fix_continue_node(self, node: Node) -> None:
start_node = self._find_start_loop(node, [])
if_loop_node = self._find_if_loop(node, [], 0)
if not start_node:
if not if_loop_node:
raise ParsingError(f"Continue in no-loop context {node.node_id}")
for son in node.sons:
son.remove_father(node)
node.set_sons([start_node])
start_node.add_father(node)
node.set_sons([if_loop_node])
if_loop_node.add_father(node)
# endregion
###################################################################################
###################################################################################
# region Try-Catch
###################################################################################
###################################################################################
def _fix_try(self, node: Node) -> None:
end_node = next((son for son in node.sons if son.type != NodeType.CATCH), None)
@ -1221,6 +1290,13 @@ class FunctionSolc(CallerContextExpression):
visited.add(son)
self._fix_catch(son, end_node, visited)
# endregion
###################################################################################
###################################################################################
# region Params, Returns, Modifiers
###################################################################################
###################################################################################
def _add_param(self, param: Dict, initialized: bool = False) -> LocalVariableSolc:
local_var = LocalVariable()
@ -1274,11 +1350,11 @@ class FunctionSolc(CallerContextExpression):
self._function.returns_src().set_offset(returns["src"], self._function.compilation_unit)
if self.is_compact_ast:
returns = returns["parameters"]
self._returnsNotParsed = returns["parameters"]
else:
returns = returns[self.get_children("children")]
self._returnsNotParsed = returns[self.get_children("children")]
for ret in returns:
for ret in self._returnsNotParsed:
assert ret[self.get_key()] == "VariableDeclaration"
local_var = self._add_param(ret)
self._function.add_return(local_var.underlying_variable)
@ -1332,6 +1408,138 @@ class FunctionSolc(CallerContextExpression):
)
)
def _fix_implicit_return(self, return_params: Dict) -> None:
"""
Creates an artificial return node iff a function has a named return variable declared in its signature.
Finds all leaf nodes in the CFG which are not return nodes, and links them to the artificial return node.
"""
does_not_have_return_params = len(self.underlying_function.returns) == 0
does_not_have_named_returns = all(
ret.name == "" for ret in self.underlying_function.returns
)
not_implemented = not self._function.is_implemented
if does_not_have_return_params or does_not_have_named_returns or not_implemented:
return
return_node = self._new_node(
NodeType.RETURN, return_params["src"], self.underlying_function
)
for node, node_solc in self._node_to_nodesolc.items():
if len(node.sons) == 0 and node.type not in [NodeType.RETURN, NodeType.THROW]:
link_underlying_nodes(node_solc, return_node)
for _, yul_block in self._node_to_yulobject.items():
for yul_node in yul_block.nodes:
node = yul_node.underlying_node
if len(node.sons) == 0 and node.type not in [NodeType.RETURN, NodeType.THROW]:
link_underlying_nodes(yul_node, return_node)
if self.is_compact_ast:
self._add_return_exp_compact(return_node, return_params)
else:
self._add_return_exp_legacy(return_node, return_params)
return_node.analyze_expressions(self)
def _add_return_exp_compact(self, return_node: NodeSolc, return_params: Dict) -> None:
if len(self.underlying_function.returns) == 1:
return_arg = self.underlying_function.returns[0]
if return_arg.name != "":
(refId, refSrc, refType) = next(
(ret["id"], ret["src"], ret["typeDescriptions"])
for ret in self._returnsNotParsed
if ret["name"] == return_arg.name
)
return_node.add_unparsed_expression(
{
"name": return_arg.name,
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": refId,
"src": refSrc,
"typeDescriptions": refType,
}
)
else:
expression = {
"components": [],
"isConstant": False,
"isInlineArray": False,
"isLValue": False,
"isPure": False,
"lValueRequested": False,
"nodeType": "TupleExpression",
"src": return_params["src"],
"typeDescriptions": {},
}
type_ids = []
type_strs = []
for return_arg in self.underlying_function.returns:
# For each named return variable, we add an identifier to the tuple.
if return_arg.name != "":
(refId, refSrc, refType) = next(
(ret["id"], ret["src"], ret["typeDescriptions"])
for ret in self._returnsNotParsed
if ret["name"] == return_arg.name
)
type_ids.append(refType["typeIdentifier"])
type_strs.append(refType["typeString"])
expression["components"].append(
{
"name": return_arg.name,
"nodeType": "Identifier",
"overloadedDeclarations": [],
"referencedDeclaration": refId,
"src": refSrc,
"typeDescriptions": refType,
}
)
expression["typeDescriptions"]["typeIdentifier"] = (
"t_tuple$_" + "_$_".join(type_ids) + "_$"
)
expression["typeDescriptions"]["typeString"] = "tuple(" + ",".join(type_strs) + ")"
return_node.add_unparsed_expression(expression)
def _add_return_exp_legacy(self, return_node: NodeSolc, return_params: Dict) -> None:
if len(self.underlying_function.returns) == 1:
return_arg = self.underlying_function.returns[0]
if return_arg.name != "":
(refSrc, refType) = next(
(ret["src"], ret["attributes"]["type"])
for ret in self._returnsNotParsed
if ret["attributes"]["name"] == return_arg.name
)
return_node.add_unparsed_expression(
{
"attributes": {"type": refType, "value": return_arg.name},
"name": "Identifier",
"src": refSrc,
}
)
else:
expression = {
"children": [],
"name": "TupleExpression",
"src": return_params["src"],
}
for return_arg in self.underlying_function.returns:
# For each named return variable, we add an identifier to the tuple.
if return_arg.name != "":
(refSrc, refType) = next(
(ret["src"], ret["attributes"]["type"])
for ret in self._returnsNotParsed
if ret["attributes"]["name"] == return_arg.name
)
expression["children"].append(
{
"attributes": {"type": refType, "value": return_arg.name},
"name": "Identifier",
"src": refSrc,
}
)
return_node.add_unparsed_expression(expression)
# endregion
###################################################################################
###################################################################################

@ -93,6 +93,8 @@ class ModifierSolc(FunctionSolc):
self._rewrite_ternary_as_if_else()
self._remove_alone_endif()
if self._function.entry_point:
self._update_reachability(self._function.entry_point)
# self._analyze_read_write()
# self._analyze_calls()

@ -614,20 +614,9 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
assert type_name[caller_context.get_key()] == "UserDefinedTypeName"
if is_compact_ast:
# Changed introduced in Solidity 0.8
# see https://github.com/crytic/slither/issues/794
# TODO explore more the changes introduced in 0.8 and the usage of pathNode/IdentifierPath
if "name" not in type_name:
assert "pathNode" in type_name and "name" in type_name["pathNode"]
contract_name = type_name["pathNode"]["name"]
else:
contract_name = type_name["name"]
else:
contract_name = type_name["attributes"]["name"]
new = NewContract(contract_name)
contract_type = parse_type(type_name, caller_context)
assert isinstance(contract_type, UserDefinedType)
new = NewContract(contract_type)
new.set_offset(src, caller_context.compilation_unit)
return new

@ -134,6 +134,10 @@ def find_top_level(
if var_name in scope.enums:
return scope.enums[var_name], False
for event in scope.events:
if var_name == event.full_name:
return event, False
for import_directive in scope.imports:
if import_directive.alias == var_name:
new_val = SolidityImportPlaceHolder(import_directive)
@ -265,6 +269,7 @@ def _find_variable_init(
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
direct_contracts: List[Contract]
@ -308,6 +313,10 @@ def _find_variable_init(
direct_contracts = []
direct_functions_parser = []
scope = caller_context.underlying_variable.file_scope
elif isinstance(caller_context, EventTopLevelSolc):
direct_contracts = []
direct_functions_parser = []
scope = caller_context.underlying_event.file_scope
elif isinstance(caller_context, CustomErrorSolc):
if caller_context.contract_parser:
direct_contracts = [caller_context.contract_parser.underlying_contract]

@ -1,3 +1,4 @@
from collections import defaultdict
import json
import logging
import os
@ -7,9 +8,10 @@ from typing import List, Dict
from slither.analyses.data_dependency.data_dependency import compute_dependency
from slither.core.compilation_unit import SlitherCompilationUnit
from slither.core.declarations import Contract
from slither.core.declarations import Contract, Function
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.import_directive import Import
from slither.core.declarations.pragma_directive import Pragma
@ -23,6 +25,7 @@ from slither.solc_parsing.declarations.caller_context import CallerContextExpres
from slither.solc_parsing.declarations.contract import ContractSolc
from slither.solc_parsing.declarations.custom_error import CustomErrorSolc
from slither.solc_parsing.declarations.function import FunctionSolc
from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.declarations.using_for_top_level import UsingForTopLevelSolc
from slither.solc_parsing.exceptions import VariableNotFound
@ -71,13 +74,14 @@ def _handle_import_aliases(
class SlitherCompilationUnitSolc(CallerContextExpression):
# pylint: disable=no-self-use,too-many-instance-attributes
# pylint: disable=too-many-instance-attributes
def __init__(self, compilation_unit: SlitherCompilationUnit) -> None:
super().__init__()
self._compilation_unit: SlitherCompilationUnit = compilation_unit
self._contracts_by_id: Dict[int, ContractSolc] = {}
self._contracts_by_id: Dict[int, Contract] = {}
self._functions_by_id: Dict[int, List[Function]] = defaultdict(list)
self._parsed = False
self._analyzed = False
self._is_compact_ast = False
@ -88,6 +92,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._variables_top_level_parser: List[TopLevelVariableSolc] = []
self._functions_top_level_parser: List[FunctionSolc] = []
self._using_for_top_level_parser: List[UsingForTopLevelSolc] = []
self._events_top_level_parser: List[EventTopLevelSolc] = []
self._all_functions_and_modifier_parser: List[FunctionSolc] = []
self._top_level_contracts_counter = 0
@ -102,6 +107,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
def add_function_or_modifier_parser(self, f: FunctionSolc) -> None:
self._all_functions_and_modifier_parser.append(f)
self._functions_by_id[f.underlying_function.id].append(f.underlying_function)
@property
def underlying_contract_to_parser(self) -> Dict[Contract, ContractSolc]:
@ -111,6 +117,14 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
def slither_parser(self) -> "SlitherCompilationUnitSolc":
return self
@property
def contracts_by_id(self) -> Dict[int, Contract]:
return self._contracts_by_id
@property
def functions_by_id(self) -> Dict[int, List[Function]]:
return self._functions_by_id
###################################################################################
###################################################################################
# region AST
@ -347,6 +361,15 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
self._compilation_unit.type_aliases[alias] = type_alias
scope.type_aliases[alias] = type_alias
elif top_level_data[self.get_key()] == "EventDefinition":
event = EventTopLevel(scope)
event.set_offset(top_level_data["src"], self._compilation_unit)
event_parser = EventTopLevelSolc(event, top_level_data, self) # type: ignore
self._events_top_level_parser.append(event_parser)
scope.events.add(event)
self._compilation_unit.events_top_level.append(event)
else:
raise SlitherException(f"Top level {top_level_data[self.get_key()]} not supported")
@ -406,6 +429,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
f"No contracts were found in {self._compilation_unit.core.filename}, check the correct compilation"
)
if self._parsed:
# pylint: disable=broad-exception-raised
raise Exception("Contract analysis can be run only once!")
# First we save all the contracts in a dict
@ -421,61 +445,70 @@ Please rename it, this name is reserved for Slither's internals"""
self._contracts_by_id[contract.id] = contract
self._compilation_unit.contracts.append(contract)
def resolve_remapping_and_renaming(contract_parser: ContractSolc, want: str) -> Contract:
contract_name = contract_parser.remapping[want]
target = None
# For contracts that are imported and aliased e.g. 'import {A as B} from "./C.sol"',
# we look through the imports's (`Import`) renaming to find the original contract name
# and then look up the original contract in the import path's scope (`FileScope`).
for import_ in contract_parser.underlying_contract.file_scope.imports:
if contract_name in import_.renaming:
target = self.compilation_unit.get_scope(
import_.filename
).get_contract_from_name(import_.renaming[contract_name])
# Fallback to the current file scope if the contract is not found in the import path's scope.
# It is assumed that it isn't possible to defined a contract with the same name as "aliased" names.
if target is None:
target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_name
)
if target == contract_parser.underlying_contract:
raise InheritanceResolutionError(
"Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name as a workaround."
"\n Please share the source code that caused this error here: https://github.com/crytic/slither/issues/"
)
assert target, f"Contract {contract_name} not found"
return target
# Update of the inheritance
for contract_parser in self._underlying_contract_to_parser.values():
# remove the first elem in linearizedBaseContracts as it is the contract itself
ancestors = []
fathers = []
father_constructors = []
# try:
# Resolve linearized base contracts.
missing_inheritance = None
# Resolve linearized base contracts.
# Remove the first elem in linearizedBaseContracts as it is the contract itself.
for i in contract_parser.linearized_base_contracts[1:]:
if i in contract_parser.remapping:
contract_name = contract_parser.remapping[i]
if contract_name in contract_parser.underlying_contract.file_scope.renaming:
contract_name = contract_parser.underlying_contract.file_scope.renaming[
contract_name
]
target = contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_name
)
if target == contract_parser.underlying_contract:
raise InheritanceResolutionError(
"Could not resolve contract inheritance. This is likely caused by an import renaming that collides with existing names (see https://github.com/crytic/slither/issues/1758)."
f"\n Try changing `contract {target}` ({target.source_mapping}) to a unique name."
)
assert target, f"Contract {contract_name} not found"
target = resolve_remapping_and_renaming(contract_parser, i)
ancestors.append(target)
elif i in self._contracts_by_id:
ancestors.append(self._contracts_by_id[i])
else:
missing_inheritance = i
# Resolve immediate base contracts
for i in contract_parser.baseContracts:
# Resolve immediate base contracts and attach references.
for (i, src) in contract_parser.baseContracts:
if i in contract_parser.remapping:
fathers.append(
contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_parser.remapping[i]
)
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i])
)
target = resolve_remapping_and_renaming(contract_parser, i)
fathers.append(target)
target.add_reference_from_raw_source(src, self.compilation_unit)
elif i in self._contracts_by_id:
fathers.append(self._contracts_by_id[i])
target = self._contracts_by_id[i]
fathers.append(target)
target.add_reference_from_raw_source(src, self.compilation_unit)
else:
missing_inheritance = i
# Resolve immediate base constructor calls
# Resolve immediate base constructor calls.
for i in contract_parser.baseConstructorContractsCalled:
if i in contract_parser.remapping:
father_constructors.append(
contract_parser.underlying_contract.file_scope.get_contract_from_name(
contract_parser.remapping[i]
)
# self._compilation_unit.get_contract_from_name(contract_parser.remapping[i])
)
target = resolve_remapping_and_renaming(contract_parser, i)
father_constructors.append(target)
elif i in self._contracts_by_id:
father_constructors.append(self._contracts_by_id[i])
else:
@ -594,6 +627,7 @@ Please rename it, this name is reserved for Slither's internals"""
self._analyze_top_level_variables()
self._analyze_top_level_structures()
self._analyze_top_level_events()
# Start with the contracts without inheritance
# Analyze a contract only if all its fathers
@ -704,6 +738,13 @@ Please rename it, this name is reserved for Slither's internals"""
except (VariableNotFound, KeyError) as e:
raise SlitherException(f"Missing {e} during variable analyze") from e
def _analyze_top_level_events(self) -> None:
try:
for event in self._events_top_level_parser:
event.analyze()
except (VariableNotFound, KeyError) as e:
raise SlitherException(f"Missing event {e} during top level event analyze") from e
def _analyze_params_top_level_function(self) -> None:
for func_parser in self._functions_top_level_parser:
func_parser.analyze_params()

@ -232,6 +232,7 @@ def parse_type(
from slither.solc_parsing.declarations.structure_top_level import StructureTopLevelSolc
from slither.solc_parsing.slither_compilation_unit_solc import SlitherCompilationUnitSolc
from slither.solc_parsing.variables.top_level_variable import TopLevelVariableSolc
from slither.solc_parsing.declarations.event_top_level import EventTopLevelSolc
sl: "SlitherCompilationUnit"
renaming: Dict[str, str]
@ -266,7 +267,13 @@ def parse_type(
functions = []
elif isinstance(
caller_context,
(StructureTopLevelSolc, CustomErrorSolc, TopLevelVariableSolc, UsingForTopLevelSolc),
(
StructureTopLevelSolc,
CustomErrorSolc,
TopLevelVariableSolc,
UsingForTopLevelSolc,
EventTopLevelSolc,
),
):
if isinstance(caller_context, StructureTopLevelSolc):
scope = caller_context.underlying_structure.file_scope
@ -274,6 +281,8 @@ def parse_type(
scope = caller_context.underlying_variable.file_scope
elif isinstance(caller_context, UsingForTopLevelSolc):
scope = caller_context.underlying_using_for.file_scope
elif isinstance(caller_context, EventTopLevelSolc):
scope = caller_context.underlying_event.file_scope
else:
assert isinstance(caller_context, CustomErrorSolc)
custom_error = caller_context.underlying_custom_error

@ -1,7 +1,7 @@
from slither.core.declarations.solidity_variables import SOLIDITY_FUNCTIONS
from slither.core.expressions import BinaryOperationType, UnaryOperationType
# taken from https://github.com/ethereum/solidity/blob/356cc91084114f840da66804b2a9fc1ac2846cff/libevmasm/Instruction.cpp#L180
# taken from https://github.com/ethereum/solidity/blob/e11b9ed9f2c254bc894d844c0a64a0eb76bbb4fd/libevmasm/Instruction.cpp#L184
evm_opcodes = [
"STOP",
"ADD",
@ -45,6 +45,7 @@ evm_opcodes = [
"EXTCODECOPY",
"RETURNDATASIZE",
"RETURNDATACOPY",
"MCOPY",
"EXTCODEHASH",
"BLOCKHASH",
"COINBASE",
@ -55,12 +56,17 @@ evm_opcodes = [
"GASLIMIT",
"CHAINID",
"SELFBALANCE",
"BASEFEE",
"BLOBHASH",
"BLOBBASEFEE",
"POP",
"MLOAD",
"MSTORE",
"MSTORE8",
"SLOAD",
"SSTORE",
"TLOAD",
"TSTORE",
"JUMP",
"JUMPI",
"PC",
@ -183,11 +189,16 @@ function_args = {
"mstore8": [2, 0],
"sload": [1, 1],
"sstore": [2, 0],
"tload": [1, 1],
"tstore": [2, 0],
"msize": [1, 1],
"gas": [0, 1],
"address": [0, 1],
"balance": [1, 1],
"selfbalance": [0, 1],
"basefee": [0, 1],
"blobhash": [1, 1],
"blobbasefee": [0, 1],
"caller": [0, 1],
"callvalue": [0, 1],
"calldataload": [1, 1],
@ -199,6 +210,7 @@ function_args = {
"extcodecopy": [4, 0],
"returndatasize": [0, 1],
"returndatacopy": [3, 0],
"mcopy": [3, 0],
"extcodehash": [1, 1],
"create": [3, 1],
"create2": [4, 1],

@ -321,6 +321,10 @@ class YulBlock(YulScope):
def function(self) -> Function:
return self._parent_func
@property
def nodes(self) -> List[YulNode]:
return self._nodes
def new_node(self, node_type: NodeType, src: Union[str, Dict]) -> YulNode:
if self._parent_func:
node = self._parent_func.new_node(node_type, src, self.node_scope)

@ -21,7 +21,10 @@ def parse_args() -> argparse.Namespace:
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
"""
parser = argparse.ArgumentParser(description="Demo", usage="slither-documentation filename")
parser = argparse.ArgumentParser(
description="Auto-generate NatSpec documentation for every function using OpenAI Codex.",
usage="slither-documentation filename",
)
parser.add_argument("project", help="The target directory/Solidity file.")

@ -77,6 +77,7 @@ class Flattening:
self._get_source_code_top_level(compilation_unit.structures_top_level)
self._get_source_code_top_level(compilation_unit.enums_top_level)
self._get_source_code_top_level(compilation_unit.events_top_level)
self._get_source_code_top_level(compilation_unit.custom_errors)
self._get_source_code_top_level(compilation_unit.variables_top_level)
self._get_source_code_top_level(compilation_unit.functions_top_level)

@ -68,13 +68,13 @@ def parse_args() -> argparse.Namespace:
:return: Returns the arguments for the program.
"""
parser = argparse.ArgumentParser(
description="Demo",
usage="slither-demo filename",
description="Generates code properties (e.g., invariants) that can be tested with unit tests or Echidna, entirely automatically.",
usage="slither-prop filename",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"filename", help="The filename of the contract or truffle directory to analyze."
"filename", help="The filename of the contract or project directory to analyze."
)
parser.add_argument("--contract", help="The targeted contract.")

@ -398,7 +398,7 @@ class SlitherReadStorage:
for contract in self.contracts:
for var in contract.state_variables_ordered:
if func(var):
if not var.is_constant and not var.is_immutable:
if var.is_stored:
self._target_variables.append((contract, var))
elif (
self.unstructured

@ -43,8 +43,8 @@ Using initialize functions to write initial values in state variables.
def _check(self) -> List[Output]:
results = []
for s in self.contract.state_variables_ordered:
if s.initialized and not (s.is_constant or s.is_immutable):
for s in self.contract.stored_state_variables_ordered:
if s.initialized:
info: CHECK_INFO = [s, " is a state variable with an initial value.\n"]
json = self.generate_result(info)
results.append(json)

@ -115,16 +115,8 @@ Avoid variables in the proxy. If a variable is in the proxy, ensure it has the s
def _check(self) -> List[Output]:
contract1 = self._contract1()
contract2 = self._contract2()
order1 = [
variable
for variable in contract1.state_variables_ordered
if not (variable.is_constant or variable.is_immutable)
]
order2 = [
variable
for variable in contract2.state_variables_ordered
if not (variable.is_constant or variable.is_immutable)
]
order1 = contract1.stored_state_variables_ordered
order2 = contract2.stored_state_variables_ordered
results: List[Output] = []
for idx, _ in enumerate(order1):
@ -244,16 +236,8 @@ Avoid variables in the proxy. If a variable is in the proxy, ensure it has the s
def _check(self) -> List[Output]:
contract1 = self._contract1()
contract2 = self._contract2()
order1 = [
variable
for variable in contract1.state_variables_ordered
if not (variable.is_constant or variable.is_immutable)
]
order2 = [
variable
for variable in contract2.state_variables_ordered
if not (variable.is_constant or variable.is_immutable)
]
order1 = contract1.stored_state_variables_ordered
order2 = contract2.stored_state_variables_ordered
results = []

@ -60,6 +60,7 @@ defaults_flag_in_config = {
"json-types": ",".join(DEFAULT_JSON_OUTPUT_TYPES),
"disable_color": False,
"filter_paths": None,
"include_paths": None,
"generate_patches": False,
# debug command
"skip_assembly": False,
@ -70,6 +71,7 @@ defaults_flag_in_config = {
"no_fail": False,
"sarif_input": "export.sarif",
"sarif_triage": "export.sarif.sarifexplorer",
"triage_database": "slither.db.json",
**DEFAULTS_FLAG_IN_CONFIG_CRYTIC_COMPILE,
}

@ -71,7 +71,7 @@ def encode_var_for_compare(var: Union[variables.Variable, SolidityVariable]) ->
if isinstance(var, variables.LocalVariable):
return f"local_solc_variable({ntype(var.type)},{var.location})"
if isinstance(var, variables.StateVariable):
if not (var.is_constant or var.is_immutable):
if var.is_stored:
try:
slot, _ = var.contract.compilation_unit.storage_layout_of(var.contract, var)
except KeyError:

@ -1,7 +1,10 @@
from typing import List, Dict, Union
from prettytable import PrettyTable
from prettytable.colortable import ColorTable, Themes
from slither.utils.colors import Colors
class MyPrettyTable:
def __init__(self, field_names: List[str], pretty_align: bool = True): # TODO: True by default?
@ -19,8 +22,12 @@ class MyPrettyTable:
def add_row(self, row: List[Union[str, List[str]]]) -> None:
self._rows.append(row)
def to_pretty_table(self) -> ColorTable:
table = ColorTable(self._field_names, theme=Themes.OCEAN)
def to_pretty_table(self) -> PrettyTable:
if Colors.COLORIZATION_ENABLED:
table = ColorTable(self._field_names, theme=Themes.OCEAN)
else:
table = PrettyTable(self._field_names)
for row in self._rows:
table.add_row(row)
if len(self._options["set_alignment"]):

@ -4,10 +4,10 @@ import logging
import os
import zipfile
from collections import OrderedDict
from importlib import metadata
from typing import Tuple, Optional, Dict, List, Union, Any, TYPE_CHECKING, Type
from zipfile import ZipFile
from pkg_resources import require
from slither.core.cfg.node import Node
from slither.core.declarations import (
@ -161,7 +161,7 @@ def output_to_sarif(
"driver": {
"name": "Slither",
"informationUri": "https://github.com/crytic/slither",
"version": require("slither-analyzer")[0].version,
"version": metadata.version("slither-analyzer"),
"rules": [],
}
},

@ -1,7 +1,17 @@
from typing import List
from typing import List, Set
from crytic_compile import CryticCompile
from slither.core.declarations import Contract, Function, Enum, Event, Import, Pragma, Structure
from slither.core.solidity_types.type import Type
from slither.core.declarations import (
Contract,
Function,
Enum,
Event,
Import,
Pragma,
Structure,
CustomError,
FunctionContract,
)
from slither.core.solidity_types import Type, TypeAlias
from slither.core.source_mapping.source_mapping import Source, SourceMapping
from slither.core.variables.variable import Variable
from slither.exceptions import SlitherError
@ -15,6 +25,10 @@ def get_definition(target: SourceMapping, crytic_compile: CryticCompile) -> Sour
pattern = "import"
elif isinstance(target, Pragma):
pattern = "pragma" # todo maybe return with the while pragma statement
elif isinstance(target, CustomError):
pattern = "error"
elif isinstance(target, TypeAlias):
pattern = "type"
elif isinstance(target, Type):
raise SlitherError("get_definition_generic not implemented for types")
else:
@ -52,5 +66,34 @@ def get_implementation(target: SourceMapping) -> Source:
return target.source_mapping
def get_all_implementations(target: SourceMapping, contracts: List[Contract]) -> Set[Source]:
"""
Get all implementations of a contract or function, accounting for inheritance and overrides
"""
implementations = set()
# Abstract contracts and interfaces are implemented by their children
if isinstance(target, Contract):
is_interface = target.is_interface
is_implicitly_abstract = not target.is_fully_implemented
is_explicitly_abstract = target.is_abstract
if is_interface or is_implicitly_abstract or is_explicitly_abstract:
for contract in contracts:
if target in contract.immediate_inheritance:
implementations.add(contract.source_mapping)
# Parent's virtual functions may be overridden by children
elif isinstance(target, FunctionContract):
for over in target.overridden_by:
implementations.add(over.source_mapping)
# Only show implemented virtual functions
if not target.is_virtual or target.is_implemented:
implementations.add(get_implementation(target))
else:
implementations.add(get_implementation(target))
return implementations
def get_references(target: SourceMapping) -> List[Source]:
return target.references

@ -60,7 +60,6 @@ def compare(
List[Function],
List[Function],
List[Function],
List[TaintedExternalContract],
]:
"""
Compares two versions of a contract. Most useful for upgradeable (logic) contracts,
@ -81,12 +80,8 @@ def compare(
tainted-contracts: list[TaintedExternalContract]
"""
order_vars1 = [
v for v in v1.state_variables_ordered if not v.is_constant and not v.is_immutable
]
order_vars2 = [
v for v in v2.state_variables_ordered if not v.is_constant and not v.is_immutable
]
order_vars1 = v1.stored_state_variables_ordered
order_vars2 = v2.stored_state_variables_ordered
func_sigs1 = [function.solidity_signature for function in v1.functions]
func_sigs2 = [function.solidity_signature for function in v2.functions]
@ -206,7 +201,7 @@ def tainted_external_contracts(funcs: List[Function]) -> List[TaintedExternalCon
elif (
isinstance(target, StateVariable)
and target not in (v for v in tainted_contracts[contract.name].tainted_variables)
and not (target.is_constant or target.is_immutable)
and target.is_stored
):
# Found a new high-level call to a public state variable getter
tainted_contracts[contract.name].add_tainted_variable(target)
@ -304,12 +299,8 @@ def get_missing_vars(v1: Contract, v2: Contract) -> List[StateVariable]:
List of StateVariables from v1 missing in v2
"""
results = []
order_vars1 = [
v for v in v1.state_variables_ordered if not v.is_constant and not v.is_immutable
]
order_vars2 = [
v for v in v2.state_variables_ordered if not v.is_constant and not v.is_immutable
]
order_vars1 = v1.stored_state_variables_ordered
order_vars2 = v2.stored_state_variables_ordered
if len(order_vars2) < len(order_vars1):
for variable in order_vars1:
if variable.name not in [v.name for v in order_vars2]:
@ -366,7 +357,7 @@ def get_proxy_implementation_slot(proxy: Contract) -> Optional[SlotInfo]:
delegate = get_proxy_implementation_var(proxy)
if isinstance(delegate, StateVariable):
if not delegate.is_constant and not delegate.is_immutable:
if delegate.is_stored:
srs = SlitherReadStorage([proxy], 20)
return srs.get_storage_slot(delegate, proxy)
if delegate.is_constant and delegate.type.name == "bytes32":
@ -400,7 +391,7 @@ def get_proxy_implementation_var(proxy: Contract) -> Optional[Variable]:
try:
delegate = next(var for var in dependencies if isinstance(var, StateVariable))
except StopIteration:
# TODO: Handle cases where get_dependencies doesn't return any state variables.
# TODO: Handle case where get_dependencies does not return any state variables.
return delegate
return delegate

@ -0,0 +1,17 @@
from typing import Dict, List, TYPE_CHECKING, Union
from slither.core.solidity_types.type import Type
if TYPE_CHECKING:
from slither.core.declarations import Function
USING_FOR_KEY = Union[str, Type]
USING_FOR_ITEM = List[Union[Type, "Function"]]
USING_FOR = Dict[USING_FOR_KEY, USING_FOR_ITEM]
def _merge_using_for(uf1: USING_FOR, uf2: USING_FOR) -> USING_FOR:
result = {**uf1, **uf2}
for key, value in result.items():
if key in uf1 and key in uf2:
result[key] = value + uf1[key]
return result

@ -210,12 +210,6 @@ class ExpressionToSlithIR(ExpressionVisitor):
for idx, _ in enumerate(left):
if not left[idx] is None:
index = idx
# The following test is probably always true?
if (
isinstance(left[idx], LocalVariableInitFromTuple)
and left[idx].tuple_index is not None
):
index = left[idx].tuple_index
operation = Unpack(left[idx], right, index)
operation.set_expression(expression)
self._result.append(operation)
@ -233,15 +227,16 @@ class ExpressionToSlithIR(ExpressionVisitor):
self._result.append(operation)
set_val(expression, None)
else:
# Init of array, like
# uint8[2] var = [1,2];
# For `InitArray`, the rhs is a list or singleton of `TupleExpression` elements.
# Init of array e.g. uint8[2] var = [1,2];
if isinstance(right, list):
operation = InitArray(right, left)
operation.set_expression(expression)
self._result.append(operation)
set_val(expression, left)
elif isinstance(left.type, ArrayType):
# Special case for init of array, when the right has only one element
# Special case for init of array, when the right has only one element e.g. arr = [1];
elif isinstance(left.type, ArrayType) and not isinstance(right.type, ArrayType):
operation = InitArray([right], left)
operation.set_expression(expression)
self._result.append(operation)
@ -276,6 +271,7 @@ class ExpressionToSlithIR(ExpressionVisitor):
self._result.append(operation)
else:
operation = convert_assignment(
left, right, expression.type, expression.expression_return_type
)
@ -436,7 +432,7 @@ class ExpressionToSlithIR(ExpressionVisitor):
set_val(expression, val)
def _post_conditional_expression(self, expression: ConditionalExpression) -> None:
raise Exception(f"Ternary operator are not convertible to SlithIR {expression}")
raise SlithIRError(f"Ternary operator are not convertible to SlithIR {expression}")
def _post_elementary_type_name_expression(
self,

@ -24,7 +24,7 @@ from slither.vyper_parsing.declarations.struct import StructVyper
from slither.vyper_parsing.variables.state_variable import StateVariableVyper
from slither.vyper_parsing.declarations.function import FunctionVyper
from slither.core.declarations.function_contract import FunctionContract
from slither.core.declarations import Contract, StructureContract, EnumContract, Event
from slither.core.declarations import Contract, StructureContract, EnumContract, EventContract
from slither.core.variables.state_variable import StateVariable
@ -478,7 +478,7 @@ class ContractVyper: # pylint: disable=too-many-instance-attributes
def parse_events(self) -> None:
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)

@ -153,10 +153,10 @@ def parse_expression(
parsed_expr.set_offset(expression.src, caller_context.compilation_unit)
return parsed_expr
# `raw_call` and `send` are treated specially in order to force `extract_tmp_call` to treat this as a `HighLevelCall` which will be converted
# to a `LowLevelCall` by `convert_to_low_level`. This is an artifact of the late conversion of Solidity...
if called.value.name == "raw_call()":
args = [parse_expression(a, caller_context) for a in expression.args]
# This is treated specially in order to force `extract_tmp_call` to treat this as a `HighLevelCall` which will be converted
# to a `LowLevelCall` by `convert_to_low_level`. This is an artifact of the late conversion of Solidity...
call = CallExpression(
MemberAccess("raw_call", "tuple(bool,bytes32)", args[0]),
args[1:],
@ -183,6 +183,17 @@ def parse_expression(
return call
if called.value.name == "send()":
args = [parse_expression(a, caller_context) for a in expression.args]
call = CallExpression(
MemberAccess("send", "tuple()", args[0]),
args[1:],
"tuple()",
)
call.set_offset(expression.src, caller_context.compilation_unit)
return call
if expression.args and isinstance(expression.args[0], VyDict):
arguments = []
for val in expression.args[0].values:

@ -61,7 +61,7 @@ def solc_binary_path(shared_directory):
@pytest.fixture
def slither_from_solidity_source(solc_binary_path):
@contextmanager
def inner(source_code: str, solc_version: str = "0.8.19"):
def inner(source_code: str, solc_version: str = "0.8.19", legacy: bool = False):
"""Yields a Slither instance using source_code string and solc_version.
Creates a temporary file and compiles with solc_version.
"""
@ -72,7 +72,7 @@ def slither_from_solidity_source(solc_binary_path):
fname = f.name
f.write(source_code)
solc_path = solc_binary_path(solc_version)
yield Slither(fname, solc=solc_path)
yield Slither(fname, solc=solc_path, force_legacy=legacy)
finally:
Path(fname).unlink()

@ -1,3 +1,6 @@
Modifier Test.loopsNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.4.25/modifier_default.sol#30-41) does not always execute _; or revert
Modifier Test.requireAssertNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.4.25/modifier_default.sol#18-22) does not always execute _; or revert
Modifier Test.noResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.4.25/modifier_default.sol#2-6) does not always execute _; or revert

@ -1,3 +1,6 @@
Modifier Test.loopsNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.5.16/modifier_default.sol#30-41) does not always execute _; or revert
Modifier Test.requireAssertNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.5.16/modifier_default.sol#18-22) does not always execute _; or revert
Modifier Test.noResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.5.16/modifier_default.sol#2-6) does not always execute _; or revert

@ -1,3 +1,6 @@
Modifier Test.loopsNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.6.11/modifier_default.sol#30-41) does not always execute _; or revert
Modifier Test.requireAssertNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.6.11/modifier_default.sol#18-22) does not always execute _; or revert
Modifier Test.noResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.6.11/modifier_default.sol#2-6) does not always execute _; or revert

@ -1,3 +1,6 @@
Modifier Test.loopsNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.7.6/modifier_default.sol#30-41) does not always execute _; or revert
Modifier Test.requireAssertNoResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.7.6/modifier_default.sol#18-22) does not always execute _; or revert
Modifier Test.noResult() (tests/e2e/detectors/test_data/incorrect-modifier/0.7.6/modifier_default.sol#2-6) does not always execute _; or revert

@ -0,0 +1,16 @@
Multiple retryable tickets created in the same function:
-Y(msg.sender).createRetryableTicket(address(1),0,0,address(0),address(0),0,0,) (tests/e2e/detectors/test_data/out-of-order-retryable/0.8.20/out_of_order_retryable.sol#62-70)
-Y(msg.sender).createRetryableTicket(address(2),0,0,address(0),address(0),0,0,) (tests/e2e/detectors/test_data/out-of-order-retryable/0.8.20/out_of_order_retryable.sol#72-80)
Multiple retryable tickets created in the same function:
-good2() (tests/e2e/detectors/test_data/out-of-order-retryable/0.8.20/out_of_order_retryable.sol#95)
-good2() (tests/e2e/detectors/test_data/out-of-order-retryable/0.8.20/out_of_order_retryable.sol#96)
Multiple retryable tickets created in the same function:
-Y(msg.sender).createRetryableTicket(address(1),0,0,address(0),address(0),0,0,) (tests/e2e/detectors/test_data/out-of-order-retryable/0.8.20/out_of_order_retryable.sol#40-48)
-Y(msg.sender).createRetryableTicket(address(2),0,0,address(0),address(0),0,0,) (tests/e2e/detectors/test_data/out-of-order-retryable/0.8.20/out_of_order_retryable.sol#50-58)
Multiple retryable tickets created in the same function:
-Y(msg.sender).createRetryableTicket(address(1),0,0,address(0),address(0),0,0,) (tests/e2e/detectors/test_data/out-of-order-retryable/0.8.20/out_of_order_retryable.sol#83-91)
-good2() (tests/e2e/detectors/test_data/out-of-order-retryable/0.8.20/out_of_order_retryable.sol#92)

@ -1,3 +1,9 @@
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#16-22):
External calls:
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#17)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#21)
Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#41-45):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#42)
@ -6,11 +12,11 @@ Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#43)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#16-22):
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#24-28):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#17)
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#21)
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#54-58):
External calls:
@ -33,15 +39,9 @@ Reentrancy in ReentrancyBenign.bad4(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#50)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#24-28):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad2(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#30-39):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#31)
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#31)
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#33)
External calls sending eth:
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.5.16/reentrancy-benign.sol#33)

@ -1,3 +1,12 @@
Reentrancy in ReentrancyBenign.bad2(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#30-39):
External calls:
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#31)
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#33)
External calls sending eth:
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#33)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#34)
Reentrancy in ReentrancyBenign.bad4(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#47-52):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#48)
@ -19,21 +28,6 @@ Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#43)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad2(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#30-39):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#31)
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#33)
External calls sending eth:
- address(target).call.value(1000)() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#33)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#34)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#24-28):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#54-58):
External calls:
- ethSender(address(0)) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#55)
@ -44,7 +38,13 @@ Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reen
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#16-22):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#17)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#17)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#21)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#24-28):
External calls:
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.6.11/reentrancy-benign.sol#27)

@ -1,20 +1,26 @@
Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#41-45):
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#16-22):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#42)
- address(target).call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#61)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#17)
State variables written after the call(s):
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#43)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#69)
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#21)
Reentrancy in ReentrancyBenign.bad2(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#30-39):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#31)
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#31)
- address(target).call{value: 1000}() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#33)
External calls sending eth:
- address(target).call{value: 1000}() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#33)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#34)
Reentrancy in ReentrancyBenign.bad3(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#41-45):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#42)
- address(target).call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#61)
State variables written after the call(s):
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#43)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#54-58):
External calls:
- ethSender(address(0)) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#55)
@ -23,6 +29,12 @@ Reentrancy in ReentrancyBenign.bad5(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#56)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#24-28):
External calls:
- (success,None) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad4(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#47-52):
External calls:
- externalCaller(target) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#48)
@ -36,15 +48,3 @@ Reentrancy in ReentrancyBenign.bad4(address) (tests/e2e/detectors/test_data/reen
- varChanger() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#50)
- anotherVariableToChange ++ (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#69)
Reentrancy in ReentrancyBenign.bad1(address) (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#24-28):
External calls:
- (success) = target.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#25)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#27)
Reentrancy in ReentrancyBenign.bad0() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#16-22):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#17)
State variables written after the call(s):
- counter += 1 (tests/e2e/detectors/test_data/reentrancy-benign/0.7.6/reentrancy-benign.sol#21)

@ -1,6 +1,6 @@
Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#16-23):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#18)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#18)
State variables written after the call(s):
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#22)
ReentrancyWrite.notCalled (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#4) can be used in cross function reentrancies:
@ -11,9 +11,9 @@ Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-n
Reentrancy in ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#25-30):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#27)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#27)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#29)
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#18)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#18)
State variables written after the call(s):
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#29)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.5.16/reentrancy-write.sol#22)

@ -1,22 +1,22 @@
Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#16-23):
Reentrancy in ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#25-30):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#18)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#27)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#29)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#18)
State variables written after the call(s):
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#22)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#29)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#22)
ReentrancyWrite.notCalled (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#4) can be used in cross function reentrancies:
- ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#16-23)
- ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#25-30)
- ReentrancyWrite.constructor(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#7-14)
- ReentrancyWrite.good() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#32-39)
Reentrancy in ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#25-30):
Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#16-23):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#27)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#29)
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#18)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#18)
State variables written after the call(s):
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#29)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#22)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#22)
ReentrancyWrite.notCalled (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#4) can be used in cross function reentrancies:
- ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#16-23)
- ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.6.11/reentrancy-write.sol#25-30)

@ -1,6 +1,6 @@
Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#20-27):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#22)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#22)
State variables written after the call(s):
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#26)
ReentrancyWrite.notCalled (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#8) can be used in cross function reentrancies:
@ -11,9 +11,9 @@ Reentrancy in ReentrancyWrite.bad0() (tests/e2e/detectors/test_data/reentrancy-n
Reentrancy in ReentrancyWrite.bad1(address) (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#29-34):
External calls:
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#31)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#31)
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#33)
- (success) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#22)
- (success,None) = msg.sender.call() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#22)
State variables written after the call(s):
- bad0() (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#33)
- notCalled = false (tests/e2e/detectors/test_data/reentrancy-no-eth/0.7.6/reentrancy-write.sol#26)

@ -1,2 +1,2 @@
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#3-7) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#5)
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#3-10) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.6.11/shift_parameter_mixup.sol#5)

@ -1,2 +1,2 @@
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#3-8) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#5)
C.f() (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#3-10) contains an incorrect shift operation: a = 8 >> a (tests/e2e/detectors/test_data/incorrect-shift/0.7.6/shift_parameter_mixup.sol#5)

@ -1,8 +1,8 @@
User.test(Target) (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#18-37) ignores return value by (e,None) = t.g() (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#36)
User.test(Target) (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#18-37) ignores return value by t.g() (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#31)
User.test(Target) (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#18-37) ignores return value by t.f() (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#19)
User.test(Target) (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#18-37) ignores return value by a.add(0) (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#23)
User.test(Target) (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#18-37) ignores return value by (e) = t.g() (tests/e2e/detectors/test_data/unused-return/0.4.25/unused_return.sol#36)

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

Loading…
Cancel
Save