Section 10 of 18
Vault: creditAvailable and debtOutstanding
What You Are Building
Two view functions that drive the vault's dynamic capital allocation. creditAvailable(strategy) answers "how much new underlying can this strategy draw on its next harvest?" debtOutstanding(strategy) answers "how much of this strategy's current debt should be returned on its next harvest?"
These are pure-view functions called by the strategy's harvest() (section 15). The strategy reads them, decides how much to send back and how much new capital to deploy, then calls vault.report() (section 11) to settle.
Why Multi-Strategy Allocation Needs These
A single-strategy vault is trivial: all idle goes to the one strategy, all strategy returns come back to the one slot. With multiple strategies, the vault needs a fair allocation policy that:
- Respects each strategy's
debtRatio(the share of total assets it should hold). - Respects the vault's overall
debtRatiocap (across all strategies, summed). - Doesn't drain idle below zero.
- Stays within per-harvest bounds (so a single harvest doesn't dump all idle into one strategy).
The math below is how that policy is encoded.
creditAvailable: Four Constraints
function creditAvailable(address strategy) public view returns (uint256) {
if (emergencyShutdown) return 0;
StrategyParams memory params = strategies[strategy];
if (params.activation == 0) return 0;
uint256 vault_TotalAssets = _totalAssets();
uint256 vault_DebtLimit = (debtRatio * vault_TotalAssets) / MAX_BPS;
uint256 strategy_DebtLimit = (params.debtRatio * vault_TotalAssets) / MAX_BPS;
if (strategy_DebtLimit <= params.totalDebt || vault_DebtLimit <= totalDebt) {
return 0;
}
uint256 available = strategy_DebtLimit - params.totalDebt;
uint256 vaultAvailable = vault_DebtLimit - totalDebt;
if (vaultAvailable < available) available = vaultAvailable;
if (totalIdle < available) available = totalIdle;
if (params.maxDebtPerHarvest < available) available = params.maxDebtPerHarvest;
if (available < params.minDebtPerHarvest) return 0;
return available;
}
Each if enforces one constraint. The binding (smallest) constraint wins.
- Strategy headroom: how far below its target debt is the strategy?
strategy_DebtLimit - params.totalDebt. - Vault headroom: how far below its total target is the vault as a whole?
vault_DebtLimit - totalDebt. (Could be 0 even if strategy has headroom, happens when other strategies are over-allocated.) - Idle balance: can't credit more than the vault is holding.
totalIdle. - Per-harvest cap:
params.maxDebtPerHarvestprevents a single big credit event.
After all four, if the result is below params.minDebtPerHarvest, return 0. The strategy waits for next harvest. (Skipping a harvest because the available credit is too small to amortize gas is a real optimization in production.)
debtOutstanding: Excess to Reclaim
function debtOutstanding(address strategy) public view returns (uint256) {
StrategyParams memory params = strategies[strategy];
if (emergencyShutdown) return params.totalDebt;
if (params.activation == 0) return 0;
uint256 strategy_DebtLimit = (params.debtRatio * _totalAssets()) / MAX_BPS;
if (params.totalDebt <= strategy_DebtLimit) return 0;
return params.totalDebt - strategy_DebtLimit;
}
Two non-trivial cases:
- Emergency shutdown: return
params.totalDebt. Governance wants every strategy to send everything back on its next harvest. The strategy will treat this as "I owe my entire debt and should liquidate to cover it." - Normal: return
params.totalDebt - strategy_DebtLimit(capped at zero). Triggered when governance reduces the strategy'sdebtRatio(the strategy is now over-allocated relative to its smaller target), or when the vault's_totalAssets()shrinks (loss elsewhere, every strategy is now over-allocated relative to a smaller pie).
A Worked Example
Vault with _totalAssets() = $10M, debtRatio = 9_000 (vault wants 90% deployed), totalDebt = $7M. Three strategies:
| Strategy | debtRatio (BPS) | totalDebt | minDebtPerHarvest | maxDebtPerHarvest |
|---|---|---|---|---|
| A | 4_000 | $4M | $0 | $1M |
| B | 3_000 | $2M | $100k | $500k |
| C | 2_000 | $1M | $0 | $2M |
Strategy A's creditAvailable:
- strategy_DebtLimit = 4000 * 10M / 10000 = $4M. totalDebt = $4M. Headroom = 0. Returns 0.
Strategy B's creditAvailable:
- strategy_DebtLimit = 3000 * 10M / 10000 = $3M. totalDebt = $2M. Strategy headroom = $1M.
- vault_DebtLimit = 9000 * 10M / 10000 = $9M. totalDebt = $7M. Vault headroom = $2M.
- min(strategy, vault) = $1M.
- totalIdle = $10M - $7M = $3M. Bounded to $1M (already smaller).
- maxDebtPerHarvest = $500k. Bounded to $500k.
- minDebtPerHarvest = $100k. $500k > $100k, returns $500k. Returns $500k.
Strategy C's creditAvailable:
- strategy_DebtLimit = 2000 * 10M / 10000 = $2M. totalDebt = $1M. Strategy headroom = $1M.
- Vault headroom (still) $2M.
- min(strategy, vault) = $1M.
- totalIdle = $3M. Bounded.
- maxDebtPerHarvest = $2M. Bounded ($1M, already smaller).
- minDebtPerHarvest = $0. Returns $1M. Returns $1M.
Strategy A's debtOutstanding:
- strategy_DebtLimit = $4M = totalDebt. Returns 0.
If governance reduces strategy A's debtRatio to 2_000:
- strategy_DebtLimit = 2000 * 10M / 10000 = $2M. totalDebt = $4M. Returns $4M - $2M = $2M outstanding.
A is now expected to return $2M on its next harvest. Section 11's report() will settle this.
What's Next
Section 11 builds report(), the function the strategy calls to acknowledge gain/loss and settle the credit/debt swap computed here.