Harvest Finance Curve Y Pool Share-Price Manipulation (2020-10-26)

  • Name: Harvest Finance Curve Y pool share-price manipulation
  • Date: 2020-10-26
  • Protocol: Harvest Finance
  • Chain: Ethereum
  • Category: oracle manipulation / vault share-price manipulation / flash-loan amplified AMM price distortion / deposit-withdrawal spread extraction
  • Loss: the attacker extracted about 33.8M of value lost across the two vaults before a later partial return of roughly 1.76M USDC and 718.9K USDT
  • Summary: The Harvest attacker used large Uniswap flash loans to skew the Curve Y pool state that Harvest used when minting and redeeming vault shares for its USDC and USDT vaults. On the first USDC leg, the attacker borrowed 50M USDC and 18.3M USDT, sold 17.22M USDT into Curve’s Y pool to distort the pool composition, deposited 49.98M USDC into Harvest’s USDC vault at a temporarily cheaper share price of about 0.97126080216 USDC per share versus a pre-attack share price near 0.980007, reversed the Curve trade, and then redeemed at about 0.98329837664 USDC per share for roughly 619,408.812299 USDC of profit on that cycle alone. The attacker repeated the sequence 17 times against the USDC vault and 13 times against the USDT vault in about seven minutes.
  • Trigger / entry point:
    • Harvest’s vault accounting trusted the real-time value of assets invested into Curve’s Y pool when issuing and redeeming fUSDC and fUSDT shares.
    • Those real-time values were affected by pool imbalance, impermanent loss, arbitrage, and slippage inside the shared Curve pool.
    • Harvest had an arbitrage check, but the manipulated share-price move on the first USDC cycle was only about 1%, below the strategy’s 3% threshold, so the transaction did not revert.
    • The vaults could pay withdrawals from their local buffers while share pricing still referenced the manipulated value of the invested assets, letting the attacker lock in the spread without needing to unwind value directly out of Curve on withdrawal.
  • Root cause:
    • Harvest treated the instantaneous state of a manipulable shared AMM pool as trustworthy valuation input for vault-share issuance and redemption.
    • In practice this was a spot-price oracle problem, even though the trusted value came from pool accounting rather than from a separate oracle contract.
    • The protocol assumed the current Curve pool state was a fair share-pricing input even when a flash borrower could shove the pool off equilibrium and restore it inside the same transaction.
    • The critical error was letting temporary pool imbalance change vault-share pricing faster than the protocol could force deposits and withdrawals onto a slower, harder-to-manipulate path.
  • Exploit path:
    1. The attacker funded address 0xf224ab004461540778a914ea397c589b677e27bb via Tornado Cash in transaction 0x4b7b9e387a79289720a0226f695913d1d11dbdc681b7218a432136cc089363c4 and deployed attack contract 0xc6028a9fa486f52efd2b95b949ac630d287ce0af.
    2. The first major attack transaction was 0x35f8d2f572fceaac9288e5d462117850ef2694786992a8c3f6d02612277b0877 at 2020-10-26 02:53:31 UTC.
    3. Inside that transaction, the attacker flash-borrowed 50,000,000 USDC and 18,308,555.417594 USDT from Uniswap into the attack contract.
    4. The attacker swapped 17,222,012.640506 USDT into USDC through Curve’s Y pool, receiving about 17,216,703.208672 USDC and pushing the pool composition into the distorted state Harvest used for vault-share accounting.
    5. With the pool temporarily skewed, the attacker deposited 49,977,468.555526 USDC into Harvest’s USDC vault and received 51,456,280.788906 fUSDC at about 0.97126080216 USDC per share.
    6. Harvest’s own post-mortem notes that the pre-attack share price was about 0.980007 USDC, so the attacker acquired vault shares at roughly a 1% discount while staying inside the vault’s 3% arbitrage threshold.
    7. The attacker then swapped 17,239,234.653146 USDC back into USDT through Curve, receiving about 17,230,747.185604 USDT and restoring the Y-pool state closer to normal.
    8. With share pricing normalized upward, the attacker redeemed all 51,456,280.788906 fUSDC for 50,596,877.367825 USDC at about 0.98329837664 USDC per share.
    9. That first USDC cycle alone produced about 619,408.812299 USDC of profit before flash-loan fees.
    10. The attacker repeated the same pattern 17 times against the USDC vault within about four minutes, then began analogous USDT-vault cycles starting with transaction 0x0fc6d2ca064fc841bc9b1c1fad1fbb97bcea5c9a1b2b66ef837f1227e06519a6.
    11. Across 30 repeated attack transactions over roughly seven minutes, the attacker extracted about 13,000,000 USDC and 11,000,000 USDT and sent them to 0x3811765a53c3188c24d412daec3f60faad5f119b in transaction 0x53fae6f1d6b8a76a666a0bf7f9c724e6006465e544f89f1515b939d8911e8c58.
    12. The attacker later returned 1,761,898.396474 USDC and 718,914.048541 USDT to the Harvest deployer in transaction 0x25119cd54a4562aa427d9770af383512f9cb5e8e4d17232ad96b69dc293a3510, but the vaults still suffered large permanent losses.
  • What the attacker needed to be true:
    • Harvest had to mint and burn vault shares using real-time Curve Y-pool-dependent value rather than a time-separated or manipulation-resistant valuation path.
    • Curve’s Y pool had to be deep enough to absorb the attacker’s huge trades yet still manipulable enough that the attacker could move the vault-share pricing by around 1% per cycle.
    • Harvest’s arbitrage guard had to be loose enough that those manipulated deposits still passed.
    • The vaults needed enough idle buffer liquidity that redemptions could be paid at the restored price after the pool was normalized.
    • Flash loans had to make the temporary imbalance cheap enough to repeat many times before Harvest paused the affected strategies.
  • Technical anatomy worth remembering:
    • SlowMist’s summary highlights the core accounting path: the vault’s share-minting math used amount.mul(totalSupply()).div(underlyingBalanceWithInvestment()), and the underlyingBalanceWithInvestment() value ultimately depended on the manipulated invested balance inside Curve.
    • This was not a collateral-borrow attack like bZx or Mango. The attacker monetized the mispricing directly by buying vault shares cheap during the distorted state and redeeming them dear after restoring the pool.
    • The exploit is a clean reminder that virtual price, pool balance state, or invested-underlying accounting can act as a solvency-critical oracle even when no explicit oracle contract is involved.
    • Harvest’s own post-mortem explicitly rejected naive external-oracle replacement as a full solution because the vault still needed a pricing path tied to the real underlying strategy assets. The harder design problem was making that path manipulation-resistant.
    • The attack combined two balance sheets: Curve determined the temporary price distortion, while Harvest’s vault buffer paid out the restored-value redemption. The printable market state and the extraction venue were related but not identical.
  • Representative transactions and addresses:
    • Tornado Cash funding transaction: 0x4b7b9e387a79289720a0226f695913d1d11dbdc681b7218a432136cc089363c4
    • Attacker EOA: 0xf224ab004461540778a914ea397c589b677e27bb
    • Attack contract: 0xc6028a9fa486f52efd2b95b949ac630d287ce0af
    • First major USDC-vault attack transaction: 0x35f8d2f572fceaac9288e5d462117850ef2694786992a8c3f6d02612277b0877
    • First major USDT-vault attack transaction: 0x0fc6d2ca064fc841bc9b1c1fad1fbb97bcea5c9a1b2b66ef837f1227e06519a6
    • Main loot transfer: 0x53fae6f1d6b8a76a666a0bf7f9c724e6006465e544f89f1515b939d8911e8c58
    • Partial return to Harvest deployer: 0x25119cd54a4562aa427d9770af383512f9cb5e8e4d17232ad96b69dc293a3510
    • Receiver address used after extraction: 0x3811765a53c3188c24d412daec3f60faad5f119b

