# market.apr is calculated incorrectly for every swap's execution

TermMaxCurve computes post-swap APR from old virtual reserves minus delta instead of recomputing virtual reserves from updated real reserves, corrupting market APR on every swap.

- Payout: Contest reward
- Severity: high
- Program: TermMax
- Credited: Drastic Watermelon
- Disclosed: 2025-03-25
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_term_max_december2024.pdf#3.1.6
- Library page: https://bugbountylibrary.com/r/cantina%3Aterm_max_december2024%3A3.1.6
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:high tech:evm tech:solidity technique:source-review vuln:business-logic vuln:oracle-manipulation

**Target:** TermMaxCurve AMM swap functions (buyFt/buyXt/sellFt/sellXt and neg variants)
**Root cause:** New APR is computed as calcVirtualReserve(oldReserve) ± amount instead of calcVirtualReserve(newRealReserve), so virtual reserves fed to calcApr are wrong after every swap.
**Steps:**
1. Read AMM spec: virtual reserves derive from real reserves via calcVirtualFtReserve/calcVirtualXtReserve.
2. Inspect TermMaxCurve.buyFt: newApr = calcApr(..., virtualFtReserve - deltaFt, virtualXtReserve + sigmaXt).
3. Note newFtReserve/newXtReserve are computed but not used for virtual reserve recalculation.
4. Write Foundry test comparing current buyFt APR vs modifiedBuyFt that recomputes virtual reserves from new real reserves.
5. Assert modifiedApr > currentApr, confirming APR underestimation.
**How found:** Cross-referenced AMM documentation equations (Eq. S-6/S-7, D-4, D-6) against TermMaxCurve source; noticed calcApr received old-virtual-reserve arithmetic rather than virtual reserves derived from post-swap real reserves.
**Insight:** When a protocol documents virtual-reserve formulas, verify every downstream computation re-derives virtual reserves from updated real reserves rather than doing linear arithmetic on the old virtual values.
