Root cause: donateToReserves() transfers EToken shares out of the caller's position without triggering a health check. No checkLiquidity() or equivalent insolvency gate is executed post-donation. An attacker can donate borrowed assets to reserves, rendering their own position artificially insolvent, then self-liquidate to capture the soft-liquidation bonus of up to 20% introduced in eIP-14. No external price oracle was manipulated — the failure is purely internal to the EToken accounting.
Attack vector: internal accounting invariant — donateToReserves() without insolvency gate | Layer: smart_contract | Attacker: UNKNOWN — funds returned progressively, no attribution published
| Time UTC | Event | Source |
|---|---|---|
| 2023-03-13 08:56 UTC | Exploit transaction executed — flash loan accumulates borrowed position, donateToReserves() pushes position below liquidation threshold, self-liquidation captures soft-liquidation bonus in the same transaction | BlockSec |
| — UTC | BlockSec detects the exploit in real time — exact detection timestamp not documented | BlockSec |
| 2023-04-03 00:00 UTC | Funds returned progressively by the attacker — recovery reported as complete 'by April 3' | DLNews citing Euler Labs |
| 2023-04-04 00:00 UTC | Euler Labs confirms complete recovery | Omniscia post-mortem |
| Protocol — Estimated Exposure |
|---|
| Euler Finance — DAI, USDC, USDT, stETH, wBTC positions drained (~$197M) |
Every figure in this report is traceable to a named source. Statements attributed to Euler Labs or to the attacker's on-chain messages are marked as primary; everything else is Forensic Capital reconstruction from public chain data. Where the public record is silent, this report says so rather than closing the gap by inference. Sources are listed in full at the end of this document.
Baseline TVL (J−7 to J−1): $0
Impact J0: N/A |
Impact J+7: N/A
Source: DefiLlama API — historical TVL, protocol slug: N/A
| Metric | Value | Source |
|---|---|---|
| Baseline TVL (J−7→J−1) | $0 | DefiLlama |
| TVL change J0 | N/A | DefiLlama |
| TVL change J+7 | N/A | DefiLlama |
| Bad debt absorbed | $0 | Omniscia post-mortem — full recovery confirmed 2023-04-04 |
The Euler exploit required no compromise of any trust boundary. It exploited a single missing gate inside the protocol's own accounting:
| # | Trust Layer | Failure | Covered by Audits |
|---|---|---|---|
| 1 | Smart contract — donateToReserves() | Moves EToken shares out of a position without triggering a health check. No checkLiquidity() gate post-donation. | No — live on-chain ~8 months since eIP-14 |
| 2 | Smart contract — eIP-14 soft liquidation | Liquidator bonus up to 20% on underwater positions. Attacker self-liquidates to capture it. | No — mechanism reviewed in isolation, not composed with donation path |
| 3 | Protocol — no external dependency | No oracle manipulated, no key compromised, no external call abused. The failure is entirely internal. | N/A — no external trust boundary involved |
FC-CLASS-005-A — Internal Invariant Failure donateToReserves() writes EToken state without re-deriving the solvency invariant. No external dependency. No oracle. No key. The protocol violated its own accounting constraint from within.
Primary sources: Omniscia post-mortem, BlockSec flashloan attack analysis. FC classification: health_check_not_enforced_before_debtWrite, unique across 38 corpus incidents.
Sources: Omniscia post-mortem (omniscia.io/blog/euler-finance-incident-post-mortem), BlockSec flashloan attack analysis (blocksec.com/blog/euler-finance-flashloan-attack).
| Component | Technical Detail |
|---|---|
| Vulnerable function | donateToReserves() — transfers EToken shares from caller position to reserves without invoking checkLiquidity() or any equivalent insolvency gate. [SOURCE:Omniscia] |
| Exploit payload | Flash loan accumulates a large borrowed position, donateToReserves() pushes it below the liquidation threshold, self-liquidation captures the soft-liquidation bonus — all in one transaction. [SOURCE:BlockSec] |
| Amplification | eIP-14 soft liquidation permits a liquidator bonus of up to 20% on underwater collateral. The attacker is both debtor and liquidator. [SOURCE:Omniscia] |
| Oracle involvement | None. No external price oracle was manipulated. The vulnerability is purely internal to EToken accounting. [SOURCE:BlockSec] |
| Exposure window | Approximately 8 months on-chain since eIP-14 deployment. [SOURCE:Omniscia] |
| Independent confirmation | Sherlock and Cyfrin independently confirmed the root cause as a missing insolvency check after donateToReserves() — not a consequence of eIP-14 alone. [SOURCE:BlockSec] |
| Named invariant | health_check_not_enforced_before_debtWrite — unique across all 38 incidents in the FC corpus. [SOURCE:FC-013] |
Derived from cross-protocol signal analysis (FC signal_inventory [2026-05-28]) and public incident data. 5 structural observations:
The attacker returned funds progressively. Euler Labs confirmed complete recovery on 2023-04-04, 22 days after the exploit. [SOURCE:Omniscia] This is the only FC-CLASS-005 incident in the corpus with confirmed full restitution. [SOURCE:FC-013]
005-A accounts for 2 of 11 incidents in FC-CLASS-005. Euler (2023-03-13) and Platypus (2023-02-16) — 25 days apart. Same structure: a solvency gate absent on a state-writing path. Neither was caught by review. [SOURCE:FC-013]
donateToReserves() was not an oversight — it was a fix. Introduced to close a 'first depositor' bug missed by every prior auditor and reported by a white hat via Immunefi roughly a year earlier. The remediation added a state-writing path without re-deriving the solvency invariant. [SOURCE:Euler Labs] In FC-CLASS-005, the recurring pattern is not a sophisticated attacker — it is a patch that opens a write path without re-checking what the invariant assumed. [SOURCE:FC-013]
Scenario 1 is realised — full recovery confirmed 2023-04-04. Scenarios 2 and 3 are structural observations drawn from the FC corpus, not predictive.
The exploit was executed on 13 March 2023 against a donation path that bypassed the health check. Attacker addresses and transaction hashes are on the public record. Approximately $197M was drained; the majority was subsequently returned following on-chain negotiation. No party has been identified. The returning address described itself as acting in good faith, a claim no independent verification supports or refutes. Forensic Capital attributes the mechanism, not the actor.
Declarations below apply at confidence threshold <70%.
| fc_id | domain | invariant_nomme | submechanism |
|---|---|---|---|
| FC-019 | curve.fi | reentrancy_guard_not_emitted_by_compiler | 005-B |
| FC-025 | solv.finance | double_mint_check_absent_in_BitcoinReserveOffering_mint_redeem_cycle | 005-B |
| FC-029 | platypusdefi.com | solvency_check_absent_in_emergencyWithdraw | 005-A |
| FC-030 | cream.finance | borrow_balance_not_updated_before_erc777_callback | 005-B |
| FC-031 | sturdy.finance | price_oracle_read_guard_absent_during_balancer_execution | 005-B |
| FC-034 | bean.money | execution_delay_absent_in_emergencyCommit | 005-C |
| FC-035 | grim.finance | share_state_not_updated_before_external_transfer | 005-B |
| FC-036 | hundred.finance | reentrancy_guard_absent_before_erc677_transfer_callback | 005-B |
| FC-037 | eralend.com | price_oracle_read_guard_absent_during_pool_execution | 005-B |
| FC-038 | sentiment.xyz | price_oracle_read_guard_absent_during_balancer_execution | 005-B |
| predicate_code | valeur | statut | compiled_by | compiled_at |
|---|---|---|---|---|
| C1_inputs_authentic | vrai | EVALUABLE | marcus | 2026-07-06T15:57:59Z |
| provenance [corpus_vector] : reclassif_batch1 — donateToReserves() chemin public, prix non manipules | ||||
| C2_path_unprivileged | vrai | EVALUABLE | marcus | 2026-07-06T15:57:59Z |
| provenance [corpus_vector] : reclassif_batch1 — donateToReserves() externe sans modifier acces, aucun role requis | ||||
| C3_invariant_violated | vrai | EVALUABLE | marcus | 2026-07-06T15:57:59Z |
| provenance [corpus_vector] : reclassif_batch1 — health check non execute avant debtWrite, solvabilite nette violee | ||||
| invariant_nomme | health_check_not_enforced_before_debtWrite | EVALUABLE | marcus | 2026-07-06T15:57:59Z |
| provenance [corpus_vector] : reclassif_batch1 — invariant nomme, verifiable on-chain TX Euler 2023-03-13 | ||||
| submechanism | 005-A | EVALUABLE | marcus | 2026-07-06T15:57:59Z |
| provenance [corpus_vector] : reclassif_batch1 — 005-A economic_accounting_invariant: debtWrite sans verification health factor | ||||
| # | fc_id | domain | date | loss_usd | submechanism |
|---|---|---|---|---|---|
| 1 | FC-013 | euler.finance | 2023-03-13 | $197.0M | 005-A |
| 2 | FC-034 | bean.money | 2022-04-17 | $80.0M | 005-C |
| 3 | FC-019 | curve.fi | 2023-07-30 | $70.0M | 005-B |
| 4 | FC-035 | grim.finance | 2021-12-18 | $30.0M | 005-B |
| 5 | FC-030 | cream.finance | 2021-08-30 | $18.8M | 005-B |
| 6 | FC-029 | platypusdefi.com | 2023-02-16 | $8.5M | 005-A |
| 7 | FC-036 | hundred.finance | 2022-03-15 | $6.3M | 005-B |
| 8 | FC-025 | solv.finance | 2026-03-03 | $2.7M | 005-B |
| 9 | FC-037 | eralend.com | 2023-07-25 | $2.7M | 005-B |
| 10 | FC-038 | sentiment.xyz | 2023-03-05 | $1.0M | 005-B |
| 11 | FC-031 | sturdy.finance | 2023-06-12 | $0.8M | 005-B |
| champ | avant | après | révisé_à | par |
|---|---|---|---|---|
| initial_admission | — non compilé | admitted depuis staging_id 9 | 2026-06-22T20:07:27Z | marcus |
| url: 9 | ||||
| certification_note | — non compilé | Admission FC-013 Euler Finance, completeness 85%, onchain_tx + calldata certifiés Marcus 2026-06-22 | 2026-06-22T20:07:27Z | marcus |
| url: 0xc310a0affe2169d1f6feec1c63dbc7f7c62a887fa48795d327d4d2da2d6b111d · date: 2026-06-22 · raison: donateToReserves() confirmé dans input data Etherscan — certifié Marcus 2026-06-22 | ||||
| recovery_note | — non compilé | Recovery FC-013 : 100% restitution — source Euler Labs 2023-04-04 | 2026-06-22T20:07:27Z | marcus |
| url: https://twitter.com/eulerfinance/status/1643349013006802946 · date: 2023-04-04 · raison: Euler Labs announced complete recovery of exploited funds on 2023-04-04; staged source text (Omniscia post-mortem) confirms restitution. | ||||
| fc_class | FC-CLASS-001 | FC-CLASS-005 | 2026-07-05T18:17:56Z | taxonomy_v2_batch1 |
| raison: C1+C2+C3 valides CLASS-005: chemin mono-chaine, aucun bridge/relayer, invariant economic manquant (donateToReserves sans health check) | ||||
| class_name | Cross-Chain State Validation Failure | Internal Invariant Failure | 2026-07-05T18:17:56Z | taxonomy_v2_batch1 |
| raison: Alignement class_name nouvelle classe | ||||
| submechanism_detail | — non compilé | 005-A | 2026-07-05T18:17:56Z | taxonomy_v2_batch1 |
| raison: Sous-famille invariant economique/comptable | ||||
This document is sealed. The hash above covers the report as published; any alteration changes it.