Three vault-share promotions this incident collapsed

  1. Curve Y-pool and strategy-path state moved

    • The attacker used Uniswap flash liquidity to shove the Curve Y pool off equilibrium, which changed the invested-underlying state Harvest’s underlyingBalanceWithInvestment()-style path was reading.
    • That only proved Harvest’s upstream strategy venue had entered a different local state. It did not yet mean the distorted pool composition deserved to become canonical vault-share truth for minting or redemption.
  2. Harvest published that transient strategy state as canonical share truth

    • Harvest then minted and later burned fUSDC / fUSDT against the manipulated invested-balance mark as if it already described one money-good share price.
    • This was the decisive false promotion: a flash-loan-authored Curve state stopped being just temporary pool imbalance exists and became Harvest's official answer for how many vault shares deposits deserve and how much stablecoin those shares may later redeem for.
  3. Harvest’s local buffer spent against that published truth after the pool normalized

    • Once the attacker restored Curve closer to equilibrium, Harvest still let the same shares redeem at the higher normalized mark while paying out of the vault’s healthier local buffer.
    • That is where the paper pricing gap became real loss. The protocol did not merely observe two different strategy states; it honored both as though they described one coherent reserve-backed share object and let the later redemption authority cash out against stronger liquidity than the manipulated venue itself provided.
  • Response / mitigation:
    • Harvest immediately withdrew funds from the affected shared-pool strategies after reconstructing the attack and left those assets parked in the vaults to avoid further manipulation.
    • In the official post-mortem, the team proposed a commit-and-reveal deposit flow so deposits and share claims could not happen atomically inside one flash-loan transaction.
    • Harvest also said the existing 3% deposit arbitrage check needed to become much stricter, but treated that as only a complementary brake rather than a sufficient fix on its own.
    • Another mitigation path in the post-mortem was changing withdrawals so users exited into underlying pool assets instead of cashing out entirely against a vault buffer priced off a manipulated invested-balance mark.
    • Harvest explicitly argued that simply swapping in an external oracle was not a complete solution, because a loose external price could diverge from the real strategy asset value and open a different arbitrage window.
    • The incident pushed the industry toward treating AMM-derived spot state as a dangerous valuation input for vault accounting, not just for lending or liquidation oracles.
    • The preserved design lesson is narrower than use an oracle: the protocol needed one share-truth object whose issuance and redemption paths could not observe a cheaply attacker-movable pool state on one side of the round-trip and a restored state on the other while the vault buffer still paid out as if both valuations described the same economic reality.

