Skip to main content

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 rfvShareBps share),
  • basket stocks (the remainder that buys RWAs), and
  • index-token bond inventory (the buybackShareBps in-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 payout under try/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] == 0 means 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.