Worked example · 2022-06
XCarnival — the borrow path checked that a pledge order existed, never that the Ape was still in the vault, so one BAYC backed loan after loan — XCarnival (Ethereum) — 2022-06-26/27
Summary
XCarnival was an NFT-collateralised lending protocol on Ethereum: a borrower pledged an NFT into the protocol, an order was recorded against that pledge, and ETH was lent against it.
The borrow function did not verify that the pledged NFT remained in the protocol's custody. Withdrawal of the collateral left the corresponding orderId open and borrowable. On 2022-06-26/27, an attacker exploited that gap directly: pledge BAYC #5110, withdraw the Ape, borrow against the still-open order, and repeat — creating multiple pledge orders from multiple attacker-deployed contract addresses, all resolving to the same single NFT.
The working capital for the attack — 120 ETH — was withdrawn from Tornado Cash beforehand. The extraction totalled 3,087 ETH (~$3.8M). XCarnival suspended the affected contract once the drain was identified.
Recovery was unusually fast and unusually public. At 13:45 UTC+8 on 2022-06-27 the attacker returned 1,467 ETH. XCarnival publicly offered the returning address a 1,500 ETH bounty and stated it would not pursue legal action; the attacker accepted. The protocol also stated that security agencies had tentatively located the operator — a claim never substantiated publicly, and plausibly part of the negotiating posture that produced the return.
Timeline (UTC)
| When | Event | OAK ref |
|---|---|---|
| (standing) | XCarnival's borrow path validates that a pledge order is open, without re-checking that the referenced NFT is still held by the protocol; withdrawal does not invalidate the order | (standing T12.006 surface) |
| (pre-event) | Attacker withdraws 120 ETH from Tornado Cash as working capital | T7.001 |
| 2022-06-26/27 | Attacker pledges BAYC #5110, withdraws it, and borrows against the still-open order; repeats across multiple self-deployed contracts, all referencing the same Ape | T12.006 |
| 2022-06-26/27 | 3,087 ETH (~$3.8M) extracted from the lending pool | T5.001 |
| 2022-06-27 | XCarnival suspends the affected contract | M34 |
| 2022-06-27 13:45 (UTC+8) | Attacker returns 1,467 ETH | (negotiated return) |
| 2022-06-27 onward | XCarnival names the returning address publicly, offers a 1,500 ETH bounty, waives legal action; claims security agencies have tentatively determined the operator's geographic location | M32, (unverified claim) |
| post-event | SlowMist publishes the vulnerability analysis; no identification, no enforcement action | (analysis) |
What defenders observed
- Pre-event (custody must be re-read at use time, not trusted from record time). The entire loss is the distance between "an order says an Ape is pledged" and "the vault owns that Ape right now".
ownerOf(tokenId) == vaultat borrow time is a one-line precondition and closes the class (M16). - Pre-event (state transitions on custody and on the loan record must be atomic). Collateral withdrawal should close every order referencing that token in the same transaction. Anywhere those two updates can be separated, an attacker will separate them.
- Pre-event (a per-collection borrow-capacity invariant bounds the unknown bug). Σ debt against BAYC ≤ escrowed BAYC × per-token limit would have capped the loss at one Ape's worth regardless of the state defect (M11).
- At-event (the same
tokenIdacross many pledge orders is a decisive signal). Multiple open orders referencing one token, from freshly deployed contracts, has no legitimate population in an NFT lending market. It is trivially queryable and would have fired on the second iteration. - At-event (mixer-funded operating wallets precede this class). 120 ETH out of Tornado Cash before the first pledge is a pre-positioning signal; funder-graph screening on new large borrowers is cheap (M04).
- Response (a pause plus a public bounty recovered half). XCarnival's combination — suspend, publicly name the address, offer 1,500 ETH, waive legal action — produced a 1,467 ETH return within roughly a day. Whatever one thinks of negotiating with an attacker, the outcome is data: NFT-lending exploits have an unusually good return record, and the traceability of NFT collateral is part of why (M32, M35).
Public references
[slowmistxcarnival2022]— SlowMist, "XCarnival NFT lending protocol vulnerability analysis" (the primary technical account: no check at borrow time whether the NFT referenced by the order had been withdrawn, enabling repeated borrowing against withdrawn collateral): https://slowmist.medium.com/xcarnival-nft-lending-protocol-vulnerability-analysis-583474cede61[coindeskxcarnival2022]— CoinDesk, "Ethereum Lending Protocol XCarnival Hit With $3.8M Exploit, Recovers 50%" (the 3,087 ETH / ~$3.8M figure, the 2022-06-27 dating, and the ~50% recovery): https://www.coindesk.com/business/2022/06/27/ethereum-lending-protocol-xcarnival-hit-with-38m-exploit-recovers-50/[halbornxcarnival2022]— Halborn, "Explained: The XCarnival Hack (June 2022)" (the orderID-not-revoked-on-withdrawal framing and the multi-loan-against-one-NFT mechanism): https://www.halborn.com/blog/post/explained-the-xcarnival-hack-june-2022[xcarnivalmedium2022]— XCarnival, "XCarnival has got 1,467 ETH back, the security agencies have tentatively determined the hacker's geographic location" (the protocol's own account: the 1,467 ETH return at 13:45 UTC+8 on 2022-06-27, the 1,500 ETH bounty to0xb7CBB4d43F1e08327A90B32A8417688C9D0B800a, the explicit waiver of legal action, and the unsubstantiated geographic-location claim recorded here as the protocol's statement): https://xcarnival-lab.medium.com/xcarnival-has-got-1-467-eth-back-the-security-agencies-have-tentatively-determined-the-hackers-3ea05ad134ae[utodayxcarnival2022]— U.Today, "NFT Platform XCarnival Under Attack: Malefactors Use BAYC Token" (identification of BAYC #5110 as the reused collateral and the multiple-contract pledge pattern): https://u.today/nft-platform-xcarnival-under-attack-malefactors-use-bayc-token[cointelegraphxcarnival2022]— Cointelegraph, "Ethereum liquidity provider XCarnival negotiates return of 50% stolen ETH" (the negotiation and the terms of the return): https://cointelegraph.com/news/ethereum-liquidity-provider-xcarnival-negotiates-return-of-50-stolen-eth
Discussion
XCarnival is the anchor OAK uses to argue that NFT-collateral lending is its own Technique rather than a lending-protocol footnote. Read as a generic bug, the finding is "missing state check" and the lesson evaporates into a truism. Read as an NFT case, the specific hazard becomes legible: the collateral is a named, indivisible item, its custody is a single canonical on-chain fact, and the protocol chose to track that fact in its own mutable records instead of reading it. Fungible lending does not present that choice — a balance is the record. Non-fungibility introduces a second source of truth, and this incident is what it costs to trust the wrong one.
The recovery deserves a sober reading rather than a warm one. XCarnival got roughly half back inside a day by naming the address, offering 1,500 ETH, and waiving legal action, and it advertised a claim about law-enforcement geolocation while doing so. That combination is a negotiation, and it worked; it is also unrepeatable as a control. The protocol had no mechanism that limited the loss, so its outcome depended entirely on an attacker's choice. Contributors should record such returns as attacker-side dispositions — facts about one operator's decision — and never as evidence that the design was resilient. The one durable, reusable observation from the aftermath is that NFT collateral is hard to launder: a Bored Ape is a named object with a public provenance trail, which materially changes the economics of returning versus keeping, and helps explain why this sector's recovery rate is out of line with DeFi's generally.
Finally, the pairing with Omni Protocol two weeks later is the reason both entries exist. Same sector, same month, same underlying question — can the protocol prove it still holds the token? — and two entirely unrelated bugs producing the same answer: no. XCarnival lost custody-belief to a stale record; Omni lost it to a callback in the middle of a state transition. A defender enumerating an NFT-lending codebase should ask that single question of every path, because the two documented ways of failing it have nothing in common except the consequence.