Web3 & smart contracts
Verified by ROOTVIEL13 minAccounting / Vault Share Inflation
A vault derives each depositor’s shares from the ratio of total assets to total shares, and an attacker manipulates that ratio while it is fragile — classically as the first depositor into an empty vault — so later depositors’ shares round down to a value far below what they paid, and the attacker redeems the difference.
Classification
- Domain
- Web3 & smart contracts
- ROOTVIEL support
- Verified by ROOTVIEL — A deterministic, LLM-free verifier settles a specific property under stated assumptions — either a structural property over a graph, or an effect reproduced in execution. It does not by itself mean the whole system is secure, nor that an end-to-end exploit was run beyond what the evidence scope states.
- Evidence scope
- Observed in execution — Reproduced in an isolated sandbox or local fork — the effect was actually triggered under stated conditions.
- Analysis family
- RootRaven
- OWASP
- SC03
Definition
Accounting / share inflation is the class of bugs in which a protocol credits participants with more or fewer shares than the value they actually contributed, because the conversion between "value deposited" and "shares minted" is computed from a ratio the attacker can distort. It is a correctness failure in the core invariant that a share must represent a fair, proportional claim on the pool’s assets.
The canonical form is the ERC-4626-style first-depositor (empty-vault) inflation attack. When the vault is empty, the attacker mints a tiny number of shares with a tiny deposit, then "donates" a large amount of the underlying asset directly to the vault (inflating assets without minting shares). The share price — assets per share — is now enormous. The next honest depositor’s shares are computed as deposit × totalShares / totalAssets, which rounds down, sometimes to zero, letting the attacker redeem a disproportionate slice of the victim’s deposit.
Context
This family targets any pooled-value system that issues fungible claims against a shared asset balance: ERC-4626 tokenized vaults, LP tokens, staking/reward pools, lending-market receipt tokens, and auto-compounding strategies. The risk is highest when the vault can be empty (or nearly so) and when the share computation reads a balance that can be increased without a corresponding mint — for example a raw token balanceOf that a direct transfer can bump.
- ▸ERC-4626 vaults and their convertToShares / convertToAssets math.
- ▸LP and staking pools where first-deposit ratios are undefined or manipulable.
- ▸Any vault that prices shares from token.balanceOf(vault) rather than an internally tracked accounting balance (donation/first-depositor risk).
- ▸Reward accrual where rounding consistently favors one side.
Technical fundamentals
A vault maintains totalAssets (the underlying it controls) and totalShares (claims outstanding). On deposit it mints shares = assets × totalShares / totalAssets; on redeem it returns assets = shares × totalAssets / totalShares. Integer division always rounds toward zero, so the result is sensitive to the magnitudes involved: when totalAssets is large relative to totalShares, a modest deposit can mint zero shares.
The fragility is the boundary condition. With the vault empty, the ratio is undefined and must be seeded by a rule; a naive rule (first depositor sets 1:1, shares read from balanceOf) lets the attacker break the ratio by donating assets outside the mint path. Two durable defenses change the arithmetic: virtual shares/assets (the vault behaves as if it always holds a small virtual offset, so no attacker can be the "first" depositor and rounding can never zero out a real deposit), and a permanent minimum liquidity / dead-shares lock (the first shares are burned to a dead address so the vault is never truly empty).
- Share price
- totalAssets / totalShares — the assets each share can claim. Inflating it harms new depositors via rounding.
- Donation attack
- Transferring the underlying asset straight to the vault (no mint) so totalAssets rises without new shares, spiking share price.
- Virtual shares/assets
- A constant offset added to totalShares and totalAssets in the math, so the ratio is well-defined and rounding can never wipe out a real deposit.
- Dead shares / minimum liquidity
- A small amount of initial shares minted to an unrecoverable address so the vault is never empty and the first-depositor edge disappears.
Root cause
The defect is that the share/asset conversion is computed from a ratio that (a) is undefined or attacker-settable at an edge (the empty vault), and/or (b) reads a balance that can be increased outside the mint path (a raw balanceOf, bumpable by a direct donation), combined with integer rounding that pushes the loss onto the honest party. The invariant "shares are a fair proportional claim" is violated because the inputs to the proportion are manipulable.
This is a logic/arithmetic correctness error rather than a single low-level primitive misuse; the SWC Registry does not assign it one clean dedicated ID, so reference it against the registry generally and OWASP Smart Contract Top 10 SC03 (Logic Errors). The fix is arithmetic and lifecycle design, not a modifier.
How the attack works, step by step
- 1The vault is empty (totalShares = 0). The attacker deposits the smallest unit — say 1 wei of the underlying — and receives 1 share.
- 2The attacker donates a large amount of the underlying directly to the vault (a plain token transfer, not a deposit), so totalAssets becomes large while totalShares stays at 1.
- 3Share price is now (large) assets per 1 share. A victim deposits, say, an amount worth fewer than 2 shares; shares = deposit × 1 / totalAssets rounds down to 0 (or 1), so the victim is credited far fewer shares than their value.
- 4The victim’s deposited assets are now pooled, but the attacker still holds a dominant share fraction.
- 5The attacker redeems their 1 share for its proportional claim — now including most of the victim’s deposit — netting a profit at the victim’s expense.
Vulnerable example
A synthetic minimal vault that mints 1:1 on first deposit and prices shares from the raw token balance, so a donation inflates the ratio.
synthetic Solidity — do not deploy; illustrates the flaw// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IERC20 { function transferFrom(address, address, uint256) external returns (bool); function transfer(address, uint256) external returns (bool); function balanceOf(address) external view returns (uint256); } // UNSAFE: first-depositor inflation via donation + rounding. contract Vault { IERC20 public asset; uint256 public totalShares; mapping(address => uint256) public sharesOf; constructor(IERC20 _asset) { asset = _asset; } // Reads the RAW balance, which a direct donation can inflate. function totalAssets() public view returns (uint256) { return asset.balanceOf(address(this)); } function deposit(uint256 assets) external returns (uint256 shares) { if (totalShares == 0) { shares = assets; // 1:1 seeding — attacker mints 1 share for 1 wei } else { // Rounds DOWN: a victim can be credited 0 shares after inflation. shares = (assets * totalShares) / totalAssets(); } asset.transferFrom(msg.sender, address(this), assets); totalShares += shares; sharesOf[msg.sender] += shares; } function redeem(uint256 shares) external returns (uint256 assets) { assets = (shares * totalAssets()) / totalShares; sharesOf[msg.sender] -= shares; totalShares -= shares; asset.transfer(msg.sender, assets); } }
Corrected example
Two complementary defenses. Virtual shares/assets add a constant offset to both sides of every conversion, so the vault behaves as if it is never empty and a real deposit can never round to zero; the donation’s effect is diluted to negligible. A permanent dead-shares lock on first deposit removes the first-depositor edge. Internal accounting (not raw balanceOf) further neutralizes donations.
synthetic Solidity — virtual offset + dead shares + internal accounting// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IERC20 { function transferFrom(address, address, uint256) external returns (bool); function transfer(address, uint256) external returns (bool); } // SAFER: virtual shares/assets offset, dead-shares lock, tracked balance. contract Vault { IERC20 public asset; uint256 public totalShares; uint256 internal _trackedAssets; // internal accounting, not balanceOf mapping(address => uint256) public sharesOf; uint256 private constant VIRTUAL = 1e3; // virtual offset on both sides address private constant DEAD = address(0xdead); bool private _seeded; constructor(IERC20 _asset) { asset = _asset; } function _toShares(uint256 assets) internal view returns (uint256) { // +VIRTUAL on both numerator basis and denominator: never divide-by-zero, // and a donation is diluted by the virtual offset. return (assets * (totalShares + VIRTUAL)) / (_trackedAssets + VIRTUAL); } function deposit(uint256 assets) external returns (uint256 shares) { require(assets > 0, "zero"); shares = _toShares(assets); require(shares > 0, "shares rounded to zero"); // refuse a dust-loss deposit asset.transferFrom(msg.sender, address(this), assets); _trackedAssets += assets; // only mint-path deposits count if (!_seeded) { _seeded = true; // Permanently lock a minimum of shares so the vault is never empty. uint256 dead = shares / 10; // synthetic size; tune to the asset sharesOf[DEAD] += dead; shares -= dead; totalShares += dead; } totalShares += shares; sharesOf[msg.sender] += shares; } function redeem(uint256 shares) external returns (uint256 assets) { assets = (shares * (_trackedAssets + VIRTUAL)) / (totalShares + VIRTUAL); sharesOf[msg.sender] -= shares; totalShares -= shares; _trackedAssets -= assets; asset.transfer(msg.sender, assets); } }
Why it works: the virtual offset makes the conversion well-defined even when the vault is empty and ensures a genuine deposit always mints a non-zero share count, so rounding cannot be weaponized; using internal _trackedAssets means a direct donation does not change the ratio the math uses; and the dead-shares lock removes the privileged first-depositor position. The require(shares > 0) refuses any residual dust-loss deposit outright.
Limits of the fix: a virtual offset shrinks but never perfectly eliminates rounding loss — it bounds the maximum value an attacker can grief to a tiny, offset-dependent amount, which must be sized against the asset’s decimals and price. Dead shares cost the first real depositor a small fixed amount. Internal accounting must be applied everywhere value enters or leaves, or a donation path reopens the hole. None of this makes the vault "safe"; it makes the known inflation attack uneconomical within chosen parameters.
Impact
- ▸Theft from depositors: later depositors lose part or all of their deposit to the inflating attacker via rounding.
- ▸Silent, per-user loss: no function reverts and no global invariant looks broken, so the loss can go unnoticed until users try to redeem.
- ▸Trust collapse: a vault that mis-credits shares is unusable even at small loss magnitudes, draining liquidity.
- ▸Composability damage: protocols that treat vault shares as collateral inherit the mispricing.
Detection
Static analysis can flag the preconditions: a share conversion that divides by a totalAssets derived from raw balanceOf, a first-deposit branch that mints 1:1, the absence of a virtual offset or dead-shares lock, and a deposit path that can mint zero shares. Those are leads. Confirmation is arithmetic and dynamic: on a local chain, run the full inflate-then-victim-deposit sequence and measure whether the attacker redeems more than they funded and whether the victim is short-changed. A proven value transfer from victim to attacker is a Finding; a missing offset alone is a signal, because the exploitability depends on decimals, amounts and whether a donation path actually exists.
How RootRaven analyzes it
- Support level
- Verified — vault accounting is a core execute-and-verify family: the loss is demonstrated numerically by running the sequence.
- The truth it decides
- Whether a protocol credits more value than it actually received or holds — specifically whether the share-to-asset ratio can be manipulated to steal from depositors.
- Evidence required
- An executed deposit/donate/redeem sequence whose deterministic verifier shows the attacker’s net gain comes at a measured loss to an honest depositor, beyond the attacker’s own funding.
RootRaven treats a missing virtual-offset or raw-balance-pricing Observation as a Hypothesis ("a first depositor can inflate the ratio and skim later deposits"). The AI composes the candidate sequence — tiny deposit, large donation, victim deposit, attacker redeem — but never adjudicates it. The sequence is executed on a disposable local blockchain or a local fork of real on-chain state (never on a live public network), and an AI-free verifier does the accounting: it compares each actor’s assets in versus assets out and checks the invariant that no honest depositor can be made strictly worse off than a fair proportional claim. Only a measured, attacker-funding-exceeding transfer of value becomes a Finding.
The verifier decides on measured value flow, not on the shape of the code. A vault lacking a virtual offset but with amounts and decimals that make the skim economically negligible yields a signal, not a Finding, and a clean run is reported as "no profitable share inflation proven within the tested scope", never "the vault is safe". This arithmetic family is mature; novel multi-asset or rebasing-token accounting carries a lower maturity caveat.
Exploitability is parameter-dependent (decimals, donation availability, deposit sizes). RootRaven reports the concrete amounts it proved; a different parameterization may change the result in either direction.
Mitigation
Design the arithmetic and lifecycle so a share is always a fair proportional claim, and never let a non-mint path change the ratio. The arithmetic is the load-bearing control.
- ▸Preventive: add virtual shares/assets (the ERC-4626 "decimals offset" / virtual-offset pattern) so the empty-vault edge is well-defined and rounding cannot zero a real deposit.
- ▸Preventive: lock a permanent minimum of shares (dead shares / minimum liquidity) on first deposit so the vault is never empty.
- ▸Preventive: price shares from internally tracked accounting, not raw balanceOf, so direct donations do not move the ratio.
- ▸Preventive: round conservatively — round shares down on deposit and assets down on redeem (never in the user’s favor against the pool), and revert on a deposit that would mint zero shares.
- ▸Detective/Corrective: monitor share price for sudden jumps; cap or seed initial deposits; prefer audited ERC-4626 implementations over hand-rolled math.
Verifying the mitigation
Verify numerically, by running the attack against the patched vault on a local chain: perform the tiny-deposit + large-donation + victim-deposit + redeem sequence and confirm the attacker’s net is not positive at the victim’s expense, and that the victim’s redeemable value matches a fair proportional claim within the offset-bounded rounding. Sweep across decimals, donation sizes and deposit magnitudes. Report "the inflation attack was non-profitable within the tested parameters", not "the vault is safe".
A non-profitable result is specific to the amounts and token decimals tested. Re-run across the realistic range of deposit sizes and for any asset whose decimals or rebasing behavior differs.
Common mistakes
- ▸Pricing shares from token.balanceOf(vault), leaving a direct-donation path that moves the ratio outside the mint flow.
- ▸Seeding the first deposit 1:1 with no virtual offset and no dead-shares lock.
- ▸Allowing a deposit to mint zero shares instead of reverting, which silently hands value to existing holders.
- ▸Rounding in the user’s favor, or inconsistently between deposit and redeem, so the pool slowly leaks.
- ▸Assuming a virtual offset makes the vault immune rather than merely bounding the griefing loss to a small, parameter-dependent amount.
References
- [1]OWASP Smart Contract Top 10 — SC03 Logic Errors
- [2]SWC Registry (overview)
- [3]EIP-4626 — Tokenized Vault Standard (security considerations)
- [4]OpenZeppelin — ERC4626 inflation attack & virtual-offset mitigation
References point to public, well-known technical sources for further study.
Related articles
