Blanc/implementations/ports

WETH.

The first port: wrapped ether, after WETH9, the mainnet contract at 0xC02a…6Cc2. Its headline is solvency — enough ETH held to honour every booked balance — preserved not once but at every altitude: a single frame, a state transition, a whole block, and every reachable state of a configured chain, across scheduled fork activations.

A port never claims byte identity with its reference — what it claims, and what it deliberately never claims, is governed by PORTING.md. This page lays out the evidence in that standing register: how we know the behavior, where it deliberately differs, what the differences cost and buy, and what the build taught.

$ scripts/check-weth.sh --no-build OK — weth fixtures: 11/11 PASS $ scripts/check-weth-coverage.sh OK — weth coverage: 10/10 selectors reached (4 direct, 6 witnessed internal), 0 unreached (budget 0); fallback deposit() DIRECT; 5 callsite falsifiers

Fig. 1 — the WETH gates, verbatim (fixtures 2026-08-12, coverage 2026-08-15): eleven oracle-filled fixtures, and the selector coverage gate with its shrink-only budget at zero — every selector, plus the fallback, has an execution witness.

988 B runtime — vs 3,124 deployed solvency at chain altitude 5 registry rows · 2 now agreements

What the port proves

Solvency, preserved at every altitude it can be stated.

The claim of record: the books never outrun the ether. Stor.Solvent is an inequality — every booked balance, summed, plus any ether still in flight, is at most the ETH the account actually holds — and seven theorems carry its preservation from a single message frame to every reachable state of a configured chain. The top of the ladder:

machine-checked
Theorem (chainUsing_preserves_solvent, Blanc/Solvent.lean).

If the WETH account holds the compiled artifact and is solvent at a configured chain state, it holds the artifact and stays solvent at every state reachable from there — whole valid blocks, every transaction anyone sent, arbitrary contracts doing arbitrary things, across scheduled fork activations among the forks the proofs cover.

theorem chainUsing_preserves_solvent (wa : Adr) (cfg : ChainConfig)
    (ch ch' : BlockChain) (h_reach : BlockChain.ReachUsing cfg ch ch')
    (hfork : ∀ t f, cfg.forkAt t = .ok f → CoveredFork f)
    (h_inv : State.Inv wa ch.state) : State.Inv wa ch'.state

Read the quantifiers — State.Inv bundles three facts: the account’s code compiles from the committed weth program, total ETH stays below 2256 wei (the model’s one arithmetic side condition, withdrawal-inclusive at block altitude), and Solvent itself. The sum inside Solvent ranges over all 2160 address-shaped storage keys, not a tracked holder list — so there is no enumeration to maintain and no hash-injectivity clause anywhere in the statement. And mid-frame, the incoming call value is counted on the books’ side of the inequality before the contract has booked it: the invariant holds while a deposit is still in the air, not merely between frames.

Blanc/Solvent.leanthe ladder — all seven in the axiom audit
weth_preserves_solvent                 -- one message frame, reentrancy included
stateTransition_preserves_solvent      -- one transaction
addBlockToChain_preserves_solvent      -- one block, from raw RLP
chain_preserves_solvent                -- every reachable state
stateTransitionUsing_preserves_solvent -- …and the same three again on a
addBlockToChainUsing_preserves_solvent    *configured* chain, whose schedule
chainUsing_preserves_solvent              selects only Prague, Osaka, BPO1, BPO2

Preserved — from where? Who establishes the invariant in the first place?

pushback

Every theorem above is conditional: it consumes State.Inv at the starting state and returns it at the endpoint. An all-zero ledger satisfies the inequality trivially, and every fixture in the suite installs the committed bytes directly — but no committed theorem states a genesis base case for WETH, and no initcode/CREATE deployment theorem exists on the WETH side. Read the family as exactly what it is: preservation, offered without a rooting theorem. The rooting discipline arrived with WETH10, whose invariants stand on a deployment root derived through the real block pipeline — the standard the portfolio has held itself to since.

Does a scheduled hard fork void the claim?

pushback

Not a covered one. The Using rungs are proved over any configured chain whose schedule selects only covered forks — Prague, Osaka, BPO1, and BPO2 — so an activation among them is one more step of the induction, not a new proof. Beneath them, the fork-parametric parents are stated for an arbitrary covered Fork. A fork outside that list is outside the claim: the pinned Jaune already models Amsterdam, and no WETH theorem covers an Amsterdam block. The unqualified rungs are Prague; nothing on this page implies deployability on earlier forks.

Safety is the cheap half. Can anyone actually get ether out?

pushback

The solvency family is deliberately pure safety, and no prose here inflates it. Liveness is earned separately, where it is actually earned: weth_balanceOf_succeeds and weth_decimals_succeeds construct successful executions instruction by instruction — 2,260 gas cold / 260 warm, and 158, as proved equations — and wethGas_le_max bounds those frames. That is also the honest extent: nothing prices transfer or withdraw, and no transaction-altitude exit theorem exists for WETH. The claim class that closes that gap — redemption proved enabled at every reachable future — is WETH10’s redeemability theorem, not this port’s.

