Skip to content

Formally prove the correctness of Sqrt.sol and Cbrt.sol#511

Open
duncancmt wants to merge 549 commits intodcmt/cbrt512from
dcmt/codex-prove-sqrt-cbrt
Open

Formally prove the correctness of Sqrt.sol and Cbrt.sol#511
duncancmt wants to merge 549 commits intodcmt/cbrt512from
dcmt/codex-prove-sqrt-cbrt

Conversation

@duncancmt
Copy link
Copy Markdown
Collaborator

No description provided.

@immunefi-magnus
Copy link
Copy Markdown

🛡️ Immunefi PR Reviews

We noticed that your project isn't set up for automatic code reviews. If you'd like this PR reviewed by the Immunefi team, you can request it manually using the link below:

🔗 Send this PR in for review

Once submitted, we'll take care of assigning a reviewer and follow up here.

@duncancmt duncancmt requested a review from e1Ru1o February 27, 2026 22:40
@duncancmt duncancmt self-assigned this Feb 27, 2026
@duncancmt duncancmt force-pushed the dcmt/codex-prove-sqrt-cbrt branch from 66c27b4 to 9e9f388 Compare March 3, 2026 11:53
duncancmt and others added 23 commits March 11, 2026 20:15
… norm reservation

- _inline_single_call now constant-folds the condition of a leave-bearing
  switch/if-else before rejecting it, dropping the dead branch when constant
- _model_name_reserved check is now per-model: skip_norm models only check
  base reserved names, not norm helper names they won't collide with
- Bonus: also fixes allows_constant_switch_with_dead_leave_branch (32 remaining)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When `leave` is inside a bare block, the inner `_parse_assignment_loop`
returns with `inner_leave=True`. The outer loop must skip remaining tokens
in the enclosing function body before breaking, otherwise `parse_function`
fails on unexpected tokens after the bare block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, 3/5)

Add constant-folding awareness to the reference scanning phase:

1. `if 0 { ... }` bodies are skipped (constant-false dead code)
2. `switch N case M { ... }` only records the matching case branch
3. Sibling function bodies exclude locally-shadowed names from the
   external name set, preventing false transitive dependencies

Also adds `_block_has_leave_at_depth1` helper (unused for now but
needed for future leave-awareness work).

NOTE: 2 of 5 WS2 tests remain unfixed (leave-based dead code). The
`_scope_references_any` correctly detects dead references after `leave`,
but `find_function`'s disambiguation logic needs additional changes to
handle the case where NEITHER candidate references the known names.
This requires a design decision about fallback behavior that needs
further analysis.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ha-renaming (WS-K)

Five fixes for helper function scoping during inlining:

1. Scope-aware collect_all_functions: skip non-function brace blocks
   (object/code wrappers) so functions in different object blocks never
   collide. Duplicate names within the same scope now raise ParseError.

2. Scope chain helper collection: walk up enclosing block scopes from
   outermost to innermost, with inner names overriding outer ones.
   Also collect nested helpers from inside the target function body.

3. find_exact_function: add search_nested parameter (used by
   prepare_translation) to find functions inside other function bodies.

4. Alpha-rename callee locals during inlining: when a callee declares
   a local variable whose name appears in the argument expressions,
   generate a fresh name to avoid substitution clobbering.

5. _find_function_body_range helper for extracting the token range
   of a function's body contents.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ng (WS-J/WS-I partial)

Two independent fixes:

1. Set ssa_count[clean]=1 after emitting zero-init assignment for return
   variables, so subsequent assignments get SSA-suffixed names instead
   of overwriting the zero-init binding.

2. Extend constant-folding in _inline_single_call to handle:
   - `if 0 { leave }` (no else_body): skip the dead block entirely
   - `if 1 { ... leave }` (no else_body): process then-body and
     return immediately, skipping dead code after the leave site
   - Non-leave constant if/switch: eliminate dead branches before
     processing bodies, preventing spurious "conditional memory write"
     errors on dead code paths

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…y writes (WS-I)

When `if` or `switch` conditions are constant at parse time:

- Constant-false `if`: parse the body (tolerating memory writes) but
  discard it entirely as dead code
- Constant-true `if`: flatten the body into the outer scope as
  straight-line code
