From 136bf5cc8ee6519b47da0c87a4937a3ec171cbc4 Mon Sep 17 00:00:00 2001 From: rootminus0x1 Date: Wed, 24 Jun 2026 14:07:32 +0100 Subject: [PATCH] foundry: classify dependencies relative to the project root --- crytic_compile/platform/foundry.py | 10 ++++++++- tests/test_is_dependency.py | 36 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/test_is_dependency.py diff --git a/crytic_compile/platform/foundry.py b/crytic_compile/platform/foundry.py index 05070575..a1713abc 100755 --- a/crytic_compile/platform/foundry.py +++ b/crytic_compile/platform/foundry.py @@ -257,7 +257,15 @@ def is_dependency(self, path: str) -> bool: """ if path in self._cached_dependencies: return self._cached_dependencies[path] - path_parts = Path(path).parts + # Classify the path RELATIVE to the project root, so a file is only a dependency + # because of a `lib`/`node_modules`/configured-libs directory *within* the project — + # not because the project itself is checked out under a parent directory of that name + # (e.g. a git submodule at `/lib/`, where every absolute source path + # would otherwise contain a "lib" component and be wrongly flagged). + try: + path_parts = Path(path).resolve().relative_to(self._project_root.resolve()).parts + except ValueError: + path_parts = Path(path).parts # outside the project tree — classify as-is config = self._get_config() libs_path = (config.libs_path if config else None) or [] ret = ( diff --git a/tests/test_is_dependency.py b/tests/test_is_dependency.py new file mode 100644 index 00000000..c4f6ad4c --- /dev/null +++ b/tests/test_is_dependency.py @@ -0,0 +1,36 @@ +"""Tests for Foundry.is_dependency — dependency classification must be relative to the +project root, not fooled by a parent directory that happens to be named `lib`/`node_modules` +(the normal layout when the project is a git submodule, e.g. ``/lib/``).""" + +from pathlib import Path + +from crytic_compile.platform.abstract_platform import PlatformConfig +from crytic_compile.platform.foundry import Foundry + + +def _make_project(root: Path) -> None: + """Create a minimal Foundry project at ``root`` with one source and one lib dependency.""" + (root / "src").mkdir(parents=True) + (root / "foundry.toml").write_text("[profile.default]\n") + (root / "src" / "Token.sol").write_text("contract Token {}") + (root / "lib" / "solady").mkdir(parents=True) + (root / "lib" / "solady" / "Solady.sol").write_text("contract Solady {}") + + +def test_is_dependency_not_fooled_by_parent_lib_directory(tmp_path: Path) -> None: + """A project checked out under a parent ``lib/`` directory must still treat its own + ``src`` as project code, while its own ``lib/`` contracts remain dependencies.""" + project = tmp_path / "lib" / "myproject" # project nested under a parent "lib/" + _make_project(project) + + foundry = Foundry(str(project)) + foundry._config = PlatformConfig() # avoid spawning `forge config --json` + + src_file = project / "src" / "Token.sol" + dep_file = project / "lib" / "solady" / "Solady.sol" + + # Source under a parent "lib/" is NOT a dependency (the bug flags it because the + # absolute path contains a "lib" component). + assert foundry.is_dependency(str(src_file)) is False + # The project's own lib/ contracts ARE dependencies. + assert foundry.is_dependency(str(dep_file)) is True