Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion crytic_compile/platform/foundry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<repo>/lib/<project>`, 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 = (
Expand Down
36 changes: 36 additions & 0 deletions tests/test_is_dependency.py
Original file line number Diff line number Diff line change
@@ -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. ``<repo>/lib/<project>``)."""

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