0VIX vGHST Donation / Oracle Self-Liquidation Exploit (2023-04-28)

  • Name: 0VIX vGHST donation / oracle self-liquidation exploit
  • Date: 2023-04-28
  • Protocol: 0VIX
  • Chain: Polygon PoS
  • Category: donation / share-price manipulation / oracle manipulation / self-liquidation monetization / lending bad-debt extraction
  • Loss: about $2M
  • Summary: 0VIX listed Aavegotchi’s vGHST wrapper through an ovGHST Compound-style market and priced it with a custom VGHSTOracle that simply multiplied the Chainlink GHST / USD feed by vGHST.convertVGHST(1e18). But Aavegotchi’s vGHST wrapper computed convertVGHST() from totalGHST(address(this)) / totalSupply(), so direct GHST transfers into the wrapper increased assets-per-share without minting new vGHST. The attacker used flash loans to build a two-contract position: the main contract borrowed most liquid assets from 0VIX, while a helper contract posted USDC collateral and loop-borrowed vGHST. After directly donating about 1.656M GHST to the vGHST contract, the printed convertVGHST() rate made the helper’s vGHST debt far more expensive on paper. The main contract then liquidated that helper debt, seized oUSDC collateral at the inflated mark, redeemed the ovGHST it had accumulated earlier, unwound the borrowed assets, and exited with roughly $2M.
  • Trigger / entry point:
    • 0VIX did not price ovGHST from an external market oracle alone. Its custom VGHSTOracle read vGHST.convertVGHST(1 ether) from the wrapper contract at 0x51195e21BDaE8722B29919db56d95Ef51FaecA6C and multiplied that live conversion rate by the Chainlink GHST feed.
    • Aavegotchi’s vGHST wrapper itself treated each share as share * totalGHST(address(this)) / totalSupply(). Because totalGHST(address(this)) included GHST sitting in the contract, a raw token transfer into the wrapper increased convertVGHST() immediately even though no new shares were minted.
    • 0VIX accepted that live, attacker-changeable wrapper exchange rate as lending and liquidation truth.
    • The protocol also allowed the same economic actor to control both the underwater borrower and the liquidator through separate contracts.
  • Root cause:
    • 0VIX treated a wrapper’s spot assets / shares exchange rate as a trustworthy oracle even though one actor could increase assets via direct donation.
    • The protocol therefore converted normal wrapper accounting into a printable collateral and debt price.
    • Because liquidation math honored the same manipulated mark, the attacker did not only create false insolvency; they also captured the liquidation bonus themselves.
    • In practice, the system fused three brittle assumptions: that convertVGHST() could not jump violently inside one transaction, that wrapper exchange-rate truth was safe to use as a lending oracle, and that liquidators would be economically independent from the borrower they were rescuing.
  • Exploit path:
    1. In the main exploit transaction 0x10f2c28f5d6cd8d7b56210b4d5e0cece27e45a30808cd3d3443c05d4275bb008, the attacker assembled flash-loaned working capital across Aave v3, Aave v2, and Balancer: about 1.95M GHST, 24.5M USDC, and 6.15M USDT in aggregate.
    2. The main attack contract entered Aavegotchi’s wrapper with about 294,000 GHST, received vGHST, deposited its USDT into 0VIX’s oUSDT market, entered that market in the Comptroller, and borrowed out most of the remaining liquid assets across 0VIX’s live pools.
    3. The attacker then transferred about 24.5M USDC plus its current vGHST balance to a helper contract.
    4. The helper minted oUSDC, entered 0VIX with that USDC collateral, borrowed all available vGHST from the ovGHST market, and then looped ovGHST.mint(vGHSTAmount) plus ovGHST.borrow(vGHSTAmount) about 24 times to build a large leveraged vGHST debt position.
    5. The helper sent the resulting ovGHST cTokens and residual vGHST back to the main attack contract, then borrowed additional USDT and about 720,000 USDC, leaving the helper heavily exposed to any increase in the oracle price of vGHST.
    6. With the two-contract structure in place, the main contract directly transferred about 1,656,000 GHST to the vGHST wrapper contract. No new vGHST shares were minted, but totalGHST(address(this)) rose immediately, so convertVGHST(1e18) and therefore 0VIX’s VGHSTOracle price both jumped.
    7. That price jump made the helper’s borrowed vGHST far more expensive relative to its USDC collateral, pushing the helper into liquidatable territory.
    8. The main attack contract then used the vGHST it controlled to call ovGHST.liquidateBorrow(helper, ...) repeatedly, seized the helper’s oUSDC collateral, and also redeemed the ovGHST position it had accumulated earlier for underlying vGHST.
    9. After capturing the seized USDC and unwinding the redeemed and previously borrowed assets into stronger tokens, the attacker repaid the flash loans, bridged funds toward Ethereum, and netted roughly $2M.
  • What the attacker needed to be true:
    • 0VIX had to read vGHST.convertVGHST() live, with no haircut, donation filter, delayed update, or cap on how much one block could move the wrapper-derived price.
    • Direct GHST transfers into the wrapper had to affect totalGHST(address(this)) and therefore convertVGHST() immediately.
    • 0VIX needed enough remaining liquidity in USDC, USDT, WETH, WBTC, MATICX, stMATIC, and related markets for the first borrowing leg to matter economically.
    • The protocol had to let the same attacker control both the insolvent borrower and the liquidator, so the liquidation incentive stayed inside the attack bundle.
    • Liquidations and cToken transfers had to remain live long enough for the attacker to seize oUSDC collateral and redeem the ovGHST position before operators paused the system.
  • Technical anatomy worth remembering:
    • This was not mainly a thin-spot GHST pump. The decisive print happened inside the wrapper exchange-rate path: VGHSTOracle asked convertVGHST(1e18), and convertVGHST() trusted raw wrapper-held GHST.
    • Some secondary descriptions say the attacker deposited vGHST into the oracle contract; the more precise description is that they donated GHST directly into the vGHST wrapper, and the oracle then honestly read the manipulated wrapper state.
    • 0VIX is a useful bridge case between donation-share-price-manipulation and oracle-manipulation. The oracle itself was not externally spoofed; it truthfully published a wrapper exchange rate the attacker could print.
    • It is also a strong self-liquidation-monetization case: the attacker created the insolvency and then captured the liquidation discount against their own helper account.
    • The exploit shows how a wrapper-share oracle can be dangerous even when the wrapper is non-empty and widely integrated. The key question is not whether convertToAssets works as designed, but whether a lending protocol should honor it as same-block solvency truth.
  • Representative transactions and addresses:
    • Main exploit tx: 0x10f2c28f5d6cd8d7b56210b4d5e0cece27e45a30808cd3d3443c05d4275bb008
    • Attacker address highlighted in public reports: 0x702ef63881b5241ffb412199547bcd0c6910a970
    • Vulnerable vGHST wrapper: 0x51195e21BDaE8722B29919db56d95Ef51FaecA6C
    • 0VIX VGHSTOracle: 0xBEF0a2A4885EA5Df895047F49c202525B73DCc68
    • 0VIX ovGHST market: 0xE053A4014b50666ED388ab8CbB18D5834de0aB12
    • 0VIX oUSDT market: 0x1372c34acC14F1E8644C72Dad82E3a21C211729f
    • 0VIX oUSDC market: 0xEBb865Bf286e6eA8aBf5ac97e1b56A76530F3fBe
    • 0VIX Comptroller / Unitroller: 0x8849f1a0cB6b5D6076aB150546EddEe193754F1C
  • Response / mitigation:
    • 0VIX publicly paused its Polygon PoS and zkEVM markets while investigating, including minting, transfers, and liquidations.
    • Public reporting after the incident described negotiations with the attacker and at least partial return of funds, though the exploit still stands as a material oracle and liquidation-design failure.
    • The immediate technical lesson was to delist or heavily constrain wrapper assets whose exchange rate can move from direct donations or other same-block balance changes.
    • More generally, Compound-style markets should not treat wrapper convertToAssets math as money-good collateral truth unless the integration explicitly hardens against donation-sensitive accounting and attacker-controlled self-liquidation.

