Skip to content

Add Alchemix V3 earmark conservation benchmark case#35

Merged
fricoben merged 12 commits into
mainfrom
alchemix-earmark-conservation
May 6, 2026
Merged

Add Alchemix V3 earmark conservation benchmark case#35
fricoben merged 12 commits into
mainfrom
alchemix-earmark-conservation

Conversation

@fricoben
Copy link
Copy Markdown
Contributor

Adds a new case (alchemix/earmark_conservation) targeting the lazy-projected earmark conservation invariant for Alchemix V3, agreed with the Alchemix team (Ov3rkoalafied, 2026-04-24): the sum over active accounts of project(account, _earmarkWeight, _redemptionWeight, _survivalAccumulator) .newEarmarked equals cumulativeEarmarked.

In scope: _earmark, _sync, _computeUnrealizedAccount, redeem, _subEarmarkedDebt, _subDebt of AlchemistV3.sol pinned at 117c95b6ee11a75221d6fbdc79f16ac6acdb96f5.

Phase 2 scaffold only — Contract.lean and Specs.lean build green; five Generated placeholders typecheck with deferred goals. Reference proofs land in Phase 3.

Simplifications listed in Contract.lean header: Q128 idealization (no floor-rounding drift), cross-epoch path elided, default vs telescoped same-epoch sub-branch collapsed, external calls opaque, cover-shares abstracted, access control / NFT ownership / pause flags dropped, Account fields pruned to the 5 invariant-relevant ones, active-id finset passed as explicit spec parameter.

Th0rgal and others added 12 commits April 28, 2026 15:31
Adds a new case (alchemix/earmark_conservation) targeting the lazy-projected
earmark conservation invariant for Alchemix V3, agreed with the Alchemix team
(Ov3rkoalafied, 2026-04-24): the sum over active accounts of
project(account, _earmarkWeight, _redemptionWeight, _survivalAccumulator)
.newEarmarked equals cumulativeEarmarked.

In scope: _earmark, _sync, _computeUnrealizedAccount, redeem,
_subEarmarkedDebt, _subDebt of AlchemistV3.sol pinned at
117c95b6ee11a75221d6fbdc79f16ac6acdb96f5.

Phase 2 scaffold only — Contract.lean and Specs.lean build green; five
Generated placeholders typecheck with deferred goals. Reference proofs land
in Phase 3.

Simplifications listed in Contract.lean header: Q128 idealization (no
floor-rounding drift), cross-epoch path elided, default vs telescoped
same-epoch sub-branch collapsed, external calls opaque, cover-shares
abstracted, access control / NFT ownership / pause flags dropped, Account
fields pruned to the 5 invariant-relevant ones, active-id finset passed as
explicit spec parameter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cleanup pass to make the Verity model trivially comparable to AlchemistV3.sol:

- Rename functions to match Solidity: _earmark, _sync, _subEarmarkedDebt,
  _subDebt (kept leading underscore where source has it). redeem unchanged.
- Rename storage slots: _earmarkWeight, _redemptionWeight, _survivalAccumulator,
  _transmuterEarmarkAmount keep leading underscore (private in source).
  cumulativeEarmarked, totalDebt unchanged (public in source).
- Flatten Account struct as _accounts_<field>[id] mappings — verbatim mirror
  of _accounts[id].<field>.
- Rename verity_contract from AlchemistEarmark to AlchemistV3 (matches source).
- Add per-block source-line annotations: every block in every function
  carries a `-- src: AlchemistV3.sol:Lxxxx-Lyyyy` comment naming the
  Solidity lines it models.
- Per-slot doc comments mapping each Verity slot to its Solidity origin.
- Specs.lean: rename storage accessors and projection function.
  _computeUnrealizedAccount now matches the source name and returns the
  full triple via ComputeUnrealizedAccountResult; projectedEarmarked is
  the thin extractor for the second component.
- Generated placeholders and task YAMLs updated to reference renamed
  theorems and contract module.

Phase 2 scaffold remains green; semantics unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI manifest validator rejects 'invariant_preservation' (not in the schema enum).
Switch all five Alchemix earmark_conservation tasks to 'state_preservation_local_effects',
the closest schema-allowed match for "operation preserves invariant".

Also regenerate REPORT.md and benchmark-inventory.json via scripts/generate_metadata.py.

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

Adds Benchmark/Cases/Alchemix/EarmarkConservation/Proofs.lean (1471 lines)
proving the lazy-projected earmark conservation invariant under each of the
five in-scope operations:

  - _sync_preserves_invariant
  - _subDebt_preserves_invariant
  - _subEarmarkedDebt_preserves_invariant
  - redeem_preserves_invariant
  - _earmark_preserves_invariant

All theorems proven from operational semantics; no `axiom`, no `sorry`, no
`admit`. The slot-write and frame lemmas are derived directly from the
verity_contract bodies (pattern: `simp [<fn>, <storage_field>..., getStorage,
setStorage, getMappingUint, setMappingUint, Verity.bind, Bind.bind,
Verity.pure, Pure.pure, Contract.run, ContractResult.snd]`) and chained
through atomic Q128 algebra hypotheses.

