Vaults, shares, and price
ERC-4626 gives tokenized vaults a common interface for deposits, withdrawals, and share accounting. A user deposits an ERC-20 token and receives shares representing a claim on the vault's underlying holdings. The vault might lend those assets, stake them, or put them into another yield strategy. Users can later redeem their shares and, where the vault allows it, transfer them to someone else.
In a simple proportional model, a user's shares divided by total shares, multiplied by the vault's assets, gives their claim on those assets. Actual redemption amounts also depend on the implementation, including rounding, fees, and any virtual assets and shares used in the calculation.
I called this project "Vault Price Honesty" because I wanted to examine what those exchange rates mean for the people using a vault. The name needs a little care: an unexpected price change is not enough to call the accounting wrong. A direct donation, for example, can raise the value of existing shares without harming a later depositor.
I focused on five situations:
- A tiny first deposit followed by a donation. Someone acquires a small number of shares, then transfers tokens directly into the vault. The resulting exchange rate can leave the next depositor with very few shares, or none. Whether the first depositor profits needs a separate calculation.
- Assets remaining when no shares exist. This can happen after the last redemption or because someone donates to an empty vault. How the next deposit is priced depends on the implementation. The two situations are worth testing separately.
- Direct donations counted as assets. Including donated tokens in the vault's accounting raises the value of existing shares. That can be intended behavior; the question is what follows from it.
- Preview and execution disagreeing. Previews have specific requirements under ERC-4626. A difference needs to be checked against those requirements before it is called a violation.
- Reported assets and assets held at the vault address differing. A strategy may hold assets elsewhere, so the difference can be legitimate. Stale or incorrect valuation is a separate concern.
Following the tokens
I started this project intending to build a reusable, hardened ERC-4626 base contract. Working through the cases made the scope less straightforward than I expected. Donation accounting, strategy valuation, and the treatment of assets remaining after the last redemption depend on the vault's design. Shared protections are useful, but they do not settle all of those choices.
I shifted the project toward a Foundry suite for examining that behavior. One local test made the reason particularly clear.
A user deposited 50 tokens and received zero shares. Before that deposit, an attacker had deposited one base unit of an 18-decimal token, then transferred 10,000 tokens directly into the vault. This fixture uses an OpenZeppelin-based vault with a decimals offset of zero.
The depositor's loss was clear. The attacker's position needed another calculation.
| Participant | Contributed | Redeemable | Net |
|---|---|---|---|
| Attacker | 10,000 + 1 wei | Approximately 5,025 | Approximately −4,975 |
| Depositor | 50 | 0 | −50 |
These are underlying-token amounts in a local fixture, excluding gas. The attacker's initial deposit was one base unit, not one whole token.
OpenZeppelin's virtual asset and share accounting explains the result: part of the donation cannot be recovered by the attacker. In this particular sequence, causing a depositor to lose 50 tokens costs the attacker roughly 4,975 tokens.
The depositor suffered a complete loss, but this sequence did not generate an attacker profit. It demonstrates griefing. It does not establish the economics of other donation amounts, multiple victims, or a different vault implementation.
The local inflation test also includes an OpenZeppelin-based vault with a decimals offset of three. With the same initial attacker deposit, donation, and victim deposit, the victim receives shares redeemable for approximately 45.02 tokens. That avoids the zero-share outcome, but still leaves the victim almost 10% short.
A test asserting only that the victim received shares would miss that loss. Comparing raw share counts between the two vaults would also be misleading because the offset changes share precision. The useful comparison is the amount of underlying assets those shares can recover.
Another local case starts with zero shares and 500 donated tokens already in the vault. The next user deposits 1,000 tokens and receives shares redeemable for 750. Here, too, a nonzero share balance conceals a substantial loss. This setup deliberately donates assets to an empty vault; it should not be confused with a normal redemption leaving a small rounding remainder.
Interpreting the other measurements
Direct donations require more care. In the recorded fork run, transferring underlying tokens into sUSDe increased its reported assets and share value. That observation alone does not establish depositor harm. A higher price per share can mean that each share represents more assets.
To make a stronger claim, I would need to follow a specific consequence: a depositor's round trip, a fee calculation, or an integration using that price as collateral value. The donation test does not demonstrate those outcomes.
The same caution applies to totalAssets(). Assets deployed through a strategy need not appear in the underlying token balance at the vault address. A difference between the two numbers is a starting point for examining the accounting. Establishing an error requires understanding what the vault owns and how it values those positions.
Preview checks have a more explicit reference: the ERC-4626 specification. When a preview is followed by the corresponding operation in the same transaction, under unchanged conditions, deposit and redeem must return at least their previewed amounts; mint and withdraw must require no more than theirs. The standard also requires previews to approximate the operation as closely as specified. Simple equality with convertToShares or convertToAssets is not a general substitute, since previews account for factors such as fees.
These checks need evidence from execution, including balance changes. A returned number is insufficient if the corresponding assets never arrive.
What I would use the report for
The local fixtures make particular sequences easy to inspect. Fork tests add real accounting structures and operational restrictions. Their conclusions remain limited to the paths actually exercised: the live tests cover deposit previews and redeem where possible, rather than all four operations. Empty-vault scenarios stay local for the populated vaults in the sample. An unsuccessful run leaves a coverage gap; it does not identify the cause of failure.
For a developer reviewing a result, I want the report to answer three questions: what operation ran, what changed, and which conclusion those measurements support. A standards violation, a demonstrated economic loss, and a design-dependent behavior each call for a different response.
The most useful result from this work was the small table above. It shows both the depositor's loss and the attacker's cost. Those two numbers say more about this particular attack than the zero-share result alone.
The Foundry project, tests, and reports are on GitHub. The suite is a focused investigation tool, not a comprehensive audit.

Top comments (0)