Rugproof Security Audit — Inflatable4626.sol
2026-05-13 · grade F · 1 Critical, 1 High
Summary
Naive ERC-4626 vault with the canonical "inflation" / "donation" attack. The first depositor mints 1 share for 1 wei, then donates a large amount directly to the vault via raw transfer. The vault's totalAssets() ballons; the next depositor's reasonable-sized deposit rounds to zero shares — the vault keeps the asset, the user gets nothing.
This pattern caused multi-protocol losses across 2023. OpenZeppelin v4.9 added the canonical defense (virtual shares / virtual assets); this contract uses none of it.
Findings
| ID | Sev | Title |
|---|---|---|
| INFL-001 | Critical | Naive convertToShares math, no virtual shares, no dead-shares, no minimum first deposit |
| INFL-002 | High | totalAssets() = balanceOf(this) — donation manipulable |
| INFL-003 | High | Round-direction reversed in withdraw (favors withdrawer, not vault) |
INFL-001 — Inflation attack (Critical)
shares = assets * totalSupply / totalAssets(); // rounds down to 0 if totalAssets >> assets * totalSupply
Attack:
- Attacker
deposit(1)→ mints 1 share, owns the vault. - Attacker
asset.transfer(address(vault), 1e30)→ totalAssets is now huge, but totalSupply is still 1. - Victim
deposit(1e18)→shares = 1e18 * 1 / 1e30 = 0→ victim loses 1 ETH for 0 shares. - Attacker
withdraw(1)→ claimstotalAssets * 1 / 1 = 1e30 + 1(their 1 wei + their donation + the victim's deposit).
Defense (use OZ ERC4626 v4.9+):
function _decimalsOffset() internal pure override returns (uint8) {
return 6; // virtual shares = 10^6 — donation impact divided by 10^6
}
INFL-002 — totalAssets() = balanceOf(this) (High)
function totalAssets() public view returns (uint256) {
return asset.balanceOf(address(this));
}
Anyone can pump totalAssets() via raw transfer. Track internally via deposit/withdraw events.
INFL-003 — Round direction reversed in withdraw (High)
assets = shares * totalAssets() / totalSupply; // rounds DOWN
ERC-4626 spec: withdraw should round UP on shares to favor the vault (i.e. burn slightly more shares than mathematically necessary). This implementation rounds DOWN, which slowly drains rounding-residue value to withdrawers.
Recommendation
Replace with OpenZeppelin's ERC4626Upgradeable ≥ v4.9.0:
contract MyVault is ERC4626Upgradeable {
function _decimalsOffset() internal pure override returns (uint8) { return 6; }
}
Or — if you need a dependency-free implementation — apply Solady's ERC4626 which uses dead-shares + minimum-first-deposit.
Skill detail: erc4626-inflation, yield-aggregator-specialist.
Generated by Rugproof — https://omermaksutii.github.io/RugProof