Wildcat Borrow Liquidity Invariant
A successful positive borrow in Wildcat V2 removes exactly the borrowed amount and still leaves enough assets to satisfy the updated liquidity requirement used by the borrow guard.
s is the market state before the borrow, s′ is the state after executing borrow(amount). The statement holds for all positive amounts and all states satisfying the assumptions listed below.
Wildcat V2 is a credit-market protocol. A Wildcat market is a lending market where lenders deposit assets, the borrower can draw down liquidity, and the contract still needs to preserve enough assets to satisfy lender obligations and protocol fees.
The relevant guard lives on the successful path of borrow(uint256 amount). Before the borrow is allowed, Wildcat computes a liquidity requirement from the current market state and only lets the borrower remove assets that remain above that threshold.
Why this matters
The failure mode here is economic, not cosmetic. If the borrow guard overestimates borrowable assets or underestimates required liquidity, the borrower can pull funds out of the market that should have remained available for lender withdrawals or protocol fees.
A borrow that succeeds but leaves the market below the required liquidity threshold would mean the reserve logic failed exactly where it matters: on the path that actually transfers assets out of the contract.
What this invariant covers
This case study is a focused protocol slice around the successful path of borrow(uint256 amount) in Wildcat V2. The theorem is for successful positive borrows only, so amount = 0 is excluded by the theorem statement. The case reasons about the updated state used by the borrow guard and keeps borrow, _getUpdatedState, liquidityRequired, and borrowableAssets in scope.
The required liquidity in this benchmark includes:
- the reserve-ratio-backed portion of non-pending supply
- 100% of pending withdrawals
- 100% of normalized unclaimed withdrawals
- updated accrued protocol fees
Out of scope are the full withdrawal-batch expiry and payment pipeline, detailed hook internals, sanctions implementation details, ERC20 transfer mechanics, and packed storage encoding parity.
The result is intentionally narrow: it says that once the borrow guard accepts a positive borrow on the already-updated state, the remaining assets still cover the requirement computed from that updated state.
How this was modeled / proven
The model isolates the successful borrow path from Wildcat V2 tag v2.0.0 at commit a70f297fbd1b1ab597e0e9a3458a2d13a34b4657. In the benchmark, _getUpdatedState() is compressed to a precomputed protocol-fee delta applied before the borrow check, and the pending-withdrawal and normalized-unclaimed-withdrawal fields are treated as already current borrow-check inputs.
totalAssets is modeled as a trusted storage mirror of the contract's asset balance. Sanctions, hooks, and the final transfer remain structurally visible in the model but are treated as trusted boundaries. Wildcat's packed state is flattened into ordinary storage slots so the proof can reason directly about the semantic fields instead of packed-slot encoding details.
The full safety argument is proved in Lean 4 with no axioms. Concretely, the main theorem shows that if amount <= borrowableAssetsAfterUpdate, then subtracting amount from totalAssets still leaves at least the required liquidity. The mechanical post-state write characterization of the successful borrow path is also fully proven.
Verify it yourself
git clone https://github.com/lfglabs-dev/verity-benchmark cd verity-benchmark lake build Benchmark.Cases.Wildcat.BorrowLiquiditySafety.Compile
Proof status
The Wildcat case has one main theorem: positive_borrow_preserves_required_liquidity. The case builds successfully and all theorems are fully proven with no remaining axioms.
| Artifact | Status |
|---|---|
| positive_borrow_preserves_required_liquidity | proven |
| totalAssets_after_positive_borrow_ge_required | proven |
| borrow_total_assets_write | proven |
Assumptions / hypotheses
borrowerFlaggedOf s = 0borrower is not sanctionedTrusted sanctions boundary
The case assumes the borrower passes Wildcat's sanctions check. Sanctions implementation details are outside this benchmark slice.
isClosedOf s = 0market is not closedborrow precondition
The successful-path theorem only reasons about states where the market is open and borrowing is still allowed.
hookAllowsBorrowOf s != 0borrow hook succeedsTrusted hook boundary
The borrow hook is an external call to an operator-configured contract whose behavior is unknown to the model. The proof assumes it returns successfully on the path being analyzed.
scaledPendingWithdrawalsOf s ≤ scaledTotalSupplyOf spending withdrawals do not exceed total supplyoutstandingSupply underflow guard
This is the basic accounting side condition needed to make the outstanding-supply subtraction meaningful on the modeled path.
uint256 range conditionschecked fee and liquidity arithmetic stays in rangecompressed overflow preconditions
The checked
uint256intermediate sums and products used in fee accrual and liquidity arithmetic are assumed to stay within range. This includes the fee update, reserve product, normalization product, and the subsequent liquidity sums.amount ≤ borrowableAssetsAfterUpdate sborrow amount respects the updated guardmain arithmetic precondition
The theorem is about successful positive borrows. The borrow amount must be at most the borrowable assets computed from the updated state.
totalAssetsOftrusted balance mirror for totalAssetsmodeling assumption
The model treats
totalAssetsas a trusted storage mirror of the market's actual ERC20 balance instead of re-modelingasset.balanceOf(address(this))._getUpdatedState() compressionfee update is precomputedmodeling assumption
Instead of re-running Wildcat's full state-update pipeline, the model takes the protocol-fee increase as a given input and treats pending and unclaimed withdrawal balances as already up to date at borrow time.
Learn more
Upstream Wildcat V2 source, the production borrow path modeled in this case.
Wildcat benchmark case, plus the contract model, specs and proofs.
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.
Cork Protocol Pool Solvency
Formally verified solvency invariant for the unwindExerciseOther function in Cork Phoenix.