Why this incident matters

  • Harvest is one of the clearest examples that vault share pricing can be an oracle-manipulation surface even without a separate oracle contract.
  • It shows how a protocol can lose money without ever extending undercollateralized credit: direct deposit-and-redeem accounting is enough if the share price can be pushed and then reset.
  • It also became a template for later attacks where a protocol trusted instantaneous AMM state for issuance, redemption, or solvency-critical accounting.

Pattern tags

  • Same-window deposit / redemption spread capture: the attacker did not need to leave with fake collateral or a governance token mint; the core print was mint shares cheap while the pool is skewed, then redeem dear after normalization.
  • Strategy-path valuation manipulation: the dangerous price input lived inside Harvest’s own underlyingBalanceWithInvestment() / invested-balance path rather than in a standalone external oracle contract.
  • Buffer-versus-strategy balance-sheet split: Curve authored the temporary discount, but Harvest’s local vault buffer paid the restored-value redemption, so the state that set the share price and the liquidity that honored it were not the same economic surface.
  • Threshold-underfit flash-loan amortization: the vault’s 3% arbitrage guard assumed harmful manipulation would look large in one shot, while the attacker showed that sub-threshold ~1% cycles repeated 30 times were enough to drain tens of millions.

Why the exploit worked

  • Harvest let a manipulable Curve Y-pool state become immediate share-mint and share-burn truth. The protocol did not force vault-share issuance and redemption onto a slower or more durable valuation surface than the one a flash borrower could distort and restore inside one transaction.
  • The arbitrage check was directionally aware but economically underfit. Harvest’s own post-mortem shows the first USDC cycle moved share price by only about 1%, so the attack stayed below the strategy’s 3% guard while still being highly profitable once repeated at scale.
  • The vault buffer made the round trip cash-settled against a stronger balance sheet than the one being manipulated. Curve supplied the transient pricing error, but Harvest’s local USDC and USDT liquidity let the attacker redeem against the restored mark without needing to realize equivalent value directly out of the skewed Curve pool.
  • Deposit and redemption semantics were still describing two different pool states as if they were one economic object. That mismatch is the sharp lesson: a protocol cannot safely let distorted invested-balance mark decide share issuance and then let normalized invested-balance mark + local buffer decide redemption when both actions can happen in one flash-loan-sized window.
  • Operationally, the exploit also showed that shared-pool strategy accounting is already oracle infrastructure. Even if the protocol rejects the label oracle, any path that tells the vault how many shares to mint or how much stablecoin a share can redeem for is balance-sheet truth and has to survive hostile timing.

