diff --git a/finance/prop-amm/anchor/CHANGELOG.md b/finance/prop-amm/anchor/CHANGELOG.md index 1d867739..fe14b7b2 100644 --- a/finance/prop-amm/anchor/CHANGELOG.md +++ b/finance/prop-amm/anchor/CHANGELOG.md @@ -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 diff --git a/finance/prop-amm/anchor/README.md b/finance/prop-amm/anchor/README.md index 66495e5b..2727af3c 100644 --- a/finance/prop-amm/anchor/README.md +++ b/finance/prop-amm/anchor/README.md @@ -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 diff --git a/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs b/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs index 6835746c..2ea29b01 100644 --- a/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs +++ b/finance/prop-amm/anchor/programs/prop-amm/tests/test_prop_amm.rs @@ -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); } // =========================================================================== @@ -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()); } @@ -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()); } @@ -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()); } @@ -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()); } @@ -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(); } diff --git a/finance/prop-amm/kani-proofs/src/lib.rs b/finance/prop-amm/kani-proofs/src/lib.rs index 655b6edd..e3e17e95 100644 --- a/finance/prop-amm/kani-proofs/src/lib.rs +++ b/finance/prop-amm/kani-proofs/src/lib.rs @@ -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] diff --git a/finance/prop-amm/quasar/CHANGELOG.md b/finance/prop-amm/quasar/CHANGELOG.md index 202c1aff..b0e2fc9c 100644 --- a/finance/prop-amm/quasar/CHANGELOG.md +++ b/finance/prop-amm/quasar/CHANGELOG.md @@ -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 diff --git a/finance/prop-amm/quasar/README.md b/finance/prop-amm/quasar/README.md index 0d3b5970..843e0c19 100644 --- a/finance/prop-amm/quasar/README.md +++ b/finance/prop-amm/quasar/README.md @@ -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. diff --git a/finance/prop-amm/quasar/src/tests.rs b/finance/prop-amm/quasar/src/tests.rs index 0b347e98..6ff613d3 100644 --- a/finance/prop-amm/quasar/src/tests.rs +++ b/finance/prop-amm/quasar/src/tests.rs @@ -367,97 +367,97 @@ fn test_initialize_market() { ); } -/// 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 env = setup(); - let quote_in = 1_651_650_000; + let quote_in = 825_825_000; let alice = env.funded_wallet(0, quote_in); - assert!(env.swap(&alice, DIRECTION_BUY_BASE, quote_in, 10 * ONE_TOKEN)); + assert!(env.swap(&alice, DIRECTION_BUY_BASE, quote_in, 5 * ONE_TOKEN)); assert_eq!( token_amount(&env.svm, &ata(&alice, &env.base_mint)), - 10 * ONE_TOKEN + 5 * ONE_TOKEN ); assert_eq!(token_amount(&env.svm, &ata(&alice, &env.quote_mint)), 0); // Conservation: the vaults moved by exactly the two legs of the fill. - assert_eq!(token_amount(&env.svm, &env.base_vault), 990 * ONE_TOKEN); + assert_eq!(token_amount(&env.svm, &env.base_vault), 995 * ONE_TOKEN); assert_eq!( token_amount(&env.svm, &env.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 env = setup(); - let bob = env.funded_wallet(10 * ONE_TOKEN, 0); + let bob = env.funded_wallet(5 * ONE_TOKEN, 0); - assert!(env.swap(&bob, DIRECTION_SELL_BASE, 10 * ONE_TOKEN, 1_648_350_000)); + assert!(env.swap(&bob, DIRECTION_SELL_BASE, 5 * ONE_TOKEN, 824_175_000)); assert_eq!(token_amount(&env.svm, &ata(&bob, &env.base_mint)), 0); assert_eq!( token_amount(&env.svm, &ata(&bob, &env.quote_mint)), - 1_648_350_000 + 824_175_000 ); } -/// A buy immediately followed by a sell of the same 10 NVDAx costs exactly -/// the round-trip spread: 3.30 USDC, all of which stays in the inventory. +/// A buy immediately followed by a sell of the same 5 NVDAx costs exactly +/// the round-trip spread: 1.65 USDC, all of which stays in the inventory. #[test] fn test_round_trip_costs_exactly_the_spread() { let mut env = setup(); - let quote_in = 1_651_650_000; + let quote_in = 825_825_000; let carol = env.funded_wallet(0, quote_in); assert!(env.swap(&carol, DIRECTION_BUY_BASE, quote_in, 0)); - assert!(env.swap(&carol, DIRECTION_SELL_BASE, 10 * ONE_TOKEN, 0)); + assert!(env.swap(&carol, DIRECTION_SELL_BASE, 5 * ONE_TOKEN, 0)); assert_eq!(token_amount(&env.svm, &ata(&carol, &env.base_mint)), 0); assert_eq!( token_amount(&env.svm, &ata(&carol, &env.quote_mint)), - quote_in - 3_300_000 + quote_in - 1_650_000 ); assert_eq!( token_amount(&env.svm, &env.quote_vault), - 200_000 * ONE_TOKEN + 3_300_000 + 200_000 * ONE_TOKEN + 1_650_000 ); } /// When the oracle reprices, the quote follows instantly. At $170 the ask is -/// $170.17, so 10 NVDAx costs exactly 1,701.70 USDC. +/// $170.17, so 5 NVDAx costs exactly 850.85 USDC. #[test] fn test_quote_follows_the_oracle() { let mut env = setup(); env.set_price(dollars(170)); - let quote_in = 1_701_700_000; + let quote_in = 850_850_000; let alice = env.funded_wallet(0, quote_in); - assert!(env.swap(&alice, DIRECTION_BUY_BASE, quote_in, 10 * ONE_TOKEN)); + assert!(env.swap(&alice, DIRECTION_BUY_BASE, quote_in, 5 * ONE_TOKEN)); assert_eq!( token_amount(&env.svm, &ata(&alice, &env.base_mint)), - 10 * ONE_TOKEN + 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 env = setup(); let operator = env.operator; assert!(env.set_quote(&operator, 50, 0)); - let quote_in = 1_658_250_000; + let quote_in = 829_125_000; let alice = env.funded_wallet(0, quote_in); - assert!(env.swap(&alice, DIRECTION_BUY_BASE, quote_in, 10 * ONE_TOKEN)); + assert!(env.swap(&alice, DIRECTION_BUY_BASE, quote_in, 5 * ONE_TOKEN)); assert_eq!( token_amount(&env.svm, &ata(&alice, &env.base_mint)), - 10 * ONE_TOKEN + 5 * ONE_TOKEN ); } @@ -471,8 +471,8 @@ fn test_operator_can_withdraw_everything_and_swaps_then_fail() { assert_eq!(token_amount(&env.svm, &env.base_vault), 0); assert_eq!(token_amount(&env.svm, &env.quote_vault), 0); - let alice = env.funded_wallet(0, 1_651_650_000); - assert!(!env.swap(&alice, DIRECTION_BUY_BASE, 1_651_650_000, 0)); + let alice = env.funded_wallet(0, 825_825_000); + assert!(!env.swap(&alice, DIRECTION_BUY_BASE, 825_825_000, 0)); } #[test] @@ -506,10 +506,10 @@ fn test_set_quote_rejects_non_operator() { #[test] fn test_swap_rejects_slippage() { let mut env = setup(); - let quote_in = 1_651_650_000; + let quote_in = 825_825_000; let alice = env.funded_wallet(0, quote_in); - // The fill would be exactly 10 NVDAx; demand one minor unit more. - assert!(!env.swap(&alice, DIRECTION_BUY_BASE, quote_in, 10 * ONE_TOKEN + 1)); + // The fill would be exactly 5 NVDAx; demand one minor unit more. + assert!(!env.swap(&alice, DIRECTION_BUY_BASE, quote_in, 5 * ONE_TOKEN + 1)); } /// An oracle price older than the staleness bound cannot be traded against: @@ -518,8 +518,8 @@ fn test_swap_rejects_slippage() { fn test_swap_rejects_stale_price() { let mut env = setup(); env.make_price_stale(); - let alice = env.funded_wallet(0, 1_651_650_000); - assert!(!env.swap(&alice, DIRECTION_BUY_BASE, 1_651_650_000, 0)); + let alice = env.funded_wallet(0, 825_825_000); + assert!(!env.swap(&alice, DIRECTION_BUY_BASE, 825_825_000, 0)); } /// A price the oracle itself is unsure about is rejected: the confidence band @@ -528,8 +528,8 @@ fn test_swap_rejects_stale_price() { fn test_swap_rejects_wide_confidence() { let mut env = setup(); env.set_price_with_confidence(dollars(165), 200_000_000); - let alice = env.funded_wallet(0, 1_651_650_000); - assert!(!env.swap(&alice, DIRECTION_BUY_BASE, 1_651_650_000, 0)); + let alice = env.funded_wallet(0, 825_825_000); + assert!(!env.swap(&alice, DIRECTION_BUY_BASE, 825_825_000, 0)); } /// While the operator has pulled its quotes, nobody can swap; unpausing @@ -540,11 +540,11 @@ fn test_swap_rejects_when_paused() { let operator = env.operator; assert!(env.set_quote(&operator, SPREAD_BPS, 1)); - let alice = env.funded_wallet(0, 1_651_650_000); - assert!(!env.swap(&alice, DIRECTION_BUY_BASE, 1_651_650_000, 0)); + let alice = env.funded_wallet(0, 825_825_000); + assert!(!env.swap(&alice, DIRECTION_BUY_BASE, 825_825_000, 0)); assert!(env.set_quote(&operator, SPREAD_BPS, 0)); - assert!(env.swap(&alice, DIRECTION_BUY_BASE, 1_651_650_000, 10 * ONE_TOKEN)); + assert!(env.swap(&alice, DIRECTION_BUY_BASE, 825_825_000, 5 * ONE_TOKEN)); } #[test]