Interest Rate Model
Veil uses a two-slope (kink) interest rate model — the same design used by Aave and Compound. Rates are low below the target utilization and increase steeply above it.
Borrow Rate Formula
utilization = total_borrows / total_deposits
if utilization ≤ optimal_utilization:
borrow_rate = base_rate + slope1 × (utilization / optimal_utilization)
else:
excess = utilization - optimal_utilization
borrow_rate = base_rate + slope1 + slope2 × (excess / (1 - optimal_utilization))Supply Rate Formula
supply_rate = borrow_rate × utilization × (1 - reserve_factor)The reserve factor determines the fraction of interest that goes to the protocol treasury rather than to depositors.
Default Parameters
All rates are WAD-scaled (WAD = 1e18). The table shows human-readable percentages.
| Parameter | Default Value | WAD value |
|---|---|---|
base_rate | 1% per year | 10_000_000_000_000_000 |
optimal_utilization | 80% | 800_000_000_000_000_000 |
slope1 | 4% per year | 40_000_000_000_000_000 |
slope2 | 75% per year | 750_000_000_000_000_000 |
reserve_factor | 10% | 100_000_000_000_000_000 |
At 80% utilization (kink): borrow rate = 1% + 4% = 5%
At 100% utilization: borrow rate = 1% + 4% + 75% = 80%
Index-Based Accrual
Interest is accrued lazily at the start of every state-mutating instruction. Two global indices are maintained:
borrow_index(t) = borrow_index(t₀) × (1 + borrow_rate × Δt / SECONDS_PER_YEAR)
supply_index(t) = supply_index(t₀) × (1 + supply_rate × Δt / SECONDS_PER_YEAR)Both indices start at WAD = 1e18 when the pool is initialised.
Depositor balance:
balance = shares × supply_index / WADBorrower current debt:
debt = principal × (borrow_index_current / borrow_index_at_borrow_time)This means individual positions don’t need to be touched to stay up-to-date — the global index does the work. Depositors and borrowers earn/owe the correct amount regardless of when they last interacted.
Computing Rates Off-Chain
const WAD = 1_000_000_000_000_000_000n;
function utilizationRate(borrows: bigint, deposits: bigint): bigint {
if (deposits === 0n) return 0n;
return (borrows * WAD) / deposits;
}
function borrowRate(
util: bigint,
baseRate: bigint,
optimalUtil: bigint,
slope1: bigint,
slope2: bigint,
): bigint {
if (util <= optimalUtil) {
return baseRate + (slope1 * util) / optimalUtil;
}
const excess = util - optimalUtil;
return baseRate + slope1 + (slope2 * excess) / (WAD - optimalUtil);
}
function supplyRate(borrowRate: bigint, util: bigint, reserveFactor: bigint): bigint {
return (borrowRate * util * (WAD - reserveFactor)) / WAD / WAD;
}