WOOFi V2 sPMM Near-Zero WOO Price / Oracle-Manipulation Drain (2024-03-05)

  • Name: WOOFi V2 sPMM near-zero WOO price / oracle-manipulation drain
  • Date: 2024-03-05
  • Protocol: WOOFi Swap V2
  • Chain: Arbitrum
  • Category: protocol-authored market-state oracle failure / internal-price override bug / flash-loan drain / low-liquidity house-token manipulation
  • Loss: about $8.5M-$8.75M
  • Summary: WOOFi did not mainly lose funds because Chainlink published a bad price or because an external AMM price was read too naively. It lost funds because WooPPV2’s synthetic proactive market maker (sPMM) could override WOO’s oracle price from live trade size, and one oversized WOO -> USDC swap drove that internally authored price down to 9 with 8 decimals of precision, or about $0.00000009. Because the Chainlink fallback check was not configured for WOO on Arbitrum, WooPPV2 accepted that near-zero internal price as valid. The attacker then used less than 1 USDC to buy back roughly 10M WOO, repaid the flash-loaned WOO at almost no cost, and repeated the cycle three times for about $8.5M-$8.75M of profit.
  • Trigger / entry point:
    • WOOFi V2’s WooPPV2 contract on Arbitrum (0xeFF23B4bE1091b53205E35f3AfCD9C7182bf3062) let the sPMM override oracle price according to trade notional in order to simulate order-book depth and slippage.
    • The attacker could source a protocol-scale amount of WOO on Arbitrum because a recent WOO lending market existed there alongside flash-loanable WOO and USDC liquidity.
    • The decisive pricing seam sat in _calcQuoteAmountSellBase(), whose newPrice path scaled the current price by a term of the form 1 - 2*k*price*baseAmount.
    • For WOO on Arbitrum, the expected Chainlink sanity check did not cover the token, so the extreme internally computed price was accepted instead of rejected.
  • Root cause:
    • WOOFi published its own dangerously manipulable internal price through the sPMM override path rather than merely consuming an external venue price.
    • The newPrice formula could be pushed arbitrarily close to zero by choosing a sufficiently large baseAmount while still staying just above underflow.
    • The fallback check that should have bounded that internal price against Chainlink did not protect WOO on Arbitrum because the WOO Chainlink source was effectively unset.
    • Arbitrum’s combination of low WOO spot liquidity and newly available WOO borrowing made the manipulation economically feasible.
  • Exploit path:
    1. The attacker used address 0x9961190b258897bca7a12b8f37f415e689d281c4 and helper contract 0xD4c633C9A765bC690E1FbA566981c1F4eab52dF0 to stage the attack.
    2. In representative exploit tx 0x40e1b8c78083fc666cb7598efcecd0ae0af313fc41441386e4db716c2808ce07, they flash-borrowed about 10.59M USDC from the Uniswap V3 USDC/WETH pool 0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443 and flash-borrowed the Trader Joe WOO-side reserves from 0xB87495219C432fc85161e4283DfF131692A528BD.
    3. They deposited about 7M USDC into the Silo market 0x5C2B80214c1961dB06f69DD4128BcfFc6423d44F and borrowed additional WOO, bringing attack inventory to roughly 7.7M WOO.
    4. They executed four swaps through WooPPV2: about 2,000,000 USDC -> WETH, then 100,000 USDC -> WOO, then about 7,856,868.8 WOO -> USDC, then a final tiny USDC -> WOO buy after the price collapse.
    5. The first two swaps increased WOOFi’s USDC-side reserves enough that the giant WOO -> USDC trade would not revert for insufficient quote liquidity. That third swap drove WOOFi’s own WOO price from about 0.56608180 to 0.00000009 dollars in oracle precision terms.
    6. With WOO now priced near zero inside WOOFi, the attacker spent only about 0.926342 USDC in the fourth swap to buy back roughly 10.23M WOO.
    7. They repaid the Trader Joe flash loan, repaid the Silo WOO borrow, withdrew the posted USDC, converted excess USDC into WETH, and exited with profit. The same pattern was repeated three times before WOOFi paused the contracts.
  • What the attacker needed to be true:
    • They needed access to enough WOO on Arbitrum to choose a baseAmount that pushed the sPMM newPrice term close to zero without causing arithmetic underflow.
    • WOOFi needed enough quote-side reserves that the giant WOO -> USDC leg could settle instead of reverting.
    • The WOO Chainlink sanity source had to be missing or inactive so the near-zero internal price would not be rejected.
    • The protocol had to let the manipulated internal WOO price become immediately spendable truth for the very next swap.
  • Technical anatomy worth remembering:
    • This is not best understood as ordinary external market moved, protocol followed. The most important price in the exploit was authored inside WOOFi by the sPMM override path itself.
    • The attack had a setup leg and a cash-out leg. The setup leg increased WOOFi’s USDC reserves and accumulated enough WOO inventory to make the decisive WOO -> USDC sale feasible. The cash-out leg was the nearly free USDC -> WOO repurchase after WOOFi had already published the near-zero internal WOO price.
    • The exploit therefore belongs partly in oracle-manipulation but more sharply in protocol-authored-market-state-oracle-failure. WOOFi’s own local trade-impact formula became reserve-spending price truth.
    • The Arbitrum-only economic precondition mattered. WOOFi’s own postmortem said no other deployment combined both a WOO lending market and enough local WOO liquidity to replay the same exploit.
  • Representative transactions and addresses:
    • Primary attacker EOA: 0x9961190b258897bca7a12b8f37f415e689d281c4
    • Attack helper contract: 0xD4c633C9A765bC690E1FbA566981c1F4eab52dF0
    • Representative exploit tx: 0x40e1b8c78083fc666cb7598efcecd0ae0af313fc41441386e4db716c2808ce07
    • Vulnerable WOOFi pool / router contract: 0xeFF23B4bE1091b53205E35f3AfCD9C7182bf3062
    • WOO oracle contract cited in public PoCs: 0x73504eaCB100c7576146618DC306c97454CB3620
    • WOO token on Arbitrum: 0xcAFcD85D8ca7Ad1e1C6F82F651fA15E33AEfD07b
    • Silo market used for WOO borrow: 0x5C2B80214c1961dB06f69DD4128BcfFc6423d44F
    • Uniswap V3 USDC/WETH flash-loan pool: 0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443
    • Trader Joe flash-loan pool used in public PoCs: 0xB87495219C432fc85161e4283DfF131692A528BD
    • Recipient address for stolen funds cited by REKT: 0xb59d04d9957c9e266dff5c4173d4d2324eb029ad
  • Response / mitigation:
    • WOOFi and outside monitors identified the abnormal swaps quickly and paused the affected contracts by about 16:02 UTC, roughly 13 minutes after the first exploit sequence.
    • The team sent an onchain message offering a 10% whitehat bounty and opened broader recovery efforts.
    • WOOFi’s public postmortem framed the core fixes around repairing the sPMM pricing error, adding additional audits, and preventing the fallback-check gap that left WOO unprotected on Arbitrum.
    • The team also emphasized that other WOOFi products were unaffected and that Arbitrum was the only deployment where the exact economic setup existed.

