Skip to content
Open
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
7 changes: 7 additions & 0 deletions finance/prop-amm/anchor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 2026-07-11 (later)

Retuned the walkthrough trade to 5 NVDAx (825.825 USDC at the ask,
824.175 back at the bid, 1.65 round-trip spread) so the numbers match the
book's convention that every character starts with 1,000 USDC. Same math,
same gates; only the amounts changed.

## 2026-07-11

Initial version: an oracle-quoted proprietary AMM. One operator funds the
Expand Down
10 changes: 5 additions & 5 deletions finance/prop-amm/anchor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,17 @@ the deployment is the firm.
tokens into the vaults. No shares are minted to anyone, because there is
nobody else to account for.

### Step 3: Alice buys 10 NVDAx at the ask
### Step 3: Alice buys 5 NVDAx at the ask

At $165 with a 10 bps spread the ask is $165.165. Alice's `swap`
(`Direction::BuyBase`) spends exactly 1,651.65 USDC for 10 NVDAx —
(`Direction::BuyBase`) spends exactly 825.825 USDC for 5 NVDAx —
whether she bought 1 or 500, the unit price would be the same.

### Step 4: Bob sells 10 NVDAx at the bid
### Step 4: Bob sells 5 NVDAx at the bid

The bid is $164.835, so Bob's `swap` (`Direction::SellBase`) receives
exactly 1,648.35 USDC. A round trip through both sides costs exactly the
3.30 USDC spread — the spread is the fee, and it lands in the inventory,
exactly 824.175 USDC. A round trip through both sides costs exactly the
1.65 USDC spread — the spread is the fee, and it lands in the inventory,
not in a fee ledger.