- Constant-discriminant `switch`: parse all branches, flatten only the
  matching branch, discard dead branches

This prevents spurious "conditional memory write" errors when mstore
appears in provably-dead branches, and correctly folds constant-true
conditionals so their assignments become straight-line.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, switch leave

1. find_exact_function: prefer top-level match before searching nested
   scopes, fixing regression where an unrelated nested homonym caused
   ambiguity (introduced by 9ffbc2b)

2. collect_all_functions: check both `functions` and `rejected` dicts
   for duplicate names, so a rejected-then-valid or valid-then-rejected
   duplicate pair is caught (pre-existing bug)

3. Nested rejected helpers shadow valid outer helpers: remove the outer
   name from helper_table when an inner scope rejects the same name,
   preventing silent fallback to the wrong definition (pre-existing bug)

4. Constant-false leave-bearing switch inlining: process the else-body
   (case-0 branch) as straight-line code instead of rewriting into a
   new ParsedIfBlock whose condition semantics are inverted, which
   caused the non-leave constant-fold path to skip the live branch
   entirely (pre-existing bug, exposed by commit 0bca759's incomplete
   constant-fold)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ambiguation

Two code-quality improvements, zero behavioral changes:

1. _TokenReader base class: _ReferenceScopeParser and YulParser had 7
   byte-for-byte identical methods (_at_end, _peek, _peek_kind, _pop,
   _expect, _expect_ident, _parse_expr). Extract them into a shared
   base class so a future expression-syntax change only needs one edit.

2. _disambiguate_by_references: extract the dense 3-level conditional
   block from find_function into a named helper with a docstring that
   explains the priority ordering for leaf selection (exclude_known=True),
   wrapper selection (exclude_known=False), and the partial-parse
   fallback. Add inline comments on each branch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…olding

Duplicated decision logic for constant-folding if/switch conditions existed
independently in _inline_single_call (Site B) and yul_function_to_model
(Site C). Extract the shared classification into a single _IfFoldDecision
enum + _classify_if_fold function, and refactor both sites to use it.

Also adds the previously-missing constant-true handling in
yul_function_to_model's non-leave path (Site C), which now flattens
then-body as straight-line assignments instead of emitting a
ConditionalBlock with a constant-true condition.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The THEN_LIVE case in Site C's non-leave path was flattening body
assignments into the outer scope using the outer var_map/subst, which
let block-local variables escape their scope. This caused three bugs:

1. Block-local variables (e.g. `let usr$tmp` inside `if 1 { ... }`)
   leaked into the outer scope, silently producing wrong models
2. Block-local pointer variables used by subsequent mstore went
   undetected instead of raising "non-constant address"
3. Block-local binders with names matching generated model functions
   could cause naming collisions in Lean emission

Fix by processing the body in branch-local copies of var_map/subst/
const_locals (same pattern as _process_conditional_branch), then
emitting only assignments to variables that existed in the outer scope.

Also add an out-of-scope variable check in _process_assignment_into:
after substitution and renaming, any Var not in emitted_ssa_names is
a reference to a variable from a closed scope.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the branch-copy-plus-filter approach with _lower_live_branch,
a dedicated helper that models two scopes during lowering:

  - Outer mutable state (var_map, SSA counters, emitted names, constant
    facts): outer writes go through the normal SSA machinery immediately,
    so reassigned variables get fresh binders (x_2, not a dropped x).
  - Block-local overlay: names introduced by `let` inside the branch
    are parked in var_map/subst for visibility to later statements in
    the same block, then cleaned up at block exit.

Scope survival is decided by source-level binding identity: a target
already in var_map or subst is an outer write; an unknown target is a
block-local declaration.

Fixes three bugs from the prior approach:
  1. Outer writes to SSA-renamed variables were dropped because the
     branch-local base name didn't match the outer SSA name.
  2. Constant facts from outer writes inside the branch were lost,
     breaking downstream mstore address resolution.
  3. ELSE_LIVE in the leave path used the same unsafe straight-line
     pattern; now shares _lower_live_branch with THEN_LIVE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The fundamental problem: _lower_live_branch inferred declaration-vs-
assignment from name visibility (s.target in outer_known), which breaks
when a `let` inside a taken branch shadows an outer binding with the
same Yul name.