Detection and audit pivots

  • Trace every place a vault’s share price depends on live strategy state, pooled-asset balances, metapool composition, or underlyingBalanceWithInvestment()-style helpers. Ask whether a flash borrower can change that upstream state before both mint and redeem paths consume it.
  • Test manipulate strategy venue -> deposit -> normalize venue -> withdraw and the reverse ordering, not just ordinary spot-oracle abuse. Harvest’s key issue was the round trip, not a single bad mark viewed in isolation.
  • Compare arbitrage or slippage thresholds against repeatability, not only per-transaction magnitude. If a 1% move passes but can be repeated dozens of times with flash liquidity, the threshold is not a real solvency defense.
  • Review whether withdrawals are paid from local idle buffers, strategy unwinds, or a mix. If share pricing is authored by one venue while cash payout comes from another, that seam deserves explicit balance-sheet review.
  • Treat vault-share pricing, strategy-invested balance reporting, and deposit / withdrawal timing as one control surface. Splitting those responsibilities across separate modules does not make the economic invariant any less coupled.
  • Ask whether a commit / claim delay, shadow pricing lane, or fresh-mark quarantine would kill the exploit. If simple time separation breaks profitability, the protocol is probably over-crediting same-window strategy state.

Failure-prevention checklist

  • Separate strategy valuation publication from immediate share issuance and redemption authority. If a live Curve, metapool, or strategy balance can be skewed inside one attacker-controlled window, deposits and exits should not both settle against that fresh state at full size.
  • Price vault shares against one coherent economic object. If minting logic reads manipulated invested-balance state while redemption logic pays from a local idle buffer after normalization, the protocol is already describing two different balance sheets as though they were one.
  • Treat sub-threshold repeatability as a first-class exploit path. A 3% guard is not meaningful if 1% distortions can be repeated dozens of times with flash liquidity before responders can intervene.
  • Quarantine fresh strategy marks before they create full issuance or redemption rights. Commit-and-reveal deposits, delayed share claims, staged withdrawal settlement, or shadow pricing lanes all help when the exploitable edge is same-window valuation authority.
  • Force manipulated-strategy exits to socialize their own unwind reality. If the strategy venue authored the discount or premium, redemptions should not be able to cash out entirely against a separate vault buffer at a restored price without carrying the strategy-state loss through the same path.
  • Review underlyingBalanceWithInvestment()-style helpers as oracle code. Any function that decides how many shares to mint or how much stablecoin a share can redeem for is already balance-sheet truth, even if the codebase calls it accounting rather than pricing.
  • Model flash-loan-scale pool imbalance explicitly for shared-pool strategies. Deep shared pools can still be dangerous if they are just shallow enough to move share pricing by 1% while the vault payout side is much larger.
  • Put strategy-level degraded modes close to the share-pricing surface: pause deposits, narrow withdrawal semantics, or force direct strategy unwinds when pool imbalance exceeds realistic bounds rather than trusting arbitrage to repair the state in time.
  • Keep issuance and redemption invariants symmetric under hostile timing. If cheap mint now, expensive redeem moments later is profitable, the vault has not preserved one reserve-backed share story.
  • Rehearse the strategy venue lies briefly, vault buffer pays honestly failure mode whenever local buffers, delayed harvest accounting, or pooled-strategy wrappers can detach payout liquidity from the venue that authored the mark.

