vaultsDocumentation

Mirror vaults

Implemented

The 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.

FunctionPurpose
depositMint shares against NAV
redeemInKindBurn shares for a pro-rata basket — no pause check, no price feed
requestBaseRedemptionEscrow shares for epoch settlement in WETH
claimRedemptionCollect a settled base-asset redemption
sync(token)Evaluate one asset and act, or record why not
totalAssetsConservative 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 delegatecall to 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. redeemInKind has 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:

solidity
(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.