Add `is_declaration: bool` to PlainAssignment and set it in the parser's
_parse_let method. Propagate through alpha-rename, inline_calls body
processing, and block substitution.

_lower_live_branch is now driven by is_declaration:
  - Declaration (True): always block-local. The processed expression
    goes into subst for visibility to later statements in the block,
    then is removed at block exit. Shadowed outer subst entries are
    saved and restored.
  - Reassignment (False): writes the outer binding via normal SSA.

Parser constant-fold flattening (Site A) also gains block scoping via
_flatten_scoped_block: when flattening constant-true if or constant-
switch live branches, declarations are substituted away and only
reassignments survive to the outer scope.

Fixes:
  - `let x` inside constant-true block no longer overwrites outer x
  - Block-local real vars available to later outer writes in same block
  - Constant-switch with shadowing let now respects block scope
  - switch-with-leave shadowing test now passes (bonus)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
is_declaration marks when a name enters the local scope, but the
binding's lifetime extends to block exit. A later `x := 2` after
`let x := 1` in the same block must update the local binding, not
escape to the outer scope.

Both _flatten_scoped_block (parser constant-fold) and _lower_live_branch
(model lowerer) now maintain a block_locals set that accumulates names
from declarations. Subsequent reassignments to names in that set update
the local binding instead of emitting to outer scope / outer SSA.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@duncancmt duncancmt changed the base branch from dcmt/cbrt512 to master April 11, 2026 21:19
@duncancmt duncancmt changed the base branch from master to dcmt/cbrt512 April 11, 2026 21:19
duncancmt and others added 26 commits April 11, 2026 23:20
Add `map_stmt` as the canonical structural NStmt mapper in norm_walk.py,
replacing three near-identical hand-rolled dispatch chains (_freshen_stmt,
_subst_stmt, translator.rw_stmt). Add `for_each_model_expr` and
`map_model_expr` to model_helpers.py, replacing four duplicated Expr
traversals.

Move dead-branch elimination out of norm_constprop into norm_simplify as
the single canonical implementation. Unify the two uniquification
strategies in restricted_names.py to a consistent `_{n}` suffix pattern.

Remove dead code: unused imports (for_each_expr, validate_ident,
Protocol), unused defaults (FunctionModel param/return names),
unreachable branches, no-op exception handlers, redundant double
validation, unnecessary variable aliases, and the SymbolIdAllocator
Protocol. Privatize functions that had no external callers.

Add assert_never to non-exhaustive dispatch chains in restricted_eval.py
and selection.py. Fix SymbolTable.declare leaked loop variable and remove
unused span parameter from SymbolTable.lookup. Fix misleading docstrings
in model_ir.py, restricted_ir.py, and norm_inline.py.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ions

Replace the monolithic ParseError with a TranslationError base class and
phase-specific subclasses (ParseError, ResolutionError, SelectionError,
LoweringError, ValidationError, EmissionError) so integrating code can
catch errors by pipeline phase.

Dissolve norm_optimize_shared.py: move const_value, const_truthy, and
simplify_ite into norm_walk.py; inline sequential_block into its only
consumer (norm_constprop.py).

Replace the custom FrozenMap class with types.MappingProxyType. Remove
the duplicate _try_const helper from norm_inline (use const_value from
norm_walk). Cache _SyntaxFunctionInfo.key/top_level_key as fields
instead of recomputing on every access. Short-circuit expr_contains to
return on first match. Rename _is_known_effect_call to
_is_recognized_expr_stmt for accuracy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add shared walker modules for restricted IR (restricted_walk.py) and
syntax AST (yul_walk.py), then refactor consumers to use them.
Simplify max_symbol_id via for_each_stmt_expr, replace direct _FactEnv
field mutation with update_from, and add EVM-correct div/mod identity
folds (div-by-zero yields 0, mod(x,x) yields 0). Remove unused simp
arguments in Sqrt512Proof to fix Lean linter warnings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract generic map_expr, for_each_expr, and expr_contains into
expr_walk.py using structural field dispatch (hasattr + dataclasses.replace).
All three IR walker modules re-export from the shared implementation.

Remove mload from recognized expression-statement patterns in the
function classifier so bare mload(x) statements are flagged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant