diff --git a/.github/scripts/unit_test_runner.sh b/.github/scripts/unit_test_runner.sh index ad338f342a..92afd0a4ea 100644 --- a/.github/scripts/unit_test_runner.sh +++ b/.github/scripts/unit_test_runner.sh @@ -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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 44bba58788..a003eb168c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8568ef7093..6e76425ddf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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,61 @@ 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 test in `tests/e2e/detectors` +2. Update `ALL_TEST` in `tests/e2e/detectors/test_detectors.py` +3. Run `python tests/e2e/detectors/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. +4. Run `pytest tests/e2e/detectors/test_detectors.py` and check that everything worked. + +> ##### 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 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..a94c3eeb87 --- /dev/null +++ b/Makefile @@ -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) \ No newline at end of file diff --git a/setup.py b/setup.py index 7e563e25a3..59fd81ab5b 100644 --- a/setup.py +++ b/setup.py @@ -15,9 +15,10 @@ "packaging", "prettytable>=0.7.2", "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@windows-rel-path#egg=crytic-compile", "web3>=6.0.0", + "solc-select@git+https://github.com/crytic/solc-select.git@query-artifact-path#egg=solc-select", ], extras_require={ "lint": [ @@ -31,6 +32,7 @@ "deepdiff", "numpy", "coverage[toml]", + "filelock", ], "doc": [ "pdoc", diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..5ea228fd34 --- /dev/null +++ b/tests/conftest.py @@ -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 diff --git a/tests/e2e/compilation/test_resolution.py b/tests/e2e/compilation/test_resolution.py index 4b50b07374..71edaa143f 100644 --- a/tests/e2e/compilation/test_resolution.py +++ b/tests/e2e/compilation/test_resolution.py @@ -3,7 +3,6 @@ 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) diff --git a/tests/tools/read-storage/test_read_storage.py b/tests/tools/read-storage/test_read_storage.py index 38d909bf88..6d2ab007dd 100644 --- a/tests/tools/read-storage/test_read_storage.py +++ b/tests/tools/read-storage/test_read_storage.py @@ -90,7 +90,9 @@ def deploy_contract(w3, ganache, contract_bin, contract_abi) -> Contract: # pylint: disable=too-many-locals @pytest.mark.usefixtures("web3", "ganache") -def test_read_storage(web3, ganache) -> None: +def test_read_storage(web3, ganache, solc_binary_path) -> None: + solc_path = solc_binary_path(version="0.8.10") + assert web3.is_connected() bin_path = Path(TEST_DATA_DIR, "StorageLayout.bin").as_posix() abi_path = Path(TEST_DATA_DIR, "StorageLayout.abi").as_posix() @@ -100,7 +102,7 @@ def test_read_storage(web3, ganache) -> None: contract.functions.store().transact({"from": ganache.eth_address}) address = contract.address - sl = Slither(Path(TEST_DATA_DIR, "storage_layout-0.8.10.sol").as_posix()) + sl = Slither(Path(TEST_DATA_DIR, "storage_layout-0.8.10.sol").as_posix(), solc=solc_path) contracts = sl.contracts srs = SlitherReadStorage(contracts, 100) diff --git a/tests/unit/core/test_arithmetic.py b/tests/unit/core/test_arithmetic.py index 621ff0f940..6de63d7674 100644 --- a/tests/unit/core/test_arithmetic.py +++ b/tests/unit/core/test_arithmetic.py @@ -1,5 +1,4 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither from slither.utils.arithmetic import unchecked_arithemtic_usage @@ -8,9 +7,9 @@ TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" / "arithmetic_usage" -def test_arithmetic_usage() -> None: - solc_select.switch_global_version("0.8.15", always_install=True) - slither = Slither(Path(TEST_DATA_DIR, "test.sol").as_posix()) +def test_arithmetic_usage(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") + slither = Slither(Path(TEST_DATA_DIR, "test.sol").as_posix(), solc=solc_path) assert { f.source_mapping.content_hash for f in unchecked_arithemtic_usage(slither.contracts[0]) diff --git a/tests/unit/core/test_code_comments.py b/tests/unit/core/test_code_comments.py index 01b9ff336d..a943591dcc 100644 --- a/tests/unit/core/test_code_comments.py +++ b/tests/unit/core/test_code_comments.py @@ -1,5 +1,4 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither @@ -8,9 +7,9 @@ CUSTOM_COMMENTS_TEST_DATA_DIR = Path(TEST_DATA_DIR, "custom_comments") -def test_upgradeable_comments() -> None: - solc_select.switch_global_version("0.8.10", always_install=True) - slither = Slither(Path(CUSTOM_COMMENTS_TEST_DATA_DIR, "upgrade.sol").as_posix()) +def test_upgradeable_comments(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.10") + slither = Slither(Path(CUSTOM_COMMENTS_TEST_DATA_DIR, "upgrade.sol").as_posix(), solc=solc_path) compilation_unit = slither.compilation_units[0] proxy = compilation_unit.get_contract_from_name("Proxy")[0] @@ -27,11 +26,13 @@ def test_upgradeable_comments() -> None: assert v1.upgradeable_version == "version_1" -def test_contract_comments() -> None: +def test_contract_comments(solc_binary_path) -> None: comments = " @title Test Contract\n @dev Test comment" - solc_select.switch_global_version("0.8.10", always_install=True) - slither = Slither(Path(CUSTOM_COMMENTS_TEST_DATA_DIR, "contract_comment.sol").as_posix()) + solc_path = solc_binary_path("0.8.10") + slither = Slither( + Path(CUSTOM_COMMENTS_TEST_DATA_DIR, "contract_comment.sol").as_posix(), solc=solc_path + ) compilation_unit = slither.compilation_units[0] contract = compilation_unit.get_contract_from_name("A")[0] @@ -40,8 +41,10 @@ def test_contract_comments() -> None: # Old solc versions have a different parsing of comments # the initial space (after *) is also not kept on every line comments = "@title Test Contract\n@dev Test comment" - solc_select.switch_global_version("0.5.16", always_install=True) - slither = Slither(Path(CUSTOM_COMMENTS_TEST_DATA_DIR, "contract_comment.sol").as_posix()) + solc_path = solc_binary_path("0.5.16") + slither = Slither( + Path(CUSTOM_COMMENTS_TEST_DATA_DIR, "contract_comment.sol").as_posix(), solc=solc_path + ) compilation_unit = slither.compilation_units[0] contract = compilation_unit.get_contract_from_name("A")[0] @@ -49,10 +52,10 @@ def test_contract_comments() -> None: # Test with legacy AST comments = "@title Test Contract\n@dev Test comment" - solc_select.switch_global_version("0.5.16", always_install=True) slither = Slither( Path(CUSTOM_COMMENTS_TEST_DATA_DIR, "contract_comment.sol").as_posix(), solc_force_legacy_json=True, + solc=solc_path, ) compilation_unit = slither.compilation_units[0] contract = compilation_unit.get_contract_from_name("A")[0] diff --git a/tests/unit/core/test_constant_folding.py b/tests/unit/core/test_constant_folding.py index eb40a43c0b..489b4e0ec1 100644 --- a/tests/unit/core/test_constant_folding.py +++ b/tests/unit/core/test_constant_folding.py @@ -6,13 +6,17 @@ CONSTANT_FOLDING_TEST_ROOT = Path(TEST_DATA_DIR, "constant_folding") -def test_constant_folding_unary(): +def test_constant_folding_unary(solc_binary_path): + solc_path = solc_binary_path("0.8.0") file = Path(CONSTANT_FOLDING_TEST_ROOT, "constant_folding_unary.sol").as_posix() - Slither(file) + Slither(file, solc=solc_path) -def test_constant_folding_rational(): - s = Slither(Path(CONSTANT_FOLDING_TEST_ROOT, "constant_folding_rational.sol").as_posix()) +def test_constant_folding_rational(solc_binary_path): + solc_path = solc_binary_path("0.8.0") + s = Slither( + Path(CONSTANT_FOLDING_TEST_ROOT, "constant_folding_rational.sol").as_posix(), solc=solc_path + ) contract = s.get_contract_from_name("C")[0] variable_a = contract.get_state_variable_from_name("a") @@ -50,8 +54,11 @@ def test_constant_folding_rational(): assert str(ConstantFolding(variable_g.expression, "int64").result()) == "-7" -def test_constant_folding_binary_expressions(): - sl = Slither(Path(CONSTANT_FOLDING_TEST_ROOT, "constant_folding_binop.sol").as_posix()) +def test_constant_folding_binary_expressions(solc_binary_path): + sl = Slither( + Path(CONSTANT_FOLDING_TEST_ROOT, "constant_folding_binop.sol").as_posix(), + solc=solc_binary_path("0.8.0"), + ) contract = sl.get_contract_from_name("BinOp")[0] variable_a = contract.get_state_variable_from_name("a") diff --git a/tests/unit/core/test_contract_declaration.py b/tests/unit/core/test_contract_declaration.py index db9a141f59..7760829351 100644 --- a/tests/unit/core/test_contract_declaration.py +++ b/tests/unit/core/test_contract_declaration.py @@ -1,6 +1,5 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither from slither.core.variables.state_variable import StateVariable @@ -9,25 +8,30 @@ CONTRACT_DECL_TEST_ROOT = Path(TEST_DATA_DIR, "contract_declaration") -def test_abstract_contract() -> None: - solc_select.switch_global_version("0.8.0", always_install=True) - slither = Slither(Path(CONTRACT_DECL_TEST_ROOT, "abstract.sol").as_posix()) +def test_abstract_contract(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.0") + slither = Slither(Path(CONTRACT_DECL_TEST_ROOT, "abstract.sol").as_posix(), solc=solc_path) assert not slither.contracts[0].is_fully_implemented - solc_select.switch_global_version("0.5.0", always_install=True) - slither = Slither(Path(CONTRACT_DECL_TEST_ROOT, "implicit_abstract.sol").as_posix()) + solc_path = solc_binary_path("0.5.0") + slither = Slither( + Path(CONTRACT_DECL_TEST_ROOT, "implicit_abstract.sol").as_posix(), solc=solc_path + ) assert not slither.contracts[0].is_fully_implemented slither = Slither( Path(CONTRACT_DECL_TEST_ROOT, "implicit_abstract.sol").as_posix(), solc_force_legacy_json=True, + solc=solc_path, ) assert not slither.contracts[0].is_fully_implemented -def test_private_variable() -> None: - solc_select.switch_global_version("0.8.15", always_install=True) - slither = Slither(Path(CONTRACT_DECL_TEST_ROOT, "private_variable.sol").as_posix()) +def test_private_variable(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") + slither = Slither( + Path(CONTRACT_DECL_TEST_ROOT, "private_variable.sol").as_posix(), solc=solc_path + ) contract_c = slither.get_contract_from_name("C")[0] f = contract_c.functions[0] var_read = f.variables_read[0] diff --git a/tests/unit/core/test_fallback_receive.py b/tests/unit/core/test_fallback_receive.py index 505a9dd6fd..9b00c5948b 100644 --- a/tests/unit/core/test_fallback_receive.py +++ b/tests/unit/core/test_fallback_receive.py @@ -1,5 +1,4 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither from slither.core.declarations.function import FunctionType @@ -7,10 +6,10 @@ TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" -def test_fallback_receive(): - solc_select.switch_global_version("0.6.12", always_install=True) +def test_fallback_receive(solc_binary_path): + solc_path = solc_binary_path("0.6.12") file = Path(TEST_DATA_DIR, "fallback.sol").as_posix() - slither = Slither(file) + slither = Slither(file, solc=solc_path) fake_fallback = slither.get_contract_from_name("FakeFallback")[0] real_fallback = slither.get_contract_from_name("Fallback")[0] diff --git a/tests/unit/core/test_function_declaration.py b/tests/unit/core/test_function_declaration.py index 6f7aa23e7a..651f449de5 100644 --- a/tests/unit/core/test_function_declaration.py +++ b/tests/unit/core/test_function_declaration.py @@ -5,7 +5,6 @@ and that these objects behave correctly. """ from pathlib import Path -from solc_select import solc_select from slither import Slither from slither.core.declarations.function import FunctionType @@ -15,11 +14,11 @@ FUNC_DELC_TEST_ROOT = Path(TEST_DATA_DIR, "function_declaration") -def test_functions(): +def test_functions(solc_binary_path): # pylint: disable=too-many-statements - solc_select.switch_global_version("0.6.12", always_install=True) + solc_path = solc_binary_path("0.6.12") file = Path(FUNC_DELC_TEST_ROOT, "test_function.sol").as_posix() - slither = Slither(file) + slither = Slither(file, solc=solc_path) functions = slither.get_contract_from_name("TestFunction")[0].available_functions_as_dict() f = functions["external_payable(uint256)"] @@ -248,10 +247,10 @@ def test_functions(): assert f.return_type[0] == ElementaryType("bool") -def test_function_can_send_eth(): - solc_select.switch_global_version("0.6.12", always_install=True) +def test_function_can_send_eth(solc_binary_path): + solc_path = solc_binary_path("0.6.12") file = Path(FUNC_DELC_TEST_ROOT, "test_function.sol").as_posix() - slither = Slither(file) + slither = Slither(file, solc=solc_path) compilation_unit = slither.compilation_units[0] functions = compilation_unit.get_contract_from_name("TestFunctionCanSendEth")[ 0 @@ -273,10 +272,10 @@ def test_function_can_send_eth(): assert functions["highlevel_call_via_external()"].can_send_eth() is False -def test_reentrant(): - solc_select.switch_global_version("0.8.10", always_install=True) +def test_reentrant(solc_binary_path): + solc_path = solc_binary_path("0.8.10") file = Path(FUNC_DELC_TEST_ROOT, "test_function_reentrant.sol").as_posix() - slither = Slither(file) + slither = Slither(file, solc=solc_path) compilation_unit = slither.compilation_units[0] functions = compilation_unit.get_contract_from_name("TestReentrant")[ 0 @@ -290,10 +289,10 @@ def test_reentrant(): assert functions["internal_and_reentrant()"].is_reentrant -def test_public_variable() -> None: - solc_select.switch_global_version("0.6.12", always_install=True) +def test_public_variable(solc_binary_path) -> None: + solc_path = solc_binary_path("0.6.12") file = Path(FUNC_DELC_TEST_ROOT, "test_function.sol").as_posix() - slither = Slither(file) + slither = Slither(file, solc=solc_path) contracts = slither.get_contract_from_name("TestFunction") assert len(contracts) == 1 contract = contracts[0] diff --git a/tests/unit/core/test_source_mapping.py b/tests/unit/core/test_source_mapping.py index 745d391d94..fe53359777 100644 --- a/tests/unit/core/test_source_mapping.py +++ b/tests/unit/core/test_source_mapping.py @@ -1,5 +1,4 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither from slither.core.declarations import Function @@ -8,14 +7,13 @@ SRC_MAPPING_TEST_ROOT = Path(TEST_DATA_DIR, "src_mapping") -def test_source_mapping(): - solc_select.switch_global_version("0.6.12", always_install=True) +def test_source_mapping(solc_binary_path): + solc_path = solc_binary_path("0.6.12") file = Path(SRC_MAPPING_TEST_ROOT, "inheritance.sol").as_posix() - slither = Slither(file) + slither = Slither(file, solc=solc_path) # Check if A.f() is at the offset 27 functions = slither.offset_to_objects(file, 27) - print(functions) assert len(functions) == 1 function = functions.pop() assert isinstance(function, Function) @@ -79,13 +77,13 @@ def _sort_references_lines(refs: list) -> list: return sorted([ref.lines[0] for ref in refs]) -def _test_references_user_defined_aliases(): +def test_references_user_defined_aliases(solc_binary_path): """ Tests if references are filled correctly for user defined aliases (declared using "type [...] is [...]" statement). """ - solc_select.switch_global_version("0.8.16", always_install=True) + solc_path = solc_binary_path("0.8.16") file = Path(SRC_MAPPING_TEST_ROOT, "ReferencesUserDefinedAliases.sol").as_posix() - slither = Slither(file) + slither = Slither(file, solc=solc_path) alias_top_level = slither.compilation_units[0].user_defined_value_types["aliasTopLevel"] assert len(alias_top_level.references) == 2 @@ -102,26 +100,16 @@ def _test_references_user_defined_aliases(): assert lines == [13, 16] -def _test_references_user_defined_types_when_casting(): +def test_references_user_defined_types_when_casting(solc_binary_path): """ Tests if references are filled correctly for user defined types in case of casting. """ - solc_select.switch_global_version("0.8.16", always_install=True) + solc_path = solc_binary_path("0.8.16") file = Path(SRC_MAPPING_TEST_ROOT, "ReferencesUserDefinedTypesCasting.sol").as_posix() - slither = Slither(file) + slither = Slither(file, solc=solc_path) contracts = slither.compilation_units[0].contracts a = contracts[0] if contracts[0].is_interface else contracts[1] assert len(a.references) == 2 lines = _sort_references_lines(a.references) assert lines == [12, 18] - - -def test_references(): - """ - Tests if references list is filled correctly in the following cases: - - user defined aliases (declared using "type [...] is [...]" statement) - - user defined types in case of casting (TypeConversion expressions) - """ - _test_references_user_defined_aliases() - _test_references_user_defined_types_when_casting() diff --git a/tests/unit/core/test_storage_layout.py b/tests/unit/core/test_storage_layout.py index 4cb439d770..3337eb0f74 100644 --- a/tests/unit/core/test_storage_layout.py +++ b/tests/unit/core/test_storage_layout.py @@ -1,21 +1,20 @@ import json from pathlib import Path from subprocess import PIPE, Popen -from solc_select import solc_select from slither import Slither TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" STORAGE_TEST_ROOT = Path(TEST_DATA_DIR, "storage_layout") -def test_storage_layout(): +def test_storage_layout(solc_binary_path): # the storage layout has not yet changed between solidity versions so we will test with one version of the compiler - solc_select.switch_global_version("0.8.10", always_install=True) + solc_path = solc_binary_path("0.8.10") test_item = Path(STORAGE_TEST_ROOT, "storage_layout-0.8.10.sol").as_posix() - sl = Slither(test_item, solc_force_legacy_json=False, disallow_partial=True) + sl = Slither(test_item, disallow_partial=True, solc=solc_path) - with Popen(["solc", test_item, "--storage-layout"], stdout=PIPE) as process: + with Popen([solc_path, test_item, "--storage-layout"], stdout=PIPE) as process: for line in process.stdout: # parse solc output if '{"storage":[{' in line.decode("utf-8"): # find the storage layout layout = iter(json.loads(line)["storage"]) @@ -34,3 +33,5 @@ def test_storage_layout(): break except KeyError as e: print(f"not found {e} ") + process.communicate() + assert process.returncode == 0 diff --git a/tests/unit/core/test_using_for.py b/tests/unit/core/test_using_for.py index 88a7ea0431..ebba72eeff 100644 --- a/tests/unit/core/test_using_for.py +++ b/tests/unit/core/test_using_for.py @@ -1,7 +1,6 @@ from pathlib import Path from crytic_compile import CryticCompile from crytic_compile.platform.solc_standard_json import SolcStandardJson -from solc_select import solc_select from slither import Slither from slither.slithir.operations import InternalCall, LibraryCall @@ -12,19 +11,21 @@ USING_FOR_TEST_DATA_DIR = Path(TEST_DATA_DIR, "using_for") -def test_using_for_global_collision() -> None: - solc_select.switch_global_version("0.8.18", always_install=True) +def test_using_for_global_collision(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") standard_json = SolcStandardJson() for source_file in Path(USING_FOR_TEST_DATA_DIR, "using_for_global_collision").rglob("*.sol"): standard_json.add_source_file(Path(source_file).as_posix()) - compilation = CryticCompile(standard_json) + compilation = CryticCompile(standard_json, solc=solc_path) sl = Slither(compilation) _run_all_detectors(sl) -def test_using_for_top_level_same_name() -> None: - solc_select.switch_global_version("0.8.15", always_install=True) - slither = Slither(Path(USING_FOR_TEST_DATA_DIR, "using-for-3-0.8.0.sol").as_posix()) +def test_using_for_top_level_same_name(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") + slither = Slither( + Path(USING_FOR_TEST_DATA_DIR, "using-for-3-0.8.0.sol").as_posix(), solc=solc_path + ) contract_c = slither.get_contract_from_name("C")[0] libCall = contract_c.get_function_from_full_name("libCall(uint256)") for ir in libCall.all_slithir_operations(): @@ -33,9 +34,11 @@ def test_using_for_top_level_same_name() -> None: assert False -def test_using_for_top_level_implicit_conversion() -> None: - solc_select.switch_global_version("0.8.15", always_install=True) - slither = Slither(Path(USING_FOR_TEST_DATA_DIR, "using-for-4-0.8.0.sol").as_posix()) +def test_using_for_top_level_implicit_conversion(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") + slither = Slither( + Path(USING_FOR_TEST_DATA_DIR, "using-for-4-0.8.0.sol").as_posix(), solc=solc_path + ) contract_c = slither.get_contract_from_name("C")[0] libCall = contract_c.get_function_from_full_name("libCall(uint16)") for ir in libCall.all_slithir_operations(): @@ -44,10 +47,11 @@ def test_using_for_top_level_implicit_conversion() -> None: assert False -def test_using_for_alias_top_level() -> None: - solc_select.switch_global_version("0.8.15", always_install=True) +def test_using_for_alias_top_level(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") slither = Slither( - Path(USING_FOR_TEST_DATA_DIR, "using-for-alias-top-level-0.8.0.sol").as_posix() + Path(USING_FOR_TEST_DATA_DIR, "using-for-alias-top-level-0.8.0.sol").as_posix(), + solc=solc_path, ) contract_c = slither.get_contract_from_name("C")[0] libCall = contract_c.get_function_from_full_name("libCall(uint256)") @@ -64,10 +68,11 @@ def test_using_for_alias_top_level() -> None: assert False -def test_using_for_alias_contract() -> None: - solc_select.switch_global_version("0.8.15", always_install=True) +def test_using_for_alias_contract(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") slither = Slither( - Path(USING_FOR_TEST_DATA_DIR, "using-for-alias-contract-0.8.0.sol").as_posix() + Path(USING_FOR_TEST_DATA_DIR, "using-for-alias-contract-0.8.0.sol").as_posix(), + solc=solc_path, ) contract_c = slither.get_contract_from_name("C")[0] libCall = contract_c.get_function_from_full_name("libCall(uint256)") @@ -84,9 +89,11 @@ def test_using_for_alias_contract() -> None: assert False -def test_using_for_in_library() -> None: - solc_select.switch_global_version("0.8.15", always_install=True) - slither = Slither(Path(USING_FOR_TEST_DATA_DIR, "using-for-in-library-0.8.0.sol").as_posix()) +def test_using_for_in_library(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.15") + slither = Slither( + Path(USING_FOR_TEST_DATA_DIR, "using-for-in-library-0.8.0.sol").as_posix(), solc=solc_path + ) contract_c = slither.get_contract_from_name("A")[0] libCall = contract_c.get_function_from_full_name("a(uint256)") for ir in libCall.all_slithir_operations(): diff --git a/tests/unit/slithir/test_operation_reads.py b/tests/unit/slithir/test_operation_reads.py index 3b5565c9f2..b82ef9d48a 100644 --- a/tests/unit/slithir/test_operation_reads.py +++ b/tests/unit/slithir/test_operation_reads.py @@ -1,6 +1,5 @@ from pathlib import Path from collections import namedtuple -from solc_select import solc_select from slither import Slither from slither.slithir.operations import Operation, NewContract @@ -28,12 +27,12 @@ def check_num_states_vars_read(function, slithir_op: Operation, num_reads_expect OPERATION_TEST = [OperationTest("NewContract", NewContract)] -def test_operation_reads() -> None: +def test_operation_reads(solc_binary_path) -> None: """ Every slithir operation has its own contract and reads all local and state variables in readAllLocalVariables and readAllStateVariables, respectively. """ - solc_select.switch_global_version("0.8.15", always_install=True) - slither = Slither(Path(TEST_DATA_DIR, "operation_reads.sol").as_posix()) + solc_path = solc_binary_path("0.8.15") + slither = Slither(Path(TEST_DATA_DIR, "operation_reads.sol").as_posix(), solc=solc_path) for op_test in OPERATION_TEST: print(op_test) @@ -48,7 +47,3 @@ def test_operation_reads() -> None: local_function = target.get_function_from_signature("readAllLocalVariables()") num_local_vars = len(local_function.local_variables) check_num_local_vars_read(local_function, op_test.slithir_op, num_local_vars) - - -if __name__ == "__main__": - test_operation_reads() diff --git a/tests/unit/slithir/test_ssa_generation.py b/tests/unit/slithir/test_ssa_generation.py index 2146a3126d..4e26aa54d0 100644 --- a/tests/unit/slithir/test_ssa_generation.py +++ b/tests/unit/slithir/test_ssa_generation.py @@ -1,16 +1,12 @@ -# pylint: disable=too-many-lines +# # pylint: disable=too-many-lines import pathlib -from argparse import ArgumentTypeError from collections import defaultdict -from contextlib import contextmanager +from argparse import ArgumentTypeError from inspect import getsourcefile -from tempfile import NamedTemporaryFile -from typing import Union, List, Optional, Dict, Callable +from typing import Union, List, Dict, Callable import pytest -from solc_select import solc_select from solc_select.solc_select import valid_version as solc_valid_version - from slither import Slither from slither.core.cfg.node import Node, NodeType from slither.core.declarations import Function, Contract @@ -230,45 +226,7 @@ def have_phi_for_var( assert have_phi_for_var(df, ssa_lvalue) -@contextmanager -def select_solc_version(version: Optional[str]) -> None: - """Selects solc version to use for running tests. - - If no version is provided, latest is used.""" - # If no solc_version selected just use the latest avail - if not version: - # This sorts the versions numerically - vers = sorted( - map( - lambda x: (int(x[0]), int(x[1]), int(x[2])), - map(lambda x: x.split(".", 3), solc_select.installed_versions()), - ) - ) - ver = list(vers)[-1] - version = ".".join(map(str, ver)) - solc_select.switch_global_version(version, always_install=True) - yield version - - -@contextmanager -def slither_from_source(source_code: str, solc_version: Optional[str] = None): - """Yields a Slither instance using source_code string and solc_version - - Creates a temporary file and changes the solc-version temporary to solc_version. - """ - - fname = "" - try: - with NamedTemporaryFile(dir=SCRIPT_DIR, mode="w", suffix=".sol", delete=False) as f: - fname = f.name - f.write(source_code) - with select_solc_version(solc_version): - yield Slither(fname) - finally: - pathlib.Path(fname).unlink() - - -def verify_properties_hold(source_code_or_slither: Union[str, Slither]) -> None: +def verify_properties_hold(slither: Slither) -> None: """Ensures that basic properties of SSA hold true""" def verify_func(func: Function) -> None: @@ -289,12 +247,8 @@ def verify(slither: Slither) -> None: _dump_function(f) verify_func(f) - if isinstance(source_code_or_slither, Slither): - verify(source_code_or_slither) - else: - slither: Slither - with slither_from_source(source_code_or_slither) as slither: - verify(slither) + assert isinstance(slither, Slither) + verify(slither) def _dump_function(f: Function) -> None: @@ -327,8 +281,8 @@ def get_ssa_of_type(f: Union[Function, Node], ssatype) -> List[Operation]: return get_filtered_ssa(f, lambda ssanode: isinstance(ssanode, ssatype)) -def test_multi_write() -> None: - contract = """ +def test_multi_write(slither_from_source) -> None: + source = """ pragma solidity ^0.8.11; contract Test { function multi_write(uint val) external pure returns(uint) { @@ -337,11 +291,12 @@ def test_multi_write() -> None: val = 3; } }""" - verify_properties_hold(contract) + with slither_from_source(source) as slither: + verify_properties_hold(slither) -def test_single_branch_phi() -> None: - contract = """ +def test_single_branch_phi(slither_from_source) -> None: + source = """ pragma solidity ^0.8.11; contract Test { function single_branch_phi(uint val) external pure returns(uint) { @@ -352,11 +307,12 @@ def test_single_branch_phi() -> None: } } """ - verify_properties_hold(contract) + with slither_from_source(source) as slither: + verify_properties_hold(slither) -def test_basic_phi() -> None: - contract = """ +def test_basic_phi(slither_from_source) -> None: + source = """ pragma solidity ^0.8.11; contract Test { function basic_phi(uint val) external pure returns(uint) { @@ -369,11 +325,12 @@ def test_basic_phi() -> None: } } """ - verify_properties_hold(contract) + with slither_from_source(source) as slither: + verify_properties_hold(slither) -def test_basic_loop_phi() -> None: - contract = """ +def test_basic_loop_phi(slither_from_source) -> None: + source = """ pragma solidity ^0.8.11; contract Test { function basic_loop_phi(uint val) external pure returns(uint) { @@ -384,12 +341,13 @@ def test_basic_loop_phi() -> None: } } """ - verify_properties_hold(contract) + with slither_from_source(source) as slither: + verify_properties_hold(slither) @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_phi_propagation_loop(): - contract = """ +def test_phi_propagation_loop(slither_from_source): + source = """ pragma solidity ^0.8.11; contract Test { function looping(uint v) external pure returns(uint) { @@ -405,12 +363,13 @@ def test_phi_propagation_loop(): } } """ - verify_properties_hold(contract) + with slither_from_source(source) as slither: + verify_properties_hold(slither) @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_free_function_properties(): - contract = """ +def test_free_function_properties(slither_from_source): + source = """ pragma solidity ^0.8.11; function free_looping(uint v) returns(uint) { @@ -427,10 +386,11 @@ def test_free_function_properties(): contract Test {} """ - verify_properties_hold(contract) + with slither_from_source(source) as slither: + verify_properties_hold(slither) -def test_ssa_inter_transactional() -> None: +def test_ssa_inter_transactional(slither_from_source) -> None: source = """ pragma solidity ^0.8.11; contract A { @@ -473,7 +433,7 @@ def test_ssa_inter_transactional() -> None: @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_ssa_phi_callbacks(): +def test_ssa_phi_callbacks(slither_from_source): source = """ pragma solidity ^0.8.11; contract A { @@ -532,7 +492,7 @@ def test_ssa_phi_callbacks(): @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_storage_refers_to(): +def test_storage_refers_to(slither_from_source): """Test the storage aspects of the SSA IR When declaring a var as being storage, start tracking what storage it refers_to. @@ -601,7 +561,7 @@ def test_storage_refers_to(): @pytest.mark.skipif( not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform" ) -def test_initial_version_exists_for_locals(): +def test_initial_version_exists_for_locals(slither_from_source): """ In solidity you can write statements such as uint a = a + 1, this test ensures that can be handled for local variables. @@ -638,7 +598,7 @@ def test_initial_version_exists_for_locals(): @pytest.mark.skipif( not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform" ) -def test_initial_version_exists_for_state_variables(): +def test_initial_version_exists_for_state_variables(slither_from_source): """ In solidity you can write statements such as uint a = a + 1, this test ensures that can be handled for state variables. @@ -675,7 +635,7 @@ def test_initial_version_exists_for_state_variables(): @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_initial_version_exists_for_state_variables_function_assign(): +def test_initial_version_exists_for_state_variables_function_assign(slither_from_source): """ In solidity you can write statements such as uint a = a + 1, this test ensures that can be handled for local variables. @@ -717,7 +677,7 @@ def test_initial_version_exists_for_state_variables_function_assign(): @pytest.mark.skipif( not valid_version("0.4.0"), reason="Solidity version 0.4.0 not available on this platform" ) -def test_return_local_before_assign(): +def test_return_local_before_assign(slither_from_source): src = """ // this require solidity < 0.5 // a variable can be returned before declared. Ensure it can be @@ -747,7 +707,7 @@ def test_return_local_before_assign(): @pytest.mark.skipif( not valid_version("0.5.0"), reason="Solidity version 0.5.0 not available on this platform" ) -def test_shadow_local(): +def test_shadow_local(slither_from_source): src = """ contract A { // this require solidity 0.5 @@ -772,7 +732,7 @@ def test_shadow_local(): @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_multiple_named_args_returns(): +def test_multiple_named_args_returns(slither_from_source): """Verifies that named arguments and return values have correct versions Each arg/ret have an initial version, version 0, and is written once and should @@ -801,7 +761,7 @@ def test_multiple_named_args_returns(): @pytest.mark.xfail(reason="Tests for wanted state of SSA IR, not current.", strict=True) -def test_memory_array(): +def test_memory_array(slither_from_source): src = """ contract MemArray { struct A { @@ -867,7 +827,7 @@ def test_memory_array(): @pytest.mark.xfail(reason="Tests for wanted state of SSA IR, not current.", strict=True) -def test_storage_array(): +def test_storage_array(slither_from_source): src = """ contract StorageArray { struct A { @@ -922,7 +882,7 @@ def test_storage_array(): @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_issue_468(): +def test_issue_468(slither_from_source): """ Ensure issue 468 is corrected as per https://github.com/crytic/slither/issues/468#issuecomment-620974151 @@ -976,7 +936,7 @@ def test_issue_468(): @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_issue_434(): +def test_issue_434(slither_from_source): source = """ contract Contract { int public a; @@ -1030,7 +990,7 @@ def test_issue_434(): @pytest.mark.xfail(strict=True, reason="Fails in current slither version. Fix in #1102.") -def test_issue_473(): +def test_issue_473(slither_from_source): source = """ contract Contract { function f() public returns (int) { @@ -1073,7 +1033,7 @@ def test_issue_473(): assert second_phi.lvalue in return_value.values -def test_issue_1748(): +def test_issue_1748(slither_from_source): source = """ contract Contract { uint[] arr; diff --git a/tests/unit/slithir/test_ternary_expressions.py b/tests/unit/slithir/test_ternary_expressions.py index 376048e1d5..0acd9345d7 100644 --- a/tests/unit/slithir/test_ternary_expressions.py +++ b/tests/unit/slithir/test_ternary_expressions.py @@ -1,5 +1,4 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither from slither.core.cfg.node import NodeType from slither.slithir.operations import Assignment @@ -8,10 +7,10 @@ TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" # pylint: disable=too-many-nested-blocks -def test_ternary_conversions() -> None: +def test_ternary_conversions(solc_binary_path) -> None: """This tests that true and false sons define the same number of variables that the father node declares""" - solc_select.switch_global_version("0.8.0", always_install=True) - slither = Slither(Path(TEST_DATA_DIR, "ternary_expressions.sol").as_posix()) + solc_path = solc_binary_path("0.8.0") + slither = Slither(Path(TEST_DATA_DIR, "ternary_expressions.sol").as_posix(), solc=solc_path) for contract in slither.contracts: for function in contract.functions: vars_declared = 0 @@ -37,7 +36,3 @@ def test_ternary_conversions() -> None: vars_assigned += 1 assert vars_declared == vars_assigned - - -if __name__ == "__main__": - test_ternary_conversions() diff --git a/tests/unit/utils/test_code_generation.py b/tests/unit/utils/test_code_generation.py index 6794896340..35f6cea0ed 100644 --- a/tests/unit/utils/test_code_generation.py +++ b/tests/unit/utils/test_code_generation.py @@ -1,5 +1,4 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither from slither.utils.code_generation import ( @@ -9,10 +8,10 @@ TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" / "code_generation" -def test_interface_generation() -> None: - solc_select.switch_global_version("0.8.4", always_install=True) +def test_interface_generation(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.4") - sl = Slither(Path(TEST_DATA_DIR, "CodeGeneration.sol").as_posix()) + sl = Slither(Path(TEST_DATA_DIR, "CodeGeneration.sol").as_posix(), solc=solc_path) actual = generate_interface(sl.get_contract_from_name("TestContract")[0]) expected_path = Path(TEST_DATA_DIR, "TEST_generated_code.sol").as_posix() diff --git a/tests/unit/utils/test_functions_ids.py b/tests/unit/utils/test_functions_ids.py index c944c54731..9af42ad856 100644 --- a/tests/unit/utils/test_functions_ids.py +++ b/tests/unit/utils/test_functions_ids.py @@ -1,5 +1,4 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither # % solc functions_ids.sol --hashes @@ -41,10 +40,10 @@ TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" -def test_functions_ids() -> None: - solc_select.switch_global_version("0.7.0", always_install=True) +def test_functions_ids(solc_binary_path) -> None: + solc_path = solc_binary_path("0.7.0") file = Path(TEST_DATA_DIR, "functions_ids.sol").as_posix() - sl = Slither(file) + sl = Slither(file, solc=solc_path) contracts_c = sl.get_contract_from_name("C") assert len(contracts_c) == 1 contract_c = contracts_c[0] @@ -58,7 +57,3 @@ def test_functions_ids() -> None: assert get_function_id(var.solidity_signature) == int(hashes, 16) else: assert get_function_id(func.solidity_signature) == int(hashes, 16) - - -if __name__ == "__main__": - test_functions_ids() diff --git a/tests/unit/utils/test_type_helpers.py b/tests/unit/utils/test_type_helpers.py index b6e913d33b..420329ab2f 100644 --- a/tests/unit/utils/test_type_helpers.py +++ b/tests/unit/utils/test_type_helpers.py @@ -1,13 +1,12 @@ from pathlib import Path -from solc_select import solc_select from slither import Slither TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data" -def test_function_id_rec_structure() -> None: - solc_select.switch_global_version("0.8.0", always_install=True) - slither = Slither(Path(TEST_DATA_DIR, "type_helpers.sol").as_posix()) +def test_function_id_rec_structure(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.0") + slither = Slither(Path(TEST_DATA_DIR, "type_helpers.sol").as_posix(), solc=solc_path) for compilation_unit in slither.compilation_units: for function in compilation_unit.functions: assert function.solidity_signature diff --git a/tests/unit/utils/test_upgradeability_util.py b/tests/unit/utils/test_upgradeability_util.py index 7d6fb82dad..1563d31179 100644 --- a/tests/unit/utils/test_upgradeability_util.py +++ b/tests/unit/utils/test_upgradeability_util.py @@ -1,8 +1,6 @@ import os from pathlib import Path -from solc_select import solc_select - from slither import Slither from slither.core.expressions import Literal from slither.utils.upgradeability import ( @@ -16,10 +14,10 @@ # pylint: disable=too-many-locals -def test_upgrades_compare() -> None: - solc_select.switch_global_version("0.8.2", always_install=True) +def test_upgrades_compare(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.2") - sl = Slither(os.path.join(TEST_DATA_DIR, "TestUpgrades-0.8.2.sol")) + sl = Slither(os.path.join(TEST_DATA_DIR, "TestUpgrades-0.8.2.sol"), solc=solc_path) v1 = sl.get_contract_from_name("ContractV1")[0] v2 = sl.get_contract_from_name("ContractV2")[0] missing_vars, new_vars, tainted_vars, new_funcs, modified_funcs, tainted_funcs = compare(v1, v2) @@ -37,9 +35,9 @@ def test_upgrades_compare() -> None: ] -def test_upgrades_implementation_var() -> None: - solc_select.switch_global_version("0.8.2", always_install=True) - sl = Slither(os.path.join(TEST_DATA_DIR, "TestUpgrades-0.8.2.sol")) +def test_upgrades_implementation_var(solc_binary_path) -> None: + solc_path = solc_binary_path("0.8.2") + sl = Slither(os.path.join(TEST_DATA_DIR, "TestUpgrades-0.8.2.sol"), solc=solc_path) erc_1967_proxy = sl.get_contract_from_name("ERC1967Proxy")[0] storage_proxy = sl.get_contract_from_name("InheritedStorageProxy")[0] @@ -53,8 +51,8 @@ def test_upgrades_implementation_var() -> None: assert target == storage_proxy.get_state_variable_from_name("implementation") assert slot.slot == 1 - solc_select.switch_global_version("0.5.0", always_install=True) - sl = Slither(os.path.join(TEST_DATA_DIR, "TestUpgrades-0.5.0.sol")) + solc_path = solc_binary_path("0.5.0") + sl = Slither(os.path.join(TEST_DATA_DIR, "TestUpgrades-0.5.0.sol"), solc=solc_path) eip_1822_proxy = sl.get_contract_from_name("EIP1822Proxy")[0] # zos_proxy = sl.get_contract_from_name("ZosProxy")[0]