Merge branch 'dev' into dev-upgradeability-util-cross-contract-taint

pull/1816/head
William E Bodell III 2 years ago committed by GitHub
commit 6a10e0a1e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .github/scripts/unit_test_runner.sh
  2. 2
      .github/workflows/black.yml
  3. 1
      .github/workflows/ci.yml
  4. 2
      .github/workflows/linter.yml
  5. 2
      .github/workflows/pip-audit.yml
  6. 2
      .github/workflows/pylint.yml
  7. 2
      .github/workflows/test.yml
  8. 1
      .gitignore
  9. 83
      CONTRIBUTING.md
  10. 88
      Makefile
  11. 8
      examples/flat/a.sol
  12. 13
      examples/flat/b.sol
  13. 10
      scripts/ci_test_flat.sh
  14. 95
      scripts/ci_test_interface.sh
  15. 13
      setup.py
  16. 3
      slither/__init__.py
  17. 1
      slither/analyses/data_dependency/data_dependency.py
  18. 15
      slither/core/cfg/node.py
  19. 8
      slither/core/compilation_unit.py
  20. 2
      slither/core/declarations/contract.py
  21. 7
      slither/core/declarations/custom_error.py
  22. 4
      slither/core/declarations/solidity_variables.py
  23. 2
      slither/detectors/all_detectors.py
  24. 4
      slither/detectors/assembly/shift_parameter_mixup.py
  25. 104
      slither/detectors/operations/encode_packed.py
  26. 32
      slither/detectors/operations/unused_return_values.py
  27. 31
      slither/detectors/reentrancy/reentrancy_events.py
  28. 23
      slither/detectors/statements/incorrect_strict_equality.py
  29. 223
      slither/detectors/statements/incorrect_using_for.py
  30. 11
      slither/detectors/variables/uninitialized_local_variables.py
  31. 2
      slither/detectors/variables/var_read_using_this.py
  32. 2
      slither/printers/functions/authorization.py
  33. 2
      slither/printers/functions/cfg.py
  34. 4
      slither/printers/inheritance/inheritance.py
  35. 2
      slither/printers/inheritance/inheritance_graph.py
  36. 3
      slither/printers/summary/contract.py
  37. 2
      slither/printers/summary/data_depenency.py
  38. 2
      slither/printers/summary/function.py
  39. 2
      slither/printers/summary/human_summary.py
  40. 2
      slither/printers/summary/slithir.py
  41. 2
      slither/printers/summary/slithir_ssa.py
  42. 6
      slither/printers/summary/when_not_paused.py
  43. 9
      slither/slithir/convert.py
  44. 2
      slither/slithir/operations/assignment.py
  45. 1
      slither/slithir/operations/binary.py
  46. 5
      slither/slithir/operations/new_array.py
  47. 3
      slither/slithir/operations/new_contract.py
  48. 3
      slither/slithir/operations/new_structure.py
  49. 2
      slither/slithir/operations/unary.py
  50. 3
      slither/solc_parsing/declarations/contract.py
  51. 15
      slither/solc_parsing/declarations/function.py
  52. 3
      slither/solc_parsing/declarations/modifier.py
  53. 5
      slither/solc_parsing/slither_compilation_unit_solc.py
  54. 2
      slither/tools/flattening/export/export.py
  55. 43
      slither/tools/flattening/flattening.py
  56. 0
      slither/tools/interface/__init__.py
  57. 105
      slither/tools/interface/__main__.py
  58. 2
      slither/tools/read_storage/read_storage.py
  59. 167
      slither/utils/code_generation.py
  60. 11
      slither/utils/expression_manipulations.py
  61. 6
      slither/utils/myprettytable.py
  62. 15
      slither/utils/type.py
  63. 7
      slither/visitors/slithir/expression_to_slithir.py
  64. 79
      tests/conftest.py
  65. 13
      tests/e2e/compilation/test_resolution.py
  66. 18
      tests/e2e/detectors/snapshots/detectors__detector_ABIEncoderV2Array_0_4_25_storage_ABIEncoderV2_array_sol__0.txt
  67. 0
      tests/e2e/detectors/snapshots/detectors__detector_ABIEncoderV2Array_0_5_10_storage_ABIEncoderV2_array_sol__0.txt
  68. 18
      tests/e2e/detectors/snapshots/detectors__detector_ABIEncoderV2Array_0_5_9_storage_ABIEncoderV2_array_sol__0.txt
  69. 6
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20NoPermit_0_4_25_arbitrary_send_erc20_sol__0.txt
  70. 6
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20NoPermit_0_5_16_arbitrary_send_erc20_sol__0.txt
  71. 6
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20NoPermit_0_6_11_arbitrary_send_erc20_sol__0.txt
  72. 6
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20NoPermit_0_7_6_arbitrary_send_erc20_sol__0.txt
  73. 2
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20NoPermit_0_8_0_arbitrary_send_erc20_inheritance_sol__0.txt
  74. 6
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20NoPermit_0_8_0_arbitrary_send_erc20_sol__0.txt
  75. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20Permit_0_4_25_arbitrary_send_erc20_permit_sol__0.txt
  76. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20Permit_0_5_16_arbitrary_send_erc20_permit_sol__0.txt
  77. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20Permit_0_6_11_arbitrary_send_erc20_permit_sol__0.txt
  78. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20Permit_0_7_6_arbitrary_send_erc20_permit_sol__0.txt
  79. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendErc20Permit_0_8_0_arbitrary_send_erc20_permit_sol__0.txt
  80. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendEth_0_4_25_arbitrary_send_eth_sol__0.txt
  81. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendEth_0_5_16_arbitrary_send_eth_sol__0.txt
  82. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendEth_0_6_11_arbitrary_send_eth_sol__0.txt
  83. 8
      tests/e2e/detectors/snapshots/detectors__detector_ArbitrarySendEth_0_7_6_arbitrary_send_eth_sol__0.txt
  84. 12
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_4_25_array_by_reference_sol__0.txt
  85. 12
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_5_16_array_by_reference_sol__0.txt
  86. 12
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_6_11_array_by_reference_sol__0.txt
  87. 12
      tests/e2e/detectors/snapshots/detectors__detector_ArrayByReference_0_7_6_array_by_reference_sol__0.txt
  88. 9
      tests/e2e/detectors/snapshots/detectors__detector_ArrayLengthAssignment_0_4_25_array_length_assignment_sol__0.txt
  89. 9
      tests/e2e/detectors/snapshots/detectors__detector_ArrayLengthAssignment_0_5_16_array_length_assignment_sol__0.txt
  90. 3
      tests/e2e/detectors/snapshots/detectors__detector_Assembly_0_4_25_inline_assembly_contract_sol__0.txt
  91. 6
      tests/e2e/detectors/snapshots/detectors__detector_Assembly_0_4_25_inline_assembly_library_sol__0.txt
  92. 3
      tests/e2e/detectors/snapshots/detectors__detector_Assembly_0_5_16_inline_assembly_contract_sol__0.txt
  93. 6
      tests/e2e/detectors/snapshots/detectors__detector_Assembly_0_5_16_inline_assembly_library_sol__0.txt
  94. 3
      tests/e2e/detectors/snapshots/detectors__detector_Assembly_0_6_11_inline_assembly_contract_sol__0.txt
  95. 6
      tests/e2e/detectors/snapshots/detectors__detector_Assembly_0_6_11_inline_assembly_library_sol__0.txt
  96. 3
      tests/e2e/detectors/snapshots/detectors__detector_Assembly_0_7_6_inline_assembly_contract_sol__0.txt
  97. 6
      tests/e2e/detectors/snapshots/detectors__detector_Assembly_0_7_6_inline_assembly_library_sol__0.txt
  98. 12
      tests/e2e/detectors/snapshots/detectors__detector_AssertStateChange_0_4_25_assert_state_change_sol__0.txt
  99. 12
      tests/e2e/detectors/snapshots/detectors__detector_AssertStateChange_0_5_16_assert_state_change_sol__0.txt
  100. 12
      tests/e2e/detectors/snapshots/detectors__detector_AssertStateChange_0_6_11_assert_state_change_sol__0.txt
  101. Some files were not shown because too many files have changed in this diff Show More

@ -2,11 +2,11 @@
# used to pass --cov=$path and --cov-append to pytest
if [ "$1" != "" ]; then
pytest "$1" tests/unit/
pytest "$1" tests/unit/ -n auto
status_code=$?
python -m coverage report
else
pytest tests/unit/
pytest tests/unit/ -n auto
status_code=$?
fi

@ -42,7 +42,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Black
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

@ -36,6 +36,7 @@ jobs:
"etherscan",
"find_paths",
"flat",
"interface",
"kspec",
"printers",
# "prop"

@ -43,7 +43,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Lint everything else
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# run linter on everything to catch preexisting problems

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

@ -37,7 +37,7 @@ jobs:
cp pyproject.toml .github/linters
- name: Pylint
uses: github/super-linter/slim@v4.9.2
uses: super-linter/super-linter/slim@v4.9.2
if: always()
env:
# Run linters only on new files for pylint to speed up the CI

@ -37,8 +37,6 @@ jobs:
- name: Install dependencies
run: |
pip install ".[test]"
solc-select install 0.8.0
solc-select use 0.8.0
- name: Setup node
uses: actions/setup-node@v3

1
.gitignore vendored

@ -42,6 +42,7 @@ htmlcov/
.coverage
.coverage.*
.cache
.pytest_cache
nosetests.xml
coverage.xml
*.cover