Pattern tags

  • Internal-price publication boundary failure: the decisive mark was not an imported venue price but WooPPV2’s own sPMM-authored newPrice, which the protocol immediately reused as reserve-spending truth.
  • Missing independent sanity-bound activation: WOO’s Arbitrum deployment still behaved as if an external Chainlink guardrail existed even though the WOO sanity source was unset there.
  • Same-window publish-and-spend collapse: one oversized WOO -> USDC trade could both write the near-zero local WOO mark and make that mark spendable on the very next USDC -> WOO swap.
  • House-token inventory monetized local quote failure: protocol-proximate WOO borrowability plus thin local liquidity turned WOO itself into the attack inventory that defeated WOOFi’s own slippage model.

Why the exploit worked

  • WOOFi let execution-path microstructure become valuation truth. The sPMM was supposed to shape swap pricing from local order-book-style depth assumptions, but on Arbitrum one attacker-sized WOO -> USDC trade could push that internal mark all the way down to a near-zero price that later consumers still treated as canonical.
  • The missing WOO Chainlink sanity bound mattered as much as the bad math. WOOFi’s own postmortem says the fallback protection did not cover WOO on Arbitrum, so the protocol had no actually independent object forcing internal execution price to stay inside a credible valuation range.
  • The exploit was a two-leg publication / monetization loop, not just one bad trade. The attacker first accumulated enough WOO and enough quote-side liquidity to make the catastrophic sale feasible, then used the freshly published near-zero WOO mark to buy back protocol-scale WOO for less than 1 USDC before the system could recover.
  • More sharply: WOOFi collapsed local trade-impact state, published internal price, and reserve-paying inventory price into one same-window authority chain. Once those three objects were joined, one manipulated local print could cash out real USDC inventory immediately.
  • The Arbitrum-specific economic setup preserved the operational lesson too. A self-authored pricing bug may exist on several deployments, but it only becomes drainable where the protocol also made enough house-token inventory and quote-side reserves locally reachable.

