DEV Community

aaannvv
aaannvv

Posted on

Exact Numbers or It Didn't Happen — The Finding I Refuted Hours Before Submitting It

I almost submitted a wrong finding. Not a borderline one — a confidently written, textbook-shaped, "Low severity, high confidence" report whose core math was wrong in a way I could only see when I finally pushed concrete wei-level numbers through it. This is the story, because the failure mode is more instructive than the techniques that worked.

The finding

An automation contract deposits a position's whole idle balance into an ERC4626 vault and reverts if it receives zero shares. No minShares parameter anywhere. The draft wrote itself: classic ERC4626 inflation attack. Attacker takes a first position of one share, donates 999× the expected deposit to the pool, victim deposits and receives a positive but negligible share count, attacker redeems and captures ~99.9% of the victim's principal. "The zero-share check blocks only total loss; every partial-loss shape passes." I had a four-step attack script, a severity justification, and a recommendation mirroring the amountOutMin pattern used elsewhere in the same codebase. It looked airtight.

The refutation

At final review I made myself do what I now consider mandatory: walk the exploit with exact numbers, end to end, in wei.

Setup: attacker holds 1 share. Attacker donates D = 999·A where A is the victim's deposit. Pool before victim: totalShares = 1, totalAssets = 1 + D.

Victim deposits A: shares_minted = floor(A · 1 / (1 + D + A)) = floor(A / (1000A)) = floor(1/1000) = 0.

Zero. The zero-share check reverts the transaction. Try to pass the check — push shares_minted to at least 1 — and you need D ≈ A, and then the victim's share of the pool is A/(A + A + A-ish) ≈ one third, and they redeem back roughly everything they put in, minus dust.

That's when the algebra fell apart. Pro-rata redemption is self-consistent: a large depositor's fraction of the pool after donating-and-depositing is A/(D + 2A), and they redeem almost all of A back no matter how big D gets. The classic inflation attack only harms deposits small enough to round down to zero shares — and this contract only ever deposits whole-position liquidity, which is large, and it already reverts on zero shares. The only provably harmful form was blocked; the form I claimed was impossible.

Why the intuition was wrong

The "donation dilutes the victim to nothing" intuition comes from fixed-share schemes. ERC4626 mints shares at deposit time, priced by the post-donation ratio — so the donation is already baked into the exchange rate the victim receives. The victim is never holding pre-donation shares. Every "they lost 99.9%" claim quietly assumed the victim's shares were priced before the attack. Analogies to famous exploits skip exactly the term that matters.

The rules I took from it

  1. Wei-level numbers, end to end, before believing any finding. If you can't walk the exploit with concrete amounts, you don't have an exploit; you have a story.
  2. Push the boundary values both ways. Make the attack fail the guard on purpose, then relax it one unit at a time and watch what the victim actually recovers. The loss curve is not what you think it is.
  3. Analogies are how findings die in triage. The judge runs the same arithmetic you skipped. A wrong submission costs credibility with every triager who remembers your name; a self-refuted one costs an evening.

I submitted nothing that round. That was the correct outcome.

Top comments (0)