Forensic Capital — Incident Intelligence

Euler Finance — donateToReserves() Missing Health Check

euler.finance  ·  2023-03-13  ·  CLOSED   INTERNAL ACCOUNTING INVARIANT — DONATETORESERVES() WITHOUT INSOLVENCY GATE   Compensation: FULL   Stage: DEMO_PUBLIC
This report is based entirely on passive, public-facing signals and publicly disclosed post-mortem data. No proprietary access, no NDA engagement, no client relationship with euler.finance. UNKNOWN declared for all data below 70% confidence threshold. — Forensic Capital

I. Incident Overview

Total Loss
$197.0M
attacker gain
Duration
0 min
exploit window
Bad Debt
$0
ecosystem absorbed
TVL J+7 Impact
N/A
vs baseline $0

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

Key Facts

Timeline

Time UTCEventSource
2023-03-13 08:56 UTCExploit transaction executed — flash loan accumulates borrowed position, donateToReserves() pushes position below liquidation threshold, self-liquidation captures soft-liquidation bonus in the same transactionBlockSec
— UTCBlockSec detects the exploit in real time — exact detection timestamp not documentedBlockSec
2023-04-03 00:00 UTCFunds returned progressively by the attacker — recovery reported as complete 'by April 3'DLNews citing Euler Labs
2023-04-04 00:00 UTCEuler Labs confirms complete recoveryOmniscia post-mortem

Affected Protocols

Protocol — Estimated Exposure
Euler Finance — DAI, USDC, USDT, stETH, wBTC positions drained (~$197M)

Evidence Classes & Policies

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.

II. TVL Impact Analysis

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

TVL IMPACT euler.finance · 30-day window · USD −$197M 03-13 $9M$71M$133M$195M$257M$319M 02-2603-0403-1003-1603-2203-28 Incident date J0–J+7 impact window FC SIGNAL_INVENTORY
DAILY TVL VARIATION euler.finance · Daily % change vs prior day · J−7 to J+7 -11.3% -15.8% +13.3% -96.0% -110.5%-55.2%+0.0%+55.2%+110.5% J-7 J-6 J-5 J-4 J-3 J-2 J-1 J0 J+1 J+2 J+3 J+4 J+5 J+6 J+7 Positive Negative FC SIGNAL_INVENTORY
MetricValueSource
Baseline TVL (J−7→J−1)$0DefiLlama
TVL change J0N/ADefiLlama
TVL change J+7N/ADefiLlama
Bad debt absorbed$0Omniscia post-mortem — full recovery confirmed 2023-04-04

III. Attack Vector — EToken Accounting Invariant

The Euler exploit required no compromise of any trust boundary. It exploited a single missing gate inside the protocol's own accounting:

#Trust LayerFailureCovered 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).

Technical Detail — EToken Accounting — Invariant Failure Path

ComponentTechnical 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]

IV. Intelligence Analysis — FC Findings

FC Analysis — Structural Findings

Derived from cross-protocol signal analysis (FC signal_inventory [2026-05-28]) and public incident data. 5 structural observations:

Largest single incident in the FC-CLASS-005 corpus — $197M of $418.8M total across 11 classified incidents (47%). One incident carries nearly half the class by value [FC corpus, 38 incidents]
Only 2 of 11 FC-CLASS-005 incidents are submechanism 005-A: Euler and Platypus (FC-029, solvency_check_absent_in_emergencyWithdraw). Both share the same structure — a solvency gate absent on a state-writing path. The other 9 are 005-B (reentrancy and oracle-read ordering) [FC corpus]
The named invariant health_check_not_enforced_before_debtWrite is unique across all 38 FC incidents — no other incident in the corpus violates this specific invariant [FC predicate_signatures]
FC-038 sentiment.xyz — same class, 8 days earlier (2023-03-05), different submechanism (005-B). Two FC-CLASS-005 incidents in one 8-day window, structurally unrelated [FC corpus]
TVL collapse is near-total and immediate: baseline $284.4M · J0 -7.21% · J+1 -96.33% · J+7 -96.36%. Unlike credential-compromise incidents where TVL erodes over days, an internal invariant failure evacuates the protocol within a single block window [DefiLlama, euler-v1]
The vulnerability was reachable for ~8 months post-eIP-14. Class FC-005 failures are not zero-days — they are invariants that were never enforced, visible in the code the entire time [Omniscia]

