EVM patterns
ERC4626 Vault Security: Rounding, Donations and Safe Integration
Protecting deposits requires an enforced execution bound as well as sound share accounting. ERC4626 vault security also extends to the oracle routes that turn shares into collateral: a rounding defense does not establish that an integrated lending market is safe.

Key facts
- Original experiment
- 400000 constructed scenarios across 4 conversion models; 100000 cases each
- Offset 0 result
- 90100 zero-share deposits despite 0 cases of positive attacker profit
- Higher offsets
- Offsets 3 and 6 produced no zero-share deposits within the tested grid
- Measurement boundary
- Integer model with one user per case; no EVM deployment or live vault tested
- Reference implementation
- OpenZeppelin Contracts v5.4.0 conversion equations, retrieved September 9, 2026
Identify the accounting and execution boundaries
A vault can satisfy an interface check while exposing a depositor to an unacceptable exchange rate. ERC4626 vault security concerns the accounting that converts assets into shares and the conditions under which those shares can be acquired or redeemed. Reviewing the function names does not establish either property. Read the implementation behind them, including any overrides inherited by the deployed contract.
Start with the meaning of totalAssets(). The standard describes managed assets. OpenZeppelin Contracts v5.4.0, the fixed reference used for our experiment, returns the underlying token balance held by the vault. Under that implementation, an unsolicited token transfer changes the conversion numerator without minting corresponding shares. A strategy vault may calculate managed assets differently. Its debt accounting, valuation updates or loss recognition then become part of the review.
- Asset units
- Amounts in the smallest indivisible unit of the underlying token. A deposit amount in contract arithmetic is not automatically a whole token.
- Share units
- Amounts in the vault token's own smallest unit. Additional share precision changes the integer representation used in conversion.
- Conversion estimate
convertToSharesandconvertToAssetsdescribe an idealized conversion. They exclude fees and cannot substitute for an execution limit.- Execution result
- The assets actually received or shares actually minted or burned, under the state in which the transaction executes.
Keep the depositor's outcome separate from the attacker's outcome. Losing a deposit to rounding is a user failure even if the party that manipulated the price loses more. Conversely, a vault that preserves reasonable deposit rounding can still expose a lending market that values its shares through a manipulable exchange rate. These are different acceptance questions and need different evidence.
The review boundary should identify the underlying asset and the vault implementation address before any test result is attached to a deployment. Record the router that users are expected to use. A guard implemented only in that router does not describe direct calls to the vault, and a frontend estimate does not enforce the signed transaction's eventual result.
Original experiment across 400000 deposit scenarios
We enumerated a bounded integer model on . Its purpose was to compare zero-share deposits with positive attacker profit under the same inputs. These are constructed scenarios, not observations of deployed vaults. The comparison includes a naive conversion baseline and virtual-adjusted equations reproduced from the pinned OpenZeppelin source.
Method
- Population
- Every combination of donation amounts from
1through1000asset units and user deposits from1through100asset units, for each of4models. - Sequence
- The attacker deposits
1asset unit into an empty vault, directly donates assets, waits for the modeled user deposit and redeems all attacker shares. - Denominator
100000cases per model and400000overall. No case was excluded or returned an unknown result.- Models
- Naive conversion without virtual balances, followed by virtual balances with decimal offsets
0,3and6. - Accounting assumptions
- Exact token transfers, no yield or fees, no minimum-share rejection and one user deposit per case. Attacker profit is the redemption payout minus the initial deposit and donation.
- Evidence
- The script
seo/research/erc4626-vault-security-2026-09-09/rounding-survey.py, outputrounding-survey.jsonand complete compressed case ledgerrounding-cases.csv.gzare retained in that repository directory. They are not published download links.
For the virtual-adjusted model, let A be actual assets and S actual share supply before a deposit. The share result is the integer floor of assets * (S + 10**offset) / (A + 1). Redemption uses the inverse adjusted fraction and also rounds down. The baseline omits both virtual terms and initially mints one share unit for the attacker's one asset unit.
Results
| Conversion model | Cases | Zero user shares | Positive attacker profit |
|---|---|---|---|
| Naive baseline | 100000 | 95050 | 98669 |
| Virtual balances, offset 0 | 100000 | 90100 | 0 |
| Virtual balances, offset 3 | 100000 | 0 | 0 |
| Virtual balances, offset 6 | 100000 | 0 | 0 |
The offset-zero row separates the two outcomes sharply. No attacker redemption produced a positive profit in that row, yet zero-share deposits remained possible. Raising precision eliminated zero-share results within this grid. Neither result establishes how often a real user encounters the condition, because the grid is deliberately uniform over small integer amounts.
| Model | User share units | Attacker payout in asset units | Attacker profit in asset units |
|---|---|---|---|
| Naive baseline | 0 | 1101 | 100 |
| Virtual offset 0 | 0 | 551 | -450 |
| Virtual offset 3 | 199 | 501 | -500 |
| Virtual offset 6 | 199600 | 501 | -500 |
Share counts across offsets use different precision, so compare them as integer conversion results, not as directly comparable amounts of wealth. In the offset-zero example, the user's deposit still receives no ownership claim. An enforced positive minimum-share requirement would reject that execution even though the modeled attacker loses money.
Limits
- The script executes arithmetic, not Solidity or an EVM deployment. It does not exercise token transfers, reentrancy or inherited overrides.
- Only one user follows each donation. Repeated deposits and alternative attacker sequences require their own model; no conclusion about them follows from these totals.
- Gas and financing costs are omitted. Positive profit means positive asset balance change in the model before those costs.
- Inputs outside the stated grid, strategy losses and share-oracle integrations were not tested. The selected offsets are comparison settings, not universal recommendations.
Rounding and slippage checks for each operation
Choose the execution bound from the quantity the user fixes. A deposit fixes assets spent, so its protection concerns shares received. A mint fixes shares received, so the corresponding protection concerns assets spent. Applying a minimum-output check to every method without considering its direction can leave the actual exposure unbounded.
| Operation | Fixed input | Preview rounding | Execution bound |
|---|---|---|---|
deposit | Asset amount | Shares down | Minimum shares received |
mint | Share amount | Assets up | Maximum assets spent |
withdraw | Asset amount | Shares up | Maximum shares burned |
redeem | Share amount | Assets down | Minimum assets received |
ERC-5143's slippage-protected overloads express these bounds as additional arguments. Check whether the specific vault or wrapper implements equivalent enforcement. The base interface alone does not supply those extra arguments. A wallet should make the user's intended bound part of the transaction path that will execute, with the correct receiver and asset denomination.
A preview is not a reservation. Reading a favorable result before signing leaves time for state to change before execution. Even a fresh preview inside a wrapper answers the current-state conversion question; comparing the result only against that fresh preview can accept a price already manipulated before the wrapper starts. The user's bound needs an independently chosen tolerance or reference, not automatic acceptance of whatever the vault currently reports.
Rejecting zero shares is only a minimum sanity check. A result that mints a positive share amount can still be far below the user's acceptable exchange. Derive the permitted result from the quoted amount and the user's tolerance, with a documented rounding rule for that bound itself. Test the exact boundary: an amount just outside the permitted result should fail, while the amount at the boundary should follow the specified policy. Otherwise, a nominal slippage field can conceal an off-by-one error or a default that effectively disables protection.
Test limits separately. A nonzero previewDeposit result does not establish that a receiver can deposit, because preview functions intentionally ignore limits checked through the corresponding maximum functions. Also verify the actual transaction outcome. The transaction simulation security guide explains why a preflight result must retain its state context and cannot guarantee a later execution.
Fees make the distinction more consequential. Conversion functions exclude fees, while operation previews include the relevant deposit or withdrawal fee. If an integration calculates its minimum from a fee-free estimate, a legitimate fee may cause an unexpected revert. If it silently relaxes the bound after that revert, it may accept an exchange the user never authorized. Present a new quote for renewed approval when the economic terms change.
Choose defenses for the state that can change
A useful defense review starts with the state variable the proposed control constrains. Virtual balances change conversion arithmetic. Internal accounting can prevent an unsolicited transfer from changing recognized managed assets. A transaction bound constrains the user's realized exchange. Each control has a different scope, so evidence for one cannot close the others.
| Control | Mechanism addressed | Remaining requirement |
|---|---|---|
| Virtual balances and share precision | Empty-vault conversion and donation economics | Test the actual offset with minimum supported deposits |
| Internal managed-asset accounting | Unsolicited transfers changing the recognized balance | Reconcile legitimate gains and losses |
| Minimum output or maximum input | An execution outside the user's tolerance | Enforce the bound on every supported user route |
| Funded initialization | Low initial supply and cheap conversion movement | Review ownership and residual supply after withdrawals |
| Collateral valuation controls | Share-rate changes entering borrowing power | Trace every oracle route and extraction path |
For minimum-deposit policy, begin with the asset units the application actually supports. Include the smallest accepted amount, values around a rounding boundary and the state after nearly all shares have been redeemed. A test suite that starts every case with a large seeded balance may never reach the condition the initialization policy is supposed to control. Record why the seed remains in place and who can remove it.
Internal accounting introduces its own reconciliation job. If unsolicited transfers are excluded, the system must still define how earned yield or realized losses enter the recognized balance. Rebasing assets and transfers that deliver less than the requested amount can break assumptions borrowed from an exact-transfer model. Either demonstrate their supported behavior or reject that asset type explicitly. Merely using a familiar token transfer helper does not supply the accounting policy.
Do not treat a larger offset as a substitute for checking integration precision. A downstream system may assume that vault shares use the same decimals as the underlying asset. Review displays and amount normalization as well as transaction encoding. A correct on-chain ratio can still be misrepresented by a consumer that rescales it incorrectly.
For customized vaults, inspect the transfer and share-accounting order around callbacks. The pinned reference transfers assets before minting shares and burns shares before transferring assets out. Overrides that call strategies or other tokens create additional intermediate states. Add those states to the protocol invariant specification with the balances and permissions that must remain valid during a callback.
Trace shares into collateral valuation
Share valuation creates an exposure beyond the depositor's entry price. A lending market may use a vault conversion to translate posted shares into collateral value. If that conversion moves after an unsolicited transfer, the next question is whether borrowing power moves with it. A deposit-level rounding defense does not answer that question.
Euler's documented donation scenario is explicitly hypothetical and conditional. Its own vault shares use internal cash accounting that breaks the direct-donation assumption described there. External share vaults and their oracle routes still require inspection. Preserve that distinction when reviewing an integration rather than generalizing a mechanism to every vault implementing the same interface.
- Identify which incoming asset movements change the vault's recognized managed balance. Distinguish ordinary deposits from transfers that mint no shares.
- Read the conversion used by the selected oracle, including wrappers and normalization. Do not infer its behavior from the oracle's display name.
- Follow that value into collateral recognition and the market's borrowing constraints, then establish whether available liquidity permits extraction.
- Include the donor's costs and retained share ownership when evaluating the full position. A displayed price increase alone does not establish profit.
The same map helps identify an accounting interruption. If assets temporarily leave a vault through a flash-lending path, determine whether the conversion sees that temporary balance and whether another consumer can read it before repayment. Euler's exchange-rate research also describes rounding remainders as a source of accumulated balance changes. Those paths require stateful tests; our single-donation experiment does not measure them.
Build the release evidence packet
Attach test results to the deployed behavior the integrator will use. A passing arithmetic model is useful evidence about its equations. Closing an implementation review requires the asset behavior and the execution routes too. Keep a failed test input intact so that a fix can be rerun against the same state.
- Freeze the implementation and dependency versions. List conversion overrides and every place that changes recognized assets without minting or burning shares.
- Run boundary deposits around empty and nearly empty states. Check both the user's minted shares and the attacker's complete asset balance change.
- Exercise each supported router and direct-call path with an unacceptable result. Confirm that the user-selected bound causes a revert and that a successful path reaches the intended receiver.
- Test recovery after losses or disabled withdrawals, retaining the maximum-function responses and actual transaction results. A preview amount alone does not establish redeemability.
- Document external consumers of the share price. Keep the collateral integration review separate from the vault's deposit-rounding results.
Use the failed case to distinguish an accounting correction from a rejected transaction. Preserve the asset balance before the donation, the recognized balance immediately before deposit and the share balance after execution. If the fix changes totalAssets(), explain why the new recognized balance reflects the intended accounting policy. If the fix adds a bound, show that the same input now reverts without leaving a partial deposit. Those outcomes close different findings.
The evidence packet should also distinguish an expected revert from a test that never reached its target. An exhausted allowance or insufficient account balance can stop the transaction before the slippage check executes. Arrange the preconditions so that the unacceptable conversion is the reason for rejection, then verify a neighboring acceptable transaction succeeds. Retain both receipts or local execution traces with their input amounts. A bare assertion that a call reverted cannot identify which guard protected it.
For an external engagement, use the security review firm directory to define the needed scope before selecting a reviewer. The Pharos Production profile provides a starting point for that firm's service coverage. Request explicit evidence for the vault implementation and its connected contracts rather than treating any company profile as evidence that a particular release has passed review.
Finally, identify whether the integration is synchronous at all. Under ERC-7540 asynchronous vault semantics, the preview methods for an asynchronous direction must revert. An adapter that treats every such revert as a broken ERC-4626 implementation can reject intended behavior or construct an invalid fallback quote. Review the request and claim lifecycle before reusing the synchronous matrix.
Frequently asked questions
Can a share price denominated in another asset be compared directly with this experiment?
No. The model uses one underlying asset and integer units of that asset. A price denominated in another currency adds a conversion path whose decimals and update assumptions need independent review.
Does an audit of the underlying strategy automatically cover a new vault wrapper?
No. Check whether the reviewed scope includes the wrapper and the exact integration version. New conversion overrides or authorization paths need their own evidence even when the strategy dependency already has a report.
Should a multisig batch use the vault caller as the final receiver?
Only if that is the intended ownership destination. Review the receiver and owner fields across the batch so that acquired shares and redeemed assets reach the intended account. An authorized transaction can still name the wrong recipient.