### Step 5: The oracle reprices; the quote follows
Expand Down
82 changes: 41 additions & 41 deletions finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,106 +443,106 @@ impl Market {
// Happy paths: exact quote math in both directions
// ===========================================================================

/// Alice buys 10 NVDAx. At $165 with a 10 bps spread the ask is $165.165, so
/// 10 NVDAx costs exactly 1,651.65 USDC.
/// Alice buys 5 NVDAx. At $165 with a 10 bps spread the ask is $165.165, so
/// 5 NVDAx costs exactly 825.825 USDC.
#[test]
fn test_swap_buys_base_at_the_ask() {
let mut market = Market::default_market();
let quote_in = 1_651_650_000; // 1,651.65 USDC
let quote_in = 825_825_000; // 825.825 USDC
let (alice, alice_base, alice_quote) = market.funded_trader(0, quote_in);

market
.swap(&alice, Direction::BuyBase, quote_in, 10 * ONE_TOKEN)
.swap(&alice, Direction::BuyBase, quote_in, 5 * ONE_TOKEN)
.unwrap();

assert_eq!(market.balance(&alice_base), 10 * ONE_TOKEN);
assert_eq!(market.balance(&alice_base), 5 * ONE_TOKEN);
assert_eq!(market.balance(&alice_quote), 0);
// Conservation: the vaults moved by exactly the two legs of the fill.
assert_eq!(market.balance(&market.base_vault), 990 * ONE_TOKEN);
assert_eq!(market.balance(&market.base_vault), 995 * ONE_TOKEN);
assert_eq!(
market.balance(&market.quote_vault),
200_000 * ONE_TOKEN + quote_in
);
}

/// Bob sells 10 NVDAx. At $165 with a 10 bps spread the bid is $164.835, so
/// he receives exactly 1,648.35 USDC.
/// Bob sells 5 NVDAx. At $165 with a 10 bps spread the bid is $164.835, so
/// he receives exactly 824.175 USDC.
#[test]
fn test_swap_sells_base_at_the_bid() {
let mut market = Market::default_market();
let (bob, bob_base, bob_quote) = market.funded_trader(10 * ONE_TOKEN, 0);
let (bob, bob_base, bob_quote) = market.funded_trader(5 * ONE_TOKEN, 0);

market
.swap(&bob, Direction::SellBase, 10 * ONE_TOKEN, 1_648_350_000)
.swap(&bob, Direction::SellBase, 5 * ONE_TOKEN, 824_175_000)
.unwrap();

assert_eq!(market.balance(&bob_base), 0);
assert_eq!(market.balance(&bob_quote), 1_648_350_000); // 1,648.35 USDC
assert_eq!(market.balance(&market.base_vault), 1_010 * ONE_TOKEN);
assert_eq!(market.balance(&bob_quote), 824_175_000); // 824.175 USDC
assert_eq!(market.balance(&market.base_vault), 1_005 * ONE_TOKEN);
assert_eq!(
market.balance(&market.quote_vault),
200_000 * ONE_TOKEN - 1_648_350_000
200_000 * ONE_TOKEN - 824_175_000
);
}

/// A buy immediately followed by a sell of the same 10 NVDAx costs exactly the
/// round-trip spread: 3.30 USDC on a $1,650 position, all of which stays in
/// A buy immediately followed by a sell of the same 5 NVDAx costs exactly the
/// round-trip spread: 1.65 USDC on an $825 position, all of which stays in
/// the market's inventory. The spread IS the fee; there is no other one.
#[test]
fn test_round_trip_costs_exactly_the_spread() {
let mut market = Market::default_market();
let quote_in = 1_651_650_000;
let quote_in = 825_825_000;
let (carol, carol_base, carol_quote) = market.funded_trader(0, quote_in);

market
.swap(&carol, Direction::BuyBase, quote_in, 0)
.unwrap();
market
.swap(&carol, Direction::SellBase, 10 * ONE_TOKEN, 0)
.swap(&carol, Direction::SellBase, 5 * ONE_TOKEN, 0)
.unwrap();

assert_eq!(market.balance(&carol_base), 0);
// 1,651.65 in, 1,648.35 back: the market kept 3.30 USDC.
assert_eq!(market.balance(&carol_quote), quote_in - 3_300_000);
// 825.825 in, 824.175 back: the market kept 1.65 USDC.
assert_eq!(market.balance(&carol_quote), quote_in - 1_650_000);
assert_eq!(market.balance(&market.base_vault), 1_000 * ONE_TOKEN);
assert_eq!(
market.balance(&market.quote_vault),
200_000 * ONE_TOKEN + 3_300_000
200_000 * ONE_TOKEN + 1_650_000
);
}

/// When the oracle reprices, the quote follows instantly — no trade has to
/// drag the price there through a curve. At $170 the ask is $170.17, so 10
/// NVDAx costs exactly 1,701.70 USDC.
/// drag the price there through a curve. At $170 the ask is $170.17, so 5
/// NVDAx costs exactly 850.85 USDC.
#[test]
fn test_quote_follows_the_oracle() {
let mut market = Market::default_market();
market.set_price(dollars(170));

let quote_in = 1_701_700_000; // 1,701.70 USDC
let quote_in = 850_850_000; // 850.85 USDC
let (alice, alice_base, _) = market.funded_trader(0, quote_in);
market
.swap(&alice, Direction::BuyBase, quote_in, 10 * ONE_TOKEN)
.swap(&alice, Direction::BuyBase, quote_in, 5 * ONE_TOKEN)
.unwrap();

assert_eq!(market.balance(&alice_base), 10 * ONE_TOKEN);
assert_eq!(market.balance(&alice_base), 5 * ONE_TOKEN);
}

/// The operator re-quotes to a 50 bps spread; the next fill prices at
/// $165.825, so 10 NVDAx costs exactly 1,658.25 USDC.
/// $165.825, so 5 NVDAx costs exactly 829.125 USDC.
#[test]
fn test_set_quote_changes_the_spread() {
let mut market = Market::default_market();
market.set_quote(50, false).unwrap();
assert_eq!(market.market_state().spread_bps, 50);

let quote_in = 1_658_250_000; // 1,658.25 USDC
let quote_in = 829_125_000; // 829.125 USDC
let (alice, alice_base, _) = market.funded_trader(0, quote_in);
market
.swap(&alice, Direction::BuyBase, quote_in, 10 * ONE_TOKEN)
.swap(&alice, Direction::BuyBase, quote_in, 5 * ONE_TOKEN)
.unwrap();

assert_eq!(market.balance(&alice_base), 10 * ONE_TOKEN);
assert_eq!(market.balance(&alice_base), 5 * ONE_TOKEN);
}

// ===========================================================================
Expand All @@ -566,9 +566,9 @@ fn test_operator_can_withdraw_everything_and_swaps_then_fail() {
assert_eq!(market.balance(&operator_base), 10_000 * ONE_TOKEN);
assert_eq!(market.balance(&operator_quote), 10_000_000 * ONE_TOKEN);

let (alice, _, _) = market.funded_trader(0, 1_651_650_000);
let (alice, _, _) = market.funded_trader(0, 825_825_000);
assert!(market
.swap(&alice, Direction::BuyBase, 1_651_650_000, 0)
.swap(&alice, Direction::BuyBase, 825_825_000, 0)
.is_err());
}

Expand Down Expand Up @@ -613,11 +613,11 @@ fn test_set_quote_rejects_non_operator() {
#[test]
fn test_swap_rejects_slippage() {
let mut market = Market::default_market();
let quote_in = 1_651_650_000;
let quote_in = 825_825_000;
let (alice, _, _) = market.funded_trader(0, quote_in);
// The fill would be exactly 10 NVDAx; demand one minor unit more.
// The fill would be exactly 5 NVDAx; demand one minor unit more.
assert!(market
.swap(&alice, Direction::BuyBase, quote_in, 10 * ONE_TOKEN + 1)
.swap(&alice, Direction::BuyBase, quote_in, 5 * ONE_TOKEN + 1)
.is_err());
}

Expand All @@ -627,12 +627,12 @@ fn test_swap_rejects_slippage() {
#[test]
fn test_swap_rejects_stale_price() {
let mut market = Market::default_market();
let (alice, _, _) = market.funded_trader(0, 1_651_650_000);
let (alice, _, _) = market.funded_trader(0, 825_825_000);
// The feed was last updated near slot 0; 200 slots later it is stale
// (the bound is 150 slots).
market.warp(200);
assert!(market
.swap(&alice, Direction::BuyBase, 1_651_650_000, 0)
.swap(&alice, Direction::BuyBase, 825_825_000, 0)
.is_err());
}

Expand All @@ -642,9 +642,9 @@ fn test_swap_rejects_stale_price() {
fn test_swap_rejects_wide_confidence() {
let mut market = Market::default_market();
market.set_price_with_confidence(dollars(165), 200_000_000);
let (alice, _, _) = market.funded_trader(0, 1_651_650_000);
let (alice, _, _) = market.funded_trader(0, 825_825_000);
assert!(market
.swap(&alice, Direction::BuyBase, 1_651_650_000, 0)
.swap(&alice, Direction::BuyBase, 825_825_000, 0)
.is_err());
}

Expand All @@ -653,15 +653,15 @@ fn test_swap_rejects_wide_confidence() {
fn test_swap_rejects_when_paused() {
let mut market = Market::default_market();
market.set_quote(SPREAD_BPS, true).unwrap();
let (alice, _, _) = market.funded_trader(0, 1_651_650_000);
let (alice, _, _) = market.funded_trader(0, 825_825_000);
assert!(market
.swap(&alice, Direction::BuyBase, 1_651_650_000, 0)
.swap(&alice, Direction::BuyBase, 825_825_000, 0)
.is_err());

// Unpausing restores the exact same quote.
market.set_quote(SPREAD_BPS, false).unwrap();
market
.swap(&alice, Direction::BuyBase, 1_651_650_000, 10 * ONE_TOKEN)
.swap(&alice, Direction::BuyBase, 825_825_000, 5 * ONE_TOKEN)
.unwrap();
}

Expand Down
22 changes: 11 additions & 11 deletions finance/prop-amm/kani-proofs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,33 +245,33 @@ mod tests {
}

#[test]
fn buy_ten_nvdax() {
// 1,651.65 USDC buys exactly 10 NVDAx at the ask.
fn buy_five_nvdax() {
// 825.825 USDC buys exactly 5 NVDAx at the ask.
let ask = ask_price(PRICE, 10).unwrap();
assert_eq!(
base_out_for_quote_in(1_651_650_000, ask, SCALE, DEC, DEC).unwrap(),
10_000_000
base_out_for_quote_in(825_825_000, ask, SCALE, DEC, DEC).unwrap(),
5_000_000
);
}

#[test]
fn sell_ten_nvdax() {
// 10 NVDAx sells for exactly 1,648.35 USDC at the bid.
fn sell_five_nvdax() {
// 5 NVDAx sells for exactly 824.175 USDC at the bid.
let bid = bid_price(PRICE, 10).unwrap();
assert_eq!(
quote_out_for_base_in(10_000_000, bid, SCALE, DEC, DEC).unwrap(),
1_648_350_000
quote_out_for_base_in(5_000_000, bid, SCALE, DEC, DEC).unwrap(),
824_175_000
);
}

#[test]
fn round_trip_costs_the_spread() {
// In 1,651.65, back 1,648.35: the market keeps exactly 3.30 USDC.
// In 825.825, back 824.175: the market keeps exactly 1.65 USDC.
let ask = ask_price(PRICE, 10).unwrap();
let bid = bid_price(PRICE, 10).unwrap();
let base = base_out_for_quote_in(1_651_650_000, ask, SCALE, DEC, DEC).unwrap();
let base = base_out_for_quote_in(825_825_000, ask, SCALE, DEC, DEC).unwrap();
let back = quote_out_for_base_in(base, bid, SCALE, DEC, DEC).unwrap();
assert_eq!(1_651_650_000 - back, 3_300_000);
assert_eq!(825_825_000 - back, 1_650_000);
}

#[test]
Expand Down
7 changes: 7 additions & 0 deletions finance/prop-amm/quasar/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 2026-07-11 (later)

Retuned the walkthrough trade to 5 NVDAx (825.825 USDC at the ask,
824.175 back at the bid, 1.65 round-trip spread) so the numbers match the
book's convention that every character starts with 1,000 USDC. Same math,
same gates; only the amounts changed.

## 2026-07-11

Initial version: Quasar port of the prop-amm example, matching the Anchor
Expand Down
2 changes: 1 addition & 1 deletion finance/prop-amm/quasar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Quasar version.
Tests run in-process with [`quasar-svm`](https://github.com/blueshift-gg/quasar-svm).
They build the program, set up both mints, an oracle feed at $165, and an
operator with funded inventory, then verify the quote math to the minor unit
in both directions, the exact 3.30 USDC round-trip spread, oracle repricing
in both directions, the exact 1.65 USDC round-trip spread, oracle repricing
and re-quoting, the operator's full exit, and that every gate shuts: slippage,
staleness, confidence, pause, zero amounts, inventory bounds, and operator
access control.
Expand Down
Loading
Loading