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
Comments
No comments yet. Start the discussion.