Why this incident matters

  • 0VIX preserves a clean wrapper exchange-rate oracle + self-liquidation variant that is distinct from thin-spot pumps like Moola and from non-liquidation donation cases like some early ERC-4626 inflation exploits.
  • It broadens the donation/share-price corpus with an Aavegotchi-specific wrapper case where the dangerous state lived in a custom convertVGHST() function rather than in a canonical ERC-4626 vault.
  • It is also a good reminder that once a manipulable wrapper price is admitted into lending math, liquidation can become the payout rail rather than the defense.
  • The preserved healthy boundary was not only price the wrapper better, but a donation-sensitive wrapper ratio becoming locally readable is still not the same thing as that ratio becoming canonical lending truth and then same-bundle liquidation authority against stronger collateral.

Three wrapper-oracle promotions this incident collapsed

  1. Aavegotchi vGHST wrapper state changed

    • The attacker’s direct donation of about 1.656M GHST into the vGHST wrapper raised totalGHST(address(this)) without minting offsetting vGHST shares, so convertVGHST(1e18) jumped immediately.
    • That only proved the wrapper’s local accounting state had moved. It did not yet mean the new ratio deserved to become lending-solvency truth for 0VIX.
  2. 0VIX promoted that wrapper-local ratio into canonical solvency truth

    • VGHSTOracle multiplied the trusted Chainlink GHST / USD feed by live convertVGHST() output and treated the result as the authoritative ovGHST price for debt valuation and liquidation eligibility.
    • This was the decisive false promotion: a donation-authorable wrapper mark stopped being just Aavegotchi wrapper accounting and became 0VIX's canonical answer about what vGHST debt and collateral were worth.
  3. The attacker spent against that published truth through self-liquidation

    • Once the helper account’s vGHST debt repriced upward, the main account could liquidate it, seize oUSDC collateral at the manipulated mark, redeem accumulated ovGHST, and unwind back into stronger assets.
    • This is where the printable wrapper rate became realized reserve loss. The manipulated mark did not merely distort paper health factors; it inherited immediate liquidation and redemption authority against live 0VIX liquidity.

