Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3474056
Only select verified installed Solidity compilers
chokri29 Nov 4, 2026
b999449
Coerce F1 to verified solc and retry Python API once
chokri29 Nov 4, 2026
96d18ee
Correct compiler discovery and resolution escaping
chokri29 Nov 4, 2026
98ed264
Correct one-time compiler mismatch retry escaping
chokri29 Nov 4, 2026
0e922b7
Set production compiler default to Solidity 0.8.38
chokri29 Nov 4, 2026
3ff528b
Align health fallback compiler version with 0.8.38
chokri29 Nov 4, 2026
cb16f1d
Remove duplicate compiler selection from solc patch shim
chokri29 Nov 4, 2026
afcbfb8
Report the effective installed compiler in health status
chokri29 Nov 4, 2026
3800c05
Add regressions for verified solc discovery and F1 pinning
chokri29 Nov 4, 2026
bbd1792
Align compiler fail-closed tests with verified binary discovery
chokri29 Nov 4, 2026
435147e
Correct Docker compiler artifact assertion for 0.8.38
chokri29 Nov 4, 2026
b77efcc
Test one-time Python API retry on solc mismatch
chokri29 Nov 4, 2026
523efd4
Run CI against the production solc 0.8.38 compiler
chokri29 Nov 4, 2026
94903d3
Prevent incompatible PATH compiler fallback and unify version selection
chokri29 Nov 4, 2026
fbdd1eb
Correct pragma matching expression
chokri29 Nov 4, 2026
a4d8460
Make fake solc fixtures executable for binary verification
chokri29 Nov 4, 2026
305b98b
Make fake solc fixtures executable for binary verification
chokri29 Nov 4, 2026
e5bb56b
Mark temporary compiler fixture executable
chokri29 Nov 4, 2026
020dbe1
Mark nested compiler fixture executable
chokri29 Nov 4, 2026
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
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ jobs:
- name: Install solc for real Slither end-to-end tests
run: |
pip install "solc-select>=1.0.4"
solc-select install 0.8.20
solc-select use 0.8.20
solc-select install 0.8.38
solc-select use 0.8.38
solc --version
- name: Run tests
env:
Expand Down
12 changes: 6 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
ENABLE_SLITHER=true \
SOLC_VERSION=0.8.20 \
SOLC_VERSION=0.8.38 \
PORT=8000 \
PATH="/home/appuser/.local/bin:$PATH" \
SOLC_SELECT_INSTALL_DIR=/home/appuser/.solc-select
Expand All @@ -31,17 +31,17 @@ RUN pip install --upgrade pip \

# Install compilers as appuser so artifacts land under SOLC_SELECT_INSTALL_DIR
USER appuser
RUN solc-select install 0.8.0 0.8.4 0.8.7 0.8.10 0.8.13 0.8.16 0.8.17 0.8.19 0.8.20 0.8.24 0.8.26 \
&& solc-select use 0.8.20 \
RUN solc-select install 0.8.4 0.8.7 0.8.10 0.8.13 0.8.16 0.8.17 0.8.19 0.8.20 0.8.24 0.8.26 0.8.38 \
&& solc-select use 0.8.38 \
&& solc --version \
&& which solc \
&& slither --version \
&& echo "=== solc-select artifacts ===" \
&& ls -la /home/appuser/.solc-select/artifacts/ \
&& (find /home/appuser/.solc-select -type f -name 'solc*' | head -40) \
&& test -e /home/appuser/.solc-select/artifacts/solc-0.8.20 \
-o -e /home/appuser/.solc-select/artifacts/solc-0.8.20/solc-0.8.20 \
-o -e /home/appuser/.solc-select/artifacts/solc-0.8.20/solc
&& test -e /home/appuser/.solc-select/artifacts/solc-0.8.38 \
-o -e /home/appuser/.solc-select/artifacts/solc-0.8.38/solc-0.8.38 \
-o -e /home/appuser/.solc-select/artifacts/solc-0.8.38/solc

