Rounding Is a Security Decision

Integer division always discards something. Who absorbs that loss — the protocol or the user — determines whether a vault can be drained one wei at a time.

Published by Xevry

Solidity has no floating point. Every share price, exchange rate and fee is computed with integer arithmetic, and every division truncates toward zero. That truncation is not a numerical detail. It is a transfer of value, and the direction of that transfer is a design decision with security consequences.

The rule

Round in favor of the protocol. Always.

When a user deposits, round the shares they receive down. When a user withdraws a fixed amount of assets, round the shares burned up. Any rounding the other way creates a sliver of value an attacker can extract — and slivers become significant when they can be repeated in a loop or amplified by a manipulated denominator.

ERC-4626 encodes this directly in its specification: convertToShares, previewDeposit and previewRedeem round down, while previewMint and previewWithdraw round up.

The first-depositor inflation attack

The best-known consequence of rounding is the vault inflation attack. A naive vault computes shares like this:

function deposit(uint256 assets) external returns (uint256 shares) {    uint256 supply = totalSupply();    shares = supply == 0 ? assets : assets * supply / totalAssets();    _mint(msg.sender, shares);    asset.transferFrom(msg.sender, address(this), assets);}

Walk through it with an empty vault:

  1. The attacker deposits 1 wei and receives 1 share.
  2. The attacker donates X tokens directly to the vault with a plain transfer. No shares are minted. totalAssets() is now X + 1.
  3. A victim deposits X tokens. Their shares are X * 1 / (X + 1), which rounds down to 0.
  4. The attacker holds the only share, now backed by 2X + 1 tokens, and redeems it — netting the victim's entire deposit.

The attacker typically front-runs the victim's deposit transaction, so this is most dangerous for newly deployed vaults and for vaults that can return to zero supply.

Defenses

DefenseHow it worksTrade-off
Virtual shares and assetsAdd a constant offset to both supply and assets in conversions, so a donation must be orders of magnitude larger to cause meaningful rounding.Small, bounded loss distributed to the virtual position.
Decimals offsetRepresent shares with more decimals than the asset (e.g. +6), multiplying the attacker's required donation by 10^offset.Share amounts are larger numbers; integrators must handle decimals correctly.
Dead sharesMint an initial amount of shares to an unrecoverable address at deployment.Requires an initial deposit; protects only that vault instance.
Internal accountingTrack deposited assets in storage rather than reading balanceOf(this), so donations do not move the price.Unsolicited transfers are stranded unless explicitly swept.

OpenZeppelin's ERC-4626 implementation combines virtual shares with a configurable _decimalsOffset(). It is a sensible default, but it is a mitigation with tunable strength, not a proof — the offset should be chosen with the expected deposit sizes in mind.

Precision loss beyond vaults

The same thinking applies everywhere a division occurs:

  • Multiply before you divide. a / b * c loses precision that a * c / b keeps — provided the multiplication cannot overflow. Use mulDiv for full-precision 512-bit intermediate results.
  • Fees that round to zero can be bypassed by splitting one large action into many small ones.
  • Reward-per-token accumulators must be scaled (commonly by 1e18 or more) or small stakers silently earn nothing.
  • Decimal mismatches between tokens (6 vs 18) amplify every rounding error by 10^12.
Audit heuristicFor every division, ask: which way does this round, who benefits, and can the caller choose the inputs to make the remainder as large as possible? Then try it in a fuzz test with tiny and enormous values.
All research Next: BOLA: The API Bug Your Scanner Will Never Find
Start a conversation

Let's discuss your security needs.

Tell us what you're building. We'll respond with clear questions, an honest view of whether we're the right fit, and a scoped proposal if we are.