From bea8ab3a1484e3b32ecfa8f87cf11e38cec05d44 Mon Sep 17 00:00:00 2001 From: Mike MacCana Date: Wed, 30 Sep 2026 20:44:18 +0000 Subject: [PATCH] Rename protocol fees to program fees, and lock test names to what they check - `protocol_fees` becomes `program_fees` in the perpetual futures pool, and `accumulated_protocol_fees` becomes `accumulated_program_fees` in the lending reserve. The lending instruction `collect_protocol_fees` becomes `collect_program_fees` (and `CollectProgramFees`), in every variant, along with the tests and the Kani harness that named them. - Test names that said an order or option "locks" tokens now say where the tokens go: `place_bid_moves_quote_into_vault`, `place_ask_moves_base_into_vault`, `test_write_call_moves_underlying_into_vault`. - Comments and READMEs that called the program "the protocol" (fees, rounding in its favour, who keeps dust) now say "the program". Mentions of the Solana protocol changing slot times are unchanged, as are CHANGELOG history entries. - "source of truth" comments now say which field the program reads. Renaming the lending instruction changes its Anchor discriminator, and renaming the account fields changes their IDL names; the byte layout is unchanged. Follows quicknode/solana-book#190, which rewrites the book's wording the same way. Claude-Session: https://claude.ai/code/session_01JGEoAUjMm7Evv69k46eNcn --- finance/betting-market/README.md | 2 +- finance/betting-market/anchor-v1/README.md | 4 +-- .../betting-market/src/state/config.rs | 2 +- finance/betting-market/anchor/README.md | 4 +-- .../betting-market/src/state/config.rs | 2 +- finance/betting-market/quasar/README.md | 4 +-- finance/betting-market/quasar/src/lib.rs | 2 +- .../betting-market/quasar/src/state/config.rs | 2 +- finance/lending/anchor-v1/README.md | 26 +++++++++---------- .../anchor-v1/programs/lending/src/errors.rs | 2 +- ...otocol_fees.rs => collect_program_fees.rs} | 14 +++++----- .../instructions/admin/initialize_reserve.rs | 2 +- .../lending/src/instructions/admin/mod.rs | 4 +-- .../instructions/deposit_reserve_liquidity.rs | 2 +- .../instructions/redeem_reserve_collateral.rs | 2 +- .../anchor-v1/programs/lending/src/lib.rs | 4 +-- .../anchor-v1/programs/lending/src/math.rs | 2 +- .../programs/lending/src/state/reserve.rs | 24 ++++++++--------- .../programs/lending/tests/common/mod.rs | 12 ++++----- .../lending/tests/test_deposit_redeem.rs | 3 ++- .../programs/lending/tests/test_interest.rs | 14 +++++----- finance/lending/anchor/README.md | 26 +++++++++---------- .../anchor/programs/lending/src/errors.rs | 2 +- ...otocol_fees.rs => collect_program_fees.rs} | 14 +++++----- .../instructions/admin/initialize_reserve.rs | 2 +- .../lending/src/instructions/admin/mod.rs | 4 +-- .../instructions/deposit_reserve_liquidity.rs | 2 +- .../instructions/redeem_reserve_collateral.rs | 2 +- .../anchor/programs/lending/src/lib.rs | 4 +-- .../anchor/programs/lending/src/math.rs | 2 +- .../programs/lending/src/state/reserve.rs | 24 ++++++++--------- .../programs/lending/tests/common/mod.rs | 12 ++++----- .../lending/tests/test_deposit_redeem.rs | 3 ++- .../programs/lending/tests/test_interest.rs | 14 +++++----- finance/lending/kani-proofs/README.md | 4 +-- finance/lending/kani-proofs/src/lib.rs | 12 ++++----- finance/lending/quasar/README.md | 12 ++++----- .../lending/quasar/src/instructions/admin.rs | 18 ++++++------- .../quasar/src/instructions/position.rs | 6 ++--- .../lending/quasar/src/instructions/supply.rs | 4 +-- finance/lending/quasar/src/lib.rs | 2 +- finance/lending/quasar/src/logic.rs | 8 +++--- finance/lending/quasar/src/math.rs | 12 ++++----- finance/lending/quasar/src/state.rs | 8 +++--- finance/lending/quasar/src/tests.rs | 14 +++++----- finance/managed-fund/VIDEO_SCRIPT.md | 4 +-- finance/managed-fund/anchor-v1/PRODUCT.md | 2 +- finance/managed-fund/anchor-v1/README.md | 6 ++--- .../src/instructions/swap_asset_for_usdc.rs | 2 +- finance/managed-fund/anchor/PRODUCT.md | 2 +- finance/managed-fund/anchor/README.md | 6 ++--- .../src/instructions/swap_asset_for_usdc.rs | 2 +- finance/managed-fund/kani-proofs/src/lib.rs | 2 +- finance/managed-fund/quasar/README.md | 2 +- .../programs/options/tests/test_options.rs | 2 +- .../programs/options/tests/test_options.rs | 2 +- finance/options/quasar/src/tests.rs | 2 +- finance/order-book/anchor-v1/README.md | 12 ++++----- .../src/instructions/place_order.rs | 2 +- .../order-book/tests/test_order_book.rs | 10 +++---- finance/order-book/anchor/README.md | 12 ++++----- .../src/instructions/place_order.rs | 2 +- .../order-book/tests/test_order_book.rs | 10 +++---- finance/order-book/kani-proofs/README.md | 2 +- finance/order-book/kani-proofs/src/lib.rs | 2 +- finance/order-book/quasar/README.md | 2 +- .../quasar/src/instructions/place_order.rs | 2 +- finance/perpetual-futures/anchor-v1/README.md | 16 ++++++------ .../programs/perpetual-futures/src/errors.rs | 2 +- .../src/instructions/close_position.rs | 4 +-- .../src/instructions/collect_fees.rs | 4 +-- .../src/instructions/initialize_pool.rs | 2 +- .../src/instructions/open_position.rs | 4 +-- .../programs/perpetual-futures/src/lib.rs | 2 +- .../perpetual-futures/src/state/pool.rs | 6 ++--- .../tests/test_perpetual_futures.rs | 6 ++--- finance/perpetual-futures/anchor/README.md | 16 ++++++------ .../programs/perpetual-futures/src/errors.rs | 2 +- .../src/instructions/close_position.rs | 4 +-- .../src/instructions/collect_fees.rs | 4 +-- .../src/instructions/initialize_pool.rs | 2 +- .../src/instructions/open_position.rs | 4 +-- .../programs/perpetual-futures/src/lib.rs | 2 +- .../perpetual-futures/src/state/pool.rs | 6 ++--- .../tests/test_perpetual_futures.rs | 6 ++--- .../quasar/src/instructions/close_position.rs | 6 ++--- .../quasar/src/instructions/collect_fees.rs | 4 +-- .../src/instructions/initialize_pool.rs | 2 +- .../quasar/src/instructions/open_position.rs | 6 ++--- .../quasar/src/instructions/shared.rs | 2 +- finance/perpetual-futures/quasar/src/state.rs | 2 +- finance/token-swap/README.md | 4 +-- .../src/instructions/deposit_liquidity.rs | 2 +- .../src/instructions/swap_tokens.rs | 4 +-- .../src/instructions/withdraw_liquidity.rs | 2 +- .../src/instructions/deposit_liquidity.rs | 2 +- .../src/instructions/swap_tokens.rs | 4 +-- .../src/instructions/withdraw_liquidity.rs | 2 +- finance/token-swap/kani-proofs/src/lib.rs | 2 +- 99 files changed, 286 insertions(+), 284 deletions(-) rename finance/lending/anchor-v1/programs/lending/src/instructions/admin/{collect_protocol_fees.rs => collect_program_fees.rs} (85%) rename finance/lending/anchor/programs/lending/src/instructions/admin/{collect_protocol_fees.rs => collect_program_fees.rs} (87%) diff --git a/finance/betting-market/README.md b/finance/betting-market/README.md index 0a9cec0bd..666bbfe35 100644 --- a/finance/betting-market/README.md +++ b/finance/betting-market/README.md @@ -1,6 +1,6 @@ # Betting Market -A parimutuel (pooled) betting market. An admin creates an event, adds its possible outcomes, and opens it to bets; bettors stake a token on the outcome they expect to win until betting closes. All stakes share one pool, and when the admin settles the event, losing stakes (minus a protocol fee) are split among winners in proportion to their stake. +A parimutuel (pooled) betting market. An admin creates an event, adds its possible outcomes, and opens it to bets; bettors stake a token on the outcome they expect to win until betting closes. All stakes share one pool, and when the admin settles the event, losing stakes (minus a program fee) are split among winners in proportion to their stake. [⚓ Anchor](./anchor) diff --git a/finance/betting-market/anchor-v1/README.md b/finance/betting-market/anchor-v1/README.md index f86e17eff..9ef7b6e11 100644 --- a/finance/betting-market/anchor-v1/README.md +++ b/finance/betting-market/anchor-v1/README.md @@ -10,7 +10,7 @@ A parimutuel (pooled) betting market on Solana. An admin creates an **event**, a **outcomes**, and opens it to bets; bettors then stake a token on the outcome they think will win, until the event's betting close time. Every stake across every outcome goes into one pool. When the admin settles the event to the winning outcome, the -losing stakes - minus a protocol fee - are split among the winners in proportion to their stake. +losing stakes - minus a program fee - are split among the winners in proportion to their stake. This is the pooled model used by Solana prediction-market platforms such as Hedgehog Markets, where odds are set by the crowd's stakes rather than by an order book or a fixed-odds bookmaker. @@ -155,7 +155,7 @@ anchor test ### How does a prediction market work on Solana? -This example uses the parimutuel (pooled) model: an admin sets up an event with `initialize_event` and `add_outcome` and opens it with `open_betting`, and bettors stake tokens on an outcome with `place_bet` until betting closes. Every stake goes into one pool; after `settle_event` names the winning outcome, winners call `claim_winnings` to split the losing stakes, minus a protocol fee, in proportion to their own stake. +This example uses the parimutuel (pooled) model: an admin sets up an event with `initialize_event` and `add_outcome` and opens it with `open_betting`, and bettors stake tokens on an outcome with `place_bet` until betting closes. Every stake goes into one pool; after `settle_event` names the winning outcome, winners call `claim_winnings` to split the losing stakes, minus a program fee, in proportion to their own stake. ### How are the odds set? diff --git a/finance/betting-market/anchor-v1/programs/betting-market/src/state/config.rs b/finance/betting-market/anchor-v1/programs/betting-market/src/state/config.rs index 43891cbc1..c0ea273a6 100644 --- a/finance/betting-market/anchor-v1/programs/betting-market/src/state/config.rs +++ b/finance/betting-market/anchor-v1/programs/betting-market/src/state/config.rs @@ -9,7 +9,7 @@ pub struct Config { pub admin: Pubkey, pub token_mint: Pubkey, pub fee_recipient: Pubkey, - // Protocol fee, in basis points, that new events copy into their own + // Program fee, in basis points, that new events copy into their own // `fee_bps` at creation. Settlement charges the event's copy, so changing // this value only affects events created afterwards. pub default_fee_bps: u16, diff --git a/finance/betting-market/anchor/README.md b/finance/betting-market/anchor/README.md index fad21c7be..615e85eb5 100644 --- a/finance/betting-market/anchor/README.md +++ b/finance/betting-market/anchor/README.md @@ -10,7 +10,7 @@ A parimutuel (pooled) betting market on Solana. An admin creates an **event**, a **outcomes**, and opens it to bets; bettors then stake a token on the outcome they think will win, until the event's betting close time. Every stake across every outcome goes into one pool. When the admin settles the event to the winning outcome, the -losing stakes - minus a protocol fee - are split among the winners in proportion to their stake. +losing stakes - minus a program fee - are split among the winners in proportion to their stake. This is the pooled model used by Solana prediction-market platforms such as Hedgehog Markets, where odds are set by the crowd's stakes rather than by an order book or a fixed-odds bookmaker. @@ -155,7 +155,7 @@ anchor test ### How does a prediction market work on Solana? -This example uses the parimutuel (pooled) model: an admin sets up an event with `initialize_event` and `add_outcome` and opens it with `open_betting`, and bettors stake tokens on an outcome with `place_bet` until betting closes. Every stake goes into one pool; after `settle_event` names the winning outcome, winners call `claim_winnings` to split the losing stakes, minus a protocol fee, in proportion to their own stake. +This example uses the parimutuel (pooled) model: an admin sets up an event with `initialize_event` and `add_outcome` and opens it with `open_betting`, and bettors stake tokens on an outcome with `place_bet` until betting closes. Every stake goes into one pool; after `settle_event` names the winning outcome, winners call `claim_winnings` to split the losing stakes, minus a program fee, in proportion to their own stake. ### How are the odds set? diff --git a/finance/betting-market/anchor/programs/betting-market/src/state/config.rs b/finance/betting-market/anchor/programs/betting-market/src/state/config.rs index 470c64b65..584f0b463 100644 --- a/finance/betting-market/anchor/programs/betting-market/src/state/config.rs +++ b/finance/betting-market/anchor/programs/betting-market/src/state/config.rs @@ -9,7 +9,7 @@ pub struct Config { pub admin: Address, pub token_mint: Address, pub fee_recipient: Address, - // Protocol fee, in basis points, that new events copy into their own + // Program fee, in basis points, that new events copy into their own // `fee_bps` at creation. Settlement charges the event's copy, so changing // this value only affects events created afterwards. pub default_fee_bps: u16, diff --git a/finance/betting-market/quasar/README.md b/finance/betting-market/quasar/README.md index 0e3d459ae..2550a3b65 100644 --- a/finance/betting-market/quasar/README.md +++ b/finance/betting-market/quasar/README.md @@ -3,7 +3,7 @@ A parimutuel betting market on Solana, written with Quasar. An admin creates events (markets), adds the possible outcomes, opens them to bets, and later settles or cancels each one. Bettors stake a fixed token on the outcome they think will happen; when the event is settled, the winners split -the losing side's stakes in proportion to their own, after a protocol fee. This +the losing side's stakes in proportion to their own, after a program fee. This is the same mechanism a racetrack tote board or a prediction market runs on. This is a [Quasar](https://github.com/blueshift-gg/quasar) port of the Anchor @@ -31,7 +31,7 @@ result is known the winners divide the pool. naming the winning outcome. Bets are accepted only while `now < betting_closes_at` and settlement only once `now >= betting_closes_at`, so nobody can stake after the result could be known. - The protocol fee is charged only on the losing pool, so a winner can never + The program fee is charged only on the losing pool, so a winner can never receive less than they staked. The fee moves to the fee recipient immediately; the figures winners need are recorded on the event. - A winner calls `claim_winnings` to withdraw their stake plus their share of diff --git a/finance/betting-market/quasar/src/lib.rs b/finance/betting-market/quasar/src/lib.rs index e2b58d028..64aba874d 100644 --- a/finance/betting-market/quasar/src/lib.rs +++ b/finance/betting-market/quasar/src/lib.rs @@ -15,7 +15,7 @@ declare_id!("7LyqAeLR3mK9dfj9LqxWzfKH61VVHzuNpkgW5Y32De74"); /// Parimutuel betting market. An admin creates events, adds outcomes, opens /// them to bets, and settles or cancels them; bettors stake a fixed token on an outcome, and winners -/// share the losing pool (net of a protocol fee) pro-rata to their stake. See +/// share the losing pool (net of a program fee) pro-rata to their stake. See /// README.md for the full walkthrough. #[program] mod quasar_betting_market { diff --git a/finance/betting-market/quasar/src/state/config.rs b/finance/betting-market/quasar/src/state/config.rs index d4e915728..55b5a03b9 100644 --- a/finance/betting-market/quasar/src/state/config.rs +++ b/finance/betting-market/quasar/src/state/config.rs @@ -13,7 +13,7 @@ pub struct Config { pub admin: Address, pub token_mint: Address, pub fee_recipient: Address, - /// Protocol fee, in basis points, that new events copy into their own + /// Program fee, in basis points, that new events copy into their own /// `fee_bps` at creation. Settlement charges the event's copy, so changing /// this value only affects events created afterwards. pub default_fee_bps: u16, diff --git a/finance/lending/anchor-v1/README.md b/finance/lending/anchor-v1/README.md index 2bec03221..1aa953e33 100644 --- a/finance/lending/anchor-v1/README.md +++ b/finance/lending/anchor-v1/README.md @@ -9,7 +9,7 @@ A Kamino/Solend-style borrow/lend program on Solana: suppliers earn interest on deposits, borrowers post collateral and draw other assets against it, and liquidators keep the market solvent. It demonstrates the techniques the most-used Solana lending -protocols share: share-token deposit accounting, a utilization-based interest +programs share: share-token deposit accounting, a utilization-based interest index, oracle-priced obligation health, and close-factor-capped liquidation. ## Purpose @@ -57,7 +57,7 @@ crosses the liquidation threshold and a liquidator can close part of the positio Supplying liquidity mints share tokens; redeeming burns them. The exchange rate is `total_liquidity / total_shares`, where `total_liquidity = available_liquidity + current_debt` and `total_shares` is the share supply plus `MINIMUM_SHARES`. -`available_liquidity` (not the vault's raw token balance) is the source of truth, +The program prices shares from `available_liquidity`, not the vault's raw token balance, so a token donated directly to the vault cannot inflate the rate. That alone does not close the empty-pool inflation attack, because @@ -102,15 +102,15 @@ at the rates that applied to them. The reserve still records `last_update_slot`, for a different job: handlers that read the reserve's value require the refresh to have run in the current slot. -### Protocol fees (how the market earns) +### Program fees (how the market earns) Borrowers owe the full interest, but suppliers don't receive all of it. On each accrual the reserve keeps `config.reserve_factor_bps` of the freshly accrued -interest in `accumulated_protocol_fees`; only the remainder lifts the supplier +interest in `accumulated_program_fees`; only the remainder lifts the supplier exchange rate. Those fees are carved out of `total_liquidity`, so they never count as a supplier claim, and the market owner withdraws them with -**`collect_protocol_fees`** (paid out of the reserve's available liquidity). -This spread between the borrow rate and the supply rate is the protocol's revenue. +**`collect_program_fees`** (paid out of the reserve's available liquidity). +This spread between the borrow rate and the supply rate is the program's revenue. ### Obligation health @@ -137,7 +137,7 @@ less, which would make the liquidator overpay. All arithmetic is integer-only `u128`: no floats, no fixed-point crates. Ratios (rates, the index, the exchange rate, obligation values) are scaled by -`FIXED_POINT_SCALE` (10^18). Every conversion rounds in the protocol's favour +`FIXED_POINT_SCALE` (10^18). Every conversion rounds in the program's favour (user output floored, debt ceiled), so dust cannot be extracted by repeated round-trips. @@ -168,7 +168,7 @@ also reject results whose confidence interval is too wide. Supplied liquidity sits in program-owned vault PDAs, and posted collateral sits in per-obligation vault PDAs whose authority is the obligation PDA. The market owner can update reserve risk parameters (`update_reserve_config`) and withdraw the -protocol's earned fees (`collect_protocol_fees`), but has no path to a supplier's +program's earned fees (`collect_program_fees`), but has no path to a supplier's deposits or a borrower's collateral: there is no admin escape hatch over user funds. ### Known limits @@ -176,7 +176,7 @@ deposits or a borrower's collateral: there is no admin escape hatch over user fu - **Tokens with transfer fees are not supported.** The program uses `token_interface`, so Token Extensions mints are accepted, but a transfer-fee extension would make the vault receive less than the recorded deposit and the - accounting would overstate `available_liquidity`. Production protocols + accounting would overstate `available_liquidity`. Production lending programs whitelist mints; a market owner here must only create reserves for tokens without transfer fees. - **Reserve config changes act immediately.** Lowering a reserve's @@ -188,7 +188,7 @@ deposits or a borrower's collateral: there is no admin escape hatch over user fu ### Instruction handlers Admin: `initialize_lending_market`, `initialize_reserve`, `update_reserve_config`, `set_price`, -`collect_protocol_fees`. +`collect_program_fees`. Supply side: `refresh_reserve`, `deposit_reserve_liquidity`, `redeem_reserve_collateral`. Borrow side: `initialize_obligation`, `refresh_obligation`, `deposit_obligation_collateral`, `withdraw_obligation_collateral`, @@ -218,15 +218,15 @@ move, the share-inflation guard, and rounding edges. ## FAQ -### How does a lending protocol work on Solana? +### How does a lending program work on Solana? Suppliers deposit a token with `deposit_reserve_liquidity` and receive share tokens that grow in value as borrowers pay interest. Borrowers post those shares as collateral (`deposit_obligation_collateral`) and draw a different token with `borrow_obligation_liquidity`, up to a loan-to-value limit. When a position's collateral no longer covers its debt, anyone can call `liquidate_obligation` to repay part of the debt in exchange for discounted collateral. ### How does interest accrue without looping over every account? -Through a cumulative accumulation factor: `refresh_reserve` advances a per-reserve factor along a utilization-based rate curve, and each obligation stores the index value from its last interaction. The gap between the two is the interest owed, so no per-account accrual loop is needed. This is the same technique the most-used Solana lending protocols share. +Through a cumulative accumulation factor: `refresh_reserve` advances a per-reserve factor along a utilization-based rate curve, and each obligation stores the index value from its last interaction. The gap between the two is the interest owed, so no per-account accrual loop is needed. This is the same technique the most-used Solana lending programs share. -### How are prices fed into the protocol? +### How are prices fed into the program? The admin `set_price` instruction handler stands in for an oracle feed in this example. `refresh_obligation` re-values collateral and debt at those prices before any borrow, withdraw, or liquidation is allowed, and stale reserves or prices are rejected. diff --git a/finance/lending/anchor-v1/programs/lending/src/errors.rs b/finance/lending/anchor-v1/programs/lending/src/errors.rs index c9c146719..0110d3d35 100644 --- a/finance/lending/anchor-v1/programs/lending/src/errors.rs +++ b/finance/lending/anchor-v1/programs/lending/src/errors.rs @@ -38,6 +38,6 @@ pub enum LendingError { MarketMismatch, #[msg("Repay amount would seize more collateral than the obligation holds")] LiquidationTooLarge, - #[msg("No protocol fees are available to collect")] + #[msg("No program fees are available to collect")] NothingToCollect, } diff --git a/finance/lending/anchor-v1/programs/lending/src/instructions/admin/collect_protocol_fees.rs b/finance/lending/anchor-v1/programs/lending/src/instructions/admin/collect_program_fees.rs similarity index 85% rename from finance/lending/anchor-v1/programs/lending/src/instructions/admin/collect_protocol_fees.rs rename to finance/lending/anchor-v1/programs/lending/src/instructions/admin/collect_program_fees.rs index b7f71b1cf..e63272043 100644 --- a/finance/lending/anchor-v1/programs/lending/src/instructions/admin/collect_protocol_fees.rs +++ b/finance/lending/anchor-v1/programs/lending/src/instructions/admin/collect_program_fees.rs @@ -6,23 +6,23 @@ use anchor_spl::token_interface::{ use crate::errors::LendingError; use crate::state::{reserve_signer_seeds, LendingMarket, Reserve}; -/// Withdraw the protocol fees accrued in a reserve to the market owner. This is +/// Withdraw the program fees accrued in a reserve to the market owner. This is /// how the owner earns: `reserve_factor_bps` of every interest accrual is set -/// aside in `accumulated_protocol_fees` (never credited to suppliers), and this +/// aside in `accumulated_program_fees` (never credited to suppliers), and this /// handler pays it out, capped by the liquidity actually sitting in the vault. -pub fn handle_collect_protocol_fees(context: Context) -> Result<()> { +pub fn handle_collect_program_fees(context: Context) -> Result<()> { context.accounts.reserve.require_refreshed()?; let reserve = &mut context.accounts.reserve; // Fees are a claim on liquidity; only what is currently un-borrowed can be paid // out right now. Any remainder stays owed until borrowers repay. let amount = reserve - .accumulated_protocol_fees + .accumulated_program_fees .min(reserve.available_liquidity); require!(amount > 0, LendingError::NothingToCollect); - reserve.accumulated_protocol_fees = reserve - .accumulated_protocol_fees + reserve.accumulated_program_fees = reserve + .accumulated_program_fees .checked_sub(amount) .ok_or(LendingError::MathOverflow)?; reserve.available_liquidity = reserve @@ -51,7 +51,7 @@ pub fn handle_collect_protocol_fees(context: Context) -> Re } #[derive(Accounts)] -pub struct CollectProtocolFees<'info> { +pub struct CollectProgramFees<'info> { // Identified by the reserve's `has_one = lending_market`; we only prove the // signer owns it. #[account(has_one = owner)] diff --git a/finance/lending/anchor-v1/programs/lending/src/instructions/admin/initialize_reserve.rs b/finance/lending/anchor-v1/programs/lending/src/instructions/admin/initialize_reserve.rs index e69bf8e61..56cc0c426 100644 --- a/finance/lending/anchor-v1/programs/lending/src/instructions/admin/initialize_reserve.rs +++ b/finance/lending/anchor-v1/programs/lending/src/instructions/admin/initialize_reserve.rs @@ -26,7 +26,7 @@ pub fn handle_initialize_reserve( let clock = Clock::get()?; reserve.last_update_slot = clock.slot; reserve.last_accrual_timestamp = clock.unix_timestamp; - reserve.accumulated_protocol_fees = 0; + reserve.accumulated_program_fees = 0; reserve.config = config; reserve.bump = context.bumps.reserve; Ok(()) diff --git a/finance/lending/anchor-v1/programs/lending/src/instructions/admin/mod.rs b/finance/lending/anchor-v1/programs/lending/src/instructions/admin/mod.rs index 2aa254adc..07ff6db2b 100644 --- a/finance/lending/anchor-v1/programs/lending/src/instructions/admin/mod.rs +++ b/finance/lending/anchor-v1/programs/lending/src/instructions/admin/mod.rs @@ -1,10 +1,10 @@ -pub mod collect_protocol_fees; +pub mod collect_program_fees; pub mod initialize_lending_market; pub mod initialize_reserve; pub mod set_price; pub mod update_reserve_config; -pub use collect_protocol_fees::*; +pub use collect_program_fees::*; pub use initialize_lending_market::*; pub use initialize_reserve::*; pub use set_price::*; diff --git a/finance/lending/anchor-v1/programs/lending/src/instructions/deposit_reserve_liquidity.rs b/finance/lending/anchor-v1/programs/lending/src/instructions/deposit_reserve_liquidity.rs index 5082960a9..40bae9838 100644 --- a/finance/lending/anchor-v1/programs/lending/src/instructions/deposit_reserve_liquidity.rs +++ b/finance/lending/anchor-v1/programs/lending/src/instructions/deposit_reserve_liquidity.rs @@ -11,7 +11,7 @@ use crate::state::{reserve_signer_seeds, Reserve}; /// Supply liquidity to a reserve and receive share tokens. The first deposit /// mints share tokens 1:1, less the `MINIMUM_SHARES` withheld; later deposits /// mint `liquidity_amount * total_shares / total_liquidity`, where -/// `total_shares` counts the withheld minimum, floored so the protocol keeps +/// `total_shares` counts the withheld minimum, floored so the program keeps /// any rounding dust. pub fn handle_deposit_reserve_liquidity( context: Context, diff --git a/finance/lending/anchor-v1/programs/lending/src/instructions/redeem_reserve_collateral.rs b/finance/lending/anchor-v1/programs/lending/src/instructions/redeem_reserve_collateral.rs index c1caf2423..19b54064b 100644 --- a/finance/lending/anchor-v1/programs/lending/src/instructions/redeem_reserve_collateral.rs +++ b/finance/lending/anchor-v1/programs/lending/src/instructions/redeem_reserve_collateral.rs @@ -8,7 +8,7 @@ use crate::math::mul_div_floor; use crate::state::{reserve_signer_seeds, Reserve}; /// Burn share tokens and withdraw the underlying liquidity they represent: -/// `share_amount * total_liquidity / total_shares`, floored so the protocol +/// `share_amount * total_liquidity / total_shares`, floored so the program /// keeps any rounding dust. `total_shares` counts the `MINIMUM_SHARES` withheld /// from the first deposit, as `deposit_reserve_liquidity` does, so their slice /// of the pool never leaves. Capped by the reserve's available (un-borrowed) diff --git a/finance/lending/anchor-v1/programs/lending/src/lib.rs b/finance/lending/anchor-v1/programs/lending/src/lib.rs index 4527fba1d..b7c9ce394 100644 --- a/finance/lending/anchor-v1/programs/lending/src/lib.rs +++ b/finance/lending/anchor-v1/programs/lending/src/lib.rs @@ -36,8 +36,8 @@ pub mod lending { instructions::handle_update_reserve_config(context, config) } - pub fn collect_protocol_fees(context: Context) -> Result<()> { - instructions::handle_collect_protocol_fees(context) + pub fn collect_program_fees(context: Context) -> Result<()> { + instructions::handle_collect_program_fees(context) } pub fn set_price( diff --git a/finance/lending/anchor-v1/programs/lending/src/math.rs b/finance/lending/anchor-v1/programs/lending/src/math.rs index 26dc107d4..4ce26e2d9 100644 --- a/finance/lending/anchor-v1/programs/lending/src/math.rs +++ b/finance/lending/anchor-v1/programs/lending/src/math.rs @@ -5,7 +5,7 @@ use crate::errors::LendingError; /// Which way to break ties when a division truncates. Deposits/redeems and /// collateral valuations round the user's favourable quantity DOWN; debt and -/// protocol-owed quantities round UP. The protocol never loses a base unit to +/// program-owed quantities round UP. The program never loses a base unit to /// rounding, so dust cannot be extracted by repeated round-trips. #[derive(Clone, Copy, PartialEq, Eq)] pub enum Rounding { diff --git a/finance/lending/anchor-v1/programs/lending/src/state/reserve.rs b/finance/lending/anchor-v1/programs/lending/src/state/reserve.rs index db844ac4c..5e9c7550e 100644 --- a/finance/lending/anchor-v1/programs/lending/src/state/reserve.rs +++ b/finance/lending/anchor-v1/programs/lending/src/state/reserve.rs @@ -45,7 +45,7 @@ pub struct Reserve { pub liquidity_decimals: u8, /// Base units sitting in `liquidity_vault`, available to borrow or redeem. - /// This is the source of truth for the pool size, not the vault's token + /// The program reads the pool size from this field, not the vault's token /// balance, so a raw token donation cannot move the exchange rate. pub available_liquidity: u64, @@ -77,11 +77,11 @@ pub struct Reserve { /// length is. pub last_accrual_timestamp: i64, - /// Liquidity owed to the market owner: the protocol's cut of accrued + /// Liquidity owed to the market owner: the program's cut of accrued /// interest (`config.reserve_factor_bps`). It is carved out of /// `total_liquidity` so it never inflates the share exchange rate, and the - /// owner withdraws it with `collect_protocol_fees`. - pub accumulated_protocol_fees: u64, + /// owner withdraws it with `collect_program_fees`. + pub accumulated_program_fees: u64, pub config: ReserveConfig, @@ -99,7 +99,7 @@ pub struct ReserveConfig { pub liquidation_bonus_bps: u16, /// Maximum fraction of a borrow that one liquidation may repay. pub close_factor_bps: u16, - /// Share of accrued borrow interest kept by the protocol (the rest lifts the + /// Share of accrued borrow interest kept by the program (the rest lifts the /// supplier exchange rate). This is how the market owner earns. pub reserve_factor_bps: u16, /// Utilization at which the borrow rate reaches `optimal_borrow_rate_bps`. @@ -147,7 +147,7 @@ impl ReserveConfig { } impl Reserve { - /// Live total debt owed to the pool, rounded up (protocol-favourable). + /// Live total debt owed to the pool, rounded up (program-favourable). pub fn current_borrowed_amount(&self) -> Result { let amount = mul_div_ceil( self.borrowed_principal, @@ -157,7 +157,7 @@ impl Reserve { u64::try_from(amount).map_err(|_| LendingError::MathOverflow.into()) } - /// Available liquidity plus live debt, before the protocol's fee is removed. + /// Available liquidity plus live debt, before the program's fee is removed. /// Used for the utilization ratio, which is about how much of the pool is lent /// out, independent of who owns the interest. pub fn gross_liquidity(&self) -> Result { @@ -167,10 +167,10 @@ impl Reserve { } /// The pool size the share token is a claim on: gross liquidity minus the - /// protocol fees owed to the owner, which belong to no supplier. + /// program fees owed to the owner, which belong to no supplier. pub fn total_liquidity(&self) -> Result { self.gross_liquidity()? - .checked_sub(self.accumulated_protocol_fees as u128) + .checked_sub(self.accumulated_program_fees as u128) .ok_or(LendingError::MathOverflow.into()) } @@ -269,7 +269,7 @@ impl Reserve { )?; // Borrowers owe the full interest (the factor grew for all of it); the - // protocol keeps `reserve_factor_bps` of the newly accrued interest, + // program keeps `reserve_factor_bps` of the newly accrued interest, // and the remainder lifts the supplier exchange rate. Flooring the fee // rounds the owner's cut down, in the suppliers' favour. let interest = self @@ -280,8 +280,8 @@ impl Reserve { self.config.reserve_factor_bps as u128, BPS_DENOMINATOR, )?; - self.accumulated_protocol_fees = self - .accumulated_protocol_fees + self.accumulated_program_fees = self + .accumulated_program_fees .checked_add(u64::try_from(fee).map_err(|_| LendingError::MathOverflow)?) .ok_or(LendingError::MathOverflow)?; } diff --git a/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs b/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs index ef59d250b..70d9e3d92 100644 --- a/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor-v1/programs/lending/tests/common/mod.rs @@ -2,7 +2,7 @@ //! Shared LiteSVM harness for the lending program tests. //! //! Sets up a lending market with reserves, funds users, and exposes one method -//! per protocol action. Actions that read value (deposit/redeem/borrow/withdraw/ +//! per program instruction. Actions that read value (deposit/redeem/borrow/withdraw/ //! liquidate) bundle the required `refresh_reserve` / `refresh_obligation` //! instructions into the same transaction, exactly as a real client must. @@ -782,10 +782,10 @@ impl Env { send(&mut self.svm, instructions, &[payer], &payer.pubkey()).unwrap(); } - /// Market owner collects accrued protocol fees from a reserve to their own + /// Market owner collects accrued program fees from a reserve to their own /// token account. Bundles `refresh_reserve` so fees are current. Returns the /// owner's fee-receiving token account. - pub fn collect_protocol_fees(&mut self, handle: &ReserveHandle) -> Pubkey { + pub fn collect_program_fees(&mut self, handle: &ReserveHandle) -> Pubkey { let owner = self.owner.insecure_clone(); let owner_liquidity = ata(&owner.pubkey(), &handle.mint); if self.svm.get_account(&owner_liquidity).is_none() { @@ -795,7 +795,7 @@ impl Env { let refresh = self.refresh_reserve_ix(handle); let collect = Instruction { program_id: lending::id(), - accounts: lending::accounts::CollectProtocolFees { + accounts: lending::accounts::CollectProgramFees { lending_market: self.market, owner: owner.pubkey(), reserve: handle.reserve, @@ -805,7 +805,7 @@ impl Env { token_program: TOKEN_PROGRAM_ID, } .to_account_metas(None), - data: lending::instruction::CollectProtocolFees {}.data(), + data: lending::instruction::CollectProgramFees {}.data(), }; send( &mut self.svm, @@ -835,7 +835,7 @@ impl Env { } /// A reasonable default reserve config: 75% LTV, 80% liquidation threshold, -/// 5% bonus, 50% close factor, 10% reserve factor (protocol's cut of interest), +/// 5% bonus, 50% close factor, 10% reserve factor (program's cut of interest), /// kink at 80% utilization, 2%/20%/150% APR curve. /// A tenth of a 365-day year, in seconds: long enough for interest to show. pub const TENTH_OF_A_YEAR: i64 = lending::constants::SECONDS_PER_YEAR as i64 / 10; diff --git a/finance/lending/anchor-v1/programs/lending/tests/test_deposit_redeem.rs b/finance/lending/anchor-v1/programs/lending/tests/test_deposit_redeem.rs index e92c4adc5..622d71068 100644 --- a/finance/lending/anchor-v1/programs/lending/tests/test_deposit_redeem.rs +++ b/finance/lending/anchor-v1/programs/lending/tests/test_deposit_redeem.rs @@ -50,7 +50,8 @@ fn raw_token_donation_does_not_inflate_exchange_rate() { env.supply(&first, &usdc, amount); // Attacker donates raw tokens straight into the reserve vault. available_liquidity - // is the source of truth, so this must NOT change the share exchange rate. + // is what the program prices shares from, so this must NOT change the share + // exchange rate. let owner = env.owner.insecure_clone(); mint_tokens_to_token_account( &mut env.svm, diff --git a/finance/lending/anchor-v1/programs/lending/tests/test_interest.rs b/finance/lending/anchor-v1/programs/lending/tests/test_interest.rs index 37e7fcb3b..5115bdb53 100644 --- a/finance/lending/anchor-v1/programs/lending/tests/test_interest.rs +++ b/finance/lending/anchor-v1/programs/lending/tests/test_interest.rs @@ -72,10 +72,10 @@ fn interest_accrues_on_borrows_over_time() { ); } -/// The protocol keeps `reserve_factor_bps` of accrued interest as fees the +/// The program keeps `reserve_factor_bps` of accrued interest as fees the /// market owner can withdraw, while the rest lifts the supplier exchange rate. #[test] -fn protocol_fees_accrue_and_owner_can_collect() { +fn program_fees_accrue_and_owner_can_collect() { let mut env = Env::new(); let collateral = env.add_reserve(6, dollars(1), default_config()); let borrow = env.add_reserve(6, dollars(1), default_config()); @@ -101,15 +101,15 @@ fn protocol_fees_accrue_and_owner_can_collect() { .unwrap(); // No interest has accrued yet, so no fees. - assert_eq!(env.reserve(&borrow).accumulated_protocol_fees, 0); + assert_eq!(env.reserve(&borrow).accumulated_program_fees, 0); env.warp_seconds(TENTH_OF_A_YEAR); env.refresh_reserve_only(&borrower, &borrow); // Fees accrued, and they are ~10% (the reserve factor) of total interest. let reserve = env.reserve(&borrow); - let fees = reserve.accumulated_protocol_fees; - assert!(fees > 0, "protocol fees should accrue once interest does"); + let fees = reserve.accumulated_program_fees; + assert!(fees > 0, "program fees should accrue once interest does"); let total_interest = reserve.current_borrowed_amount().unwrap() - 500_000_000; let expected_fee = total_interest / 10; // 1000 bps = 10% // Allow a 1-unit rounding tolerance from flooring. @@ -119,7 +119,7 @@ fn protocol_fees_accrue_and_owner_can_collect() { ); // Maria withdraws the fees to her own account. - let owner_account = env.collect_protocol_fees(&borrow); + let owner_account = env.collect_program_fees(&borrow); assert_eq!(env.token_balance(owner_account), fees); - assert_eq!(env.reserve(&borrow).accumulated_protocol_fees, 0); + assert_eq!(env.reserve(&borrow).accumulated_program_fees, 0); } diff --git a/finance/lending/anchor/README.md b/finance/lending/anchor/README.md index f097a89f7..9bb65e5b8 100644 --- a/finance/lending/anchor/README.md +++ b/finance/lending/anchor/README.md @@ -9,7 +9,7 @@ A Kamino/Solend-style borrow/lend program on Solana: suppliers earn interest on deposits, borrowers post collateral and draw other assets against it, and liquidators keep the market solvent. It demonstrates the techniques the most-used Solana lending -protocols share: share-token deposit accounting, a utilization-based interest +programs share: share-token deposit accounting, a utilization-based interest index, oracle-priced obligation health, and close-factor-capped liquidation. ## Purpose @@ -57,7 +57,7 @@ crosses the liquidation threshold and a liquidator can close part of the positio Supplying liquidity mints share tokens; redeeming burns them. The exchange rate is `total_liquidity / total_shares`, where `total_liquidity = available_liquidity + current_debt` and `total_shares` is the share supply plus `MINIMUM_SHARES`. -`available_liquidity` (not the vault's raw token balance) is the source of truth, +The program prices shares from `available_liquidity`, not the vault's raw token balance, so a token donated directly to the vault cannot inflate the rate. That alone does not close the empty-pool inflation attack, because @@ -102,15 +102,15 @@ at the rates that applied to them. The reserve still records `last_update_slot`, for a different job: handlers that read the reserve's value require the refresh to have run in the current slot. -### Protocol fees (how the market earns) +### Program fees (how the market earns) Borrowers owe the full interest, but suppliers don't receive all of it. On each accrual the reserve keeps `config.reserve_factor_bps` of the freshly accrued -interest in `accumulated_protocol_fees`; only the remainder lifts the supplier +interest in `accumulated_program_fees`; only the remainder lifts the supplier exchange rate. Those fees are carved out of `total_liquidity`, so they never count as a supplier claim, and the market owner withdraws them with -**`collect_protocol_fees`** (paid out of the reserve's available liquidity). -This spread between the borrow rate and the supply rate is the protocol's revenue. +**`collect_program_fees`** (paid out of the reserve's available liquidity). +This spread between the borrow rate and the supply rate is the program's revenue. ### Obligation health @@ -137,7 +137,7 @@ less, which would make the liquidator overpay. All arithmetic is integer-only `u128`: no floats, no fixed-point crates. Ratios (rates, the index, the exchange rate, obligation values) are scaled by -`FIXED_POINT_SCALE` (10^18). Every conversion rounds in the protocol's favour +`FIXED_POINT_SCALE` (10^18). Every conversion rounds in the program's favour (user output floored, debt ceiled), so dust cannot be extracted by repeated round-trips. @@ -168,7 +168,7 @@ also reject results whose confidence interval is too wide. Supplied liquidity sits in program-owned vault PDAs, and posted collateral sits in per-obligation vault PDAs whose authority is the obligation PDA. The market owner can update reserve risk parameters (`update_reserve_config`) and withdraw the -protocol's earned fees (`collect_protocol_fees`), but has no path to a supplier's +program's earned fees (`collect_program_fees`), but has no path to a supplier's deposits or a borrower's collateral: there is no admin escape hatch over user funds. ### Known limits @@ -176,7 +176,7 @@ deposits or a borrower's collateral: there is no admin escape hatch over user fu - **Tokens with transfer fees are not supported.** The program uses `token_interface`, so Token Extensions mints are accepted, but a transfer-fee extension would make the vault receive less than the recorded deposit and the - accounting would overstate `available_liquidity`. Production protocols + accounting would overstate `available_liquidity`. Production lending programs whitelist mints; a market owner here must only create reserves for tokens without transfer fees. - **Reserve config changes act immediately.** Lowering a reserve's @@ -188,7 +188,7 @@ deposits or a borrower's collateral: there is no admin escape hatch over user fu ### Instruction handlers Admin: `initialize_lending_market`, `initialize_reserve`, `update_reserve_config`, `set_price`, -`collect_protocol_fees`. +`collect_program_fees`. Supply side: `refresh_reserve`, `deposit_reserve_liquidity`, `redeem_reserve_collateral`. Borrow side: `initialize_obligation`, `refresh_obligation`, `deposit_obligation_collateral`, `withdraw_obligation_collateral`, @@ -218,15 +218,15 @@ move, the share-inflation guard, and rounding edges. ## FAQ -### How does a lending protocol work on Solana? +### How does a lending program work on Solana? Suppliers deposit a token with `deposit_reserve_liquidity` and receive share tokens that grow in value as borrowers pay interest. Borrowers post those shares as collateral (`deposit_obligation_collateral`) and draw a different token with `borrow_obligation_liquidity`, up to a loan-to-value limit. When a position's collateral no longer covers its debt, anyone can call `liquidate_obligation` to repay part of the debt in exchange for discounted collateral. ### How does interest accrue without looping over every account? -Through a cumulative accumulation factor: `refresh_reserve` advances a per-reserve factor along a utilization-based rate curve, and each obligation stores the index value from its last interaction. The gap between the two is the interest owed, so no per-account accrual loop is needed. This is the same technique the most-used Solana lending protocols share. +Through a cumulative accumulation factor: `refresh_reserve` advances a per-reserve factor along a utilization-based rate curve, and each obligation stores the index value from its last interaction. The gap between the two is the interest owed, so no per-account accrual loop is needed. This is the same technique the most-used Solana lending programs share. -### How are prices fed into the protocol? +### How are prices fed into the program? The admin `set_price` instruction handler stands in for an oracle feed in this example. `refresh_obligation` re-values collateral and debt at those prices before any borrow, withdraw, or liquidation is allowed, and stale reserves or prices are rejected. diff --git a/finance/lending/anchor/programs/lending/src/errors.rs b/finance/lending/anchor/programs/lending/src/errors.rs index c9c146719..0110d3d35 100644 --- a/finance/lending/anchor/programs/lending/src/errors.rs +++ b/finance/lending/anchor/programs/lending/src/errors.rs @@ -38,6 +38,6 @@ pub enum LendingError { MarketMismatch, #[msg("Repay amount would seize more collateral than the obligation holds")] LiquidationTooLarge, - #[msg("No protocol fees are available to collect")] + #[msg("No program fees are available to collect")] NothingToCollect, } diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_program_fees.rs similarity index 87% rename from finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs rename to finance/lending/anchor/programs/lending/src/instructions/admin/collect_program_fees.rs index ccab9bbea..ada11fcb7 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/collect_protocol_fees.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/collect_program_fees.rs @@ -6,23 +6,23 @@ use anchor_spl::token_interface::{ use crate::errors::LendingError; use crate::state::{reserve_signer_seeds, LendingMarket, Reserve}; -/// Withdraw the protocol fees accrued in a reserve to the market owner. This is +/// Withdraw the program fees accrued in a reserve to the market owner. This is /// how the owner earns: `reserve_factor_bps` of every interest accrual is set -/// aside in `accumulated_protocol_fees` (never credited to suppliers), and this +/// aside in `accumulated_program_fees` (never credited to suppliers), and this /// handler pays it out, capped by the liquidity actually sitting in the vault. -pub fn handle_collect_protocol_fees(context: &mut Context) -> Result<()> { +pub fn handle_collect_program_fees(context: &mut Context) -> Result<()> { context.accounts.reserve.require_refreshed()?; let reserve = &mut context.accounts.reserve; // Fees are a claim on liquidity; only what is currently un-borrowed can be paid // out right now. Any remainder stays owed until borrowers repay. let amount = reserve - .accumulated_protocol_fees + .accumulated_program_fees .min(reserve.available_liquidity); require!(amount > 0, LendingError::NothingToCollect); - reserve.accumulated_protocol_fees = reserve - .accumulated_protocol_fees + reserve.accumulated_program_fees = reserve + .accumulated_program_fees .checked_sub(amount) .ok_or(LendingError::MathOverflow)?; reserve.available_liquidity = reserve @@ -61,7 +61,7 @@ pub fn handle_collect_protocol_fees(context: &mut Context) } #[derive(Accounts)] -pub struct CollectProtocolFees { +pub struct CollectProgramFees { // Identified by `address = reserve.lending_market`; we only prove the // signer owns it. #[account(address = reserve.lending_market)] diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs index a559ffb28..e5ceec7de 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/initialize_reserve.rs @@ -28,7 +28,7 @@ pub fn handle_initialize_reserve( let clock = Clock::get()?; reserve.last_update_slot = clock.slot; reserve.last_accrual_timestamp = clock.unix_timestamp; - reserve.accumulated_protocol_fees = 0; + reserve.accumulated_program_fees = 0; reserve.config = config; reserve.bump = context.bumps.reserve; Ok(()) diff --git a/finance/lending/anchor/programs/lending/src/instructions/admin/mod.rs b/finance/lending/anchor/programs/lending/src/instructions/admin/mod.rs index 2aa254adc..07ff6db2b 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/admin/mod.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/admin/mod.rs @@ -1,10 +1,10 @@ -pub mod collect_protocol_fees; +pub mod collect_program_fees; pub mod initialize_lending_market; pub mod initialize_reserve; pub mod set_price; pub mod update_reserve_config; -pub use collect_protocol_fees::*; +pub use collect_program_fees::*; pub use initialize_lending_market::*; pub use initialize_reserve::*; pub use set_price::*; diff --git a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs index 84d17d7d9..8aaefb45a 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/deposit_reserve_liquidity.rs @@ -11,7 +11,7 @@ use crate::state::{reserve_signer_seeds, Reserve}; /// Supply liquidity to a reserve and receive share tokens. The first deposit /// mints share tokens 1:1, less the `MINIMUM_SHARES` withheld; later deposits /// mint `liquidity_amount * total_shares / total_liquidity`, where -/// `total_shares` counts the withheld minimum, floored so the protocol keeps +/// `total_shares` counts the withheld minimum, floored so the program keeps /// any rounding dust. pub fn handle_deposit_reserve_liquidity( context: &mut Context, diff --git a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs index d5676f1f4..5c00cdf7f 100644 --- a/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs +++ b/finance/lending/anchor/programs/lending/src/instructions/redeem_reserve_collateral.rs @@ -8,7 +8,7 @@ use crate::math::mul_div_floor; use crate::state::{reserve_signer_seeds, Reserve}; /// Burn share tokens and withdraw the underlying liquidity they represent: -/// `share_amount * total_liquidity / total_shares`, floored so the protocol +/// `share_amount * total_liquidity / total_shares`, floored so the program /// keeps any rounding dust. `total_shares` counts the `MINIMUM_SHARES` withheld /// from the first deposit, as `deposit_reserve_liquidity` does, so their slice /// of the pool never leaves. Capped by the reserve's available (un-borrowed) diff --git a/finance/lending/anchor/programs/lending/src/lib.rs b/finance/lending/anchor/programs/lending/src/lib.rs index 2662ac1ab..70d21f14e 100644 --- a/finance/lending/anchor/programs/lending/src/lib.rs +++ b/finance/lending/anchor/programs/lending/src/lib.rs @@ -37,8 +37,8 @@ pub mod lending { instructions::handle_update_reserve_config(context, config) } - pub fn collect_protocol_fees(context: &mut Context) -> Result<()> { - instructions::handle_collect_protocol_fees(context) + pub fn collect_program_fees(context: &mut Context) -> Result<()> { + instructions::handle_collect_program_fees(context) } pub fn set_price( diff --git a/finance/lending/anchor/programs/lending/src/math.rs b/finance/lending/anchor/programs/lending/src/math.rs index 26dc107d4..4ce26e2d9 100644 --- a/finance/lending/anchor/programs/lending/src/math.rs +++ b/finance/lending/anchor/programs/lending/src/math.rs @@ -5,7 +5,7 @@ use crate::errors::LendingError; /// Which way to break ties when a division truncates. Deposits/redeems and /// collateral valuations round the user's favourable quantity DOWN; debt and -/// protocol-owed quantities round UP. The protocol never loses a base unit to +/// program-owed quantities round UP. The program never loses a base unit to /// rounding, so dust cannot be extracted by repeated round-trips. #[derive(Clone, Copy, PartialEq, Eq)] pub enum Rounding { diff --git a/finance/lending/anchor/programs/lending/src/state/reserve.rs b/finance/lending/anchor/programs/lending/src/state/reserve.rs index 9a99d0890..750aac839 100644 --- a/finance/lending/anchor/programs/lending/src/state/reserve.rs +++ b/finance/lending/anchor/programs/lending/src/state/reserve.rs @@ -45,7 +45,7 @@ pub struct Reserve { pub liquidity_decimals: u8, /// Base units sitting in `liquidity_vault`, available to borrow or redeem. - /// This is the source of truth for the pool size, not the vault's token + /// The program reads the pool size from this field, not the vault's token /// balance, so a raw token donation cannot move the exchange rate. pub available_liquidity: u64, @@ -77,11 +77,11 @@ pub struct Reserve { /// length is. pub last_accrual_timestamp: i64, - /// Liquidity owed to the market owner: the protocol's cut of accrued + /// Liquidity owed to the market owner: the program's cut of accrued /// interest (`config.reserve_factor_bps`). It is carved out of /// `total_liquidity` so it never inflates the share exchange rate, and the - /// owner withdraws it with `collect_protocol_fees`. - pub accumulated_protocol_fees: u64, + /// owner withdraws it with `collect_program_fees`. + pub accumulated_program_fees: u64, pub config: ReserveConfig, @@ -101,7 +101,7 @@ pub struct ReserveConfig { pub liquidation_bonus_bps: u16, /// Maximum fraction of a borrow that one liquidation may repay. pub close_factor_bps: u16, - /// Share of accrued borrow interest kept by the protocol (the rest lifts the + /// Share of accrued borrow interest kept by the program (the rest lifts the /// supplier exchange rate). This is how the market owner earns. pub reserve_factor_bps: u16, /// Utilization at which the borrow rate reaches `optimal_borrow_rate_bps`. @@ -149,7 +149,7 @@ impl ReserveConfig { } impl Reserve { - /// Live total debt owed to the pool, rounded up (protocol-favourable). + /// Live total debt owed to the pool, rounded up (program-favourable). pub fn current_borrowed_amount(&self) -> Result { let amount = mul_div_ceil( self.borrowed_principal, @@ -159,7 +159,7 @@ impl Reserve { u64::try_from(amount).map_err(|_| LendingError::MathOverflow.into()) } - /// Available liquidity plus live debt, before the protocol's fee is removed. + /// Available liquidity plus live debt, before the program's fee is removed. /// Used for the utilization ratio, which is about how much of the pool is lent /// out, independent of who owns the interest. pub fn gross_liquidity(&self) -> Result { @@ -169,10 +169,10 @@ impl Reserve { } /// The pool size the share token is a claim on: gross liquidity minus the - /// protocol fees owed to the owner, which belong to no supplier. + /// program fees owed to the owner, which belong to no supplier. pub fn total_liquidity(&self) -> Result { self.gross_liquidity()? - .checked_sub(self.accumulated_protocol_fees as u128) + .checked_sub(self.accumulated_program_fees as u128) .ok_or(LendingError::MathOverflow.into()) } @@ -271,7 +271,7 @@ impl Reserve { )?; // Borrowers owe the full interest (the factor grew for all of it); the - // protocol keeps `reserve_factor_bps` of the newly accrued interest, + // program keeps `reserve_factor_bps` of the newly accrued interest, // and the remainder lifts the supplier exchange rate. Flooring the fee // rounds the owner's cut down, in the suppliers' favour. let interest = self @@ -282,8 +282,8 @@ impl Reserve { self.config.reserve_factor_bps as u128, BPS_DENOMINATOR, )?; - self.accumulated_protocol_fees = self - .accumulated_protocol_fees + self.accumulated_program_fees = self + .accumulated_program_fees .checked_add(u64::try_from(fee).map_err(|_| LendingError::MathOverflow)?) .ok_or(LendingError::MathOverflow)?; } diff --git a/finance/lending/anchor/programs/lending/tests/common/mod.rs b/finance/lending/anchor/programs/lending/tests/common/mod.rs index b8b05f832..144a86e83 100644 --- a/finance/lending/anchor/programs/lending/tests/common/mod.rs +++ b/finance/lending/anchor/programs/lending/tests/common/mod.rs @@ -2,7 +2,7 @@ //! Shared LiteSVM harness for the lending program tests. //! //! Sets up a lending market with reserves, funds users, and exposes one method -//! per protocol action. Actions that read value (deposit/redeem/borrow/withdraw/ +//! per program instruction. Actions that read value (deposit/redeem/borrow/withdraw/ //! liquidate) bundle the required `refresh_reserve` / `refresh_obligation` //! instructions into the same transaction, exactly as a real client must. @@ -790,10 +790,10 @@ impl Env { send(&mut self.svm, instructions, &[payer], &payer.pubkey()).unwrap(); } - /// Market owner collects accrued protocol fees from a reserve to their own + /// Market owner collects accrued program fees from a reserve to their own /// token account. Bundles `refresh_reserve` so fees are current. Returns the /// owner's fee-receiving token account. - pub fn collect_protocol_fees(&mut self, handle: &ReserveHandle) -> Address { + pub fn collect_program_fees(&mut self, handle: &ReserveHandle) -> Address { let owner = self.owner.insecure_clone(); let owner_liquidity = ata(&owner.pubkey(), &handle.mint); if self.svm.get_account(&owner_liquidity).is_none() { @@ -803,7 +803,7 @@ impl Env { let refresh = self.refresh_reserve_ix(handle); let collect = Instruction { program_id: lending::id(), - accounts: lending::accounts::CollectProtocolFees { + accounts: lending::accounts::CollectProgramFees { lending_market: self.market, owner: owner.pubkey(), reserve: handle.reserve, @@ -813,7 +813,7 @@ impl Env { token_program: TOKEN_PROGRAM_ID, } .to_account_metas(None), - data: lending::instruction::CollectProtocolFees {}.data(), + data: lending::instruction::CollectProgramFees {}.data(), }; send( &mut self.svm, @@ -843,7 +843,7 @@ impl Env { } /// A reasonable default reserve config: 75% LTV, 80% liquidation threshold, -/// 5% bonus, 50% close factor, 10% reserve factor (protocol's cut of interest), +/// 5% bonus, 50% close factor, 10% reserve factor (program's cut of interest), /// kink at 80% utilization, 2%/20%/150% APR curve. /// A tenth of a 365-day year, in seconds: long enough for interest to show. pub const TENTH_OF_A_YEAR: i64 = lending::constants::SECONDS_PER_YEAR as i64 / 10; diff --git a/finance/lending/anchor/programs/lending/tests/test_deposit_redeem.rs b/finance/lending/anchor/programs/lending/tests/test_deposit_redeem.rs index 92a11d783..63e28b0b4 100644 --- a/finance/lending/anchor/programs/lending/tests/test_deposit_redeem.rs +++ b/finance/lending/anchor/programs/lending/tests/test_deposit_redeem.rs @@ -48,7 +48,8 @@ fn raw_token_donation_does_not_inflate_exchange_rate() { env.supply(&first, &usdc, amount); // Attacker donates raw tokens straight into the reserve vault. available_liquidity - // is the source of truth, so this must NOT change the share exchange rate. + // is what the program prices shares from, so this must NOT change the share + // exchange rate. let owner = env.owner.insecure_clone(); mint_tokens_to_token_account( &mut env.svm, diff --git a/finance/lending/anchor/programs/lending/tests/test_interest.rs b/finance/lending/anchor/programs/lending/tests/test_interest.rs index 0b7814480..c80f8ace5 100644 --- a/finance/lending/anchor/programs/lending/tests/test_interest.rs +++ b/finance/lending/anchor/programs/lending/tests/test_interest.rs @@ -72,10 +72,10 @@ fn interest_accrues_on_borrows_over_time() { ); } -/// The protocol keeps `reserve_factor_bps` of accrued interest as fees the +/// The program keeps `reserve_factor_bps` of accrued interest as fees the /// market owner can withdraw, while the rest lifts the supplier exchange rate. #[test] -fn protocol_fees_accrue_and_owner_can_collect() { +fn program_fees_accrue_and_owner_can_collect() { let mut env = Env::new(); let collateral = env.add_reserve(6, dollars(1), default_config()); let borrow = env.add_reserve(6, dollars(1), default_config()); @@ -101,15 +101,15 @@ fn protocol_fees_accrue_and_owner_can_collect() { .unwrap(); // No interest has accrued yet, so no fees. - assert_eq!(env.reserve(&borrow).accumulated_protocol_fees, 0); + assert_eq!(env.reserve(&borrow).accumulated_program_fees, 0); env.warp_seconds(TENTH_OF_A_YEAR); env.refresh_reserve_only(&borrower, &borrow); // Fees accrued, and they are ~10% (the reserve factor) of total interest. let reserve = env.reserve(&borrow); - let fees = reserve.accumulated_protocol_fees; - assert!(fees > 0, "protocol fees should accrue once interest does"); + let fees = reserve.accumulated_program_fees; + assert!(fees > 0, "program fees should accrue once interest does"); let total_interest = reserve.current_borrowed_amount().unwrap() - 500_000_000; let expected_fee = total_interest / 10; // 1000 bps = 10% // Allow a 1-unit rounding tolerance from flooring. @@ -119,7 +119,7 @@ fn protocol_fees_accrue_and_owner_can_collect() { ); // Maria withdraws the fees to her own account. - let owner_account = env.collect_protocol_fees(&borrow); + let owner_account = env.collect_program_fees(&borrow); assert_eq!(env.token_balance(owner_account), fees); - assert_eq!(env.reserve(&borrow).accumulated_protocol_fees, 0); + assert_eq!(env.reserve(&borrow).accumulated_program_fees, 0); } diff --git a/finance/lending/kani-proofs/README.md b/finance/lending/kani-proofs/README.md index eac2d2af6..9f5f4186a 100644 --- a/finance/lending/kani-proofs/README.md +++ b/finance/lending/kani-proofs/README.md @@ -20,7 +20,7 @@ but everything else is pure integer arithmetic. This crate reproduces the formulas faithfully and checks their invariants: - `proof_mul_div_floor_ceil_correct`: `mul_div_floor`/`mul_div_ceil` are the true floor/ceil of `a·b/d`, differ by ≤ 1, and coincide iff the division is exact. -- `proof_rounding_is_protocol_favourable`: `ceil ≥ floor` always, debt (rounded up) is never undercounted and a supplier claim (rounded down) never overcounted, so dust can't be extracted by round-trips. +- `proof_rounding_is_program_favourable`: `ceil ≥ floor` always, debt (rounded up) is never undercounted and a supplier claim (rounded down) never overcounted, so dust can't be extracted by round-trips. - `proof_accumulation_factor_monotonic`: The borrow accumulation factor never decreases (`accrue_interest` multiplies by a factor ≥ 1), borrowers always owe ≥ principal. - `proof_utilization_in_range`: Utilization is always a valid `[0, 10000]` bps fraction (`borrowed ≤ gross`). - `proof_borrow_rate_within_bounds`: The kinked rate curve stays within `[min_rate, max_rate]` for every utilization, given the config ordering `min ≤ optimal ≤ max`. @@ -47,7 +47,7 @@ so the harness can use a small one: property is identical at any scale). - `proof_mul_div_floor_ceil_correct`: `a, b, d <= 31`, ~37s -- `proof_rounding_is_protocol_favourable`: `a, b, d <= 127`, ~29s +- `proof_rounding_is_program_favourable`: `a, b, d <= 127`, ~29s - `proof_accumulation_factor_monotonic`: `old/accrued <= 255`, `scale <= 127`, ~5s - `proof_utilization_in_range`: `<= 4095`, ~1s - `proof_borrow_rate_within_bounds`: rates `<= 255`, `full_utilization <= 32`, ~25s diff --git a/finance/lending/kani-proofs/src/lib.rs b/finance/lending/kani-proofs/src/lib.rs index 1f594469c..39a4fdccd 100644 --- a/finance/lending/kani-proofs/src/lib.rs +++ b/finance/lending/kani-proofs/src/lib.rs @@ -14,7 +14,7 @@ //! factor and bonus (`liquidate_obligation`). All of that is pure integer //! arithmetic; the token movement is delegated to SPL CPIs that Kani cannot //! symbolically execute. This crate reproduces the formulas faithfully and -//! checks the invariants the protocol's safety rests on. +//! checks the invariants the program's safety rests on. //! //! Nonlinear 128-bit arithmetic is the hard case for a bit-precise solver, so — //! as percolator does — the harnesses use bounded model checking: symbolic @@ -87,21 +87,21 @@ fn proof_mul_div_floor_ceil_correct() { } /// Directional-rounding safety, the property `math.rs`'s `Rounding` enum exists -/// to guarantee: debt/protocol quantities (rounded UP) are never *less* than the +/// to guarantee: debt/program-owed quantities (rounded UP) are never *less* than the /// same quantity rounded DOWN for the user. So a borrower's debt is never /// undercounted and a supplier's claim is never overcounted by rounding — the -/// protocol cannot be drained by repeated round-trips. +/// program cannot be drained by repeated round-trips. #[cfg(kani)] #[kani::proof] #[kani::solver(cadical)] -fn proof_rounding_is_protocol_favourable() { +fn proof_rounding_is_program_favourable() { let a: u128 = kani::any(); let b: u128 = kani::any(); let d: u128 = kani::any(); kani::assume(a <= 127 && b <= 127); kani::assume(d >= 1 && d <= 127); - let up = mul_div_ceil(a, b, d).unwrap(); // debt / protocol-owed + let up = mul_div_ceil(a, b, d).unwrap(); // debt / program-owed let down = mul_div_floor(a, b, d).unwrap(); // user-favourable assert!(up >= down); } @@ -251,7 +251,7 @@ pub fn shares_to_liquidity(shares: u128, total_liquidity: u128, supply: u128) -> } /// A deposit-then-redeem round-trip can never return more liquidity than was put -/// in. Both legs floor (in the protocol's favour), so redeeming the shares a +/// in. Both legs floor (in the program's favour), so redeeming the shares a /// deposit minted yields `<= amount` — there is no rounding round-trip that /// extracts value from the pool. This is the supplier-side analogue of the AMM's /// constant-product safety. diff --git a/finance/lending/quasar/README.md b/finance/lending/quasar/README.md index 2080d47ba..9ae8d0a5f 100644 --- a/finance/lending/quasar/README.md +++ b/finance/lending/quasar/README.md @@ -60,7 +60,7 @@ Everything else mirrors the Anchor version. liquidator overpay. - **Share tokens**: supplying mints them, redeeming burns them; the exchange rate `total_liquidity / total_shares` rises as borrowers pay interest. - `available_liquidity` (not the vault's raw balance) is the source of truth, so a + Shares are priced from `available_liquidity`, not the vault's raw balance, so a token donation can't inflate the rate. That is not enough on its own, because total liquidity also counts interest owed on borrows and a supplier can borrow from their own reserve: a lone supplier can raise the value of their one share @@ -69,13 +69,13 @@ Everything else mirrors the Anchor version. and `total_shares` (the share supply plus that minimum) is what every conversion between shares and liquidity divides by. The withheld shares belong to nobody, so the attacker's one share is 1 of 1,001. -- **Protocol fees**: the reserve keeps `reserve_factor_bps` of each interest - accrual in `accumulated_protocol_fees` (carved out of total liquidity, so it +- **Program fees**: the reserve keeps `reserve_factor_bps` of each interest + accrual in `accumulated_program_fees` (carved out of total liquidity, so it never lifts the supplier exchange rate); the market owner withdraws it with - `collect_protocol_fees`. That spread between the borrow and supply rates is how + `collect_program_fees`. That spread between the borrow and supply rates is how the owner earns. - **Integer-only math**: `u128`, scaled by `FIXED_POINT_SCALE` (10^18), every - conversion rounding in the protocol's favour. + conversion rounding in the program's favour. - **Interest on the wall clock**: a reserve's rate curve is annual, and the conversion to a per-second rate divides by `SECONDS_PER_YEAR`. Elapsed time is the Clock's `unix_timestamp` minus the reserve's `last_accrual_timestamp`, so a @@ -94,7 +94,7 @@ Everything else mirrors the Anchor version. `initialize_obligation` (5), `deposit_obligation_collateral` (6), `withdraw_obligation_collateral` (7), `borrow_obligation_liquidity` (8), `repay_obligation_liquidity` (9), `liquidate_obligation` (10), -`collect_protocol_fees` (11). +`collect_program_fees` (11). ## Setup diff --git a/finance/lending/quasar/src/instructions/admin.rs b/finance/lending/quasar/src/instructions/admin.rs index 811bfffe2..bcc62953b 100644 --- a/finance/lending/quasar/src/instructions/admin.rs +++ b/finance/lending/quasar/src/instructions/admin.rs @@ -159,7 +159,7 @@ impl InitializeReserve { price_feed: *self.price_feed.address(), available_liquidity: 0, share_mint_supply: 0, - accumulated_protocol_fees: 0, + accumulated_program_fees: 0, borrowed_principal: 0, borrow_accumulation_factor: crate::constants::FIXED_POINT_SCALE, last_update_slot: slot, @@ -221,11 +221,11 @@ impl SetPrice { } // --------------------------------------------------------------------------- -// collect_protocol_fees +// collect_program_fees // --------------------------------------------------------------------------- #[derive(Accounts)] -pub struct CollectProtocolFees { +pub struct CollectProgramFees { #[account(mut)] pub owner: Signer, #[account(has_one(owner))] @@ -245,10 +245,10 @@ pub struct CollectProtocolFees { pub token_program: Program, } -impl CollectProtocolFees { - /// Pay the reserve's accrued protocol fees to the market owner. This is how +impl CollectProgramFees { + /// Pay the reserve's accrued program fees to the market owner. This is how /// the owner earns: `reserve_factor_bps` of every interest accrual is set - /// aside in `accumulated_protocol_fees`, and this withdraws it — capped by + /// aside in `accumulated_program_fees`, and this withdraws it — capped by /// the liquidity currently sitting in the vault. #[inline(always)] pub fn run(&mut self) -> Result<(), ProgramError> { @@ -257,11 +257,11 @@ impl CollectProtocolFees { accrue(&mut reserve, slot, timestamp)?; let amount = reserve - .accumulated_protocol_fees + .accumulated_program_fees .min(reserve.available_liquidity); require!(amount > 0, LendingError::NothingToCollect); - reserve.accumulated_protocol_fees = reserve - .accumulated_protocol_fees + reserve.accumulated_program_fees = reserve + .accumulated_program_fees .checked_sub(amount) .ok_or(LendingError::MathOverflow)?; reserve.available_liquidity = reserve diff --git a/finance/lending/quasar/src/instructions/position.rs b/finance/lending/quasar/src/instructions/position.rs index fc1ec491c..6222a9c6d 100644 --- a/finance/lending/quasar/src/instructions/position.rs +++ b/finance/lending/quasar/src/instructions/position.rs @@ -193,7 +193,7 @@ impl BorrowObligationLiquidity { collateral.available_liquidity, collateral.borrowed_principal, collateral.borrow_accumulation_factor, - collateral.accumulated_protocol_fees, + collateral.accumulated_program_fees, )?; let collateral_liquidity = mul_div_floor( obligation.deposited_shares as u128, @@ -409,7 +409,7 @@ impl WithdrawObligationCollateral { collateral.available_liquidity, collateral.borrowed_principal, collateral.borrow_accumulation_factor, - collateral.accumulated_protocol_fees, + collateral.accumulated_program_fees, )?; let remaining_liquidity = mul_div_floor( remaining_shares as u128, @@ -556,7 +556,7 @@ impl LiquidateObligation { collateral.available_liquidity, collateral.borrowed_principal, collateral.borrow_accumulation_factor, - collateral.accumulated_protocol_fees, + collateral.accumulated_program_fees, )?; let collateral_liquidity = mul_div_floor( obligation.deposited_shares as u128, diff --git a/finance/lending/quasar/src/instructions/supply.rs b/finance/lending/quasar/src/instructions/supply.rs index 56548504f..4862bbf53 100644 --- a/finance/lending/quasar/src/instructions/supply.rs +++ b/finance/lending/quasar/src/instructions/supply.rs @@ -63,7 +63,7 @@ impl DepositReserveLiquidity { reserve.available_liquidity, reserve.borrowed_principal, reserve.borrow_accumulation_factor, - reserve.accumulated_protocol_fees, + reserve.accumulated_program_fees, )?; let shares = if reserve.share_mint_supply == 0 && total == 0 { // Bootstrap: shares track liquidity one-for-one, less the withheld @@ -176,7 +176,7 @@ impl RedeemReserveCollateral { reserve.available_liquidity, reserve.borrowed_principal, reserve.borrow_accumulation_factor, - reserve.accumulated_protocol_fees, + reserve.accumulated_program_fees, )?; // The withheld minimum counts as shares nobody holds, as it does in // deposit_reserve_liquidity, so its slice of the pool never leaves. diff --git a/finance/lending/quasar/src/lib.rs b/finance/lending/quasar/src/lib.rs index 2c95c64ca..cf4537781 100644 --- a/finance/lending/quasar/src/lib.rs +++ b/finance/lending/quasar/src/lib.rs @@ -141,7 +141,7 @@ mod quasar_lending { } #[instruction(discriminator = 11)] - pub fn collect_protocol_fees(ctx: Ctx) -> Result<(), ProgramError> { + pub fn collect_program_fees(ctx: Ctx) -> Result<(), ProgramError> { ctx.accounts.run() } } diff --git a/finance/lending/quasar/src/logic.rs b/finance/lending/quasar/src/logic.rs index 751eb77b7..a20ea677d 100644 --- a/finance/lending/quasar/src/logic.rs +++ b/finance/lending/quasar/src/logic.rs @@ -32,7 +32,7 @@ pub fn snapshot_reserve(reserve: &Account) -> ReserveInner { price_feed: reserve.price_feed, available_liquidity: u64::from(reserve.available_liquidity), share_mint_supply: u64::from(reserve.share_mint_supply), - accumulated_protocol_fees: u64::from(reserve.accumulated_protocol_fees), + accumulated_program_fees: u64::from(reserve.accumulated_program_fees), borrowed_principal: u128::from(reserve.borrowed_principal), borrow_accumulation_factor: u128::from(reserve.borrow_accumulation_factor), last_update_slot: u64::from(reserve.last_update_slot), @@ -101,7 +101,7 @@ pub fn accrue( reserve.optimal_borrow_rate_bps, reserve.max_borrow_rate_bps, )?; - // The protocol keeps `reserve_factor_bps` of the newly accrued interest; the + // The program keeps `reserve_factor_bps` of the newly accrued interest; the // rest lifts the supplier exchange rate. Flooring rounds the owner's cut down. let borrowed_after = current_debt( reserve.borrowed_principal, @@ -113,8 +113,8 @@ pub fn accrue( reserve.reserve_factor_bps as u128, BPS_DENOMINATOR, )?; - reserve.accumulated_protocol_fees = reserve - .accumulated_protocol_fees + reserve.accumulated_program_fees = reserve + .accumulated_program_fees .checked_add(u64::try_from(fee).map_err(|_| LendingError::MathOverflow)?) .ok_or(LendingError::MathOverflow)?; if elapsed > 0 { diff --git a/finance/lending/quasar/src/math.rs b/finance/lending/quasar/src/math.rs index 0425ea8b3..f7f4f634f 100644 --- a/finance/lending/quasar/src/math.rs +++ b/finance/lending/quasar/src/math.rs @@ -1,6 +1,6 @@ //! Integer-only arithmetic (no floats, no fixed-point crates), shared by the //! handlers. Ratios are scaled by `FIXED_POINT_SCALE`; conversions round in the -//! protocol's favour. +//! program's favour. use quasar_lang::prelude::*; @@ -104,13 +104,13 @@ pub fn value_to_amount( // --- reserve interest / share helpers (free functions over reserve fields) --- -/// Live total debt owed to the pool, rounded up (protocol-favourable). +/// Live total debt owed to the pool, rounded up (program-favourable). pub fn current_debt(borrowed_principal: u128, factor: u128) -> Result { let debt = mul_div_ceil(borrowed_principal, factor, FIXED_POINT_SCALE)?; u64::try_from(debt).map_err(|_| LendingError::MathOverflow.into()) } -/// Available liquidity plus live debt, before the protocol fee is removed. Used +/// Available liquidity plus live debt, before the program fee is removed. Used /// for the utilization ratio (about how much of the pool is lent out). pub fn total_liquidity( available: u64, @@ -122,16 +122,16 @@ pub fn total_liquidity( .ok_or(LendingError::MathOverflow.into()) } -/// What the share token is a claim on: gross liquidity minus the protocol fees +/// What the share token is a claim on: gross liquidity minus the program fees /// owed to the owner, which belong to no supplier. pub fn net_total_liquidity( available: u64, borrowed_principal: u128, factor: u128, - protocol_fees: u64, + program_fees: u64, ) -> Result { total_liquidity(available, borrowed_principal, factor)? - .checked_sub(protocol_fees as u128) + .checked_sub(program_fees as u128) .ok_or(LendingError::MathOverflow.into()) } diff --git a/finance/lending/quasar/src/state.rs b/finance/lending/quasar/src/state.rs index 70d2ad369..de782cc68 100644 --- a/finance/lending/quasar/src/state.rs +++ b/finance/lending/quasar/src/state.rs @@ -30,10 +30,10 @@ pub struct Reserve { pub price_feed: Address, pub available_liquidity: u64, pub share_mint_supply: u64, - /// Liquidity owed to the market owner: the protocol's cut of accrued + /// Liquidity owed to the market owner: the program's cut of accrued /// interest, carved out of total liquidity and withdrawn via - /// `collect_protocol_fees`. - pub accumulated_protocol_fees: u64, + /// `collect_program_fees`. + pub accumulated_program_fees: u64, pub borrowed_principal: u128, pub borrow_accumulation_factor: u128, /// The slot of the last accrual. Every handler that reads the reserve's @@ -49,7 +49,7 @@ pub struct Reserve { pub liquidation_threshold_bps: u16, pub liquidation_bonus_bps: u16, pub close_factor_bps: u16, - /// Share of accrued borrow interest kept by the protocol (how the owner earns). + /// Share of accrued borrow interest kept by the program (how the owner earns). pub reserve_factor_bps: u16, pub optimal_utilization_bps: u16, pub min_borrow_rate_bps: u16, diff --git a/finance/lending/quasar/src/tests.rs b/finance/lending/quasar/src/tests.rs index 20812e9b4..547b947a7 100644 --- a/finance/lending/quasar/src/tests.rs +++ b/finance/lending/quasar/src/tests.rs @@ -139,7 +139,7 @@ fn base_world(test: &mut Test) -> Pdas { TokenAccount::new(w.collateral_share_mint, LIQUIDATOR).at(LIQUIDATOR_COLLATERAL_SHARE), ); // The owner's opening deposits come from these; once they are made, - // `OWNER_BORROW` is empty again and receives collected protocol fees. + // `OWNER_BORROW` is empty again and receives collected program fees. test.add( TokenAccount::new(BORROW_MINT, OWNER) .at(OWNER_BORROW) @@ -613,7 +613,7 @@ mod clock_warp { token(BORROWER_BORROW, BORROW_MINT, BORROWER, 0), // The owner's opening deposits come from these; once they are // made, `OWNER_BORROW` is empty again and receives collected - // protocol fees. + // program fees. token(OWNER_BORROW, BORROW_MINT, OWNER, OPENING_DEPOSIT), token(OWNER_BORROW_SHARE, borrow_share_mint, OWNER, 0), token(OWNER_COLLATERAL, COLLATERAL_MINT, OWNER, OPENING_DEPOSIT), @@ -700,7 +700,7 @@ mod clock_warp { u64::from_le_bytes(account.data[64..72].try_into().unwrap()) } - /// The borrow reserve's liquidity, net of protocol fees, and the share + /// The borrow reserve's liquidity, net of program fees, and the share /// count every conversion divides by, as the program prices them. fn borrow_reserve_totals(&self) -> (u128, u128) { let reserve = self.reserve(self.borrow_reserve); @@ -708,7 +708,7 @@ mod clock_warp { u64::from(reserve.available_liquidity), u128::from(reserve.borrowed_principal), u128::from(reserve.borrow_accumulation_factor), - u64::from(reserve.accumulated_protocol_fees), + u64::from(reserve.accumulated_program_fees), ) .unwrap(); let shares = total_shares(u64::from(reserve.share_mint_supply)).unwrap(); @@ -1017,7 +1017,7 @@ mod clock_warp { .assert_success(); } - /// Market owner collects accrued protocol fees from the borrow reserve + /// Market owner collects accrued program fees from the borrow reserve /// into `OWNER_BORROW`. The handler accrues interest itself, so no /// separate refresh. fn collect_borrow_fees(&mut self) -> quasar_svm::ExecutionResult { @@ -1188,7 +1188,7 @@ mod clock_warp { } #[test] - fn protocol_fees_accrue_and_owner_can_collect() { + fn program_fees_accrue_and_owner_can_collect() { let mut world = World::new(); world.bootstrap_position(); world @@ -1203,7 +1203,7 @@ mod clock_warp { result.assert_success(); assert!( balance(&result, OWNER_BORROW) > 0, - "owner should collect a positive protocol fee, got {}", + "owner should collect a positive program fee, got {}", balance(&result, OWNER_BORROW) ); } diff --git a/finance/managed-fund/VIDEO_SCRIPT.md b/finance/managed-fund/VIDEO_SCRIPT.md index f6ff79e08..3fc10ff69 100644 --- a/finance/managed-fund/VIDEO_SCRIPT.md +++ b/finance/managed-fund/VIDEO_SCRIPT.md @@ -12,7 +12,7 @@ Let's build a managed fund: the onchain equivalent of a mutual fund, sometimes s By the end you will have watched an asset get approved, a fund get built, someone deposit, the manager invest and rebalance, a fee accrue, and someone redeem, and you will know which instruction handler does each one. The program controls every dollar the whole time: the manager invests the deposits but can never move them to herself, a limit we will pin down precisely. -You have seen this shape on Solana, in protocols like Symmetry and Kamino. This is the teaching-sized version. +You have seen this shape on Solana, in programs like Symmetry and Kamino. This is the teaching-sized version. Two things genuinely change once the fund is onchain: @@ -244,7 +244,7 @@ NARRATION: Alice calls `withdraw` and burns all 900 of her shares. Here is the part people miss: withdrawal is in kind and proportional. She does not get cash. She gets her exact fraction of every balance the fund holds, across the USDC vault and both asset vaults. It is the same move an ETF makes when it redeems in kind, handing back the underlying holdings instead of cash. Just like deposit, the handler insists on seeing every asset, so her slice is computed against the whole fund. -Her fraction is 900 shares out of the 1,363.5 that now exist. The handler floors each amount in the protocol's favor, so any rounding dust stays with the remaining holders. +Her fraction is 900 shares out of the 1,363.5 that now exist. The handler floors each amount in the program's favor, so any rounding dust stays with the remaining holders. ON SCREEN: diff --git a/finance/managed-fund/anchor-v1/PRODUCT.md b/finance/managed-fund/anchor-v1/PRODUCT.md index 66e553934..1be37f405 100644 --- a/finance/managed-fund/anchor-v1/PRODUCT.md +++ b/finance/managed-fund/anchor-v1/PRODUCT.md @@ -20,7 +20,7 @@ An **educational demo dApp** shipped alongside the `managed-fund` Solana program ## Positioning -A **multi-asset** fund built from single-asset vaults, transparently priced on-chain. Unlike an ERC-4626-style single-asset vault, one "fund" owns one vault per asset plus a USDC vault, and every deposit is deployed across the basket at its target weights in the same transaction — there is no idle-cash mode. Deposit pricing, slippage floors, and fees are all derived on-chain from the Pyth oracle and the fund's own parameters rather than trusted from a caller, which is the truth the interface must make visible: the numbers a user sees are the numbers the program enforces. +A **multi-asset** fund built from single-asset vaults, transparently priced on-chain. Unlike an ERC-4626-style single-asset vault, one "fund" owns one vault per asset plus a USDC vault, and every deposit is deployed across the basket at its target weights in the same transaction — there is no idle-cash mode. Deposit pricing, slippage floors, and fees are all derived on-chain from the Pyth oracle and the fund's own parameters rather than trusted from a caller, which is what the interface must make visible: the numbers a user sees are the numbers the program enforces. ## Operating Context diff --git a/finance/managed-fund/anchor-v1/README.md b/finance/managed-fund/anchor-v1/README.md index bc2c5b065..db964f09c 100644 --- a/finance/managed-fund/anchor-v1/README.md +++ b/finance/managed-fund/anchor-v1/README.md @@ -74,7 +74,7 @@ An [in-kind distribution](https://www.investopedia.com/terms/i/in-kind.asp) retu ### Participants -- **Victor**, the registry authority: curates which assets, and which official Pyth feed, are safe to hold. A protocol role, not a manager. +- **Victor**, the registry authority: curates which assets, and which official Pyth feed, are safe to hold. A role in the program, separate from the managers. - **Maria**, the fund manager: earns a 1% annual fee running a basket she has a thesis on. - **Alice**, the early depositor: wants diversified TSLAx and NVDAx exposure without managing positions. - **Bob**, the later depositor: joins the same fund after it has been running. @@ -111,7 +111,7 @@ A price move pushes the basket off target. `rebalance(sell_amount, usdc_to_inves ### Alice withdraws in kind -`withdraw(shares_to_burn, min_usdc_out)`, with each asset's `[asset_config, vault, mint, user_token_account]` as remaining accounts. Alice's shares burn and she receives her proportional slice of USDC and every asset. Amounts floor in the protocol's favour. +`withdraw(shares_to_burn, min_usdc_out)`, with each asset's `[asset_config, vault, mint, user_token_account]` as remaining accounts. Alice's shares burn and she receives her proportional slice of USDC and every asset. Amounts floor in the program's favour. --- @@ -142,7 +142,7 @@ What remains to trust: the honesty of the registered router and registry. With a ## Financial Math Implementation - Integer arithmetic only; intermediate products use `u128`; multiply before divide. -- All arithmetic uses `checked_*`. Users receive floor division; the protocol keeps the remainder. +- All arithmetic uses `checked_*`. Users receive floor division; the program keeps the remainder. - `transfer_checked` carries decimals through every token CPI. --- diff --git a/finance/managed-fund/anchor-v1/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs b/finance/managed-fund/anchor-v1/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs index 750cf3b36..b036ba2f0 100644 --- a/finance/managed-fund/anchor-v1/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs +++ b/finance/managed-fund/anchor-v1/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs @@ -73,7 +73,7 @@ pub fn handle_swap_asset_for_usdc( require!(rate > 0, RouterError::ZeroRate); require!(asset_amount_in > 0, RouterError::ZeroAmount); - // usdc_out = asset_amount_in * rate (u128 intermediate, protocol gets ceil on sell) + // usdc_out = asset_amount_in * rate (u128 intermediate, program gets ceil on sell) let usdc_out: u64 = (asset_amount_in as u128) .checked_mul(rate as u128) .ok_or(RouterError::MathOverflow)? as u64; diff --git a/finance/managed-fund/anchor/PRODUCT.md b/finance/managed-fund/anchor/PRODUCT.md index 66e553934..1be37f405 100644 --- a/finance/managed-fund/anchor/PRODUCT.md +++ b/finance/managed-fund/anchor/PRODUCT.md @@ -20,7 +20,7 @@ An **educational demo dApp** shipped alongside the `managed-fund` Solana program ## Positioning -A **multi-asset** fund built from single-asset vaults, transparently priced on-chain. Unlike an ERC-4626-style single-asset vault, one "fund" owns one vault per asset plus a USDC vault, and every deposit is deployed across the basket at its target weights in the same transaction — there is no idle-cash mode. Deposit pricing, slippage floors, and fees are all derived on-chain from the Pyth oracle and the fund's own parameters rather than trusted from a caller, which is the truth the interface must make visible: the numbers a user sees are the numbers the program enforces. +A **multi-asset** fund built from single-asset vaults, transparently priced on-chain. Unlike an ERC-4626-style single-asset vault, one "fund" owns one vault per asset plus a USDC vault, and every deposit is deployed across the basket at its target weights in the same transaction — there is no idle-cash mode. Deposit pricing, slippage floors, and fees are all derived on-chain from the Pyth oracle and the fund's own parameters rather than trusted from a caller, which is what the interface must make visible: the numbers a user sees are the numbers the program enforces. ## Operating Context diff --git a/finance/managed-fund/anchor/README.md b/finance/managed-fund/anchor/README.md index a2a714e6d..d64e1d0b3 100644 --- a/finance/managed-fund/anchor/README.md +++ b/finance/managed-fund/anchor/README.md @@ -74,7 +74,7 @@ An [in-kind distribution](https://www.investopedia.com/terms/i/in-kind.asp) retu ### Participants -- **Victor**, the registry authority: curates which assets, and which official Pyth feed, are safe to hold. A protocol role, not a manager. +- **Victor**, the registry authority: curates which assets, and which official Pyth feed, are safe to hold. A role in the program, separate from the managers. - **Maria**, the fund manager: earns a 1% annual fee running a basket she has a thesis on. - **Alice**, the early depositor: wants diversified TSLAx and NVDAx exposure without managing positions. - **Bob**, the later depositor: joins the same fund after it has been running. @@ -111,7 +111,7 @@ A price move pushes the basket off target. `rebalance(sell_amount, usdc_to_inves ### Alice withdraws in kind -`withdraw(shares_to_burn, min_usdc_out)`, with each asset's `[asset_config, vault, mint, user_token_account]` as remaining accounts. Alice's shares burn and she receives her proportional slice of USDC and every asset. Amounts floor in the protocol's favour. +`withdraw(shares_to_burn, min_usdc_out)`, with each asset's `[asset_config, vault, mint, user_token_account]` as remaining accounts. Alice's shares burn and she receives her proportional slice of USDC and every asset. Amounts floor in the program's favour. --- @@ -142,7 +142,7 @@ What remains to trust: the honesty of the registered router and registry. With a ## Financial Math Implementation - Integer arithmetic only; intermediate products use `u128`; multiply before divide. -- All arithmetic uses `checked_*`. Users receive floor division; the protocol keeps the remainder. +- All arithmetic uses `checked_*`. Users receive floor division; the program keeps the remainder. - `transfer_checked` carries decimals through every token CPI. --- diff --git a/finance/managed-fund/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs b/finance/managed-fund/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs index 8ca2896f4..5294f0043 100644 --- a/finance/managed-fund/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs +++ b/finance/managed-fund/anchor/programs/mock-swap-router/src/instructions/swap_asset_for_usdc.rs @@ -73,7 +73,7 @@ pub fn handle_swap_asset_for_usdc( require!(rate > 0, RouterError::ZeroRate); require!(asset_amount_in > 0, RouterError::ZeroAmount); - // usdc_out = asset_amount_in * rate (u128 intermediate, protocol gets ceil on sell) + // usdc_out = asset_amount_in * rate (u128 intermediate, program gets ceil on sell) let usdc_out: u64 = (asset_amount_in as u128) .checked_mul(rate as u128) .ok_or(RouterError::MathOverflow)? as u64; diff --git a/finance/managed-fund/kani-proofs/src/lib.rs b/finance/managed-fund/kani-proofs/src/lib.rs index 64631e986..638eb9e7b 100644 --- a/finance/managed-fund/kani-proofs/src/lib.rs +++ b/finance/managed-fund/kani-proofs/src/lib.rs @@ -92,7 +92,7 @@ fn proof_withdraw_within_balance() { /// In a USDC-only vault (NAV == vault USDC, no basket assets), depositing and /// immediately withdrawing the minted shares never returns more USDC than was -/// deposited. Both legs floor in the protocol's favour, so a deposit/withdraw +/// deposited. Both legs floor in the program's favour, so a deposit/withdraw /// round-trip is never profitable — there is no rounding attack that mints /// shares worth more than they cost. #[cfg(kani)] diff --git a/finance/managed-fund/quasar/README.md b/finance/managed-fund/quasar/README.md index 5a5822d4f..de9cdbd16 100644 --- a/finance/managed-fund/quasar/README.md +++ b/finance/managed-fund/quasar/README.md @@ -27,7 +27,7 @@ aggregator so the example is self-contained. ## How it works -A separate protocol authority curates a registry of assets, binding each +A separate registry authority curates a registry of assets, binding each approved mint to its official price feed. This authority is deliberately not the fund manager: it vets which real assets and feeds are safe, and the manager only chooses among them, so a manager can never list a token they mint diff --git a/finance/options/anchor-v1/programs/options/tests/test_options.rs b/finance/options/anchor-v1/programs/options/tests/test_options.rs index 878229c6c..946adbaca 100644 --- a/finance/options/anchor-v1/programs/options/tests/test_options.rs +++ b/finance/options/anchor-v1/programs/options/tests/test_options.rs @@ -505,7 +505,7 @@ fn test_market_owns_both_vaults() { /// Alice writes 5 covered calls on her 5 NVDAx. The whole 5 NVDAx moves into /// the vault at once; the option is listed for a 25 USDC premium. #[test] -fn test_write_call_locks_the_underlying() { +fn test_write_call_moves_underlying_into_vault() { let mut venue = Venue::new(); let alice = venue.person(FIVE_NVDAX, STANDARD_USDC); diff --git a/finance/options/anchor/programs/options/tests/test_options.rs b/finance/options/anchor/programs/options/tests/test_options.rs index cebd20c4a..7b08047bb 100644 --- a/finance/options/anchor/programs/options/tests/test_options.rs +++ b/finance/options/anchor/programs/options/tests/test_options.rs @@ -512,7 +512,7 @@ fn test_market_owns_both_vaults() { /// Alice writes 5 covered calls on her 5 NVDAx. The whole 5 NVDAx moves into /// the vault at once; the option is listed for a 25 USDC premium. #[test] -fn test_write_call_locks_the_underlying() { +fn test_write_call_moves_underlying_into_vault() { let mut venue = Venue::new(); let alice = venue.person(FIVE_NVDAX, STANDARD_USDC); diff --git a/finance/options/quasar/src/tests.rs b/finance/options/quasar/src/tests.rs index 827cc2781..1c26e0d0f 100644 --- a/finance/options/quasar/src/tests.rs +++ b/finance/options/quasar/src/tests.rs @@ -337,7 +337,7 @@ fn market_owns_both_vaults(test: &mut Test) { /// Alice writes 5 covered calls on her 5 NVDAx. The whole 5 NVDAx moves into /// the vault at once; the option is listed for a 25 USDC premium. #[quasar_test] -fn write_call_locks_the_underlying(test: &mut Test) { +fn write_call_moves_underlying_into_vault(test: &mut Test) { let env = setup(test); let option = write_call(test, &env); diff --git a/finance/order-book/anchor-v1/README.md b/finance/order-book/anchor-v1/README.md index 71da75efc..687d1b2f6 100644 --- a/finance/order-book/anchor-v1/README.md +++ b/finance/order-book/anchor-v1/README.md @@ -506,7 +506,7 @@ them is: 3. `place_order` (a user - as many times as they want) 4. `cancel_order` (a user - to remove a resting order) 5. `settle_funds` (a user - to collect winnings) -6. `withdraw_fees` (market authority - to collect protocol revenue) +6. `withdraw_fees` (market authority - to collect program revenue) For each, the shape is: who signs, what accounts go in, what PDAs get created, what token flows happen, what state mutates, what checks are @@ -1488,8 +1488,8 @@ test initialize_market_rejects_zero_quote_lot_size ... ok test initialize_market_rejects_zero_tick_size ... ok test initialize_market_sets_market_and_order_book ... ok test initialize_market_user_tracks_market_and_owner ... ok -test place_ask_locks_base_in_vault ... ok -test place_bid_locks_quote_in_vault ... ok +test place_ask_moves_base_into_vault ... ok +test place_bid_moves_quote_into_vault ... ok test place_order_rejects_below_min_order_size ... ok test place_order_rejects_unaligned_tick ... ok test place_order_rejects_zero_price ... ok @@ -1511,8 +1511,8 @@ test taker_partially_fills_resting_order_rest_stays_on_book ... ok - `initialize_market_sets_market_and_order_book`: PDA creation, vault setup, initial field values - `initialize_market_user_tracks_market_and_owner`: Per-user PDA derivation and zero-initialised counters -- `place_bid_locks_quote_in_vault`: Fund lock on bid -- `place_ask_locks_base_in_vault`: Fund lock on ask +- `place_bid_moves_quote_into_vault`: Fund lock on bid +- `place_ask_moves_base_into_vault`: Fund lock on ask - `settle_funds_moves_unsettled_base_to_user`: Vault → user ATA transfer via market PDA signer **Validation:** @@ -1641,7 +1641,7 @@ Openbook v2 (`src/state/slab/`). in CU cost regardless of the taker's depth. - **Market-makers as CPI users.** Formalise the `remaining_accounts` - protocol so a market-making program can call `place_order` on + layout so a market-making program can call `place_order` on behalf of its users, pre-computing the crossings offchain and rewriting the book in one transaction. diff --git a/finance/order-book/anchor-v1/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor-v1/programs/order-book/src/instructions/place_order.rs index a5a29ca38..56e7162a0 100644 --- a/finance/order-book/anchor-v1/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor-v1/programs/order-book/src/instructions/place_order.rs @@ -219,7 +219,7 @@ pub fn handle_place_order<'info>( .try_into() .map_err(|_| error!(ErrorCode::NumericalOverflow))?; - // Ceiling division: round the fee in the protocol's favour. Flooring + // Ceiling division: round the fee in the program's favour. Flooring // would leak up to 1 minor unit of quote per fill to the maker, which // an attacker could industrialise with many tiny fills. let fee_quote: u64 = (gross_quote as u128) diff --git a/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs index e9296ddf3..ae7303cba 100644 --- a/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor-v1/programs/order-book/tests/test_order_book.rs @@ -59,7 +59,7 @@ const QUOTE_DECIMALS: u8 = 6; // USDC const FEE_BASIS_POINTS: u16 = 10; // Mirror of the program's fee rounding: ceiling division so the fee rounds -// in the protocol's favour (flooring would leak dust to the maker per fill). +// in the program's favour (flooring would leak dust to the maker per fill). const fn fee_ceil(gross: u64) -> u64 { ((gross as u128 * FEE_BASIS_POINTS as u128 + 9_999) / 10_000) as u64 } @@ -590,7 +590,7 @@ fn initialize_market_user_tracks_market_and_owner() { } #[test] -fn place_bid_locks_quote_in_vault() { +fn place_bid_moves_quote_into_vault() { let mut sc = full_setup(); initialize_market_and_users(&mut sc); @@ -636,7 +636,7 @@ fn place_bid_locks_quote_in_vault() { } #[test] -fn place_ask_locks_base_in_vault() { +fn place_ask_moves_base_into_vault() { let mut sc = full_setup(); initialize_market_and_users(&mut sc); @@ -1523,7 +1523,7 @@ fn taker_partially_filled_remainder_rests_on_book() { // The taker's own Order PDA holds the true remaining-on-book quantity // (original_quantity - filled_quantity). On-book quantity isn't stored // on OrderEntry directly - see state/order_book.rs - so this is the - // source of truth both here and at runtime. + // field the program reads both here and at runtime. assert_eq!( TAKER_BID_QUANTITY - taker_filled, TAKER_BID_QUANTITY - MAKER_ASK_QUANTITY @@ -1839,7 +1839,7 @@ fn taker_bid_gets_price_improvement_from_resting_ask() { #[test] fn fee_rounds_up_when_gross_is_not_a_bps_multiple() { // Rounding regression: with fee_bps = 10, a gross of 501 quote tokens - // gives 501 * 10 / 10_000 = 0.501, which must round UP to 1 (protocol- + // gives 501 * 10 / 10_000 = 0.501, which must round UP to 1 (program- // favouring ceiling), not down to 0. A floor here would let makers // fill fee-free with many small orders. let mut sc = full_setup(); diff --git a/finance/order-book/anchor/README.md b/finance/order-book/anchor/README.md index 518689997..dbbf6de47 100644 --- a/finance/order-book/anchor/README.md +++ b/finance/order-book/anchor/README.md @@ -511,7 +511,7 @@ them is: 3. `place_order` (a user - as many times as they want) 4. `cancel_order` (a user - to remove a resting order) 5. `settle_funds` (a user - to collect winnings) -6. `withdraw_fees` (market authority - to collect protocol revenue) +6. `withdraw_fees` (market authority - to collect program revenue) For each, the shape is: who signs, what accounts go in, what PDAs get created, what token flows happen, what state mutates, what checks are @@ -1496,8 +1496,8 @@ test initialize_market_rejects_zero_quote_lot_size ... ok test initialize_market_rejects_zero_tick_size ... ok test initialize_market_sets_market_and_order_book ... ok test initialize_market_user_tracks_market_and_owner ... ok -test place_ask_locks_base_in_vault ... ok -test place_bid_locks_quote_in_vault ... ok +test place_ask_moves_base_into_vault ... ok +test place_bid_moves_quote_into_vault ... ok test place_order_rejects_below_min_order_size ... ok test place_order_rejects_unaligned_tick ... ok test place_order_rejects_zero_price ... ok @@ -1519,8 +1519,8 @@ test taker_partially_fills_resting_order_rest_stays_on_book ... ok - `initialize_market_sets_market_and_order_book`: PDA creation, vault setup, initial field values - `initialize_market_user_tracks_market_and_owner`: Per-user PDA derivation and zero-initialised counters -- `place_bid_locks_quote_in_vault`: Fund lock on bid -- `place_ask_locks_base_in_vault`: Fund lock on ask +- `place_bid_moves_quote_into_vault`: Fund lock on bid +- `place_ask_moves_base_into_vault`: Fund lock on ask - `settle_funds_moves_unsettled_base_to_user`: Vault → user ATA transfer via market PDA signer **Validation:** @@ -1649,7 +1649,7 @@ Openbook v2 (`src/state/slab/`). in CU cost regardless of the taker's depth. - **Market-makers as CPI users.** Formalise the `remaining_accounts` - protocol so a market-making program can call `place_order` on + layout so a market-making program can call `place_order` on behalf of its users, pre-computing the crossings offchain and rewriting the book in one transaction. diff --git a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs index 539b7199a..7293728b8 100644 --- a/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs +++ b/finance/order-book/anchor/programs/order-book/src/instructions/place_order.rs @@ -251,7 +251,7 @@ pub fn handle_place_order( .try_into() .map_err(|_| ErrorCode::NumericalOverflow)?; - // Ceiling division: round the fee in the protocol's favour. Flooring + // Ceiling division: round the fee in the program's favour. Flooring // would leak up to 1 minor unit of quote per fill to the maker, which // an attacker could industrialise with many tiny fills. let fee_quote: u64 = (gross_quote as u128) diff --git a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs index 2488ae679..e8b4fcfc7 100644 --- a/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs +++ b/finance/order-book/anchor/programs/order-book/tests/test_order_book.rs @@ -62,7 +62,7 @@ const QUOTE_DECIMALS: u8 = 6; // USDC const FEE_BASIS_POINTS: u16 = 10; // Mirror of the program's fee rounding: ceiling division so the fee rounds -// in the protocol's favour (flooring would leak dust to the maker per fill). +// in the program's favour (flooring would leak dust to the maker per fill). const fn fee_ceil(gross: u64) -> u64 { ((gross as u128 * FEE_BASIS_POINTS as u128 + 9_999) / 10_000) as u64 } @@ -593,7 +593,7 @@ fn initialize_market_user_tracks_market_and_owner() { } #[test] -fn place_bid_locks_quote_in_vault() { +fn place_bid_moves_quote_into_vault() { let mut sc = full_setup(); initialize_market_and_users(&mut sc); @@ -639,7 +639,7 @@ fn place_bid_locks_quote_in_vault() { } #[test] -fn place_ask_locks_base_in_vault() { +fn place_ask_moves_base_into_vault() { let mut sc = full_setup(); initialize_market_and_users(&mut sc); @@ -1526,7 +1526,7 @@ fn taker_partially_filled_remainder_rests_on_book() { // The taker's own Order PDA holds the true remaining-on-book quantity // (original_quantity - filled_quantity). On-book quantity isn't stored // on OrderEntry directly - see state/order_book.rs - so this is the - // source of truth both here and at runtime. + // field the program reads both here and at runtime. assert_eq!( TAKER_BID_QUANTITY - taker_filled, TAKER_BID_QUANTITY - MAKER_ASK_QUANTITY @@ -1842,7 +1842,7 @@ fn taker_bid_gets_price_improvement_from_resting_ask() { #[test] fn fee_rounds_up_when_gross_is_not_a_bps_multiple() { // Rounding regression: with fee_bps = 10, a gross of 501 quote tokens - // gives 501 * 10 / 10_000 = 0.501, which must round UP to 1 (protocol- + // gives 501 * 10 / 10_000 = 0.501, which must round UP to 1 (program- // favouring ceiling), not down to 0. A floor here would let makers // fill fee-free with many small orders. let mut sc = full_setup(); diff --git a/finance/order-book/kani-proofs/README.md b/finance/order-book/kani-proofs/README.md index 712d218d3..499f84027 100644 --- a/finance/order-book/kani-proofs/README.md +++ b/finance/order-book/kani-proofs/README.md @@ -47,7 +47,7 @@ every push/PR, because they are slow. A fast unit-test job runs per push/PR. - The ceiling fee can make `fee == gross` on dust fills (e.g. `gross = 1`), so a maker can net zero quote on a sub-unit fill. This is intended (the comment in - `place_order` notes ceiling rounding is in the protocol's favour to stop + `place_order` notes ceiling rounding is in the program's favour to stop fee-dust farming), not a bug: the model check confirms `fee <= gross` always holds, so the maker is never *overdrawn*. diff --git a/finance/order-book/kani-proofs/src/lib.rs b/finance/order-book/kani-proofs/src/lib.rs index 999b0ded4..5d203bd06 100644 --- a/finance/order-book/kani-proofs/src/lib.rs +++ b/finance/order-book/kani-proofs/src/lib.rs @@ -281,7 +281,7 @@ mod tests { assert_eq!(ceil_fee(1, 5_000).unwrap(), 1); // gross 10_000, bps 30 -> exactly 30. assert_eq!(ceil_fee(10_000, 30).unwrap(), 30); - // gross 1, bps 1 -> ceil(0.0001) == 1 (rounds up in protocol favour). + // gross 1, bps 1 -> ceil(0.0001) == 1 (rounds up in the program's favour). assert_eq!(ceil_fee(1, 1).unwrap(), 1); // never exceeds gross. assert!(ceil_fee(10_000, 10_000).unwrap() <= 10_000); diff --git a/finance/order-book/quasar/README.md b/finance/order-book/quasar/README.md index b3a9d61a1..91da4d5dd 100644 --- a/finance/order-book/quasar/README.md +++ b/finance/order-book/quasar/README.md @@ -121,7 +121,7 @@ are all consequences of Quasar being zero-copy, `no_std`, and zero-allocation: - `place_order` binds every market-owned account (`base_vault`, `quote_vault`, `fee_vault`, both mints, the order book) to the addresses stored on the `Market` PDA with `has_one`, so a caller can't substitute the fee vault for a user vault and drain fees. -- Taker fees use **ceiling** division, rounding in the protocol's favor so many tiny fills can't leak a minor +- Taker fees use **ceiling** division, rounding in the program's favor so many tiny fills can't leak a minor unit to the maker. - A full side **evicts its worst order** for a better one instead of refusing every new order, so filling the book with far-off orders cannot shut a market (see the lifecycle section). diff --git a/finance/order-book/quasar/src/instructions/place_order.rs b/finance/order-book/quasar/src/instructions/place_order.rs index 89e6dbe2a..11697bf11 100644 --- a/finance/order-book/quasar/src/instructions/place_order.rs +++ b/finance/order-book/quasar/src/instructions/place_order.rs @@ -61,7 +61,7 @@ fn quote_value(price: u64, lots: u64, quote_lot_size: u64) -> Result Result { (gross_quote as u128) .checked_mul(fee_basis_points as u128) diff --git a/finance/perpetual-futures/anchor-v1/README.md b/finance/perpetual-futures/anchor-v1/README.md index 9616d0165..cc0a81fda 100644 --- a/finance/perpetual-futures/anchor-v1/README.md +++ b/finance/perpetual-futures/anchor-v1/README.md @@ -60,7 +60,7 @@ The mark price comes from an oracle feed. This example validates the price for s ### Fees and slippage -Open and close fees are charged in [basis points](https://www.investopedia.com/terms/b/basispoint.asp) (1 bp = 0.01%) of notional and accrue to the protocol. Every state-changing handler takes a `minimum_*` / acceptable-price bound (protection against [slippage](https://www.investopedia.com/terms/s/slippage.asp), the gap between the expected and actual fill) and reverts if the bound is breached. Pass `0` to opt out. +Open and close fees are charged in [basis points](https://www.investopedia.com/terms/b/basispoint.asp) (1 bp = 0.01%) of notional and accrue to the program. Every state-changing handler takes a `minimum_*` / acceptable-price bound (protection against [slippage](https://www.investopedia.com/terms/s/slippage.asp), the gap between the expected and actual fill) and reverts if the bound is breached. Pass `0` to opt out. --- @@ -68,7 +68,7 @@ Open and close fees are charged in [basis points](https://www.investopedia.com/t ### Participants -- **Admin** (Pool operator): Operate the market and collect the protocol's slice of trading fees. +- **Admin** (Pool operator): Operate the market and collect the program's slice of trading fees. - **Carol** (Liquidity provider): Earn fees by funding the pool and being the counterparty to traders. - **Alice** (Long trader): She has a thesis that NVDA will rise and wants leveraged upside without buying the stock. - **Bob** (Short trader): He thinks NVDA will fall and wants to profit from the downside. @@ -84,7 +84,7 @@ Amounts below are shown in whole USDC; onchain they are base units (× 10⁶). T **Accounts created:** -- `Pool` [PDA](https://solana.com/docs/terminology#program-derived-address-pda), seeds `["pool", collateral_mint, oracle_feed]`: parameters, liquidity, reserved liquidity, collateral total, per-side open-interest accumulators, funding index, protocol fees. The pool owns the vault and is the LP mint's authority, and signs vault transfers and mint/burn CPIs with its own seeds; there is no separate signing PDA +- `Pool` [PDA](https://solana.com/docs/terminology#program-derived-address-pda), seeds `["pool", collateral_mint, oracle_feed]`: parameters, liquidity, reserved liquidity, collateral total, per-side open-interest accumulators, funding index, program fees. The pool owns the vault and is the LP mint's authority, and signs vault transfers and mint/burn CPIs with its own seeds; there is no separate signing PDA - `custody_vault` [token account](https://solana.com/docs/terminology#token-account) PDA, seeds `["vault", pool]`: all USDC, both provider liquidity and trader collateral; `pool` is its owner - `lp_mint` PDA, seeds `["lp_mint", pool]`: the share [mint](https://solana.com/docs/terminology#mint-account); `pool` is the mint authority @@ -117,7 +117,7 @@ NVDAx is at $100. The 0.1% open fee ($5) comes out of her collateral, leaving $9 - `alice_usdc`: −1,000 USDC - `custody_vault`: +1,000 USDC - `Pool.total_collateral`: +$995 -- `Pool.protocol_fees`: +$5 +- `Pool.program_fees`: +$5 - `Pool.reserved_liquidity`: +$5,000 (must stay ≤ liquidity) - `Pool` long open-interest accumulators: += this position @@ -127,7 +127,7 @@ NVDAx is at $100. The 0.1% open fee ($5) comes out of her collateral, leaving $9 **Instruction:** `open_position(side = Short, collateral_amount = 1,000 USDC, size = 5,000 USDC, acceptable_price)` -**Accounts modified:** a `Position` PDA `["position", pool, bob, Short]` is created; `custody_vault` +1,000 USDC; `Pool.total_collateral` +$995; `Pool.protocol_fees` +$5; `Pool.reserved_liquidity` +$5,000 (now $10,000 of the $100,000 reserved); short open-interest accumulators rise. +**Accounts modified:** a `Position` PDA `["position", pool, bob, Short]` is created; `custody_vault` +1,000 USDC; `Pool.total_collateral` +$995; `Pool.program_fees` +$5; `Pool.reserved_liquidity` +$5,000 (now $10,000 of the $100,000 reserved); short open-interest accumulators rise. While both are open, **funding** accrues to the pool from the heavier side; it is settled when each position closes. @@ -144,7 +144,7 @@ Her profit is `5,000 × (116 − 100) / 100 = $800` (well under the $5,000 reser - `Pool.liquidity`: −$800 (providers pay her profit) - `Pool.reserved_liquidity`: −$5,000 (reserve released) - `Pool.total_collateral`: −$995 -- `Pool.protocol_fees`: +$5 +- `Pool.program_fees`: +$5 - long open-interest accumulators: −= this position - `custody_vault` → `alice_usdc`: pays out $1,790 (net collateral + profit − close fee) - `Position` (Alice): closed; rent returned to Alice @@ -169,11 +169,11 @@ At $116 Bob's short has lost $800; his equity ($995 − $800 = $195) has fallen --- -### Step 7: Admin collects the protocol's fees +### Step 7: Admin collects the program's fees **Instruction:** `collect_fees()` -**Accounts modified:** `Pool.protocol_fees` → 0; `custody_vault` pays that amount to `admin_usdc`. +**Accounts modified:** `Pool.program_fees` → 0; `custody_vault` pays that amount to `admin_usdc`. --- diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/errors.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/errors.rs index 94509f063..3f33f0c2c 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/errors.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/errors.rs @@ -53,7 +53,7 @@ pub enum PerpError { #[msg("Position equity is below maintenance margin; it must be liquidated, not closed")] PositionNotHealthy, - #[msg("No protocol fees are available to collect")] + #[msg("No program fees are available to collect")] NothingToClaim, #[msg("Oracle price is stale: it predates the last cluster restart")] diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/close_position.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/close_position.rs index 3685b40c0..61c741394 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/close_position.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/close_position.rs @@ -61,8 +61,8 @@ pub fn handle_close_position( pool.liquidity = new_liquidity .try_into() .map_err(|_| PerpError::MathOverflow)?; - pool.protocol_fees = pool - .protocol_fees + pool.program_fees = pool + .program_fees .checked_add(close_fee) .ok_or(PerpError::MathOverflow)?; diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/collect_fees.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/collect_fees.rs index 03e9d6b46..0be91a4f9 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/collect_fees.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/collect_fees.rs @@ -10,11 +10,11 @@ use crate::state::Pool; pub fn handle_collect_fees(context: Context) -> Result<()> { let pool = &mut context.accounts.pool; - let amount = pool.protocol_fees; + let amount = pool.program_fees; require!(amount > 0, PerpError::NothingToClaim); // Effects before interaction: zero the balance, then transfer. - pool.protocol_fees = 0; + pool.program_fees = 0; // The pool signs the CPI below with its own seeds. let pool_seeds: &[&[u8]] = &[ diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/initialize_pool.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/initialize_pool.rs index e104f0a5d..7c2079182 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/initialize_pool.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/initialize_pool.rs @@ -84,7 +84,7 @@ pub fn handle_initialize_pool( pool.liquidity = 0; pool.reserved_liquidity = 0; pool.total_collateral = 0; - pool.protocol_fees = 0; + pool.program_fees = 0; pool.long_size = 0; pool.short_size = 0; pool.long_size_scaled = 0; diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/open_position.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/open_position.rs index 8c3ec8dc9..45b27ac47 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/open_position.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/instructions/open_position.rs @@ -80,8 +80,8 @@ pub fn handle_open_position( .total_collateral .checked_add(net_collateral) .ok_or(PerpError::MathOverflow)?; - pool.protocol_fees = pool - .protocol_fees + pool.program_fees = pool + .program_fees .checked_add(open_fee) .ok_or(PerpError::MathOverflow)?; diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/lib.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/lib.rs index 0f49f100d..23d968fab 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/lib.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/lib.rs @@ -75,7 +75,7 @@ pub mod perpetual_futures { instructions::handle_liquidate_position(context) } - /// The pool operator sweeps the accumulated protocol fees from the vault. + /// The pool operator sweeps the accumulated program fees from the vault. pub fn collect_fees(context: Context) -> Result<()> { instructions::handle_collect_fees(context) } diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/state/pool.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/state/pool.rs index 9e3eea141..46f8831a7 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/state/pool.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/src/state/pool.rs @@ -9,7 +9,7 @@ use anchor_lang::prelude::*; #[account] #[derive(InitSpace)] pub struct Pool { - /// Admin: configures the pool and sweeps protocol fees. Not a custody + /// Admin: configures the pool and sweeps program fees. Not a custody /// escape hatch — it cannot touch liquidity-provider or trader funds. pub authority: Pubkey, @@ -43,8 +43,8 @@ pub struct Pool { /// Sum of every open position's posted collateral, held in the same vault. pub total_collateral: u64, - /// Protocol fees accrued from open/close fees, awaiting `collect_fees`. - pub protocol_fees: u64, + /// Program fees accrued from open/close fees, awaiting `collect_fees`. + pub program_fees: u64, /// Aggregate long open interest (sum of position `size`), in collateral /// base units of notional. diff --git a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs index cde376ec1..c58b81b2d 100644 --- a/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs +++ b/finance/perpetual-futures/anchor-v1/programs/perpetual-futures/tests/test_perpetual_futures.rs @@ -748,7 +748,7 @@ fn test_open_long_updates_pool() { // Collateral minus the 0.1% open fee is now tracked as trader collateral. let open_fee = size / 1_000; assert_eq!(pool.total_collateral, collateral - open_fee); - assert_eq!(pool.protocol_fees, open_fee); + assert_eq!(pool.program_fees, open_fee); } #[test] @@ -1197,7 +1197,7 @@ fn test_collect_fees() { .open_position(&trader, trader_collateral, Side::Long, collateral, size, 0) .unwrap(); - let fees = market.pool_state().protocol_fees; + let fees = market.pool_state().program_fees; assert!(fees > 0); let admin = market.admin.insecure_clone(); @@ -1214,7 +1214,7 @@ fn test_collect_fees() { get_token_account_balance(&market.svm, &admin_collateral).unwrap(), fees ); - assert_eq!(market.pool_state().protocol_fees, 0); + assert_eq!(market.pool_state().program_fees, 0); // Nothing left to claim on a second sweep. assert!(market.collect_fees(&admin).is_err()); diff --git a/finance/perpetual-futures/anchor/README.md b/finance/perpetual-futures/anchor/README.md index e8040ea34..f5559d6f2 100644 --- a/finance/perpetual-futures/anchor/README.md +++ b/finance/perpetual-futures/anchor/README.md @@ -60,7 +60,7 @@ The mark price comes from an oracle feed. This example validates the price for s ### Fees and slippage -Open and close fees are charged in [basis points](https://www.investopedia.com/terms/b/basispoint.asp) (1 bp = 0.01%) of notional and accrue to the protocol. Every state-changing handler takes a `minimum_*` / acceptable-price bound (protection against [slippage](https://www.investopedia.com/terms/s/slippage.asp), the gap between the expected and actual fill) and reverts if the bound is breached. Pass `0` to opt out. +Open and close fees are charged in [basis points](https://www.investopedia.com/terms/b/basispoint.asp) (1 bp = 0.01%) of notional and accrue to the program. Every state-changing handler takes a `minimum_*` / acceptable-price bound (protection against [slippage](https://www.investopedia.com/terms/s/slippage.asp), the gap between the expected and actual fill) and reverts if the bound is breached. Pass `0` to opt out. --- @@ -68,7 +68,7 @@ Open and close fees are charged in [basis points](https://www.investopedia.com/t ### Participants -- **Admin** (Pool operator): Operate the market and collect the protocol's slice of trading fees. +- **Admin** (Pool operator): Operate the market and collect the program's slice of trading fees. - **Carol** (Liquidity provider): Earn fees by funding the pool and being the counterparty to traders. - **Alice** (Long trader): She has a thesis that NVDA will rise and wants leveraged upside without buying the stock. - **Bob** (Short trader): He thinks NVDA will fall and wants to profit from the downside. @@ -84,7 +84,7 @@ Amounts below are shown in whole USDC; onchain they are base units (× 10⁶). T **Accounts created:** -- `Pool` [PDA](https://solana.com/docs/terminology#program-derived-address-pda), seeds `["pool", collateral_mint, oracle_feed]`: parameters, liquidity, reserved liquidity, collateral total, per-side open-interest accumulators, funding index, protocol fees. The pool owns the vault and is the LP mint's authority, and signs vault transfers and mint/burn CPIs with its own seeds; there is no separate signing PDA +- `Pool` [PDA](https://solana.com/docs/terminology#program-derived-address-pda), seeds `["pool", collateral_mint, oracle_feed]`: parameters, liquidity, reserved liquidity, collateral total, per-side open-interest accumulators, funding index, program fees. The pool owns the vault and is the LP mint's authority, and signs vault transfers and mint/burn CPIs with its own seeds; there is no separate signing PDA - `custody_vault` [token account](https://solana.com/docs/terminology#token-account) PDA, seeds `["vault", pool]`: all USDC, both provider liquidity and trader collateral; `pool` is its owner - `lp_mint` PDA, seeds `["lp_mint", pool]`: the share [mint](https://solana.com/docs/terminology#mint-account); `pool` is the mint authority @@ -117,7 +117,7 @@ NVDAx is at $100. The 0.1% open fee ($5) comes out of her collateral, leaving $9 - `alice_usdc`: −1,000 USDC - `custody_vault`: +1,000 USDC - `Pool.total_collateral`: +$995 -- `Pool.protocol_fees`: +$5 +- `Pool.program_fees`: +$5 - `Pool.reserved_liquidity`: +$5,000 (must stay ≤ liquidity) - `Pool` long open-interest accumulators: += this position @@ -127,7 +127,7 @@ NVDAx is at $100. The 0.1% open fee ($5) comes out of her collateral, leaving $9 **Instruction:** `open_position(side = Short, collateral_amount = 1,000 USDC, size = 5,000 USDC, acceptable_price)` -**Accounts modified:** a `Position` PDA `["position", pool, bob, Short]` is created; `custody_vault` +1,000 USDC; `Pool.total_collateral` +$995; `Pool.protocol_fees` +$5; `Pool.reserved_liquidity` +$5,000 (now $10,000 of the $100,000 reserved); short open-interest accumulators rise. +**Accounts modified:** a `Position` PDA `["position", pool, bob, Short]` is created; `custody_vault` +1,000 USDC; `Pool.total_collateral` +$995; `Pool.program_fees` +$5; `Pool.reserved_liquidity` +$5,000 (now $10,000 of the $100,000 reserved); short open-interest accumulators rise. While both are open, **funding** accrues to the pool from the heavier side; it is settled when each position closes. @@ -144,7 +144,7 @@ Her profit is `5,000 × (116 − 100) / 100 = $800` (well under the $5,000 reser - `Pool.liquidity`: −$800 (providers pay her profit) - `Pool.reserved_liquidity`: −$5,000 (reserve released) - `Pool.total_collateral`: −$995 -- `Pool.protocol_fees`: +$5 +- `Pool.program_fees`: +$5 - long open-interest accumulators: −= this position - `custody_vault` → `alice_usdc`: pays out $1,790 (net collateral + profit − close fee) - `Position` (Alice): closed; rent returned to Alice @@ -169,11 +169,11 @@ At $116 Bob's short has lost $800; his equity ($995 − $800 = $195) has fallen --- -### Step 7: Admin collects the protocol's fees +### Step 7: Admin collects the program's fees **Instruction:** `collect_fees()` -**Accounts modified:** `Pool.protocol_fees` → 0; `custody_vault` pays that amount to `admin_usdc`. +**Accounts modified:** `Pool.program_fees` → 0; `custody_vault` pays that amount to `admin_usdc`. --- diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/errors.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/errors.rs index 94509f063..3f33f0c2c 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/errors.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/errors.rs @@ -53,7 +53,7 @@ pub enum PerpError { #[msg("Position equity is below maintenance margin; it must be liquidated, not closed")] PositionNotHealthy, - #[msg("No protocol fees are available to collect")] + #[msg("No program fees are available to collect")] NothingToClaim, #[msg("Oracle price is stale: it predates the last cluster restart")] diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs index 5c37d3f08..bbfebd9bb 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/close_position.rs @@ -61,8 +61,8 @@ pub fn handle_close_position( pool.liquidity = new_liquidity .try_into() .map_err(|_| PerpError::MathOverflow)?; - pool.protocol_fees = pool - .protocol_fees + pool.program_fees = pool + .program_fees .checked_add(close_fee) .ok_or(PerpError::MathOverflow)?; diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs index 8f5550d84..a686687ff 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/collect_fees.rs @@ -10,11 +10,11 @@ use crate::state::Pool; pub fn handle_collect_fees(context: &mut Context) -> Result<()> { let pool = &mut context.accounts.pool; - let amount = pool.protocol_fees; + let amount = pool.program_fees; require!(amount > 0, PerpError::NothingToClaim); // Effects before interaction: zero the balance, then transfer. - pool.protocol_fees = 0; + pool.program_fees = 0; // The pool signs the CPI below with its own seeds. Copy them out first: a // data account holds a live borrow on its buffer, which the runtime diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs index 558723e31..7bae54af3 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/initialize_pool.rs @@ -86,7 +86,7 @@ pub fn handle_initialize_pool( pool.liquidity = 0; pool.reserved_liquidity = 0; pool.total_collateral = 0; - pool.protocol_fees = 0; + pool.program_fees = 0; pool.long_size = 0; pool.short_size = 0; pool.long_size_scaled = 0; diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs index 0a1d96507..e820f032f 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/instructions/open_position.rs @@ -80,8 +80,8 @@ pub fn handle_open_position( .total_collateral .checked_add(net_collateral) .ok_or(PerpError::MathOverflow)?; - pool.protocol_fees = pool - .protocol_fees + pool.program_fees = pool + .program_fees .checked_add(open_fee) .ok_or(PerpError::MathOverflow)?; diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs index c06613b60..dbfe8d7a9 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/lib.rs @@ -78,7 +78,7 @@ pub mod perpetual_futures { instructions::handle_liquidate_position(context) } - /// The pool operator sweeps the accumulated protocol fees from the vault. + /// The pool operator sweeps the accumulated program fees from the vault. pub fn collect_fees(context: &mut Context) -> Result<()> { instructions::handle_collect_fees(context) } diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/pool.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/pool.rs index e1e12afdd..a8df9b5c0 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/pool.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/src/state/pool.rs @@ -9,7 +9,7 @@ use anchor_lang::prelude::*; #[account(borsh)] #[derive(InitSpace)] pub struct Pool { - /// Admin: configures the pool and sweeps protocol fees. Not a custody + /// Admin: configures the pool and sweeps program fees. Not a custody /// escape hatch — it cannot touch liquidity-provider or trader funds. pub authority: Address, @@ -43,8 +43,8 @@ pub struct Pool { /// Sum of every open position's posted collateral, held in the same vault. pub total_collateral: u64, - /// Protocol fees accrued from open/close fees, awaiting `collect_fees`. - pub protocol_fees: u64, + /// Program fees accrued from open/close fees, awaiting `collect_fees`. + pub program_fees: u64, /// Aggregate long open interest (sum of position `size`), in collateral /// base units of notional. diff --git a/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs b/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs index 7ec642eeb..7bbb772f7 100644 --- a/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs +++ b/finance/perpetual-futures/anchor/programs/perpetual-futures/tests/test_perpetual_futures.rs @@ -746,7 +746,7 @@ fn test_open_long_updates_pool() { // Collateral minus the 0.1% open fee is now tracked as trader collateral. let open_fee = size / 1_000; assert_eq!(pool.total_collateral, collateral - open_fee); - assert_eq!(pool.protocol_fees, open_fee); + assert_eq!(pool.program_fees, open_fee); } #[test] @@ -1192,7 +1192,7 @@ fn test_collect_fees() { .open_position(&trader, trader_collateral, Side::Long, collateral, size, 0) .unwrap(); - let fees = market.pool_state().protocol_fees; + let fees = market.pool_state().program_fees; assert!(fees > 0); let admin = market.admin.insecure_clone(); @@ -1209,7 +1209,7 @@ fn test_collect_fees() { get_token_account_balance(&market.svm, &admin_collateral).unwrap(), fees ); - assert_eq!(market.pool_state().protocol_fees, 0); + assert_eq!(market.pool_state().program_fees, 0); // Nothing left to claim on a second sweep. assert!(market.collect_fees(&admin).is_err()); diff --git a/finance/perpetual-futures/quasar/src/instructions/close_position.rs b/finance/perpetual-futures/quasar/src/instructions/close_position.rs index 340078248..006513e16 100644 --- a/finance/perpetual-futures/quasar/src/instructions/close_position.rs +++ b/finance/perpetual-futures/quasar/src/instructions/close_position.rs @@ -123,13 +123,13 @@ pub fn handle_close_position( .liquidity .set(u64::try_from(new_liquidity).map_err(|_| ProgramError::ArithmeticOverflow)?); - let new_protocol_fees = accounts + let new_program_fees = accounts .pool - .protocol_fees + .program_fees .get() .checked_add(close_fee) .ok_or(ProgramError::ArithmeticOverflow)?; - accounts.pool.protocol_fees.set(new_protocol_fees); + accounts.pool.program_fees.set(new_program_fees); // The pool signs the CPI below with its own seeds. let bump = [bumps.pool]; diff --git a/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs b/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs index 181080844..475a8b153 100644 --- a/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs +++ b/finance/perpetual-futures/quasar/src/instructions/collect_fees.rs @@ -40,11 +40,11 @@ pub fn handle_collect_fees( accounts: &mut CollectFees, bumps: &CollectFeesBumps, ) -> Result<(), ProgramError> { - let amount = accounts.pool.protocol_fees.get(); + let amount = accounts.pool.program_fees.get(); if amount == 0 { return Err(err(error::NOTHING_TO_CLAIM)); } - accounts.pool.protocol_fees.set(0); + accounts.pool.program_fees.set(0); // The pool signs the CPI below with its own seeds. let bump = [bumps.pool]; diff --git a/finance/perpetual-futures/quasar/src/instructions/initialize_pool.rs b/finance/perpetual-futures/quasar/src/instructions/initialize_pool.rs index 9d3f42464..0266d545e 100644 --- a/finance/perpetual-futures/quasar/src/instructions/initialize_pool.rs +++ b/finance/perpetual-futures/quasar/src/instructions/initialize_pool.rs @@ -97,7 +97,7 @@ pub fn handle_initialize_pool( liquidity: 0, reserved_liquidity: 0, total_collateral: 0, - protocol_fees: 0, + program_fees: 0, long_size: 0, short_size: 0, long_size_scaled: 0, diff --git a/finance/perpetual-futures/quasar/src/instructions/open_position.rs b/finance/perpetual-futures/quasar/src/instructions/open_position.rs index 6a2c76397..61d0d557e 100644 --- a/finance/perpetual-futures/quasar/src/instructions/open_position.rs +++ b/finance/perpetual-futures/quasar/src/instructions/open_position.rs @@ -132,13 +132,13 @@ pub fn handle_open_position( .ok_or(ProgramError::ArithmeticOverflow)?; accounts.pool.total_collateral.set(new_total_collateral); - let new_protocol_fees = accounts + let new_program_fees = accounts .pool - .protocol_fees + .program_fees .get() .checked_add(open_fee) .ok_or(ProgramError::ArithmeticOverflow)?; - accounts.pool.protocol_fees.set(new_protocol_fees); + accounts.pool.program_fees.set(new_program_fees); if side == SIDE_LONG { let long_size = accounts diff --git a/finance/perpetual-futures/quasar/src/instructions/shared.rs b/finance/perpetual-futures/quasar/src/instructions/shared.rs index 8cd13d5d5..f4a08c1ac 100644 --- a/finance/perpetual-futures/quasar/src/instructions/shared.rs +++ b/finance/perpetual-futures/quasar/src/instructions/shared.rs @@ -1,6 +1,6 @@ //! Arithmetic and the oracle decode, ported verbatim from the Anchor sibling. //! All integer, all `checked_*`, multiply-before-divide, rounding toward the -//! protocol. Errors are `ProgramError::Custom(code)`; the codes are listed here. +//! program. Errors are `ProgramError::Custom(code)`; the codes are listed here. use quasar_lang::{prelude::*, sysvars::Sysvar}; diff --git a/finance/perpetual-futures/quasar/src/state.rs b/finance/perpetual-futures/quasar/src/state.rs index 452b107af..b3828bbe7 100644 --- a/finance/perpetual-futures/quasar/src/state.rs +++ b/finance/perpetual-futures/quasar/src/state.rs @@ -22,7 +22,7 @@ pub struct Pool { /// `reserved + size <= liquidity`. pub reserved_liquidity: u64, pub total_collateral: u64, - pub protocol_fees: u64, + pub program_fees: u64, pub long_size: u128, pub short_size: u128, pub long_size_scaled: u128, diff --git a/finance/token-swap/README.md b/finance/token-swap/README.md index 70c247314..75205bde5 100644 --- a/finance/token-swap/README.md +++ b/finance/token-swap/README.md @@ -11,7 +11,7 @@ The pool keeps `x * y = K` invariant: if `x` is the reserve of token A and `y` i - LP positions tracked as SPL tokens via a per-pool `liquidity_provider_mint`, so they're composable with any wallet or downstream [program](https://solana.com/docs/terminology#program). - Deposits clamped to the current pool ratio (Uniswap V2's `mint()` pattern), with caller amounts treated as upper bounds and all ratio math done in `u128` with checked arithmetic. - Constant-product (`x * y = k`) swaps with a trading fee split between LPs and the admin, configured by `Config.fee` and `Config.admin_share_bps`. -- Admin protocol fees accrued onchain as virtual claims on the pool reserves, swept on demand by `claim_admin_fees`. +- Admin program fees accrued onchain as virtual claims on the pool reserves, swept on demand by `claim_admin_fees`. - Withdrawals proportional to LP-token share of the **effective reserves** (raw reserve minus admin's owed slice), so the admin's accrued fees don't dilute exiting LPs. - **Caller-supplied [slippage](https://www.investopedia.com/terms/s/slippage.asp) floors on every state-changing [instruction](https://solana.com/docs/terminology#instruction):** swaps revert with `SlippageExceeded` if the output falls below `min_output_amount`, deposits revert with `DepositBelowMinimum` if the LP mint amount falls below `minimum_lp_tokens_out`, withdrawals revert with `WithdrawalBelowMinimum` if either side falls below its floor. - **Defence-in-depth invariant check:** every swap re-verifies `effective_pool_a * effective_pool_b` doesn't decrease after the transfers, so a bug in the curve math fails the transaction instead of silently giving the trader too much. @@ -157,7 +157,7 @@ A worked example, end to end, using this program. The example uses three tokens: **Cast:** -- **Alice** - AMM operator. Deploys and runs the exchange. Earns a slice of every trading fee via the admin protocol-fee mechanism; also earns LP [yield](https://www.investopedia.com/terms/y/yield.asp) on her own initial deposits. Wants real usage so fee income compounds. She calls `initialize_config` to fix the trading fee at 0.3% and sets `admin_share_bps = 1667` so she earns ~1/6 of every trading fee (LPs keep the other ~5/6). She seeds both the NVDAx/USDC pool and the TSLAx/USDC pool herself (eating the locked `MINIMUM_LIQUIDITY` cost) so users have something to trade from day one. +- **Alice** - AMM operator. Deploys and runs the exchange. Earns a slice of every trading fee via the admin fee mechanism; also earns LP [yield](https://www.investopedia.com/terms/y/yield.asp) on her own initial deposits. Wants real usage so fee income compounds. She calls `initialize_config` to fix the trading fee at 0.3% and sets `admin_share_bps = 1667` so she earns ~1/6 of every trading fee (LPs keep the other ~5/6). She seeds both the NVDAx/USDC pool and the TSLAx/USDC pool herself (eating the locked `MINIMUM_LIQUIDITY` cost) so users have something to trade from day one. - **Bob** - yield farmer / [liquidity provider](https://www.investopedia.com/terms/l/liquidity-provider.asp). Has idle capital (NVDAx and USDC) earning nothing. Wants to earn [passive income](https://www.investopedia.com/terms/p/passiveincome.asp) from the swap fees the pool collects, without actively trading. - **Carol** - retail trader. Holds USDC and has a bullish [thesis](https://www.investopedia.com/terms/i/investmentthesis.asp) on NVIDIA: she believes NVDAx will appreciate. She wants to swap USDC for NVDAx quickly, without a centralised exchange account. She also later buys TSLAx on the TSLAx/USDC pool. - **Dave** - [arbitrageur](https://www.investopedia.com/terms/a/arbitrage.asp). Profits by trading the gap between the pool's mid-price and the offchain market price. Side effect: his trades drag the pool price back toward fair value. diff --git a/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/deposit_liquidity.rs b/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/deposit_liquidity.rs index 3105c9381..154bfd756 100644 --- a/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/deposit_liquidity.rs +++ b/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/deposit_liquidity.rs @@ -151,7 +151,7 @@ pub fn handle_deposit_liquidity( // overflow `u64`, but `u128` absorbs it for any supply a real mint can // reach, and the checked multiply reports the rest. We multiply before // dividing to keep precision, then round down (floor) so the pool keeps - // any sub-unit rounding dust - protocol-favouring rounding, per the + // any sub-unit rounding dust - program-favouring rounding, per the // financial-math rules. let liquidity: u64 = if pool_creation { let product = (amount_a as u128) diff --git a/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/swap_tokens.rs b/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/swap_tokens.rs index af35ce811..14e94d966 100644 --- a/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/swap_tokens.rs +++ b/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/swap_tokens.rs @@ -36,7 +36,7 @@ pub fn handle_swap_tokens( // u128 + checked arithmetic: `input * fee` can overflow u64 (both are // u64-sized in practice; fee is u16 but the multiplication grows fast). // Multiply before divide to preserve precision; floor on the divide is - // protocol-favouring (the trader pays slightly more fee on rounding, + // program-favouring (the trader pays slightly more fee on rounding, // not less). let config = &context.accounts.config; let fee_amount = (input_amount as u128) @@ -102,7 +102,7 @@ pub fn handle_swap_tokens( // // u128 + checked: the numerator `taxed_input * reserve` can fill the // full u128 (both factors are u64). Multiply before divide to keep - // precision. Floor on the divide is protocol-favouring (the pool keeps + // precision. Floor on the divide is program-favouring (the pool keeps // sub-base-unit rounding, the trader gets slightly less output) - same // direction as Uniswap V2. let (this_reserve, other_reserve) = if input_is_token_a { diff --git a/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/withdraw_liquidity.rs b/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/withdraw_liquidity.rs index 65cf7d212..16f28f412 100644 --- a/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/withdraw_liquidity.rs +++ b/finance/token-swap/anchor-v1/programs/token-swap/src/instructions/withdraw_liquidity.rs @@ -59,7 +59,7 @@ pub fn handle_withdraw_liquidity( // // u128 + checked: `lp_amount * reserve` can fill the full u128 (both // factors are u64). Multiply before divide to preserve precision; floor - // is protocol-favouring (sub-base-unit rounding stays with the pool, + // is program-favouring (sub-base-unit rounding stays with the pool, // grows LP value for everyone still in). // // Both amounts are computed up-front (before the slippage checks) so diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs index cffdfdf66..44b072b96 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/deposit_liquidity.rs @@ -152,7 +152,7 @@ pub fn handle_deposit_liquidity( // overflow `u64`, but `u128` absorbs it for any supply a real mint can // reach, and the checked multiply reports the rest. We multiply before // dividing to keep precision, then round down (floor) so the pool keeps - // any sub-unit rounding dust - protocol-favouring rounding, per the + // any sub-unit rounding dust - program-favouring rounding, per the // financial-math rules. let liquidity: u64 = if pool_creation { let product = (amount_a as u128) diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs index 67098b4c3..c301a1e84 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/swap_tokens.rs @@ -36,7 +36,7 @@ pub fn handle_swap_tokens( // u128 + checked arithmetic: `input * fee` can overflow u64 (both are // u64-sized in practice; fee is u16 but the multiplication grows fast). // Multiply before divide to preserve precision; floor on the divide is - // protocol-favouring (the trader pays slightly more fee on rounding, + // program-favouring (the trader pays slightly more fee on rounding, // not less). let config = &context.accounts.config; let fee_amount = (input_amount as u128) @@ -102,7 +102,7 @@ pub fn handle_swap_tokens( // // u128 + checked: the numerator `taxed_input * reserve` can fill the // full u128 (both factors are u64). Multiply before divide to keep - // precision. Floor on the divide is protocol-favouring (the pool keeps + // precision. Floor on the divide is program-favouring (the pool keeps // sub-base-unit rounding, the trader gets slightly less output) - same // direction as Uniswap V2. let (this_reserve, other_reserve) = if input_is_token_a { diff --git a/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs b/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs index f8396dce0..fcefa0a4a 100644 --- a/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs +++ b/finance/token-swap/anchor/programs/token-swap/src/instructions/withdraw_liquidity.rs @@ -59,7 +59,7 @@ pub fn handle_withdraw_liquidity( // // u128 + checked: `lp_amount * reserve` can fill the full u128 (both // factors are u64). Multiply before divide to preserve precision; floor - // is protocol-favouring (sub-base-unit rounding stays with the pool, + // is program-favouring (sub-base-unit rounding stays with the pool, // grows LP value for everyone still in). // // Both amounts are computed up-front (before the slippage checks) so diff --git a/finance/token-swap/kani-proofs/src/lib.rs b/finance/token-swap/kani-proofs/src/lib.rs index 90e51f9d2..100352fe6 100644 --- a/finance/token-swap/kani-proofs/src/lib.rs +++ b/finance/token-swap/kani-proofs/src/lib.rs @@ -228,7 +228,7 @@ fn integer_sqrt(n: u128) -> u128 { /// `integer_sqrt` returns the exact floor of the real square root: /// `r*r <= n < (r+1)*(r+1)`. This is what makes the initial-deposit LP mint -/// (`sqrt(a*b) - MINIMUM_LIQUIDITY`) correct and protocol-favouring. +/// (`sqrt(a*b) - MINIMUM_LIQUIDITY`) correct and program-favouring. /// /// `n` is bounded so `(r+1)^2` cannot overflow `u128` and so the Newton /// iteration's unwind stays tractable; the property is value-general within the