IndexTreasury
contracts/src/index/IndexTreasury.sol — the per-index backing vault and redemption backstop. One is deployed per launch. It holds three things the IndexFeeHook routes into it on every swap:
- USDG reserve (the
rfvShareBpsshare), - basket stocks (the remainder that buys RWAs), and
- index-token bond inventory (the
buybackShareBpsin-kind share).
The backing is the reserve + the basket stocks. The bond inventory is the index's own token and is deliberately not part of the backing (self-referential) — it only seeds bond markets.
Redemption (oracle-free, multi-asset)
function redeem(uint256 amount)
external
returns (uint256 reserveOut, address[] memory stocks, uint256[] memory stockOuts);
Burning amount index tokens (approve the treasury first) pays a pro-rata slice of every backing asset — USDG and each basket stock — each less the fixed 5% redemption spread (REDEMPTION_SPREAD_BPS) that stays in the vault.
Key properties:
- Oracle-free. Payouts are pure pro-rata arithmetic over the treasury's raw balances — no price feed is consulted, so a stale/broken oracle can never break or be gamed against the exit path.
- Per-asset isolation. Each payout leg runs through a self-call
payoutundertry/catch. A stock that reverts on transfer (paused / blocklisted / frozen RWA) is skipped — its share stays as backing and the redeemer still receives every other asset. No all-or-nothing brick.stockOuts[i] == 0means that leg was skipped. - Strictly floor-raising. The spread stays behind and burning shrinks supply, so every redemption weakly raises backing-per-token for the holders who remain.
- CEI-safe. All pro-rata amounts are computed on the pre-burn supply/balances; the burn (effect) precedes the payouts (interactions).
Quote helpers: quoteReserveOut(amount) and quoteStockOuts(amount) return exactly what a redemption would pay, net of the spread.
Backing valuation (display + bonds)
function navUsdg() external view returns (uint256); // tolerant, display
function backingPerTokenUsdg() external view returns (uint256); // tolerant, display
function navUsdgGuarded() external view returns (uint256); // fail-closed, for bonds
function backingPerTokenUsdgGuarded() external view returns (uint256); // fail-closed, for bonds
Two flavours of the same USDG-denominated NAV (USDG balance 1:1 + Σ stock × Chainlink price, decimal-normalized):
- Tolerant (
…Usdg) — for display. A stock with no feed or a non-positive price is valued at 0 rather than reverting the whole view. - Guarded (
…Guarded) — for bond pricing. Fails closed: reverts on a down/recovering L2 sequencer (SequencerDown), a held backing stock with no feed (NoPriceFeed), or a stale/non-positive price (StalePrice). This guarantees the bond backing floor can never be under-stated by a stale-low or missing feed (which would let a bonder buy inventory below true backing). Stock feeds use a longer staleness window (STOCK_ORACLE_MAX_AGE= 3 days) because equity feeds only push during market hours.
Redemption never touches either — only the bond depository reads the guarded path.
Bond inventory
function bondInventory() external view returns (uint256); // = balanceOf(index)
function releaseBondInventory(uint256 amount) external; // depository-only
function setBondDepository(address) external; // factory-only, one-shot
The bond inventory is the index tokens the buyback fee share accumulated. Only the index's BondDepository — wired once by the factory — may pull it, and only into bond escrow at bond time.
Rescue (can never touch backing)
recover(token, to, amount) (guardian or owner) can sweep mis-sent tokens only. It reverts for the reserve asset, any basket stock, and the index token itself — so it can never rug the redemption floor or drain the bond inventory.