Claim class, stated: every theorem above is about the exact compiled Blanc artifact under Jaune’s executable semantics, conditioned on the account holding Prog.compile weth. The statements in Blanc/Solvent.lean are the authority on their own premises — the CI axiom audit pins each theorem’s dependency closure, not its prose — and nothing here verifies the WETH9 deployed at 0xC02a…6Cc2.

How we know it is WETH

Two layers, neither asked to do the other’s job.

“Is WETH” means two checkable things here, and only those: the behavior agrees with the deployed reference on differential evidence a third party can re-run, and the load-bearing properties are theorems about the exact compiled bytes. The tests catch proving the wrong program; the theorems quantify where tests can only sample. Open the layer you want to audit.

▸Differential evidence — the sanity layer11 fixtures · pinned EELS oracle · coverage budget 0

check-weth.sh runs eleven committed fixtures through Jaune’s fixture runner, each with the committed wethCode installed as the WETH account’s code and every expectation filled by the pinned frozen EELS oracle’s t8n — external adjudication, not self-agreement. The cases: the five happy paths (deposit, withdraw, transfer, approve + transferFrom, and an adversarial reentrancy attempt against withdraw), two view probes that make the hand-rolled ABI return encoding externally observable, the balance and allowance guards refusing, and the two deviation-registry claims that are testable at all.

Three hardenings keep the suite honest. The generator computes each case’s WETH-semantic expectation from the pre-state and transaction alone and asserts it against the oracle’s answer before writing the fixture — two implementations agreeing cannot see a contract that is wrong the same way to everyone. A runtime-byte gate requires every fixture’s installed code to be byte-identical to the committed 988-byte Lean literal, so the evidence cannot drift from the contract it is about. And the selector coverage gate refuses credit for a selector merely embedded in bytes: all ten selectors plus the direct fallback carry execution witnesses, against a shrink-only budget that is currently empty.

What this layer is worth is stated with the same care: specification-checked differential testing on chosen inputs — never semantic equivalence, never a proof. The fixtures README is the case-by-case account.

▸Proven functional specifications — the quantified layercompile witness · constructed success · exact gas

The quantified apex of this page — the solvency ladder — is stated in full in the results section above; this layer holds what ties it, and every other statement, to the artifact and its endpoints. wethCode_compile is the compile witness — Prog.compile weth = some wethCode, proved by kernel evaluation (decide +kernel, nothing added to the trusted base). Every theorem above is conditioned on the account code being exactly what the compiler returns, so without this equation they could all hold vacuously.

Beside it sit the constructed-success rows — weth_balanceOf_succeeds in WethLive.lean and weth_decimals_succeeds beside the gas lemmas in WethGas.lean — which build a successful execution instruction by instruction, with exact gas: balanceOf at 2,260 cold and 260 warm, decimals at 158, the 19-gas nonpayable guard visible in the statements, and the wethGas closed forms and maxima as lemmas beside the code they price. What they establish is functional: the returned word is the stored balance, the constant is the constant — the hand-rolled ABI encoding doing, in a proved execution, what the view probes show the oracle expects. All of it sits in the repository-wide axiom audit, each theorem pinned to its exact axiom set in CI.

Deviations

Five rows, each argued — two now agreements.

Deviations are governed, not forbidden: every observable difference from the reference is a registry row with a stance and fixture evidence, and an observable difference with no row is a defect by the project’s own rules. WETH_DEVIATIONS.md is the authority; the five rows are summarised here with their arguments. Two began life as genuine divergences and were later closed to agreements — the registry keeps their history rather than quietly reclassifying them.

Dirty address words are refused, not masked

deviation

WETH9’s Solidity 0.4.x decoder silently ignores nonzero upper bits in an address word, so a noncanonical alias resolves to the low 160-bit address. Blanc’s mutating paths reject such words outright; the two views use the full word as key material, neither masking nor checking.

The argument: no equivalence claim is made — callers are expected to use canonical ABI encoding, and an alias silently resolving is exactly the kind of implicit behavior a proof-oriented artifact should refuse rather than reproduce. Discharged by a dedicated fixture that probes the dirty word in every position.

Balances live at raw address words

deviation

WETH9 stores balances in a Solidity mapping, at hash-derived slots. Blanc uses the raw 256-bit address word itself as the storage slot.

The argument: a deliberately simple, proof-oriented representation — storage proofs read off directly, with no hashing lemma in the way. No storage-layout compatibility is claimed anywhere; the same account’s balance simply lives at a different slot, and every fixture in the suite asserts storage at exactly these keys.

Allowances fail on hash collision instead of writing through one

deviation

