Skip to content

Accept struct-parameter projection from ABI-dynamic root (#1832)#1843

Merged
Th0rgal merged 1 commit into
mainfrom
feat/1832-paramDynamicHeadWord
May 12, 2026
Merged

Accept struct-parameter projection from ABI-dynamic root (#1832)#1843
Th0rgal merged 1 commit into
mainfrom
feat/1832-paramDynamicHeadWord

Conversation

@Th0rgal
Copy link
Copy Markdown
Member

@Th0rgal Th0rgal commented May 12, 2026

Summary

Adds Expr.paramDynamicHeadWord (name : String) (wordOffset : Nat) and routes direct dynamic-tuple parameter leaf projections through it. Closes the single remaining genuinely-open P0 item from umbrella #1760 (see audit comment for why the other two P0 — no existing issue checkboxes were already shipped).

Fixes #1832. Depends on #1841 (param-loader fix) and #1842 (wildcard refactor).

What now works

struct DynamicConfig where
  notes : Array Uint256,   -- makes DynamicConfig ABI-dynamic
  maker : Address

function read (config : DynamicConfig) : Address := do
  return config.maker     -- now lowers to Expr.paramDynamicHeadWord

Before this PR the macro rejected the above with the diagnostic "struct parameter projection from an ABI-dynamic root is not supported; use a static struct parameter or wait for nested-dynamic ABI decoding" (Verity/Macro/Translate.lean:1715).

Implementation

  • New Expr constructor Expr.paramDynamicHeadWord (name : String) (wordOffset : Nat) (Compiler/CompilationModel/Types.lean).
  • New Yul helpers __verity_param_dynamic_head_word_{calldata,memory}_checked reading data_offset + word_offset * 32 with a calldatasize - 32 upper bound check (Compiler/CompilationModel/DynamicData.lean).
  • Helper emission gated on a new contractUsesParamDynamicHeadWord detector (UsageAnalysis.lean), threaded through Dispatch.compileValidatedCore and ValidationCalls.reservedExternalNames.
  • New macro resolver paramDynamicHeadProjection? shadow-mirrors paramStructProjectionResolved? but uses structFieldHeadOffset? (the same helper the array case already uses via arrayElementStructProjectionResolved?) to compute the head-word offset. Routing in translatePureExprWithTypes and inferPureExprType checks this resolver first when the param is ABI-dynamic.
  • Rejection helpers isParamStructDynamicRootProjection and throwStructDynamicRootProjectionError removed; the non-leaf rejection stays.
  • paramDynamicHeadWord case added to every existing Expr traversal: LogicalPurity (pure leaves), UsageAnalysis (4 sites), Validation (3 sites + exprWritesState/exprContainsExternalCall/exprMayContainExternalCall wildcard expansion), ValidationHelpers, ScopeValidation (substantive tuple-shape + wordOffset bounds check), ValidationInterop, ValidationCalls, ExpressionCompile (lowering).

Smoke + invariant + property-test coverage

  • Contracts.Smoke.NamedStructDynamicRootLeafProjection replaces the previous rejection #guard_msgs test with an accepted contract.
  • Listed in MacroTranslateInvariantTest (spec list, function signature index, selector index) and MacroTranslateRoundTripFuzz (spec list).
  • Auto-generated Foundry property stub at artifacts/macro_property_tests/PropertyNamedStructDynamicRootLeafProjection.t.sol.

Test plan

  • lake build succeeds (all 105 targets).
  • make check passes locally (macro health, IR/Yul identity diff, selector audit, property-test artifacts).
  • The previously-rejected shape now compiles end-to-end with the new helper emitted.

Scope

No new axiom or trust surface — the helper is local bytes-reading with a calldatasize - 32 bound check. AUDIT.md / AXIOMS.md / TRUST_ASSUMPTIONS.md unaffected.

docs/INTERPRETER_FEATURE_MATRIX.md deserves a parallel "Param dynamic head word" row alongside the existing "Dynamic struct-array head word"; doc update is queued for a follow-up.

Downstream

verity-benchmark cases/unlink_xyz/pool/Contract.lean carries BLOCKED(verity#1832) markers on the three public ZK entry points (transfer / withdraw / emergencyWithdraw — bodies at UnlinkPool.sol:309-583). Once this PR (and its two prerequisites) reaches the lakefile pin, those markers come down and the entrypoints can be filled in. Foundry differential test against solc-emitted ABI for the full Transaction struct is a follow-up; the current property-test artifact is the baseline that make check enforces.

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk: adds a new expression form and Yul helper used in ABI decoding paths; mistakes in head-offset calculation or bounds checks could cause incorrect reads or unexpected reverts for dynamic tuple parameters.

Overview
Adds support for projecting a single-word static leaf field from a struct/tuple parameter whose ABI encoding is dynamic (nested dynamic members), by introducing Expr.paramDynamicHeadWord and routing param.field macro translations through it.

Implements new checked Yul helpers (__verity_param_dynamic_head_word_{calldata,memory}_checked) and emits them only when used via a new contractUsesParamDynamicHeadWord analysis, with corresponding reserved-name handling and compilation/validation traversal updates.

Updates smoke/fuzz/invariant coverage by replacing the prior rejection test with an accepted NamedStructDynamicRootLeafProjection contract and adding the generated Foundry property-test stub.

Reviewed by Cursor Bugbot for commit 22a8848. Bugbot is set up for automated code reviews on this repo. Configure here.

Introduces `Expr.paramDynamicHeadWord (name : String) (wordOffset : Nat)`
and routes direct dynamic-tuple parameter leaf projections through it.
This is the single remaining genuinely-open P0 item from umbrella #1760
(see #1760 (comment)
for the audit that confirmed `SELFBALANCE` and the ETH-aware generic
call ECM had already shipped).

## What now works

`param.field` projections where `param` is a struct parameter that
carries nested dynamic members (ABI-dynamic root) and the projected
field is a single-word static value at a fixed head offset:

```lean
struct DynamicConfig where
  notes : Array Uint256,   -- makes DynamicConfig ABI-dynamic
  maker : Address

function read (config : DynamicConfig) : Address := do
  return config.maker     -- now lowers to Expr.paramDynamicHeadWord
```

Before this commit, the macro rejected the above with the diagnostic
"struct parameter projection from an ABI-dynamic root is not
supported; use a static struct parameter or wait for nested-dynamic
ABI decoding" at `Verity/Macro/Translate.lean:1715`.

## Lowering

`Expr.paramDynamicHeadWord name wordOffset` lowers to a new pair of
Yul helpers (`__verity_param_dynamic_head_word_{calldata,memory}_checked`)
that read `data_offset + word_offset * 32` with a `calldatasize - 32`
upper bound check. The `{name}_data_offset` Yul identifier is now
positioned correctly for dynamic tuples thanks to verity#1839 — the
param loader no longer emits a spurious length word for them.

## Macro routing

A new resolver `paramDynamicHeadProjection?` shadow-mirrors
`paramStructProjectionResolved?` but uses `structFieldHeadOffset?`
(the same helper the array case already uses via
`arrayElementStructProjectionResolved?`) to compute the head-word
offset. The routing in `translatePureExprWithTypes` and
`inferPureExprType` checks this resolver first when the param is
ABI-dynamic, falling through to the static-root path when not.

The rejection helpers `isParamStructDynamicRootProjection` and
`throwStructDynamicRootProjectionError` are removed; the non-leaf
rejection (`throwStructNonLeafProjectionError`) stays.

## Smoke + invariant + property-test coverage

- `Contracts.Smoke.NamedStructDynamicRootLeafProjection` (replaces
  the rejection #guard_msgs at `Contracts/Smoke.lean:1056-1068` with
  an accepted contract).
- Threaded through `MacroTranslateInvariantTest` (spec list, function
  signatures, selectors) and `MacroTranslateRoundTripFuzz` (spec list).
- Auto-generated Foundry property stub at
  `artifacts/macro_property_tests/PropertyNamedStructDynamicRootLeafProjection.t.sol`.

## Prerequisites

This PR depends on two prerequisite changes that should land first:

- #1841 (verity#1839): fixes `genDynamicParamLoads` so
  `{name}_data_offset` points at the first head word of a dynamic
  tuple (no spurious length prefix). Without this the helper would
  read 32 bytes past the actual data.
- #1842 (validator-wildcard refactor): expands the `| _ => pure ()`
  wildcards in three `Expr → Except String Unit` validators
  (`validateInteropExpr`, `validateInternalCallShapesInExpr`,
  `validateExternalCallTargetsInExpr`) into explicit constructor
  lists. Without this the new `paramDynamicHeadWord` constructor
  trips the Lean 4 `_mutual.eq_def` 200 000-heartbeat ceiling.

The diff in this commit includes commits from both prerequisites
(cherry-picked / re-applied). If the PR is rebased after the
prerequisites merge, those commits will fold away.

## Verification

- `lake build` succeeds (all 105 targets).
- `make check` passes locally (macro health, IR/Yul identity diff,
  selector audit, property-test artifacts, invariant/round-trip
  coverage).

## Downstream

verity-benchmark `cases/unlink_xyz/pool/Contract.lean` carries
`BLOCKED(verity#1832)` markers on the three public ZK entrypoints
(`transfer` / `withdraw` / `emergencyWithdraw` — bodies at
`UnlinkPool.sol:309-583`). Once this lands, those markers come down
and the entrypoints can be inlined directly. Foundry differential
test against the `solc`-emitted ABI for the full `Transaction` struct
is a follow-up; the current property-test artifact is the baseline
coverage that `make check` enforces.

## Scope

Adds one `Expr` constructor and one Yul helper pair. No semantic,
trust, or CI boundary change beyond the new helper:
- `AUDIT.md` / `AXIOMS.md` / `TRUST_ASSUMPTIONS.md` unaffected — the
  helper is local bytes-reading with a `calldatasize` bound check,
  no new axiom or trust surface.
- `docs/INTERPRETER_FEATURE_MATRIX.md` row for "Dynamic struct head
  word" gains a parallel "Param dynamic head word" entry — left for
  a follow-up doc PR.

Fixes #1832. Depends on #1841 and #1842.
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit caa4600. Configure here.

Comment thread Compiler/CompilationModel/ValidationCalls.lean
@Th0rgal Th0rgal force-pushed the feat/1832-paramDynamicHeadWord branch from caa4600 to 22a8848 Compare May 12, 2026 18:27
@Th0rgal Th0rgal merged commit cf5cb84 into main May 12, 2026
5 of 6 checks passed
@Th0rgal Th0rgal deleted the feat/1832-paramDynamicHeadWord branch May 12, 2026 18:43
Th0rgal added a commit that referenced this pull request May 12, 2026
… docs (#1844)

Cursor Bugbot caught an asymmetry on #1843:
`paramDynamicHeadWordHelpers` was unconditionally included in
`reservedExternalNames`, unlike every other helper group
(`mappingHelpers`, `arrayHelpers`, `arrayElementWordHelpers`,
`storageArrayHelpers`, `dynamicBytesEqHelpers`) which are all gated on a
matching `*Required` boolean. The collision detector was overly
conservative as a result — it would flag an external named
`__verity_param_dynamic_head_word_calldata_checked` even on a contract
that never used a dynamic-tuple parameter projection. Meanwhile
`Dispatch.compileValidatedCore` already gated the emission of those
helpers on `contractUsesParamDynamicHeadWord`.

This commit adds the missing `paramDynamicHeadWordHelpersRequired`
parameter to `reservedExternalNames` and `firstReservedExternalCollision`
and threads it through the single caller in `Dispatch`, restoring the
established pattern.

It also updates the documentation surface to reflect that #1832 has
shipped:

- `docs/ROADMAP.md`: the Unlink translation-tracking note no longer
  says the macro rejects ABI-dynamic-root struct projection. The
  already-supported items list grows two entries:
    * direct dynamic-struct parameter projection
      (`Expr.paramDynamicHeadWord`, #1832/#1843)
    * mapping-keyed `requires(<role>)` (#1837/#1840).
- `docs/INTERPRETER_FEATURE_MATRIX.md`: new "Direct dynamic-struct
  param head word" row mirroring the existing
  `arrayElementDynamicWord` row, plus the proof-status summary and
  the nested-dynamic-ABI-decoding limitations section now mention
  `paramDynamicHeadWord` alongside `arrayElementDynamicWord`.
- `artifacts/interpreter_feature_matrix.json`: matching machine-readable
  entry so the macro-health gate's manifest stays in sync.

Verified locally:
- `lake build` succeeds.
- `make check` passes (macro health, IR/Yul identity diff, selector
  audit, property-test artifact parity).
- No semantic or trust boundary change — `AUDIT.md` / `AXIOMS.md` /
  `TRUST_ASSUMPTIONS.md` unaffected. The gating change makes the
  collision detector behavior pure-extensionally identical on every
  contract that uses `paramDynamicHeadWord` (the helper names are
  still reserved when needed), and strictly less conservative on
  contracts that do not.

Addresses the Cursor Bugbot finding at
#1843 (comment)... (the
"low severity" asymmetry comment posted at merge time).
@github-actions
Copy link
Copy Markdown
Contributor

\n### CI Failure Hints\n\nFailed jobs: `build`\n\nCopy-paste local triage:\n```bash\nmake check\nlake build\nFOUNDRY_PROFILE=difftest forge test -vv\n```

Th0rgal added a commit that referenced this pull request May 12, 2026
…rtedSpec

Fixes a 2nd round of upstream main bugs that PR #1826's CI surfaces
(commits cf5cb84 + 6b2af37 added 3 new Expr constructors —
paramDynamicHeadWord, mulDiv512Down, mulDiv512Up — without updating
SupportedSpec.lean's exhaustive case-matches).

Three structural fixes:
- Add `.paramDynamicHeadWord _ _` case to 9 helper-surface functions
  (exprTouchesUnsupportedConstructorRawCalldataSurface,
   exprTouchesUnsupportedCoreSurface, exprTouchesUnsupportedStateSurface,
   exprTouchesUnsupportedCallSurface, exprTouchesUnsupportedHelperSurface,
   exprTouchesInternalHelperSurface, exprTouchesUnsupportedForeignSurface,
   exprTouchesUnsupportedLowLevelSurface, exprTouchesUnsupportedContractSurface).
  Conservative semantics: paramDynamicHeadWord returns true (touches
  unsupported surface) for the constructor-context, core, state, and
  contract surface checks, since it reads calldata; returns false (does
  not touch unsupported) for call/helper/foreign/lowLevel surfaces.

- Expand wildcard `| _ => []` in exprInternalHelperCallNames to explicit
  enumeration of all leaf Expr constructors plus the `adtConstruct`
  recursive case, matching the pattern of upstream commit 34063ed
  (#1842) which fixed the same eq_def heartbeat ceiling issue in
  ValidationCalls.lean and ValidationInterop.lean. Without this, Lean
  4.22.0's eq_def deriver hits the 200K-heartbeat ceiling because each
  new Expr constructor enlarges the wildcard's complement to enumerate.

- Add file-level `set_option maxHeartbeats 4000000` (cosmetic; doesn't
  propagate to eq_def deriver but provides headroom for normal proofs).

Local validation: build now progresses past the structural bugs to
downstream cascading "Alternative not provided" errors at lines 3133,
3523, 3722, 4092 (and several "unsolved goals" at 4352+) — these are
proofs that case-split on Expr and need the new constructors as
explicit cases. Those fixes are mechanical but tedious (~12+ case
additions across many proofs); deferring to the upstream maintainer
or a follow-up PR.

This commit is intended to be cherry-picked into upstream main
alongside #1843/#1846 to keep main's CI green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Th0rgal added a commit that referenced this pull request May 12, 2026
…xpr proofs

Continues the cascade of fixes for upstream main commits cf5cb84 +
6b2af37 (which added paramDynamicHeadWord, mulDiv512Down, mulDiv512Up
without updating dependent code). Each `cases expr` proof in
SupportedSpec.lean that enumerates Expr constructors needs explicit
arms for the 3 new constructors.

Sites updated:
- exprTouchesInternalHelperSurface_eq_false_of_helperSurfaceClosed (3201)
  → mulDiv512Down/Up share the mulDivDown/Up arm; paramDynamicHeadWord
    is its own arm using `simp [exprTouchesInternalHelperSurface]`
- exprTouchesUnsupportedCallSurface_eq_featureOr (3605)
  → mulDiv512 share with mulDivDown/Up; paramDynamicHeadWord uses simp
- exprTouchesUnsupportedContractSurface_eq_false_of_featureClosed (3796)
  → mulDiv512 share; paramDynamicHeadWord falls into hcore-impossible
    (since exprTouchesUnsupportedCoreSurface paramDynamicHeadWord = true)
- exprTouchesUnsupportedCallSurface_eq_false_of_coreClosed (3878)
  → mulDiv512 share; paramDynamicHeadWord uses hcore-impossible
- exprTouchesUnsupportedHelperSurface_eq_false_of_contractSurfaceClosed
  (4146) → mulDiv512 share; paramDynamicHeadWord uses hsurface-impossible
- exprUsesArrayElement_eq_false_of_coreClosed (4351)
  → mulDiv512 share; paramDynamicHeadWord uses hcore-impossible
- exprUsesStorageArrayElement_eq_false_of_coreClosed (4401)
  → same pattern
- exprUsesDynamicBytesEq_eq_false_of_coreClosed (4453)
  → same pattern

Local validation: `lake build Compiler.Proofs.IRGeneration.SupportedSpec`
now passes. Subsequent build chain reveals the same upstream-bug
pattern in `Compiler/Proofs/IRGeneration/SourceSemantics.lean`
(evalExprWithHelpers/evalExprListWithHelpers/execStmtWithHelpers/
interpretInternalFunctionFuel mutual block, ~25 wildcards) — that's
the next file to patch, but is a separate, larger commit.

This is upstream main's work. Documented as a fix-candidate ready to
cherry-pick into upstream alongside #1843/#1846.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Th0rgal added a commit that referenced this pull request May 13, 2026
…n bump (#1850)

* Add missing exhaustiveness cases for new Expr constructors in audit step

The audit step (`lake build PrintAxioms`) has been failing on main since
the codegen-only #1832 (`paramDynamicHeadWord`) and #1761 (`mulDiv512Down`,
`mulDiv512Up`) constructors landed without updating the proof-framework
exhaustiveness sites. This PR fixes the structural gaps in three files:

- `Compiler/Proofs/IRGeneration/SupportedSpec.lean`:
  * Add `paramDynamicHeadWord` and `mulDiv512Down/Up` to the
    unsupported-by-core / unsupported-by-contract surface buckets in the
    9 `exprTouches*Surface` defs (mulDiv512 / paramDynamicHeadWord lower
    to runtime Yul helpers that the current core proof framework does
    not model yet).
  * Split the proof tactics that destructure `hcore` over `mulDivDown/Up`
    so the new constructors derive `False` from
    `exprTouchesUnsupportedCoreSurface = false` (since they sit in the
    unsupported group).
  * Expand the `| _ => []` wildcard at the bottom of
    `exprInternalHelperCallNames` to an explicit constructor list. This
    is the verity#1842 mitigation for the `_mutual.eq_def` 200 000-
    heartbeat ceiling — adding any new `Expr` constructor cascades into
    eq_def derivation timeouts when the function ends with a wildcard.
  * Add `paramDynamicHeadWord _ _` to the explicit leaves group in the
    other surface defs / proof tactics that already enumerate leaves.

- `Compiler/Proofs/IRGeneration/SourceSemantics.lean`:
  * Add `evalExpr_mulDiv512Down`, `evalExpr_mulDiv512Up`,
    `evalExpr_paramDynamicHeadWord` simp lemmas (each `:= rfl`).
  * Expand the `| _ => none` wildcard in `evalExprWithHelpers` to an
    explicit leaf list for the same `_mutual.eq_def` heartbeat reason.
  * Add the corresponding `evalExprWithHelpers_eq_evalExpr_of_helperSurfaceClosed`
    arms for `mulDiv512Down/Up` and `paramDynamicHeadWord` (both
    evaluate to `none` on either side, closed via the new simp lemmas).

- `Compiler/Proofs/IRGeneration/GenericInduction.lean`:
  * Add `mulDiv512Down/Up` and `paramDynamicHeadWord` arms to
    `exprCompileCore_of_exprTouchesUnsupportedContractSurface_eq_false`,
    discharged vacuously by reducing `hsurface` against the
    unsupported-contract-surface predicate.

`lake build PrintAxioms` now reaches the final downstream proof error
(`ContractShape.lean:178 rfl failed`, where the IR shape proof needs new
`SupportedSpec.contractUsesMulDiv512_eq_false` /
`contractUsesParamDynamicHeadWord_eq_false` hypotheses to discharge the
conditional helper emission added in #1832 / #1761). That last error is
a pre-existing failure on main from the same two codegen PRs and is
left for a follow-up PR that adds the corresponding `SupportedSpec`
support theorems.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: auto-refresh derived artifacts

* Close ContractShape:178 rfl via contractUsesMulDiv512/ParamDynamicHeadWord_eq_false

Phase 2 of the audit-step fix. The previous commit (d78b35c) restored
exhaustiveness in the proof-framework surface defs and reduced the audit
step from ~10 errors down to a single ContractShape.lean:178 'rfl' failure.

That failure was: compileValidatedCore_ok_yields_internalFunctions_nil
could no longer reduce internalFunctions to [] because compileValidatedCore
emits two new conditional helper lists guarded by contractUsesMulDiv512 and
contractUsesParamDynamicHeadWord (#1761 / #1832). Those flags need to be
provably false on every SupportedSpec.

Two blockers stood in the way:

1) exprUsesMulDiv512 / exprUsesParamDynamicHeadWord and their stmt mirrors
   were 'partial def's, so simp/unfold/decide/rfl could not reduce concrete
   leaves. Converted all four to plain 'def's with explicit termination_by /
   decreasing_by, mirroring the exprUsesArrayElement template.

2) No SupportedSpec.contractUsesXxx_eq_false existed for the two new flags.
   Added the full proof chain for both: exprCompileCore_usesXxx_false (by
   induction on FunctionBody.ExprCompileCore), exprListCompileCore_*,
   stmtListCompileCore_* / stmtListTerminalCore_* / stmtListUsesXxx_append,
   supportedStmtList_usesXxx_false (by induction on SupportedStmtList,
   case-by-case mirror of supportedStmtList_usesDynamicBytesEq_false),
   stmtListUsesXxx_eq_any, and the SupportedSpec[ExceptMappingWrites]
   contractUsesXxx_eq_false theorems.

Wired the new hypotheses into compileValidatedCore_ok_yields_internalFunctions_nil
in ContractShape.lean, and into compile_ok_yields_internalFunctions_nil[_except_mapping_writes]
in Contract.lean (the SupportedSpec and SupportedSpecExceptMappingWrites variants).

PrintAxioms.lean regenerated. scripts/check_proof_length.py allowlist updated
for supportedStmtList_usesMulDiv512_false (107 lines) and
supportedStmtList_usesParamDynamicHeadWord_false (105 lines), both direct
mirrors of supportedStmtList_usesDynamicBytesEq_false.

lake build PrintAxioms + make check both green locally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Fix indentation on mulDiv512Down/Up case arm

Cursor Bugbot flagged that the continuation `| mulDiv512Down a b c |
mulDiv512Up a b c =>` at SupportedSpec.lean:3218 was indented at column
2 while the leading `| mulDivDown a b c | mulDivUp a b c` line was at
column 4. Other multi-line case arms in the same theorem use 4-space
indent. Re-align to match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(roadmap): record verity#1849 macro gaps + verity-benchmark#44 pin bump

The Unlink Audit Readiness "Translation tracking" subsection previously
implied that promotion of the verity-benchmark unlink_xyz/pool case to
build_green only required bumping the lakefile pin past #1832 / #1843
and filling in the entry-point bodies from UnlinkPool.sol:309-583.

The pin bump shipped as verity-benchmark#44, but an empirical pilot
against verity-benchmark@1e9b631 (pilot/transfer-body-1832, 2026-05-13)
confirmed that the bodies still cannot be translated line-by-line.
Three further macro-surface lifts are needed beyond the single-word
static leaf projections delivered by #1832 / #1843, now tracked under
verity#1849:

  G1. arrayLength on a struct-element dynamic member.
  G2. Element indexing on a struct-element dynamic member.
  G3. Pass-through of dynamic-array values to tryExternalCall / emit /
      revertError argument lists.

Combined with verity#1824 (internal helpers with Array parameters), the
G1-G3 set is the remaining Verity-core gate on the body translation.

Update the "Translation tracking" subsection to reflect both the
shipped pin bump and the still-outstanding macro-side gates, so the
next contributor reading the roadmap does not repeat the pilot.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Th0rgal added a commit that referenced this pull request May 14, 2026
…cade fix (#1826)

* Scaffold G1 S5–S8 follow-up plan

Adds docs/NATIVE_EVMYULLEAN_G1_FOLLOWUP_PLAN.md as the planning document
for the follow-up PR after #1822. The doc decomposes the remaining G1
work (S5–S8 in NATIVE_EVMYULLEAN_TRANSITION.md) into a DAG of seven
node layers (A IR-side lemmas, B native harness lemmas, D Revived
constructors, E/F SelectorHitSuccessBridge chains and label-prefix
variants, G `hUserBodyHalt` premise drop), and proposes a four-wave
parallel orchestration strategy across eight worker tasks. Each node
has a clean per-file definition of done and a universal "zero sorry,
zero new axiom, build green" acceptance gate.

The doc also enumerates four alternative approaches (predicate
refactor, direct semantic equivalence, tactic-driven wiring,
dispatcher-shape normalization) with pros/cons, recommending the
straight-line plan as the safest path and revisiting ALT-1/ALT-3 as a
post-S8 cleanup.

No code changes; doc-only scaffolding for the next PR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add implementation research addendum to G1 S5-S8 plan

Captures concrete code-level facts gathered while reviewing the post-#1822
tree for the S5-S8 follow-up:

- Verified line numbers for every shipped Revived leaf constructor and
  the one shipped chain (`of_empty_body`).
- Corrected the original plan: the `of_leave_body` success-bridge chain
  is NOT pre-existing - it must be authored alongside E3, E4, E5.
- Documented the per-body-shape preservation status: `empty_block` and
  `comment` preservation lemmas already ship; `Leave` and
  `NativePreservableStraightStmts` preservation are net-new work.
- Pointed F1 readers at the redundancy with E5: F1 and E5 describe the
  same body shape `[.comment text]`, so F1 can be dropped once E5 lands.
- Listed concrete file insertion points for Layers A, B, D, and the
  call-site sweep required for Layer G.
- Revised the effort estimate to 5-9 senior-engineer days and noted
  why this scope is not compatible with a single autonomous coding
  session (prior worker attempts repeatedly stalled or fabricated
  phantom pushes).

No code changes; planning document only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* G1 Layer B: harness append/leave equation helpers for switch-id dispatcher

Adds three lemmas to the native dispatcher harness that the consumer-side
Revived constructors in Compiler/Proofs/EndToEnd.lean (Layer D) will need
to thread a NativePreservableStraightStmts-derived prefix through a switch
case body:

* exec_block_append_eq_of_continue (B3): equation form of
  exec_block_append_ok; given the left prefix evaluates to .ok mid at
  fuel + left.length + k, executing left ++ right at the same total fuel
  coincides with executing right on mid at fuel + k. Used by B1.

* exec_block_lowerStmtsNativeWithSwitchIds_with_leave_ok_eq_of_NativeBlockPreservesWord
  (B1): splices a NativePreservableStraightStmts-derived prefix in front
  of .Leave at the canonical fuel + suffixLen + native.length + 10 budget
  used by the dispatcher harness, producing .ok mid.setLeave.

* exec_block_lowerStmtsNativeWithSwitchIds_ok_eq_of_NativeBlockPreservesWord
  (B2): trivial no-leave variant via List.append_nil; keeps consumers
  free of fuel-arithmetic plumbing.

These are pure facts about EvmYul.Yul.exec and the Block reduction rule,
matching the "Layer B — Native harness lemmas" specification in
docs/NATIVE_EVMYULLEAN_G1_FOLLOWUP_PLAN.md (lines 69-85).

No new sorry, no new axiom. PrintAxioms registry regenerated
(5212 → 5215 entries). lake build green, make check green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Layer A: IR-side observational-equality lemmas (G1 S5-S8 follow-up)

Adds A1, A2, A3 IR-side observational-equality lemmas in
Compiler/Proofs/IRGeneration/IRInterpreter.lean and registers them in
PrintAxioms.lean. Uses option (b) inline IR-side predicate
IRStmtPreservesObs to avoid the EvmYulLeanNativeHarness import cycle.

* Layer A: weaken IRStmtPreservesObs predicate (drop vacuous storage/events equality)

The original IRStmtPreservesObs required state'.storage = state.storage AND
state'.events = state.events. This is FALSE for most NativePreservableStraightStmt
constructors (sstore, log0..log4, tstore, mstore, etc., which all mutate state),
making the predicate unsatisfiable for any non-trivial prefix and rendering
A1/A2 vacuously true.

The corrected predicate just claims execIRStmt terminates in `.continue _`.
This is genuinely satisfiable by the 17 non-terminator constructors of
NativePreservableStraightStmt. Storage and event correspondence between IR
and native runs is properly the responsibility of the bridge-side machinery
(harness state projection), not a per-stmt IR invariant.

A1 and A2 keep the same names and structures, with the storage/events
conjuncts dropped from their conclusions. A3 unchanged.

Local validation:
  lake build           exit 0
  make check           exit 0 (802 tests passed)
  zero new sorry/axiom in Compiler/ vs cca8c5d (base)

* docs: addendum on Layer A predicate evaluability issue + recommended path

Records the architectural finding from the May 2026 implementation pass:
- Layer A's IRStmtPreservesObs (even after ade52b0 weakening) is only
  satisfied unconditionally by .comment text. For let_/assign/sstore/
  log/etc, IR semantics revert when expression evaluation fails, so the
  per-stmt predicate (∀ state, .continue _) is too strong.
- letMany unconditionally reverts in current IR semantics.
- Recommended forward path: state-relative predicate IRStmtPreservesObs
  (stmt) (state) so A1/A2 can thread state through the inductive proof.
- Substrate caveat: 4/4 worker spawns failed during this session.

Layer A's lemmas (A1, A2, A3) remain in the tree and compile; they
provide list-induction scaffolding usable by callers who can supply the
per-stmt witness via a different route. D1's full implementation will
need either the state-relative predicate or an inline per-stmt induction.

* Layer A scaffolding: LetManyFree + NotTerminator side-condition helpers

Adds two purely-syntactic predicates on YulStmt that Layer D's prefix
hypotheses need:

  - LetManyFree stmt — excludes Yul .letMany. Compiled internal/external
    calls emit .letMany (Compile.lean:270,276,282), but the simple IR
    executor execIRStmt returns .revert on .letMany by design — those
    bodies use the contract-aware execIRStmtsWithInternals path instead.
    D1/D2's "preStmts ++ [.leave]" prefix needs each stmt to actually
    .continue in IR, so .letMany must be excluded.

  - NotTerminator stmt — excludes the three IR-side terminators
    (expr_stop/return/revert). These are valid NativePreservableStraightStmt
    constructors but produce .stop/.return/.revert rather than .continue,
    so they break the prefix-then-leave shape.

Both predicates ship with @[simp] proofs for the common non-letMany /
non-terminator constructors so they discharge automatically in callers.

Validation:
  lake build           exit 0
  make check           exit 0 (802 tests, "All checks passed")
  PrintAxioms.lean regenerated to 5227 theorems (+9 public)
  zero new sorry, zero new axiom in Compiler/ vs cca8c5d

* Layer A: add state-relative IRStmtPreservesObsAt with unconditional comment/leave proofs

Adds a state-relative variant of IRStmtPreservesObs that pins the predicate
to a specific IRState. This is what Layer D's cross-cast actually needs:
constructors like let_/assign/sstore depend on evalIRExpr succeeding, which
is state-dependent. Only .comment / .leave / .funcDef satisfy the
state-quantified version unconditionally.

  - def IRStmtPreservesObsAt (state) (stmt) — state-relative version
  - theorem IRStmtPreservesObs_iff_forall_state — bridges to existing
    state-quantified predicate
  - @[simp] proofs IRStmtPreservesObsAt_comment, _leave (unconditional)

The non-trivial constructors (let_/assign/expr_*) will need additional
evaluability witnesses tying their expressions to the state's variable
bindings. That work is deferred to a future commit.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated to 5231 theorems (+4 public)
  zero new sorry, zero new axiom in Compiler/ vs cca8c5d

* Layer A: cross-cast lemmas for let_/assign/sstore (state-relative)

Adds three IRStmtPreservesObsAt cross-cast lemmas, each conditional on an
expression-evaluability witness:

  - IRStmtPreservesObsAt_of_let_      — let_ name value
  - IRStmtPreservesObsAt_of_assign    — assign name value
  - IRStmtPreservesObsAt_of_sstore_lit_expr — expr (sstore [lit slot, valExpr])

Each takes a hypothesis (∃ v, evalIRExpr state expr = some v) to discharge
the .revert branch of execIRStmt. Pattern is uniform; the remaining
constructors (log0..log4, tstore, mstore, mstore8, calldatacopy,
returndatacopy, sstore-of-mappingSlot, sstore-of-ident, sstore-of-add,
expr_call general) follow the same shape.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated (+3 public)
  zero new sorry, zero new axiom

* Layer A: remove iff_forall_state lemma (CI Lean 4.22.0 mis-parses identifier)

The lemma `IRStmtPreservesObs_iff_forall_state` (added in af6974e) caused
CI's stricter Lean (v4.22.0) to auto-bind `IRStmtPreservesObs` as a free
type variable in the theorem signature, failing with "Function expected"
at the use site. Local Lean was lenient. Removing the lemma — it was
not load-bearing for any downstream theorem; the two predicates
`IRStmtPreservesObs` and `IRStmtPreservesObsAt` stand alone.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated (-1 public)
  zero new sorry, zero new axiom

* Layer A: cross-cast batch 2a (tstore, mstore)

Two more state-relative IRStmtPreservesObsAt cross-cast lemmas in the
same pattern as _of_sstore_lit_expr:

  - IRStmtPreservesObsAt_of_tstore  — expr (tstore [offsetExpr, valExpr])
  - IRStmtPreservesObsAt_of_mstore  — expr (mstore [offsetExpr, valExpr])

Each takes two evaluability hypotheses (offset + val), discharges the
.revert branches.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated (+2 public)
  zero new sorry, zero new axiom

* Layer A: cross-cast batch 2b (mstore8, calldatacopy, returndatacopy)

Three more IRStmtPreservesObsAt cross-casts. Unlike sstore/mstore/tstore
(which IR semantics models explicitly), these three fall through the
opaque `.continue state` branch in execIRStmt — the IR evaluator only
checks that the call's args evaluate, then leaves state unchanged.

  - IRStmtPreservesObsAt_of_mstore8
  - IRStmtPreservesObsAt_of_calldatacopy
  - IRStmtPreservesObsAt_of_returndatacopy

Each takes a single (∃ v, evalIRExpr state call = some v) witness for the
whole call expression. The IR model's lack of mstore8 byte-effect /
calldata-copy memory-write modeling is a known limitation; the bridge
to native handles those effects on the harness side via state projection.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated (+3 public)
  zero new sorry, zero new axiom

* Layer A: cross-cast batch 2c (log0..log4)

Five IRStmtPreservesObsAt cross-casts for the YulLog family. Each goes
through execIRStmt's `.call func args` branch with isYulLogName=true,
unfolding evalIRExprs over the arg list and applyYulLogCall? to bind the
new state via state.appendYulLog.

  - IRStmtPreservesObsAt_of_log0  (2 eval witnesses)
  - IRStmtPreservesObsAt_of_log1  (3)
  - IRStmtPreservesObsAt_of_log2  (4)
  - IRStmtPreservesObsAt_of_log3  (5)
  - IRStmtPreservesObsAt_of_log4  (6)

The proof structure is uniform: obtain each eval witness, refine with
state.appendYulLog as the continuation state, simp through execIRStmt /
evalIRExprs / applyYulLogCall? + Option.bind + rfl.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated (+5 public)
  zero new sorry, zero new axiom

* Layer A: cross-cast batch 3a (sstore_mappingSlot, sstore_ident)

Two more IRStmtPreservesObsAt cross-casts for sstore variants the
compiler emits:

  - IRStmtPreservesObsAt_of_sstore_mappingSlot — sstore at a slot
    computed via mappingSlot(base, key). Takes three eval witnesses.
  - IRStmtPreservesObsAt_of_sstore_ident — sstore at a slot read from
    a local variable. Takes an `IRState.getVar` witness for the ident
    plus an eval witness for the value.

These cover the bridged-straight sstore cases in BridgedStraightStmt
(line ~57-67 of EvmYulLeanBridgePredicates.lean) other than the
literal-slot case shipped earlier and the .add case still to come.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated (+2 public)
  zero new sorry, zero new axiom

* Layer A: cross-cast batch 3b (sstore_add + general expr_call_opaque)

Two more IRStmtPreservesObsAt cross-casts that complete the per-stmt
coverage for the bridged-straight fragment:

  - IRStmtPreservesObsAt_of_sstore_add — sstore at slot computed by
    add(left, right). Takes the slot-eval and val-eval witnesses.
  - IRStmtPreservesObsAt_of_expr_call_opaque — general-form catchall
    for any `.expr (.call func args)` whose `func` is not one of the
    special builtins (sstore/mstore/tstore/stop/return/revert/log*).
    Discharged by case-splitting on args shape and ruling out each
    structural pattern via the function-name hypotheses.

After this commit, all atomic NativePreservableStraightStmt
constructors (excluding letMany + the 3 terminators) have a
state-relative cross-cast lemma that produces IRStmtPreservesObsAt
given evaluability of the relevant sub-expressions.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated (+2 public)
  zero new sorry, zero new axiom

* Layer A: strengthen IRStmtPreservesObsAt + StmtsContinueFrom + state-threaded A1/A2

Substantial Layer A foundation work setting up next steps:

1. Strengthen IRStmtPreservesObsAt from ∀-∃ to ∃-∀ form:
     def IRStmtPreservesObsAt state stmt :=
       ∃ state', ∀ fuel, execIRStmt (fuel + 1) state stmt = .continue state'
   The witness state is now declared fuel-independent up front — this
   matches the actual proofs (each cross-cast computes a single state'
   that works for all fuels) and lets the predicate compose cleanly with
   list-induction without re-deriving fuel irrelevance at each step.

2. All 18 cross-cast proofs (comment, leave, let_, assign, sstore × 4,
   tstore, mstore, mstore8, calldatacopy, returndatacopy, log0..4,
   expr_call_opaque) updated to the new shape via `refine ⟨state',
   fun _ => ?_⟩` — same proofs, just reorder the quantifier handling.

3. Add `StmtsContinueFrom (state : IRState) : List YulStmt → Prop` —
   recursive state-threaded predicate carrying per-step continuation
   witnesses with fuel-independence.

4. Add state-threaded A1 / A2:
   - execIRStmts_continue_of_StmtsContinueFrom_pre_leave
   - execIRStmts_continue_of_StmtsContinueFrom_falling_through
   Both consume StmtsContinueFrom and produce existential continuation
   for execIRStmts at the right fuel budget. These are the versions D1
   and D2 will actually use (the original state-quantified A1/A2 only
   apply to comment/leave-prefix bodies; the threaded variants apply to
   ANY prefix whose per-stmt witnesses can be supplied by the dispatch
   theorem from NativePreservableStraightStmt + eval witnesses).

5. Shorten original `_pre_leave` proof (remove redundant `show` lines)
   so it no longer trips the 50-line proof-length audit.

Net change: +90 LoC. Sets up the dispatch theorem (next turn) which
will assemble `StmtsContinueFrom state stmts` from
`NativePreservableStraightStmts stmts + LetManyFree stmts +
NotTerminator stmts + per-stmt evaluability`.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated
  zero new sorry, zero new axiom

* Layer A: composition helpers for StmtsContinueFrom

Three small introduction lemmas to make D1/D2 builders cleaner:

  - @[simp] StmtsContinueFrom_nil — trivial nil case.
  - StmtsContinueFrom.cons_of_witness — cons given a per-step ∀-fuel
    witness and a tail witness.
  - StmtsContinueFrom.cons_of_IRStmtPreservesObsAt — cons given an
    IRStmtPreservesObsAt witness (destructures internally to expose
    the next-state binding to the tail continuation).

These mirror the shape produced by the 18 cross-cast lemmas, so D1's
construction of a StmtsContinueFrom witness for a NativePreservableStraightStmts
prefix becomes a list induction with `cons_of_IRStmtPreservesObsAt` at
each step.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated (+3 public)
  zero new sorry, zero new axiom

* Layer A: fix CI red on d333e45 (dedup + unused simp args)

Two issues from prior commit:
1. StmtsContinueFrom_nil declared twice (out-of-band push added a
   dot-notation set of helpers at line 1609+; my earlier underscore
   versions at line 1530+ conflicted). Removed my duplicates; kept
   the dot-notation set (StmtsContinueFrom.cons_of_witness,
   .cons_of_IRStmtPreservesObsAt).
2. _of_expr_call_opaque's [a, b] arm had three simp args
   (hReturnFalse, hRevertFalse, hNotLog) that CI's
   linter.unusedSimpArgs flagged. These were carry-over from earlier
   experimentation — the kernel reduces the match arms directly given
   hNotSStore/hNotMStore/hNotTStore. Removed.

Validation:
  lake build         exit 0
  make check         exit 0
  PrintAxioms.lean   regenerated
  zero new sorry, zero new axiom

* Layer A: remove final unused simp args in expr_call_opaque

CI linter.unusedSimpArgs flagged hNotSStore/hNotMStore/hNotTStore/hNotLog
across the four arms of _of_expr_call_opaque's args case analysis.
Simp's match-reduction discharges the special-case branches via
constructor inequality on string literals at the outer level, so the
explicit non-equality hypotheses are unused as rewrite rules. Hypothesis
parameters retained for caller-side documentation.

Validation:
  lake build         exit 0
  make check         exit 0
  zero new sorry, zero new axiom

* Layer A: fix CI strict-Lean unsolved-goals on opaque expr_call branches

Three Layer A cross-cast theorems for opaque fallthrough builtins
(`mstore8`, `calldatacopy`, `returndatacopy`) were failing on the
EVMYulLean fork probe (Lean 4.22.0, strict mode) because `simp only`
could not fully reduce the chained `match e with | .call "..." [...] =>
... | .call func args => if isYulLogName func then ... else ...` even
with the appropriate evaluation hypothesis. Full `simp` does reduce,
but `linter.unusedSimpArgs` then flags load-bearing simp args as
"unused" because they are consumed by match-reduction rather than
direct rewrites.

Resolution:
- Set `linter.unusedSimpArgs false` at file scope (matches the
  existing precedent in `Compiler.Proofs.IRGeneration.Function.lean:11`).
- Switch the three opaque-fallthrough cross-casts from `simp only` +
  `rfl` to plain `simp` with the same simp set; the linter no longer
  flags the auxiliary args as unused.
- Apply the same `simp` (vs `simp only`) shape to the five log
  cross-casts (`log0`..`log4`) for consistency.
- Delete `IRStmtPreservesObsAt_of_expr_call_opaque` (the catch-all
  cross-cast over generic call shapes); it was the source of the
  cascading linter failures and its case is covered by the per-builtin
  cross-casts that the Layer-D dispatch will actually consume.
- Regenerate `PrintAxioms.lean` (-1 theorem).

Local validation:
  make test-evmyullean-fork  →  "EVMYulLean fork conformance probe
  passed." (build 1134/1134 green, including
  Compiler.Proofs.IRGeneration.IRInterpreter and the full
  Compiler.Proofs.EndToEnd).

No new sorry, no new axiom. Layer-A constructor surface for Layer D is
unchanged except for the deletion of the generic-opaque entry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Layer A: restore expr_call_opaque cross-cast + add funcDef

Two additions to the Layer A cross-cast surface that the Layer D dispatch
will consume:

1. Restore IRStmtPreservesObsAt_of_expr_call_opaque (deleted in b7ed463
   to fix CI strict-Lean unsolved-goals). The proof now uses `cases args`
   to enumerate argument-list shapes (0/1/2/3+) before applying `simp`
   with the specific non-builtin hypotheses, avoiding the cascading-match
   reduction issue that motivated the original deletion. The hypotheses
   are simplified: hNotRevert and hNotReturn become plain inequalities
   (func ≠ "revert" / "return") rather than the awkward universally-
   quantified args-bound forms.

2. Add IRStmtPreservesObsAt_funcDef. Mirrors the structure of
   IRStmtPreservesObsAt_comment and IRStmtPreservesObsAt_leave: the IR
   semantics for `.funcDef _ _ _ _` is unconditional `.continue state`,
   so the cross-cast holds at every state. Used by the BridgedStraightStmt
   `.funcDef` constructor in the Layer D dispatch.

PrintAxioms.lean regenerated (+2 theorems: 5250 → 5252).

Local validation: `make test-evmyullean-fork` →
"EVMYulLean fork conformance probe passed" (1134/1134 green, including
the full Compiler.Proofs.EndToEnd target).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* upstream/main fix candidate: mulDiv512Down/Up missing cases in SupportedSpec.lean

Upstream main commit 6b2af37 (#1846, "Add mulDiv512Down / mulDiv512Up codegen
with FullMath Yul helper") introduced new Expr constructors `mulDiv512Down` /
`mulDiv512Up` but did not update the 9 helper functions in SupportedSpec.lean
that explicitly enumerate Expr cases. This results in CI build failure on
main 6b2af37 (run 25764352385) with 9 "Missing cases" errors at lines 556,
683, 724, 762, 804, 854, 898, 940, 983 — each citing "Expr.mulDiv512Up _ _ _".

This commit applies the conservative fix: add `| .mulDiv512Down a b c |
.mulDiv512Up a b c` to each affected pattern, sharing the body with the
existing `mulDivDown`/`mulDivUp` cases (which are semantically the same shape:
3 expression args, no new helper surface introduced).

Local validation: lake build now passes the "Missing cases" stage. A separate
upstream Lean elaborator bug (`enableRealizationsForConst` failure at
SupportedSpec.lean:5694) still blocks the SupportedSpec build, but that's an
unrelated issue.

This patch is intended to be cherry-picked into upstream main.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* upstream/main fix: paramDynamicHeadWord + wildcard expansion in SupportedSpec

Fixes a 2nd round of upstream main bugs that PR #1826's CI surfaces
(commits cf5cb84 + 6b2af37 added 3 new Expr constructors —
paramDynamicHeadWord, mulDiv512Down, mulDiv512Up — without updating
SupportedSpec.lean's exhaustive case-matches).

Three structural fixes:
- Add `.paramDynamicHeadWord _ _` case to 9 helper-surface functions
  (exprTouchesUnsupportedConstructorRawCalldataSurface,
   exprTouchesUnsupportedCoreSurface, exprTouchesUnsupportedStateSurface,
   exprTouchesUnsupportedCallSurface, exprTouchesUnsupportedHelperSurface,
   exprTouchesInternalHelperSurface, exprTouchesUnsupportedForeignSurface,
   exprTouchesUnsupportedLowLevelSurface, exprTouchesUnsupportedContractSurface).
  Conservative semantics: paramDynamicHeadWord returns true (touches
  unsupported surface) for the constructor-context, core, state, and
  contract surface checks, since it reads calldata; returns false (does
  not touch unsupported) for call/helper/foreign/lowLevel surfaces.

- Expand wildcard `| _ => []` in exprInternalHelperCallNames to explicit
  enumeration of all leaf Expr constructors plus the `adtConstruct`
  recursive case, matching the pattern of upstream commit 34063ed
  (#1842) which fixed the same eq_def heartbeat ceiling issue in
  ValidationCalls.lean and ValidationInterop.lean. Without this, Lean
  4.22.0's eq_def deriver hits the 200K-heartbeat ceiling because each
  new Expr constructor enlarges the wildcard's complement to enumerate.

- Add file-level `set_option maxHeartbeats 4000000` (cosmetic; doesn't
  propagate to eq_def deriver but provides headroom for normal proofs).

Local validation: build now progresses past the structural bugs to
downstream cascading "Alternative not provided" errors at lines 3133,
3523, 3722, 4092 (and several "unsolved goals" at 4352+) — these are
proofs that case-split on Expr and need the new constructors as
explicit cases. Those fixes are mechanical but tedious (~12+ case
additions across many proofs); deferring to the upstream maintainer
or a follow-up PR.

This commit is intended to be cherry-picked into upstream main
alongside #1843/#1846 to keep main's CI green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* upstream/main fix: add paramDynamicHeadWord/mulDiv512 to 6 cases-on-Expr proofs

Continues the cascade of fixes for upstream main commits cf5cb84 +
6b2af37 (which added paramDynamicHeadWord, mulDiv512Down, mulDiv512Up
without updating dependent code). Each `cases expr` proof in
SupportedSpec.lean that enumerates Expr constructors needs explicit
arms for the 3 new constructors.

Sites updated:
- exprTouchesInternalHelperSurface_eq_false_of_helperSurfaceClosed (3201)
  → mulDiv512Down/Up share the mulDivDown/Up arm; paramDynamicHeadWord
    is its own arm using `simp [exprTouchesInternalHelperSurface]`
- exprTouchesUnsupportedCallSurface_eq_featureOr (3605)
  → mulDiv512 share with mulDivDown/Up; paramDynamicHeadWord uses simp
- exprTouchesUnsupportedContractSurface_eq_false_of_featureClosed (3796)
  → mulDiv512 share; paramDynamicHeadWord falls into hcore-impossible
    (since exprTouchesUnsupportedCoreSurface paramDynamicHeadWord = true)
- exprTouchesUnsupportedCallSurface_eq_false_of_coreClosed (3878)
  → mulDiv512 share; paramDynamicHeadWord uses hcore-impossible
- exprTouchesUnsupportedHelperSurface_eq_false_of_contractSurfaceClosed
  (4146) → mulDiv512 share; paramDynamicHeadWord uses hsurface-impossible
- exprUsesArrayElement_eq_false_of_coreClosed (4351)
  → mulDiv512 share; paramDynamicHeadWord uses hcore-impossible
- exprUsesStorageArrayElement_eq_false_of_coreClosed (4401)
  → same pattern
- exprUsesDynamicBytesEq_eq_false_of_coreClosed (4453)
  → same pattern

Local validation: `lake build Compiler.Proofs.IRGeneration.SupportedSpec`
now passes. Subsequent build chain reveals the same upstream-bug
pattern in `Compiler/Proofs/IRGeneration/SourceSemantics.lean`
(evalExprWithHelpers/evalExprListWithHelpers/execStmtWithHelpers/
interpretInternalFunctionFuel mutual block, ~25 wildcards) — that's
the next file to patch, but is a separate, larger commit.

This is upstream main's work. Documented as a fix-candidate ready to
cherry-pick into upstream alongside #1843/#1846.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* allowlist: 2 SupportedSpec proofs that grew past 50 lines

The two proofs `exprUsesStorageArrayElement_eq_false_of_coreClosed`
and `exprUsesDynamicBytesEq_eq_false_of_coreClosed` grew to 51-52
lines after I added paramDynamicHeadWord/mulDiv512Down/Up arms in
commit dfdd124. Each new arm is a single mechanical case (`simp
[exprTouchesUnsupportedCoreSurface] at hcore`); splitting the proof
into smaller lemmas would just duplicate the same boilerplate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* upstream/main fix: SourceSemantics — wildcard expansion + new constructor cases

Continues the cascade of upstream main fixes (commits cf5cb84 + 6b2af37
added paramDynamicHeadWord, mulDiv512Down, mulDiv512Up to Expr without
updating dependent code).

Three structural fixes in SourceSemantics.lean:

1. Expand wildcard `| _ => none` in evalExprWithHelpers (line 2691) into
   explicit enumeration of paramDynamicHeadWord, mulDiv512Down/Up, plus
   16 other previously-wildcarded constructors. This fixes the
   eq_def heartbeat ceiling that was triggering simp/realization
   failures at every downstream call site.

2. Apply the same wildcard expansion to the standalone `evalExpr`
   function (line 853) — same heartbeat issue, separate scope.

3. Add 3 new evalExpr_xxx rfl-lemmas (`evalExpr_mulDiv512Down`,
   `evalExpr_mulDiv512Up`, `evalExpr_paramDynamicHeadWord` — all `= none`
   by definition) so the cases-on-Expr proof at line 3577
   (evalExprWithHelpers_eq_evalExpr_of_helperSurfaceClosed) can use the
   `unfold + rw + reflective rewrite` pattern instead of running into
   heartbeat-prone `simp` recursion.

4. Add `set_option maxHeartbeats 1000000 in` before the mutual block
   at line 3557 to give the recursive proofs in that block more
   budget (defensive; current proofs fit in 200K but the budget is
   tight).

5. Add 3 new arms (`mulDiv512Down a b c`, `mulDiv512Up a b c`,
   `paramDynamicHeadWord name offset`) to the cases-on-Expr proof
   at line 3577 using the `unfold evalExprWithHelpers; rw [evalExpr_*]`
   pattern.

Local validation: `lake build Compiler.Proofs.IRGeneration.SourceSemantics`
now passes. Downstream files (ContractShape.lean and GenericInduction.lean)
still need similar treatment — those are separate, smaller commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* upstream/main fix: total defs + new SupportedSpec _eq_false lemmas + cascade

Cascade fixes for upstream commits cf5cb84 (paramDynamicHeadWord) and 6b2af37
(mulDiv512Down/Up) that added Expr constructors but broke downstream proofs.

UsageAnalysis.lean:
- Convert exprUsesParamDynamicHeadWord, stmtUsesParamDynamicHeadWord,
  exprUsesMulDiv512, stmtUsesMulDiv512 from `partial def` to total `def` with
  `mutual` blocks, helper exprList/stmtList/matchBranches functions, and
  `termination_by sizeOf`. Partial defs were kernel-opaque, blocking downstream
  proofs by induction.

SupportedSpec.lean:
- Mark `mulDiv512Down/Up` as touching unsupported Core+Contract surface
  (consistent with `paramDynamicHeadWord => true`); they have no
  `ExprCompileCore` constructor, so the surface predicate must reject them.
- Split four cases-on-Expr proofs (mulDivDown vs mulDiv512Down/Up) where the
  combined recursion no longer typechecks under the new surface semantics.
- Add 14 new lemmas: exprCompileCore_uses{ParamDynamicHeadWord,MulDiv512}_false,
  exprListCompileCore_…, stmtListCompileCore_…, stmtListTerminalCore_…,
  _eq_any bridges, supportedStmtList_…_false, and
  SupportedSpec[ExceptMappingWrites].contractUses{…}_eq_false.

ContractShape.lean / Contract.lean:
- Wire new flag-eq-false hypotheses into the three rfl-failing
  `compileValidatedCore_ok_yields_internalFunctions_nil` proofs.

GenericInduction.lean:
- Add `mulDiv512Down/Up` cases to
  `exprCompileCore_of_exprTouchesUnsupportedContractSurface_eq_false`.

CompilationModelFeatureTest.lean:
- Declare `external echo` in `adtAliasPayloadMemoizesExprSpec`. Upstream's
  Issue #732 validation now rejects undeclared external call targets.

scripts/check_proof_length.py:
- Allowlist 4 mechanical inductions whose case count is bounded by the number
  of SupportedStmtList constructors.

PrintAxioms.lean:
- Regenerated for the 4 new public theorems and 14 private helpers.

Local: lake build green, make check green, zero new sorry/axiom.

* G1 E3 + E5: success-bridge chains for of_block_empty and of_singleton_comment

Two new private theorems each in EndToEnd.lean (one preserves bridge, one
success bridge per leaf):

- NativeGeneratedSelectorHitUserBodyPreservesBridgeAtFuel.of_block_empty
- NativeGeneratedSelectorHitSuccessBridge.of_block_empty
- NativeGeneratedSelectorHitUserBodyPreservesBridgeAtFuel.of_singleton_comment
- NativeGeneratedSelectorHitSuccessBridge.of_singleton_comment

Both leaves lower to native shape `[.Block []]`, so the per-stmt
preservation half is discharged by the existing
`NativeStmtPreservesWord_empty_block` harness lemma (no new harness work).
The success bridge follows the standard chain template via
`of_selected_user_body_exec_only_and_preserves`, pairing the existing
`ExecOnlyBridgeAtFuelRevived.of_<leaf>` constructor with the new preserves
bridge.

E2 (`of_leave_body`) and E4 (`of_block_leave`) still blocked on a missing
`NativeStmtPreservesWord_leave` harness lemma (per plan §358). E6/E7
blocked on D1/D2.

PrintAxioms.lean regenerated (4 new private theorems).

Local: lake build green, make check green, zero new sorry/axiom.

* Add parallel _revived preservation predicates with leave-singleton bridge

Plan §358's proposed `NativeStmtPreservesWord_leave` is structurally false
because `state[name]!` via GetElem falls through the empty `default` Finmap
for Checkpoint states. The Leave-ending exec produces `Checkpoint (.Leave
shared store)` whose `[name]!` is ⟨0⟩, not the inner-store value.

Adds:
- 5 reviveJump simp lemmas (Ok, OutOfFuel, Leave, Continue, Break)
- NativeBlockPreservesWord_revived / NativeStmtPreservesWord_revived defs
  that read the inner store via `state.reviveJump[name]!`
- NativeStmtPreservesWord_revived_leave (provable in NEW form)
- NativeBlockPreservesWord_revived_nil/cons/singleton builders

The original NativeBlockPreservesWord/NativeStmtPreservesWord/Expr/EvalArgs
predicates are untouched, so 145+ existing usages in EndToEnd.lean and the
existing harness proofs remain unchanged.

Next: add NativeGeneratedSelectorHitUserBodyPreservesBridgeAtFuel_revived
def and E2/E4 success-bridge chains in EndToEnd.lean using the _revived
predicates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add NativeStmtPreservesWord_revived_empty_block

Companion to NativeStmtPreservesWord_revived_leave from 5f8e602. The
empty-block case is trivial in the revived form: exec _ (.Block []) _ s
returns s unchanged, so final.reviveJump[name]! = state.reviveJump[name]!
follows directly from the hypothesis.

Useful for E1/E3 chains that wish to be expressed in _revived form
without going through the OLD-form predicate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Regenerate PrintAxioms.lean for Path B revived predicates

Adds the new reviveJump simp lemmas and _revived predicates introduced
in 5f8e602 + 2f8a801 to the axiom audit roster. No new sorry, no new
axiom — these are all standard theorems.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Cascade fix for upstream Expr.arrayElementDynamicMember{Length,Element}

Upstream commits 041a783 (G1: length on struct-array elements) and
300fd81 (G2: arrayElementDynamicMemberElement constructor) added two
new Expr constructors but did not update SourceSemantics.lean's
evalExprWithHelpers unmodeled-list nor the
evalExprWithHelpers_eq_evalExpr_of_helperSurfaceClosed match.

Adds:
- evalExpr_arrayElementDynamicMember{Length,Element} eval lemmas (= none, rfl)
- New constructors in the unmodeled-list of evalExprWithHelpers
- Match cases in evalExprWithHelpers_eq_evalExpr_of_helperSurfaceClosed
- PrintAxioms.lean regenerated

lake build + make check both green locally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Add _revived block adapter + .Block [.Leave] composition

Two small foundation lemmas building on the Path B core:
- NativeStmtPreservesWord_revived_block: trivial adapter mirroring the
  OLD-form NativeStmtPreservesWord_block (witness is the hypothesis)
- NativeStmtPreservesWord_revived_block_leave: composition for the
  lowered shape of IR [.block [.leave]] (E4 target body)

Both are pure compositions using the _revived predicates from 5f8e602.
PrintAxioms regenerated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Layer B Worker <worker@verity.lfg>
Co-authored-by: Claude Worker <claude-worker@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.

P0: struct parameter projection from an ABI-dynamic root (Unlink Transaction[] etc.)

1 participant