Theorem hypotheses are restricted to:

  (a) atomic Q128 algebra identities (`hQ128MulOne`, `hQ128MulLinear`,
      `hQ128MulAssoc`, `hQ128DivCommScale`, etc.) — disclosed in
      Contract.lean as the Q128-idealization simplification.

  (b) pre-state operational invariants of the contract (`hLastRWNonZero`,
      `hLastEWNonZero`, `hSyncedAtTokenId`, `hCumulativeLeTotalDebt`,
      `hAccountEarmarkedLeCumulative`).

  (c) one bridging parallel-debt-conservation summation
      (`hSumZ_eq_liveUnearmarked`) on `_earmark`, documented as a
      corollary of the protocol's parallel debt invariant.

No abusive hypotheses (post-state assertions about specific contract
effects) — every operational fact is derived from the contract body via
slot-write lemmas.

Specs.lean adds public defs for six pure helpers (`redeem_ratioApplied`,
`redeem_active`, `_earmark_ratioApplied`, `_earmark_active`,
`_earmark_effectiveEarmarked`, `earmark_unearmarkedTimesRSR`) that the
placeholder hypotheses reference. The five Generated/Tasks placeholders
have their hypothesis sets locked to the same Q128-clean shape as the
reference proofs in Proofs.lean.

case.yaml and all five task YAMLs flip proof_status to `complete`.
REPORT.md and benchmark-inventory.json regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reframe the five hypotheses originally surfaced on the preservation
theorems against an honest taxonomy: which are scope cuts (provable
within the case but skipped) vs. which are real model artifacts
(not provable without expanding the model).

H5 — `accounts_earmarked accountId ≤ cumulativeEarmarked` (in
_subEarmarkedDebt). Discharged. Replaced with a no-overflow
hypothesis on the projection sum, then derived the inequality from
invariant + H2-synced + non-negativity via a new
`singleton_le_sum_of_no_overflow` lemma. The new no-overflow
hypothesis is a generic property of the sum semantics that the
protocol's debt cap ensures by design.

H2 — `accounts_lastAccrued{Earmark,Redemption}Weight = global`. The
Solidity convention is that every call site of _subDebt /
_subEarmarkedDebt invokes _sync(id) immediately before. Two new
composite theorems prove the call-site sequence:

  - _sync_then_subDebt_preserves_invariant
  - _sync_then_subEarmarkedDebt_preserves_invariant

H2 is discharged inside via _sync_writes_lastEW /
_sync_writes_lastRW; the no-overflow hypothesis transfers across
sync via projection unchangedness.

H4 — `cumulativeEarmarked s ≤ totalDebt s − amount` (in _subDebt).
Decomposed into:
  - sister invariant `cumulativeEarmarked ≤ totalDebt` (the line-1306
    clamp invariant), now proven preserved by all five operations:
      _sync_preserves_cumLeTotalDebt
      _subDebt_preserves_cumLeTotalDebt
      _subEarmarkedDebt_preserves_cumLeTotalDebt
      redeem_preserves_cumLeTotalDebt
      _earmark_preserves_cumLeTotalDebt
  - caller-side bound `amount ≤ totalDebt − cumulativeEarmarked`
    (the live-unearmarked-debt check the caller already does).
A new wrapper `_sync_then_subDebt_preserves_invariant_v2` takes the
two cleaner hypotheses and discharges H4 internally.

H3 — `accounts_lastAccruedRedemptionWeight ≠ 0` for every active id
(in redeem and _earmark). NOT a scope cut. A worked counterexample
(documented in-file) shows that, within the model, dropping H3
makes redeem_preserves_invariant FALSE: starting from a state with
_redemptionWeight = 0, an account with lastRW = 0 and earmarked > 0,
applying redeem(amount) breaks conservation. The state is reachable
in the model because the model collapses redeem's epoch-reset (full
wipe sets _redemptionWeight = 0); in deployed Solidity the epoch
advances and _redemptionWeight resets to ONE_Q128, so H3 is a true
invariant of the contract. Discharging H3 in our model would
require modeling epochs faithfully — out of scope for this case.

H6 — parallel debt-conservation summation (in _earmark). Documented
as a scope cut. Discharging it from first principles requires
modeling the debt-mutation operations (_addDebt, _resetDebt, the
constructor write seeding totalDebt) and proving debt conservation
as a sister invariant. Each step is mechanical and reuses this
file's slot-write + survival-ratio toolkit; a follow-up case on
the debt side closes the loop.

Net result on the user-facing theorems:

  - _subEarmarkedDebt_preserves_invariant: H5 replaced by no-overflow.
  - _sync_then_subDebt_preserves_invariant: drops H2.
  - _sync_then_subDebt_preserves_invariant_v2: drops H2 and H4
    (sister invariant + caller bound).
  - _sync_then_subEarmarkedDebt_preserves_invariant: drops H2.

