Cork Protocol Pool Solvency
After an unwind, locked collateral still covers the outstanding swap share supply.
The left side converts collateral to 18-decimal units (e.g. for USDC with 6 decimals, colScaleUp = 1012). The right side is the outstanding cST shares held by users (total supply minus what the pool holds). The inequality says: there is enough collateral to back every outstanding share.
Cork Protocol is a risk layer for DeFi assets like stablecoins, LSTs, and yield-bearing tokens. Each Cork pool pairs a collateral asset (CA) with a reference asset (REF), an existing on-chain token (e.g. a stablecoin or LST) whose peg the pool protects against. Depositors provide CA and receive two tokens: a swap token (cST) granting the right to exchange REF for CA before expiry, and a principal token (cPT) representing the depositor's claim on whatever collateral remains after all swaps are settled. In the happy path (no depeg), cPT holders redeem their full deposit; in a depeg, cST holders exercise the swap to claim collateral, and cPT holders absorb the loss.
unwindExerciseOther lets a user reverse a swap position. The user provides reference assets; the pool computes how many cST shares to release and how much additional collateral to lock. Certora's formal verification of Cork Phoenix defined a solvency invariant called P-02: after any state-changing operation, locked collateral must still cover all outstanding cST claims. They verified P-02 for every other CorkPool function but timed out on this one. This proof closes that gap.
Why this matters
An unwind reverses a swap position: the user sends reference assets back, and the pool returns cST shares to its own balance (reducing outstanding claims) while locking additional collateral. If solvency broke on this code path, the collateral locked could fall short of the remaining outstanding cST, meaning holders who bought depeg protection could find the pool unable to pay out when they exercise their swap.
The function chains three rounding operations across different decimal scales. The proof covers the full parameter space: arbitrary exchange rates and arbitrary decimal combinations.
What this covers
This case study isolates the solvency-critical arithmetic of unwindExerciseOther from Cork Phoenix v1.1.2. The modeled call path includes:
calculateEqualSwapAmount(floor-rounded share computation)tokenNativeDecimalsToFixedandnormalizeDecimalsWithCeilDiv(decimal scaling)mulDivCeil(ceiling-rounded collateral computation)- The
requirecheck for sufficient liquidity
Collateral and swap shares use different decimal precisions (e.g. USDC has 6 decimals, swap shares have 18). The invariant compares them on the same scale by multiplying collateral by colScaleUp (= 10(18 - decimals)).
Out of scope: external token transfers, oracle rate updates, the time-decay fee pipeline, access control, reentrancy guards, and event emissions. These are orthogonal to the solvency arithmetic.
How this was modeled / proven
The model is built directly from the Solidity source code of Cork Phoenix v1.1.2 (commit 40d9b173c4b), not from Certora's specification. The call path crosses four Solidity files: PoolLib.sol, MathHelper.sol, TransferHelper.sol, and CorkPoolManager.sol. These are inlined into a single Lean file (Contract.lean) since the proof only needs the arithmetic, not the contract boundaries.
When Cork processes an unwind, it computes two things: how many shares to release (using floor rounding, which rounds down) and how much collateral to lock (using ceiling rounding, which rounds up). Both roundings favor the pool: it locks slightly more collateral than strictly needed and releases slightly fewer shares than strictly needed.
The core lemma double_ceilDiv_sandwich proves that this asymmetry is enough to guarantee solvency for any exchange rate and any decimal combination. It says: the ceiling-rounded collateral always covers the floor-rounded shares.
Simplifications
- External calls elided. Token transfers and oracle rate updates happen outside the solvency arithmetic. The model only captures the math that changes the pool's internal state (locked collateral and swap balances).
- Time-decay fee factored out. In the real Solidity, the pool adds gross collateral then subtracts a fee. We model the net result directly (
+= assetsInWithoutFee), which is mathematically identical but skips the fee calculation pipeline. - Access control and events omitted. Reentrancy guards, role checks, and event emissions do not affect the solvency arithmetic.
Verify it yourself
git clone https://github.com/lfglabs-dev/verity-benchmark cd verity-benchmark lake build Benchmark.Cases.Cork.PoolSolvency.Compile
If the build succeeds, the proofs are correct. Source repository
Proof status
The Cork case has one main theorem and two supporting results. All are fully proven with no axioms and no sorry.
| Theorem | Status |
|---|---|
| solvency_preserved | proven |
| unwind_slot_write | proven |
| double_ceilDiv_sandwich | proven |
Assumptions / hypotheses
The proof uses zero axioms. The hypotheses below are preconditions on the contract state before the call. Each mirrors a constraint enforced by Cork's pool creation or by ERC-20 accounting.
hSolvencyBeforesolvency holds before the callP-02 invariant (inductive step)
The theorem is an inductive step: if solvency held before
unwindExerciseOther, it still holds after. The base case is pool creation, where both collateral and outstanding shares start at zero.hColScale, hRefScale, hSwapRate, hRefOutcolScaleUp, refScaleUp, swapRate, referenceAssetsOut > 0Pool creation + oracle + non-trivial call
All scale factors, the exchange rate, and the unwind amount must be positive. Scale factors are 10(18 - decimals) (positive for any token with ≤ 18 decimals); the rate comes from the oracle; a zero-amount unwind is a no-op. In Lean's natural number type there is no “unsigned nonzero”, so these are stated individually as > 0.
hNoOvf1 .. hNoOvf5intermediate uint256 arithmetic stays in rangeOverflow preconditions
Five hypotheses guard that products and sums during the unwind do not exceed 2256. In practice these hold whenever token supplies and rates stay below ~2128 (well within any realistic ERC-20 supply). They could be derived from a single weaker bound on the inputs, but we state them per-operation to match the Solidity exactly.
hSupplyGeBalswapTotalSupply ≥ swapBalanceOfPoolERC-20 invariant
Total supply of swap shares is at least the pool's own balance. This means outstanding shares (supply minus pool balance) is non-negative. Without this, the right side of the solvency inequality would underflow.
How this relates to Certora's audit
Certora verified P-02 solvency across 13 CorkPool functions using bounded model checking (SMT solver). Their prover timed out on unwindExerciseOther because the chained rounding produces non-linear arithmetic the solver cannot discharge within its search depth.
We took a different route: manual translation of the arithmetic into Lean 4, then a step-by-step proof that the ceiling/floor asymmetry preserves solvency for all inputs. No search bound, no timeout: the proof is checked by Lean's kernel.
Produced as part of the Verity benchmark, an open database of formally specified contract properties.
Learn more
Upstream Solidity contracts: PoolLib.sol, MathHelper.sol, TransferHelper.sol, CorkPoolManager.sol.
Full case directory in the Verity benchmark repository.
What is a formal proof? A short explanation for non-specialists.
More research
Alchemix V3 Earmark Conservation
Formally verified earmark conservation invariant for Alchemix V3's lazy-accrual debt accounting system.

Reserve DTF Auction Price Band Invariant
Formally verified per-pair price band invariant for the auction pricing path in Reserve DTF Protocol.
TermMax Single-Segment Buy-XT Reserve Integrity
Formally verified reserve-update integrity of the single-segment debtToken-to-XT swap path.