USER root
COPY main.py slither_runner.py solc_util.py solc_patch.py index.html ./
Expand Down
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def public_entry_points(source_code: str) -> list:
except Exception:
def _solc_status():
return {
"default_version": os.getenv("SOLC_VERSION", "0.8.20"),
"default_version": os.getenv("SOLC_VERSION", "0.8.38"),
"versions": [],
"version_count": 0,
"default_binary_found": False,
Expand Down
204 changes: 92 additions & 112 deletions slither_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

SLITHER_TIMEOUT = int(os.getenv("SLITHER_TIMEOUT_SECONDS", "90"))
ENABLE_SLITHER = os.getenv("ENABLE_SLITHER", "true").strip().lower() in {"1", "true", "yes", "on"}
DEFAULT_SOLC = os.getenv("SOLC_VERSION", "0.8.20").strip() or "0.8.20"
DEFAULT_SOLC = os.getenv("SOLC_VERSION", "0.8.38").strip() or "0.8.38"
KEEP_IMPACTS = {"High", "Medium"}
PRIORITY_CHECKS = {
"reentrancy-eth", "reentrancy-no-eth", "reentrancy-benign", "reentrancy-events",
Expand Down Expand Up @@ -76,26 +76,26 @@ def _installed_solc_versions() -> List[str]:


def _extract_solc_version(source_code: str, *, require_pragma: bool = False) -> Optional[str]:
# Prefer shared util (PATH solc + solc-select artifacts)
try:
ver = _util_extract_solc_version(source_code, require_pragma=require_pragma)
if ver:
return ver
except Exception as exc:
logger.warning("util extract_solc_version failed: %s", exc)
# Last resort: any PATH solc that satisfies the pragma
"""Select from the runner's verified installed-binary inventory."""
match = re.search(r"pragma\s+solidity\s+([^;]+);", source_code)
if require_pragma and not match:
return None
spec = match.group(1).strip() if match else DEFAULT_SOLC
path_ver, path_bin = _util_path_solc_info()
if path_bin and path_ver:
try:
available = sorted(set(_installed_solc_versions()), key=Version)
except Exception as exc:
logger.warning("Could not enumerate installed solc binaries: %s", exc)
return None
if not available:
return None
if match:
try:
if NpmSpec(spec).match(Version(path_ver)):
return path_ver
constraint = NpmSpec(match.group(1).strip())
compatible = [version for version in available if constraint.match(Version(version))]
if compatible:
return compatible[-1]
except ValueError:
pass
return None
logger.warning("Unsupported Solidity pragma: %s", match.group(1))
return available[-1]


_SOLC_LOCK = threading.Lock()
Expand Down Expand Up @@ -220,31 +220,21 @@ def _install_solc(version: str) -> Optional[Path]:

def _resolve_solc_binary(version: Optional[str]):
if not version:
# Fall back to whatever is on PATH
path_ver, path_bin = _util_path_solc_info()
if path_bin:
return path_ver or DEFAULT_SOLC, str(path_bin)
if path_bin and path_ver:
return path_ver, str(path_bin)
return None, None
try:
ver, path = _util_resolve_solc_binary(version)
if path:
return ver, path
resolved_version, resolved_path = _util_resolve_solc_binary(version)
if resolved_path and resolved_version == version:
return version, resolved_path
except Exception as exc:
logger.warning("util resolve_solc_binary failed: %s", exc)
# Installation is allowed only as an explicit attempt to obtain the exact
# requested version. Never relabel a different PATH compiler as this version.
path = _solc_artifact_path(version) or _install_solc(version)
if path:
return version, str(path)
# PATH solc as last resort when version is compatible
path_ver, path_bin = _util_path_solc_info()
if path_bin and path_ver == version:
return version, str(path_bin)
if path_bin and path_ver:
try:
if Version(path_ver).major == Version(version).major:
logger.info("Using PATH solc %s for requested %s", path_ver, version)
return path_ver, str(path_bin)
except Exception:
pass
return version, None


Expand Down Expand Up @@ -363,52 +353,53 @@ def _init_slither(path: Path, kw: dict):
if sl is None and init_err is not None:
err_text = str(init_err)
logger.warning("Slither() failed: %s", err_text)
# e.g. "solc 0.8.0 not installed, only 0.8.38 available"
m = re.search(r"only\s+([0-9]+\.[0-9]+\.[0-9]+)\s+available", err_text, re.I)
if not m:
m = re.search(r"solc\s+([0-9]+\.[0-9]+\.[0-9]+)\s+not installed", err_text, re.I)
# If we only know the missing version, try highest preferred installed
if m:
try:
from solc_util import solc_select_installed_versions, installed_solc_versions as _iv
avail = sorted(set(_iv()) | set(solc_select_installed_versions()), key=Version)
if avail:
# synthesize match for retry path below
class _M:
def group(self, _i):
return avail[-1]
m = _M()
except Exception:
m = None
if m:
alt = m.group(1)
logger.info("Retrying Slither with host solc %s", alt)
try:
subprocess.run(["solc-select", "use", alt], capture_output=True, text=True, timeout=60, check=False)
except Exception:
pass
# Rewrite pragma to exact available version so compile matches
# A host can report either the missing requested compiler or the
# only compiler it has. Retry once, but only with a verified binary.
missing_match = re.search(r"solc\s+([0-9]+\.[0-9]+\.[0-9]+)\s+not installed", err_text, re.I)
available_match = re.search(r"only\s+([0-9]+\.[0-9]+\.[0-9]+)\s+available", err_text, re.I)
try:
available = sorted(set(_util_installed_solc_versions()), key=Version)
except Exception:
available = []
retry_version = None
if available_match and available_match.group(1) in available:
retry_version = available_match.group(1)
elif missing_match and available:
retry_version = available[-1]
if retry_version:
logger.info("Retrying Slither Python API once with installed solc %s", retry_version)
try:
fixed = re.sub(
r"pragma\s+solidity\s+[^;]+;",
f"pragma solidity {alt};",
sol_file.read_text(encoding="utf-8"),
count=1,
subprocess.run(
["solc-select", "use", retry_version],
capture_output=True, text=True, timeout=60, check=False,
)
except Exception as exc:
logger.warning("solc-select use %s failed: %s", retry_version, exc)
resolved_version, retry_bin = _util_resolve_solc_binary(retry_version)
if retry_bin and resolved_version == retry_version:
try:
original = sol_file.read_text(encoding="utf-8")
pinned = f"pragma solidity {retry_version};"
if re.search(r"pragma\s+solidity\s+[^;]+;", original):
fixed = re.sub(r"pragma\s+solidity\s+[^;]+;", pinned, original, count=1)
else:
fixed = pinned + "\n" + original
sol_file.write_text(fixed, encoding="utf-8")
alt_kwargs = dict(kwargs)
alt_kwargs["solc"] = str(retry_bin)
parent = str(Path(retry_bin).parent)
os.environ["PATH"] = parent + os.pathsep + os.environ.get("PATH", "")
os.environ["SOLC_BINARY"] = str(retry_bin)
os.environ["SOLC"] = str(retry_bin)
sl, init_err = _init_slither(sol_file, alt_kwargs)
except Exception as retry_exc:
init_err = retry_exc
else:
init_err = RuntimeError(
f"Compiler {retry_version} was reported but no matching executable binary could be resolved"
)
sol_file.write_text(fixed, encoding="utf-8")
except Exception:
pass
alt_bin = None
try:
from solc_util import resolve_solc_binary as _rs
_, alt_bin = _rs(alt)
except Exception:
alt_bin = solc_bin
alt_kw = dict(kwargs)
if alt_bin:
alt_kw["solc"] = str(alt_bin)
sl, init_err = _init_slither(sol_file, alt_kw)
if sl is None:
error_text = str(init_err or err_text)
return {
"success": False,
"detectors": [],
Expand All @@ -417,8 +408,8 @@ def group(self, _i):
"count": 0,
"filtered_count": 0,
"raw": None,
"error": f"Slither API init failed: {init_err}",
"error_kind": _classify_slither_error(str(init_err)),
"error": f"Slither compile failed: {error_text}",
"error_kind": _classify_slither_error(error_text) or "compile_failed",
}

# Register stock detectors if not already present
Expand Down Expand Up @@ -518,41 +509,27 @@ def run_slither(source_code: str, contract_name: str = "Contract.sol", *, requir
if require_pragma and not pragma_match:
return {"success": False, "detectors": [], "filtered_detectors": [], "detector_checks": [], "count": 0, "filtered_count": 0, "raw": None, "error": "unverified: patch has no Solidity pragma.", "error_kind": "compile_failed"}
rewritten = rewrite_known_oz_imports(source_code)
selected_version = _extract_solc_version(source_code)
# Always coerce to a compiler that is actually installed on this host
# Select only verified installed binaries. Do not trust solc-select's
# inventory unless the corresponding executable can actually be resolved.
try:
from solc_util import solc_select_installed_versions, installed_solc_versions as _iv
available = sorted(set(_iv()) | set(solc_select_installed_versions()), key=Version)
if available:
if not selected_version or selected_version not in available:
pragma_m = re.search(r"pragma\s+solidity\s+([^;]+);", source_code)
if pragma_m:
try:
constraint = NpmSpec(pragma_m.group(1).strip())
matching = [v for v in available if constraint.match(Version(v))]
selected_version = sorted(matching, key=Version)[-1] if matching else available[-1]
except ValueError:
selected_version = available[-1]
else:
# No pragma (legacy 0.4-style): use highest available; compile may still fail
selected_version = available[-1]
logger.info("Coerced solc selection to installed %s (available=%s)", selected_version, available)
else:
# Prefer highest matching available for caret/range pragmas
pragma_m = re.search(r"pragma\s+solidity\s+([^;]+);", source_code)
if pragma_m:
try:
constraint = NpmSpec(pragma_m.group(1).strip())
matching = [v for v in available if constraint.match(Version(v))]
if matching:
best = sorted(matching, key=Version)[-1]
if best != selected_version:
logger.info("Upgrading selected solc %s → %s", selected_version, best)
selected_version = best
except ValueError:
pass
available = sorted(set(_installed_solc_versions()), key=Version)
except Exception as exc:
logger.warning("solc version coercion failed: %s", exc)
logger.warning("Could not enumerate installed solc binaries: %s", exc)
available = []
selected_version = None
if available:
if pragma_match:
try:
constraint = NpmSpec(pragma_match.group(1).strip())
matching = [version for version in available if constraint.match(Version(version))]
selected_version = matching[-1] if matching else available[-1]
except ValueError:
selected_version = available[-1]
else:
# Legacy/no-pragma sources use the highest installed compiler;
# real syntax incompatibility is returned as a compile error.
selected_version = available[-1]
logger.info("F1 selected verified solc %s from installed=%s", selected_version, available)
if not selected_version:
path_ver, path_bin = _util_path_solc_info()
detail = (
Expand Down Expand Up @@ -588,7 +565,10 @@ def run_slither(source_code: str, contract_name: str = "Contract.sol", *, requir
if api_result is not None and api_result.get("success"):
return api_result
if api_result is not None and api_result.get("error"):
logger.warning("Python API path failed: %s — falling back to CLI", api_result.get("error"))
# The Python API is the preferred path on Render. Do not re-enter
# the CLI parser after an API compile error (argparse incompatibility).
logger.warning("Python API path failed; returning its compile result without CLI fallback: %s", api_result.get("error"))
return api_result

try:
proc, run_err = _run_slither_process(sol_file, out_file, solc_bin, tmp_path)
Expand Down
Loading
Loading