Pattern tags:

Why the exploit worked

  • 0VIX’s custom VGHSTOracle multiplied a trusted GHST feed by a live wrapper conversion function that could be moved with a direct donation into vGHST.
  • A raw GHST transfer increased vGHST’s numerator-side backing without minting new shares, so the oracle faithfully published attacker-authored wrapper state.
  • The two-contract position let the attacker create the unhealthy borrower themselves, then capture the liquidation discount on the other side instead of leaving that value for an independent keeper.
  • 0VIX treated the manipulated wrapper rate as immediate debt and liquidation truth, and retained enough liquid collateral elsewhere in the system for the seized oUSDC and borrowed assets to cash out into real value.

Detection and audit pivots

  • Search for custom wrapper oracles that multiply a base asset feed by convertToAssets, pricePerShare, exchangeRate, or protocol-specific wrapper math read live from another contract.
  • Ask whether direct transfers into the wrapper, staking recipient changes, or other off-path balance increases can move that conversion function without share issuance.
  • Model borrower and liquidator as one adversary. If a helper account can be made unhealthy by the same actor who later liquidates it, the liquidation bonus is part of the exploit surface.
  • Check whether a sudden wrapper-rate jump on the borrowed asset side can force liquidations against stronger collateral before any cap, pause, or delayed-mark mechanism reacts.
  • Review whether listed wrapper assets can simultaneously serve as price inputs, debt assets, and liquidation ammunition across sibling accounts.

Failure-prevention checklist

  • Do not compose a trusted base-asset feed with a live wrapper conversion function unless the wrapper’s numerator-side backing is donation-neutral or otherwise explicitly recognized through hardened accounting rails.
  • Haircut, cap, or quarantine wrapper-derived price moves before they become borrow, liquidation, or seize authority; a same-block convertToAssets jump should not become immediate solvency truth.
  • Treat raw balance changes into wrappers as hostile by default. If direct donations, rescue transfers, reward top-ups, or staking-recipient changes can move share value, require explicit recognition rather than ambient balance reads.
  • Review borrowed-asset listings as well as collateral listings. A manipulable wrapper price can be monetized by making debt expensive on paper and then liquidating stronger collateral, not only by over-borrowing against the wrapper itself.
  • Enforce bounded-loss controls around wrapper integrations: borrow caps, isolated listings, delayed collateral enablement, liquidation throttles, and fast pause paths should all assume the wrapper exchange rate may be attacker-authored.
  • Make self-liquidation assumptions explicit. If the same actor can cheaply control borrower and liquidator accounts through helper contracts, the liquidation incentive is part of the attack surface and should be constrained accordingly.
  • Rehearse the wrapper rate jumps 2x-10x in one transaction case in borrow, health-factor, and liquidation simulations. If the protocol’s answer is just that should not happen, the integration is incomplete.

Audit prompts

  • What exact function defines wrapper truth here: convertToAssets, pricePerShare, exchangeRateStored, a rebasing balanceOf, or a protocol-specific wrapper helper like convertVGHST()?
  • Can a raw token transfer into the wrapper move that function without minting new wrapper shares or triggering a reviewable accounting-recognition step?
  • Does the protocol ever multiply a trusted external feed by an attacker-movable wrapper ratio and then call the result an oracle?
  • If the wrapper mark spikes inside one block, which downstream paths trust it immediately: borrowing power, liquidation eligibility, seize math, redeem math, or reserve accounting?
  • Can the same economic actor create the unhealthy borrower and then capture the liquidation bonus through a helper account, alternate wallet, or router-mediated path?
  • Are borrow caps, isolation rules, or collateral factors calibrated to the possibility that the wrapper’s exchange rate is locally printable even when the base asset’s own oracle is sound?
  • What blast-radius limit stops a wrapper-mark anomaly from turning into systemwide stablecoin or blue-chip collateral extraction before governance or operators can react?
  • If the wrapper is kept for product reasons, what independent sanity bound proves that one unit of wrapper debt or collateral cannot outrun plausible underlying earnings by an attacker-sized multiple in one transaction?

Lessons that should already be standard

  • A wrapper exchange rate is an oracle the moment lending or liquidation code honors it as money-good truth.
  • The oracle read the contract honestly is not a defense when the contract state being read was attacker-authored through an ambient balance change.
  • Borrow-side pricing can be just as dangerous as collateral-side pricing. Making debt suddenly expensive on paper can be enough to unlock liquidation-based cash-out against stronger collateral.
  • Liquidation discounts are not protective by default. Once borrower and liquidator independence disappears, the liquidation bonus becomes attacker revenue.
  • Wrapper integrations should be reviewed as joint accounting-and-market-structure decisions, not as simple token listings. The dangerous question is not just is GHST priced correctly? but who is allowed to define vGHST share truth this block?
  • Same-block solvency trust in convertToAssets-style math is a design choice, not a neutral convenience. If a wrapper can be donated into, harvested into, or otherwise topped up off-path, immediate trust is usually too generous.

Internal linkages

Control surface