# IMD Contract-Build & Token-Launch Checklist — Merge 02

One document combining 15 incident notes. Each item researched the same shape: what happened (date,
chain, loss, what was taken), the root cause, the general pattern, and the check that would have
caught it before deployment. Each incident below keeps its loss, root cause, pattern, check and
**every source URL** its note carried. Where notes disagree (loss, date, whether a first-party
post-mortem exists), the disagreement is stated, not silently resolved. All 15 inputs
(`item-resupply`, `item-gmx-v1`, `item-kinto`, `item-arcadia`, `item-superrare`, `item-bunni`,
`item-typus`, `item-balancer`, `item-yearn-yeth`, `item-truebit`, `item-crosscurve`, `item-moonwell`,
`item-resolv`, `item-drift`, `item-kelp`) were present and non-empty, so **no incident note is
missing**; where a note reports a project post-mortem or named security-firm write-up it could not
retrieve, that gap is recorded per incident under **Gaps** and summarised at the end.

| # | Incident | Date | Chain(s) | Loss (note range) | Class |
|---|----------|------|----------|-------------------|-------|
| 1 | Resupply | 2025-06-26 | Ethereum | ~$9.5M–$10M | ERC-4626 inflation + rounding-to-zero |
| 2 | GMX v1 | 2025-07-09 | Arbitrum | ~$42M (~$5M bounty) | Cross-contract reentrancy |
| 3 | Kinto ($K) | 2025-07-10 | Arbitrum | ~$1.55M (577 ETH) | Uninitialized proxy (CPIMP) |
| 4 | Arcadia Finance | 2025-07-14/15 | Base | $3.6M | Unvalidated calldata / arbitrary call |
| 5 | SuperRare staking | 2025-07-28 | Ethereum | ~$730K (~11.9M RARE) | Inverted access control |
| 6 | Bunni v2 | 2025-09-01/02 | Ethereum, Unichain | ~$8.4M | Rounding direction in state update |
| 7 | Typus Finance | 2025-10-15 | Sui | ~$3.44M | Discarded authorization result |
| 8 | Balancer v2 | 2025-11-03 | Ethereum + L2s | $94.8M–$128M | Rounding asymmetry in scaling |
| 9 | Yearn yETH | 2025-11-30 | Ethereum | ~$9M | Unchecked solver + reachable init |
| 10 | Truebit | 2026-01-08 | Ethereum | $26.4M–$26.6M | Integer overflow (pre-0.8.0) |
| 11 | CrossCurve | 2026-01/02 | Ethereum, Arbitrum, +6 | $1.4M–$3M | Bridge fast-path missing validation |
| 12 | Moonwell cbETH | 2026-02-15 | Base | $1,779,044.83 bad debt | Oracle misconfiguration |
| 13 | Resolv (USR) | 2026-03-22 | Ethereum | ~$25M | Privileged-key / supply-chain |
| 14 | Drift Protocol | 2026-04-01 | Solana | ~$285.26M | Signer compromise + no timelock |
| 15 | KelpDAO rsETH | 2026-04-18 | Ethereum/Unichain | ~$292M | Cross-chain validation + 1-of-1 DVN |

---

## 1. Resupply — 2025-06-26, Ethereum
Note: `item-resupply`.

**Loss / taken (sources disagree).** ~$10M (BlockSec); "$10M of reUSD bad debt" (Resupply post-mortem); $9.8M (rekt.news); $9.6M (Decrypt); $9.56M (Ackee); $9.5M (QuillAudits; CryptoPotato; The Block) — range **~$9.5M–$10M**. The attacker borrowed 10,000,000 reUSD from the freshly deployed crvUSD/wstUSR "ResupplyPair" market (0x6e90) with ~1 wei of vault-share collateral, swapping to ~9.33M scrvUSD → ~9.81M crvUSD → WETH and extracting ~1,581 ETH (BlockSec, QuillAudits, The Block).

**Root cause.** Not a key/signer/multisig compromise — a price/configuration flaw. The market priced its ERC-4626 collateral (CurveLend cvcrvUSD shares) as `_price = IERC4626(vault).convertToAssets(1e18)` and computed the exchange rate by floor division, `_exchangeRate = 1e36 / oracle.getPrices(collateral)` (BlockSec, Ackee). It was deployed while its CurveLend vault held zero deposits, so the attacker inflated the share price by donating 2,000 crvUSD to the vault controller (0x8970), then deposited ~2 crvUSD to mint 1 wei of share, making 1 wei worth ~2e18 crvUSD (a price near 2e36) (BlockSec, Resupply post-mortem). Above 1e36 the division rounded **down to zero**, so `_exchangeRate = 0`, forcing `_ltv = 0`; `_ltv <= _maxLTV` then passed for any size and the attacker borrowed the pair's full 10M reUSD debt limit (Resupply post-mortem). BlockSec notes the price-oracle implementation resembled a Curve template whose applicability limits Curve's docs had flagged; Resupply's post-mortem says the code was in scope for multiple audits that missed it, and the issue was unique to this pair.

**Pattern.** ERC-4626 vault inflation / empty-market "donation" attack combined with a rounding-to-zero bug that degenerates a solvency check: (1) a price path divides by an attacker-inflatable value with no lower bound; (2) a fresh market starts with negligible liquidity so its share price is trivially manipulable; (3) a safety check multiplies by the manipulated price, so driving it to zero passes unconditionally:

```
price        = vault.convertToAssets(1e18)   // donation -> ~2e36
exchangeRate = 1e36 / price                  // floor division -> 0
ltv          = borrowerAmount * exchangeRate * P / E / collateralAmount  // 0
require(ltv <= maxLTV)                       // 0 <= maxLTV -> always true
```

**Check that would have caught it.** Fuzz the oracle price across the full `uint256` range (>= 1e36) and assert the invariant **`_exchangeRate > 0`** and that a 1-wei-collateral position is never solvent while debt > 0; enforce with `require(exchangeRate > 0)` (or round up) (Ackee; Resupply post-mortem). Validate initialization before enabling a market: require the ERC-4626 vault seeded with minimum non-zero liquidity, or reject a market whose `convertToAssets(1e18)` exceeds a sane bound. ChainSecurity said ensuring secure initialization "wasn't in scope"; Electi confirmed the rounding issue was in scope but the deployment was not, so the gap sat between code review and deployment validation (rekt.news). Review rule: any `a / b` whose `b` derives from an external or attacker-influenceable oracle must carry a documented lower bound before feeding a health/solvency calc (Ackee).

**Sources.** https://blocksec.com/blog/in-depth-analysis-and-reflections-on-the-resupply-protocol-attack-incident; https://web.archive.org/web/20250706/https://mirror.xyz/0x521CB9b35514E9c8a8a929C890bf1489F63B2C84/ygJ1kh6satW9l_NDBM47V87CfaQbn2q0tWy_rtp76OI (Resupply post-mortem); https://rekt.news/resupplyfi-rekt; https://ackee.xyz/blog/resupply-hack-analysis/; https://quillaudits.com/blog/hack-analysis/resupply-hack-analysis; https://decrypt.co/327148/hacker-drained-9-6-million-from-defi-stablecoin-protocol-resupply; https://cryptopotato.com/stablecoin-protocol-resupply-suffers-9-5m-exploit/; https://www.theblock.co/news/ecosystems/2025-06-26-resupply-exploited-blocksec-359764; https://paragraph.com/@resupply/BQGdyz30aJ1uZJ2Lc2UT

**Disagreements / gaps.** rekt's body says June 25; its dateline (June 27) and every primary source say June 26, which is used. Loss range $9.5M–$10M; no missing source.

---

## 2. GMX v1 — 2025-07-09, Arbitrum
Note: `item-gmx-v1`.

**Loss / taken.** ~**$42M** in tokens from the **GLP liquidity pool** on GMX v1 (Arbitrum); ~$9.6M bridged to Ethereum [1][2]. Two days later GMX called it a **coordinated vulnerability disclosure**: the attacker returned ~$40M to the GMX multisig and kept ~$5M as a white-hat bounty [1][2].

**Root cause.** A **cross-contract reentrancy** in `executeDecreaseOrder()` of the orderbook contract. When a keeper bot called it to execute a decrease-order, the contract refunded gas to the account that opened the position; if that account was a smart contract, the refund callback handed control to the attacker mid-transaction. The attacker then called `increasePosition()` directly on the **Vault**, bypassing `PositionRouter`/`PositionManager` — the intermediaries that update the global average short price — opening a large short that drove BTC's global average short price from ~$109,505 to ~$1,913 and distorted GLP pricing. The vulnerability was introduced in a **2022 fix for a separate $1M bug bounty** paid to Collider VC; PeckShield confirmed the bug "was introduced in the fix for the $1M bounty issue." The post-audit changes to `executeDecreaseOrder` and vault logic were **never re-audited** [1][2].