Detection and audit pivots

  • Trace every path where an internal execution model writes a price later reused by reserve-paying consumers. Ask whether trade-impact state, quoted execution state, and valuation state safe to spend inventory against are still one storage object.
  • Inventory chain-by-chain sanity bounds instead of assuming one global oracle story. WOOFi’s bug only monetized on Arbitrum because WOO lacked a live independent bound there even though the broader design expected one.
  • Stress-test oversized local trade -> internal mark collapse -> immediate reverse trade / withdrawal loops, not only ordinary external-price manipulation. If the reverse leg still pays before a delayed check or bound runs, publication and spend are too tightly coupled.
  • Review how much house-token inventory an attacker can source locally through lending markets, flash loans, or bridgeable liquidity. A protocol-authored price bug becomes much more dangerous when the protocol’s own token is cheap to borrow at attack size.
  • Ask what degraded mode applies when the supposedly independent bound is absent, stale, or unset. A healthy answer is usually halt or sharply narrow authority, not keep using the internal model anyway.

Failure-prevention checklist

  • Separate execution slippage model from valuation state allowed to spend reserves against. Local sPMM depth math can help quote trades, but one attacker-sized fill should not directly rewrite the canonical price that the next swap, borrow, or withdrawal path honors as money-good inventory truth.
  • Require a live independent sanity bound per asset and per deployment. If a Chainlink or other external bound is missing, stale, or unset for one token on one chain, the affected pair should fail closed or enter a sharply narrowed mode instead of silently continuing on internal math alone.
  • Put hard floors and deviation guards around internal repricing paths. If one trade would push a locally authored mark from ordinary market range to effectively zero, the trade should revert, clamp to a bounded range, or force a pause before that mark becomes readable by reserve-paying logic.
  • Break same-window publication and monetization loops. A newly collapsed internal mark should not become immediately usable for the reverse-side buyback in the same transaction or same block without delayed activation, inventory quarantine, or a second independent valuation check.
  • Review house-token listings and borrowability as security changes, not just growth changes. Adding a lending market, deep flash-loan source, or bridge inventory for the protocol’s own token can turn a theoretical internal-price bug into a drainable production path.
  • Bound reserve outflow even if one bad local mark lands: per-asset trade caps, per-block quote-asset limits, route-level circuit breakers, and rapid auto-pauses reduce the distance between wrong price published and treasury-sized loss.
  • Keep chain-specific quote liquidity and token inventory in the threat model. WOOFi’s own postmortem stressed that Arbitrum’s combination of local WOO borrowing and available quote reserves made the exploit feasible where other deployments were not.
  • Test pathological-but-reachable local price moves directly. The important regression is not only oracle feed wrong; it is attacker can source enough base inventory to drive our internal price formula to a near-zero or otherwise nonsensical state while reserves are still available to pay out.

