DEV Community

juan23z
juan23z

Posted on

7 ways to drain an ERC-4626 vault (and how a good protocol closes each one)

Based on real audits of production vaults and AMMs.

A tokenized vault (ERC-4626) looks simple: deposit assets, get shares; burn shares, get assets back. Yet the
4626 is one of the contract types where the most funds have been lost. These are the 7 cracks I check in
every vault, and how each is properly closed:

1. First-depositor inflation. The classic: the first depositor mints 1 wei of shares, donates assets to
the vault, and rounding makes the second depositor receive 0 shares — losing their deposit. Closed with
virtual shares/assets (OpenZeppelin's +1 and 10**offset pattern) or a minimum initial deposit.

2. Rounding in the user's favor. On every asset↔share conversion, if rounding favors the withdrawer, the
vault is drained penny by penny. Golden rule: deposit/mint round against the user; withdraw/redeem too. A
single Floor where a Ceil belonged = a leak.

3. Manipulable totalAssets. If the vault reads its assets from an external protocol (a "connector") and
that balance can be inflated (donation, oracle), the share price is manipulated. Closed by reading from
sources that can't be inflated and using trusted connectors, immutable per pool.

4. Reentrancy in hooks. Modern vaults call extensions/callbacks during deposits and swaps. If a hook runs
via delegatecall, a malicious extension is the vault. Closed by using call (not delegatecall),
checking the return value, and isolating extensions per pool.

5. Fee accounting as shares. Charging the fee by minting shares to the protocol is correct — but an
ordering bug (accruing without updating the "last total") means double-charging or misallocated dilution.
Closed by updating lastTotalAssets on every operation that touches the fee (deposit, withdraw, collect).

6. Access control on upgrades. Many vaults are upgradeable via beacon/proxy. If the upgrade path isn't
gated well, an attacker deploys malicious code into the vault. Closed with strict roles and, where
applicable, a timelock.

7. Bad debt not socialized correctly. When a position ends up with uncollectable debt, the loss must be
shared across LPs via a lossFactor — and beware dividing by totalUnits when it can be zero.


The difference between a vault that holds and one that gets drained isn't a big idea: it's discipline in
rounding, access control, and accounting
. When we audit, we check all 7, one by one, with the real code in
front of us — not with assumptions.

Got a vault, an AMM, or any contract that moves funds and want a fast, honest review? See a sample report:
https://juan23z.github.io/sample-audit-report.html — or reach out: naweljuan@gmail.com


I run fast, honest Solidity security reviews (custom detectors + manual verification, 0 false positives on all of OpenZeppelin) plus continuous monitoring. Sample report · Order an audit

Top comments (0)