# Infini (February 2025, Ethereum)

## 1. What happened

On **24 February 2025** (exploit transactions at 02:15:59 UTC, Ethereum block
21913141) an attacker drained the Infini Earn vault contract
`0x9A79f4105A4e1A050Ba0b42F25351D394fA7E1DC` (Blockscout tags it "Infini")
using the method `withdrawToReserve`
(https://eth.blockscout.com/api/v2/transactions/0xacf84c5944f662a4fcf783806993d713a150994932008e72e4e47a58d6665f7f).
Two transactions came from `0xc49b5e5B9DA66B9126c1a62e9761E6b2147DE3E1`, moving
11,455,666.71 USDC and 38,060,996.26 USDC, i.e. about 49,516,662.98 USDC in
total (same URL and
https://eth.blockscout.com/api/v2/transactions/0xecb31ff694c0e6c5e5b225c261854c0749ecf5d53c698fcda61f2d8e3db8f9fc).
The chain was **Ethereum mainnet**.

Amount lost at the time: **$49.5 million** (https://rekt.news/infini-rekt/,
https://www.quillaudits.com/blog/hack-analysis/how-infini-lost-49-million-in-a-defi-hack,
https://decrypt.co/307513/crypto-neo-bank-infini-50-million-exploit). Some
write-ups round this to ~$50M, e.g. Halborn says "approximately $50 million"
(https://www.halborn.com/blog/post/explained-the-infini-hack-february-2025);
Decrypt's headline says "$50 Million Exploit" while its body says $49.5M (same
Decrypt URL). The two on-chain withdrawals are the source of the 49.5M figure.

What was taken: USDC held in Infini Earn / the Morpho MEVCapital Usual USDC
Vault (https://www.quillaudits.com/blog/hack-analysis/how-infini-lost-49-million-in-a-defi-hack,
https://rekt.news/infini-rekt/). The attacker swapped USDC to DAI, then to
about 17,696 ETH at $2,798 (https://x.com/lookonchain/status/1893869666717585756).

## 2. Root cause

This was **not a smart-contract logic bug**; it was compromised privileged
access. The vault was deployed as an **unverified contract** that contained a
special role (`0x8e0b`) able to withdraw all funds from the vault; that role
was held by `0xc49b...e3e1`
(https://www.quillaudits.com/blog/hack-analysis/how-infini-lost-49-million-in-a-defi-hack).
QuillAudits and Cyvers, via Decrypt, describe it as a **private key breach of
the account holding the role**, followed by privilege escalation
(https://decrypt.co/307513/crypto-neo-bank-infini-50-million-exploit). Halborn
describes the same role as a "backdoor" granted to an address under a **rogue
former developer's** control, who waited 114 days for the vault to fill
(https://www.halborn.com/blog/post/explained-the-infini-hack-february-2025).
Founder Christian stated his personal private key was not leaked and that he
was "negligent when transferring the authority before"
(https://x.com/Christianeth/status/1893885666557411712,
https://www.quillaudits.com/blog/hack-analysis/how-infini-lost-49-million-in-a-defi-hack).

Sources disagree on the exact mechanism: a leaked key of the privileged
account (QuillAudits/Decrypt) versus a developer who deliberately retained the
role (Halborn/rekt). Sources also disagree on the role hex value - QuillAudits
and Decrypt say `0x8e0b`, while rekt.news writes `0x8e9b` in its step list
(https://rekt.news/infini-rekt/). All agree the funds were moved through an
admin/withdraw privilege rather than by breaking contract logic. rekt.news also
notes the attacker's contract was funded with 1 ETH from Tornado Cash
(https://rekt.news/infini-rekt/).

## 3. Pattern

General class: **key or signer compromise combined with excessive, retained
privileged access** (centralized admin authority), not an on-chain arithmetic,
oracle or reentrancy flaw. The vulnerable shape:

```
role WITHDRAW = 0x8e0b
grantRole(WITHDRAW, someEOA)          // single EOA, granted at build time

function withdrawToReserve(uint amount) onlyRole(WITHDRAW) {
    token.transfer(msg.sender, amount) // no cap, no timelock, never revoked
}
```

One externally-owned account could move the entire reserve, the privilege was
never revoked after handover, and the deployed bytecode was unverified so the
role was not visible to reviewers.

## 4. The check that would have caught it

Two concrete, incident-specific checks:

1. **Pre-launch role inventory, as an automated test.** For each deployed
   contract (including unverified ones) enumerate every privileged
   role/selector (here the `0x8e0b` withdraw role) and assert in a unit test
   that no plain EOA holds it: `assertFalse(hasRole(WITHDRAW_ROLE, anyEOA))` and
   `assertTrue(isMultisig(holder) || isTimelock(holder))`. Verify the source
   on Etherscan so the role is reviewable, and add a handover checklist step
   that revokes developer/deployer roles immediately after launch.
2. **Invariant test on the withdraw path.** Invariant:
   `token.balanceOf(vault) + investedAssets >= totalUserDeposits` and no single
   signer can reduce `token.balanceOf(vault)` unless a 2-of-N multisig plus a
   timelock has executed. Fuzz the withdraw entry point with arbitrary
   `msg.sender` addresses and assert every non-multisig caller reverts. Had
   this held, the 11.45M and 38.06M USDC withdrawals could not have executed
   from one account.

## 5. Sources

- https://rekt.news/infini-rekt/
- https://www.halborn.com/blog/post/explained-the-infini-hack-february-2025
- https://www.quillaudits.com/blog/hack-analysis/how-infini-lost-49-million-in-a-defi-hack
- https://decrypt.co/307513/crypto-neo-bank-infini-50-million-exploit
- https://x.com/lookonchain/status/1893869666717585756
- https://x.com/Infini_Global/status/1893973307596435871 (Infini official)
- https://x.com/Christianeth/status/1893885666557411712 (founder Christian)
- https://x.com/Christianeth/status/1893925373546516916 (founder follow-up)
- https://eth.blockscout.com/api/v2/transactions/0xacf84c5944f662a4fcf783806993d713a150994932008e72e4e47a58d6665f7f
- https://eth.blockscout.com/api/v2/transactions/0xecb31ff694c0e6c5e5b225c261854c0749ecf5d53c698fcda61f2d8e3db8f9fc

Not found: a formal project post-mortem document on infini.money's blog; the
project's own statements are the X posts above.