Blanc stores an allowance at keccak256(src ‖ dst). In the exceptional case where that hash is also a valid raw-address balance key, approve and allowance-consuming transferFrom revert. WETH9’s domain-separated mapping layout cannot express the collision, so it has no such branch.

The argument: given the simpler layout, the choice is between refusing the operation and silently writing through a third party’s balance slot — and refusing is a feature. The collision branch is untestable by construction (a ~2⁹⁶ search); the registry says so instead of pretending coverage.

Ether sent to nonpayable entry points

agreement since 2026-08-09

WETH9 rejects nonzero call value on every entry point except the payable fallback and deposit(). Blanc originally dispatched on the selector alone: a value-carrying transfer could succeed, the contract kept the ether, and nobody was credited — a permanently unrecoverable surplus.

The resolution: adjudicated under PORTING.md’s then-standing wager (retired 2026-08-19; its policy note records why), value-rejection was ruled a feature by prevailing norms — its modern beneficiary is the buggy integrator, for whom it converts silent permanent loss into a clean revert. Every entry point now carries the shared nonpayable guard: 888 → 988 bytes, 19 gas per guarded call, every priced gas walk re-derived. A fixture shows the suite would have caught the old behavior: rebuilt against the pre-change artifact, 11 of its 21 expectations fail.

Revert data on a failed guard

agreement since 2026-08-05

The deployed WETH9 runtime contains fourteen REVERT sites and every one is REVERT(0, 0) — empty return data, unused gas handed back. Blanc’s early Func.revert instead reverted over whatever two stack words a guard left live: a garbage-data revert, a stack-underflow halt, or a memory-expansion out-of-gas halt — the latter two burning the frame’s entire gas, which WETH9 never does.

The resolution: Func.revert was normalised to PUSH0 PUSH0 REVERT in the one shared definition, reaching both WETH and FMINT at once — two bytes per revert site, twenty-one sites in the current literal. Against WETH9, which encodes no reason either, the result is agreement; the discriminating fixture evidence lives in the FMINT suite’s falsifier table.

Deltas

Size, and gas by path.

Two registers, kept apart on purpose: proved figures are committed theorems about Blanc’s exact bytes; the measured comparison against deployed WETH9 is a one-off referee’d measurement, labelled with its provenance and its drift.

proved — committed theorems and literals
quantityvaluestatus
runtime size988 Bcommitted literal, byte-checked by every fixture
deployed WETH9 runtime3,124 Bpinned reference lock in the registry
balanceOf gas2,260 / 260cold / warm — proved equations
decimals gas158proved equation
nonpayable guard+19per guarded call, visible in the statements

Closed forms and maxima for the remaining paths ship as lemmas in WethGas.lean, beside the code they price.

measured — one run, 2026-08-03, against deployed WETH9

Referee: the pinned frozen EELS oracle at Prague, harness validated against a committed fixture’s exact block gasUsed. On the money-moving paths — deposit, withdraw, transfer, transferFrom — Blanc is only 1–8% cheaper in execution gas: SSTORE and CALL dominate, and both implementations pay them equally. On the constant-returning views it is 17–22× cheaper — decimals 139 vs 2,444, symbol 145 vs 3,264 — because WETH9 keeps name, symbol, and decimals in constructor-initialised storage (a cold SLOAD each) while Blanc’s compiler bakes them into code as PUSH constants. The raw-address balance scheme is real but small: +126 gas on transfer, +336 on transferFrom. Opcode count per call runs 2–6× lower (transfer: 73 ops vs 273).

Provenance and drift, stated: the harness is not part of the committed gate battery, and the measurement predates two runtime changes — the revert-site normalisation (+22 B) and the nonpayable guard (+100 B, +19 gas per guarded call, decimals 139 → 158). From an EOA, the 21,000-gas intrinsic compresses everything: the best whole-transaction saving was 12.8%. The view advantage pays off contract-to-contract. And the comparison is not equivalence-fair by construction — the registry rows above are part of the gap.

Notes

What the build taught.

a retracted headline

The project once remembered this port as “many times smaller and significantly cheaper.” Measurement kept the first half — 3.2× smaller holds — and retracted the second: blanket gas superiority does not exist, because the expensive opcodes are the same for everyone. The honest residue is specific — views win through code constants, storage schemes buy a little, size and opcode count are the robust wins — and the site’s copy follows the measurement, not the memory.

the registry as changelog

Two of the five rows are agreements that used to be divergences, each closed by a change the registry forced into the open: the revert-site normalisation (found while building FMINT, and reaching WETH through the one shared definition) and the nonpayable guard (adjudicated under the then-standing porting wager, priced at 100 bytes and 19 gas). A port that keeps its deviation history is telling you how it converges — and the sibling-module discipline is why one fix landed in two contracts at once.

Boundary, restated: the suite is differential testing on chosen inputs, not equivalence; the solvency family is pure safety, with liveness earned separately by the constructed-success rows; and nothing on this page verifies the contract deployed at 0xC02a…6Cc2 — it is the reference, never the subject.