Audit prompts

  • What exact state does share minting trust here: pool reserves, virtual price, strategy withdrawal quote, investedUnderlyingBalance, or a higher-level cached NAV?
  • Can an attacker move that state, deposit against it, normalize it, and redeem before the vault forces any time separation or strategy-side realization of the move?
  • Do deposit and withdrawal paths settle against the same economic object, or can one leg read manipulated strategy state while the other leg pays from a healthier local buffer?
  • How large a price move is blocked per transaction, and how many profitable sub-threshold cycles fit inside one block, one flash-loan session, or one operational response window?
  • If the strategy venue is temporarily skewed, what mechanism stops the vault from issuing too many shares now and honoring them later at a restored price?
  • Which helpers or reports translate strategy balances into vault-share pricing, and are those helpers reviewed with the same paranoia as explicit oracle adapters?
  • When the strategy depends on a shared AMM or metapool, who actually bears the unwind cost of manipulated state: the depositor causing it, all vault shareholders, or a local idle buffer that never touched the manipulated venue?
  • What exact delay, claim flow, or redemption staging rule would make the attack unprofitable, and why is that control absent today?
  • If the local vault buffer were empty and exits had to realize directly through the manipulated strategy, would the protocol still quote the same share value?
  • Which operational switch shrinks fastest during strategy-valuation stress: deposits, withdrawals, buffer usage, share-price updates, or strategy exposure itself?

Lessons that should already be standard

  • Vault accounting is oracle design when it decides how many redeemable claims to mint. Calling the read path strategy balance instead of oracle does not make it less security-critical.
  • Same-window mint and redeem authority against manipulable strategy state is usually fatal. If the protocol will buy and sell the same share against two different attacker-timed balance-sheet views, it is inviting spread extraction.
  • Local buffers can turn a pricing bug into a cash-out bug. They make UX smoother in normal times, but they also let a protocol honor restored-value exits even when the manipulated venue never truly paid that value.
  • Per-transaction guardrails are not enough when flash liquidity makes sub-threshold cycles repeatable. A safe-looking 1% mispricing can still be a drain if the loop can run 30 times before anyone reacts.
  • Shared-pool strategy integrations inherit market microstructure risk, not just smart-contract risk. Impermanent loss, slippage, arbitrage, and temporary imbalance are part of the vault’s solvency surface once they influence share issuance or redemption.
  • The healthiest boundary is one coherent share-truth object: the same strategy state, the same reserve realization story, and the same payout semantics should govern both issuance and redemption.
  • Commit delays, staged exits, and slower claim semantics are not UX embarrassments here; they are often the simplest way to keep one flash-loan-shaped market print from becoming permanent vault loss.

Internal linkages

  • Core recurring pattern: oracle-manipulation.
  • Sharper vault-accounting lens: vault-strategy-path-valuation-manipulation — Harvest’s real oracle surface was its own share-pricing path through the manipulated Curve Y-pool state.
  • Positive-design companion: vault-share-truth-security-object — the sharper healthy-side follow-up when the question becomes what one vault should have kept bound together before a temporary Curve imbalance could become cheap share issuance, normalized redemption, and real stablecoin outflow.
  • Positive-design timing companion: delayed-snapshots-and-same-block-quarantine — useful when the sharper question is why one attacker-shaped pool state should not have become same-window deposit and withdrawal authority.
  • Positive-design withdrawal-semantics companion: lp-collateral-weakest-leg-unwind-and-same-window-quarantine — useful when the sharper question is why readable pool value was not the same thing as executable unwind value for a vault paying out stablecoins against shared-pool marks.

Control surface

  • The leverage sat in Harvest’s share-pricing path, Curve Y-pool dependence, arbitrage-threshold configuration, vault-buffer withdrawal design, and the ability to complete deposit and redemption against differently manipulated states inside a flash-loan-sized loop.
  • Harvest did not merely have a flash loan issue. It had a valuation-path issue: the protocol let a transient AMM imbalance decide how many vault shares to mint and how much stablecoin those shares could later redeem for.
  • The preserved healthy boundary was not just price shares better somehow; it was keep share issuance, redemption, invested-asset valuation, and local-buffer payout semantics describing one bounded economic object instead of two different pool states separated by attacker-controlled timing.

Sources

Last reviewed

  • 2026-08-03 UTC