How the AuditHub Approach Would Have Flagged Euler’s $197M Bug: Pre-Mortem

Share this post

euler-finance-insolvency-bug-audithub-pre-mortem

A routine upgrade left one Euler function free to reduce collateral with no health check at the end. A custom detector flags exactly that omission at commit time.

Pre-Mortem is a weekly series by Kostas Ferles, CTO of AuditHub. This article looks at a solvency check that every fund-moving function ran except one, and the automated pass that catches the omission before deployment. New breakdown every week. Find Kostas at @KFerles.

Jon Stephens just covered this bug in the Auditor’s Take: how a senior auditor catches the one Euler function missing the health check the rest of the functions had. That article prompted this one. If an auditor can catch that by reading the code, a detector should catch it automatically, on every commit. So here is the deep dive from the AuditHub side: could a static analyzer have flagged the missing check before it ever reached audit?

TL;DR

  • Euler’s donateToReserves cut a caller’s collateral without the checkLiquidity call every other function ran.
  • Vanguard flags any function that writes a balance without reaching the checkLiquidity call.
  • Run on Euler’s EToken contract, the detector returns exactly one function: donateToReserves.
  • The detector is configurable per protocol and re-runs on every commit, before production.

What is the AuditHub Approach

The AuditHub Approach is how we systematically catch the classes of bugs that manual review struggles to reason about exhaustively. It lets a team define automated checks tailored to their own protocol, in code, so the reasoning a reviewer does once keeps running. For a lending protocol like Euler, it rests on two pillars.

The first is custom static analysis: per-protocol detectors that flag suspicious structural patterns at commit time. The second is specification-guided fuzzing: running a formal invariant against real contract state to confirm whether a flagged pattern is actually exploitable.

Static analysis is fast and exhaustive, but it works at the structural layer, so it tells you where a bug could be, not whether it is. Fuzzing works at the execution layer and answers exploitability, but it is slow without a target to aim at. The combination is the point, though which pillar carries a given case depends on the bug. The SuperRare bug in an earlier Pre-Mortem was source-shaped, and one static pass settled it. Euler’s donateToReserves is the same kind of case: the detector below settles exactly where the check is missing.

How Vanguard flags the missing check

Vanguard analyzes Solidity without running it, matching patterns defined in a detector written for the protocol’s own logic. The property is simple to state: any externally callable function that changes a user’s balance has to reach a checkLiquidity call before it returns. The detector encodes exactly that and flags any function that violates it.

The query keeps every externally callable function on the EToken contract that writes a user’s balance and does not reach a checkLiquidity call anywhere in its execution. This is interprocedural: it follows the balance write and the check across function boundaries, not just within one body. Run against that contract, the detector reports a single function:

What static analysis cannot tell you is whether that missing check actually leads to a loss. A function can skip a guard and still be safe if nothing downstream can push the account past the boundary. For Euler, that comes down to leverage and liquidation elsewhere in the protocol, which the companion Auditor’s Take walks through in full.

A takeaway for developers

Any protocol that enforces a safety invariant by having each function remember to call the same check is one commit away from the same failure. The moment someone adds an entry point and forgets the call, the invariant holds everywhere else but not on that new function.

A custom Vanguard detector written for this invariant catches the same mistake the next time it happens, flagging any new function that touches a balance without reaching the guard, on every commit. Pair it with a V Spec run against the deployed market, so what the detector flags is confirmed exploitable before user funds arrive, not after. Better still, enforce the invariant by construction: route the health check through a single point every state-changing path has to cross, so no new function can skip it. Euler itself moved in that direction in a later version.

Run these on every commit, not once, because of timing. An audit is a snapshot of the code the reviewers saw, and donateToReserves landed in an upgrade well after Euler’s core was first audited. The code keeps changing; the invariant does not. So the check belongs on every push, not in a single review.

Want checks like this running on your codebase?

One missing call turned a donation function into a $197M drain. If you want a detector like this, and the fuzzing that confirms it, running on your protocol on every commit, try AuditHub for free or book a demo.

What to remember about insolvency bugs

Insolvency bug detection is the practice of confirming that every state-changing entry point re-establishes a protocol’s solvency invariant, instead of trusting that each function remembered to check. It flags the function that skips the guard, the oversight that let Euler’s donation path create bad debt. Run on every commit, it surfaces the problem before deploy.

Sign up to our newsletter

Stay up to date with the latest news and developments from AuditHub

No spam. Always free. We respect privacy.

About author

Picture of Kostas Ferles

Kostas Ferles

Chief Technology Officer (CTO)

More articles

FeaturedSecurity Analysis

How the AuditHub Approach Would Have Flagged Beanstalk’s $182M Bug: Pre-Mortem

FeaturedSecurity Analysis

How the AuditHub Approach Would Have Flagged Nomad’s $190M Bug: Pre-Mortem

AuditHub Announcements

Run AuditHub’s Security Tools From Your AI Agent

An open-source MCP server that connects Claude Code and Codex to AuditHub for static analysis and fuzzing runs.

Ready to automate your security?

Join leading Web3 teams who’ve already embedded continuous security into their development process.

 Get started in 30 minutes / No setup required / See results immediately