**Pattern.** Reentrancy (cross-contract, callback-based); the reentrant call distorts a shared global accumulator (`globalShortAveragePrices`) feeding AUM → GLP pricing, letting the attacker mint GLP cheap and redeem dear [1][2]:

```
function executeDecreaseOrder(address _account, ...) external {
    // ... decrease logic ...
    (bool success, ) = _account.call{value: gasRefund}(""); // contract _account reenters here
    // state updates after the external call are vulnerable to reentry
}
```

**Check that would have caught it.** (1) Apply `nonReentrant` (OpenZeppelin `ReentrancyGuard`) to `executeDecreaseOrder` and every order-execution path that makes external calls before finalising state; a unit test deploys a mock attacker as the account, calls `executeDecreaseOrder`, reenters the Vault from the callback, and asserts it reverts. (2) Fuzz/invariant test opening random positions from EOA and contract accounts, executing decrease-orders, and asserting `globalShortAveragePrice` never deviates more than a few basis points from the oracle price — this catches the $109K → $1.9K collapse [1][2].

**Sources.** 1. rekt.news "GMX — Rekt": https://rekt.news/gmx-rekt/ (leaderboard confirms $42M, 9 July 2025, Arbitrum). 2. QuillAudits "How GMX V1 Lost $42 Million to a Reentrancy Attack?": https://quillaudits.com/blog/hack-analysis/how-gmx-lost-42m

**Gaps.** GMX's official post-mortem (Notion: https://gmxio.notion.site/GMX-v1-Exploit-Post-Mortem-186af3d74056803e909ed1a6a7ae35aa) requires auth and could not be accessed; BlockSec/SlowMist/CertiK/Halborn/Verichains searches returned 404/403 or unrelated pages; the two sources agree on all material facts.

---

## 3. Kinto ($K) — 2025-07-10, Arbitrum
Note: `item-kinto`.

**Loss / taken (disagreement on mint size).** **~$1.55M = 577 ETH** drained from a Uniswap v4 pool and a Morpho Blue vault [1][4]; The Block rounded to ~$1.6M [7]; Morpho suppliers left with bad debt ([1] cites ~$3.2M owed vs ~$2.4M loans). The attacker **minted 110,000 counterfeit `$K`** and dumped them [5][4]; `$K` fell ~90–95% in under an hour (~$7.69 → ~$0.50), erasing ~$13M of value [4][5]. Early reporting (CoinJournal, relayed) said **"nearly 7 million"** `$K` (>3x the <2M supply) [7][8]; the post-mortem, rekt and The Defiant say **110,000** [1][4][5].

**Root cause / date.** Not a Kinto business-logic bug, key theft or tricked signer: a **deployment/configuration flaw in an off-the-shelf ERC-1967 upgradeable proxy**, exploited as the "CPIMP" (Clandestine Proxy In The Middle of Proxy) attack [1][2][3]. `$K`'s Arbitrum token was a transparent-proxy ERC-20 whose proxy was **deployed without being initialized in the same transaction**, leaving an uninitialized "danger zone" [1][3]. The attacker front-ran `initialize()`, writing a malicious "CPIMP" implementation into a proxy slot; it forwarded normal calls to the legit implementation, restored itself after every transaction (surviving upgrades), and spoofed the storage slot / `Upgraded` events explorers read, hiding the backdoor for weeks [2]. On activation the attacker repointed the proxy to a malicious minter, minted `$K`, and drained liquidity [1]. Dozens of contracts across chains (EtherFi, Pendle, Berachain, Orderly, KIP, MYX) shared the flaw; Kinto was missed during a 36-hour war room and hit ~12h after public disclosure [2][4]. **Date:** attack executed 10 July 2025 ~08:40 UTC [1][4]; the post-mortem's TL;DR says the upgrade was "9 July 20:17 UTC" while its timeline says 10 July 08:40 (self-contradictory); rekt and The Defiant date the attack 10 July [1][4][5].

**Pattern.** Uninitialized proxy / initialization front-running (CPIMP):

```
impl  = new TokenLogic();
proxy = new ERC1967Proxy(impl, "");  // deployed with EMPTY init data
attacker.initProxy();                // front-runs; writes attacker implementation
TokenLogic(proxy).initialize(admin); // legit init, but slot already hijacked
```

**Check that would have caught it.** Atomic-deployment unit test: assert the proxy is constructed with non-empty init data and initialized in the constructor, e.g. `new ERC1967Proxy(impl, abi.encodeCall(Logic.initialize, (admin)))`, and that the EIP-1967 implementation slot already equals the legit implementation right after deployment (no window) [3]. Review rule / invariant: require every upgradeable proxy to deploy and `initialize()` in one transaction (or via `CREATE2` precomputed addresses for circular deps); monitor that the implementation slot never changes outside an event-logged upgrade and that an uninitialized proxy is never observable on-chain; where delayed init is unavoidable, restrict `initialize()` to a single hard-coded deployer as a last resort [3].

**Sources.** 1. https://medium.com/mamori-finance/%EF%B8%8F-post-mortem-k-proxy-hack-our-path-forward-c2c3809882c6 (Kinto/Ramon Recuero post-mortem). 2. https://dedaub.com/blog/the-cpimp-attack-an-insanely-far-reaching-vulnerability-successfully-mitigated/ 3. https://www.nethermind.io/blog/cpimp-attack-how-uninitialized-proxies-are-exploited-during-deployment 4. https://rekt.news/just-bad-luck 5. https://thedefiant.io/news/blockchains/kinto-token-tanks-90-as-backdoor-disclosure-lets-attacker-mint-110-000-tokens-and-drain 6. https://thedefiant.io/news/blockchains/ethereum-l2-kinto-shuts-down-months-after-exploit-as-fundraising-options-disappeared (Sep-2025 shutdown; 577 ETH). 7. https://itokenly.com/hacks/kinto (aggregator; The Block's ~$1.6M). 8. https://vcpcryptonews.com/2025/07/10/kinto-coin-crashes-99-after-arbitrum-contract-exploit/ (early "nearly 7M" report).

**Gaps.** No Kinto-specific analysis from BlockSec/SlowMist/CertiK/Halborn/Verichains/QuillAudits was found; the technical write-ups come from Dedaub, Nethermind and the Venn/Dedaub researchers, plus rekt.news.

---

## 4. Arcadia Finance (ArcadiaFi) — 2025-07-14/15, Base
Note: `item-arcadia`.

**Loss / taken.** **$3.6M** on **Base**, in two waves: ~$2.5M then ~$1M about 25 minutes later while the team was responding. rekt's leaderboard lists 7/15/2025 and $3,600,000 (https://rekt.news/leaderboard); the article confirms the two-wave total ~$3.6M (https://rekt.news/arcadiafi-rekt).

