Add Kamino/Solend-style lending program example#44
Merged
Conversation
A finance/lending Anchor example implementing the core techniques of the most-used Solana lending protocols: - Per-asset reserves with a program-owned liquidity vault and a share-token mint; the share/liquidity exchange rate rises as interest accrues. - Utilization-based kinked interest-rate curve compounded through a cumulative borrow-rate index, with per-obligation scaled debt. - Per-borrower obligations: post share-token collateral, borrow against it up to a loan-to-value limit, repay, withdraw. - Oracle-priced health and close-factor-capped liquidation with a seize bonus. - Switchboard-On-Demand-shaped price feed with a set_price test writer. - Integer-only u128 math (no floats/fixed-point), rounding always in the protocol's favour; available_liquidity as source of truth defeats the empty-pool share-inflation attack. Rust + LiteSVM tests cover supply/redeem, borrow/repay, withdraw, interest accrual, liquidation, the inflation guard, stale reserve/price rejection, and rounding edges (18 tests). https://claude.ai/code/session_01RwE8f8ahP5S6SDNTsXmpj9
`anchor build`'s IDL generation compiles a generated test under the
idl-build feature, where the `#[constant]` macro mis-evaluates the 1e18
u128 literal as i32 ("literal out of range for i32"), failing CI. These
values don't need to be in the IDL, so they're plain `pub const`s now.
https://claude.ai/code/session_01RwE8f8ahP5S6SDNTsXmpj9
A Quasar (zero-copy, no_std) port of finance/lending, mirroring the shipped Quasar escrow/vault examples' fixed-size-account idiom: - Isolated single-collateral / single-borrow obligations (fixed-size accounts) rather than the Anchor version's Vec-based multi-asset obligation. Quasar does support Vec<T, N> and CtxWithRemaining, but the DeFi examples favour fixed-size positions, so this follows that idiom. - Interest accrues inline per instruction instead of via a separate refresh. - Keeps every core technique: share-token deposits, a kinked-curve cumulative interest index, oracle-priced health, and close-factor liquidation with a bonus. - Integer-only u128 math scaled by 1e18, rounding in the protocol's favour. quasar-svm tests cover supply/redeem, borrow up to the LTV limit (and rejection beyond), repay, interest accrual lifting share value, and liquidation of an unhealthy position with the healthy path rejected. https://claude.ai/code/session_01RwE8f8ahP5S6SDNTsXmpj9
- Price feed PDAs are now seeded [b"price_feed", authority, mint]: a signer can only write the feed derived from their own key, removing the first-caller-claims race on the previous per-mint feed. Reserves trust exactly the feed their market owner registered. - Lending markets are now isolation boundaries: every obligation handler (deposit/withdraw collateral, borrow, repay, liquidate, refresh) rejects reserves whose lending_market differs from the obligation's (MarketMismatch). - Liquidation reads the close factor from the repay reserve (a property of the debt) and the bonus from the collateral reserve (a property of the seized asset), and rejects repayments whose seizure would exceed posted collateral (LiquidationTooLarge) instead of silently capping, which made the liquidator pay full price for less collateral. - Withdraw health checks round the removed borrow power up at every step, so independent flooring can never let a withdraw pass that an exact recompute would reject. - Documented share_mint_supply drift from direct token-program burns (protocol-favourable), the transfer-fee mint limitation, instant config changes, and the audit expectation; moved reserve_signer_seeds from math.rs to state/reserve.rs. - New tests: cross-market reserve rejection, foreign-signer feed write rejection, over-seizing liquidation rejection (21 tests total). https://claude.ai/code/session_01RwE8f8ahP5S6SDNTsXmpj9
Applies the same fixes as the Anchor version: - Price feed PDAs seeded [b"price_feed", authority, mint]; init_reserve binds each reserve to the feed written by its market owner, removing the first-caller-claims race and the claim branch in set_price. - deposit_obligation_collateral now requires the reserve to belong to the obligation's lending market (the one handler that was missing the check; the others already enforce it via has_one or stored-key equality). - Liquidation reads the close factor from the borrow reserve (a property of the debt) and rejects repayments whose seizure would exceed posted collateral (LiquidationTooLarge) instead of silently capping. https://claude.ai/code/session_01RwE8f8ahP5S6SDNTsXmpj9
Borrowers owe the full interest, but suppliers no longer receive all of it: each accrual now keeps `reserve_factor_bps` of the freshly accrued interest in the reserve's `accumulated_protocol_fees`, and the market owner withdraws it with a new `collect_protocol_fees` handler. The fees are carved out of `total_liquidity` (utilization still uses gross liquidity), so they never lift the supplier exchange rate, and collection is capped by the vault's available liquidity. This spread between the borrow and supply rates is the protocol's revenue — the missing profit motive for the market owner. Applied to both the Anchor and Quasar versions, with tests asserting fees accrue at the configured factor and that the owner collects them, plus README and CHANGELOG updates. https://claude.ai/code/session_01RwE8f8ahP5S6SDNTsXmpj9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new
finance/lending/anchorexample: a Kamino/Solend-style borrow/lend program built with the techniques the most-used Solana lending protocols share.How it works
deposit_reserve_liquidityto mint share tokens; the share/liquidity exchange rate rises as interest accrues, soredeem_reserve_collateralreturns more than was deposited.available_liquidity(not the vault's raw balance) is the source of truth, which defeats the empty-pool share-inflation attack.min/optimal/maxborrow rate aroundoptimal_utilization) compounded through a cumulative borrow-rate index advanced byrefresh_reserve. Each borrow stores scaled debt (principal ÷ index at borrow time), so every obligation's debt grows automatically.refresh_obligationrecomputesborrowed_value,allowed_borrow_value(Σ collateral × LTV) andunhealthy_borrow_value(Σ collateral × liquidation threshold) from oracle prices.borrow_obligation_liquidity/withdraw_obligation_collateralare gated by the LTV limit;liquidate_obligationrepays an unhealthy obligation's debt (capped by the close factor) and seizes collateral plus a bonus.PriceFeedmirrors a Switchboard On-Demand pull feed (mantissa + exponent + slot); aset_pricehandler writes it directly for deterministic LiteSVM tests, with the production Switchboard mapping documented in the README. Freshness is checked in slots.u128(no floats, no fixed-point crates), ratios scaled byFIXED_POINT_SCALE(10^18), every conversion rounding in the protocol's favour.A worked directional-short example (supply USDC, borrow + sell NVDAx, pay a variable rate, buy back, repay) is in the README.
Tests
Rust + LiteSVM, run with
cargo test(Anchor'sanchor test). 18 tests across 6 files cover supply/redeem, borrow/repay, withdraw, interest accrual, liquidation with bonus and close-factor cap, the inflation guard, stale reserve/price rejection, and rounding/health-boundary edges.anchor build(orcargo build-sbf) must run first so the tests can load the compiled.so.https://claude.ai/code/session_01RwE8f8ahP5S6SDNTsXmpj9
Generated by Claude Code