Pricing Functions (Internal)¶
All pricing logic is internal to LiquidityVault. These functions are not directly callable externally but underpin all share pricing and redemption calculations.
Per-Position Pricing¶
_modeledPrice¶
Computes the current modeled price of NO shares for a single position:
if p.status == SETTLING:
return adapter.currentPrice() // gap = 0 for this slot; accrual frozen
if p.entryPrice == 0:
return 0 // written off
// status == ACTIVE: linear accrual
elapsed = block.timestamp - p.startTime
duration = p.maturity - p.startTime
accrualRate = min(elapsed / duration, 1.0)
modeledPrice = p.entryPrice + (1e18 - p.entryPrice) × accrualRate
For SETTLING slots, the modeled price is the live market price — no accrual, no gap. For ACTIVE slots, the modeled price accrues linearly from entryPrice toward 1e18 ($1.00).
positionModeledValue¶
| Status | Return value |
|---|---|
ACTIVE |
_modeledPrice(position) × adapter.positionSize() / 1e18 (linear accrual) |
SETTLING |
adapter.positionValue() — market value; gap contribution collapses to zero |
WRITTEN_OFF |
0 |
EMPTY |
0 |
positionMarketValue¶
| Status | Return value |
|---|---|
ACTIVE |
adapter.positionValue() (live market price × position size) |
SETTLING |
adapter.positionValue() (same as ACTIVE) |
WRITTEN_OFF |
0 |
EMPTY |
0 |
Aggregate NAV Functions¶
aggregateModeledNav¶
aggModeledNAV = Σ positionModeledValue(i) + idleReserve // i in [0..3]
// ACTIVE: linear accrual value
// SETTLING: adapter.positionValue() — gap = 0 for this slot
// WRITTEN_OFF, EMPTY: 0
This is the investor-facing share price basis — what the vault is modeled to be worth. SETTLING slots contribute their market value, not their linear accrual, so they do not inflate the modeled NAV beyond what the market price supports.
aggregateMarketNav¶
aggMarketNAV = Σ positionMarketValue(i) + idleReserve // i in [0..3]
// ACTIVE or SETTLING: adapter.positionValue()
// WRITTEN_OFF, EMPTY: 0
This is the true mark-to-market value — what the vault is worth based on live prediction market prices.
aggregateGapBps¶
Returns the spread between modeled and market NAV as basis points. SETTLING slots contribute zero to the gap — their modeled and market values are identical by definition.
Curve Integration¶
_avgCurvePrice¶
function _avgCurvePrice(
uint256 fillBefore, // [0, 1e18] — pre-scaled by caller: redeemedToday*1e18/dailyCap
uint256 fillAfter, // [0, 1e18] — pre-scaled by caller: (redeemedToday+reqVal)*1e18/dailyCap
uint256 mdl, // aggModeledNAV (USDC, 6 dec)
uint256 mkt // aggMarketNAV (USDC, 6 dec)
) internal pure returns (uint256 curveNAV)
Return value is a total vault NAV, not a per-share price
curveNAV is the curve-weighted aggregate vault NAV (USDC, 6 dec) — a total vault valuation between aggMarketNAV and aggModeledNAV. It is not a per-share price. The only correct conversion: exitValue = shares * curveNAV / totalShares. Writing shares * curveNAV / 1e18 is wrong.
Computes the exact closed-form average curve-weighted vault NAV over the fill range [fillBefore, fillAfter]. A loop approximation introduces precision error that varies with step size and creates consensus risk if the keeper is replaced. The closed form must be used.
Closed-form derivation:
exitNAV(x) = mkt + (mdl - mkt) × (1 - x)²
avgNAV(a, b) = (1 / (b-a)) × ∫[a..b] exitNAV(x) dx
= mkt + (mdl - mkt) × [(1-a)³ - (1-b)³] / [3 × (b-a)]
Returns: curveNAV (USDC, 6 dec) — total vault valuation.
Integer arithmetic implementation (1e18 fixed-point):
function _avgCurvePrice(
uint256 fillBefore,
uint256 fillAfter,
uint256 mdl,
uint256 mkt
) internal pure returns (uint256 curveNAV) {
uint256 oneMinusA = 1e18 - fillBefore;
uint256 oneMinusB = 1e18 - fillAfter;
uint256 fillRange = fillAfter - fillBefore; // non-zero: enforced by caller
// Compute (1-a)³ and (1-b)³, dividing after each multiply
// to keep intermediates within uint256.
uint256 cubeA = oneMinusA * oneMinusA / 1e18 * oneMinusA / 1e18;
uint256 cubeB = oneMinusB * oneMinusB / 1e18 * oneMinusB / 1e18;
uint256 cubeDiff = cubeA - cubeB; // cubeA >= cubeB always
if (mdl <= mkt) return mkt; // no gap — return market NAV
uint256 gap = mdl - mkt;
// curveNAV = mkt + gap × cubeDiff / (3 × fillRange)
// Units: gap (USDC 6dec) * cubeDiff (1e18) / fillRange (1e18) = USDC 6dec.
curveNAV = mkt + gap * cubeDiff / (3 * fillRange);
}
// CALLER USAGE:
// exitValue = request.shares * curveNAV / totalShares; ✓ correct
// exitValue = request.shares * curveNAV / 1e18; ✗ wrong divisor
fillBefore/fillAfter must be pre-scaled to 1e18 by the caller
The function does not perform scaling internally. fillRange must be non-zero — the caller must ensure fillAfter > fillBefore. Division by zero is not handled inside the function.