Merge branch 'dev' into top-level-yul-identifier

pull/1188/head
Josselin Feist 2 years ago
commit 8509db796e
  1. 14
      .github/workflows/ci.yml
  2. 23
      .github/workflows/pip-audit.yml
  3. 11
      Dockerfile
  4. 18
      README.md
  5. 2
      scripts/ci_test_kspec.sh
  6. 4
      scripts/ci_test_simil.sh
  7. 2
      scripts/ci_test_truffle.sh
  8. 8
      setup.py
  9. 43
      slither/__main__.py
  10. 4
      slither/core/cfg/node.py
  11. 2
      slither/core/children/child_node.py
  12. 23
      slither/core/declarations/contract.py
  13. 11
      slither/core/declarations/enum.py
  14. 2
      slither/core/declarations/solidity_variables.py
  15. 9
      slither/core/slither_core.py
  16. 3
      slither/core/solidity_types/type_information.py
  17. 6
      slither/detectors/all_detectors.py
  18. 4
      slither/detectors/assembly/shift_parameter_mixup.py
  19. 2
      slither/detectors/attributes/incorrect_solc.py
  20. 0
      slither/detectors/erc/erc20/__init__.py
  21. 95
      slither/detectors/erc/erc20/arbitrary_send_erc20.py
  22. 45
      slither/detectors/erc/erc20/arbitrary_send_erc20_no_permit.py
  23. 53
      slither/detectors/erc/erc20/arbitrary_send_erc20_permit.py
  24. 0
      slither/detectors/erc/erc20/incorrect_erc20_interface.py
  25. 6
      slither/detectors/functions/arbitrary_send_eth.py
  26. 2
      slither/detectors/naming_convention/naming_convention.py
  27. 12
      slither/detectors/statements/too_many_digits.py
  28. 2
      slither/detectors/statements/unprotected_upgradeable.py
  29. 4
      slither/detectors/variables/similar_variables.py
  30. 2
      slither/formatters/attributes/constant_pragma.py
  31. 3
      slither/formatters/attributes/incorrect_solc.py
  32. 6
      slither/formatters/naming_convention/naming_convention.py
  33. 2
      slither/printers/summary/variable_order.py
  34. 2
      slither/slither.py
  35. 24
      slither/slithir/convert.py
  36. 2
      slither/solc_parsing/expressions/expression_parsing.py
  37. 4
      slither/solc_parsing/slither_compilation_unit_solc.py
  38. 6
      slither/solc_parsing/solidity_types/type_parsing.py
  39. 4
      slither/solc_parsing/yul/parse_yul.py
  40. 7
      slither/tools/erc_conformance/erc/ercs.py
  41. 4
      slither/tools/kspec_coverage/analysis.py
  42. 4
      slither/tools/read_storage/README.md
  43. 6
      slither/tools/upgradeability/checks/initialization.py
  44. 8
      slither/utils/colors.py
  45. 49
      slither/utils/erc.py
  46. 2
      slither/utils/integer_conversion.py
  47. 2
      slither/utils/output.py
  48. 18
      slither/visitors/slithir/expression_to_slithir.py
  49. BIN
      tests/ast-parsing/compile/assembly-all.sol-0.8.13-compact.zip
  50. BIN
      tests/ast-parsing/compile/assembly-all.sol-0.8.14-compact.zip
  51. BIN
      tests/ast-parsing/compile/assembly-all.sol-0.8.15-compact.zip
  52. BIN
      tests/ast-parsing/compile/assignment-0.4.7.sol-0.8.13-compact.zip
  53. BIN
      tests/ast-parsing/compile/assignment-0.4.7.sol-0.8.14-compact.zip
  54. BIN
      tests/ast-parsing/compile/assignment-0.4.7.sol-0.8.15-compact.zip
  55. BIN
      tests/ast-parsing/compile/break-all.sol-0.8.13-compact.zip
  56. BIN
      tests/ast-parsing/compile/break-all.sol-0.8.14-compact.zip
  57. BIN
      tests/ast-parsing/compile/break-all.sol-0.8.15-compact.zip
  58. BIN
      tests/ast-parsing/compile/call_to_variable-all.sol-0.8.13-compact.zip
  59. BIN
      tests/ast-parsing/compile/call_to_variable-all.sol-0.8.14-compact.zip
  60. BIN
      tests/ast-parsing/compile/call_to_variable-all.sol-0.8.15-compact.zip
  61. BIN
      tests/ast-parsing/compile/comment-all.sol-0.8.13-compact.zip
  62. BIN
      tests/ast-parsing/compile/comment-all.sol-0.8.14-compact.zip
  63. BIN
      tests/ast-parsing/compile/comment-all.sol-0.8.15-compact.zip
  64. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.13-compact.zip
  65. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.14-compact.zip
  66. BIN
      tests/ast-parsing/compile/complex_imports/import_aliases/test.sol-0.8.15-compact.zip
  67. BIN
      tests/ast-parsing/compile/conditional-all.sol-0.8.13-compact.zip
  68. BIN
      tests/ast-parsing/compile/conditional-all.sol-0.8.14-compact.zip
  69. BIN
      tests/ast-parsing/compile/conditional-all.sol-0.8.15-compact.zip
  70. BIN
      tests/ast-parsing/compile/continue-all.sol-0.8.13-compact.zip
  71. BIN
      tests/ast-parsing/compile/continue-all.sol-0.8.14-compact.zip
  72. BIN
      tests/ast-parsing/compile/continue-all.sol-0.8.15-compact.zip
  73. BIN
      tests/ast-parsing/compile/contract-0.6.0.sol-0.8.13-compact.zip
  74. BIN
      tests/ast-parsing/compile/contract-0.6.0.sol-0.8.14-compact.zip
  75. BIN
      tests/ast-parsing/compile/contract-0.6.0.sol-0.8.15-compact.zip
  76. BIN
      tests/ast-parsing/compile/custom_error-0.4.0.sol-0.8.13-compact.zip
  77. BIN
      tests/ast-parsing/compile/custom_error-0.4.0.sol-0.8.14-compact.zip
  78. BIN
      tests/ast-parsing/compile/custom_error-0.4.0.sol-0.8.15-compact.zip
  79. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.10-compact.zip
  80. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.11-compact.zip
  81. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.12-compact.zip
  82. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.4-compact.zip
  83. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.5-compact.zip
  84. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.6-compact.zip
  85. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.7-compact.zip
  86. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.8-compact.zip
  87. BIN
      tests/ast-parsing/compile/custom_error_with_state_variable.sol-0.8.9-compact.zip
  88. BIN
      tests/ast-parsing/compile/dowhile-0.4.5.sol-0.8.13-compact.zip
  89. BIN
      tests/ast-parsing/compile/dowhile-0.4.5.sol-0.8.14-compact.zip
  90. BIN
      tests/ast-parsing/compile/dowhile-0.4.5.sol-0.8.15-compact.zip
  91. BIN
      tests/ast-parsing/compile/emit-0.5.0.sol-0.8.13-compact.zip
  92. BIN
      tests/ast-parsing/compile/emit-0.5.0.sol-0.8.14-compact.zip
  93. BIN
      tests/ast-parsing/compile/emit-0.5.0.sol-0.8.15-compact.zip
  94. BIN
      tests/ast-parsing/compile/enum-0.8.0.sol-0.8.13-compact.zip
  95. BIN
      tests/ast-parsing/compile/enum-0.8.0.sol-0.8.14-compact.zip
  96. BIN
      tests/ast-parsing/compile/enum-0.8.0.sol-0.8.15-compact.zip
  97. BIN
      tests/ast-parsing/compile/event-all.sol-0.8.13-compact.zip
  98. BIN
      tests/ast-parsing/compile/event-all.sol-0.8.14-compact.zip
  99. BIN
      tests/ast-parsing/compile/event-all.sol-0.8.15-compact.zip
  100. BIN
      tests/ast-parsing/compile/for-all.sol-0.8.13-compact.zip
  101. Some files were not shown because too many files have changed in this diff Show More

