Protocol Calculations#
On-chain calculations used throughout the Coalesce Finance protocol.
Constants#
WAD = 10^18 // Precision constant
BPS = 10,000 // Basis points denominator
SECONDS_PER_YEAR = 31,536,000 // 365 days
SECONDS_PER_DAY = 86,400 // 24 hours
DAYS_PER_YEAR = 365 // Used for daily rate
USDC_DECIMALS = 6 // USDC decimal places
Interest and Scale Factor#
Scale Factor Update#
Interest compounds daily. Elapsed time is split into whole days and remaining seconds:
days_elapsed = elapsed_seconds / 86400
remaining_seconds = elapsed_seconds % 86400
daily_rate_wad = annual_interest_bps × WAD / (365 × BPS)
// Step 1: Compound for whole days (exponentiation by squaring)
new_scale_factor = old_scale_factor × pow_wad(WAD + daily_rate_wad, days_elapsed) / WAD
// Step 2: Linear for remaining sub-day seconds
sub_day_delta = annual_interest_bps × remaining_seconds × WAD / (SECONDS_PER_YEAR × BPS)
new_scale_factor = new_scale_factor × (WAD + sub_day_delta) / WAD
pow_wad(base, exp) computes base^exp in WAD precision using binary exponentiation (O(log n) multiplications).
Example:
APR = 8% (800 bps)
Elapsed = 90 days exactly (7,776,000 seconds)
Old Scale = 1.0 × 10^18
daily_rate_wad = 800 × 10^18 / (365 × 10,000)
= 219,178,082,191,780
new_scale_factor = 10^18 × pow_wad(10^18 + 219,178,082,191,780, 90) / 10^18
≈ 1,019,920,553,041,990,000
≈ 1.01992 × 10^18
For any given elapsed interval, this computation is deterministic. Within a single day, transaction frequency does not change the result; across multiple days, more frequent accrual compounds more often.
Deposits and Withdrawals#
Deposit: USDC → Shares#
shares = deposit_amount × WAD / scale_factor
Example:
Deposit = 10,000 USDC (10,000,000,000 in 6-decimal)
Scale Factor = 1.02 × 10^18
shares = 10,000,000,000 × 10^18 / (1.02 × 10^18)
= 9,803,921,568,627,450,980,392
≈ 9,803.92 shares (in WAD)
Withdrawal: Shares → USDC#
usdc_value = shares × scale_factor / WAD
Example (8% APR, 1 year, daily compounding):
Shares = 9,803,921,568,627,450,980,392
Scale Factor = 1.08328 × 10^18
usdc_value = 9,803,921,568,627,450,980,392 × 1.08328 × 10^18 / 10^18
≈ 10,620,392,156,862,745,098
≈ 10,620.39 USDC
Settlement#
Settlement Factor Calculation#
available_for_lenders = vault_balance
raw_factor = available_for_lenders × WAD / total_expected_value
settlement_factor = max(1, min(WAD, raw_factor))
If total_expected_value = 0, the program sets settlement_factor = WAD.
Lender claims are senior to protocol fees. The full vault balance is used — fees are not reserved or subtracted. Fee collection is separately guarded and only permitted when the market is fully solvent (see Fees).
Where:
vault_balance= USDC in vault at settlementavailable_for_lenders= full vault balancetotal_expected_value= total shares × scale_factor / WAD
Example:
Vault Balance = 80,000 USDC
Total Shares = 100,000 × 10^18 (WAD)
Scale Factor = 1.08328 × 10^18 (8% APR, 1 year, daily compounding)
total_expected = 100,000 × 10^18 × 1.08328 × 10^18 / 10^18
= 108,328 × 10^18 USDC (in WAD)
= 108,328 USDC
raw_factor = 80,000 × 10^18 / 108,328
≈ 738,498,000,000,000,000
≈ 0.7385 (73.85%)
settlement_factor = max(1, min(WAD, raw_factor))
Re-Settlement Factor Calculation#
When ReSettle is called after additional repayments, the factor is recomputed using the HaircutState aggregate rather than subtracting from the vault balance:
new_sf = WAD × (V + O) / (R + W)
new_factor = max(1, min(WAD, new_sf))
Where:
V= current vault balanceR= normalized claim of remaining lenders (scaled_total_supply × scale_factor / WAD)W=claim_weight_sumfrom the HaircutState account (conservative weight of prior withdrawers' claims)O=claim_offset_sumfrom the HaircutState account (offset term for prior withdrawers)
new_factor must be strictly greater than the current settlement factor, or the instruction fails with SettlementNotImproved.
This formula accounts for prior withdrawers' claims through the conservative linearised aggregate in HaircutState, while keeping borrower repayments immediately visible to settlement improvement. See Settlement for details.
Haircut Claim Calculation#
When a lender who withdrew at a loss calls claim_haircut after the settlement factor improves:
claimable = haircut_owed × (current_sf - withdrawal_sf) / (WAD - withdrawal_sf)
The claim is further capped at the vault surplus above remaining-lender obligations:
remaining_obligations = scaled_total_supply × scale_factor / WAD × current_sf / WAD
available = vault_balance - remaining_obligations
payout = min(claimable, available)
Payout Calculation#
payout = shares × scale_factor × settlement_factor / WAD²
Example:
Shares = 10,000 × 10^18
Scale Factor = 1.08328 × 10^18 (8% APR, 1 year, daily compounding)
Settlement Factor = 0.75 × 10^18
payout = 10,000 × 10^18 × 1.08328 × 10^18 × 0.75 × 10^18 / (10^18)²
≈ 8,124.60 USDC
Capacity and Borrowability#
Capacity Check (On-Chain)#
The deposit cap is enforced against the market's capacity-tracking counter:
projected_total_deposited = total_deposited + deposit_amount
projected_total_deposited <= max_total_supply
total_deposited increases on Deposit and decreases by actual lender payout on Withdraw, so exited lenders reopen capacity. scaled_total_supply × scale_factor / WAD remains a useful normalized-balance metric, but it is not the on-chain cap gate.
Available to Borrow (On-Chain Check)#
The full vault balance is available for borrowing. Fees are not reserved pre-maturity.
available_to_borrow = vault_balance
Borrow enforces:
borrow_amount <= available_to_borrow
total_borrowed and total_repaid are cumulative counters. total_deposited is the protocol's mutable capacity tracker. None of these counters is used for borrowability checks; borrowability depends only on live vault_balance.
Fee Calculations#
Protocol Fee Accrual (Per Accrual Step)#
Protocol fees are accrued from each interest step using the pre-accrual scale factor:
interest_delta_wad = new_scale_factor × WAD / scale_factor - WAD
fee_delta_wad = interest_delta_wad × fee_rate_bps / BPS
fee_normalized = scaled_total_supply × scale_factor / WAD × fee_delta_wad / WAD
interest_delta_wad is derived from the ratio of the post-accrual scale factor to the pre-accrual scale factor, capturing compound growth from daily compounding (see Interest Accrual).
The fee computation uses the pre-accrual scale_factor to value the principal base, since fees represent a fraction of the interest delta for that accrual step. The fee remains an additional borrower charge and does not reduce lender interest.
Example (8% APR, 10,000 USDC, 1 year, daily compounding, 10% fee, approximate):
Interest Earned ≈ 832.78 USDC
Protocol Fee ≈ 83.28 USDC (approximate; exact value varies with accrual frequency)
Total borrower obligation ≈ 10,916.05 USDC
Borrower Total Obligation#
total_obligation = principal + gross_interest + fee
Note: the protocol fee is charged in addition to lender interest — it does not reduce what lenders receive (see Fees).
Time Calculations#
Days to Maturity#
days_remaining = (maturity_timestamp - current_timestamp) / 86400
Term Progress#
progress = (current_timestamp - start_timestamp) / (maturity_timestamp - start_timestamp)
Annualized Return#
annualized = (final / initial)^(365/days) - 1
Or linear approximation:
annualized ≈ (final / initial - 1) × (365 / days)
Precision Notes#
WAD Precision (10^18)#
- Used for: APR, scale factor, settlement factor, shares
- Prevents precision loss in fractional calculations
- All intermediate calculations maintain WAD precision
USDC Precision (10^6)#
- Used for: token amounts
- Final outputs converted from WAD to USDC precision
Overflow Prevention#
All calculations use checked arithmetic:
let result = a.checked_mul(b)?.checked_div(c)?;
Maximum safe values with u128:
- WAD × WAD: Safe (10^36 < 2^128)
- WAD × WAD × WAD: Overflow risk, requires intermediate division