TermMax Single-Segment Buy-XT Reserve Integrity
When a user swaps debt tokens for XT, the reserve goes down by exactly what the pricing curve says it should.
s: the contract state (storage) at the time of the swap.virtualXtReserve / virtualXtReserve′: XT reserve before / after.singleSegmentBuyXtTokenAmtOut(...): how many XT the curve says the user gets. Modeled in Lean from the Solidity source.
TermMax (by Term Structure) is a fixed-rate lending protocol. Users trade between debt tokens and XT (extended tokens) through on-chain order books, where pricing is governed by a bonding curve parameterized by liquidity, a time-dependent interest factor, and configurable fee ratios.
The TermMaxOrderV2 contract's swapExactTokenToToken function is the entry point for exact-input swaps. This case study isolates the debtToken → XT path on a single curve segment: the path a user takes when borrowing against one active curve cut. The proof tracks the modeled call stack from the swap entry point down through the curve library and confirms that the reserve update matches the curve computation exactly inside that model.
Why this matters
The virtualXtReserve is the accounting variable that tracks how much XT liquidity remains in the order. Every subsequent swap prices off this value. If the reserve update diverges from the curve computation, even by one wei, all future swaps on that order will price off an incorrect base, and the order's implied interest rate will drift from its configured curve.
What this covers
The modeled call stack follows the debtToken → XT branch of swapExactTokenToToken through every internal helper down to the curve computation:
swapExactTokenToToken→_swapAndUpdateReserves→_buyXt→_buyToken→_buyXtStep→TermMaxCurve.buyXt→cutsReverseIter→calcIntervalPropsMathLib.plusInt256(signed-integer addition on uint256) is also modeled.- The
nonReentrantmodifier is included. The reentrancy lock is modeled as a separate storage slot.
Out of scope: other token-pair branches of swapExactTokenToToken, access control (onlyBorrowingIsAllowed), token transfers, rebalancing, events, and multi-cut curve iteration.
How this was modeled
The benchmark extracts eight Solidity functions across three source files (TermMaxOrderV2.sol, TermMaxCurve.sol, MathLib.sol) into a single Verity contract block. Each Solidity helper is kept as a separate Verity function to preserve the call structure of the original code.
TermMax's curve can have multiple cuts, each with its own liquidity parameters. This model covers the case where there is exactly one active cut at index 0 and that cut has xtReserve = 0. That means the Solidity loop that iterates over cuts runs exactly once, and if the swap needs more liquidity than that single cut provides, the model reverts with InsufficientLiquidity().
Simplifications
The model preserves the reserve-update logic for this execution path, with deliberate simplifications. The full details are documented in Contract.lean.
- Single active curve cut only. The Solidity contract supports multiple curve cuts and iterates over them. This model covers exactly one active cut at index 0, with
cuts[0].xtReserve = 0. Multi-cut swaps are not covered. - One swap direction. Only the
debtToken → XTpath is modeled. The other token-pair branches ofswapExactTokenToTokenare not included. - Access control omitted. The
onlyBorrowingIsAllowedmodifier does not touch the reserve, so it is left out without weakening the guarantee. - Post-swap side effects omitted. Token transfers, rebalancing, and events all happen after the reserve write and are guarded by
nonReentrant, so they cannot affect the proven property. - Checked arithmetic abstracted. Solidity 0.8 checked arithmetic in
plusInt256is modeled with Verity's unsigned arithmetic operations. This proof verifies the reserve update inside that arithmetic model, not Solidity's overflow-revert behavior.
A few cosmetic differences also exist: struct fields are passed as individual parameters instead of structs, and library functions are inlined as helpers. These change the shape of the code but not its logic.
Verify it yourself
git clone https://github.com/lfglabs-dev/verity-benchmark cd verity-benchmark lake build Benchmark.Cases.TermMax.OrderV2BuyXtSingleSegment.Compile
If the build succeeds, the proofs are correct. Source repository
Proof status
This case has one headline theorem in Lean 4. The corresponding proof file in the Verity benchmark repository is sorry-free, so the claim below is accepted by Lean's kernel rather than by informal review.
| Guarantee | Status |
|---|---|
| virtualXtReserve′ = virtualXtReserve − curveOutput | proven |
Assumptions
The proof uses zero axioms. The five hypotheses below are preconditions for successful execution. Each one mirrors an explicit check or implicit requirement in the Solidity source.
hNonZeroInputdebtTokenAmtIn ≠ 0swapExactTokenToToken gates the body with if (tokenAmtIn != 0)
The Solidity entry point skips the swap body when the input is zero. The model covers the non-trivial execution path.
hLockOpennonReentrancyLock = 0OpenZeppelin ReentrancyGuard slot
The function uses a
nonReentrantmodifier. The lock must be open (not already held by a prior call) for the swap to execute.hVXtNonZeroplusInt256(virtualXtReserve, cutOffset) ≠ 0calcIntervalProps divides by vXtReserve
The curve formula divides the liquidity squared by the virtual XT reserve (after applying the curve offset). A zero virtual reserve would cause a division-by-zero revert.
hNoCrosscurveOutput ≤ virtualXtReservecutsReverseIter single-cut liquidity check
The computed XT output cannot exceed the available reserve. In the Solidity source, exceeding this triggers
revert InsufficientLiquidity().hMinOuttokenAmtOut + debtTokenAmtIn ≥ minTokenOut_buyToken slippage check
The net output (XT received plus debt tokens returned) must meet the caller's minimum. In the Solidity source, failing this triggers
revert UnexpectedAmount(minTokenOut, netOut).
Learn more
Upstream Solidity contracts: TermMaxOrderV2.sol, TermMaxCurve.sol, MathLib.sol.
Full case directory in the Verity benchmark repository.
Verity benchmark repository, which contains the formal specification, machine-checked proofs, and the broader benchmark suite this case belongs to.
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.