Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this repository are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [2026-09-23] - Finance examples point their production oracle path at Pyth

The oracle network that the lending, perpetual futures and prop AMM examples
modeled their price feeds on has shut down. The perpetual futures and prop AMM
mock oracle program is now `mock-price-feed` in both Anchor variants, with the
same program ID, instructions and account layout. Every production-path comment
and README in the three examples, across Anchor v2, Anchor v1 and Quasar, now
points at a Pyth `PriceUpdateV2` account, which `basics/pyth` reads. No program
behavior changes.

## [2026-09-23] - The token fundraiser and order book Anchor v1 copies catch up

Under the old rule that `anchor-v1/` copies were frozen, two v1 copies were
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ members = [

# finance
#
# The two `mock-switchboard` crates are excluded below: they share a package
# The two `mock-price-feed` crates are excluded below: they share a package
# name, and one workspace cannot hold two packages called the same thing.
# They stay path dependencies of the programs that test against them.
"finance/betting-market/anchor/programs/betting-market",
Expand Down Expand Up @@ -127,11 +127,11 @@ members = [
"tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook",
]
# A path dependency inside the workspace directory joins the workspace unless it
# is excluded. Both of these are named `mock_switchboard`, so leaving them in
# fails with "two packages named `mock_switchboard` in this workspace".
# is excluded. Both of these are named `mock_price_feed`, so leaving them in
# fails with "two packages named `mock_price_feed` in this workspace".
exclude = [
"finance/perpetual-futures/anchor/programs/mock-switchboard",
"finance/prop-amm/anchor/programs/mock-switchboard",
"finance/perpetual-futures/anchor/programs/mock-price-feed",
"finance/prop-amm/anchor/programs/mock-price-feed",
]
resolver = "2"

Expand Down
8 changes: 7 additions & 1 deletion finance/lending/anchor-v1/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ previous program, and by `first_deposit_must_exceed_the_minimum` and
opens each reserve with a deposit from the market owner; `add_empty_reserve`
leaves it empty.

The price feed's production path now points at a Pyth `PriceUpdateV2` account
(`price_mantissa = price_message.price`, `exponent = price_message.exponent`,
`last_updated_slot = posted_slot`), since the oracle network the feed was
modeled on has shut down. Documentation only: the account layout and handlers
are unchanged.

## 2026-09-22

Accrue interest by the wall clock instead of by slots. The reserve's
Expand Down Expand Up @@ -76,7 +82,7 @@ Initial lending program: a Kamino/Solend-style borrow/lend market.
borrow-rate index; per-obligation scaled debt.
- Oracle-priced obligation health with loan-to-value and liquidation-threshold
limits, and close-factor-capped liquidation with a seize bonus.
- Switchboard-On-Demand-shaped price feed with a `set_price` test writer.
- Mantissa-and-exponent price feed with a `set_price` test writer.
- Rust + LiteSVM integration tests covering supply/redeem, borrow/repay,
withdraw, interest accrual, liquidation, the share-inflation guard, and
rounding/stale-input edge cases.
Expand Down
13 changes: 7 additions & 6 deletions finance/lending/anchor-v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ round-trips.

### Oracle

`PriceFeed` mirrors a Switchboard On-Demand pull feed: a signed mantissa, an
`PriceFeed` mirrors an oracle price feed such as Pyth's: a signed mantissa, an
exponent (`price = mantissa * 10^exponent`), and the slot the price was written.
Freshness is checked in **slots** (`MAX_PRICE_STALENESS_SLOTS`), not wall-clock
time, plus one check slots alone cannot make: a cluster restart passes hours of
Expand All @@ -156,11 +156,12 @@ trusts exactly its own market's feed for the mint, and isolated markets can
price the same asset independently.

The `set_price` handler writes the feed directly so the LiteSVM tests are
deterministic; in production a reserve points at the real Switchboard feed and the
program decodes `PullFeedAccountData` (`price_mantissa = current_result.value`,
`exponent = -18`, `last_updated_slot = current_result.slot`) instead, and should
also reject results whose confidence interval is too wide. Switchboard is used
rather than Pyth here for its lower compute cost.
deterministic; in production a reserve points at a Pyth price feed and the
program reads its `PriceUpdateV2` account instead, as
[`basics/pyth`](../../../basics/pyth/) does, after checking the update's
`feed_id`: `price_mantissa` is `price_message.price`, `exponent` is
`price_message.exponent`, and `last_updated_slot` is `posted_slot`. It should
also reject results whose confidence interval is too wide.

### Custody

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use anchor_spl::token_interface::Mint;
use crate::constants::PRICE_FEED_SEED;
use crate::state::{LendingMarket, PriceFeed};

/// Test stand-in for a Switchboard On-Demand feed: writes a price directly so
/// Test stand-in for an oracle price feed: writes a price directly so
/// LiteSVM tests are deterministic. In production the reserve points at a real
/// Switchboard feed instead and this handler is unused.
/// Pyth price feed instead and this handler is unused.
///
/// The feed PDA is seeded by `[b"price_feed", market, mint]` and writing it
/// requires the market's `owner` to sign, so a market's prices can only be set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ use crate::math::price_mantissa_to_scaled;
/// individual), so each market prices its own assets and one market can never
/// write another's feed. Only the market's `owner` may write it (`set_price`).
///
/// The layout mirrors a Switchboard On-Demand pull feed: a signed mantissa plus
/// an exponent (`price = price_mantissa * 10^exponent`) and the slot the value
/// was written. In production this account would be the real Switchboard feed
/// and the program would decode it with the `switchboard-on-demand` crate
/// (`PullFeedAccountData`): `price_mantissa = current_result.value`,
/// `exponent = -18`, `last_updated_slot = current_result.slot`. Here the
/// The layout mirrors an oracle price feed such as Pyth's: a signed mantissa
/// plus an exponent (`price = price_mantissa * 10^exponent`) and the slot the
/// value was written. In production this account would be a Pyth
/// `PriceUpdateV2` owned by the Pyth Receiver program (`basics/pyth` reads
/// one), mapped as `price_mantissa = price_message.price`,
/// `exponent = price_message.exponent` and `last_updated_slot = posted_slot`,
/// after checking the update's `feed_id`. Here the
/// `set_price` handler writes it directly so LiteSVM tests are deterministic.
/// A production read should also reject results whose confidence interval is
/// too wide; this stand-in has no confidence field to check.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use lending::state::{Obligation, Reserve, ReserveConfig};
pub use anchor_lang::prelude::Pubkey;

/// A FIXED_POINT_SCALE-scaled price exponent: prices are passed as
/// `mantissa * 10^-18`, matching a Switchboard On-Demand feed's 1e18 result.
/// `mantissa * 10^-18`, the same 18 decimals as `FIXED_POINT_SCALE`.
pub const PRICE_EXPONENT: i32 = -18;

pub fn dollars(whole: u64) -> i128 {
Expand Down
8 changes: 7 additions & 1 deletion finance/lending/anchor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ previous program, and by `first_deposit_must_exceed_the_minimum` and
opens each reserve with a deposit from the market owner; `add_empty_reserve`
leaves it empty.

The price feed's production path now points at a Pyth `PriceUpdateV2` account
(`price_mantissa = price_message.price`, `exponent = price_message.exponent`,
`last_updated_slot = posted_slot`), since the oracle network the feed was
modeled on has shut down. Documentation only: the account layout and handlers
are unchanged.

## 2026-09-22

Accrue interest by the wall clock instead of by slots. The reserve's
Expand Down Expand Up @@ -76,7 +82,7 @@ Initial lending program: a Kamino/Solend-style borrow/lend market.
borrow-rate index; per-obligation scaled debt.
- Oracle-priced obligation health with loan-to-value and liquidation-threshold
limits, and close-factor-capped liquidation with a seize bonus.
- Switchboard-On-Demand-shaped price feed with a `set_price` test writer.
- Mantissa-and-exponent price feed with a `set_price` test writer.
- Rust + LiteSVM integration tests covering supply/redeem, borrow/repay,
withdraw, interest accrual, liquidation, the share-inflation guard, and
rounding/stale-input edge cases.
Expand Down
13 changes: 7 additions & 6 deletions finance/lending/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ round-trips.

### Oracle

`PriceFeed` mirrors a Switchboard On-Demand pull feed: a signed mantissa, an
`PriceFeed` mirrors an oracle price feed such as Pyth's: a signed mantissa, an
exponent (`price = mantissa * 10^exponent`), and the slot the price was written.
Freshness is checked in **slots** (`MAX_PRICE_STALENESS_SLOTS`), not wall-clock
time, plus one check slots alone cannot make: a cluster restart passes hours of
Expand All @@ -156,11 +156,12 @@ trusts exactly its own market's feed for the mint, and isolated markets can
price the same asset independently.

The `set_price` handler writes the feed directly so the LiteSVM tests are
deterministic; in production a reserve points at the real Switchboard feed and the
program decodes `PullFeedAccountData` (`price_mantissa = current_result.value`,
`exponent = -18`, `last_updated_slot = current_result.slot`) instead, and should
also reject results whose confidence interval is too wide. Switchboard is used
rather than Pyth here for its lower compute cost.
deterministic; in production a reserve points at a Pyth price feed and the
program reads its `PriceUpdateV2` account instead, as
[`basics/pyth`](../../../basics/pyth/) does, after checking the update's
`feed_id`: `price_mantissa` is `price_message.price`, `exponent` is
`price_message.exponent`, and `last_updated_slot` is `posted_slot`. It should
also reject results whose confidence interval is too wide.

### Custody

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use anchor_spl::token_interface::Mint;
use crate::constants::PRICE_FEED_SEED;
use crate::state::{LendingMarket, PriceFeed};

/// Test stand-in for a Switchboard On-Demand feed: writes a price directly so
/// Test stand-in for an oracle price feed: writes a price directly so
/// LiteSVM tests are deterministic. In production the reserve points at a real
/// Switchboard feed instead and this handler is unused.
/// Pyth price feed instead and this handler is unused.
///
/// The feed PDA is seeded by `[b"price_feed", market, mint]` and writing it
/// requires the market's `owner` to sign, so a market's prices can only be set
Expand Down
13 changes: 7 additions & 6 deletions finance/lending/anchor/programs/lending/src/state/price_feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ use crate::math::price_mantissa_to_scaled;
/// individual), so each market prices its own assets and one market can never
/// write another's feed. Only the market's `owner` may write it (`set_price`).
///
/// The layout mirrors a Switchboard On-Demand pull feed: a signed mantissa plus
/// an exponent (`price = price_mantissa * 10^exponent`) and the slot the value
/// was written. In production this account would be the real Switchboard feed
/// and the program would decode it with the `switchboard-on-demand` crate
/// (`PullFeedAccountData`): `price_mantissa = current_result.value`,
/// `exponent = -18`, `last_updated_slot = current_result.slot`. Here the
/// The layout mirrors an oracle price feed such as Pyth's: a signed mantissa
/// plus an exponent (`price = price_mantissa * 10^exponent`) and the slot the
/// value was written. In production this account would be a Pyth
/// `PriceUpdateV2` owned by the Pyth Receiver program (`basics/pyth` reads
/// one), mapped as `price_mantissa = price_message.price`,
/// `exponent = price_message.exponent` and `last_updated_slot = posted_slot`,
/// after checking the update's `feed_id`. Here the
/// `set_price` handler writes it directly so LiteSVM tests are deterministic.
/// A production read should also reject results whose confidence interval is
/// too wide; this stand-in has no confidence field to check.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use lending::state::{Obligation, Reserve, ReserveConfig};
pub use anchor_lang::prelude::Address;

/// A FIXED_POINT_SCALE-scaled price exponent: prices are passed as
/// `mantissa * 10^-18`, matching a Switchboard On-Demand feed's 1e18 result.
/// `mantissa * 10^-18`, the same 18 decimals as `FIXED_POINT_SCALE`.
pub const PRICE_EXPONENT: i32 = -18;

pub fn dollars(whole: u64) -> i128 {
Expand Down
7 changes: 6 additions & 1 deletion finance/lending/quasar/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## [2026-09-23]

### Changed

- Documentation only: the price feed's production path now points at a Pyth
price feed, since the oracle network it was modeled on has shut down.

### Fixed

- Lock a minimum number of reserve shares. The first deposit now mints
Expand Down Expand Up @@ -113,7 +118,7 @@ Initial Quasar port of the Kamino/Solend-style borrow/lend program.
borrow-rate index, accrued inline per instruction.
- Oracle-priced health with loan-to-value and liquidation-threshold limits, and
close-factor-capped liquidation with a seize bonus.
- Switchboard-On-Demand-shaped price feed with a `set_price` test writer.
- Mantissa-and-exponent price feed with a `set_price` test writer.
- quasar-svm integration tests covering supply/redeem, borrow/repay, interest
accrual, and liquidation (including the healthy-rejection path).
- Price feed PDAs are seeded by their authority, so no signer can write or
Expand Down
6 changes: 3 additions & 3 deletions finance/lending/quasar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ Everything else mirrors the Anchor version.
- **`Obligation`**: a borrower's isolated position: the collateral reserve and
deposited share amount, plus the borrow reserve and scaled debt. PDA:
`["obligation", market, owner]`.
- **`PriceFeed`**: a Switchboard-On-Demand-shaped price (`mantissa * 10^exponent`
- **`PriceFeed`**: an oracle-shaped price (`mantissa * 10^exponent`
+ slot). PDA: `["price_feed", market, mint]`: scoped to a market, not to any
individual; only the market's `owner` may write it, so prices can't be squatted
and each market prices its own assets. `set_price` writes it directly for
deterministic tests; in production a reserve points at the real Switchboard
feed. Freshness is checked in slots.
deterministic tests; in production a reserve points at a real Pyth
price feed. Freshness is checked in slots.
- **Liquidation**: the close factor (max fraction of the debt one call repays)
comes from the borrow reserve; the bonus from the collateral reserve. A
repayment whose seizure would exceed the posted collateral fails with
Expand Down
2 changes: 1 addition & 1 deletion finance/lending/quasar/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ impl InitializeReserve {
}

// ---------------------------------------------------------------------------
// set_price (Switchboard stand-in for tests)
// set_price (oracle stand-in for tests)
// ---------------------------------------------------------------------------

#[derive(Accounts)]
Expand Down
9 changes: 5 additions & 4 deletions finance/lending/quasar/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ pub struct Obligation {
pub bump: u8,
}

/// Switchboard-On-Demand-shaped price feed. PDA: `["price_feed", market, mint]`
/// — scoped to a market (not to any individual); only the market's `owner` may
/// write it, so prices can't be squatted and each market prices its own assets.
/// Oracle-shaped price feed, a mantissa and exponent like Pyth's.
/// PDA: `["price_feed", market, mint]` — scoped to a market (not to any
/// individual); only the market's `owner` may write it, so prices can't be
/// squatted and each market prices its own assets.
/// `price = price_mantissa * 10^exponent`; freshness is checked in slots. In
/// production this account would be the real Switchboard feed.
/// production this account would be a real Pyth price feed.
#[account(discriminator = 4, set_inner)]
#[seeds(b"price_feed", market: Address, mint: Address)]
pub struct PriceFeed {
Expand Down
2 changes: 1 addition & 1 deletion finance/lending/quasar/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use {
quasar_test::prelude::*,
};

// Prices are passed as `mantissa * 10^-18` (Switchboard-shaped).
// Prices are passed as `mantissa * 10^-18`, the program's fixed-point scale.
const EXP: i32 = -18;
fn dollars(whole: u64) -> i128 {
(whole as i128) * 1_000_000_000_000_000_000
Expand Down
2 changes: 1 addition & 1 deletion finance/perpetual-futures/anchor-v1/Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ skip-lint = false

[programs.localnet]
perpetual_futures = "3uCm8Jep469pHUpYQCh6eA6dpYV3ogvTvaRDZBPtw5So"
mock_switchboard = "FnisQqhF56BxVYh5Wt8xW8wuTVN6STAGnk13MM5SRM7b"
mock_price_feed = "FnisQqhF56BxVYh5Wt8xW8wuTVN6STAGnk13MM5SRM7b"

[provider]
cluster = "localnet"
Expand Down
8 changes: 8 additions & 0 deletions finance/perpetual-futures/anchor-v1/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 2026-09-23

The mock oracle program is now `mock-price-feed` (library and program
`mock_price_feed`), with the same program ID, instructions and
account layout. The oracle network it was modeled on has shut down, so the
production path described in `state/oracle.rs` now reads a Pyth
`PriceUpdateV2` account, as `basics/pyth` does. No behavior changes.

## 2026-09-22

Accrue funding by the wall clock instead of by slots. The rate was quoted per
Expand Down
2 changes: 1 addition & 1 deletion finance/perpetual-futures/anchor-v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A [perpetual future](https://www.investopedia.com/terms/f/futurescontract.asp) (
## Programs

- `perpetual-futures`: The exchange: pool creation, liquidity provision, opening/closing leveraged positions, funding, liquidation, and fee collection.
- `mock-switchboard`: Test-only price feed. Stores a price, scale, last-update slot, and confidence band that tests write directly. Replaced by a real [Switchboard](https://docs.switchboard.xyz/) On-Demand feed in production.
- `mock-price-feed`: Test-only price feed. Stores a price, scale, last-update slot, and confidence band that tests write directly. Replaced in production by a Pyth `PriceUpdateV2` account, as read in [`basics/pyth`](../../../basics/pyth/).

All money math is integer `u128` with `checked_*` operations, multiplying before dividing and rounding in the pool's favour: no floats, no fixed-point library.

Expand Down
3 changes: 2 additions & 1 deletion finance/perpetual-futures/anchor-v1/TERMINOLOGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Terms used in this example, in the sense they carry here.
- **Liquidity-provider share** — a token representing a pro-rata claim on
assets-under-management.
- **Oracle feed** — the account the pool reads its price from. This example uses
a mock Switchboard On-Demand feed; production points at a real one.
a mock oracle price feed; production points at a real one, such as
a Pyth price feed.
- **Mark price** — the price positions are valued at. Here it is the oracle
price directly, with no separate mark/index distinction.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[package]
name = "mock_switchboard"
name = "mock_price_feed"
version = "0.1.0"
description = "Mock Switchboard On-Demand feed for testing the prop-amm program"
description = "Mock oracle price feed for testing the perpetual-futures program"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "mock_switchboard"
name = "mock_price_feed"

[features]
default = []
Expand Down
Loading
Loading