@ -3,8 +3,7 @@ name: CI
defaults: defaults:
run: run:
# To load bashrc shell: bash
shell: bash -ieo pipefail {0}
on: on:
push: push:
@ -52,16 +51,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Set up shell - name: Set up Python 3.8
if: runner.os == 'Windows'
run: |
echo 'C:\msys64\mingw64\bin' >> "$GITHUB_PATH"
echo 'C:\msys64\usr\bin' >> "$GITHUB_PATH"
- name: Set up Python 3.6
uses: actions/setup-python@v3 uses: actions/setup-python@v3
with: with:
python-version: 3.6 python-version: 3.8
- name: Install dependencies - name: Install dependencies
run: | run: |
@ -87,6 +80,7 @@ jobs:
- name: Run Tests - name: Run Tests
env: env:
PYTHONUTF8: 1
TEST_TYPE: ${{ matrix.type }} TEST_TYPE: ${{ matrix.type }}
GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }} GITHUB_ETHERSCAN: ${{ secrets.GITHUB_ETHERSCAN }}
run: | run: |

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

@ -1,4 +1,4 @@
FROM ubuntu:bionic FROM ubuntu:focal
LABEL name=slither LABEL name=slither
LABEL src="https://github.com/trailofbits/slither" LABEL src="https://github.com/trailofbits/slither"
@ -6,11 +6,12 @@ LABEL creator=trailofbits
LABEL dockerfile_maintenance=trailofbits LABEL dockerfile_maintenance=trailofbits
LABEL desc="Static Analyzer for Solidity" LABEL desc="Static Analyzer for Solidity"
RUN apt-get update \ RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get upgrade -y \ && apt-get update \
&& apt-get install -y git python3 python3-setuptools wget software-properties-common && apt-get upgrade -yq \
&& apt-get install -yq gcc git python3 python3-dev python3-setuptools wget software-properties-common
RUN wget https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux \ RUN wget -q https://github.com/ethereum/solidity/releases/download/v0.4.25/solc-static-linux \
&& chmod +x solc-static-linux \ && chmod +x solc-static-linux \
&& mv solc-static-linux /usr/bin/solc && mv solc-static-linux /usr/bin/solc