Audit prompts

  • Which variables in the sPMM path are merely execution helpers, and which ones become canonical price truth for subsequent reserve-paying swaps? Are those two roles still sharing one storage object or one same-call computation?
  • For each asset on each chain, what exact independent sanity source bounds the local price, and what concrete runtime behavior occurs if that bound is absent, stale, paused, or unset?
  • Can one attacker-sized sell base leg both deepen quote reserves enough to avoid revert and also drive the locally authored newPrice low enough that the next buy base leg becomes almost free?
  • What is the maximum economically reachable baseAmount under live flash-loan, lending, and bridge inventory conditions, and how far can that amount push the internal price formula before a clamp, revert, or circuit breaker fires?
  • If the protocol’s own token is borrowable elsewhere on the same chain, how does that change the threat model for every internal slippage or local-mark formula that references that token?
  • Does the venue ever treat internal mark published and independent mark validated as equivalent events, or can local quote logic keep operating with full authority when the independent bound disappears?
  • What exact per-asset or per-route caps limit quote-asset outflow if one internally authored price becomes absurd but remains syntactically valid for even one block?
  • Are monitoring and pause systems looking for impossible local repricing events such as major asset price to near zero, one trade consuming abnormal quote inventory, or reverse-leg buyback at dust cost, or are they only watching external oracle freshness?

Lessons that should already be standard

  • Internal pricing models are oracle surfaces. If a protocol writes a local mark and later spends reserves against it, that model deserves the same adversarial review as any external feed adapter.
  • Fallback exists in the architecture is meaningless unless it is live for the exact asset and exact deployment under attack. An unset guardrail is not a weaker oracle; it is no oracle.
  • Same-window publish-and-spend is a design choice. Once one trade can both write a catastrophic local price and let the next trade monetize it immediately, the protocol has collapsed observation, valuation, and reserve authority into one exploitable moment.
  • House-token growth changes can reopen old math risk. A new lending market or flash-loan source for the protocol’s own token can transform a previously impractical price-formula bug into a protocol-scale drain.
  • Reserve sufficiency can be part of the exploit setup, not just the payout. WOOFi’s attacker first made sure the pool had enough USDC-side inventory to honor the giant WOO -> USDC leg before cashing out the near-zero reverse price.
  • A protocol-authored mark should have a degraded mode. If the independent bound disappears, the safe answer is to narrow authority, shrink caps, or halt, not to keep treating local slippage math as global truth.
  • Novel microstructure is not free security budget. A sophisticated market-making formula can still fail in a very primitive way if one attacker-sized trade can drive it outside its intended economic range.

Why this incident matters

  • WOOFi is a clean example of a protocol writing its own catastrophic price truth rather than merely importing one.
  • It preserves an important oracle-manipulation cousin where the exploitable state was not thin external venue alone, but internal price-override logic plus missing guardrail.
  • It also shows how a house token can become the attack inventory that defeats the protocol’s own slippage model once new lending rails make enough size borrowable.
  • It is also a good positive-design handoff into internal-mark publication quarantine and reserve-spend separation, safe-read oracle interfaces, delayed snapshots and same-block quarantine, and oracle independence inventory and degraded-mode discipline. The missing healthy boundary was not just use Chainlink; it was do not let one attacker-sized local trade become immediately spendable price truth unless an actually independent bound is live, and otherwise force consumers onto a delayed or fail-closed valuation object.

Internal linkages

Control surface

  • The decisive leverage sat in WooPPV2’s internal price-publication logic, the absence of a live Chainlink safety bound for WOO on Arbitrum, and the economic availability of protocol-scale WOO inventory through flash loans plus Silo borrowing.
  • WOOFi did not just trust a manipulable market. It transformed one attacker-sized trade into its own canonical near-zero WOO price and immediately sold real inventory against that fiction.
  • The healthier control object would have separated local sPMM state used for execution from valuation state safe to spend reserves against, and would have narrowed or halted authority when the supposedly independent WOO sanity check was unavailable.

Sources

Last reviewed

  • 2026-07-24 UTC