**Root cause.** An unchecked external call in `SwapLogic._swapViaRouter()` on the **RebalancerSpot** contract. Call chain: `_RebalancerSpot.rebalance()` → `AccountV1.flashAction()` → `actionTarget.executeAction()` → `_swap()` → `_swapViaRouter()` → `router.call(data)`. `swapData` accepted arbitrary calldata and the router address was never whitelist-validated. The attacker registered a malicious contract as both a router and a whitelisted Arcadia Account, then used the trusted Rebalancer context to drain victim accounts. Three of four prior audits (Trust Security, Sherlock, Renascence Labs) had excluded the Rebalancer from scope; Pashov Group audited it in Q3 2024, flagged the missing input validation, but closed it as unexploitable and never told ArcadiaFi (https://rekt.news/arcadiafi-rekt).

**Pattern.** Unvalidated input / calldata (arbitrary external call): a trusted contract passes caller-supplied `data` to a low-level `call()` on an unvalidated target:

```
function swapViaRouter(address router, bytes calldata data) internal {
    (bool success, ) = router.call(data);   // router and data both untrusted
    require(success);
}
```

**Check that would have caught it.** (1) Every `call()`/`delegatecall()` to a caller-supplied address must either validate the target against a hardcoded/governance whitelist or restrict calldata to known selectors; a unit test asserting `swapViaRouter` reverts for a non-whitelisted router or arbitrary calldata catches this. (2) Invariant: "only whitelisted routers can receive calls from the Rebalancer, and no funds can leave a victim Account through a Rebalancer call initiated by a different Account" — fuzzed over arbitrary Account/router/calldata — surfaces the path Pashov closed as unexploitable.

**Sources.** https://rekt.news/arcadiafi-rekt (full breakdown, audit history, attacker trail); https://rekt.news/leaderboard (amount/date confirmation).

**Gaps.** The project's Medium post-mortem (arcadiafi.medium.com) and Pashov's post-exploit analysis could not be retrieved (Medium blocked, blogs unreachable); they are referenced in the rekt article, which credits ArcadiaFi post-mortem, CertiK Alert (~$1.6M initial), Cyvers Alerts (+$1M), Chaofan Shou (technical breakdown) and Pashov Auditing Group.

---

## 5. SuperRare staking (RareStakingV1) — 2025-07-28, Ethereum
Note: `item-superrare`.

**Loss / taken (USD figures disagree).** About **11.9M RARE** worth roughly **$730,000** [1][2][3][6]. Victim `0xfFB512B9176D527C5D32189c3e310Ed4aB2Bb9eC`; attack tx `0xd8137...5ba6e3c1`; exploiter `0x5B9B4...2D4a2` [2][3][14][15]. Only the staking/reward pool was hit; the RARE token and marketplace were unaffected [3][6][7]. Verichains gives 11,907,874.71 RARE [2]. USD: ~$730,000 (SlowMist, SolidityScan, CertiK/PeckShield/Blockaid, Cointelegraph) [1][3][5][9]; **$731,809.68** (Cyvers) [13]; **~$680,000** (Verichains) [2]; **$710,000** (SupLabsYi via Quadriga) [9]. **Front-running twist:** Blockaid says the first attacker deployed an exploit contract but the draining tx came from a separate front-runner one block later [7][9][12]; Cyvers confirmed [13]. Cyvers traced funding to Tornado Cash ~186 days earlier [7][9][13].

**Root cause.** Not a stolen key, tricked signer or key-handling failure — a smart-contract logic bug. `updateMerkleRoot` was meant to let only `owner()` or one hard-coded authorized address set the reward Merkle root; the deployed `require` was inverted:

```
require(msg.sender != owner() || msg.sender != address(0xc2F394a45e994bc81EfF678bDE9172e10f7c8ddc), "Not authorized to update merkle root");
```

No address is both `owner()` and `0xc2F3...8ddc`, so at least one `!=` is always true, the `||` is always true, and the check never reverts (everyone is admitted) [3][4][10], so anyone could set a malicious root and claim the full pool [1][2][3]. Veridise adds (by the team's own account) the check was introduced **after** the contract's initial review [4]. Co-founder Jonathan Perkins told Cointelegraph the contracts were audited and unit-tested but the bug was introduced late and not covered by final test scenarios; 61 wallets were affected, no core protocol funds lost, affected users made whole [5].

**Pattern.** Broken / inverted access control (OWASP SC Top 10 SC01) — the guard names who is *excluded*, not who is *allowed* [3][4], so root control equals claim control [2][4][10]:

```
function updateMerkleRoot(bytes32 newRoot) external {
    require(msg.sender != owner() || msg.sender != AUTHORIZED, "Not authorized");
    currentClaimRoot = newRoot;   // tautology: every caller passes
}
```

**Check that would have caught it.** Unit test: call `updateMerkleRoot` from a fresh non-privileged address and assert it *reverts* and `currentClaimRoot` is unchanged; assert `owner()` and the authorized address succeed — the negative test final scenarios omitted [5][10]. Fuzz/invariant: "for every `msg.sender` that is neither `owner()` nor the authorized address, `updateMerkleRoot` must revert" [4]; statically flag access-control `require`s that compare `msg.sender` with `!=` rather than `==` (SolidityScan, Bug Hunter, Veridise's Vanguard flagged this contract) [3][4][10]. Operational: re-audit and re-test every post-audit change, however small [4][5].

**Sources.** 1. https://hacked.slowmist.io/ 2. https://blog.verichains.io/p/superrare-exploit-analysis 3. https://blog.solidityscan.com/superrare-hack-analysis-488d544d89e0/ 4. https://veridise.com/blog/audit-insights/smart-contract-access-control-superrare-exploit/ 5. https://cointelegraph.com/news/superrare-730-000-exploit-was-easily-preventable-experts-weigh-in 6. https://crypto.news/superrare-nft-platform-loses-730k-in-rare-token-exploit/ 7. https://cryptonews.com/news/breaking-superrare-staking-contract-hit-by-730k-exploit-rare-token-unscathed/ 8. https://protos.com/tornado-cash-user-hacks-superrare-staking-contract-steals-730k-in-rare/ 9. https://www.quadrigainitiative.com/hackfraudscam/superrareupdatemerklerootbackwardspermissionchecklogic.php 10. https://docs.bughunter.live/blog/superrare-bughunter/ 11. https://www.thecoinrepublic.com/2025/07/28/superrare-hack-730k-stolen-in-staking-exploit-with-frontrun-twist/ 12. https://x.com/blockaid_/status/1949757641720242494 13. https://x.com/CyversAlerts/status/1949766758635610276 14. https://etherscan.io/address/0xfFB512B9176D527C5D32189c3e310Ed4aB2Bb9eC 15. https://etherscan.io/tx/0xd813751bfb98a51912b8394b5856ae4515be6a9c6e5583e06b41d9255ba6e3c1 16. rekt.news leaderboard (checked; **no dedicated SuperRare entry**): https://rekt.news/leaderboard/

**Gaps.** No first-party SuperRare/RareDAO post-mortem found (as of 2025-07-28 cryptonews.com noted none was released; Quadriga found no public acknowledgement; Perkins' Cointelegraph remarks are closest [5]); no rekt.news article on this incident [16]. Differing USD amounts are reported with their sources rather than reconciled [2][9][13].

---

## 6. Bunni v2 — 2025-09-01/02, Ethereum & Unichain
Note: `item-bunni`.

**Loss / taken.** ~**$8.4M** from Bunni v2, a Uniswap v4-hook AMM, across Ethereum and Unichain. BlockSec first flagged ~$2.3M from the Ethereum USDC/USDT pool; CertiK found the same pattern on Unichain's ETH/weETH pool (rekt.news). Breakdown: ~$2.4M Ethereum (USDC/USDT), ~$6M Unichain (ETH/weETH). The attacker flashloaned a swap to push spot price extreme, shrinking USDC active balance to 28 wei; 44 tiny withdrawals exploited rounding to erode it to 4 wei, collapsing computed liquidity 84.4%; a large swap reversed this artificial hole (+16.8% bounce), which the attacker sandwiched. Net ~1.33M USDC + ~1M USDT from Ethereum alone (Bunni post-mortem). Unichain funds were bridged to Ethereum in 100-ETH chunks via Across, consolidated into two Tornado-Cash-funded wallets (rekt.news). **Date:** Sept 1 (rekt/BlockSec/CertiK); Bunni's own post-mortem says Sept 2 (timezone).

**Root cause.** A rounding-direction error in `BunniHubLogic::withdraw()`. LP withdrawals reduced the idle balance with `balance.mulDiv(shares, currentTotalSupply)` (rounds down); devs assumed this rounds the active balance down — "safe" because lower liquidity raises swap impact — which holds for one operation but breaks across sequences: draining active USDC to 28 wei then 44 withdrawals eroded it to 4 wei (85.7% drop, disproportionate to shares burned), collapsing total liquidity 84.4%, then a swap reversed it (+16.8%) creating a sandwichable gap. Fix: `mulDiv` → `mulDivUp` (Bunni post-mortem). A pure smart-contract arithmetic bug, not key compromise or oracle manipulation. Trail of Bits, Pashov and Cyfrin audited the code; Cyfrin's June 2025 audit warned "it is statistically likely that there are more complex bugs still present" and recommended more fuzzing before scaling — yet TVL rose $2.4M → $23.9M (rekt.news).

**Pattern.** Rounding / precision error in multi-step state updates — a rounding choice "safe" once becomes exploitable across many small ops, distorting state enough that a later op reverses it:

```
// Vulnerable: BunniHubLogic::withdraw()
uint256 newBalance = balance - balance.mulDiv(shares, currentTotalSupply);   // rounds DOWN
// Fix:
uint256 newBalance = balance - balance.mulDivUp(shares, currentTotalSupply);
```

**Check that would have caught it.** Invariant fuzz test: for any sequence of minimal-share withdrawals on a pool whose active balance for one token is below threshold T (e.g. 1000 wei), the drop in computed total liquidity must not exceed the proportional share reduction by more than epsilon E (e.g. 1%). A Foundry/Medusa campaign pushing one token's active balance to dust via a large swap, then running randomized minimal-withdrawal sequences, catches it; Cyfrin's post-exploit fork test confirmed reproducibility (Cyfrin gist). Review rule: every `mulDiv` in state-update arithmetic must justify its rounding direction across *sequences*, not only singly.

**Sources.** https://blog.bunni.xyz/posts/exploit-post-mortem/; https://rekt.news/bunni-rekt/; https://gist.github.com/giovannidisiena/716324d50b6649be3a0e91395890917e; https://x.com/phalcon_xyz/status/1962743751568433416; https://x.com/CertiKAlert/status/1962782447931703548; https://x.com/hackenclub/status/1962768341367390643; https://x.com/bunni_xyz/status/1962773674634756450; https://github.com/Cyfrin/cyfrin-audit-reports/blob/main/reports/2025-06-10-cyfrin-bunni-v2.1.pdf; https://github.com/trailofbits/publications/blob/master/reviews/2025-01-bacon-labs-bunniv2-securityreview.pdf

**Disagreements / gaps.** Date Sept 1 vs Sept 2 (timezone). No missing source.

---

## 7. Typus Finance — 2025-10-15, Sui
Note: `item-typus`.

**Loss / taken (headline figures disagree).** **~$3.44M** USD at the time, per the project post-mortem: **588,357.9 SUI, 1,604,034.7 USDC, 0.6 xBTC, 32.227 suiETH** [1]. SlowMist/MistTrack give the same breakdown and ~$3.44M [2]; Halborn [3] and Defimon [4] also cite ~$3.44M. crypto.news/Hacken ~$3.4M [6], Unchained $3.4M [7], Coinspeaker ~$3M [8]. First malicious tx 13:05 UTC; the team was alerted 13:24 UTC and paused all contracts by 13:39 UTC, but the pool was already empty [1][2]. The attacker swapped to USDC on Sui and bridged ~3,430,717 USDC to Ethereum via Circle CCTP (14 txs), converting to 3,430,241.91 DAI [2]. SAFU, DeFi Options Vaults and open-position collateral were unaffected; the vaults' crankers validated prices against an independent oracle and rejected the manipulated value [1].

**Root cause.** A smart-contract bug, not a key/signer compromise. In `typus_oracle/sources/oracle.move`, public `update_v2` was meant to restrict price updates to a whitelist: it called `vector::contains(&update_authority.authority, &sender)` but the returned `bool` was computed and **discarded** — never wrapped in `assert!`. `UpdateAuthority` was a **shared object** (`transfer::share_object`), so anyone could reference it; the whitelist existed but was never enforced, letting any address overwrite oracle prices [1][2], confirmed in the pre-fix source, commit `a918e98` [9]. Two process failures compounded it: the module (deployed Nov 13, 2024) was **excluded from the May 2025 MoveBit audit scope**, and monitoring alerts did not fire immediately [1].

**Pattern.** Missing access control / authorization bypass (a check whose result is not enforced):

```
public fun update_v2(oracle, update_authority /* shared, anyone may pass */,
                     price, twap_price, clock, ctx) {
  vector::contains(&update_authority.authority, &tx_context::sender(ctx)); // bool discarded
  update_(oracle, price, twap_price, clock, ctx);                          // runs regardless
}
```

The attacker set one oracle to 651,548,270 and another to 1, then used the pool's oracle-priced swap path to trade a trivial amount for a large one (e.g. 1 SUI for ~60,000,000 xBTC base units ≈ 0.6 BTC), repeating across pools [2][4].

**Check that would have caught it.** (1) Call the price-update entry point from a non-whitelisted address and assert the tx **aborts** — the pre-fix code would not abort ([9]). (2) Every security predicate returned by a pure function (e.g. `vector::contains`) must be consumed by an `assert!`/`if`; flag discarded authorization results (Move emits no unused-return warning, so this needs an explicit rule) [5]. Invariant: *the set of addresses able to mutate oracle prices equals the configured whitelist* (fuzz non-whitelisted senders, requiring abort); put every state-mutating module in audit scope before deployment.

**Sources.** 1. Project post-mortem: https://medium.com/@TypusFinance/typus-finance-tlp-oracle-exploit-post-mortem-report-response-plan-ce2d0800808b 2. SlowMist: https://slowmist.medium.com/is-the-move-language-secure-the-typus-permission-validation-vulnerability-755a5175f7c3 3. Halborn: https://www.halborn.com/blog/post/explained-the-typus-finance-hack-october-2025 4. Defimon: https://defimon.xyz/blog/typus-finance-hack-october-2025 5. mehvetero: https://mehvetero.com/how-a-missing-assert-drained-3-44m-from-typus-finance-and-why-the-code-looked-correct 6. crypto.news: https://crypto.news/sui-based-typus-finance-plummets-35-after-oracle-exploit/ 7. Unchained: https://unchainedcrypto.com/sui-based-typus-finance-loses-3-4-million-in-hack/ 8. Coinspeaker: https://www.coinspeaker.com/typus-finances-unaudited-contract-loses-3m-3rd-major-sui-exploit-in-2025/ 9. Pre-fix source, commit `a918e98`: https://github.com/Typus-Lab/typus/blob/a918e98c4f7d3a28d0d809d3263d8c21e90d3c01/typus_oracle/sources/oracle.move 10. rekt.news Oct 2025 brief: https://newsletter.rekt.news/p/blockchain-security-brief-aws-outage-kadena-s-collapse-copy-paste-exploits 11. rekt.news leaderboard (checked; **no dedicated Typus Oct 2025 entry**): https://rekt.news/leaderboard

**Gaps.** No dedicated rekt.news post-mortem [11]; no BlockSec/CertiK/Verichains write-up specific to Typus. The Medium pages [1][2] returned HTTP 403 and were read via Wayback: http://web.archive.org/web/20261004122615/https://medium.com/@TypusFinance/typus-finance-tlp-oracle-exploit-post-mortem-report-response-plan-ce2d0800808b and http://web.archive.org/web/20261004122627/https://slowmist.medium.com/is-the-move-language-secure-the-typus-permission-validation-vulnerability-755a5175f7c3

---

## 8. Balancer v2 — 2025-11-03, Ethereum & L2s
Note: `item-balancer`.

**Loss / taken (sources disagree).** Composable Stable Pools exploited on Ethereum and multiple L2s (Arbitrum, Polygon, Base, Sonic, Optimism); forks Beets and Beethoven X hit by copycats. rekt.news: **$128M**. Balancer's own post-mortem gave two inconsistent figures: **$94.8M** (introduction) vs **$121.1M** (Financial Impact), unreconciled. QuillAudits: ~**$125M** (rekt.news; QuillAudits).

**Root cause.** A rounding direction mismatch in the Composable Stable Pool scaling logic let systematic precision loss be weaponised against the pool invariant D. Upscaling used `mulDown` while downscaling used `divUp`/`divDown`, so an EXACT_OUT (`_swapGivenOut`) path could understate the required `amountIn`. By executing carefully tuned micro-swaps inside a single atomic `batchSwap()`, the attacker compounded wei-level truncations, quietly reducing D (and thus BPT price = D/totalSupply) without reverting. The attacker simulated swaps off-chain with a replica of `StableMath` to place each swap on a rounding cliff before executing on-chain (QuillAudits; rekt.news).

**Pattern.** Precision / rounding asymmetry in contract math, amplified by batch atomicity:

```
function _upscale(amount, scalingFactor):   return amount.mulDown(scalingFactor)  // toward zero
function _downscale(amount, scalingFactor): return amount.divUp(scalingFactor)    // inconsistent
```

The asymmetric rounding made `_swapGivenOut` compute a smaller `amountIn` than the true cost; repeating inside one `batchSwap` eroded D across many iterations atomically, bypassing per-swap checks; the attacker then redeemed BPT at a deflated virtual price (QuillAudits; Balancer PR #322).

**Check that would have caught it.** (1) Fuzz swap sequences through `batchSwap` and assert the invariant `D_post >= D_pre - fees` across extreme balance ratios, single-wei amounts, large amplification parameters and different token decimals. (2) Round-trip property test: for every `_upscale(x, s)` / `_downscale(y, s)`, assert compose-and-round-trip never produces net invariant loss; audit rule: every scaling path must round conservatively for the pool (never under-report `amountIn`) (QuillAudits).

**Sources.** https://rekt.news/out-of-balance/ (rekt.news post-mortem); https://quillaudits.com/blog/hack-analysis/the-balancer-hack (QuillAudits, Nov 7 2025); https://github.com/balancer/balancer-v2-monorepo/pull/322 (Balancer PR #322 "Stable pool rounding").

**Gaps.** The official Balancer post-mortem (medium.com/balancer-protocol/balancer-v2-pools-exploit-post-mortem-f44a74e9ac21) was Cloudflare-blocked; no accessible BlockSec/SlowMist/CertiK/Halborn/Verichains analysis was found. rekt.news cites the post-mortem's figures and is used as a proxy where noted.

---

## 9. Yearn yETH — 2025-11-30, Ethereum
Note: `item-yearn-yeth`.

**Loss / taken (amount disagrees).** **~$9M** = **~$8M** from the yETH weighted stableswap pool + **~$0.9M** from the yETH/WETH Curve pool, at block **23,914,086**, tx **21:11 UTC** (Yearn disclosure; rekt.news; Bitcoin.com). Taken: the pool's LSTs (apxETH, sfrxETH, wstETH, cbETH, rETH, ETHx, mETH, wOETH) plus **298.35 WETH** from the Curve pool. The attacker **minted ~2.3544e56 yETH LP** from dust and drained both pools, then sent **1,000 ETH (~$3M)** to Tornado Cash in the same block; **857.49 pxETH (~$2.33–2.39M)** was later recovered with Plume and Dinero (Yearn disclosure; rekt.news; The Block). Early coverage reported a **"$3M"** drain (MSN), matching the 1,000 ETH laundered then; Yearn's settled figure is ~$9M. Yearn's V2/V3 vaults were unaffected.

**Root cause.** Not a stolen key, tricked signer or oracle attack — a numeric flaw in the pool's own code. The custom weighted-stableswap pool solves its invariant variable `D` (LP supply) with an iterative fixed-point `_calc_supply()` using **unchecked ("unsafe") arithmetic**. Under an adversarially imbalanced pool, the iteration's product term `r = r * sp / s` suffered **integer round-down to exactly 0** (`unsafe_div` floors), permanently collapsing `Π` to zero and degenerating the invariant into near-constant-sum behaviour, so `_calc_supply` returned a **grossly inflated supply** minted as real yETH. A second unchecked **underflow** (`unsafe_sub` on `l - s*r`, wrapping to ~2^256) fired in the degenerate zero-supply state and minted ~2.35e56 LP. Two design choices made it reachable: `add_liquidity` updated `Π` incrementally (corruptible) while `remove_liquidity(0)` recomputed it from scratch, and the `prev_supply == 0` **bootstrap branch was never permanently disabled** after deployment (Yearn disclosure; BlockSec; banteg report).

**Pattern.** Rounding / precision bug in an unchecked fixed-point solver plus a reachable initialization path — a cousin of the Balancer rounding exploit; a16z groups both as numerical edge cases exploited under engineered conditions (a16z):

```
sp = unsafe_div( unsafe_sub(l, unsafe_mul(s, r)), d )   # can underflow -> huge sp
r  = unsafe_div( unsafe_mul(r, sp), s )                 # can floor to 0; once 0 stays 0 -> over-mint
add_liquidity(): if (prev_supply == 0) { bootstrap path }  # never gated
```

**Check that would have caught it.** Property-test `_calc_supply` (with `add_liquidity`/`remove_liquidity(0)`/`update_rates`) over extreme imbalanced inputs; assert the invariant that `Π` is never 0 and stays in a valid band during iteration, and that LP minted per call is bounded by deposited value (e.g. `minted <= k * deposit_value`) — fuzzing skewed dust deposits like `[1,1,1,1,1,1,1,9]` surfaces the divergence (Yearn disclosure). Ops: replace `unsafe_*` with checked math so a domain violation **reverts**; enforce `A*Σ >= D*Π` explicitly; and **permanently disable the bootstrap branch after the first deposit** (BlockSec).

**Sources.** https://github.com/yearn/yearn-security/blob/master/disclosures/2025-12-01.md; https://gov.yearn.fi/t/yeth-exploit-discussion; https://github.com/banteg/yeth-exploit/blob/main/report.pdf; https://github.com/0xkorin/yETH-exploit-summary/blob/master/summary.pdf; https://blocksec.com/blog/yearn-finance-incident-unsafe-arithmetic-in-the-invariant-solver-earns-its-name; https://www.quillaudits.com/blog/hack-analysis/yearn-9m-exploit; https://rekt.news/yearn-rekt3; https://www.theblock.co/post/380993/yearn-recovers-2-4-million-stolen-assets-unchecked-arithmetic-bug; https://news.bitcoin.com/yearn-finance-hit-by-9m-defi-exploit-recovers-2-39m-pxeth/; https://a16zcrypto.com/posts/article/runtime-enforcement-defense-numerical-exploits/; https://app.blocksec.com/explorer/tx/eth/0x53fe7ef190c34d810c50fb66f0fc65a1ceedc10309cf4b4013d64042a0331156; https://etherscan.io/address/0xCcd04073f4BdC4510927ea9Ba350875C3c65BF81; https://www.msn.com/en-us/money/markets/yearn-finance-s-yeth-pool-drained-in-3m-liquid-staking-token-exploit/ar-AA1Rsfeb

**Gaps.** SlowMist (URL found; page returned a bot-check, unreadable): https://slowmist.medium.com/9-million-stolen-analysis-of-the-yearn-yeth-pool-vulnerability-557237092054

---

## 10. Truebit — 2026-01-08, Ethereum
Note: `item-truebit`.

**Loss / taken (sources disagree).** ~**$26.4M–$26.6M** (8,535 ETH + ~71 ETH by a copycat) from the Truebit Protocol token sale contract on **Ethereum**; a second attacker extracted a further ~71 ETH (~$224k) with the same technique shortly after [1][2][3]. CertiK states ~$26.6M; DefiLlama records $26.4M — the difference is ETH price variation at report time. No dedicated rekt.news post-mortem was found.

**Root cause.** `getPurchasePrice()` computed the ETH price to mint TRU by chaining multiplications of `totalSupply`, `amount` and `reserve`. The contract was compiled with Solidity < 0.8.0 and did not use SafeMath, so intermediate products in the numerator overflowed past 2^256, wrapped small, and the final integer division rounded the price to zero wei. The attacker called `buyTRU()` at that zero price, minting tokens for free, then sold them for real ETH [1][2].

**Pattern.** Overflow / arithmetic error (token accounting):

```
price = (100 * amount^2 * reserve + 200 * totalSupply * amount * reserve) / (25 * totalSupply^2)
```

With a large enough `amount` the numerator exceeds `type(uint256).max`, overflowing in Solidity <0.8.0; the wrapped result divided by the denominator rounds to zero, and the contract trusts that price [1][2].

**Check that would have caught it.** Compile with Solidity >=0.8.0 (built-in overflow checks) or OpenZeppelin SafeMath — a deploy-time check that `pragma solidity ^0.8.0` is present on every arithmetic file catches it directly [1]. Fuzz `getPurchasePrice(amount)` with amounts up to `type(uint256).max` against the invariant: "for any non-zero `amount`, the returned price must be non-zero and must yield `amount` tokens when paid to `buyTRU()`" — an overflow surfaces immediately.

**Sources.** 1. CertiK: https://www.certik.com/resources/blog/truebit-incident-analysis (~$26.6M, 8,535 ETH). 2. DeFiHackLabs reproduction: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-01/Truebit_exp.sol 3. DefiLlama hacks DB: https://api.llama.fi/hacks ($26,400,000). 4. Attacker: https://etherscan.io/address/0x6C8EC8f14bE7C01672d31CFa5f2CEfeAB2562b50 5. Vulnerable contract: https://etherscan.io/address/0x764C64b2A09b09Acb100B80d8c505Aa6a0302EF2 6. Attack tx: https://etherscan.io/tx/0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014

**Gaps.** No dedicated rekt.news post-mortem found for this incident.

---

## 11. CrossCurve (ex-EYWA) bridge — 2026-01/02, multiple chains
Note: `item-crosscurve`.

**Loss / taken (dates and amounts disagree).** The CrossCurve "Consensus Bridge" was exploited on **Ethereum mainnet**, then repeated against **PortalV2 on Arbitrum** (Cantina; MixBytes). **Dates:** KuCoin/Defimon place the first txs on 31 January; The Block/Crypto.news use 1 February; BlockSec 2026/02/02. **Amounts:** project market value **$1,441,892.31**; BlockSec ~$2.8M; Defimon near $3M across Ethereum, Arbitrum, Optimism, Base, Mantle, Frax, Celo and Blast (Yahoo Finance); QuillAudits $1.4M. Taken: mostly the protocol's own EYWA — 999,787,453.03 EYWA (~999.8M, the entire PortalV2 EYWA balance) unlocked to the attacker, plus USDT 815,361, CRV 239,889.64, WETH 123.59, USDC 34,820.73, USDB 10,288.43, and smaller 3CRV/2CRV/WBTC/frxUSD (project X post). Ethereum-side EYWA could not be liquidated (no EYWA DEX pools; sole CEX route frozen).

**Root cause.** Not a key theft or tricked signer — a smart-contract access-control/configuration flaw, an integration blunder. ReceiverAxelar inherited Axelar GMP SDK v5.10's `AxelarExpressExecutable`, whose `expressExecute()` is `external`, callable by anyone, guarded only by a fresh-commandId check (MixBytes; Axelar SDK source); CrossCurve never overrode or guarded it. The normal `execute()` path is protected by `gateway.validateContractCall()`; `expressExecute()` skips it. Inside `_execute()`, ReceiverAxelar only verified the caller-supplied sourceChain/sourceAddress matched a stored `peers` entry — attacker-chosen public values proving nothing (BlockSec). Impact was amplified because the peer's confirmation threshold (chain id 80,094 = Berachain, address 0xF3792BAE7F35DCDE2916C6E6A72CCD3A5330D565) was 1, so one bridge path sufficed (Cantina).

**Pattern.** Cross-chain message validation / missing access control on a public execution path:

```
function expressExecute(commandId, sourceChain, sourceAddress, payload) external {
    require(!gateway.isCommandExecuted(commandId));   # only real check
    _execute(sourceChain, sourceAddress, payload);     # no gateway.validateContractCall()
}
# _execute(): require(peers[sourceChain] == sourceAddress)  # both attacker-supplied
# then decode(payload) -> BURN_UNLOCK -> PortalV2.unlock(token, amount, from, attacker)
```

`expressExecuteWithToken()` and the v6.x siblings share this shape.

**Check that would have caught it.** From a non-Gateway EOA, call `ReceiverAxelar.expressExecute()` with a fresh commandId, whitelisted sourceChain/sourceAddress and crafted payload, and assert it reverts (the fast path must be callable only by the Axelar Gateway or a trusted executor). Assert no code path moves value out of PortalV2 (`unlock`/`_unlock`) unless `gateway.validateContractCall()` approved the (commandId, sourceChain, sourceAddress, payloadHash) tuple; fuzz arbitrary tuples into `expressExecute()`. Review rule: enumerate every inherited public entrypoint (e.g. Slither's entrypoint list), require symmetric validation between fast and normal paths, and require any consensus threshold > 1.

**Sources.** https://x.com/MixBytes/status/2018304206763892766 (MixBytes post-mortem); https://x.com/crosscurvefi/status/2018366973936087512 (project token/amount breakdown); https://x.com/crosscurvefi/article/2019475142594744611 (recovery memorandum); https://cantina.xyz/blog/crosscurve-bridge-hack-axelar-expressexecute (via web.archive.org/web/20260521195744/); https://blocksec.com/blog/weekly-web3-security-incident-roundup-feb-2-feb-8-2026; https://www.quillaudits.com/blog/hack-analysis/cross-curve-exploit; https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/releases/5.10.x/contracts/express/AxelarExpressExecutable.sol#L92-L108; https://crypto.news/crosscurve-exploited-for-3-million-in-multi-network-bridge-attack/; https://finance.yahoo.com/news/crosscurve-contains-exploit-crypto-thefts-115656873.html; https://www.kucoin.com/news/articles/crosscurve-bridge-exploit-3m-loss-due-to-fabricated-message-vulnerability; https://www.theblock.co/post/387939/crosscurve-bridge-exploited-for-approximately-3-million-across-multiple-chains-via-spoofed-messages

**Gaps.** No separate project-authored technical post-mortem exists; the memorandum states the auditor's report is definitive and no further report is planned (https://x.com/crosscurvefi/article/2019475142594744611).

---

## 12. Moonwell cbETH oracle — 2026-02-15, Base
Note: `item-moonwell`.

**Loss / taken.** **$1,779,044.83** in bad debt across eleven assets, when governance proposal MIP-X43 was executed on-chain at 6:01 PM UTC on **Base**; 1,096.317 cbETH was seized by liquidation bots [1][2]. Largest losses: cbETH ($1,033,393), WETH ($478,998), USDC ($232,584); smaller amounts of EURC, cbBTC, cbXRP, DAI, USDS, AERO, MORPHO and wstETH were also lost [1][2].

**Root cause.** The cbETH price oracle deployed by MIP-X43 was misconfigured: it fed only the cbETH/ETH exchange rate (~1.12) and omitted the multiplication by ETH/USD, so a ~$2,200 asset was reported on-chain at $1.12 — a 99.9% collapse. The flawed configuration was introduced in GitHub PR #578, co-authored by the AI coding assistant Claude Opus 4.6; neither the AI, human reviewers, nor the 99.1% of governance voters who approved the proposal caught the missing multiplication [1][2][3]. This was **not a smart-contract bug in the traditional sense** — the oracle contract functioned as configured; the failure was a configuration error (missing price-conversion step) plus inadequate review and no automated price-sanity check [1][2].

**Pattern.** Oracle misconfiguration / missing price conversion — a ratio feed treated as dollars:

```
# Faulty (as deployed): cbETH_USD_price = cbETH_ETH_rate                         // ~1.12 as dollars
# Correct:               cbETH_USD_price = cbETH_ETH_rate * ETH_USD_price        // ~1.12 x ~1960 = ~$2,200
```

A single missing term turned a ~$2,200 collateral asset into a $1.12 asset; liquidation bots (correctly following protocol rules) seized collateral at the falsely low price and borrowers extracted cbETH with trivial collateral. The five-day governance timelock prevented rapid correction [1][2]. Moonwell's third oracle failure in four months: mispriced volatile-token oracles (Oct 2025, $1.7M) and a wrsETH oracle feeding an absurd value after a Balancer exploit (Nov 2025, $3.7M) [1].

**Check that would have caught it.** (1) Price-sanity bounds in the oracle adapter: `require(price >= minPrice && price <= maxPrice)` with bounds from the asset's known market price would have rejected $1.12 for a ~$2,200 asset — run on every oracle update, not just deployment [1][2]. (2) An integration test comparing oracle output to a known reference price (e.g. Chainlink ETH/USD × cbETH/ETH, asserting within ~10%, run in CI before the vote; invariant: *the oracle must report a price within N% of the asset's independently observed market price* [1][2].

**Sources.** 1. rekt.news "Moonwell — Rekt": https://rekt.news/moonwell-rekt/ (primary analysis, all loss figures, historical context). 2. Moonwell Governance Forum "MIP-X43 cbETH Oracle Incident Summary" (AnthiasLabs): https://forum.moonwell.fi/t/mip-x43-cbeth-oracle-incident-summary/2068 (official post-mortem, timeline, bad-debt breakdown). 3. GitHub PR #578: https://github.com/moonwell-fi/moonwell-contracts-v2/pull/578 (introduced the misconfiguration; a reviewer flagged a concern about the cbETH price calc that was not resolved). 4. QuillAudits mMAMO analysis (separate incident, confirmed unrelated): https://www.quillaudits.com/blog/hack-analysis/moonwell-donation-exploit

**Gaps.** No BlockSec/SlowMist/CertiK/Halborn/Verichains analysis specific to this incident was locatable; QuillAudits covered a different Moonwell incident [4].

---

## 13. Resolv (USR stablecoin) — 2026-03-22, Ethereum
Note: `item-resolv`.

**Loss / taken (sources differ).** ~**$25M taken in ETH** [PM]; Chainalysis "roughly $23 million" [CHA]; Cyvers/Cointelegraph ~11,400 ETH (~$24M) plus ~$1.1–1.3M residual wstUSR [CT2] — differences track price-at-time and whether residual wstUSR is counted. First malicious tx 02:21:35 UTC, second 03:41 UTC [PM] (reported as Sunday March 22 [CD][BLK]; one Cointelegraph RSS entry shows an earlier timestamp but its body says Sunday [CT1]). Chain: Ethereum (all cited txs and the USR Counter contract are Ethereum) [PM]. The attacker **minted 80M unbacked USR** (50M then 30M) against ~$100K–$200K of USDC deposits, then swapped it out for ETH via wstUSR [PM][CHA]. Resolv states the **collateral pool was never touched** — damage was supply inflation plus market impact, not stolen backing [PM]; USR depegged to ~$0.025 on its Curve pool within 17 minutes [BLK].

**Root cause.** Not a smart-contract bug — a privileged-key / infrastructure (supply-chain) compromise the contract could not contain. Resolv's post-mortem says the attack began outside its perimeter: a credential tied to a contractor's GitHub account at a third-party project was reused to reach Resolv repositories, where attackers ran a malicious CI/CD workflow that exfiltrated infrastructure credentials and escalated in the cloud [PM]. Early attempts to reach the signing key were denied; the attackers succeeded by using a higher-privileged role to **modify the key's access policy in AWS KMS directly**, gaining signing authority over the SERVICE_ROLE key used by the minting backend [PM][CHA]. With it, `completeSwap` minted whatever output the signer requested; the contract worked as designed and assumed the key never leaks [CHA]. No insider involvement was found [PM].

**Pattern.** Key/signer compromise, amplified by missing on-chain limits and unvalidated input:

```
requestSwap:  user deposits collateral (USDC) -> on-chain request
completeSwap: SERVICE_ROLE key (off-chain) passes finalMintAmount as an unchecked parameter
gated:   signature valid? yes -> mint finalMintAmount
missing: mintAmount <= f(collateral); per-tx / per-period cap; oracle price
```

Two flaws stack: (a) one privileged key authorizes unlimited issuance (no multisig, no ceiling); (b) the contract trusts the signed output with no ratio/price check [CHA][ZAC]. SERVICE_ROLE was a plain EOA held since May 2024 that also controlled other infrastructure contracts [HAK]; analyst Vadim states a prior audit finding was named "Missing upper [limit]" [ZAC].

**Check that would have caught it.** (1) Invariant/fuzz test on the mint path: assert every completed swap satisfies `mintedUsr <= collateralIn * collateralPrice / (1 - maxSlippageBps)`, bounded by hard per-tx and per-period caps and a total-supply ceiling; fuzz `completeSwap` with signer outputs far above deposit value and require a revert — fails on the real 500x case (100K USDC → 50M USR) [BLK]. (2) Ops: move mint authorization off a single long-lived EOA (multisig/MPC, one key per contract); add Resolv's adopted on-chain mint cap and oracle price validation, plus real-time monitoring wired to an **automatic pause** on anomalous mints — the exploit repeated ~3 times before anyone halted it [PM][QA].

**Sources.** [PM] https://resolv.xyz/blog/resolv-postmortem-march-22-2026-incident; [CHA] https://www.chainalysis.com/blog/lessons-from-the-resolv-hack/; [REKT] https://rekt.news/resolv-labs-rekt; [BLK] https://www.theblock.co/post/394582/resolvs-usr-stablecoin-depegs-after-attacker-mints-80-million-unbacked-tokens-extracts-roughly-25-million; [CD] https://www.coindesk.com/markets/2026/03/23/resolv-stablecoin-drops-70-after-usd80-million-exploit-after-attacker-mints-usr; [CT1] https://cointelegraph.com/news/resolv-labs-stablecoin-depegs-attacker-mints-millions-of-tokens; [CT2] https://cointelegraph.com/news/resolv-says-no-assets-lost-as-defi-partners-respond-to-usr-depeg; [DEF] https://thedefiant.io/news/hacks/defi-has-seen-resolv-s-usd25m-usr-exploit-many-times-before; [BE] https://beincrypto.com/resolv-usr-stablecoin-depegs-after-security-failure/; [SEC] https://docs.resolv.xyz/litepaper/resources/security; [HAK] https://x.com/hackenclub/status/2036062689772855687; [QA] https://x.com/QuillAudits_AI/status/2036027959765856625; [ZAC] https://x.com/zacodil/status/2035658779706974556; [D2] https://x.com/D2_Finance/status/2035560501070225770

**Gaps.** No standalone BlockSec/SlowMist/CertiK/Halborn/Verichains analysis found (blogs carried no write-up or were unreachable), so their claims are not used. Rekt entry and its $25M figure: https://rekt.news/leaderboard/ [REKT].

---

## 14. Drift Protocol — 2026-04-01, Solana
Note: `item-drift`.

**Loss / taken.** ~**$285.26M** across 18 tokens in 128 seconds from Drift Protocol, a Solana-based perpetuals and lending platform; largest losses ~$159M in JLP and ~$71.4M in USDC; the vault balance collapsed from ~$309M to under $8M (rekt.news, the sole source here).

**Root cause.** Not a smart-contract bug. The exploit was the final step of a six-month social-engineering operation attributed with medium-high confidence to **UNC4736** (AppleJeus/Citrine Sleet), a North Korean state-affiliated group. Operatives posed as a quantitative trading firm, met Drift contributors at conferences from ~Oct 2025, onboarded an Ecosystem Vault with >$1M, and delivered malware via shared code repositories or a weaponized TestFlight app exploiting a VSCode/Cursor vulnerability to reach contributor machines. They then obtained pre-signed transactions from multisig signers using Solana's durable nonce feature. Drift's governance used a **2-of-5 multisig with no timelock** — any two signers could authorize instant, irreversible admin-level changes. Attackers used the pre-signed nonces to transfer ownership of Drift's State account, created a collateral market for a wash-traded fake token (CarbonVote Token, CVT) with maximal borrowing parameters, deposited 500M CVT at negligible cost, and drained the vaults.

**Pattern.** Key or signer compromise (social engineering of multisig holders) combined with missing operational security controls (no timelock on admin actions):

```
// Admin multisig: 2-of-5, no timelock
function executeAdminAction(bytes calldata action) external {
    require(multisig.checkSignatures(action, 2)); // any 2 of 5
    _executeImmediately(action);                  // NO delay, NO veto window
}
```

Because there was no timelock, two compromised signers' pre-signed nonce txs sufficed to transfer full admin control instantly, after which the admin could create markets with arbitrary parameters, deposit a near-worthless token at an attacker-controlled oracle price, and borrow all vault assets.

**Check that would have caught it.** (1) Timelock on admin actions: every admin action that can move/rehypothecate user funds must pass a timelock (e.g. 12–24h) plus an emergency pause any council member can trigger; review rule "Can any N-of-M signers move funds instantly? If yes, the governance design is incomplete" — Jupiter Lend (4/7, 12h) and Kamino (5/10, 12h) already did this. (2) Invariant test on admin capabilities: assert no single admin tx, nor any 24h sequence, can reduce total vault TVL by more than N% without a timelock expiry — fuzz the admin action space (create markets, update oracles, change parameters) on a mainnet-fork simulation.

**Sources.** Primary: https://rekt.news/drift-protocol-rekt/ — comprehensive post-mortem (timeline, root cause, mechanism, fund tracing, governance comparison), itself citing Drift's public statements, ZachXBT's on-chain analysis, and statements from Hayden Adams and Taylor Monahan.

**Gaps.** Drift's own post-mortem on Medium (HTTP 403), Drift docs (redirects to velocity.exchange, HTTP 500), and BlockSec/SlowMist/CertiK/Halborn/Verichains/QuillAudits analyses (HTTP 403/404) were not accessible, so this incident rests on the single rekt.news article.

---

## 15. KelpDAO rsETH bridge — 2026-04-18, Ethereum/Unichain
Note: `item-kelp`.

**Loss / taken (sources disagree).** **116,500 rsETH (~$292M then)**, ~18% of the ~630,000-token supply, drained at 17:35 UTC from KelpDAO's LayerZero-based rsETH bridge (LayerZero report; rekt.news; Chainalysis). Released on **Ethereum mainnet** by the rsETH OFT Adapter (0x85d456b2...e98ef3); the forged packet claimed to come from KelpDAO's rsETH OApp on **Unichain**, the only source chain whose peer was registered on the adapter (migalabs PDF; banteg gist). Custodied rsETH belonging to users who had bridged out: the adapter held 116,723.52 rsETH one block before and 223.52 after (banteg gist) — a custody release, not a mint of new supply (migalabs PDF). Amount estimates: ~$292M (LayerZero, Chainalysis, KelpDAO), ~$290M (BlockSec, rekt; rekt cites Cyvers at $293.7M), ~$294M (migalabs PDF). KelpDAO paused contracts ~46 min after the drain, blocking a second packet (nonce 309, ~40,000 rsETH / ~$95–100M) (rekt.news; banteg gist; KelpDAO X).

**Root cause.** Not a smart-contract bug. Per LayerZero's post-mortem, a developer was socially engineered on 2026-03-06 (malicious GitHub repo, macOS malware); session keys let the attacker poison two internal op-geth RPC nodes used by the LayerZero Labs DVN, returning a forged `PacketSent`/state response to the DVN's IPs only while looking normal to monitoring; the attacker then DDoS'd external RPC providers so failover relied solely on those nodes, yielding a valid attestation for a message that never occurred on Unichain (LayerZero report). The app's **1-of-1 DVN config** (one required DVN, no optional DVNs, zero threshold) made one attestation enough: the destination's hash-equality check passed and the adapter released funds (rekt.news; BlockSec). KelpDAO published no full post-mortem; its account is the "April 18 Incident: Additional Context" thread, which says the 1-of-1 setup was LayerZero's documented default (KelpDAO X). Attribution: DPRK TraderTraitor/UNC4899 (LayerZero report; Chainalysis).

**Pattern.** Cross-chain message validation failure from a single-verifier trust assumption plus off-chain (RPC) infrastructure compromise — the destination checks only that the required DVN(s) signed the payload hash, never that the claimed source burn happened:

```
verify(payloadHash, sigs):
    assert quorum(configured_DVNs, sigs)   # hash/signer equality only
    commit(payloadHash)                    # dest releases/locks
# no assertion that the source PacketSent/burn exists
```

With `requiredDVNs = [one]`, forging one attestation suffices. The forgery is provable on-chain: Unichain's outbound nonce stayed 307 (308 never existed) and Unichain's rsETH supply was only 49.26, so 116,500 could not have been burned (rekt.news; banteg gist).

**Check that would have caught it.** (1) Cross-chain invariant monitoring (catches it live): continuously assert the destination release is backed by a matching source burn — `released_on_dest <= burned_on_src` and `dest_inbound_nonce <= src_outbound_nonce` per channel (Chainalysis names this exact missing detection); banteg's reproducible `eth_getLogs` scan of Unichain's EndpointV2 for the claimed `PacketSent` returns 0 events (Chainalysis; banteg gist). (2) Deployment-gating config test / review rule: assert `requiredDVNCount + optionalDVNCount >= 2` with independently operated DVNs and fail CI/deploy otherwise — a 2-of-2 stack would need two independent DVNs compromised at once (LayerZero report; QuillAudits); operationally, refuse to sign as the sole required attestor on any channel.

**Sources.** https://layerzero.network/publications/kelpdao-incident-report.pdf; https://layerzero.network/blog/layerzero-labs-kelpdao-incident-report; https://x.com/KelpDAO/status/2046332070277091807; https://rekt.news/kelpdao-rekt; https://www.chainalysis.com/blog/kelpdao-bridge-exploit-april-2026/; https://blocksec.com/blog/the-decentralization-dilemma-cascading-risk-and-emergency-power-in-the-kelp-dao-crisis; https://www.quillaudits.com/blog/hack-analysis/kelp-dao-hack; https://discover.credshields.com/incident-report-kelp-dao-rseth-bridge-exploit/; https://www.migalabs.io/uploads/Kelp_DAO_IEEE_V4_6_BRAINS_2026_6361a2be9b.pdf; https://gist.github.com/banteg/705d0284513b74ad20f61d90f5b5de62; https://ethdaily.io/layerzero-publishes-full-post-mortem-on-dollar292m-kelpdao-exploit; https://www.kucoin.com/blog/kelpdao-hack-2026-rseth-exploit-analysis; https://shattered.io/kelp-dao-sues-layerzero-292-million-rseth-2026/

**Gaps.** No standalone SlowMist or CertiK technical post-mortem; CertiK/Cyvers figures appear second-hand via rekt. KelpDAO published no full root-cause post-mortem.

---

## Cross-incident patterns (merge summary of the notes' stated patterns)

- **Rounding / precision / unchecked arithmetic (4):** Resupply (rounding-to-zero on an inflatable price), Bunni v2 (rounding direction across a sequence), Balancer v2 (upscale/downscale rounding asymmetry), Yearn yETH (unchecked fixed-point solver plus a reachable bootstrap path).
- **Missing / broken access control (3):** SuperRare (inverted `require`), Typus (discarded authorization result), CrossCurve (unvalidated inherited `expressExecute()` fast path).
- **Deployment / configuration flaws (3):** Kinto (uninitialized proxy, CPIMP), Moonwell (oracle missing a price-conversion step), CrossCurve (confirmation threshold of 1).
- **Key / signer / infrastructure compromise (3):** Resolv (privileged key + supply chain), Drift (social-engineered multisig signers, no timelock), KelpDAO (developer infected; poisoned RPC nodes feeding a 1-of-1 DVN). None of these was a contract logic bug.
- **Overflow (1):** Truebit (pre-0.8.0 multiplication overflow rounds price to zero).
- **Unvalidated external call (1):** Arcadia Finance (arbitrary `call()` to an unvalidated router).
- **Reentrancy (1):** GMX v1 (gas-refund callback enables cross-contract reentry into the Vault).

**Loss figures the notes disagree on:** Resupply ($9.5M–$10M), Kinto ($1.55M vs early "nearly 7M" tokens), SuperRare (~$680K–$731.8K), Balancer ($94.8M / $121.1M / $125M / $128M), Yearn yETH (~$9M vs early "$3M"), Truebit ($26.4M vs $26.6M), CrossCurve ($1.4M / ~$2.8M / ~$3M), Typus ($3.44M vs $3.4M vs ~$3M), Resolv (~$23M / ~$24M / ~$25M), KelpDAO (~$290M / ~$292M / ~$293.7M / ~$294M). GMX ($42M), Arcadia ($3.6M), Bunni (~$8.4M), Moonwell ($1,779,044.83) and Drift (~$285.26M) carry a single figure each in their notes. **Dates the notes disagree on:** Resupply (Jun 25 vs Jun 26), Bunni (Sep 1 vs Sep 2), Kinto (post-mortem internally contradicts 9 vs 10 July), CrossCurve (Jan 31 vs Feb 1 vs Feb 2).

## Incidents with a noted source gap (named, not guessed)

No incident note is missing — all 15 were supplied and read. Several notes record that a project post-mortem or a named security-firm write-up was unavailable; each such incident still rests on at least one accessible source (usually rekt.news and/or the project's own disclosure):

- **GMX v1** — official Notion post-mortem login-walled; named-firm analyses not accessible. Rests on rekt.news + QuillAudits.
- **Kinto** — no Kinto-specific named-firm analysis found. Rests on Dedaub, Nethermind, rekt.news, The Defiant, the Kinto post-mortem.
- **Arcadia Finance** — project Medium post-mortem and Pashov post-exploit analysis unreachable. Rests on rekt.news.
- **SuperRare** — no first-party post-mortem; no dedicated rekt.news entry. Rests on Verichains, SolidityScan, Veridise, Cointelegraph and others.
- **Balancer v2** — official Medium post-mortem Cloudflare-blocked; no accessible named-firm analysis. Rests on rekt.news + QuillAudits.
- **Yearn yETH** — SlowMist page bot-checked/unreadable. Rests on the Yearn disclosure, BlockSec, rekt.news and others.
- **Typus Finance** — no dedicated rekt.news post-mortem; project and SlowMist Medium posts read via Wayback only. Rests on the project post-mortem, SlowMist, Halborn, Defimon.
- **Moonwell** — no accessible named-firm analysis; QuillAudits covered a different Moonwell incident. Rests on rekt.news + the Moonwell governance post-mortem.
- **Truebit** — no dedicated rekt.news post-mortem found. Rests on CertiK, DeFiHackLabs, DefiLlama.
- **CrossCurve** — no separate project-authored technical post-mortem; the auditor's report is designated definitive. Rests on MixBytes, the project's X posts, Cantina, BlockSec.
- **Resolv** — no standalone named-firm analysis found. Rests on the Resolv post-mortem, Chainalysis, rekt.news, The Block and others.
- **Drift** — project post-mortem (Medium 403), docs (500) and all named-firm analyses unreachable; rests on rekt.news alone.
- **KelpDAO** — no standalone SlowMist/CertiK post-mortem; KelpDAO published no full root-cause post-mortem. Rests on LayerZero's incident report, Chainalysis, rekt.news, BlockSec and others.

No incident was found entirely without a source.
