Mirror vaults
ImplementedThe contract that holds the assets: what it can do, and the things it deliberately cannot.
The contract that holds the assets, and the things it deliberately cannot do.
#Topology
flowchart TB F[MirrorVaultFactory] -->|deploys| V[MirrorVault] V --> SR[SourceRegistry<br/>which address to observe] V --> AR[AssetRiskRegistry<br/>what may be traded] V --> PR[PriceRouter<br/>conservative valuation] V --> FC[FeeController<br/>policy only, no custody] V --> ER[ExecutionRouter] ER --> AD[IExecutionAdapter<br/>allowlisted] F -.->|isVault| ER
One vault per source version. The registries can tell a vault no; none of them can tell it to move an asset somewhere.
#What it owns
The vault is the only contract in the system with custody. It is an ERC-20 whose shares are a pro-rata claim on everything it holds.
| Function | Purpose |
|---|---|
deposit | Mint shares against NAV |
redeemInKind | Burn shares for a pro-rata basket — no pause check, no price feed |
requestBaseRedemption | Escrow shares for epoch settlement in WETH |
claimRedemption | Collect a settled base-asset redemption |
sync(token) | Evaluate one asset and act, or record why not |
totalAssets | Conservative NAV, or revert |
divergence(token) | The accepted snapshot and the live source balance |
#What it deliberately cannot do
- No arbitrary calldata. The order struct produced for an adapter has no calldata field. There is nowhere to express an arbitrary call.
- No arbitrary recipient. The order has no recipient field; the recipient is forced to the calling vault.
- No upgrade path. There is no proxy, no
delegatecallto a mutable implementation, and no admin function that changes trading behaviour. - No discretionary trade. A keeper supplies a token address and nothing else.
- No pausable exit.
redeemInKindhas no pause check.
Security. Approvals are exact and revoked in the same transaction, with the residual balance asserted to be zero afterwards. An adapter never holds a standing allowance.
#Immutable references
baseAsset, sourceRegistry, assetRegistry, priceRouter, executionRouter, feeController, sourceVersionId and vaultVersion are all immutable. They are set at construction and cannot be repointed — so no role can swap the price router for a friendlier one after depositors have committed.
#Valuation
totalAssets() sums the base-asset balance (less anything reserved for pending claims) plus the base-asset value of every tracked position. If a position cannot be priced, it reverts rather than omitting the position:
(bool ok, uint256 price,) = priceRouter.tryPriceInBase(token);
if (!ok) revert PriceUnavailable(token);
nav += MirrorMath.valueInBase(held, price, _decimalsOf(token));A NAV that silently omitted an unpriceable position would be too low, so a deposit against it would mint too many shares. Reverting means deposits fail; in-kind redemption, which never calls this, keeps working.