@ -1,15 +1,19 @@
# Contributing to Slither
First, thanks for your interest in contributing to Slither! We welcome and appreciate all contributions, including bug reports, feature suggestions, tutorials/blog posts, and code improvements.
If you're unsure where to start, we recommend our [`good first issue`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/slither/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels.
## 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.
## Questions
Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel).
Questions can be submitted to the "Discussions" page, and you may also join our [chat room](https://empireslacking.herokuapp.com/) (in the #ethereum channel).
## 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/).
Some pull request guidelines:
@ -23,6 +27,7 @@ Some pull request guidelines:
## Directory Structure
Below is a rough outline of slither's design:
```text
.
├── analyses # Provides additional info such as data dependency
@ -39,9 +44,10 @@ Below is a rough outline of slither's design:
A code walkthrough is available [here](https://www.youtube.com/watch?v=EUl3UlYSluU).
## Development Environment
Instructions for installing a development version of Slither can be found in our [wiki](https://github.com/crytic/slither/wiki/Developer-installation).
To run the unit tests, you need to clone this repository and run `pip install ".[dev]"`.
To run the unit tests, you need to clone this repository and run `make test`. Run a specific test with `make test TESTS=$test_name`. The names of tests can be obtained with `pytest tests --collect-only`.
### Linters
@ -49,40 +55,63 @@ Several linters and security checkers are run on the PRs.
To run them locally in the root dir of the repository:
- `pylint slither tests --rcfile pyproject.toml`
- `black . --config pyproject.toml`
- `make lint`
> Note, this only validates but does not modify the code.
To automatically reformat the code:
- `make reformat`
We use pylint `2.13.4`, black `22.3.0`.
### Detectors tests
### Testing
Slither's test suite is divided into three categories end-to-end (`tests/e2e`), unit (`tests/unit`), and tools (`tests/tools/`).
How do I know what kind of test(s) to write?
- End-to-end: functionality that requires invoking `Slither` and inspecting some output such as printers and detectors.
- Unit: additions and modifications to objects should be accompanied by a unit test that defines the expected behavior. Aim to write functions in as pure a way as possible such that they are easier to test.
- Tools: tools built on top of Slither (`slither/tools`) but not apart of its core functionality
#### Adding detector tests
For each new detector, at least one regression tests must be present.
- Create a test in `tests`
- Update `ALL_TEST` in `tests/test_detectors.py`
- Run `python ./tests/test_detectors.py --generate`. This will generate the json artifacts in `tests/expected_json`. Add the generated files to git.
- If updating an existing detector, identify the respective json artifacts and then delete them, or run `python ./tests/test_detectors.py --overwrite` instead.
- Run `pytest ./tests/test_detectors.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`.
To run tests for a specific detector, run `pytest tests/test_detectors.py -k ReentrancyReadBeforeWritten` (the detector's class name is the argument).
To run tests for a specific version, run `pytest tests/test_detectors.py -k 0.7.6`.
The IDs of tests can be inspected using `pytest tests/test_detectors.py --collect-only`.
### Parser tests
- Create a test in `tests/ast-parsing`
- Run `python ./tests/test_ast_parsing.py --compile`. This will compile the artifact in `tests/ast-parsing/compile`. Add the compiled artifact to git.
- Run `python ./tests/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/ast-parsing/expected_json`. Add the generated files to git.
- Run `pytest ./tests/test_ast_parsing.py` and check that everything worked.
To see the tests coverage, run `pytest tests/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`
To run tests for a specific test case, run `pytest tests/test_ast_parsing.py -k user_defined_value_type` (the filename is the argument).
To run tests for a specific version, run `pytest tests/test_ast_parsing.py -k 0.8.12`.
To run tests for a specific compiler json format, run `pytest tests/test_ast_parsing.py -k legacy` (can be legacy or compact).
The IDs of tests can be inspected using ``pytest tests/test_ast_parsing.py --collect-only`.
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_TEST` 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.
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.
> ##### Helpful commands for detector tests
>
> - To see the tests coverage, run `pytest tests/e2e/detectors/test_detectors.py --cov=slither/detectors --cov-branch --cov-report html`.
> - To run tests for a specific detector, run `pytest tests/e2e/detectors/test_detectors.py -k ReentrancyReadBeforeWritten`(the detector's class name is the argument).
> - To run tests for a specific version, run `pytest tests/e2e/detectors/test_detectors.py -k 0.7.6`.
> - The IDs of tests can be inspected using `pytest tests/e2e/detectors/test_detectors.py --collect-only`.
#### Adding parsing tests
1. Create a test in `tests/e2e/solc_parsing/`
2. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --compile`. This will compile the artifact in `tests/e2e/solc_parsing/compile`. Add the compiled artifact to git.
3. Run `python tests/e2e/solc_parsing/test_ast_parsing.py --generate`. This will generate the json artifacts in `tests/e2e/solc_parsing/expected_json`. Add the generated files to git.
4. Run `pytest tests/e2e/solc_parsing/test_ast_parsing.py` and check that everything worked.
> ##### Helpful commands for parsing tests
>
> - To see the tests coverage, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py --cov=slither/solc_parsing --cov-branch --cov-report html`
> - To run tests for a specific test case, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k user_defined_value_type` (the filename is the argument).
> - To run tests for a specific version, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k 0.8.12`.
> - To run tests for a specific compiler json format, run `pytest tests/e2e/solc_parsing/test_ast_parsing.py -k legacy` (can be legacy or compact).
> - The IDs of tests can be inspected using ``pytest tests/e2e/solc_parsing/test_ast_parsing.py --collect-only`.
### Synchronization with crytic-compile
By default, `slither` follows either the latest version of crytic-compile in pip, or `crytic-compile@master` (look for dependencies in [`setup.py`](./setup.py). If crytic-compile development comes with breaking changes, the process to update `slither` is:
- Update `slither/setup.py` to point to the related crytic-compile's branch
- Create a PR in `slither` and ensure it passes the CI
- Once the development branch is merged in `crytic-compile@master`, ensure `slither` follows the `master` branch

@ -0,0 +1,88 @@
SHELL := /bin/bash
PY_MODULE := slither
TEST_MODULE := tests
ALL_PY_SRCS := $(shell find $(PY_MODULE) -name '*.py') \
$(shell find test -name '*.py')
# Optionally overriden by the user, if they're using a virtual environment manager.
VENV ?= env
# On Windows, venv scripts/shims are under `Scripts` instead of `bin`.
VENV_BIN := $(VENV)/bin
ifeq ($(OS),Windows_NT)
VENV_BIN := $(VENV)/Scripts
endif
# Optionally overridden by the user in the `release` target.
BUMP_ARGS :=
# Optionally overridden by the user in the `test` target.
TESTS :=
# Optionally overridden by the user/CI, to limit the installation to a specific
# subset of development dependencies.
SLITHER_EXTRA := dev
# If the user selects a specific test pattern to run, set `pytest` to fail fast
# and only run tests that match the pattern.
# Otherwise, run all tests and enable coverage assertions, since we expect
# complete test coverage.
ifneq ($(TESTS),)
TEST_ARGS := -x -k $(TESTS)
COV_ARGS :=
else
TEST_ARGS := -n auto
COV_ARGS := # --fail-under 100
endif
.PHONY: all
all:
@echo "Run my targets individually!"
.PHONY: dev
dev: $(VENV)/pyvenv.cfg
.PHONY: run
run: $(VENV)/pyvenv.cfg
@. $(VENV_BIN)/activate && slither $(ARGS)
$(VENV)/pyvenv.cfg: pyproject.toml
# Create our Python 3 virtual environment
python3 -m venv env
$(VENV_BIN)/python -m pip install --upgrade pip
$(VENV_BIN)/python -m pip install -e .[$(SLITHER_EXTRA)]
.PHONY: lint
lint: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
black --check . && \
pylint $(PY_MODULE) $(TEST_MODULE)
# ruff $(ALL_PY_SRCS) && \
# mypy $(PY_MODULE) &&
.PHONY: reformat
reformat:
. $(VENV_BIN)/activate && \
black .
.PHONY: test tests
test tests: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
pytest --cov=$(PY_MODULE) $(T) $(TEST_ARGS) && \
python -m coverage report -m $(COV_ARGS)
.PHONY: doc
doc: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
PDOC_ALLOW_EXEC=1 pdoc -o html slither '!slither.tools'
.PHONY: package
package: $(VENV)/pyvenv.cfg
. $(VENV_BIN)/activate && \
python3 -m build
.PHONY: edit
edit:
$(EDITOR) $(ALL_PY_SRCS)

@ -1,3 +1,9 @@
contract A{
pragma solidity 0.8.19;
error RevertIt();
contract Example {
function reverts() external pure {
revert RevertIt();
}
}

@ -1,5 +1,16 @@
import "./a.sol";
contract B is A{
pragma solidity 0.8.19;
enum B {
a,
b
}
contract T {
Example e = new Example();
function b() public returns(uint) {
B b = B.a;
return 4;
}
}

@ -1,6 +1,8 @@
#!/usr/bin/env bash
shopt -s extglob
### Test slither-prop
### Test slither-flat
solc-select use 0.8.19 --always-install
cd examples/flat || exit 1
@ -9,4 +11,10 @@ if ! slither-flat b.sol; then
exit 1
fi
SUFFIX="@(sol)"
if ! solc "crytic-export/flattening/"*$SUFFIX; then
echo "solc failed on flattened files"
exit 1
fi
exit 0

@ -0,0 +1,95 @@
#!/usr/bin/env bash
### Test slither-interface
DIR_TESTS="tests/tools/interface"
solc-select use 0.8.19 --always-install
#Test 1 - Etherscan target
slither-interface WETH9 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
DIFF=$(diff crytic-export/interfaces/IWETH9.sol "$DIR_TESTS/test_1.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 1 failed"
cat "crytic-export/interfaces/IWETH9.sol"
echo ""
cat "$DIR_TESTS/test_1.sol"
exit 255
fi
#Test 2 - Local file target
slither-interface Mock tests/tools/interface/ContractMock.sol
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_2.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 2 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_2.sol"
exit 255
fi
#Test 3 - unroll structs
slither-interface Mock tests/tools/interface/ContractMock.sol --unroll-structs
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_3.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 3 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_3.sol"
exit 255
fi
#Test 4 - exclude structs
slither-interface Mock tests/tools/interface/ContractMock.sol --exclude-structs
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_4.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 4 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_4.sol"
exit 255
fi
#Test 5 - exclude errors
slither-interface Mock tests/tools/interface/ContractMock.sol --exclude-errors
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_5.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 5 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_5.sol"
exit 255
fi
#Test 6 - exclude enums
slither-interface Mock tests/tools/interface/ContractMock.sol --exclude-enums
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_6.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 6 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_6.sol"
exit 255
fi
#Test 7 - exclude events
slither-interface Mock tests/tools/interface/ContractMock.sol --exclude-events
DIFF=$(diff crytic-export/interfaces/IMock.sol "$DIR_TESTS/test_7.sol" --strip-trailing-cr)
if [ "$DIFF" != "" ]
then
echo "slither-interface test 7 failed"
cat "crytic-export/interfaces/IMock.sol"
echo ""
cat "$DIR_TESTS/test_7.sol"
exit 255
fi
rm -r crytic-export

@ -13,11 +13,14 @@ setup(
python_requires=">=3.8",
install_requires=[
"packaging",
"prettytable>=0.7.2",
"prettytable>=3.3.0",
"pycryptodome>=3.4.6",
"crytic-compile>=0.3.1,<0.4.0",
# "crytic-compile@git+https://github.com/crytic/crytic-compile.git@master#egg=crytic-compile",
# "crytic-compile>=0.3.1,<0.4.0",
"crytic-compile@git+https://github.com/crytic/crytic-compile.git@dev#egg=crytic-compile",
"web3>=6.0.0",
"eth-abi>=4.0.0",
"eth-typing>=3.0.0",
"eth-utils>=2.1.0",
],
extras_require={
"lint": [
@ -31,6 +34,9 @@ setup(
"deepdiff",
"numpy",
"coverage[toml]",
"filelock",
"pytest-insta",
"solc-select@git+https://github.com/crytic/solc-select.git@query-artifact-path#egg=solc-select",
],
"doc": [
"pdoc",
@ -58,6 +64,7 @@ setup(
"slither-read-storage = slither.tools.read_storage.__main__:main",
"slither-doctor = slither.tools.doctor.__main__:main",
"slither-documentation = slither.tools.documentation.__main__:main",
"slither-interface = slither.tools.interface.__main__:main",
]
},
)

@ -1 +1,4 @@
"""
.. include:: ../README.md
"""
from .slither import Slither

@ -129,6 +129,7 @@ GENERIC_TAINT = {
SolidityVariableComposed("msg.value"),
SolidityVariableComposed("msg.data"),
SolidityVariableComposed("tx.origin"),
SolidityVariableComposed("tx.gasprice"),
}

@ -620,6 +620,21 @@ class Node(SourceMapping): # pylint: disable=too-many-public-methods
"""
self._sons.append(son)
def replace_son(self, ori_son: "Node", new_son: "Node") -> None:
"""Replace a son node. Do nothing if the node to replace is not a son
Args:
ori_son: son to replace
new_son: son to replace with
"""
for i, s in enumerate(self._sons):
if s.node_id == ori_son.node_id:
idx = i
break
else:
return
self._sons[idx] = new_son
def set_sons(self, sons: List["Node"]) -> None:
"""Set the son nodes

@ -13,7 +13,7 @@ from slither.core.declarations import (
Function,
Modifier,
)
from slither.core.declarations.custom_error import CustomError
from slither.core.declarations.custom_error_top_level import CustomErrorTopLevel
from slither.core.declarations.enum_top_level import EnumTopLevel
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.structure_top_level import StructureTopLevel
@ -46,7 +46,7 @@ class SlitherCompilationUnit(Context):
self._using_for_top_level: List[UsingForTopLevel] = []
self._pragma_directives: List[Pragma] = []
self._import_directives: List[Import] = []
self._custom_errors: List[CustomError] = []
self._custom_errors: List[CustomErrorTopLevel] = []
self._user_defined_value_types: Dict[str, TypeAliasTopLevel] = {}
self._all_functions: Set[Function] = set()
@ -128,7 +128,7 @@ class SlitherCompilationUnit(Context):
"""list(Contract): List of contracts that are derived and not inherited."""
inheritances = [x.inheritance for x in self.contracts]
inheritance = [item for sublist in inheritances for item in sublist]
return [c for c in self.contracts if c not in inheritance and not c.is_top_level]
return [c for c in self.contracts if c not in inheritance]
def get_contract_from_name(self, contract_name: Union[str, Constant]) -> List[Contract]:
"""
@ -216,7 +216,7 @@ class SlitherCompilationUnit(Context):
return self._using_for_top_level
@property
def custom_errors(self) -> List[CustomError]:
def custom_errors(self) -> List[CustomErrorTopLevel]:
return self._custom_errors
@property

@ -100,8 +100,6 @@ class Contract(SourceMapping): # pylint: disable=too-many-public-methods
self._is_upgradeable_proxy: Optional[bool] = None
self._upgradeable_version: Optional[str] = None
self.is_top_level = False # heavily used, so no @property
self._initial_state_variables: List["StateVariable"] = [] # ssa
self._is_incorrectly_parsed: bool = False

@ -1,8 +1,8 @@
from typing import List, TYPE_CHECKING, Optional, Type
from slither.core.solidity_types import UserDefinedType
from slither.core.source_mapping.source_mapping import SourceMapping
from slither.core.variables.local_variable import LocalVariable
from slither.utils.type import is_underlying_type_address
if TYPE_CHECKING:
from slither.core.compilation_unit import SlitherCompilationUnit
@ -43,10 +43,7 @@ class CustomError(SourceMapping):
@staticmethod
def _convert_type_for_solidity_signature(t: Optional[Type]) -> str:
# pylint: disable=import-outside-toplevel
from slither.core.declarations import Contract
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
if is_underlying_type_address(t):
return "address"
return str(t)

@ -201,6 +201,10 @@ class SolidityCustomRevert(SolidityFunction):
self._custom_error = custom_error
self._return_type: List[Union[TypeInformation, ElementaryType]] = []
@property
def custom_error(self) -> CustomError:
return self._custom_error
def __eq__(self, other: Any) -> bool:
return (
self.__class__ == other.__class__

@ -89,3 +89,5 @@ from .functions.protected_variable import ProtectedVariables
from .functions.permit_domain_signature_collision import DomainSeparatorCollision
from .functions.codex import Codex
from .functions.cyclomatic_complexity import CyclomaticComplexity
from .statements.incorrect_using_for import IncorrectUsingFor
from .operations.encode_packed import EncodePackedCollision

@ -52,7 +52,9 @@ The shift statement will right-shift the constant 8 by `a` bits"""
BinaryType.LEFT_SHIFT,
BinaryType.RIGHT_SHIFT,
]:
if isinstance(ir.variable_left, Constant):
if isinstance(ir.variable_left, Constant) and not isinstance(
ir.variable_right, Constant
):
info: DETECTOR_INFO = [
f,
" contains an incorrect shift operation: ",

@ -0,0 +1,104 @@
"""
Module detecting usage of more than one dynamic type in abi.encodePacked() arguments which could lead to collision
"""
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.core.declarations.solidity_variables import SolidityFunction
from slither.slithir.operations import SolidityCall
from slither.analyses.data_dependency.data_dependency import is_tainted
from slither.core.solidity_types import ElementaryType
from slither.core.solidity_types import ArrayType
def _is_dynamic_type(arg):
"""
Args:
arg (function argument)
Returns:
Bool
"""
if isinstance(arg.type, ElementaryType) and (arg.type.name in ["string", "bytes"]):
return True
if isinstance(arg.type, ArrayType) and arg.type.length is None:
return True
return False
def _detect_abi_encodePacked_collision(contract):
"""
Args:
contract (Contract)
Returns:
list((Function), (list (Node)))
"""
ret = []
# pylint: disable=too-many-nested-blocks
for f in contract.functions_and_modifiers_declared:
for n in f.nodes:
for ir in n.irs:
if isinstance(ir, SolidityCall) and ir.function == SolidityFunction(
"abi.encodePacked()"
):
dynamic_type_count = 0
for arg in ir.arguments:
if is_tainted(arg, contract) and _is_dynamic_type(arg):
dynamic_type_count += 1
elif dynamic_type_count > 1:
ret.append((f, n))
dynamic_type_count = 0
else:
dynamic_type_count = 0
if dynamic_type_count > 1:
ret.append((f, n))
return ret
class EncodePackedCollision(AbstractDetector):
"""
Detect usage of more than one dynamic type in abi.encodePacked() arguments which could to collision
"""
ARGUMENT = "encode-packed-collision"
HELP = "ABI encodePacked Collision"
IMPACT = DetectorClassification.HIGH
CONFIDENCE = DetectorClassification.HIGH
WIKI = (
"https://github.com/crytic/slither/wiki/Detector-Documentation#abi-encodePacked-collision"
)
WIKI_TITLE = "ABI encodePacked Collision"
WIKI_DESCRIPTION = """Detect collision due to dynamic type usages in `abi.encodePacked`"""
WIKI_EXPLOIT_SCENARIO = """
```solidity
contract Sign {
function get_hash_for_signature(string name, string doc) external returns(bytes32) {
return keccak256(abi.encodePacked(name, doc));
}
}
```
Bob calls `get_hash_for_signature` with (`bob`, `This is the content`). The hash returned is used as an ID.
Eve creates a collision with the ID using (`bo`, `bThis is the content`) and compromises the system.
"""
WIKI_RECOMMENDATION = """Do not use more than one dynamic type in `abi.encodePacked()`
(see the [Solidity documentation](https://solidity.readthedocs.io/en/v0.5.10/abi-spec.html?highlight=abi.encodePacked#non-standard-packed-modeDynamic)).
Use `abi.encode()`, preferably."""
def _detect(self):
"""Detect usage of more than one dynamic type in abi.encodePacked(..) arguments which could lead to collision"""
results = []
for c in self.compilation_unit.contracts:
values = _detect_abi_encodePacked_collision(c)
for func, node in values:
info = [
func,
" calls abi.encodePacked() with multiple dynamic arguments:\n\t- ",
node,
"\n",
]
json = self.generate_result(info)
results.append(json)
return results

@ -3,7 +3,7 @@ Module detecting unused return values from external calls
"""
from typing import List
from slither.core.cfg.node import Node
from slither.core.cfg.node import Node, NodeType
from slither.core.declarations import Function
from slither.core.declarations.function_contract import FunctionContract
from slither.core.variables.state_variable import StateVariable
@ -12,8 +12,8 @@ from slither.detectors.abstract_detector import (
DetectorClassification,
DETECTOR_INFO,
)
from slither.slithir.operations import HighLevelCall
from slither.slithir.operations.operation import Operation
from slither.slithir.operations import HighLevelCall, Assignment, Unpack, Operation
from slither.slithir.variables import TupleVariable
from slither.utils.output import Output
@ -50,7 +50,9 @@ 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
return isinstance(ir, HighLevelCall) and (
return (
isinstance(ir, HighLevelCall)
and (
(
isinstance(ir.function, Function)
and ir.function.solidity_signature
@ -58,6 +60,9 @@ contract MyConc{
)
or not isinstance(ir.function, Function)
)
or ir.node.type == NodeType.TRY
and isinstance(ir, (Assignment, Unpack))
)
def detect_unused_return_values(
self, f: FunctionContract
@ -71,18 +76,27 @@ contract MyConc{
"""
values_returned = []
nodes_origin = {}
# pylint: disable=too-many-nested-blocks
for n in f.nodes:
for ir in n.irs:
if self._is_instance(ir):
# if a return value is stored in a state variable, it's ok
if ir.lvalue and not isinstance(ir.lvalue, StateVariable):
values_returned.append(ir.lvalue)
values_returned.append((ir.lvalue, None))
nodes_origin[ir.lvalue] = ir
if isinstance(ir.lvalue, TupleVariable):
# we iterate the number of elements the tuple has
# and add a (variable, index) in values_returned for each of them
for index in range(len(ir.lvalue.type)):
values_returned.append((ir.lvalue, index))
for read in ir.read:
if read in values_returned:
values_returned.remove(read)
return [nodes_origin[value].node for value in values_returned]
remove = (read, ir.index) if isinstance(ir, Unpack) else (read, None)
if remove in values_returned:
# this is needed to remove the tuple variable when the first time one of its element is used
if remove[1] is not None and (remove[0], None) in values_returned:
values_returned.remove((remove[0], None))
values_returned.remove(remove)
return [nodes_origin[value].node for (value, _) in values_returned]
def _detect(self) -> List[Output]:
"""Detect high level calls which return a value that are never used"""

@ -29,24 +29,45 @@ class ReentrancyEvent(Reentrancy):
# region wiki_description
WIKI_DESCRIPTION = """
Detection of the [reentrancy bug](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy).
Only report reentrancies leading to out-of-order events."""
Detects [reentrancies](https://github.com/trailofbits/not-so-smart-contracts/tree/master/reentrancy) that allow manipulation of the order or value of events."""
# endregion wiki_description
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
function bug(Called d){
contract ReentrantContract {
function f() external {
if (BugReentrancyEvents(msg.sender).counter() == 1) {
BugReentrancyEvents(msg.sender).count(this);
}
}
}
contract Counter {
uint public counter;
event Counter(uint);
}
contract BugReentrancyEvents is Counter {
function count(ReentrantContract d) external {
counter += 1;
d.f();
emit Counter(counter);
}
}
contract NoReentrancyEvents is Counter {
function count(ReentrantContract d) external {
counter += 1;
emit Counter(counter);
d.f();
}
}
```
If `d.()` re-enters, the `Counter` events will be shown in an incorrect order, which might lead to issues for third parties."""
If the external call `d.f()` re-enters `BugReentrancyEvents`, the `Counter` events will be incorrect (`Counter(2)`, `Counter(2)`) whereas `NoReentrancyEvents` will correctly emit
(`Counter(1)`, `Counter(2)`). This may cause issues for offchain components that rely on the values of events e.g. checking for the amount deposited to a bridge."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](http://solidity.readthedocs.io/en/v0.4.21/security-considerations.html#re-entrancy)."
WIKI_RECOMMENDATION = "Apply the [`check-effects-interactions` pattern](https://docs.soliditylang.org/en/latest/security-considerations.html#re-entrancy)."
STANDARD_JSON = False

@ -31,6 +31,7 @@ from slither.slithir.variables.constant import Constant
from slither.slithir.variables.local_variable import LocalIRVariable
from slither.slithir.variables.temporary_ssa import TemporaryVariableSSA
from slither.utils.output import Output
from slither.utils.type import is_underlying_type_address
class IncorrectStrictEquality(AbstractDetector):
@ -72,6 +73,19 @@ contract Crowdsale{
def is_direct_comparison(ir: Operation) -> bool:
return isinstance(ir, Binary) and ir.type == BinaryType.EQUAL
@staticmethod
def is_not_comparing_addresses(ir: Binary) -> bool:
"""
Comparing addresses strictly should not be flagged.
"""
if is_underlying_type_address(ir.variable_left.type) and is_underlying_type_address(
ir.variable_right.type
):
return False
return True
@staticmethod
def is_any_tainted(
variables: List[
@ -108,7 +122,6 @@ contract Crowdsale{
):
taints.append(ir.lvalue)
if isinstance(ir, HighLevelCall):
# print(ir.function.full_name)
if (
isinstance(ir.function, Function)
and ir.function.full_name == "balanceOf(address)"
@ -125,7 +138,6 @@ contract Crowdsale{
if isinstance(ir, Assignment):
if ir.rvalue in self.sources_taint:
taints.append(ir.lvalue)
return taints
# Retrieve all tainted (node, function) pairs
@ -145,7 +157,12 @@ contract Crowdsale{
for ir in node.irs_ssa:
# Filter to only tainted equality (==) comparisons
if self.is_direct_comparison(ir) and self.is_any_tainted(ir.used, taints, func):
if (
self.is_direct_comparison(ir)
# Filter out address comparisons which may occur due to lack of field sensitivity in data dependency
and self.is_not_comparing_addresses(ir)
and self.is_any_tainted(ir.used, taints, func)
):
if func not in results:
results[func] = []
results[func].append(node)

@ -0,0 +1,223 @@
from typing import List
from slither.core.declarations import Contract, Structure, Enum
from slither.core.declarations.using_for_top_level import UsingForTopLevel
from slither.core.solidity_types import (
UserDefinedType,
Type,
ElementaryType,
TypeAlias,
MappingType,
ArrayType,
)
from slither.core.solidity_types.elementary_type import Uint, Int, Byte
from slither.detectors.abstract_detector import (
AbstractDetector,
DetectorClassification,
DETECTOR_INFO,
)
from slither.utils.output import Output
def _is_correctly_used(type_: Type, library: Contract) -> bool:
"""
Checks if a `using library for type_` statement is used correctly (that is, does library contain any function
with type_ as the first argument).
"""
for f in library.functions:
if len(f.parameters) == 0:
continue
if f.parameters[0].type and not _implicitly_convertible_to(type_, f.parameters[0].type):
continue
return True
return False
def _implicitly_convertible_to(type1: Type, type2: Type) -> bool:
"""
Returns True if type1 may be implicitly converted to type2.
"""
if isinstance(type1, TypeAlias) or isinstance(type2, TypeAlias):
if isinstance(type1, TypeAlias) and isinstance(type2, TypeAlias):
return type1.type == type2.type
return False
if isinstance(type1, UserDefinedType) and isinstance(type2, UserDefinedType):
if isinstance(type1.type, Contract) and isinstance(type2.type, Contract):
return _implicitly_convertible_to_for_contracts(type1.type, type2.type)
if isinstance(type1.type, Structure) and isinstance(type2.type, Structure):
return type1.type.canonical_name == type2.type.canonical_name
if isinstance(type1.type, Enum) and isinstance(type2.type, Enum):
return type1.type.canonical_name == type2.type.canonical_name
if isinstance(type1, ElementaryType) and isinstance(type2, ElementaryType):
return _implicitly_convertible_to_for_elementary_types(type1, type2)
if isinstance(type1, MappingType) and isinstance(type2, MappingType):
return _implicitly_convertible_to_for_mappings(type1, type2)
if isinstance(type1, ArrayType) and isinstance(type2, ArrayType):
return _implicitly_convertible_to_for_arrays(type1, type2)
return False
def _implicitly_convertible_to_for_arrays(type1: ArrayType, type2: ArrayType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2.
"""
return _implicitly_convertible_to(type1.type, type2.type)
def _implicitly_convertible_to_for_mappings(type1: MappingType, type2: MappingType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2.
"""
return type1.type_from == type2.type_from and type1.type_to == type2.type_to
def _implicitly_convertible_to_for_elementary_types(
type1: ElementaryType, type2: ElementaryType
) -> bool:
"""
Returns True if type1 may be implicitly converted to type2.
"""
if type1.type == "bool" and type2.type == "bool":
return True
if type1.type == "string" and type2.type == "string":
return True
if type1.type == "bytes" and type2.type == "bytes":
return True
if type1.type == "address" and type2.type == "address":
return _implicitly_convertible_to_for_addresses(type1, type2)
if type1.type in Uint and type2.type in Uint:
return _implicitly_convertible_to_for_uints(type1, type2)
if type1.type in Int and type2.type in Int:
return _implicitly_convertible_to_for_ints(type1, type2)
if (
type1.type != "bytes"
and type2.type != "bytes"
and type1.type in Byte
and type2.type in Byte
):
return _implicitly_convertible_to_for_bytes(type1, type2)
return False
def _implicitly_convertible_to_for_bytes(type1: ElementaryType, type2: ElementaryType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2 assuming they are both bytes.
"""
assert type1.type in Byte and type2.type in Byte
assert type1.size is not None
assert type2.size is not None
return type1.size <= type2.size
def _implicitly_convertible_to_for_addresses(type1: ElementaryType, type2: ElementaryType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2 assuming they are both addresses.
"""
assert type1.type == "address" and type2.type == "address"
# payable attribute to be implemented; for now, always return True
return True
def _implicitly_convertible_to_for_ints(type1: ElementaryType, type2: ElementaryType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2 assuming they are both ints.
"""
assert type1.type in Int and type2.type in Int
assert type1.size is not None
assert type2.size is not None
return type1.size <= type2.size
def _implicitly_convertible_to_for_uints(type1: ElementaryType, type2: ElementaryType) -> bool:
"""
Returns True if type1 may be implicitly converted to type2 assuming they are both uints.
"""
assert type1.type in Uint and type2.type in Uint
assert type1.size is not None
assert type2.size is not None
return type1.size <= type2.size
def _implicitly_convertible_to_for_contracts(contract1: Contract, contract2: Contract) -> bool:
"""
Returns True if contract1 may be implicitly converted to contract2.
"""
return contract1 == contract2 or contract2 in contract1.inheritance
class IncorrectUsingFor(AbstractDetector):
"""
Detector for incorrect using-for statement usage.
"""
ARGUMENT = "incorrect-using-for"
HELP = "Detects using-for statement usage when no function from a given library matches a given type"
IMPACT = DetectorClassification.INFORMATIONAL
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#incorrect-using-for-usage"
WIKI_TITLE = "Incorrect usage of using-for statement"
WIKI_DESCRIPTION = (
"In Solidity, it is possible to use libraries for certain types, by the `using-for` statement "
"(`using <library> for <type>`). However, the Solidity compiler doesn't check whether a given "
"library has at least one function matching a given type. If it doesn't, such a statement has "
"no effect and may be confusing. "
)
# region wiki_exploit_scenario
WIKI_EXPLOIT_SCENARIO = """
```solidity
library L {
function f(bool) public pure {}
}
using L for uint;
```
Such a code will compile despite the fact that `L` has no function with `uint` as its first argument."""
# endregion wiki_exploit_scenario
WIKI_RECOMMENDATION = (
"Make sure that the libraries used in `using-for` statements have at least one function "
"matching a type used in these statements. "
)
def _append_result(
self, results: List[Output], uf: UsingForTopLevel, type_: Type, library: Contract
) -> None:
info: DETECTOR_INFO = [
f"using-for statement at {uf.source_mapping} is incorrect - no matching function for {type_} found in ",
library,
".\n",
]
res = self.generate_result(info)
results.append(res)
def _detect(self) -> List[Output]:
results: List[Output] = []
for uf in self.compilation_unit.using_for_top_level:
# UsingForTopLevel.using_for is a dict with a single entry, which is mapped to a list of functions/libraries
# the following code extracts the type from using-for and skips using-for statements with functions
type_ = list(uf.using_for.keys())[0]
for lib_or_fcn in uf.using_for[type_]:
# checking for using-for with functions is already performed by the compiler; we only consider libraries
if isinstance(lib_or_fcn, UserDefinedType):
lib_or_fcn_type = lib_or_fcn.type
if (
isinstance(type_, Type)
and isinstance(lib_or_fcn_type, Contract)
and not _is_correctly_used(type_, lib_or_fcn_type)
):
self._append_result(results, uf, type_, lib_or_fcn_type)
return results

@ -6,7 +6,7 @@
"""
from typing import List
from slither.core.cfg.node import Node
from slither.core.cfg.node import Node, NodeType
from slither.core.declarations.function_contract import FunctionContract
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification
from slither.utils.output import Output
@ -64,6 +64,15 @@ Bob calls `transfer`. As a result, all Ether is sent to the address `0x0` and is
self.visited_all_paths[node] = list(set(self.visited_all_paths[node] + fathers_context))
# Remove a local variable declared in a for loop header
if (
node.type == NodeType.VARIABLE
and len(node.sons) == 1 # Should always be true for a node that has a STARTLOOP son
and node.sons[0].type == NodeType.STARTLOOP
):
if node.variable_declaration in fathers_context:
fathers_context.remove(node.variable_declaration)
if self.key in node.context:
fathers_context += node.context[self.key]

@ -17,7 +17,7 @@ class VarReadUsingThis(AbstractDetector):
IMPACT = DetectorClassification.OPTIMIZATION
CONFIDENCE = DetectorClassification.HIGH
WIKI = "https://github.com/crytic/slither/wiki/Vulnerabilities-Description#public-variable-read-in-external-context"
WIKI = "https://github.com/crytic/slither/wiki/Detector-Documentation#public-variable-read-in-external-context"
WIKI_TITLE = "Public variable read in external context"
WIKI_DESCRIPTION = "The contract reads its own variable using `this`, adding overhead of an unnecessary STATICCALL."

@ -47,8 +47,6 @@ class PrinterWrittenVariablesAndAuthorization(AbstractPrinter):
txt = ""
all_tables = []
for contract in self.contracts: # type: ignore
if contract.is_top_level:
continue
txt += f"\nContract {contract.name}\n"
table = MyPrettyTable(
["Function", "State variables written", "Conditions on msg.sender"]

@ -19,8 +19,6 @@ class CFG(AbstractPrinter):
info = ""
all_files = []
for contract in self.contracts: # type: ignore
if contract.is_top_level:
continue
for function in contract.functions + list(contract.modifiers):
if filename:
new_filename = f"{filename}-{contract.name}-{function.full_name}.dot"

@ -36,8 +36,6 @@ class PrinterInheritance(AbstractPrinter):
result = {"child_to_base": {}}
for child in self.contracts:
if child.is_top_level:
continue
info += blue(f"\n+ {child.name}\n")
result["child_to_base"][child.name] = {"immediate": [], "not_immediate": []}
if child.inheritance:
@ -58,8 +56,6 @@ class PrinterInheritance(AbstractPrinter):
result["base_to_child"] = {}
for base in self.contracts:
if base.is_top_level:
continue
info += green(f"\n+ {base.name}") + "\n"
children = list(self._get_child_contracts(base))

@ -194,8 +194,6 @@ class PrinterInheritanceGraph(AbstractPrinter):
content = 'digraph "" {\n'
for c in self.contracts:
if c.is_top_level:
continue
content += self._summary(c) + "\n"
content += "}"

@ -28,9 +28,6 @@ class ContractSummary(AbstractPrinter):
all_contracts = []
for c in self.contracts:
if c.is_top_level:
continue
is_upgradeable_proxy = c.is_upgradeable_proxy
is_upgradeable = c.is_upgradeable

@ -40,8 +40,6 @@ class DataDependency(AbstractPrinter):
txt = ""
for c in self.contracts:
if c.is_top_level:
continue
txt += f"\nContract {c.name}\n"
table = MyPrettyTable(["Variable", "Dependencies"])
for v in c.state_variables:

@ -33,8 +33,6 @@ class FunctionSummary(AbstractPrinter):
all_txt = ""
for c in self.contracts:
if c.is_top_level:
continue
(name, inheritance, var, func_summaries, modif_summaries) = c.get_summary()
txt = f"\nContract {name}"
txt += "\nContract vars: " + str(var)

@ -205,7 +205,7 @@ class PrinterHumanSummary(AbstractPrinter):
def _number_contracts(self):
if self.slither.crytic_compile is None:
return len(self.slither.contracts), 0, 0
contracts = [c for c in self.slither.contracts if not c.is_top_level]
contracts = self.slither.contracts
deps = [c for c in contracts if c.is_from_dependency()]
tests = [c for c in contracts if c.is_test]
return len(contracts) - len(deps) - len(tests), len(deps), len(tests)

@ -36,8 +36,6 @@ class PrinterSlithIR(AbstractPrinter):
txt = ""
for compilation_unit in self.slither.compilation_units:
for contract in compilation_unit.contracts:
if contract.is_top_level:
continue
txt += f"Contract {contract.name}\n"
for function in contract.functions:
txt += f'\tFunction {function.canonical_name} {"" if function.is_shadowed else "(*)"}\n'

@ -21,8 +21,6 @@ class PrinterSlithIRSSA(AbstractPrinter):
txt = ""
for contract in self.contracts:
if contract.is_top_level:
continue
txt += f"Contract {contract.name}" + "\n"
for function in contract.functions:
txt += f"\tFunction {function.canonical_name}" + "\n"

@ -10,8 +10,6 @@ from slither.utils.myprettytable import MyPrettyTable
def _use_modifier(function: Function, modifier_name: str = "whenNotPaused") -> bool:
if function.is_constructor or function.view or function.pure:
return False
for internal_call in function.all_internal_calls():
if isinstance(internal_call, SolidityFunction):
@ -23,7 +21,7 @@ def _use_modifier(function: Function, modifier_name: str = "whenNotPaused") -> b
class PrinterWhenNotPaused(AbstractPrinter):
ARGUMENT = "pausable"
ARGUMENT = "not-pausable"
HELP = "Print functions that do not use whenNotPaused"
WIKI = "https://github.com/trailofbits/slither/wiki/Printer-documentation#when-not-paused"
@ -46,6 +44,8 @@ class PrinterWhenNotPaused(AbstractPrinter):
table = MyPrettyTable(["Name", "Use whenNotPaused"])
for function in contract.functions_entry_points:
if function.is_constructor or function.view or function.pure:
continue
status = "X" if _use_modifier(function, modifier_name) else ""
table.add_row([function.solidity_signature, status])

@ -1363,7 +1363,14 @@ def convert_to_pop(ir: HighLevelCall, node: "Node") -> List[Operation]:
# TODO the following is equivalent to length.points_to = arr
# Should it be removed?
ir_length.lvalue.points_to = arr
element_to_delete.set_type(ElementaryType("uint256"))
# Note bytes is an ElementaryType not ArrayType so in that case we use ir.destination.type
# while in other cases such as uint256[] (ArrayType) we use ir.destination.type.type
# in this way we will have the type always set to the corresponding ElementaryType
element_to_delete.set_type(
ir.destination.type
if isinstance(ir.destination.type, ElementaryType)
else ir.destination.type.type
)
ir_assign_element_to_delete.set_expression(ir.expression)
ir_assign_element_to_delete.set_node(ir.node)
ret.append(ir_assign_element_to_delete)

@ -50,5 +50,5 @@ class Assignment(OperationWithLValue):
points = lvalue.points_to
while isinstance(points, ReferenceVariable):
points = points.points_to
return f"{lvalue} (->{points}) := {self.rvalue}({self.rvalue.type})"
return f"{lvalue}({lvalue.type}) (->{points}) := {self.rvalue}({self.rvalue.type})"
return f"{lvalue}({lvalue.type}) := {self.rvalue}({self.rvalue.type})"

@ -94,7 +94,6 @@ class BinaryType(Enum):
return self in [
BinaryType.POWER,
BinaryType.MULTIPLICATION,
BinaryType.MODULO,
BinaryType.ADDITION,
BinaryType.SUBTRACTION,
BinaryType.DIVISION,

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

@ -104,4 +104,5 @@ class NewContract(Call, OperationWithLValue): # pylint: disable=too-many-instan
if self.call_salt:
options += f"salt:{self.call_salt} "
args = [str(a) for a in self.arguments]
return f"{self.lvalue} = new {self.contract_name}({','.join(args)}) {options}"
lvalue = self.lvalue
return f"{lvalue}({lvalue.type}) = new {self.contract_name}({','.join(args)}) {options}"

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

@ -58,7 +58,7 @@ class Unary(OperationWithLValue):
@property
def type_str(self):
return self._type.value
return str(self._type)
def __str__(self):
return f"{self.lvalue} = {self.type_str} {self.rvalue} "

@ -170,10 +170,9 @@ class ContractSolc(CallerContextExpression):
elif attributes["contractKind"] == "library":
self._contract.is_library = True
self._contract.contract_kind = attributes["contractKind"]
self._contract.is_fully_implemented = attributes["fullyImplemented"]
self._contract.is_fully_implemented = attributes["fullyImplemented"]
self._linearized_base_contracts = attributes["linearizedBaseContracts"]
# self._contract.fullyImplemented = attributes["fullyImplemented"]
# Parse base contract information
self._parse_base_contract_info()

@ -1,5 +1,5 @@
import logging
from typing import Dict, Optional, Union, List, TYPE_CHECKING, Tuple
from typing import Dict, Optional, Union, List, TYPE_CHECKING, Tuple, Set
from slither.core.cfg.node import NodeType, link_nodes, insert_node, Node
from slither.core.cfg.scope import Scope
@ -774,6 +774,7 @@ class FunctionSolc(CallerContextExpression):
"nodeType": "Identifier",
"src": v["src"],
"name": v["name"],
"referencedDeclaration": v["id"],
"typeDescriptions": {"typeString": v["typeDescriptions"]["typeString"]},
}
var_identifiers.append(identifier)
@ -1150,15 +1151,16 @@ class FunctionSolc(CallerContextExpression):
if end_node:
for son in node.sons:
if son.type == NodeType.CATCH:
self._fix_catch(son, end_node)
self._fix_catch(son, end_node, set())
def _fix_catch(self, node: Node, end_node: Node) -> None:
def _fix_catch(self, node: Node, end_node: Node, visited: Set[Node]) -> None:
if not node.sons:
link_nodes(node, end_node)
else:
for son in node.sons:
if son != end_node:
self._fix_catch(son, end_node)
if son != end_node and son not in visited:
visited.add(son)
self._fix_catch(son, end_node, visited)
def _add_param(self, param: Dict) -> LocalVariableSolc:
@ -1395,8 +1397,7 @@ class FunctionSolc(CallerContextExpression):
endif_node = self._new_node(NodeType.ENDIF, node.source_mapping, node.scope)
for father in node.fathers:
father.remove_son(node)
father.add_son(condition_node.underlying_node)
father.replace_son(node, condition_node.underlying_node)
condition_node.underlying_node.add_father(father)
for son in node.sons:

@ -87,6 +87,9 @@ class ModifierSolc(FunctionSolc):
for node in self._node_to_nodesolc.values():
node.analyze_expressions(self)
for yul_parser in self._node_to_yulobject.values():
yul_parser.analyze_expressions()
self._rewrite_ternary_as_if_else()
self._remove_alone_endif()

@ -402,10 +402,7 @@ class SlitherCompilationUnitSolc(CallerContextExpression):
# First we save all the contracts in a dict
# the key is the contractid
for contract in self._underlying_contract_to_parser:
if (
contract.name.startswith("SlitherInternalTopLevelContract")
and not contract.is_top_level
):
if contract.name.startswith("SlitherInternalTopLevelContract"):
raise SlitherException(
# region multi-line-string
"""Your codebase has a contract named 'SlitherInternalTopLevelContract'.

@ -15,7 +15,7 @@ ZIP_TYPES_ACCEPTED = {
Export = namedtuple("Export", ["filename", "content"])
logger = logging.getLogger("Slither")
logger = logging.getLogger("Slither-flat")
def save_to_zip(files: List[Export], zip_filename: str, zip_type: str = "lzma"):

@ -11,6 +11,7 @@ from slither.core.declarations import SolidityFunction, EnumContract, StructureC
from slither.core.declarations.contract import Contract
from slither.core.declarations.function_top_level import FunctionTopLevel
from slither.core.declarations.top_level import TopLevel
from slither.core.declarations.solidity_variables import SolidityCustomRevert
from slither.core.solidity_types import MappingType, ArrayType
from slither.core.solidity_types.type import Type
from slither.core.solidity_types.user_defined_type import UserDefinedType
@ -23,7 +24,8 @@ from slither.tools.flattening.export.export import (
save_to_disk,
)
logger = logging.getLogger("Slither-flattening")
logger = logging.getLogger("Slither-flat")
logger.setLevel(logging.INFO)
# index: where to start
# patch_type:
@ -75,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.custom_errors)
self._get_source_code_top_level(compilation_unit.variables_top_level)
self._get_source_code_top_level(compilation_unit.functions_top_level)
@ -249,12 +252,14 @@ class Flattening:
t: Type,
contract: Contract,
exported: Set[str],
list_contract: List[Contract],
list_top_level: List[TopLevel],
list_contract: Set[Contract],
list_top_level: Set[TopLevel],
):
if isinstance(t, UserDefinedType):
t_type = t.type
if isinstance(t_type, (EnumContract, StructureContract)):
if isinstance(t_type, TopLevel):
list_top_level.add(t_type)
elif isinstance(t_type, (EnumContract, StructureContract)):
if t_type.contract != contract and t_type.contract not in exported:
self._export_list_used_contracts(
t_type.contract, exported, list_contract, list_top_level
@ -275,8 +280,8 @@ class Flattening:
self,
contract: Contract,
exported: Set[str],
list_contract: List[Contract],
list_top_level: List[TopLevel],
list_contract: Set[Contract],
list_top_level: Set[TopLevel],
):
# TODO: investigate why this happen
if not isinstance(contract, Contract):
@ -332,19 +337,21 @@ class Flattening:
for read in ir.read:
if isinstance(read, TopLevel):
if read not in list_top_level:
list_top_level.append(read)
if isinstance(ir, InternalCall):
function_called = ir.function
if isinstance(function_called, FunctionTopLevel):
list_top_level.append(function_called)
list_top_level.add(read)
if isinstance(ir, InternalCall) and isinstance(ir.function, FunctionTopLevel):
list_top_level.add(ir.function)
if (
isinstance(ir, SolidityCall)
and isinstance(ir.function, SolidityCustomRevert)
and isinstance(ir.function.custom_error, TopLevel)
):
list_top_level.add(ir.function.custom_error)
if contract not in list_contract:
list_contract.append(contract)
list_contract.add(contract)
def _export_contract_with_inheritance(self, contract) -> Export:
list_contracts: List[Contract] = [] # will contain contract itself
list_top_level: List[TopLevel] = []
list_contracts: Set[Contract] = set() # will contain contract itself
list_top_level: Set[TopLevel] = set()
self._export_list_used_contracts(contract, set(), list_contracts, list_top_level)
path = Path(self._export_path, f"{contract.name}_{uuid.uuid4()}.sol")
@ -401,8 +408,8 @@ class Flattening:
def _export_with_import(self) -> List[Export]:
exports: List[Export] = []
for contract in self._compilation_unit.contracts:
list_contracts: List[Contract] = [] # will contain contract itself
list_top_level: List[TopLevel] = []
list_contracts: Set[Contract] = set() # will contain contract itself
list_top_level: Set[TopLevel] = set()
self._export_list_used_contracts(contract, set(), list_contracts, list_top_level)
if list_top_level:

@ -0,0 +1,105 @@
import argparse
import logging
from pathlib import Path
from crytic_compile import cryticparser
from slither import Slither
from slither.utils.code_generation import generate_interface
logging.basicConfig()
logger = logging.getLogger("Slither-Interface")
logger.setLevel(logging.INFO)
def parse_args() -> argparse.Namespace:
"""
Parse the underlying arguments for the program.
:return: Returns the arguments for the program.
"""
parser = argparse.ArgumentParser(
description="Generates code for a Solidity interface from contract",
usage=("slither-interface <ContractName> <source file or deployment address>"),
)
parser.add_argument(
"contract_source",
help="The name of the contract (case sensitive) followed by the deployed contract address if verified on etherscan or project directory/filename for local contracts.",
nargs="+",
)
parser.add_argument(
"--unroll-structs",
help="Whether to use structures' underlying types instead of the user-defined type",
default=False,
action="store_true",
)
parser.add_argument(
"--exclude-events",
help="Excludes event signatures in the interface",
default=False,
action="store_true",
)
parser.add_argument(
"--exclude-errors",
help="Excludes custom error signatures in the interface",
default=False,
action="store_true",
)
parser.add_argument(
"--exclude-enums",
help="Excludes enum definitions in the interface",
default=False,
action="store_true",
)
parser.add_argument(
"--exclude-structs",
help="Exclude struct definitions in the interface",
default=False,
action="store_true",
)
cryticparser.init(parser)
return parser.parse_args()
def main() -> None:
args = parse_args()
contract_name, target = args.contract_source
slither = Slither(target, **vars(args))
_contract = slither.get_contract_from_name(contract_name)[0]
interface = generate_interface(
contract=_contract,
unroll_structs=args.unroll_structs,
include_events=not args.exclude_events,
include_errors=not args.exclude_errors,
include_enums=not args.exclude_enums,
include_structs=not args.exclude_structs,
)
# add version pragma
interface = (
f"pragma solidity {_contract.compilation_unit.pragma_directives[0].version};\n\n"
+ interface
)
# write interface to file
export = Path("crytic-export", "interfaces")
export.mkdir(parents=True, exist_ok=True)
filename = f"I{contract_name}.sol"
path = Path(export, filename)
logger.info(f" Interface exported to {path}")
with open(path, "w", encoding="utf8") as f:
f.write(interface)
if __name__ == "__main__":
main()

@ -441,7 +441,7 @@ class SlitherReadStorage:
if "int" in key_type: # without this eth_utils encoding fails
key = int(key)
key = coerce_type(key_type, key)
slot = keccak(encode([key_type, "uint256"], [key, decode("uint256", slot)]))
slot = keccak(encode([key_type, "uint256"], [key, decode(["uint256"], slot)[0]]))
if isinstance(target_variable_type.type_to, UserDefinedType) and isinstance(
target_variable_type.type_to.type, Structure

@ -1,62 +1,149 @@
# Functions for generating Solidity code
from typing import TYPE_CHECKING, Optional
from slither.utils.type import convert_type_for_solidity_signature_to_string
from slither.utils.type import (
convert_type_for_solidity_signature_to_string,
export_nested_types_from_variable,
export_return_type_from_variable,
)
from slither.core.solidity_types import (
Type,
UserDefinedType,
MappingType,
ArrayType,
ElementaryType,
)
from slither.core.declarations import Structure, Enum, Contract
if TYPE_CHECKING:
from slither.core.declarations import FunctionContract, Structure, Contract
from slither.core.declarations import FunctionContract, CustomErrorContract
from slither.core.variables.state_variable import StateVariable
from slither.core.variables.local_variable import LocalVariable
def generate_interface(contract: "Contract") -> str:
# pylint: disable=too-many-arguments
def generate_interface(
contract: "Contract",
unroll_structs: bool = True,
include_events: bool = True,
include_errors: bool = True,
include_enums: bool = True,
include_structs: bool = True,
) -> str:
"""
Generates code for a Solidity interface to the contract.
Args:
contract: A Contract object
contract: A Contract object.
unroll_structs: Whether to use structures' underlying types instead of the user-defined type (default: True).
include_events: Whether to include event signatures in the interface (default: True).
include_errors: Whether to include custom error signatures in the interface (default: True).
include_enums: Whether to include enum definitions in the interface (default: True).
include_structs: Whether to include struct definitions in the interface (default: True).
Returns:
A string with the code for an interface, with function stubs for all public or external functions and
state variables, as well as any events, custom errors and/or structs declared in the contract.
"""
interface = f"interface I{contract.name} {{\n"
if include_events:
for event in contract.events:
name, args = event.signature
interface += f" event {name}({', '.join(args)});\n"
if include_errors:
for error in contract.custom_errors:
args = [
convert_type_for_solidity_signature_to_string(arg.type)
.replace("(", "")
.replace(")", "")
for arg in error.parameters
]
interface += f" error {error.name}({', '.join(args)});\n"
interface += f" error {generate_custom_error_interface(error, unroll_structs)};\n"
if include_enums:
for enum in contract.enums:
interface += f" enum {enum.name} {{ {', '.join(enum.values)} }}\n"
if include_structs:
for struct in contract.structures:
interface += generate_struct_interface_str(struct)
interface += generate_struct_interface_str(struct, indent=4)
for var in contract.state_variables_entry_points:
interface += f" function {var.signature_str.replace('returns', 'external returns ')};\n"
interface += f" function {generate_interface_variable_signature(var, unroll_structs)};\n"
for func in contract.functions_entry_points:
if func.is_constructor or func.is_fallback or func.is_receive:
continue
interface += f" function {generate_interface_function_signature(func)};\n"
interface += (
f" function {generate_interface_function_signature(func, unroll_structs)};\n"
)
interface += "}\n\n"
return interface
def generate_interface_function_signature(func: "FunctionContract") -> Optional[str]:
def generate_interface_variable_signature(
var: "StateVariable", unroll_structs: bool = True
) -> Optional[str]:
if var.visibility in ["private", "internal"]:
return None
if unroll_structs:
params = [
convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "")
for x in export_nested_types_from_variable(var)
]
returns = [
convert_type_for_solidity_signature_to_string(x).replace("(", "").replace(")", "")
for x in export_return_type_from_variable(var)
]
else:
_, params, _ = var.signature
params = [p + " memory" if p in ["bytes", "string"] else p for p in params]
returns = []
_type = var.type
while isinstance(_type, MappingType):
_type = _type.type_to
while isinstance(_type, (ArrayType, UserDefinedType)):
_type = _type.type
ret = str(_type)
if isinstance(_type, Structure) or (isinstance(_type, Type) and _type.is_dynamic):
ret += " memory"
elif isinstance(_type, Contract):
ret = "address"
returns.append(ret)
return f"{var.name}({','.join(params)}) external returns ({', '.join(returns)})"
def generate_interface_function_signature(
func: "FunctionContract", unroll_structs: bool = True
) -> Optional[str]:
"""
Generates a string of the form:
func_name(type1,type2) external {payable/view/pure} returns (type3)
Args:
func: A FunctionContract object
unroll_structs: Determines whether structs are unrolled into underlying types (default: True)
Returns:
The function interface as a str (contains the return values).
Returns None if the function is private or internal, or is a constructor/fallback/receive.
"""
name, parameters, return_vars = func.signature
def format_var(var: "LocalVariable", unroll: bool) -> str:
if unroll:
return (
convert_type_for_solidity_signature_to_string(var.type)
.replace("(", "")
.replace(")", "")
)
if isinstance(var.type, ArrayType) and isinstance(
var.type.type, (UserDefinedType, ElementaryType)
):
return (
convert_type_for_solidity_signature_to_string(var.type)
.replace("(", "")
.replace(")", "")
+ f" {var.location}"
)
if isinstance(var.type, UserDefinedType):
if isinstance(var.type.type, (Structure, Enum)):
return f"{str(var.type.type)} memory"
if isinstance(var.type.type, Contract):
return "address"
if var.type.is_dynamic:
return f"{var.type} {var.location}"
return str(var.type)
name, _, _ = func.signature
if (
func not in func.contract.functions_entry_points
or func.is_constructor
@ -64,26 +151,20 @@ def generate_interface_function_signature(func: "FunctionContract") -> Optional[
or func.is_receive
):
return None
view = " view" if func.view else ""
view = " view" if func.view and not func.pure else ""
pure = " pure" if func.pure else ""
payable = " payable" if func.payable else ""
returns = [
convert_type_for_solidity_signature_to_string(ret.type).replace("(", "").replace(")", "")
for ret in func.returns
]
parameters = [
convert_type_for_solidity_signature_to_string(param.type).replace("(", "").replace(")", "")
for param in func.parameters
]
returns = [format_var(ret, unroll_structs) for ret in func.returns]
parameters = [format_var(param, unroll_structs) for param in func.parameters]
_interface_signature_str = (
name + "(" + ",".join(parameters) + ") external" + payable + pure + view
)
if len(return_vars) > 0:
if len(returns) > 0:
_interface_signature_str += " returns (" + ",".join(returns) + ")"
return _interface_signature_str
def generate_struct_interface_str(struct: "Structure") -> str:
def generate_struct_interface_str(struct: "Structure", indent: int = 0) -> str:
"""
Generates code for a structure declaration in an interface of the form:
struct struct_name {
@ -92,13 +173,37 @@ def generate_struct_interface_str(struct: "Structure") -> str:
... ...
}
Args:
struct: A Structure object
struct: A Structure object.
indent: Number of spaces to indent the code block with.
Returns:
The structure declaration code as a string.
"""
definition = f" struct {struct.name} {{\n"
spaces = ""
for _ in range(0, indent):
spaces += " "
definition = f"{spaces}struct {struct.name} {{\n"
for elem in struct.elems_ordered:
definition += f" {elem.type} {elem.name};\n"
definition += " }\n"
if isinstance(elem.type, UserDefinedType):
if isinstance(elem.type.type, (Structure, Enum)):
definition += f"{spaces} {elem.type.type} {elem.name};\n"
elif isinstance(elem.type.type, Contract):
definition += f"{spaces} address {elem.name};\n"
else:
definition += f"{spaces} {elem.type} {elem.name};\n"
definition += f"{spaces}}}\n"
return definition
def generate_custom_error_interface(
error: "CustomErrorContract", unroll_structs: bool = True
) -> str:
args = [
convert_type_for_solidity_signature_to_string(arg.type).replace("(", "").replace(")", "")
if unroll_structs
else str(arg.type.type)
if isinstance(arg.type, UserDefinedType) and isinstance(arg.type.type, (Structure, Enum))
else str(arg.type)
for arg in error.parameters
]
return f"{error.name}({', '.join(args)})"

@ -21,7 +21,7 @@ from slither.core.expressions.new_array import NewArray
from slither.core.expressions.new_contract import NewContract
from slither.core.expressions.tuple_expression import TupleExpression
from slither.core.expressions.type_conversion import TypeConversion
from slither.core.expressions.new_elementary_type import NewElementaryType
# pylint: disable=protected-access
def f_expressions(
@ -100,7 +100,14 @@ class SplitTernaryExpression:
if isinstance(
expression,
(Literal, Identifier, NewArray, NewContract, ElementaryTypeNameExpression),
(
Literal,
Identifier,
NewArray,
NewContract,
ElementaryTypeNameExpression,
NewElementaryType,
),
):
return

@ -1,6 +1,6 @@
from typing import List, Dict, Union
from prettytable import PrettyTable
from prettytable.colortable import ColorTable, Themes
class MyPrettyTable:
@ -11,8 +11,8 @@ class MyPrettyTable:
def add_row(self, row: List[Union[str, List[str]]]) -> None:
self._rows.append(row)
def to_pretty_table(self) -> PrettyTable:
table = PrettyTable(self._field_names)
def to_pretty_table(self) -> ColorTable:
table = ColorTable(self._field_names, theme=Themes.OCEAN)
for row in self._rows:
table.add_row(row)
return table

@ -197,3 +197,18 @@ def export_return_type_from_variable(
return ret
return [variable_or_type.type]
def is_underlying_type_address(t: "Type") -> bool:
"""
Return true if the underlying type is an address
i.e. if the type is an address or a contract
"""
# pylint: disable=import-outside-toplevel
from slither.core.declarations.contract import Contract
if t == ElementaryType("address"):
return True
if isinstance(t, UserDefinedType) and isinstance(t.type, Contract):
return True
return False

@ -10,6 +10,7 @@ from slither.core.declarations import (
Contract,
EnumContract,
EnumTopLevel,
Enum,
)
from slither.core.expressions import (
AssignmentOperation,
@ -405,10 +406,13 @@ class ExpressionToSlithIR(ExpressionVisitor):
right = get(expression.expression_right)
operation: Operation
# Left can be a type for abi.decode(var, uint[2])
if isinstance(left, Type):
if isinstance(left, (Type, Contract, Enum)):
# Nested type are not yet supported by abi.decode, so the assumption
# Is that the right variable must be a constant
assert isinstance(right, Constant)
# Case for abi.decode(var, I[2]) where I is an interface/contract or an enum
if isinstance(left, (Contract, Enum)):
left = UserDefinedType(left)
t = ArrayType(left, int(right.value))
set_val(expression, t)
return
@ -622,7 +626,6 @@ class ExpressionToSlithIR(ExpressionVisitor):
set_val(expression, value)
elif expression.type in [UnaryOperationType.MINUS_PRE]:
lvalue = TemporaryVariable(self._node)
assert isinstance(value.type, ElementaryType)
operation = Binary(lvalue, Constant("0", value.type), value, BinaryType.SUBTRACTION)
operation.set_expression(expression)
self._result.append(operation)

@ -0,0 +1,79 @@
# pylint: disable=redefined-outer-name
import os
from pathlib import Path
import tempfile
import shutil
from contextlib import contextmanager
import pytest
from filelock import FileLock
from solc_select import solc_select
from slither import Slither
def pytest_configure(config):
"""Create a temporary directory for the tests to use."""
if is_master():
config.stash["shared_directory"] = tempfile.mkdtemp()
def pytest_unconfigure(config):
"""Remove the temporary directory after the tests are done."""
if is_master():
shutil.rmtree(config.stash["shared_directory"])
def pytest_configure_node(node):
"""Configure each worker node with the shared directory."""
node.workerinput["shared_directory"] = node.config.stash["shared_directory"]
def is_master():
"""Returns True if the current process is the master process (which does not have a worker id)."""
return os.environ.get("PYTEST_XDIST_WORKER") is None
@pytest.fixture
def shared_directory(request):
"""Returns the shared directory for the current process."""
if is_master():
return request.config.stash["shared_directory"]
return request.config.workerinput["shared_directory"]
@pytest.fixture
def solc_binary_path(shared_directory):
"""
Returns the path to the solc binary for the given version.
If the binary is not installed, it will be installed.
"""
def inner(version):
lock = FileLock(f"{shared_directory}/{version}.lock", timeout=60)
with lock:
if not solc_select.artifact_path(version).exists():
print("Installing solc version", version)
solc_select.install_artifacts([version])
return solc_select.artifact_path(version).as_posix()
return inner
@pytest.fixture
def slither_from_source(solc_binary_path):
@contextmanager
def inner(source_code: str, solc_version: str = "0.8.19"):
"""Yields a Slither instance using source_code string and solc_version.
Creates a temporary file and compiles with solc_version.
"""
fname = ""
try:
with tempfile.NamedTemporaryFile(mode="w", suffix=".sol", delete=False) as f:
fname = f.name
f.write(source_code)
solc_path = solc_binary_path(solc_version)
yield Slither(fname, solc=solc_path)
finally:
Path(fname).unlink()
return inner

@ -3,7 +3,6 @@ import pytest
from crytic_compile import CryticCompile
from crytic_compile.platform.solc_standard_json import SolcStandardJson
from solc_select import solc_select
from slither import Slither
@ -24,8 +23,8 @@ def test_node_modules() -> None:
_run_all_detectors(slither)
def test_contract_name_collisions() -> None:
solc_select.switch_global_version("0.8.0", always_install=True)
def test_contract_name_collision(solc_binary_path) -> None:
solc_path = solc_binary_path("0.8.0")
standard_json = SolcStandardJson()
standard_json.add_source_file(
Path(TEST_DATA_DIR, "test_contract_name_collisions", "a.sol").as_posix()
@ -34,13 +33,13 @@ def test_contract_name_collisions() -> None:
Path(TEST_DATA_DIR, "test_contract_name_collisions", "b.sol").as_posix()
)
compilation = CryticCompile(standard_json)
compilation = CryticCompile(standard_json, solc=solc_path)
slither = Slither(compilation)
_run_all_detectors(slither)
def test_cycle() -> None:
solc_select.switch_global_version("0.8.0", always_install=True)
slither = Slither(Path(TEST_DATA_DIR, "test_cyclic_import", "a.sol").as_posix())
def test_cycle(solc_binary_path) -> None:
solc_path = solc_binary_path("0.8.0")
slither = Slither(Path(TEST_DATA_DIR, "test_cyclic_import", "a.sol").as_posix(), solc=solc_path)
_run_all_detectors(slither)

@ -0,0 +1,18 @@
Function A.bad3() (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#39-41) trigger an abi encoding bug:
- b = abi.encode(s) (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#40)
Function A.bad0() (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#21-23) trigger an abi encoding bug:
- this.bad0_external(bad_arr) (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#22)
Function A.bad4() (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#44-46) trigger an abi encoding bug:
- event1_bad(bad_arr) (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#45)
Function A.bad2() (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#34-36) trigger an abi encoding bug:
- b = abi.encode(bad_arr) (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#35)
Function A.bad1(A.S[3]) (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#29-31) trigger an abi encoding bug:
- this.bad1_external(s) (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#30)
Function A.bad5() (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#49-51) trigger an abi encoding bug:
- event2_bad(s) (tests/e2e/detectors/test_data/abiencoderv2-array/0.4.25/storage_ABIEncoderV2_array.sol#50)

@ -0,0 +1,18 @@
Function A.bad5() (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#49-51) trigger an abi encoding bug:
- event2_bad(s) (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#50)
Function A.bad0() (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#21-23) trigger an abi encoding bug:
- this.bad0_external(bad_arr) (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#22)
Function A.bad4() (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#44-46) trigger an abi encoding bug:
- event1_bad(bad_arr) (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#45)
Function A.bad2() (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#34-36) trigger an abi encoding bug:
- b = abi.encode(bad_arr) (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#35)
Function A.bad1(A.S[3]) (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#29-31) trigger an abi encoding bug:
- this.bad1_external(s) (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#30)
Function A.bad3() (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#39-41) trigger an abi encoding bug:
- b = abi.encode(s) (tests/e2e/detectors/test_data/abiencoderv2-array/0.5.9/storage_ABIEncoderV2_array.sol#40)

@ -0,0 +1,6 @@
C.bad4(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.4.25/arbitrary_send_erc20.sol#65-67) uses arbitrary from in transferFrom: SafeERC20.safeTransferFrom(erc20,from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.4.25/arbitrary_send_erc20.sol#66)
C.bad1(address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.4.25/arbitrary_send_erc20.sol#35-37) uses arbitrary from in transferFrom: erc20.transferFrom(notsend,to,am) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.4.25/arbitrary_send_erc20.sol#36)
C.bad3(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.4.25/arbitrary_send_erc20.sol#57-59) uses arbitrary from in transferFrom: erc20.safeTransferFrom(from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.4.25/arbitrary_send_erc20.sol#58)

@ -0,0 +1,6 @@
C.bad4(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.5.16/arbitrary_send_erc20.sol#65-67) uses arbitrary from in transferFrom: SafeERC20.safeTransferFrom(erc20,from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.5.16/arbitrary_send_erc20.sol#66)
C.bad3(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.5.16/arbitrary_send_erc20.sol#57-59) uses arbitrary from in transferFrom: erc20.safeTransferFrom(from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.5.16/arbitrary_send_erc20.sol#58)
C.bad1(address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.5.16/arbitrary_send_erc20.sol#35-37) uses arbitrary from in transferFrom: erc20.transferFrom(notsend,to,am) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.5.16/arbitrary_send_erc20.sol#36)

@ -0,0 +1,6 @@
C.bad1(address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.6.11/arbitrary_send_erc20.sol#35-37) uses arbitrary from in transferFrom: erc20.transferFrom(notsend,to,am) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.6.11/arbitrary_send_erc20.sol#36)
C.bad3(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.6.11/arbitrary_send_erc20.sol#57-59) uses arbitrary from in transferFrom: erc20.safeTransferFrom(from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.6.11/arbitrary_send_erc20.sol#58)
C.bad4(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.6.11/arbitrary_send_erc20.sol#65-67) uses arbitrary from in transferFrom: SafeERC20.safeTransferFrom(erc20,from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.6.11/arbitrary_send_erc20.sol#66)

@ -0,0 +1,6 @@
C.bad4(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.7.6/arbitrary_send_erc20.sol#65-67) uses arbitrary from in transferFrom: SafeERC20.safeTransferFrom(erc20,from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.7.6/arbitrary_send_erc20.sol#66)
C.bad3(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.7.6/arbitrary_send_erc20.sol#57-59) uses arbitrary from in transferFrom: erc20.safeTransferFrom(from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.7.6/arbitrary_send_erc20.sol#58)
C.bad1(address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.7.6/arbitrary_send_erc20.sol#35-37) uses arbitrary from in transferFrom: erc20.transferFrom(notsend,to,am) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.7.6/arbitrary_send_erc20.sol#36)

@ -0,0 +1,2 @@
T.bad(address) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.8.0/arbitrary_send_erc20_inheritance.sol#11-13) uses arbitrary from in transferFrom: erc20.safeTransferFrom(from,address(0x1),90) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.8.0/arbitrary_send_erc20_inheritance.sol#12)

@ -0,0 +1,6 @@
C.bad1(address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.8.0/arbitrary_send_erc20.sol#35-37) uses arbitrary from in transferFrom: erc20.transferFrom(notsend,to,am) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.8.0/arbitrary_send_erc20.sol#36)
C.bad3(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.8.0/arbitrary_send_erc20.sol#57-59) uses arbitrary from in transferFrom: erc20.safeTransferFrom(from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.8.0/arbitrary_send_erc20.sol#58)
C.bad4(address,address,uint256) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.8.0/arbitrary_send_erc20.sol#65-67) uses arbitrary from in transferFrom: SafeERC20.safeTransferFrom(erc20,from,to,amount) (tests/e2e/detectors/test_data/arbitrary-send-erc20/0.8.0/arbitrary_send_erc20.sol#66)

@ -0,0 +1,8 @@
C.int_transferFrom(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.4.25/arbitrary_send_erc20_permit.sol#42-45) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.4.25/arbitrary_send_erc20_permit.sol#44)
C.bad3(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.4.25/arbitrary_send_erc20_permit.sol#47-50) uses arbitrary from in transferFrom in combination with permit: erc20.safeTransferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.4.25/arbitrary_send_erc20_permit.sol#49)
C.bad4(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.4.25/arbitrary_send_erc20_permit.sol#52-55) uses arbitrary from in transferFrom in combination with permit: SafeERC20.safeTransferFrom(erc20,from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.4.25/arbitrary_send_erc20_permit.sol#54)
C.bad1(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.4.25/arbitrary_send_erc20_permit.sol#32-35) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.4.25/arbitrary_send_erc20_permit.sol#34)

@ -0,0 +1,8 @@
C.int_transferFrom(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.5.16/arbitrary_send_erc20_permit.sol#42-45) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.5.16/arbitrary_send_erc20_permit.sol#44)
C.bad4(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.5.16/arbitrary_send_erc20_permit.sol#52-55) uses arbitrary from in transferFrom in combination with permit: SafeERC20.safeTransferFrom(erc20,from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.5.16/arbitrary_send_erc20_permit.sol#54)
C.bad3(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.5.16/arbitrary_send_erc20_permit.sol#47-50) uses arbitrary from in transferFrom in combination with permit: erc20.safeTransferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.5.16/arbitrary_send_erc20_permit.sol#49)
C.bad1(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.5.16/arbitrary_send_erc20_permit.sol#32-35) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.5.16/arbitrary_send_erc20_permit.sol#34)

@ -0,0 +1,8 @@
C.bad3(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.6.11/arbitrary_send_erc20_permit.sol#47-50) uses arbitrary from in transferFrom in combination with permit: erc20.safeTransferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.6.11/arbitrary_send_erc20_permit.sol#49)
C.bad4(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.6.11/arbitrary_send_erc20_permit.sol#52-55) uses arbitrary from in transferFrom in combination with permit: SafeERC20.safeTransferFrom(erc20,from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.6.11/arbitrary_send_erc20_permit.sol#54)
C.int_transferFrom(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.6.11/arbitrary_send_erc20_permit.sol#42-45) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.6.11/arbitrary_send_erc20_permit.sol#44)
C.bad1(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.6.11/arbitrary_send_erc20_permit.sol#32-35) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.6.11/arbitrary_send_erc20_permit.sol#34)

@ -0,0 +1,8 @@
C.int_transferFrom(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.7.6/arbitrary_send_erc20_permit.sol#42-45) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.7.6/arbitrary_send_erc20_permit.sol#44)
C.bad1(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.7.6/arbitrary_send_erc20_permit.sol#32-35) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.7.6/arbitrary_send_erc20_permit.sol#34)
C.bad4(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.7.6/arbitrary_send_erc20_permit.sol#52-55) uses arbitrary from in transferFrom in combination with permit: SafeERC20.safeTransferFrom(erc20,from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.7.6/arbitrary_send_erc20_permit.sol#54)
C.bad3(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.7.6/arbitrary_send_erc20_permit.sol#47-50) uses arbitrary from in transferFrom in combination with permit: erc20.safeTransferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.7.6/arbitrary_send_erc20_permit.sol#49)

@ -0,0 +1,8 @@
C.bad3(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.8.0/arbitrary_send_erc20_permit.sol#47-50) uses arbitrary from in transferFrom in combination with permit: erc20.safeTransferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.8.0/arbitrary_send_erc20_permit.sol#49)
C.bad4(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.8.0/arbitrary_send_erc20_permit.sol#52-55) uses arbitrary from in transferFrom in combination with permit: SafeERC20.safeTransferFrom(erc20,from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.8.0/arbitrary_send_erc20_permit.sol#54)
C.int_transferFrom(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.8.0/arbitrary_send_erc20_permit.sol#42-45) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.8.0/arbitrary_send_erc20_permit.sol#44)
C.bad1(address,uint256,uint256,uint8,bytes32,bytes32,address) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.8.0/arbitrary_send_erc20_permit.sol#32-35) uses arbitrary from in transferFrom in combination with permit: erc20.transferFrom(from,to,value) (tests/e2e/detectors/test_data/arbitrary-send-erc20-permit/0.8.0/arbitrary_send_erc20_permit.sol#34)

@ -0,0 +1,8 @@
Test.indirect() (tests/e2e/detectors/test_data/arbitrary-send-eth/0.4.25/arbitrary_send_eth.sol#19-21) sends eth to arbitrary user
Dangerous calls:
- destination.send(address(this).balance) (tests/e2e/detectors/test_data/arbitrary-send-eth/0.4.25/arbitrary_send_eth.sol#20)
Test.direct() (tests/e2e/detectors/test_data/arbitrary-send-eth/0.4.25/arbitrary_send_eth.sol#11-13) sends eth to arbitrary user
Dangerous calls:
- msg.sender.send(address(this).balance) (tests/e2e/detectors/test_data/arbitrary-send-eth/0.4.25/arbitrary_send_eth.sol#12)

@ -0,0 +1,8 @@
Test.direct() (tests/e2e/detectors/test_data/arbitrary-send-eth/0.5.16/arbitrary_send_eth.sol#11-13) sends eth to arbitrary user
Dangerous calls:
- msg.sender.send(address(this).balance) (tests/e2e/detectors/test_data/arbitrary-send-eth/0.5.16/arbitrary_send_eth.sol#12)
Test.indirect() (tests/e2e/detectors/test_data/arbitrary-send-eth/0.5.16/arbitrary_send_eth.sol#19-21) sends eth to arbitrary user
Dangerous calls:
- destination.send(address(this).balance) (tests/e2e/detectors/test_data/arbitrary-send-eth/0.5.16/arbitrary_send_eth.sol#20)

@ -0,0 +1,8 @@
Test.indirect() (tests/e2e/detectors/test_data/arbitrary-send-eth/0.6.11/arbitrary_send_eth.sol#19-21) sends eth to arbitrary user
Dangerous calls:
- destination.send(address(this).balance) (tests/e2e/detectors/test_data/arbitrary-send-eth/0.6.11/arbitrary_send_eth.sol#20)
Test.direct() (tests/e2e/detectors/test_data/arbitrary-send-eth/0.6.11/arbitrary_send_eth.sol#11-13) sends eth to arbitrary user
Dangerous calls:
- msg.sender.send(address(this).balance) (tests/e2e/detectors/test_data/arbitrary-send-eth/0.6.11/arbitrary_send_eth.sol#12)

@ -0,0 +1,8 @@
Test.direct() (tests/e2e/detectors/test_data/arbitrary-send-eth/0.7.6/arbitrary_send_eth.sol#11-13) sends eth to arbitrary user
Dangerous calls:
- msg.sender.send(address(this).balance) (tests/e2e/detectors/test_data/arbitrary-send-eth/0.7.6/arbitrary_send_eth.sol#12)
Test.indirect() (tests/e2e/detectors/test_data/arbitrary-send-eth/0.7.6/arbitrary_send_eth.sol#19-21) sends eth to arbitrary user
Dangerous calls:
- destination.send(address(this).balance) (tests/e2e/detectors/test_data/arbitrary-send-eth/0.7.6/arbitrary_send_eth.sol#20)

@ -0,0 +1,12 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#25-28)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.4.25/array_by_reference.sol#21-23)which only takes arrays by value

@ -0,0 +1,12 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#25-28)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.5.16/array_by_reference.sol#21-23)which only takes arrays by value

@ -0,0 +1,12 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#25-28)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.6.11/array_by_reference.sol#21-23)which only takes arrays by value

@ -0,0 +1,12 @@
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
D.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#42-48) passes array D.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#39)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value
C.f() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#4-8) passes array C.x (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#2)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValueAndReturn(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#25-28)which only takes arrays by value
C.g() (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#10-15) passes array C.g().y (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#11)by reference to C.setByValue(uint256[1]) (tests/e2e/detectors/test_data/array-by-reference/0.7.6/array_by_reference.sol#21-23)which only takes arrays by value

@ -0,0 +1,9 @@
ArrayLengthAssignment (tests/e2e/detectors/test_data/controlled-array-length/0.4.25/array_length_assignment.sol#1-46) contract sets array length with a user-controlled value:
- b.subStruct.x.length = param + 1 (tests/e2e/detectors/test_data/controlled-array-length/0.4.25/array_length_assignment.sol#41)
ArrayLengthAssignment (tests/e2e/detectors/test_data/controlled-array-length/0.4.25/array_length_assignment.sol#1-46) contract sets array length with a user-controlled value:
- a.x.length = param (tests/e2e/detectors/test_data/controlled-array-length/0.4.25/array_length_assignment.sol#36)
ArrayLengthAssignment (tests/e2e/detectors/test_data/controlled-array-length/0.4.25/array_length_assignment.sol#1-46) contract sets array length with a user-controlled value:
- arr.length = param (tests/e2e/detectors/test_data/controlled-array-length/0.4.25/array_length_assignment.sol#26)

@ -0,0 +1,9 @@
ArrayLengthAssignment (tests/e2e/detectors/test_data/controlled-array-length/0.5.16/array_length_assignment.sol#1-46) contract sets array length with a user-controlled value:
- b.subStruct.x.length = param + 1 (tests/e2e/detectors/test_data/controlled-array-length/0.5.16/array_length_assignment.sol#41)
ArrayLengthAssignment (tests/e2e/detectors/test_data/controlled-array-length/0.5.16/array_length_assignment.sol#1-46) contract sets array length with a user-controlled value:
- a.x.length = param (tests/e2e/detectors/test_data/controlled-array-length/0.5.16/array_length_assignment.sol#36)
ArrayLengthAssignment (tests/e2e/detectors/test_data/controlled-array-length/0.5.16/array_length_assignment.sol#1-46) contract sets array length with a user-controlled value:
- arr.length = param (tests/e2e/detectors/test_data/controlled-array-length/0.5.16/array_length_assignment.sol#26)

@ -0,0 +1,3 @@
GetCode.at(address) (tests/e2e/detectors/test_data/assembly/0.4.25/inline_assembly_contract.sol#6-20) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.4.25/inline_assembly_contract.sol#7-20)

@ -0,0 +1,6 @@
VectorSum.sumPureAsm(uint256[]) (tests/e2e/detectors/test_data/assembly/0.4.25/inline_assembly_library.sol#25-47) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.4.25/inline_assembly_library.sol#26-47)
VectorSum.sumAsm(uint256[]) (tests/e2e/detectors/test_data/assembly/0.4.25/inline_assembly_library.sol#16-22) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.4.25/inline_assembly_library.sol#18-21)

@ -0,0 +1,3 @@
GetCode.at(address) (tests/e2e/detectors/test_data/assembly/0.5.16/inline_assembly_contract.sol#6-20) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.5.16/inline_assembly_contract.sol#7-19)

@ -0,0 +1,6 @@
VectorSum.sumAsm(uint256[]) (tests/e2e/detectors/test_data/assembly/0.5.16/inline_assembly_library.sol#16-22) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.5.16/inline_assembly_library.sol#18-20)
VectorSum.sumPureAsm(uint256[]) (tests/e2e/detectors/test_data/assembly/0.5.16/inline_assembly_library.sol#25-47) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.5.16/inline_assembly_library.sol#26-46)

@ -0,0 +1,3 @@
GetCode.at(address) (tests/e2e/detectors/test_data/assembly/0.6.11/inline_assembly_contract.sol#4-18) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.6.11/inline_assembly_contract.sol#5-17)

@ -0,0 +1,6 @@
VectorSum.sumAsm(uint256[]) (tests/e2e/detectors/test_data/assembly/0.6.11/inline_assembly_library.sol#14-20) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.6.11/inline_assembly_library.sol#16-18)
VectorSum.sumPureAsm(uint256[]) (tests/e2e/detectors/test_data/assembly/0.6.11/inline_assembly_library.sol#23-45) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.6.11/inline_assembly_library.sol#24-44)

@ -0,0 +1,3 @@
GetCode.at(address) (tests/e2e/detectors/test_data/assembly/0.7.6/inline_assembly_contract.sol#4-18) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.7.6/inline_assembly_contract.sol#5-17)

@ -0,0 +1,6 @@
VectorSum.sumAsm(uint256[]) (tests/e2e/detectors/test_data/assembly/0.7.6/inline_assembly_library.sol#14-20) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.7.6/inline_assembly_library.sol#16-18)
VectorSum.sumPureAsm(uint256[]) (tests/e2e/detectors/test_data/assembly/0.7.6/inline_assembly_library.sol#23-45) uses assembly
- INLINE ASM (tests/e2e/detectors/test_data/assembly/0.7.6/inline_assembly_library.sol#24-44)

@ -0,0 +1,12 @@
A.bad2() (tests/e2e/detectors/test_data/assert-state-change/0.4.25/assert_state_change.sol#19-21) has an assert() call which possibly changes state.
-assert(bool)(bad2_callee()) (tests/e2e/detectors/test_data/assert-state-change/0.4.25/assert_state_change.sol#20)
Consider using require() or change the invariant to not modify the state.
A.bad0() (tests/e2e/detectors/test_data/assert-state-change/0.4.25/assert_state_change.sol#6-8) has an assert() call which possibly changes state.
-assert(bool)((s_a += 1) > 10) (tests/e2e/detectors/test_data/assert-state-change/0.4.25/assert_state_change.sol#7)
Consider using require() or change the invariant to not modify the state.
A.bad1(uint256) (tests/e2e/detectors/test_data/assert-state-change/0.4.25/assert_state_change.sol#11-13) has an assert() call which possibly changes state.
-assert(bool)((s_a += a) > 10) (tests/e2e/detectors/test_data/assert-state-change/0.4.25/assert_state_change.sol#12)
Consider using require() or change the invariant to not modify the state.

@ -0,0 +1,12 @@
A.bad1(uint256) (tests/e2e/detectors/test_data/assert-state-change/0.5.16/assert_state_change.sol#11-13) has an assert() call which possibly changes state.
-assert(bool)((s_a += a) > 10) (tests/e2e/detectors/test_data/assert-state-change/0.5.16/assert_state_change.sol#12)
Consider using require() or change the invariant to not modify the state.
A.bad2() (tests/e2e/detectors/test_data/assert-state-change/0.5.16/assert_state_change.sol#19-21) has an assert() call which possibly changes state.
-assert(bool)(bad2_callee()) (tests/e2e/detectors/test_data/assert-state-change/0.5.16/assert_state_change.sol#20)
Consider using require() or change the invariant to not modify the state.
A.bad0() (tests/e2e/detectors/test_data/assert-state-change/0.5.16/assert_state_change.sol#6-8) has an assert() call which possibly changes state.
-assert(bool)((s_a += 1) > 10) (tests/e2e/detectors/test_data/assert-state-change/0.5.16/assert_state_change.sol#7)
Consider using require() or change the invariant to not modify the state.

@ -0,0 +1,12 @@
A.bad0() (tests/e2e/detectors/test_data/assert-state-change/0.6.11/assert_state_change.sol#6-8) has an assert() call which possibly changes state.
-assert(bool)((s_a += 1) > 10) (tests/e2e/detectors/test_data/assert-state-change/0.6.11/assert_state_change.sol#7)
Consider using require() or change the invariant to not modify the state.
A.bad1(uint256) (tests/e2e/detectors/test_data/assert-state-change/0.6.11/assert_state_change.sol#11-13) has an assert() call which possibly changes state.
-assert(bool)((s_a += a) > 10) (tests/e2e/detectors/test_data/assert-state-change/0.6.11/assert_state_change.sol#12)
Consider using require() or change the invariant to not modify the state.
A.bad2() (tests/e2e/detectors/test_data/assert-state-change/0.6.11/assert_state_change.sol#19-21) has an assert() call which possibly changes state.
-assert(bool)(bad2_callee()) (tests/e2e/detectors/test_data/assert-state-change/0.6.11/assert_state_change.sol#20)
Consider using require() or change the invariant to not modify the state.

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

Loading…
Cancel
Save