Five new sister-invariant preservation theorems for cum ≤ td. All
proven from operational semantics. No axioms, no sorry, no admit.
The full `Compile` target builds green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
H6 was previously documented in a code comment (Proofs.lean) as a
scope cut. This adds a concrete `(s, ids)` pair where the H6
conclusion fails — verified by `native_decide`. The witness is:

  totalDebt = 100, cumulativeEarmarked = 0, transmuterAmount = 50,
  ids = empty.

`_earmark_active = true` on this state and the LHS sum is 0 while
the RHS is sub 100 0 = 100, so the bridging identity is false on a
state the model accepts. This confirms H6 cannot be discharged from
the storage-only model used in the case — it depends on the (currently
unmodeled) debt-conservation sister invariant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
H3 (`accounts_lastAccruedRedemptionWeight s id ≠ 0` for every active
id) was previously a model-artifact hypothesis on
`redeem_preserves_invariant` documented only in a code comment. This
makes the counterexample mechanically checkable via `native_decide`.

The witness is a state where:
  - the conservation invariant holds in pre,
  - id 1 has lastAccruedRedemptionWeight = 0 (H3 violated),
  - after `redeem(1)`, sumProjectedEarmarked s' ids = 2 but
    cumulativeEarmarked s' = 1, so the invariant is broken.

The packaged `H3_required_for_redeem` theorem witnesses the
existence of a state at which dropping H3 from `redeem_preserves_invariant`
would make the theorem false. Per the existing comment, the underlying
state is unreachable in deployed Solidity — `redeem(amount=cum)`
advances the redemption epoch — but reachable in our flat-Q128 model;
H3 is the proxy precondition that hides that elision.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds explicit named theorems in HypothesisAudit.lean making the
discharge of H2/H4/H5 from `Proofs.lean` directly visible, instead of
buried inside the composite call-site theorems.

  * H2_synced_after_sync — after `_sync(tokenId)`, the account at
    `tokenId` is synced at the current globals. Discharges the H2
    precondition on `_subDebt_preserves_invariant` and
    `_subEarmarkedDebt_preserves_invariant` from the call-site
    sequence.

  * H4_from_sister_invariant — combines the proven sister invariant
    `cumulativeEarmarked ≤ totalDebt` with the caller-side
    live-unearmarked-debt bound to recover the original
    `cumulativeEarmarked s ≤ sub (totalDebt s) amount` precondition.

  * H5_from_invariant_and_no_overflow — under the invariant, H2 at
    accountId, and no-overflow on the projection sum, the stored
    earmarked at accountId is bounded by cumulativeEarmarked.

To enable these named theorems we exposed `_sync_slot_write`,
`_sync_writes_lastEW`, `_sync_writes_lastRW`, and
`accounts_earmarked_le_cumulative_of_invariant` (previously private)
as public theorems in Proofs.lean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the empty-FiniteSet H6 witness with a non-empty one.

  totalDebt = 100, cumulativeEarmarked = 0, transmuter = 50,
  weights = ONE_Q128, ids = {1}, account 1 has debt = 1.

This is exactly the situation where the (currently unmodeled)
debt-conservation sister invariant fails: Sigma accounts_debt = 1 but
totalDebt = 100. The per-account earmark_unearmarkedTimesRSR computes
to 1, the LHS sum is 1, the RHS is 100; H6 is broken.

The empty-set witness was technically correct but readers can object
"the protocol would never have empty active ids". The non-empty form
witnesses the actual debt/earmark mismatch H6 is designed to rule out
once the debt-conservation case lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the natural sister of the main earmark-conservation invariant —
projected debt conservation: sumProjectedDebt = totalDebt — to
Specs.lean, where:

  projectedDebt(s, id) := projectedEarmarked(s, id) + earmark_unearmarkedTimesRSR(s, id)

Algebraically (under Q128 idealization, dbt ≥ earm), this equals
mulQ128 (accounts_debt s id) USR-redeem(id) — the natural per-id
projection of stored debt through the redemption-survival ratio. The
two summands decompose the projected total debt at the id into its
projected-earmarked part (which sums to cumulativeEarmarked) and its
projected-unearmarked-and-survived part (which sums to live
unearmarked debt = totalDebt − cumulativeEarmarked).

`H6_from_projectedDebt_conservation` (Proofs.lean) proves the original
H6 form follows from:
  - the main invariant: sumProjectedEarmarked = cumulativeEarmarked;
  - the new sister: sumProjectedDebt = totalDebt;
  - the line-1306 sister already proven: cumulativeEarmarked ≤ totalDebt.

`HypothesisAudit.H6_from_sister_invariant` re-exposes this for callers.

Why this is the cheap fix, not a discharge: the new sister invariant
is still an assumption — we have not proven it preserved by every op,
because the model lacks debt-mutation operations (`_addDebt`,
`_resetDebt`, the constructor seed for totalDebt). The
`H6_not_a_tautology` counterexample stands. What changes is only the
shape of the assumption: instead of a Q128-projected technicality
that's hard to read, it's now the obvious sister of the main invariant.

Build green; no axioms, no sorry, no admit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fricoben fricoben merged commit f58eae7 into main May 6, 2026
1 check passed
@fricoben fricoben deleted the alchemix-earmark-conservation branch May 6, 2026 10:14
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.

3 participants