Skip to content

Commit efdf8db

Browse files
committed
audit: add CI sync checks for internalFunctionPrefix and special entrypoints
check_selectors.py hardcodes _INTERNAL_FUNCTION_PREFIX ("internal_") and _SPECIAL_ENTRYPOINTS ({"fallback", "receive"}) used in collision and filtering checks. These were validated only by code comments saying "Must match ContractSpec.lean" — no automated enforcement. Add checks #10 and #11 that regex-extract the canonical Lean definitions and validate both Python constants match, following the same pattern as checks #7–9. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 65f6022 commit efdf8db

2 files changed

Lines changed: 73 additions & 1 deletion

File tree

AUDIT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ EDSL uses **wrapping** `mod 2^256` arithmetic. Solidity uses **checked** arithme
137137
30+ scripts enforce consistency between proofs, tests, and documentation. Key checks:
138138

139139
- `check_yul_compiles.py`: All generated Yul compiles with solc; legacy/AST bytecode diff baseline
140-
- `check_selectors.py` / `check_selector_fixtures.py`: Selector cross-validation (both ContractSpec and ASTSpecs; cross-checks signature equivalence; reserved prefix collision check; Error(string) selector constant sync; address mask constant sync; selector shift constant sync)
140+
- `check_selectors.py` / `check_selector_fixtures.py`: Selector cross-validation (both ContractSpec and ASTSpecs; cross-checks signature equivalence; reserved prefix collision check; Error(string) selector constant sync; address mask constant sync; selector shift constant sync; internal prefix sync; special entrypoint names sync)
141141
- `check_doc_counts.py`: Theorem/test counts consistent across all docs
142142
- `check_lean_warning_regression.py`: Zero-warning policy
143143
- `check_axiom_locations.py`: All axioms documented in AXIOMS.md

scripts/check_selectors.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
7) Error(string) selector constant sync between ContractSpec and Interpreter.
1212
8) Address mask constant sync between ContractSpec and Interpreter.
1313
9) Selector shift constant sync between ContractSpec, Codegen, and Builtins.
14+
10) Internal function prefix sync between ContractSpec and CI script.
15+
11) Special entrypoint names sync between ContractSpec and CI script.
1416
"""
1517

1618
from __future__ import annotations
@@ -597,6 +599,70 @@ def check_selector_shift_sync() -> List[str]:
597599
return errors
598600

599601

602+
# ---------------------------------------------------------------------------
603+
# Internal function prefix sync
604+
# ---------------------------------------------------------------------------
605+
606+
_INTERNAL_PREFIX_RE = re.compile(
607+
r'def\s+internalFunctionPrefix\s*:\s*String\s*:=\s*"([^"]*)"'
608+
)
609+
610+
611+
def check_internal_prefix_sync() -> List[str]:
612+
"""Verify _INTERNAL_FUNCTION_PREFIX matches ContractSpec.internalFunctionPrefix."""
613+
errors: List[str] = []
614+
if not CONTRACT_SPEC_FILE.exists():
615+
errors.append(f"Missing {CONTRACT_SPEC_FILE}")
616+
return errors
617+
text = CONTRACT_SPEC_FILE.read_text(encoding="utf-8")
618+
m = _INTERNAL_PREFIX_RE.search(text)
619+
if not m:
620+
errors.append(
621+
"ContractSpec.lean: missing internalFunctionPrefix definition"
622+
)
623+
elif m.group(1) != _INTERNAL_FUNCTION_PREFIX:
624+
errors.append(
625+
f"ContractSpec.internalFunctionPrefix is {m.group(1)!r} "
626+
f"but check_selectors.py uses {_INTERNAL_FUNCTION_PREFIX!r}"
627+
)
628+
return errors
629+
630+
631+
# ---------------------------------------------------------------------------
632+
# Special entrypoint names sync
633+
# ---------------------------------------------------------------------------
634+
635+
_INTEROP_ENTRYPOINTS_RE = re.compile(
636+
r'def\s+isInteropEntrypointName\b.*?:=\s*\[([^\]]*)\]',
637+
re.DOTALL,
638+
)
639+
640+
641+
def check_special_entrypoints_sync() -> List[str]:
642+
"""Verify _SPECIAL_ENTRYPOINTS matches ContractSpec.isInteropEntrypointName."""
643+
errors: List[str] = []
644+
if not CONTRACT_SPEC_FILE.exists():
645+
errors.append(f"Missing {CONTRACT_SPEC_FILE}")
646+
return errors
647+
text = CONTRACT_SPEC_FILE.read_text(encoding="utf-8")
648+
m = _INTEROP_ENTRYPOINTS_RE.search(text)
649+
if not m:
650+
errors.append(
651+
"ContractSpec.lean: missing isInteropEntrypointName definition"
652+
)
653+
return errors
654+
# Parse the Lean list literal: ["fallback", "receive"]
655+
lean_names = set(
656+
n.strip().strip('"') for n in m.group(1).split(",") if n.strip()
657+
)
658+
if lean_names != _SPECIAL_ENTRYPOINTS:
659+
errors.append(
660+
f"ContractSpec.isInteropEntrypointName uses {sorted(lean_names)} "
661+
f"but check_selectors.py uses {sorted(_SPECIAL_ENTRYPOINTS)}"
662+
)
663+
return errors
664+
665+
600666
def main() -> None:
601667
if not SPEC_FILE.exists():
602668
die(f"Missing specs file: {SPEC_FILE}")
@@ -658,6 +724,12 @@ def main() -> None:
658724
# Validate selector shift constant consistency.
659725
errors.extend(check_selector_shift_sync())
660726

727+
# Validate internal function prefix consistency.
728+
errors.extend(check_internal_prefix_sync())
729+
730+
# Validate special entrypoint names consistency.
731+
errors.extend(check_special_entrypoints_sync())
732+
661733
report_errors(errors, "Selector checks failed")
662734
print("Selector checks passed.")
663735

0 commit comments

Comments
 (0)