Skip to content

Commit 714d9df

Browse files
iZonexclaude
andcommitted
[corrections] resolve TAXONOMY-AUDIT Finding C: two mislabelled-mechanism pairs
Both contradictory pairs flagged in TAXONOMY-AUDIT Finding C turned out to have BOTH files wrong on the vulnerability class. Traced provenance, researched the real incidents against primary sources, corrected + cited; removed only pure fabrications. ParaSpace (2023-03-17): - Both files were wrong. One (release) called it reentrancy; the other (a "19 fixture examples" filler commit) called it cross-contract reinitialisation. The real mechanism (BlockSec, CertiK) is book-value oracle manipulation: depositApeCoin(_recipient = proxy cAPE position) inflated stakedAmount ~+373%, inflating the rebasing collateral index -> borrow against phantom collateral, flash-loan-funded. -> T9.001 (book-value sub-shape, next to Cream/Summer.fi) + T9.002. Not reentrancy, not reinitialisation. - Also corrected the rescue framing: it was NOT a pre-exploit code-review discovery with no adversary. There was an active attacker whose gas-heavy attempts kept reverting; BlockSec's monitoring caught it and rescued by redeploying the attacker's own exploit contract to land first (the attacker later messaged BlockSec asking for gas back). Removed the false claim that ParaSpace is OAK's canonical "pre-exploit, no adversary" window anchor. - Deleted the fabricated reinitialisation file; repointed T9.009 anchor prose and spec fixture to Exactly Protocol (Optimism, 2023-08, ~$7.3M realised) — a real reinitialisation incident. Curve / Market.xyz (three files -> one): - The "canonical T9.010 anchor" conflated two different real incidents. Real Market.xyz = October 2022, QuickSwap/Polygon, ~$220K, read-only reentrancy in a Curve LP oracle via remove_liquidity's native-token callback (no Vyper bug). It was NOT part of the July 2023 Curve/Vyper event (Alchemix/JPEG'd/Metronome/ Ellipsis; direct-drain reentrancy, T9.005), and pcaversaccio's canonical list has no market.xyz July-2023 entry. - Rewrote as 2022-10-market-xyz-curve-lp-oracle-read-only-reentrancy.md with primary sources (QuillAudits, ChainSecurity). Deleted two fabrications: the "$70M read-only + Ellipsis" fixture and the July-2023/$500K conflation. - Decoupled the conflation across T9.010, T9.005, T9.002, the architecture cohort, and Cygnus's cross-ref; T9.010 "First documented" now 2022. Root cause of both: files manufactured to satisfy "every technique needs an example anchor" (fixture commit c28e744) invented mechanisms. The other 17 fixtures from that commit are worth a follow-up sweep. check_linkage / check_backlinks / check_citations / check_tags / check_specs: pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0e7e25a commit 714d9df

19 files changed

Lines changed: 110 additions & 200 deletions

BACKLOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
_Generated by `tools/build_backlog.py` on 2026-07-23. Regenerated on every `npm run site:data`._
44

5-
_Scope: 656 worked examples, 149 Techniques, 19 Threat Actors._
5+
_Scope: 654 worked examples, 149 Techniques, 19 Threat Actors._
66

77
This file is a prioritized contributor backlog. **P0** items close hard structural gaps (empty Tactics, placeholder actor cards). **P1** items lift per-Tactic coverage below the documented minimum. **P2** items anchor candidate sub-Techniques from `TAXONOMY-GAPS.md`.
88

SPECS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,4 @@ Maturity legend:
254254

255255
---
256256

257-
Updated automatically when `tools/build_specs_index.py` runs as part of `npm run site:data`. Last regenerated from corpus state at `2026-07-23T08:25:49+00:00`.
257+
Updated automatically when `tools/build_specs_index.py` runs as part of `npm run site:data`. Last regenerated from corpus state at `2026-07-23T09:31:08+00:00`.

STATS.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# OAK — Stats Snapshot
22

3-
_Auto-generated by `tools/build_stats.py` at 2026-07-23 08:25 UTC._
3+
_Auto-generated by `tools/build_stats.py` at 2026-07-23 09:31 UTC._
44

55
## Catalogue
66

77
- **17** Tactics · **149** Techniques · **19** Threat Actors · **47** Mitigations · **41** Software · **12** Data Sources
8-
- **656** Worked Examples · **1555** bibtex entries
8+
- **654** Worked Examples · **1555** bibtex entries
99

1010
## Examples by Tactic
1111

@@ -19,7 +19,7 @@ _Auto-generated by `tools/build_stats.py` at 2026-07-23 08:25 UTC._
1919
| T6 (Defense Evasion) | 55 |
2020
| T7 (Laundering) | 169 |
2121
| T8 (Operator Continuity / Attribution Signals) | 91 |
22-
| T9 (Smart-Contract Exploit) | 207 |
22+
| T9 (Smart-Contract Exploit) | 205 |
2323
| T10 (Bridge / Cross-Chain) | 55 |
2424
| T11 (Custody / Signing) | 151 |
2525
| T12 (NFT-Specific) | 23 |
@@ -42,8 +42,8 @@ _Auto-generated by `tools/build_stats.py` at 2026-07-23 08:25 UTC._
4242
| 2019 | 10 |
4343
| 2020 | 30 |
4444
| 2021 | 45 |
45-
| 2022 | 96 |
46-
| 2023 | 105 |
45+
| 2022 | 97 |
46+
| 2023 | 102 |
4747
| 2024 | 151 |
4848
| 2025 | 78 |
4949
| 2026 | 74 |
@@ -81,9 +81,9 @@ _Auto-generated by `tools/build_stats.py` at 2026-07-23 08:25 UTC._
8181

8282
| Strength | Count |
8383
| --- | ---: |
84-
| pseudonymous | 312 (47.6%) |
85-
| unattributed | 151 (23.0%) |
86-
| confirmed | 126 (19.2%) |
84+
| pseudonymous | 312 (47.7%) |
85+
| unattributed | 149 (22.8%) |
86+
| confirmed | 126 (19.3%) |
8787
| inferred-strong | 64 (9.8%) |
8888
| inferred-weak | 3 (0.5%) |
8989

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Market.xyz — a lending market prices Curve LP collateral through `get_virtual_price()` and is drained by re-entering the pool's view during `remove_liquidity` — Market.xyz / QuickSwap (Polygon) — 2022-10-24
2+
3+
**Loss:** **~\$220K** of MAI stablecoin extracted from a **Market.xyz** lending market hosted on **QuickSwap** (Polygon). Reported figures vary by snapshot (~\$188K–\$220K). The drained MAI had been **seeded to the market by QiDAO**, so no end-user deposits were lost; the loss fell on the protocol-supplied liquidity. Two attack transactions netted the attacker roughly **138 ETH and 700 MATIC** after swaps. QuickSwap shut down its lending service in response.
4+
5+
**OAK Techniques observed:** **OAK-T9.010** (Read-Only Reentrancy — *primary, confirmed mechanism*. Market.xyz priced Curve LP-token collateral by reading the Curve pool's `get_virtual_price()` view. The attacker re-entered that view while the pool was mid-execution inside `remove_liquidity`: the pool transfers native tokens to the caller before it finishes updating its accounting, so during that callback the LP-token supply had already decreased while the invariant `D` had not been re-settled, and `get_virtual_price()` returned an inflated value. Market.xyz consumed the inflated price and extended a loan against over-valued collateral, which the attacker withdrew. The Curve pool itself lost nothing — the `staticcall` read did not move its assets. See [`techniques/T9.010-read-only-reentrancy.md`](../techniques/T9.010-read-only-reentrancy.md)). **OAK-T9.002** (Flash-Loan-Enabled Exploit — the working capital: the position that made the transient mispricing worth ~\$220K was flash-loan-funded, with add-liquidity, the re-entrant read, the over-collateralised borrow, and repayment all inside one transaction. See [`techniques/T9.002-flash-loan-enabled-exploit.md`](../techniques/T9.002-flash-loan-enabled-exploit.md)).
6+
7+
**Attribution:** **pseudonymous.** No named individual or group. On-chain identifiers from the public write-ups: attacker EOA `0x4206d62305d2815494dcdb759c4e32fca1d181a0` with exploit contract `0xeb4c67e5be040068fa477a539341d6aef081e4eb` (first transaction `0xb8efe839da0c89daa763f39f30577dc21937ae351c6f99336a0017e63d387558`), and a second EOA `0xe3671a41c44f50048e60939df3d26704c9652d9d` with contract `0xC6CD77fcf37ffcd97B3429F0284ecfF50616EA74`. The 138 ETH proceeds were bridged from Polygon to Ethereum and sent to Tornado Cash. The read-only-reentrancy trace path (a `staticcall` to `get_virtual_price()` while the pool's outer `remove_liquidity` frame had not returned) is reproducible from the transaction trace.
8+
9+
**Key teaching point:** **A view function is only a safe price source if the contract it reads cannot be mid-execution at the moment you read it — and a Curve pool can be, because `remove_liquidity` hands control back to the caller before it settles.** Market.xyz's own lending logic was not reentrant and was not the flaw; the flaw was the cross-protocol trust assumption that `get_virtual_price()` always returns settled state. That assumption is false during the pool's native-token transfer callback, and any consumer that reads the price then sees an inflated number. The load-bearing mitigation is entirely on the consumer side and independent of Curve: before using an external pool's view output for a value-transfer decision, assert the pool is not mid-execution — the pattern later standardised as Curve/Balancer read-only-reentrancy guards (calling a state-mutating no-op that reverts if the pool's reentrancy lock is held, or Balancer's `ensureNotInVaultContext`). This case is one of the earliest in-the-wild exploitations of that surface, predating the higher-profile Sentiment (April 2023) and Curve-Vyper (July 2023) events, and ChainSecurity had already warned about exactly this Curve-LP-oracle class before it was exploited here.
10+
11+
## Summary
12+
13+
**Market.xyz** was a permissionless lending-market platform (Fuse/Compound-style isolated money markets) whose markets were hosted on **QuickSwap**, a DEX on **Polygon**. One Market.xyz market accepted a **Curve LP token** as collateral and priced it through a Curve-LP oracle that read the pool's `get_virtual_price()` view function.
14+
15+
Curve's `get_virtual_price()` returns the pool's invariant value per LP token. It is safe to read only when the pool is in a settled state. But `remove_liquidity` on a pool holding a native asset transfers that asset to the caller **before** it finishes updating the pool's internal accounting — a legitimate callback that momentarily leaves the pool mid-execution, with total LP supply already reduced but the invariant not yet recomputed. During that window, `get_virtual_price()` reports an inflated value.
16+
17+
On **2022-10-24**, an attacker exploited this. Using flash-loaned capital, the attacker added liquidity to the Curve pool, then called `remove_liquidity`; inside the native-token transfer callback, while the pool was mid-execution, the attacker's contract invoked Market.xyz's borrow path, which read the inflated `get_virtual_price()` and valued the attacker's LP collateral far above its true worth. Market.xyz extended a loan against that inflated valuation, and the attacker withdrew the borrowed MAI, repaying the flash loan and keeping the difference — about **\$220K** across two transactions.
18+
19+
The Curve pool was untouched: the read was a `staticcall`, no assets left the pool. The loss fell entirely on the Market.xyz market, and specifically on MAI that **QiDAO had seeded** to bootstrap it, so no user deposits were lost. **QuickSwap closed its lending service** after the exploit and initially attributed the failure to the Curve/QiDAO oracle configuration; **QiDAO stated the exploit was unrelated to its own smart contracts.** The 138 ETH of proceeds were bridged to Ethereum and routed through Tornado Cash.
20+
21+
## Timeline (UTC)
22+
23+
| When | Event | OAK ref |
24+
|---|---|---|
25+
| (standing) | Curve `remove_liquidity` transfers a native asset to the caller before settling pool accounting, leaving `get_virtual_price()` inflated mid-callback | (standing T9.010 surface) |
26+
| (pre-event) | ChainSecurity publicly warns that Curve-LP oracles reading `get_virtual_price()` are exposed to read-only reentrancy | (advisory, pre-event) |
27+
| (standing) | Market.xyz market on QuickSwap prices a Curve LP token via `get_virtual_price()` with no mid-execution check | (T9.010 surface present) |
28+
| 2022-10-24 | Attacker flash-borrows, adds liquidity, calls `remove_liquidity`; re-enters Market.xyz's borrow path during the callback and reads the inflated `get_virtual_price()` | **T9.002 → T9.010** |
29+
| 2022-10-24 | Loan extended against over-valued LP collateral; attacker withdraws MAI, repays flash loan; ~\$220K extracted across two transactions | **T9.010 extraction** |
30+
| 2022-10-24 onward | 138 ETH bridged Polygon → Ethereum → Tornado Cash | (laundering) |
31+
| post-event | QuickSwap closes its lending service; QiDAO states the exploit is unrelated to its contracts | (operator response) |
32+
33+
## What defenders observed
34+
35+
- **Pre-event (the class was already published).** ChainSecurity had documented the Curve-LP-oracle read-only-reentrancy exposure before this incident. The pre-event control is inventorying which of a protocol's price sources are external pool `view` reads that can be reached mid-execution, and treating any that lack a mid-execution check as a live exposure rather than a theoretical one.
36+
- **At-event (the vulnerable protocol shows no anomaly).** The Curve pool's own balances and invariants were intact throughout — forensic monitoring of the pool alone finds nothing. The anomaly lives on the consumer: a loan extended against a collateral valuation that was correct against the oracle read at that instant but wrong against settled state. This is what makes T9.010 harder to catch at the per-contract layer than a direct drain (T9.005), and why cross-protocol call-trace analysis is the detection surface.
37+
- **At-event (the diagnostic trace).** The direct signature is a `staticcall` to `get_virtual_price()` (or an equivalent pool view) executed while that pool's outer `remove_liquidity` frame has not returned, followed in the same transaction by a borrow against the value it produced. A per-transaction call-trace rule for view-reads of a mid-execution target is the highest-leverage at-event signal.
38+
- **Mitigation (consumer-side, Curve-independent).** The fix does not depend on Curve changing anything: the consumer asserts the pool is not mid-execution before trusting its view — invoking the pool's reentrancy lock via a state-mutating no-op that reverts if the lock is held, the pattern later standardised across Curve-LP and Balancer-LP integrations. Per-contract `ReentrancyGuard` on the consumer does not help, because the re-entry is into a `view`, not a state-mutating function.
39+
- **Disposition (seeded liquidity, not user funds).** The drained MAI was QiDAO-seeded bootstrap liquidity, so end users lost nothing — a disposition detail, neutral to how much of an attack this was. The mechanism and the missing control are identical whether the drained liquidity was protocol-seeded or user-deposited.
40+
41+
## Public references
42+
43+
- `[quillauditsmarketxyz2022]` — QuillAudits, "Decoding \$220K Read-Only Reentrancy Exploit" (primary technical breakdown: `get_virtual_price()` manipulation via `remove_liquidity`, the `D`-not-updated-while-supply-decreased window, flash loan, both attacker addresses and transaction hashes, ~\$220K MAI / 138 ETH + 700 MATIC, QiDAO-seeded funds): <https://quillaudits.medium.com/decoding-220k-read-only-reentrancy-exploit-quillaudits-30871d728ad5>
44+
- `[chainsecuritycurvelp2022]` — ChainSecurity, "Curve LP Oracle Manipulation: Post Mortem" (authoritative post-mortem of the Curve-LP-oracle read-only-reentrancy class and the consumer-side guard; the firm's prior "Heartbreaks & Curve LP Oracles" advisory pre-dated the exploit): <https://www.chainsecurity.com/blog/curve-lp-oracle-manipulation-post-mortem>
45+
- `[ambergroupmaifinance2022]` — Amber Group, "Mai Finance's Oracle Manipulation Vulnerability Explained" (independent mechanism analysis of the QuickSwap/Market.xyz oracle manipulation): <https://medium.com/amber-group/mai-finances-oracle-manipulation-vulnerability-explained-55e4b5cc2b82>
46+
- `[web3isgreatmarketxyz2022]` — Web3 Is Going Great, "Oracle manipulation attack on a QuickSwap market earns exploiter \$188,000" (contemporaneous record; QuickSwap closes lending service; QiDAO states its contracts unrelated): <https://www.web3isgoinggreat.com/?id=oracle-manipulation-attack-on-a-quickswap-market-earns-exploiter-188000>
47+
48+
## Discussion
49+
50+
Market.xyz is OAK's canonical anchor for the **read-only reentrancy via AMM LP-token oracle** shape of T9.010, and it earns the slot by being one of the earliest clean in-the-wild instances — October 2022, before the higher-profile 2023 cases. It pairs with Sentiment (April 2023, Balancer-Vault callback) and Cygnus (March 2024) as the cross-AMM anchor set: three different pool architectures, one identical consumer-side vulnerability — a lending protocol trusting a pool `view` output at a moment the pool was mid-execution. The AMM design and the specific callback differ; the trust assumption that breaks is the same.
51+
52+
The case is worth separating cleanly from the **July 2023 Curve/Vyper incident**, with which it has been confused, because they are different events with different mechanisms. The Vyper incident ([`examples/2023-07-curve-vyper.md`](2023-07-curve-vyper.md)) was a *compiler-level* `@nonreentrant`-lock failure that made Curve pools reentrancy-capable when they should not have been, and its primary losses were **direct** reentrancy drains of the pools themselves (T9.005, ~\$73M aggregate). Market.xyz was not involved in that event, and the read-only-reentrancy surface it exploited here needed no compiler bug at all: `remove_liquidity`'s native-token callback is a legitimate, by-design mid-execution window. Conflating the two overstates both and misattributes the mechanism; OAK keeps Market.xyz as the Oct-2022 Polygon T9.010 anchor and the Vyper chain as the July-2023 T9.005 anchor.
53+
54+
The durable lesson is the one ChainSecurity had already published before the exploit: **every external `view` read consumed for a pricing decision is a trust assumption that the read reflects settled state, and that assumption fails whenever the target can be mid-execution.** The mitigation is the consumer's to implement and does not require the target pool to change — which is exactly why a protocol cannot outsource this risk to the AMM it integrates.

examples/2022-2025-smart-contract-architecture-exploit-cohort.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
## Timeline
1313

14-
T9.008 from ~2022-07 (Li.Fi v1) to 2024-07 (Li.Fi ~$10M); T9.009 from 2023 onward (post-UUPS/transparent-proxy proliferation); T9.010 from 2022–2023 (class characterised, Curve/Market.xyz July 2023 canonical anchor).
14+
T9.008 from ~2022-07 (Li.Fi v1) to 2024-07 (Li.Fi ~$10M); T9.009 from 2023 onward (post-UUPS/transparent-proxy proliferation); T9.010 from 2022 (class characterised; Market.xyz on QuickSwap, October 2022 canonical anchor).
1515

1616
## T9.008 — Diamond-Pattern Facet-Audit Incomplete
1717

@@ -36,7 +36,7 @@ The attack is a specific failure of the UUPS/Transparent proxy initialisation di
3636

3737
The attacker re-enters a `view`/`pure` function on a target contract while the target is mid-execution, reading state that is temporarily inconsistent. The re-entry is a `staticcall` (EVM-enforced as read-only), and no state is double-spent on the target — the loss is realised on a **third protocol** that consumes the stale view-function output for pricing, accounting, or liquidation decisions.
3838

39-
Canonical case: **Curve Finance Vyper + Market.xyz — July 2023.** The Vyper compiler's `@nonreentrant` lock failure (Vyper 0.2.15–0.3.0) opened a reentrancy window. Market.xyz relied on Curve pool LP-token pricing via `get_virtual_price()` — a view function. The attacker manipulated Curve pool state mid-transaction; Market.xyz read the stale `get_virtual_price()` and extended a loan against an inflated LP-token valuation. The Curve pool itself lost nothing; Market.xyz lost the loan principal.
39+
Canonical case: **Market.xyz — QuickSwap (Polygon), October 2022.** Market.xyz priced Curve LP-token collateral via `get_virtual_price()` — a view function. Curve's `remove_liquidity` transfers a native asset to the caller before settling the pool's accounting, so during that callback `get_virtual_price()` returns an inflated value; the attacker re-entered Market.xyz's borrow path in that window and extended a loan against inflated collateral (~$220K, flash-loan-funded). The Curve pool itself lost nothing; Market.xyz lost the loan principal. See `examples/2022-10-market-xyz-curve-lp-oracle-read-only-reentrancy.md`. (Distinct from the July 2023 Curve/Vyper compiler-lock event, whose primary losses were direct-drain reentrancy — T9.005 — and did not involve Market.xyz.)
4040

4141
Additional cases: **Cygnus Finance — March 2024** — read-only reentrancy in DeFi lending via Balancer LP integration. See `examples/2024-03-cygnus-finance-read-only-reentrancy.md`.
4242

@@ -49,5 +49,5 @@ The T9.005 mitigation (checks-effects-interactions + `ReentrancyGuard`) does not
4949
- `[eip2535]` — EIP-2535 Diamond Proxy standard
5050
- `[openzeppelininitializable]` — OpenZeppelin Initializable contract and `initializer` modifier
5151
- `[curvevyper2023]` — Curve Finance Vyper compiler reentrancy incident (July 2023)
52-
- `[marketxyz2023]` — Market.xyz Curve pool read-only reentrancy loss
52+
- `[quillauditsmarketxyz2022]` — Market.xyz (QuickSwap / Polygon) Curve LP-oracle read-only reentrancy, October 2022, ~$220K (see `examples/2022-10-market-xyz-curve-lp-oracle-read-only-reentrancy.md`)
5353
- `[cygnus2024]` — Cygnus Finance read-only reentrancy (March 2024)

0 commit comments

Comments
 (0)