From 1fbd4d20fd0e8954d9d3bd0bd3c09a7da94b19cc Mon Sep 17 00:00:00 2001 From: GianfrancoBazzani <55500596+GianfrancoBazzani@users.noreply.github.com> Date: Wed, 3 Apr 2024 18:55:50 +0200 Subject: [PATCH 1/2] :hammer: fallback to Foundry remappings --- fuzz_utils/utils/remappings.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/fuzz_utils/utils/remappings.py b/fuzz_utils/utils/remappings.py index 22ccbcb..c98e6d0 100644 --- a/fuzz_utils/utils/remappings.py +++ b/fuzz_utils/utils/remappings.py @@ -13,17 +13,23 @@ def find_remappings(include_attacks: bool) -> dict: solmate = r"(\S+)=lib\/solmate\/(?!\S*lib\/)(\S*)" properties = r"(\S+)=lib\/properties\/(?!\S*lib\/)(\S*)" remappings: str = "" - + if os.path.exists("remappings.txt"): with open("remappings.txt", "r", encoding="utf-8") as file: remappings = file.read() - else: - output = subprocess.run(["forge", "remappings"], capture_output=True, text=True, check=True) - remappings = str(output.stdout) + + output = subprocess.run(["forge", "remappings"], capture_output=True, text=True, check=True) + forge_remappings = str(output.stdout) oz_matches = re.findall(openzeppelin, remappings) + if len(oz_matches) == 0: + oz_matches = re.findall(openzeppelin, forge_remappings) sol_matches = re.findall(solmate, remappings) + if len(sol_matches) == 0: + sol_matches = re.findall(solmate, forge_remappings) prop_matches = re.findall(properties, remappings) + if len(prop_matches) == 0: + prop_matches = re.findall(properties, forge_remappings) if include_attacks and len(oz_matches) == 0 and len(sol_matches) == 0: handle_exit( From a0371a1d5eec8c7fddba9b9f031e37119973d277 Mon Sep 17 00:00:00 2001 From: tuturu-tech Date: Wed, 24 Apr 2024 11:04:46 +0200 Subject: [PATCH 2/2] fetch remappings from crytic-compile, add two tests --- fuzz_utils/parsing/commands/template.py | 2 +- fuzz_utils/utils/remappings.py | 35 +++++++------ tests/conftest.py | 13 +++-- tests/test_harness.py | 4 +- tests/test_remapping_detection.py | 68 +++++++++++++++++++++++-- 5 files changed, 94 insertions(+), 28 deletions(-) diff --git a/fuzz_utils/parsing/commands/template.py b/fuzz_utils/parsing/commands/template.py index 46dd165..7a6abf5 100644 --- a/fuzz_utils/parsing/commands/template.py +++ b/fuzz_utils/parsing/commands/template.py @@ -64,7 +64,7 @@ def template_command(args: Namespace) -> None: # Check if dependencies are installed include_attacks = bool("attacks" in config and len(config["attacks"]) > 0) - remappings = find_remappings(include_attacks) + remappings = find_remappings(include_attacks, slither) generator = HarnessGenerator(config, slither, remappings) generator.generate_templates() diff --git a/fuzz_utils/utils/remappings.py b/fuzz_utils/utils/remappings.py index c98e6d0..f3cd2a4 100644 --- a/fuzz_utils/utils/remappings.py +++ b/fuzz_utils/utils/remappings.py @@ -2,34 +2,39 @@ import os import subprocess import re +from slither import Slither from fuzz_utils.utils.crytic_print import CryticPrint from fuzz_utils.utils.error_handler import handle_exit - -def find_remappings(include_attacks: bool) -> dict: +# pylint: disable=too-many-locals +def find_remappings(include_attacks: bool, slither: Slither) -> dict: """Finds the remappings used and returns a dict with the values""" CryticPrint().print_information("Checking dependencies...") openzeppelin = r"(\S+)=lib\/openzeppelin-contracts\/(?!\S*lib\/)(\S*)" solmate = r"(\S+)=lib\/solmate\/(?!\S*lib\/)(\S*)" properties = r"(\S+)=lib\/properties\/(?!\S*lib\/)(\S*)" + + working_dir = slither.crytic_compile.working_dir + platform_config = slither.crytic_compile.platform.config(working_dir) + remappings: str = "" - - if os.path.exists("remappings.txt"): - with open("remappings.txt", "r", encoding="utf-8") as file: - remappings = file.read() - - output = subprocess.run(["forge", "remappings"], capture_output=True, text=True, check=True) - forge_remappings = str(output.stdout) + if platform_config: + remappings = "\n".join(platform_config.remappings) + else: + output = subprocess.run(["forge", "remappings"], capture_output=True, text=True, check=True) + forge_remaps = str(output.stdout).split("\n") + + if os.path.exists("remappings.txt"): + with open("remappings.txt", "r", encoding="utf-8") as file: + remappings_file = file.read().split("\n") + # Converting to set to remove duplicates, back to list for joining + forge_remaps = list(set(forge_remaps + remappings_file)) + + remappings = "\n".join(forge_remaps) oz_matches = re.findall(openzeppelin, remappings) - if len(oz_matches) == 0: - oz_matches = re.findall(openzeppelin, forge_remappings) sol_matches = re.findall(solmate, remappings) - if len(sol_matches) == 0: - sol_matches = re.findall(solmate, forge_remappings) prop_matches = re.findall(properties, remappings) - if len(prop_matches) == 0: - prop_matches = re.findall(properties, forge_remappings) if include_attacks and len(oz_matches) == 0 and len(sol_matches) == 0: handle_exit( diff --git a/tests/conftest.py b/tests/conftest.py index 5afa030..f12215c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -118,7 +118,7 @@ def setup_foundry_temp_dir(tmp_path_factory: Any) -> None: ["forge", "install", "transmissions11/solmate", "--no-git"], check=True, cwd=temp_dir ) # Create remappings file - create_remappings_file(temp_dir) + create_remappings_file(temp_dir, None) # Delete unnecessary files counter_path = temp_dir / "src" / "Counter.sol" @@ -155,13 +155,16 @@ def setup_foundry_temp_dir(tmp_path_factory: Any) -> None: os.chdir(temp_dir) -def create_remappings_file(temp_dir: Any) -> None: +def create_remappings_file(temp_dir: Any, out_str: str | None) -> None: """Creates a remappings file""" remappings = os.path.join(temp_dir, "remappings.txt") with open(remappings, "w", encoding="utf-8") as outfile: - outfile.write( - "forge-std/=lib/forge-std/src/\nproperties/=lib/properties/contracts/\nsolmate/=lib/solmate/src/\nsrc/=src/" - ) + if out_str: + outfile.write(out_str) + else: + outfile.write( + "forge-std/=lib/forge-std/src/\nproperties/=lib/properties/contracts/\nsolmate/=lib/solmate/src/\nsrc/=src/" + ) def copy_directory_contents(src_dir: str, dest_dir: str) -> None: diff --git a/tests/test_harness.py b/tests/test_harness.py index be05cee..feafb0c 100644 --- a/tests/test_harness.py +++ b/tests/test_harness.py @@ -191,9 +191,9 @@ def run_harness( expected_functions: set[str], ) -> None: """Sets up the HarnessGenerator""" - remappings = find_remappings(False) - config = copy.deepcopy(default_config) slither = Slither(compilation_path) + remappings = find_remappings(False, slither) + config = copy.deepcopy(default_config) config["name"] = harness_name config["compilationPath"] = compilation_path diff --git a/tests/test_remapping_detection.py b/tests/test_remapping_detection.py index 292ad6d..2b39283 100644 --- a/tests/test_remapping_detection.py +++ b/tests/test_remapping_detection.py @@ -2,6 +2,7 @@ import os from typing import Any import pytest +from slither import Slither from fuzz_utils.utils.remappings import find_remappings from .conftest import create_remappings_file @@ -11,6 +12,7 @@ def test_remappings_are_detected_when_no_file( ) -> None: """Test if remappings are fetched when no remappings.txt file exists""" temp_dir = os.getcwd() + slither = Slither(temp_dir) # Remove remappings file remappings_path = os.path.join(temp_dir, "remappings.txt") @@ -19,18 +21,18 @@ def test_remappings_are_detected_when_no_file( # Look for remappings, expecting not to fail try: - find_remappings(True) + find_remappings(True, slither) except SystemExit: # Re-create remappings file and raise error - create_remappings_file(temp_dir) + create_remappings_file(temp_dir, None) pytest.fail("Finding remappings failed with SystemExit") except Exception: # pylint: disable=broad-except # Re-create remappings file and raise error - create_remappings_file(temp_dir) + create_remappings_file(temp_dir, None) pytest.fail("Finding remappings failed with an Exception") # If success - create_remappings_file(temp_dir) + create_remappings_file(temp_dir, None) def test_remappings_are_detected_when_file_exists( @@ -38,5 +40,61 @@ def test_remappings_are_detected_when_file_exists( ) -> None: """Test if remappings are fetched when a remappings.txt file exists""" temp_dir = os.getcwd() + slither = Slither(temp_dir) assert os.path.exists(os.path.join(temp_dir, "remappings.txt")) - find_remappings(True) + find_remappings(True, slither) + + +def test_remappings_are_detected_when_incomplete_remappings_file( + setup_foundry_temp_dir: Any, # pylint: disable=unused-argument +) -> None: + """Test if remappings are fetched when a remappings.txt file exists but is incomplete""" + temp_dir = os.getcwd() + slither = Slither(temp_dir) + + # Modify remappings file to contain an incomplete list of remappings + out_str = "forge-std/=lib/forge-std/src/\nsolmate/=lib/solmate/src/\nsrc/=src/" + create_remappings_file(temp_dir, out_str) + + # Look for remappings, expecting not to fail + try: + find_remappings(True, slither) + except SystemExit: + # Reset remappings file and raise error + create_remappings_file(temp_dir, None) + pytest.fail("Finding remappings failed with SystemExit") + except Exception: # pylint: disable=broad-except + # Reset remappings file and raise error + create_remappings_file(temp_dir, None) + pytest.fail("Finding remappings failed with an Exception") + + # If success, reset remappings file + create_remappings_file(temp_dir, None) + + +def test_remappings_are_detected_when_file_target( + setup_foundry_temp_dir: Any, # pylint: disable=unused-argument +) -> None: + """Test if remappings are fetched when the slither target is a .sol file""" + temp_dir = os.getcwd() + file_path = os.path.join(temp_dir, "src", "BasicTypes.sol") + slither = Slither(file_path) + + # Modify remappings file to contain an incomplete list of remappings + out_str = "forge-std/=lib/forge-std/src/\nsolmate/=lib/solmate/src/\nsrc/=src/" + create_remappings_file(temp_dir, out_str) + + # Look for remappings, expecting not to fail + try: + find_remappings(True, slither) + except SystemExit: + # Reset remappings file and raise error + create_remappings_file(temp_dir, None) + pytest.fail("Finding remappings failed with SystemExit") + except Exception: # pylint: disable=broad-except + # Reset remappings file and raise error + create_remappings_file(temp_dir, None) + pytest.fail("Finding remappings failed with an Exception") + + # If success, reset remappings file + create_remappings_file(temp_dir, None)