Worked example · 2020-10
Harvest Finance flash-loan-funded oracle manipulation — Ethereum — 2020-10-26
Summary
Harvest Finance was an Ethereum-based yield-aggregator protocol that issued vault-share tokens (fUSDC, fUSDT, etc.) representing depositors' pro-rata claims on a strategy-managed pool. Deposits into the USDC and USDT vaults were strategy-routed into Curve.fi's Y pool (a four-stablecoin pool over yDAI / yUSDC / yUSDT / yTUSD). The vault-share mint-and-redeem accounting priced deposits and withdrawals using the current per-share value derived from the Y pool's reported balances — i.e., Harvest read the Y pool's spot pool-state as the price source for fUSDC and fUSDT minting, with only a per-strategy arbitrage-check threshold (~3%) as a guard.
On 2020-10-26 at ~02:53 UTC, an attacker took a ~$50M USDC flash loan from Uniswap and used it to (a) swap a large block of USDC into USDT inside the Curve Y pool, temporarily depressing the USDC-side and inflating the USDT-side per-share value via impermanent-loss-shaped pool dislocation; (b) deposit the now-undervalued USDC into Harvest's USDC vault, minting fUSDC shares at the depressed share price; (c) reverse the swap inside the same pool, restoring USDC's value relative to USDT and lifting the share price back; and (d) redeem the fUSDC for more USDC than was originally deposited. Each cycle stayed under the strategy's 3% arbitrage-check threshold and was therefore not rejected by Harvest's strategy contract. The attacker repeated the cycle 17 times against the USDC vault over ~4 minutes and 13 times against the USDT vault over ~3 minutes, extracting ~$13M USDC and ~$11M USDT respectively, for ~$24M total. Flash loans were repaid in the same transactions; gross capital outlay was the gas cost.
This was not the first production demonstration of flash-loan-funded oracle manipulation on Ethereum — bZx Protocol (2020-02) was — but it was the first instance of the same attack class at the $10M+ scale against a then-prominent yield aggregator, and it occurred eight months after the bZx incidents had produced an industry-wide post-mortem cycle, public PeckShield and samczsun write-ups, and the now-standard "flash-loan attack" vocabulary. Harvest October 2020 demonstrated that the bZx-era lessons had not yet propagated to all production-deployed accounting designs that consumed AMM pool-state as a price source.
Timeline (UTC)
| When | Event | OAK ref |
|---|---|---|
| pre-event | Harvest's USDC and USDT vault strategies route deposits into Curve.fi's Y pool; vault-share mint/redeem prices read pool-state directly with a ~3% per-cycle arbitrage-check threshold as the only on-chain guard | (standing T9.001 surface) |
| 2020-10-26 ~02:53 (T+0) | Attacker contract initiates the exploit transaction sequence | T9.002 setup |
| 2020-10-26 (same window) | ~$50M USDC flash-borrowed from Uniswap; ~$18M USDT also sourced via flash-loan / pool routing | T9.002 precondition |
| 2020-10-26 (per cycle) | Attacker swaps ~$17M USDC → USDT inside Curve Y pool, depressing USDC-side per-share value | T9.001 oracle-side move |
| 2020-10-26 (per cycle) | Attacker deposits USDC into Harvest USDC vault at the depressed share price, minting fUSDC at a beneficial rate (~0.971 USDC/share, vs. fair ~0.983 — within 3% threshold, so strategy arbitrage-check does not trigger) |
T9.001 setup (mint side) |
| 2020-10-26 (per cycle) | Attacker swaps USDT → USDC inside Curve Y pool, restoring USDC's value and lifting fUSDC share price back toward fair |
T9.001 oracle-side reversal |
| 2020-10-26 (per cycle) | Attacker redeems fUSDC for USDC at the restored share price; net USDC out > net USDC in |
T9.001 extraction (redeem side) |
| 2020-10-26 ~02:53–02:57 | 17 such cycles executed against USDC vault; ~$13M extracted | T9.001 + T9.002 chained extraction |
| 2020-10-26 ~02:57–03:00 | 13 such cycles executed against USDT vault; ~$11M extracted | T9.001 + T9.002 chained extraction |
| 2020-10-26 (per cycle) | Flash loans repaid; total ~$24M retained by attacker (denominated in USDC + USDT) | T9.002 closure |
| 2020-10-26 (post-event) | Harvest team withdraws funds from shared pools (DAI, USDC, USDT, TUSD, WBTC, renBTC) to halt further extraction; FARM token price falls from ~$242 to ~$100 over the event window | (defender response) |
| 2020-10-26 (post-event) | Attacker sends ~$2.5M back to a Harvest deployer address; remaining ~$21.5M retained | (partial-return) |
| 2020-10-26 onward | Harvest posts $100,000 bounty for attacker identification; no public resolution | (post-event) |
| 2020-10 onward | Harvest publishes post-mortem describing the incident as an "engineering error" — specifically, the unprotected use of Curve Y pool spot-state as the share-price input | (post-event) |
What defenders observed
- Pre-event (oracle-design layer): Harvest's
fUSDC/fUSDTshare price was computed from the Curve Y pool's current on-chain pool balances, with only a 3% per-cycle arbitrage-check threshold guarding against manipulation. There was no TWAP window on the price input, no deviation circuit-breaker against multi-source reference prices (e.g., Chainlink stablecoin feeds), and no per-block sanity check on the pool-state inputs themselves. This was a standing T9.001 surface from the day the Y-pool-routed strategy was deployed — structurally identical in shape to bZx's February 2020 sUSD-on-Kyber surface, scaled up by an order of magnitude in the consuming protocol's TVL. - Pre-event (industry-context layer): the bZx February 2020 incidents had, eight months earlier, established the T9.001 + T9.002 attack class in production and produced widely-circulated technical post-mortems (PeckShield, samczsun) plus the "flash loan attack" vocabulary in DeFi-security discourse. The defender-portfolio observation Harvest illustrates is that industry-wide adoption of the bZx-era lessons was uneven: oracle-design changes (TWAP windows, multi-source quorum, deviation breakers) were adopted by some protocols and not others, and Harvest's strategy contracts had not adopted them. Eight months is not enough time for a class-of-vulnerability lesson to propagate across all production deployments unless the propagation channel is structural (audit-firm checklists, framework-level libraries, deployer-tool defaults).
- At-event (oracle-side signal): within each exploit cycle, the Curve Y pool's USDC-side balance moved sharply on a single-actor swap, and the per-share value derived from those balances moved correspondingly. A multi-source deviation monitor — comparing the Y pool-derived stablecoin price against any external reference (Chainlink, a TWAP-windowed alternative, or even a second AMM pool) — would have observed the divergence in the same block as the manipulation. This is the same signal that any post-bZx oracle-resilience design produces by construction.
- At-event (protocol-side signal): the same actor was both the marginal swap-side liquidity-provider in Curve and the marginal deposit/redeem actor in Harvest, in repeated atomic cycles, with flash-loan-funded working capital. A cross-action correlation detector applied to the consuming protocol — same actor, swap-then-deposit-then-reverse-swap-then-redeem inside a flash-loan-bracketed window — would have produced an independent signal. The 30-cycle repetition over ~7 minutes also makes this case unusually visible to any rate-of-cycles or repeated-pattern monitor; most T9.001 cases extract in a single transaction, while Harvest extracted across 30 transactions. This is a defender-side artefact of the 3% threshold cap on per-cycle profit: the attacker had to repeat to reach the available-buffer ceiling.
- Post-event (recovery channel): the attacker returned ~$2.5M of ~$24M (~10%) without negotiation. This is a partial-return outcome — between the on-chain-only laundering rule (Cream October 2021) and the explicit-negotiated-recovery exception (Mango Markets 2022) — and is the v0.1 base-rate evidence that unprompted partial returns occur but rarely materially change the loss magnitude.
What this example tells contributors writing future Technique pages
- Lesson propagation between canonical incidents is itself a teaching point. Harvest October 2020 occurred eight months after bZx February 2020, against a structurally-identical oracle-design surface, in an environment where the bZx post-mortems had already been published and widely read. Worked examples should explicitly note the time-since-precedent and the structural similarity to prior canonical cases, because this exposes how slow lesson propagation is in DeFi when the propagation channel is informal (post-mortems and Twitter) rather than structural (framework defaults, audit-firm checklists). The defender takeaway is not that Harvest's team failed to read bZx's post-mortem — it is that the industry-wide propagation channel was not strong enough to ensure deployed code reflected the lesson.
- The 3% per-cycle threshold is a teaching case for under-specified safeguards. Harvest's strategy contract had a guard (the 3% arbitrage check) — it just was not the right shape. The safeguard limited per-cycle profit but did not prevent multi-cycle repetition, so the attacker simply repeated 30 times. Worked examples should distinguish threshold-style safeguards (which cap per-action loss) from circuit-breaker-style safeguards (which halt on cumulative or pattern-level deviation). T9.001 mitigation guidance should specify the latter, not just the former.
- AMM pool-state as a price source is a sub-pattern of T9.001 distinct from book-value-of-vault and spot-price-of-thin-venue. Harvest October 2020 priced shares from Curve Y pool spot-state; Cream October 2021 priced shares from yUSDVault book-value; Mango Markets October 2022 priced perp collateral from spot prices on a thin set of venues. All three are T9.001, but the mechanically-correct mitigation differs: AMM pool-state oracles need TWAP windows or migration to aggregator price feeds (Chainlink); book-value-of-vault oracles need direct-transfer-resistance and minimum-supply floors; thin-venue spot oracles need multi-venue quorum and deviation breakers. T9.001 pages should describe all three sub-patterns.
- Flash loans are the precondition, not the cause. As with bZx, Cream, Beanstalk, and Euler, the Harvest extraction is enabled by the flash loan but caused by the oracle-design choice. Banning flash loans would not retire the T9.001 surface; only fixing the pool-state-as-oracle pattern would. Contributors should keep this distinction tight in T9.002 framings.
- Partial-return outcomes are a third category alongside total-loss and negotiated-recovery. Harvest's ~10% unprompted return falls between Cream's 0% (on-chain laundering only) and Mango's ~60% (explicit DAO negotiation). Worked examples should preserve the recovery-channel category since it materially affects the realised-loss base rate that downstream risk modelling depends on.
Public references
- Harvest Finance — Harvest Flashloan Economic Attack Post-Mortem — the protocol-side official post-mortem; the canonical source for the per-cycle mechanic, the 3% threshold, and the engineering-error framing —
[harvestpostmortem2020]. - SlowMist — Analysis of Harvest.Finance's Hacked Event — transaction-level forensic walkthrough; cited for the per-cycle balance and share-price arithmetic —
[slowmistharvest2020]. - ImmuneBytes — Harvest Finance Fund Theft Incident – Oct 26, 2020 — Detailed Analysis — independent forensic write-up; cited for the cycle-count (17 USDC, 13 USDT) and timing facts —
[immunebytesharvest2020]. - CoinDesk — Harvest Finance: $24M Attack Triggers $570M 'Bank Run' in Latest DeFi Exploit — contemporaneous reporting; cited for the wider-pool secondary-outflow figure and the FARM-token price reaction —
[coindeskharvest2020]. - The Block — DeFi protocol Harvest Finance exploited, attacker drained $33.8M and then returned $2.5M — contemporaneous reporting; cited for the partial-return figure —
[theblockharvest2020]. - Decrypt — 'Engineering Error' Led to $34 Million DeFi Hack, Harvest Finance Says — cited for the protocol-team's "engineering error" framing.
[zhou2023sok]— academic taxonomy classifying this as a flash-loan-enabled price-oracle-manipulation chain; Harvest October 2020 is one of the SoK's worked examples for the T9.001-class.[peckshieldbzx2020],[bzxpostmortem2020]— companion bZx historical-anchor citations; cited here for the time-since-precedent observation, not for Harvest specifics.
Discussion
Harvest Finance October 2020 is the second link in the canonical chain of T9.001 + T9.002 cases that runs bZx (Feb 2020) → Harvest (Oct 2020) → Cream (Oct 2021) → Mango Markets (Oct 2022). Each case demonstrates the same failure mode at progressively larger loss magnitudes (~$1M → ~$24M → ~$130M → ~$110M, with Mango's notional extraction the largest and Cream's the largest by clean attribution to the consuming protocol's available liquidity), and each case reveals a different sub-pattern of the same Technique class:
- bZx Feb 2020 — thin-liquidity AMM pool used as singleton price oracle (sUSD on Kyber); the historical anchor.
- Harvest Oct 2020 — Curve AMM pool-state used directly as vault-share-price input, with only a per-cycle threshold guard; the second-generation case demonstrating the bZx pattern persisted at scale eight months post-disclosure.
- Cream Oct 2021 — book-value of a yield-vault token (yUSDVault) used as collateral price; a mechanically-distinct sub-pattern within the same Technique class.
- Mango Markets Oct 2022 — spot price across a thin set of venues (FTX, AscendEX, Serum) used for perpetual-collateral valuation; the modern canonical case with full regulatory and legal-status record.
The teaching point of placing Harvest second in the chain is not the dollar magnitude (Cream and Mango are larger) but the time elapsed since bZx. Eight months between the foundational case and the first $10M-scale recurrence is short enough that the bZx post-mortems were public and widely read, and long enough that any contributor inclined to argue "the team should have known" can reasonably do so. The honest framing is that the industry-wide propagation channel was the failing layer: there was no audit-firm checklist, framework-level library, or deployer-tool default that would have caused Harvest's strategy contract to use a TWAP-windowed or multi-source oracle by construction. Worked examples in the T9.001 family should use Harvest as the case for "what happens between the foundational case and the modern canonical case, when lesson propagation is informal," and should preserve this framing rather than reducing it to a per-protocol engineering criticism. Contributors writing other early-canonical cases in any Technique family should consider the same time-since-precedent framing where applicable.