Reproduced Exploit
Hyperhyper: PUT liquidity check ignores the payout token
Chain
Other
Category
untagged
Date
Jan 1970
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: Hyperhyper-security-review_2025-03-30. The historical source/toolchain is unavailable; this entry is documentation only and claims no executable Forge PoC.
Vulnerability classes: vuln/logic
Reproduction: a faithful minimal reproduction of the vulnerable finding — the vulnerable code is reproduced verbatim (marked
@>) with faithful minimal doubles; local deploy, no fork.
Root cause#
OperationalTreasury._checkEnoughLiquidity gates a PUT open on the TOTAL stablecoin value of the pool (availableStable = _getPoolValue(true,false), the USD sum of every listed stablecoin) instead of the balance of the specific payout token pos.buyToken. With poolAmount[USDC]=0 and poolAmount[USDXL]=3500e18 the pool looks solvent (3500 USD), so a USDC PUT with sizeUSD=3500e18 opens (0 + 3500e18 !> 3500e18) even though there is 0 USDC to ever pay it. When the holder exercises the now-$500-in-the-money PUT, _payout runs strg.ledger.state.poolAmount[pos.buyToken] -= pnl = (0 - 500e18) which underflows and reverts, so the position is permanently unexercisable and the holder's rightful 500e18 (USDC) payout is stranded.
// Synthetic, self-contained reproduction of Hyperhyper finding 57739 (H-02):
// "Insufficient liquidity checks for `PUT` may cause exercise failures".
//
// Real audited source (the vulnerable lines are reproduced VERBATIM, the primary
// vulnerable line is marked @> VULN):
Why it's exploitable here#
OperationalTreasury._checkEnoughLiquidity gates a PUT open on the TOTAL stablecoin value of the pool (availableStable = _getPoolValue(true,false), the USD sum of every listed stablecoin) instead of the balance of the specific payout token pos.buyToken. With poolAmount[USDC]=0 and poolAmount[USDXL]=3500e18 the pool looks solvent (3500 USD), so a USDC PUT with sizeUSD=3500e18 opens (0 + 3500e18 !> 3500e18) even though there is 0 USDC to ever pay it. When the holder exercises the now-$500-in-the-money PUT, _payout runs strg.ledger.state.poolAmount[pos.buyToken] -= pnl = (0 - 500e18) which underflows and reverts, so the position is permanently unexercisable and the holder's rightful 500e18 (USDC) payout is stranded.
Attack path#
Marked-line walkthrough (Playground)#
The EVM Playground pins each step to the exact executed source line in 0xce01759b82…:
- L133 — Setup: position ledger id counter: Setup: the treasury tracks option positions in a ledger keyed by an incrementing id.
- L172 — Pool valued as total stablecoin USD: _getPoolValue sums the USD value of every listed stablecoin (here USDXL, 3500 USD), ignoring which token a position pays out in.
- L184 — Liquidity gate checks the wrong balance: Root cause: the open check gates on the pool's TOTAL stablecoin value, not on the balance of the PUT's specific payout token pos.buyToken.
- L190 — USDC PUT opens with zero USDC: A USDC PUT of size 3500 opens because 0 + 3500 is not > 3500 — yet poolAmount[USDC] is 0, so nothing can ever pay it.
- L206 — Holder exercises the in-the-money PUT: Later the holder exercises the now-$500-in-the-money PUT, expecting a 500 USDC payout.
- L213 — Payout path reproduced verbatim: The _payout and _doTransferOut logic here is byte-for-byte the finding's embedded source.
- L227 — Payout debits the empty USDC pool: _payout runs poolAmount[USDC] -= pnl = 0 - 500e18 against a pool that holds no USDC.
- L235 — Underflow bricks the position: That subtraction underflows and reverts, so the PUT is permanently unexercisable and the holder's 500e18 payout is stranded.
PoC#
Registry (Foundry, local deploy — verbatim vulnerable source + harm-asserting test):
cd 57739-h-02-insufficient-liquidity-checks-for-put-may-cause-exercis_exp
forge test -vvv
The browser Playground replays the same synthetic opcode-for-opcode and measures the harm. Both gates are green (registry forge test PASS + Playground _verify-poc VERDICT: PASS).
Sources & further analysis#
Reproductions & code
- No executable Forge reproduction is claimed; the historical source/toolchain was unavailable for this finding.
- AuditVault finding: Hyperhyper-security-review_2025-03-30.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Hyperhyper: PUT liquidity check ignores the payout token".
- Web3Sec X hacked database: search.
- Rekt leaderboard: search.
- Solodit incident search: search.
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.