# Loopscale — USDC/SOL Genesis Vault exploit (Solana, 26 April 2025)

Keys `[x]` map to URLs in §5.

## 1. What happened
- **Date / chain:** 26 April 2025, Solana; proceeds bridged to Ethereum via Wormhole [L][R]. rekt.news, QuillAudits and Helius agree [R][Q][He]; one summary calls 27 April the *disclosure* date [V].
- **Amount:** "the unauthorized outflow of 5,726,724.97 USDC and 1,211.4 SOL" [L] — reported as **~$5.8M**, "roughly 12%" of TVL (~$40M) [R][Q][T]; rekt prices it at 39,474.5 SOL ≈ $5.792M [R], Halborn writes "5.7 million USDC and 1200 SOL" [H]. No separately reconciled USD figure was found.
- **Taken:** from the USDC and SOL Genesis Vaults — four borrows of 1.5M, 1.5M, 1.5M and 1,226,725 USDC, then 1,211.4 SOL; the USDC was swapped for 38,261 SOL via Jupiter [L]. 3,126 USDC and 2,047 SOL depositors were affected; "No user deposits incurred any loss" — all returned by 29 April, withdrawals reopened 8 May [L][R][He].

## 2. Root cause
Plainly: **not** a stolen key, tricked signer or leaked secret — missing validation in code. RateX runs each market as its own program exposing `get_pt_price`; Loopscale valued RateX principal-token (PT) collateral by CPI into it, with Pyth data. Program-ID validation existed for Exponent PT tokens and RateX PT "Loops" but was not extended to "non-Loop borrows collateralized by RateX tokens" [L], so the program answering the price call need not be the genuine registered market: an impostor that "spoofed the interface of a valid RateX market" returned "an artificially inflated PT exchange rate via `get_pt_price`", the health check passed, and "a series of undercollateralized loans" followed [L]. The code shipped "on March 27" 2025 with the RateX collateral upgrade and "had not been subject to a third-party audit" [L]. Halborn likewise calls it business logic, not an off-chain failure [H].
The same *shape* was known here: OShield's Jan–Feb 2025 audit rated it critical that pricing "lacks proper validation of the `oracle_account`", "just taken from the `remaining accounts`"; fixed in `get_price()` against `market_information` [O]. The March upgrade added a path where that check was absent.
**Sources disagree.** CryptoNinjas and AuditOne describe flash-loan/low-liquidity-pool manipulation — "a single, time-point price feed sourced from a liquidity pool", no TWAP [C][A] — conflicting with the post-mortem's *substituted program*; I found no evidence for that version. rekt quotes Max N. — "the attacker simply deployed a malicious price feed and called Loopscale's own `create_loan` function" [R] — consistent with a substituted program, though "price feed" and "market program" differ; Bill Papas, also there, says the interface came from the IDL pulled out of the deployed binary [R], unverified.

## 3. Pattern
**Unvalidated input — missing account/program-identity validation on a price source** (oracle spoofing: a lying source substituted, not a moved one).
```
fn collateral_value(market, pt, remaining_accounts) -> u64 {
    let prover = remaining_accounts[0]; // BUG: caller-supplied price authority
    // BUG: no lookup (program_id, market) -> registered_price_source
    let rate = cpi_get_pt_price(prover, market, pt);
    pt_amount * combine(rate, pyth_pt_feed)  // vs debt * min_ratio
}
// Only Exponent-PT and RateX-"Loops" resolved the prover from a registry.
```
A price is only as trustworthy as what produced it.

## 4. The check that would have caught it
1. **Invariant test with a hostile source.** Invariant: *every instruction that reads a collateral price must use a source the program derived from its own registry — no caller-supplied account may answer a price call.* Assert each price CPI resolves its program ID via the on-chain `market_information`/`collateral_map` lookup; fuzz `remaining_accounts` and every optional account with attacker-owned accounts and executables, requiring the instruction to **fail**, not return a price. The incident's own "test program", deployed minutes before the exploit [L], is that fixture.
2. **Review rule + operational control.** Route all collateral pricing through one `verify_price_source(program_id, market)` helper; require any PR touching a collateral integration to prove in the diff that the check sits on **every** path reading that asset's price — the incident existed because two paths had it and a third did not [L]. Operationally, nothing ships without third-party review of the newly introduced path, plus interim backend co-signing. Loopscale did both; its patch "enforce[s] strict validation of RateX program IDs during loan health checks", reviewed by Sec3 [L][He][CT].

## 5. Sources
[L] blog.loopscale.com/posts/postmortem · [R] rekt.news/loopscale-rekt · [H] halborn.com/blog/post/explained-the-loopscale-hack-april-2025 · [Q] quillaudits.com/blog/hack-analysis/loopscale-lost-5.8m-in-price-manipulation-attack · [O] github.com/oshieldio/Publications/blob/main/Loopscale/loopscale-v1.md · [A] auditone.io/blog-posts/the-loopscale-incident-a-case-study-in-oracle-exploitation · [He] helius.dev/blog/solana-hacks · [T] theblock.co/news/ecosystems/2025-04-26-solana-defi-protocol-loopscale-hit-with-5-8-million-exploit-two-weeks-after-launch-352083 · [C] cryptoninjas.net/news/solanas-loopscale-suspends-lending-after-5-8m-exploit/ · [V] vibraniumaudits.com/post/loopscale-exploited-for-5-8-million-just-two-weeks-after-launch · [CT] cointime.ai/flash-news/loopscale-89091
Also read: blog.loopscale.com/posts/genesis, docs.loopscale.com/resources/faq, 36crypto.com/solanas-loopscale-hacked-for-5-8m-shocking-new-details-uncovered/.
**Not found:** no Loopscale write-up I could reach from BlockSec, CertiK, SlowMist, Verichains or Neodyme — blocksec.com/blog lists only Sept–Oct 2026 posts; blog.slowmist.com refused connection; slowmist.medium.com and medium.com gave 403; verichains.io and certik.com Loopscale URLs gave 404; hacked.slowmist.io has no entry.