@ -40,9 +40,12 @@ Run Slither on a single file:
slither tests/uninitialized.sol slither tests/uninitialized.sol
``` ```
For GitHub action integration, see [slither-action](https://github.com/marketplace/actions/slither-action). For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation. ### Integration
- For GitHub action integration, use [slither-action](https://github.com/marketplace/actions/slither-action).
- To generate a Markdown report, use `slither [target] --checklist`.
- To generate a Markdown with GitHub source code highlighting, use `slither [target] --checklist --markdown-root https://github.com/ORG/REPO/blob/COMMIT/` (replace `ORG`, `REPO`, `COMMIT`)
Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc. Use [solc-select](https://github.com/crytic/solc-select) if your contracts require older versions of solc. For additional configuration, see the [usage](https://github.com/trailofbits/slither/wiki/Usage) documentation.
### Detectors ### Detectors
@ -51,7 +54,7 @@ Num | Detector | What it Detects | Impact | Confidence
--- | --- | --- | --- | --- --- | --- | --- | --- | ---
1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High 1 | `abiencoderv2-array` | [Storage abiencoderv2 array](https://github.com/crytic/slither/wiki/Detector-Documentation#storage-abiencoderv2-array) | High | High
2 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High 2 | `array-by-reference` | [Modifying storage array by value](https://github.com/crytic/slither/wiki/Detector-Documentation#modifying-storage-array-by-value) | High | High
3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#shift-parameter-mixup) | High | High 3 | `incorrect-shift` | [The order of parameters in a shift instruction is incorrect.](https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-shift-in-assembly) | High | High
4 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High 4 | `multiple-constructors` | [Multiple constructor schemes](https://github.com/crytic/slither/wiki/Detector-Documentation#multiple-constructor-schemes) | High | High
5 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High 5 | `name-reused` | [Contract's name reused](https://github.com/crytic/slither/wiki/Detector-Documentation#name-reused) | High | High
6 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High 6 | `public-mappings-nested` | [Public mappings with nested variables](https://github.com/crytic/slither/wiki/Detector-Documentation#public-mappings-with-nested-variables) | High | High
@ -121,7 +124,7 @@ Num | Detector | What it Detects | Impact | Confidence
70 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium 70 | `costly-loop` | [Costly operations in a loop](https://github.com/crytic/slither/wiki/Detector-Documentation#costly-operations-inside-a-loop) | Informational | Medium
71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium 71 | `dead-code` | [Functions that are not used](https://github.com/crytic/slither/wiki/Detector-Documentation#dead-code) | Informational | Medium
72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium 72 | `reentrancy-unlimited-gas` | [Reentrancy vulnerabilities through send and transfer](https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities-4) | Informational | Medium
73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-are-too-similar) | Informational | Medium 73 | `similar-names` | [Variable names are too similar](https://github.com/crytic/slither/wiki/Detector-Documentation#variable-names-too-similar) | Informational | Medium
74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium 74 | `too-many-digits` | [Conformance to numeric notation best practices](https://github.com/crytic/slither/wiki/Detector-Documentation#too-many-digits) | Informational | Medium
75 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High 75 | `constable-states` | [State variables that could be declared constant](https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant) | Optimization | High
76 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High 76 | `external-function` | [Public function that could be declared external](https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external) | Optimization | High
@ -181,7 +184,7 @@ We recommend using a Python virtual environment, as detailed in the [Developer I
### Using Docker ### Using Docker
Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container. Use the [`eth-security-toolbox`](https://github.com/trailofbits/eth-security-toolbox/) docker image. It includes all of our security tools and every major version of Solidity in a single image. `/home/share` will be mounted to `/share` in the container.
```bash ```bash
docker pull trailofbits/eth-security-toolbox docker pull trailofbits/eth-security-toolbox
@ -225,5 +228,8 @@ Title | Usage | Authors | Venue
[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 [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
[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 [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) [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
[SolType: Refinement Types for Arithmetic Overflow in Solidity](https://arxiv.org/abs/2110.00677) | Use Slither as frontend to build refinement type system | Bryan Tan, Benjamin Mariano, Shuvendu K. Lahiri, Isil Dillig, Yu Feng | POPL 22
[Do Not Rug on Me: Leveraging Machine Learning Techniques for Automated Scam Detection](https://www.mdpi.com/2227-7390/10/6/949) | Use Slither to extract tokens' features (mintable, pausable, ..) | Mazorra, Bruno, Victor Adan, and Vanesa Daza | Mathematics 10.6 (2022)
If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/). If you are using Slither on an academic work, consider applying to the [Crytic $10k Research Prize](https://blog.trailofbits.com/2019/11/13/announcing-the-crytic-10k-research-prize/).

@ -7,7 +7,7 @@ slither-check-kspec "$DIR_TESTS/safeAdd/safeAdd.sol" "$DIR_TESTS/safeAdd/spec.md
DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt") DIFF=$(diff test_1.txt "$DIR_TESTS/test_1.txt")
if [ "$DIFF" != "" ] if [ "$DIFF" != "" ]
then then
echo "slither-check-upgradeability 1 failed" echo "slither-check-kspec 1 failed"
cat test_1.txt cat test_1.txt
echo "" echo ""
cat "$DIR_TESTS/test_1.txt" cat "$DIR_TESTS/test_1.txt"

@ -2,8 +2,8 @@
### Install requisites ### Install requisites
pip3.6 install pybind11 pip3 install pybind11
pip3.6 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip pip3 install https://github.com/facebookresearch/fastText/archive/0.2.0.zip
### Test slither-simil ### Test slither-simil

@ -15,7 +15,7 @@ npm install -g truffle
truffle unbox metacoin truffle unbox metacoin
slither . slither .
if [ $? -eq 9 ] if [ $? -eq 6 ]
then then
exit 0 exit 0
fi fi

@ -8,16 +8,16 @@ setup(
description="Slither is a Solidity static analysis framework written in Python 3.", description="Slither is a Solidity static analysis framework written in Python 3.",
url="https://github.com/crytic/slither", url="https://github.com/crytic/slither",
author="Trail of Bits", author="Trail of Bits",
version="0.8.2", version="0.8.3",
packages=find_packages(), packages=find_packages(),
python_requires=">=3.6", python_requires=">=3.6",
install_requires=[ install_requires=[
"prettytable>=0.7.2", "prettytable>=0.7.2",
"pysha3>=1.0.2", "pysha3>=1.0.2",
"crytic-compile>=0.2.3", # "crytic-compile>=0.2.3",
# "crytic-compile", "crytic-compile",
], ],
# dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"], dependency_links=["git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile"],
license="AGPL-3.0", license="AGPL-3.0",
long_description=long_description, long_description=long_description,
entry_points={ entry_points={

@ -299,6 +299,9 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_detector = parser.add_argument_group("Detectors") group_detector = parser.add_argument_group("Detectors")
group_printer = parser.add_argument_group("Printers") group_printer = parser.add_argument_group("Printers")
group_checklist = parser.add_argument_group(
"Checklist (consider using https://github.com/crytic/slither-action)"
)
group_misc = parser.add_argument_group("Additional options") group_misc = parser.add_argument_group("Additional options")
group_detector.add_argument( group_detector.add_argument(
@ -312,7 +315,7 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
group_printer.add_argument( group_printer.add_argument(
"--print", "--print",
help="Comma-separated list fo contract information printers, " help="Comma-separated list of contract information printers, "
f"available printers: {', '.join(d.ARGUMENT for d in printer_classes)}", f"available printers: {', '.join(d.ARGUMENT for d in printer_classes)}",
action="store", action="store",
dest="printers_to_run", dest="printers_to_run",
@ -392,6 +395,28 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["show_ignored_findings"], default=defaults_flag_in_config["show_ignored_findings"],
) )
group_checklist.add_argument(
"--checklist",
help="Generate a markdown page with the detector results",
action="store_true",
default=False,
)
group_checklist.add_argument(
"--checklist-limit",
help="Limite the number of results per detector in the markdown file",
action="store",
default="",
)
group_checklist.add_argument(
"--markdown-root",
type=check_and_sanitize_markdown_root,
help="URL for markdown generation",
action="store",
default="",
)
group_misc.add_argument( group_misc.add_argument(
"--json", "--json",
help='Export the results as a JSON file ("--json -" to export to stdout)', help='Export the results as a JSON file ("--json -" to export to stdout)',
@ -429,14 +454,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
default=defaults_flag_in_config["zip_type"], default=defaults_flag_in_config["zip_type"],
) )
group_misc.add_argument(
"--markdown-root",
type=check_and_sanitize_markdown_root,
help="URL for markdown generation",
action="store",
default="",
)
group_misc.add_argument( group_misc.add_argument(
"--disable-color", "--disable-color",
help="Disable output colorization", help="Disable output colorization",
@ -487,12 +504,6 @@ def parse_args(detector_classes, printer_classes): # pylint: disable=too-many-s
parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False) parser.add_argument("--markdown", help=argparse.SUPPRESS, action=OutputMarkdown, default=False)
group_misc.add_argument(
"--checklist", help=argparse.SUPPRESS, action="store_true", default=False
)
group_misc.add_argument("--checklist-limit", help=argparse.SUPPRESS, action="store", default="")
parser.add_argument( parser.add_argument(
"--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False "--wiki-detectors", help=argparse.SUPPRESS, action=OutputWiki, default=False
) )
@ -648,7 +659,7 @@ def main_impl(all_detector_classes, all_printer_classes):
cp.enable() cp.enable()
# Set colorization option # Set colorization option
set_colorization_enabled(not args.disable_color) set_colorization_enabled(False if args.disable_color else sys.stdout.isatty())
# Define some variables for potential JSON output # Define some variables for potential JSON output
json_results = {} json_results = {}

@ -39,12 +39,11 @@ from slither.slithir.variables import (
TupleVariable, TupleVariable,
) )
from slither.all_exceptions import SlitherException from slither.all_exceptions import SlitherException
from slither.core.declarations import Contract from slither.core.declarations import Contract, Function
from slither.core.expressions.expression import Expression from slither.core.expressions.expression import Expression
if TYPE_CHECKING: if TYPE_CHECKING:
from slither.core.declarations import Function
from slither.slithir.variables.variable import SlithIRVariable from slither.slithir.variables.variable import SlithIRVariable
from slither.core.compilation_unit import SlitherCompilationUnit from slither.core.compilation_unit import SlitherCompilationUnit
from slither.utils.type_helpers import ( from slither.utils.type_helpers import (
@ -917,6 +916,7 @@ class Node(SourceMapping, ChildFunction): # pylint: disable=too-many-public-met
) )
elif isinstance(ir, LibraryCall): elif isinstance(ir, LibraryCall):
assert isinstance(ir.destination, Contract) assert isinstance(ir.destination, Contract)
assert isinstance(ir.function, Function)
self._high_level_calls.append((ir.destination, ir.function)) self._high_level_calls.append((ir.destination, ir.function))
self._library_calls.append((ir.destination, ir.function)) self._library_calls.append((ir.destination, ir.function))

@ -28,4 +28,4 @@ class ChildNode:
@property @property
def compilation_unit(self) -> "SlitherCompilationUnit": def compilation_unit(self) -> "SlitherCompilationUnit":
return self.contract.compilation_unit return self.node.compilation_unit

@ -21,6 +21,8 @@ from slither.utils.erc import (
ERC777_signatures, ERC777_signatures,
ERC1155_signatures, ERC1155_signatures,
ERC2612_signatures, ERC2612_signatures,
ERC1363_signatures,
ERC4524_signatures,
ERC4626_signatures, ERC4626_signatures,
) )
from slither.utils.tests_pattern import is_test_contract from slither.utils.tests_pattern import is_test_contract
@ -903,6 +905,7 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
("ERC721", self.is_erc721), ("ERC721", self.is_erc721),
("ERC777", self.is_erc777), ("ERC777", self.is_erc777),
("ERC2612", self.is_erc2612), ("ERC2612", self.is_erc2612),
("ERC1363", self.is_erc1363),
("ERC4626", self.is_erc4626), ("ERC4626", self.is_erc4626),
] ]
@ -998,6 +1001,26 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
full_names = self.functions_signatures full_names = self.functions_signatures
return all(s in full_names for s in ERC2612_signatures) return all(s in full_names for s in ERC2612_signatures)
def is_erc1363(self) -> bool:
"""
Check if the contract is an erc1363
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc1363
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC1363_signatures)
def is_erc4524(self) -> bool:
"""
Check if the contract is an erc4524
Note: it does not check for correct return values
:return: Returns a true if the contract is an erc4524
"""
full_names = self.functions_signatures
return all(s in full_names for s in ERC4524_signatures)
@property @property
def is_token(self) -> bool: def is_token(self) -> bool:
""" """

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

@ -104,7 +104,7 @@ class SolidityVariable(Context):
# dev function, will be removed once the code is stable # dev function, will be removed once the code is stable
def _check_name(self, name: str): # pylint: disable=no-self-use def _check_name(self, name: str): # pylint: disable=no-self-use
assert name in SOLIDITY_VARIABLES or name.endswith("_slot") or name.endswith("_offset") assert name in SOLIDITY_VARIABLES or name.endswith(("_slot", "_offset"))
@property @property
def state_variable(self): def state_variable(self):

@ -4,6 +4,7 @@
import json import json
import logging import logging
import os import os
import pathlib
import posixpath import posixpath
import re import re
from typing import Optional, Dict, List, Set, Union from typing import Optional, Dict, List, Set, Union
@ -218,8 +219,12 @@ class SlitherCore(Context):
for elem in r["elements"] for elem in r["elements"]
if "source_mapping" in elem if "source_mapping" in elem
] ]
source_mapping_elements = map(
lambda x: posixpath.normpath(x) if x else x, source_mapping_elements # Use POSIX-style paths so that filter_paths works across different
# OSes. Convert to a list so elements don't get consumed and are lost
# while evaluating the first pattern
source_mapping_elements = list(
map(lambda x: pathlib.Path(x).resolve().as_posix() if x else x, source_mapping_elements)
) )
matching = False matching = False

@ -13,8 +13,9 @@ class TypeInformation(Type):
def __init__(self, c): def __init__(self, c):
# pylint: disable=import-outside-toplevel # pylint: disable=import-outside-toplevel
from slither.core.declarations.contract import Contract from slither.core.declarations.contract import Contract
from slither.core.declarations.enum import Enum
assert isinstance(c, (Contract, ElementaryType)) assert isinstance(c, (Contract, ElementaryType, Enum))
super().__init__() super().__init__()
self._type = c self._type = c

@ -6,7 +6,9 @@ from .variables.uninitialized_local_variables import UninitializedLocalVars
from .attributes.constant_pragma import ConstantPragma from .attributes.constant_pragma import ConstantPragma
from .attributes.incorrect_solc import IncorrectSolc from .attributes.incorrect_solc import IncorrectSolc
from .attributes.locked_ether import LockedEther from .attributes.locked_ether import LockedEther
from .functions.arbitrary_send import ArbitrarySend from .functions.arbitrary_send_eth import ArbitrarySendEth
from .erc.erc20.arbitrary_send_erc20_no_permit import ArbitrarySendErc20NoPermit
from .erc.erc20.arbitrary_send_erc20_permit import ArbitrarySendErc20Permit
from .functions.suicidal import Suicidal from .functions.suicidal import Suicidal
# from .functions.complex_function import ComplexFunction # from .functions.complex_function import ComplexFunction
@ -34,7 +36,7 @@ from .shadowing.builtin_symbols import BuiltinSymbolShadowing
from .operations.block_timestamp import Timestamp from .operations.block_timestamp import Timestamp
from .statements.calls_in_loop import MultipleCallsInLoop from .statements.calls_in_loop import MultipleCallsInLoop
from .statements.incorrect_strict_equality import IncorrectStrictEquality from .statements.incorrect_strict_equality import IncorrectStrictEquality
from .erc.incorrect_erc20_interface import IncorrectERC20InterfaceDetection from .erc.erc20.incorrect_erc20_interface import IncorrectERC20InterfaceDetection
from .erc.incorrect_erc721_interface import IncorrectERC721InterfaceDetection from .erc.incorrect_erc721_interface import IncorrectERC721InterfaceDetection
from .erc.unindexed_event_parameters import UnindexedERC20EventParameters from .erc.unindexed_event_parameters import UnindexedERC20EventParameters
from .statements.deprecated_calls import DeprecatedStandards from .statements.deprecated_calls import DeprecatedStandards

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

@ -14,7 +14,7 @@ from slither.formatters.attributes.incorrect_solc import custom_format
# 4: version number # 4: version number
# pylint: disable=anomalous-backslash-in-string # pylint: disable=anomalous-backslash-in-string
PATTERN = re.compile("(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)") PATTERN = re.compile(r"(\^|>|>=|<|<=)?([ ]+)?(\d+)\.(\d+)\.(\d+)")
class IncorrectSolc(AbstractDetector): class IncorrectSolc(AbstractDetector):

@ -0,0 +1,95 @@
from typing import List
from slither.core.cfg.node import Node
from slither.core.declarations.solidity_variables import SolidityVariable
from slither.slithir.operations import HighLevelCall, LibraryCall
from slither.core.declarations import Contract, Function, SolidityVariableComposed
from slither.analyses.data_dependency.data_dependency import is_dependent
from slither.core.compilation_unit import SlitherCompilationUnit
class ArbitrarySendErc20:
"""Detects instances where ERC20 can be sent from an arbitrary from address."""
def __init__(self, compilation_unit: SlitherCompilationUnit):
self._compilation_unit = compilation_unit
self._no_permit_results: List[Node] = []
self._permit_results: List[Node] = []
@property
def compilation_unit(self) -> SlitherCompilationUnit:
return self._compilation_unit
@property
def no_permit_results(self) -> List[Node]:
return self._no_permit_results
@property
def permit_results(self) -> List[Node]:
return self._permit_results
def _detect_arbitrary_from(self, contract: Contract):
for f in contract.functions:
all_high_level_calls = [
f_called[1].solidity_signature
for f_called in f.high_level_calls
if isinstance(f_called[1], Function)
]
all_library_calls = [f_called[1].solidity_signature for f_called in f.library_calls]
if (
"transferFrom(address,address,uint256)" in all_high_level_calls
or "safeTransferFrom(address,address,address,uint256)" in all_library_calls
):
if (
"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)"
in all_high_level_calls
):
ArbitrarySendErc20._arbitrary_from(f.nodes, self._permit_results)
else:
ArbitrarySendErc20._arbitrary_from(f.nodes, self._no_permit_results)
@staticmethod
def _arbitrary_from(nodes: List[Node], results: List[Node]):
"""Finds instances of (safe)transferFrom that do not use msg.sender or address(this) as from parameter."""
for node in nodes:
for ir in node.irs:
if (
isinstance(ir, HighLevelCall)
and isinstance(ir.function, Function)
and ir.function.solidity_signature == "transferFrom(address,address,uint256)"
and not (
is_dependent(
ir.arguments[0],
SolidityVariableComposed("msg.sender"),
node.function.contract,
)
or is_dependent(
ir.arguments[0],
SolidityVariable("this"),
node.function.contract,
)
)
):
results.append(ir.node)
elif (
isinstance(ir, LibraryCall)
and ir.function.solidity_signature
== "safeTransferFrom(address,address,address,uint256)"
and not (
is_dependent(
ir.arguments[1],
SolidityVariableComposed("msg.sender"),
node.function.contract,
)
or is_dependent(
ir.arguments[1],
SolidityVariable("this"),
node.function.contract,
)
)
):
results.append(ir.node)
def detect(self):
"""Detect transfers that use arbitrary `from` parameter."""
for c in self.compilation_unit.contracts_derived:
self._detect_arbitrary_from(c)

@ -0,0 +1,45 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output
from .arbitrary_send_erc20 import ArbitrarySendErc20
class ArbitrarySendErc20NoPermit(AbstractDetector):
"""
Detect when `msg.sender` is not used as `from` in transferFrom
"""
ARGUMENT = "arbitrary-send-erc20"
HELP = "transferFrom uses arbitrary `from`"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20"
WIKI_TITLE = "Arbitrary `from` in transferFrom"
WIKI_DESCRIPTION = "Detect when `msg.sender` is not used as `from` in transferFrom."
WIKI_EXPLOIT_SCENARIO = """
```solidity
function a(address from, address to, uint256 amount) public {
erc20.transferFrom(from, to, am);
}
```
Alice approves this contract to spend her ERC20 tokens. Bob can call `a` and specify Alice's address as the `from` parameter in `transferFrom`, allowing him to transfer Alice's tokens to himself."""
WIKI_RECOMMENDATION = """
Use `msg.sender` as `from` in transferFrom.
"""
def _detect(self) -> List[Output]:
""""""
results: List[Output] = []
arbitrary_sends = ArbitrarySendErc20(self.compilation_unit)
arbitrary_sends.detect()
for node in arbitrary_sends.no_permit_results:
func = node.function
info = [func, " uses arbitrary from in transferFrom: ", node, "\n"]
res = self.generate_result(info)
results.append(res)
return results

@ -0,0 +1,53 @@
from typing import List
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output
from .arbitrary_send_erc20 import ArbitrarySendErc20
class ArbitrarySendErc20Permit(AbstractDetector):
"""
Detect when `msg.sender` is not used as `from` in transferFrom along with the use of permit.
"""
ARGUMENT = "arbitrary-send-erc20-permit"
HELP = "transferFrom uses arbitrary from with permit"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM
WIKI = "https://github.com/trailofbits/slither/wiki/Detector-Documentation#arbitrary-send-erc20-permit"
WIKI_TITLE = "Arbitrary `from` in transferFrom used with permit"
WIKI_DESCRIPTION = (
"Detect when `msg.sender` is not used as `from` in transferFrom and permit is used."
)
WIKI_EXPLOIT_SCENARIO = """
```solidity
function bad(address from, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s, address to) public {
erc20.permit(from, address(this), value, deadline, v, r, s);
erc20.transferFrom(from, to, value);
}
```
If an ERC20 token does not implement permit and has a fallback function e.g. WETH, transferFrom allows an attacker to transfer all tokens approved for this contract."""
WIKI_RECOMMENDATION = """
Ensure that the underlying ERC20 token correctly implements a permit function.
"""
def _detect(self) -> List[Output]:
""""""
results: List[Output] = []
arbitrary_sends = ArbitrarySendErc20(self.compilation_unit)
arbitrary_sends.detect()
for node in arbitrary_sends.permit_results:
func = node.function
info = [
func,
" uses arbitrary from in transferFrom in combination with permit: ",
node,
"\n",
]
res = self.generate_result(info)
results.append(res)
return results

@ -90,8 +90,8 @@ def detect_arbitrary_send(contract: Contract):
return ret return ret
class ArbitrarySend(AbstractDetector): class ArbitrarySendEth(AbstractDetector):
ARGUMENT = "arbitrary-send" ARGUMENT = "arbitrary-send-eth"
HELP = "Functions that send Ether to arbitrary destinations" HELP = "Functions that send Ether to arbitrary destinations"
IMPACT = DetectorClassification.HIGH IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.MEDIUM CONFIDENCE = DetectorClassification.MEDIUM
@ -104,7 +104,7 @@ class ArbitrarySend(AbstractDetector):
# region wiki_exploit_scenario # region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """ WIKI_EXPLOIT_SCENARIO = """
```solidity ```solidity
contract ArbitrarySend{ contract ArbitrarySendEth{
address destination; address destination;
function setDestination(){ function setDestination(){
destination = msg.sender; destination = msg.sender;

@ -94,7 +94,7 @@ Solidity defines a [naming convention](https://solidity.readthedocs.io/en/v0.4.2
"private", "private",
] and self.is_mixed_case_with_underscore(func.name): ] and self.is_mixed_case_with_underscore(func.name):
continue continue
if func.name.startswith("echidna_") or func.name.startswith("crytic_"): if func.name.startswith(("echidna_", "crytic_")):
continue continue
info = ["Function ", func, " is not in mixedCase\n"] info = ["Function ", func, " is not in mixedCase\n"]

@ -2,9 +2,19 @@
Module detecting numbers with too many digits. Module detecting numbers with too many digits.
""" """
import re
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.slithir.variables import Constant from slither.slithir.variables import Constant
_HEX_ADDRESS_REGEXP = re.compile("(0[xX])?[0-9a-fA-F]{40}")
def is_hex_address(value) -> bool:
"""
Checks if the given string of text type is an address in hexadecimal encoded form.
"""
return _HEX_ADDRESS_REGEXP.fullmatch(value) is not None
class TooManyDigits(AbstractDetector): class TooManyDigits(AbstractDetector):
""" """
@ -58,7 +68,7 @@ Use:
if isinstance(read, Constant): if isinstance(read, Constant):
# read.value can return an int or a str. Convert it to str # read.value can return an int or a str. Convert it to str
value_as_str = read.original_value value_as_str = read.original_value
if "00000" in value_as_str: if "00000" in value_as_str and not is_hex_address(value_as_str):
# Info to be printed # Info to be printed
ret.append(node) ret.append(node)
return ret return ret

@ -102,7 +102,7 @@ class UnprotectedUpgradeable(AbstractDetector):
info = ( info = (
[ [
contract, contract,
" is an upgradeable contract that does not protect its initiliaze functions: ", " is an upgradeable contract that does not protect its initialize functions: ",
] ]
+ initialize_functions + initialize_functions
+ [ + [

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

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

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

@ -300,10 +300,10 @@ def _patch(compilation_unit: SlitherCompilationUnit, result, element, _target):
# group 2: beginning of the to type # group 2: beginning of the to type
# nested mapping are within the group 1 # nested mapping are within the group 1
# RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)' # RE_MAPPING = '[ ]*mapping[ ]*\([ ]*([\=\>\(\) a-zA-Z0-9\._\[\]]*)[ ]*=>[ ]*([a-zA-Z0-9\._\[\]]*)\)'
RE_MAPPING_FROM = b"([a-zA-Z0-9\._\[\]]*)" RE_MAPPING_FROM = rb"([a-zA-Z0-9\._\[\]]*)"
RE_MAPPING_TO = b"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)" RE_MAPPING_TO = rb"([\=\>\(\) a-zA-Z0-9\._\[\]\ ]*)"
RE_MAPPING = ( RE_MAPPING = (
b"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + b"\)" rb"[ ]*mapping[ ]*\([ ]*" + RE_MAPPING_FROM + b"[ ]*" + b"=>" + b"[ ]*" + RE_MAPPING_TO + rb"\)"
) )

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

@ -53,7 +53,7 @@ class Slither(SlitherCore): # pylint: disable=too-many-instance-attributes
Keyword Args: Keyword Args:
solc (str): solc binary location (default 'solc') solc (str): solc binary location (default 'solc')
disable_solc_warnings (bool): True to disable solc warnings (default false) disable_solc_warnings (bool): True to disable solc warnings (default false)
solc_arguments (str): solc arguments (default '') solc_args (str): solc arguments (default '')
ast_format (str): ast format (default '--ast-compact-json') ast_format (str): ast format (default '--ast-compact-json')
filter_paths (list(str)): list of path to filter (default []) filter_paths (list(str)): list of path to filter (default [])
triage_mode (bool): if true, switch to triage mode (default false) triage_mode (bool): if true, switch to triage mode (default false)

@ -817,12 +817,28 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]): # pylint: dis
# lib L { event E()} # lib L { event E()}
# ... # ...
# emit L.E(); # emit L.E();
if str(ins.ori.variable_right) in [f.name for f in ins.ori.variable_left.events]: if str(ins.ori.variable_right) in ins.ori.variable_left.events_as_dict:
eventcall = EventCall(ins.ori.variable_right) eventcall = EventCall(ins.ori.variable_right)
eventcall.set_expression(ins.expression) eventcall.set_expression(ins.expression)
eventcall.call_id = ins.call_id eventcall.call_id = ins.call_id
return eventcall return eventcall
# lib Lib { error Error()} ... revert Lib.Error()
if str(ins.ori.variable_right) in ins.ori.variable_left.custom_errors_as_dict:
custom_error = ins.ori.variable_left.custom_errors_as_dict[
str(ins.ori.variable_right)
]
assert isinstance(
custom_error,
CustomError,
)
sol_function = SolidityCustomRevert(custom_error)
solidity_call = SolidityCall(
sol_function, ins.nbr_arguments, ins.lvalue, ins.type_call
)
solidity_call.set_expression(ins.expression)
return solidity_call
libcall = LibraryCall( libcall = LibraryCall(
ins.ori.variable_left, ins.ori.variable_left,
ins.ori.variable_right, ins.ori.variable_right,
@ -1369,7 +1385,11 @@ def convert_type_library_call(ir: HighLevelCall, lib_contract: Contract):
if len(candidates) == 1: if len(candidates) == 1:
func = candidates[0] func = candidates[0]
if func is None: # We can discard if there are arguments here because libraries only support constant variables
# And constant variables cannot have non-value type
# i.e. "uint[2] constant arr = [1,2];" is not possible in Solidity
# If this were to change, the following condition might be broken
if func is None and not ir.arguments:
# TODO: handle collision with multiple state variables/functions # TODO: handle collision with multiple state variables/functions
func = lib_contract.get_state_variable_from_name(ir.function_name) func = lib_contract.get_state_variable_from_name(ir.function_name)
if func is None and candidates: if func is None and candidates:

@ -445,7 +445,7 @@ def parse_expression(expression: Dict, caller_context: CallerContextExpression)
t = expression["attributes"]["type"] t = expression["attributes"]["type"]
if t: if t:
found = re.findall("[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t) found = re.findall(r"[struct|enum|function|modifier] \(([\[\] ()a-zA-Z0-9\.,_]*)\)", t)
assert len(found) <= 1 assert len(found) <= 1
if found: if found:
value = value + "(" + found[0] + ")" value = value + "(" + found[0] + ")"

@ -679,12 +679,12 @@ Please rename it, this name is reserved for Slither's internals"""
for func in contract.functions + contract.modifiers: for func in contract.functions + contract.modifiers:
try: try:
func.generate_slithir_and_analyze() func.generate_slithir_and_analyze()
except AttributeError: except AttributeError as e:
# This can happens for example if there is a call to an interface # This can happens for example if there is a call to an interface
# And the interface is redefined due to contract's name reuse # And the interface is redefined due to contract's name reuse
# But the available version misses some functions # But the available version misses some functions
self._underlying_contract_to_parser[contract].log_incorrect_parsing( self._underlying_contract_to_parser[contract].log_incorrect_parsing(
f"Impossible to generate IR for {contract.name}.{func.name}" f"Impossible to generate IR for {contract.name}.{func.name}:\n {e}"
) )
contract.convert_expression_to_slithir_ssa() contract.convert_expression_to_slithir_ssa()

@ -112,7 +112,7 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
if not var_type: if not var_type:
if name.startswith("function "): if name.startswith("function "):
found = re.findall( found = re.findall(
"function \(([ ()\[\]a-zA-Z0-9\.,]*?)\)(?: payable)?(?: (?:external|internal|pure|view))?(?: returns \(([a-zA-Z0-9() \.,]*)\))?", r"function \(([ ()\[\]a-zA-Z0-9\.,]*?)\)(?: payable)?(?: (?:external|internal|pure|view))?(?: returns \(([a-zA-Z0-9() \.,]*)\))?",
name, name,
) )
assert len(found) == 1 assert len(found) == 1
@ -159,10 +159,10 @@ def _find_from_type_name( # pylint: disable=too-many-locals,too-many-branches,t
if name.startswith("mapping("): if name.startswith("mapping("):
# nested mapping declared with var # nested mapping declared with var
if name.count("mapping(") == 1: if name.count("mapping(") == 1:
found = re.findall("mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name) found = re.findall(r"mapping\(([a-zA-Z0-9\.]*) => ([ a-zA-Z0-9\.\[\]]*)\)", name)
else: else:
found = re.findall( found = re.findall(
"mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)", r"mapping\(([a-zA-Z0-9\.]*) => (mapping\([=> a-zA-Z0-9\.\[\]]*\))\)",
name, name,
) )
assert len(found) == 1 assert len(found) == 1

@ -738,7 +738,7 @@ def _parse_yul_magic_suffixes(name: str, root: YulScope) -> Optional[Expression]
# Currently SlithIR doesnt support raw access to memory # Currently SlithIR doesnt support raw access to memory
# So things like .offset/.slot will return the variable # So things like .offset/.slot will return the variable
# Instaed of the actual offset/slot # Instaed of the actual offset/slot
if name.endswith("_slot") or name.endswith(".slot"): if name.endswith(("_slot", ".slot")):
potential_name = name[:-5] potential_name = name[:-5]
variable_found = _check_for_state_variable_name(root, potential_name) variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found: if variable_found:
@ -746,7 +746,7 @@ def _parse_yul_magic_suffixes(name: str, root: YulScope) -> Optional[Expression]
var = root.function.get_local_variable_from_name(potential_name) var = root.function.get_local_variable_from_name(potential_name)
if var and var.is_storage: if var and var.is_storage:
return Identifier(var) return Identifier(var)
if name.endswith("_offset") or name.endswith(".offset"): if name.endswith(("_offset", ".offset")):
potential_name = name[:-7] potential_name = name[:-7]
variable_found = _check_for_state_variable_name(root, potential_name) variable_found = _check_for_state_variable_name(root, potential_name)
if variable_found: if variable_found:

@ -192,9 +192,10 @@ def generic_erc_checks(contract, erc_functions, erc_events, ret, explored=None):
logger.info("## Check functions") logger.info("## Check functions")
for erc_function in erc_functions: for erc_function in erc_functions:
_check_signature(erc_function, contract, ret) _check_signature(erc_function, contract, ret)
logger.info("\n## Check events") if erc_events:
for erc_event in erc_events: logger.info("\n## Check events")
_check_events(erc_event, contract, ret) for erc_event in erc_events:
_check_events(erc_event, contract, ret)
logger.info("\n") logger.info("\n")

@ -22,8 +22,8 @@ def _get_all_covered_kspec_functions(target: str) -> Set[Tuple[str, str]]:
# Create a set of our discovered functions which are covered # Create a set of our discovered functions which are covered
covered_functions: Set[Tuple[str, str]] = set() covered_functions: Set[Tuple[str, str]] = set()
BEHAVIOUR_PATTERN = re.compile("behaviour\s+(\S+)\s+of\s+(\S+)") BEHAVIOUR_PATTERN = re.compile(r"behaviour\s+(\S+)\s+of\s+(\S+)")
INTERFACE_PATTERN = re.compile("interface\s+([^\r\n]+)") INTERFACE_PATTERN = re.compile(r"interface\s+([^\r\n]+)")
# Read the file contents # Read the file contents
with open(target, "r", encoding="utf8") as target_file: with open(target, "r", encoding="utf8") as target_file:

@ -46,13 +46,13 @@ slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --layout --rpc-u
To view only the slot of the `slot0` structure variable, pass `--variable-name slot0`: To view only the slot of the `slot0` structure variable, pass `--variable-name slot0`:
```shell ```shell
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --variable-name slot0 --rpc-url https://mainnet.infura.io/v3/04942f7970ef41cc847a147bc64e460e --value slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --variable-name slot0 --rpc-url $RPC_URL --value
``` ```
To view a member of the `slot0` struct, pass `--struct-var tick` To view a member of the `slot0` struct, pass `--struct-var tick`
```shell ```shell
slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --variable-name slot0 --rpc-url https://mainnet.infura.io/v3/04942f7970ef41cc847a147bc64e460e --value --struct-var tick slither-read-storage 0x8ad599c3a0ff1de082011efddc58f1908eb6e6d8 --variable-name slot0 --rpc-url $RPC_URL --value --struct-var tick
``` ```
Retrieve the ERC20 balance slot of an account: Retrieve the ERC20 balance slot of an account:

@ -15,7 +15,7 @@ class MultipleInitTarget(Exception):
pass pass
def _has_initiliaze_modifier(function: Function): def _has_initialize_modifier(function: Function):
if not function.modifiers: if not function.modifiers:
return False return False
return any((m.name == "initializer") for m in function.modifiers) return any((m.name == "initializer") for m in function.modifiers)
@ -25,7 +25,7 @@ def _get_initialize_functions(contract):
return [ return [
f f
for f in contract.functions for f in contract.functions
if (f.name == "initialize" or _has_initiliaze_modifier(f)) and f.is_implemented if (f.name == "initialize" or _has_initialize_modifier(f)) and f.is_implemented
] ]
@ -313,7 +313,7 @@ contract DerivedDerived is Derived{
} }
``` ```
`Base.initialize(uint)` is called two times in `DerivedDerived.initiliaze` execution, leading to a potential corruption. `Base.initialize(uint)` is called two times in `DerivedDerived.initialize` execution, leading to a potential corruption.
""" """
# endregion wiki_exploit_scenario # endregion wiki_exploit_scenario

@ -1,5 +1,6 @@
from functools import partial from functools import partial
import platform import platform
import sys
class Colors: # pylint: disable=too-few-public-methods class Colors: # pylint: disable=too-few-public-methods
@ -73,7 +74,7 @@ def set_colorization_enabled(enabled: bool):
if enabled and platform.system() == "Windows": if enabled and platform.system() == "Windows":
Colors.COLORIZATION_ENABLED = enable_windows_virtual_terminal_sequences() Colors.COLORIZATION_ENABLED = enable_windows_virtual_terminal_sequences()
else: else:
# This is not windows so we can enable color immediately. # This is not windows, or colorization is being disabled, so we can adjust the state immediately.
Colors.COLORIZATION_ENABLED = enabled Colors.COLORIZATION_ENABLED = enabled
@ -83,6 +84,5 @@ red = partial(colorize, Colors.RED)
blue = partial(colorize, Colors.BLUE) blue = partial(colorize, Colors.BLUE)
magenta = partial(colorize, Colors.MAGENTA) magenta = partial(colorize, Colors.MAGENTA)
# We enable colorization by default (this call is important as it will enable color mode on Windows by default), # We enable colorization by default if the output is a tty
# regardless of whether Slither is interacted with from CLI or another script. set_colorization_enabled(sys.stdout.isatty())
set_colorization_enabled(True)

@ -340,6 +340,53 @@ ERC2612 = [
ERC2612_signatures = erc_to_signatures(ERC2612) ERC2612_signatures = erc_to_signatures(ERC2612)
# Review
# https://eips.ethereum.org/EIPS/eip-1363
# Must have ERC20 and ERC165
ERC1363_EVENTS = []
ERC1363 = (
[
ERC("transferAndCall", ["address", "uint256"], "bool", False, True, []),
ERC("transferAndCall", ["address", "uint256", "bytes"], "bool", False, True, []),
ERC("transferFromAndCall", ["address", "address", "uint256"], "bool", False, True, []),
ERC(
"transferFromAndCall",
["address", "address", "uint256", "bytes"],
"bool",
False,
True,
[],
),
ERC("approveAndCall", ["address", "uint256"], "bool", False, True, []),
ERC("approveAndCall", ["address", "uint256", "bytes"], "bool", False, True, []),
]
+ ERC20
+ ERC165
)
ERC1363_signatures = erc_to_signatures(ERC1363)
# Review
# https://eips.ethereum.org/EIPS/eip-4524
# Must have ERC20 and ERC165
ERC4524_EVENTS = []
ERC4524 = (
[
ERC("safeTransfer", ["address", "uint256"], "bool", False, True, []),
ERC("safeTransfer", ["address", "uint256", "bytes"], "bool", False, True, []),
ERC("safeTransferFrom", ["address", "address", "uint256"], "bool", False, True, []),
ERC(
"safeTransferFrom", ["address", "address", "uint256", "bytes"], "bool", False, True, []
),
]
+ ERC20
+ ERC165
)
ERC4524_signatures = erc_to_signatures(ERC4524)
# Final # Final
# https://eips.ethereum.org/EIPS/eip-4626 # https://eips.ethereum.org/EIPS/eip-4626
# Must have ERC20 # Must have ERC20
@ -405,5 +452,7 @@ ERCS = {
"ERC777": (ERC777, ERC777_EVENTS), "ERC777": (ERC777, ERC777_EVENTS),
"ERC1155": (ERC1155, ERC1155_EVENTS), "ERC1155": (ERC1155, ERC1155_EVENTS),
"ERC2612": (ERC2612, ERC2612_EVENTS), "ERC2612": (ERC2612, ERC2612_EVENTS),
"ERC1363": (ERC1363, ERC1363_EVENTS),
"ERC4524": (ERC4524, ERC4524_EVENTS),
"ERC4626": (ERC4626, ERC4626_EVENTS), "ERC4626": (ERC4626, ERC4626_EVENTS),
} }

@ -7,7 +7,7 @@ from slither.exceptions import SlitherError
def convert_string_to_int(val: Union[str, int]) -> int: def convert_string_to_int(val: Union[str, int]) -> int:
if isinstance(val, int): if isinstance(val, int):
return val return val
if val.startswith("0x") or val.startswith("0X"): if val.startswith(("0x", "0X")):
return int(val, 16) return int(val, 16)
if "e" in val or "E" in val: if "e" in val or "E" in val:

@ -160,7 +160,7 @@ def output_to_sarif(
], ],
} }
for detector in results["detectors"]: for detector in results.get("detectors", []):
_output_result_to_sarif(detector, detectors_classes, sarif) _output_result_to_sarif(detector, detectors_classes, sarif)
if filename == "-": if filename == "-":

@ -9,6 +9,7 @@ from slither.core.declarations import (
SolidityFunction, SolidityFunction,
Contract, Contract,
) )
from slither.core.declarations.enum import Enum
from slither.core.expressions import ( from slither.core.expressions import (
AssignmentOperationType, AssignmentOperationType,
UnaryOperationType, UnaryOperationType,
@ -403,18 +404,25 @@ class ExpressionToSlithIR(ExpressionVisitor):
assert len(expression.expression.arguments) == 1 assert len(expression.expression.arguments) == 1
val = TemporaryVariable(self._node) val = TemporaryVariable(self._node)
type_expression_found = expression.expression.arguments[0] type_expression_found = expression.expression.arguments[0]
assert isinstance(type_expression_found, ElementaryTypeNameExpression) if isinstance(type_expression_found, ElementaryTypeNameExpression):
type_found = type_expression_found.type type_found = type_expression_found.type
if expression.member_name == "min:": constant_type = type_found
else:
# type(enum).max/min
assert isinstance(type_expression_found, Identifier)
type_found = type_expression_found.value
assert isinstance(type_found, Enum)
constant_type = None
if expression.member_name == "min":
op = Assignment( op = Assignment(
val, val,
Constant(str(type_found.min), type_found), Constant(str(type_found.min), constant_type),
type_found, type_found,
) )
else: else:
op = Assignment( op = Assignment(
val, val,
Constant(str(type_found.max), type_found), Constant(str(type_found.max), constant_type),
type_found, type_found,
) )
self._result.append(op) self._result.append(op)

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

Loading…
Cancel
Save