V. Forward Scenarios

Scenario 1 — Full Recovery (realised)Confidence: 100%

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]

Scenario 2 — Class recurrenceConfidence: None%

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]

Scenario 3 — Origin of the vulnerable pathConfidence: None%

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.

Attribution

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.

VI. Unknown Declarations

Declarations below apply at confidence threshold <70%.

UNKNOWN — Identity of the attacker — no attribution published
UNKNOWN — Exact motive for the progressive return of funds
UNKNOWN — Whether the 8-month exposure window was flagged internally prior to exploitation

VII. Sources — Public Record

[1] https://omniscia.io/blog/euler-finance-incident-post-mortem/
[2] https://blocksec.com/blog/euler-finance-flashloan-attack
[3] https://www.euler.finance/blog/war-peace-behind-the-scenes-of-eulers-240m-exploit-recovery
[4] https://www.certik.com/resources/blog/4iSrYY6HoaYxk1aKyjFb5v-euler-finance-incident-analysis
[FC-SIG] FC signal_inventory — passive scan euler.finance, 2026-05-28, 0 CONFIRMED signals

Position in the corpus

Corpus · Cell Analysis
FC-013 · euler.finance
FC-CLASS-005
I. Incident
fc_idFC-013
domaineuler.finance
fc_classFC-CLASS-005
II. Cellule
incidents dans cette classe11
total corpus (fc_incidents)46
part de classe11 / 46
axe séparateur interne (class_config)invariant_nomme
III. Position
invariant_nommehealth_check_not_enforced_before_debtWrite
submechanism005-A
IV. Voisins — 10 autres incidents · FC-CLASS-005
fc_iddomaininvariant_nommesubmechanism
FC-019curve.fireentrancy_guard_not_emitted_by_compiler005-B
FC-025solv.financedouble_mint_check_absent_in_BitcoinReserveOffering_mint_redeem_cycle005-B
FC-029platypusdefi.comsolvency_check_absent_in_emergencyWithdraw005-A
FC-030cream.financeborrow_balance_not_updated_before_erc777_callback005-B
FC-031sturdy.financeprice_oracle_read_guard_absent_during_balancer_execution005-B
FC-034bean.moneyexecution_delay_absent_in_emergencyCommit005-C
FC-035grim.financeshare_state_not_updated_before_external_transfer005-B
FC-036hundred.financereentrancy_guard_absent_before_erc677_transfer_callback005-B
FC-037eralend.comprice_oracle_read_guard_absent_during_pool_execution005-B
FC-038sentiment.xyzprice_oracle_read_guard_absent_during_balancer_execution005-B
V. Divergence
A · Famille invariant_nomme
Famille : health_check_not_enforced_before_debtWrite · 0 co-incident(s) dans cette famille
Aucun co-incident dans cette famille.
Autres : 10 dans autre(s) famille(s)
B · Distribution invariant_nomme · FC-CLASS-005 · 11 incident(s)
borrow_balance_not_updated_before_erc777_callback
1/11
double_mint_check_absent_in_BitcoinReserveOffering_mint_redeem_cycle
1/11
execution_delay_absent_in_emergencyCommit
1/11
health_check_not_enforced_before_debtWrite
1/11 ← FC-013
price_oracle_read_guard_absent_during_balancer_execution
2/11
price_oracle_read_guard_absent_during_pool_execution
1/11
reentrancy_guard_absent_before_erc677_transfer_callback
1/11
reentrancy_guard_not_emitted_by_compiler
1/11
share_state_not_updated_before_external_transfer
1/11
solvency_check_absent_in_emergencyWithdraw
1/11
VI. Profil prédicatif sourcé
predicate_codevaleurstatutcompiled_bycompiled_at
C1_inputs_authenticvraiEVALUABLEmarcus2026-07-06T15:57:59Z
provenance [corpus_vector] : reclassif_batch1 — donateToReserves() chemin public, prix non manipules
C2_path_unprivilegedvraiEVALUABLEmarcus2026-07-06T15:57:59Z
provenance [corpus_vector] : reclassif_batch1 — donateToReserves() externe sans modifier acces, aucun role requis
C3_invariant_violatedvraiEVALUABLEmarcus2026-07-06T15:57:59Z
provenance [corpus_vector] : reclassif_batch1 — health check non execute avant debtWrite, solvabilite nette violee
invariant_nommehealth_check_not_enforced_before_debtWriteEVALUABLEmarcus2026-07-06T15:57:59Z
provenance [corpus_vector] : reclassif_batch1 — invariant nomme, verifiable on-chain TX Euler 2023-03-13
submechanism005-AEVALUABLEmarcus2026-07-06T15:57:59Z
provenance [corpus_vector] : reclassif_batch1 — 005-A economic_accounting_invariant: debtWrite sans verification health factor
VII. Rang dans la cellule
position par loss_usdrang 1 / 11
#fc_iddomaindateloss_usdsubmechanism
1FC-013euler.finance2023-03-13$197.0M005-A
2FC-034bean.money2022-04-17$80.0M005-C
3FC-019curve.fi2023-07-30$70.0M005-B
4FC-035grim.finance2021-12-18$30.0M005-B
5FC-030cream.finance2021-08-30$18.8M005-B
6FC-029platypusdefi.com2023-02-16$8.5M005-A
7FC-036hundred.finance2022-03-15$6.3M005-B
8FC-025solv.finance2026-03-03$2.7M005-B
9FC-037eralend.com2023-07-25$2.7M005-B
10FC-038sentiment.xyz2023-03-05$1.0M005-B
11FC-031sturdy.finance2023-06-12$0.8M005-B
La colonne « submechanism » affiche fc_incidents.submechanism_detail — texte libre du corpus, sans provenance compilée ni statut de validation. Ne pas confondre avec un prédicat signé (voir section VI).
VIII. Historique de révision
champavantaprèsrévisé_àpar
initial_admission— non compiléadmitted depuis staging_id 92026-06-22T20:07:27Zmarcus
url: 9
certification_note— non compiléAdmission FC-013 Euler Finance, completeness 85%, onchain_tx + calldata certifiés Marcus 2026-06-222026-06-22T20:07:27Zmarcus
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-042026-06-22T20:07:27Zmarcus
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_classFC-CLASS-001FC-CLASS-0052026-07-05T18:17:56Ztaxonomy_v2_batch1
raison: C1+C2+C3 valides CLASS-005: chemin mono-chaine, aucun bridge/relayer, invariant economic manquant (donateToReserves sans health check)
class_nameCross-Chain State Validation FailureInternal Invariant Failure2026-07-05T18:17:56Ztaxonomy_v2_batch1
raison: Alignement class_name nouvelle classe
submechanism_detail— non compilé005-A2026-07-05T18:17:56Ztaxonomy_v2_batch1
raison: Sous-famille invariant economique/comptable
IX. Ancrage
décisionanchor_confirmed
mécanismebinding_validation_gap
declared_bymarcus
créé le2026-07-03 17:05:34
rationale : binding validation gap — donateToReserves() not subject to health-check, enabled unbacked self-liquidation loop
anchor_confirmed dans cette classe : 2 / 11
Source : data/forensic_capital.sqlite · read-only · 46 incidents · 11 incidents FC-CLASS-005
FC Signal Coverage
FC signal_inventory — passive scan 2026-05-28 — 0 CONFIRMED signals for euler.finance
DefiLlama TVL — 0 data points — protocol slug:
Public sources: 4 post-mortems and investigations cited above.
Rebuilt from public chain data alone — no client access, no privileged information.
sha256 (pre-hash) · 0ee8278c7101936698aeefe2b9628265cf0e4273f2900ab49dcdfab3037736e4
seal · FC·fc-013-XXXX·2023-03-13

This document is sealed. The hash above covers the report as published; any alteration changes it.