Mirror math
ImplementedHow a source’s position change becomes a vault-sized trade, and every cap applied on the way.
#Proportional, not absolute
A vault copies the proportion of a trade, not its size. If a source moves 10% of its eligible portfolio into an asset, the vault aims to move 10% of its own NAV.
desiredSpend = vaultNav × (sourceDelta × price / priorEligibleSourceNav)#The denominator is the source's PRIOR portfolio
priorEligibleSourceNav is the source's eligible portfolio value before the trade being mirrored — which means adding back the value of what it just spent.
Reading the source's live base-asset balance after a buy undercounts the denominator, because that balance is already short by the amount spent. A smaller denominator means a larger proportion, and every mirrored trade is oversized. An earlier version of this system had exactly that bug and oversized by about 11%.
#Only approved assets count
The denominator includes only assets on the approved list.
This is not a detail. If unapproved holdings counted, anyone could airdrop a source a nominally valuable token, inflate the denominator, and shrink every trade that vault makes from then on. Excluding them removes that lever entirely.
The same holdings are also never bought — they are recorded as visible divergence instead.
#The caps, in order
flowchart TB
D["desiredSpend<br/>(proportional target)"] --> E["min with exposure headroom<br/>position concentration cap"]
E --> S["min with spendable base asset<br/>NAV minus cash buffer"]
S --> C["cappedSpend"]
C --> L{"Above dust<br/>threshold?"}
L -- no --> SK1["Skip: BelowDustThreshold"]
L -- yes --> Q["Quote the swap"]
Q --> LI{"Pool liquidity<br/>above floor?"}
LI -- no --> SK2["Skip: LiquidityTooLow"]
LI -- yes --> IM{"Price impact within<br/>ceiling vs oracle?"}
IM -- no --> SK3["Skip: EntryImpactTooHigh"]
IM -- yes --> SL{"Slippage within<br/>floor?"}
SL -- no --> SK4["Skip: SlippageExceeded"]
SL -- yes --> X["Execute, then accept<br/>the new snapshot"]#Divergence is measured against the desired size
Divergence must be computed from the immutable desired spend, not from a value that has already been reduced by a cap:
divergence = desiredSpend − cappedSpendAn early implementation mutated targetSpend in place as each cap applied, so whichever cap bound last reported a divergence of zero — the system silently under-reported exactly the cases a depositor most needs to see. The desired figure is now immutable and the capped figure derived from it.
#Valuation
valueInBase(amount, price, decimals) = amount × price / 10^decimalsInteger arithmetic throughout. mulDiv floors; Solidity 0.8 reverts on overflow, so the intermediate product needs no explicit guard.
#Cash buffer
spendable = baseAssetBalance − ceil(vaultNav × minCashBufferBps / 10000)The reserve rounds up, so it is never under-provisioned by a wei. It exists so that in-kind redemption stays cheap even when the vault is mostly in positions.
#Block numbers are not clocks
Robinhood Chain is an Arbitrum Orbit chain, where block.number is an estimate of the L1 block, not a local counter. Every piece of elapsed-time logic in this protocol — pending expiry, epoch timing, staleness — uses block.timestamp.