mirror of
https://github.com/d0zingcat/ore.git
synced 2026-06-06 07:36:48 +00:00
market
This commit is contained in:
375
api/src/state/market/market.rs
Normal file
375
api/src/state/market/market.rs
Normal file
@@ -0,0 +1,375 @@
|
||||
use spl_associated_token_account::get_associated_token_address;
|
||||
use steel::*;
|
||||
|
||||
use crate::state::{market_pda, OreAccount};
|
||||
|
||||
// TODO Bonding curve stuff
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
|
||||
pub struct Market {
|
||||
/// Base token parameters.
|
||||
pub base: TokenParams,
|
||||
|
||||
/// Quote token parameters.
|
||||
pub quote: TokenParams,
|
||||
|
||||
/// Fee parameters.
|
||||
pub fee: FeeParams,
|
||||
|
||||
/// Snapshot of the market state at the time of the last swap.
|
||||
pub snapshot: Snapshot,
|
||||
|
||||
/// The id of the block this market is associated with.
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
impl Market {
|
||||
pub fn pda(&self) -> (Pubkey, u8) {
|
||||
market_pda(self.id)
|
||||
}
|
||||
|
||||
pub fn base_vault(&self) -> Pubkey {
|
||||
get_associated_token_address(&self.pda().0, &self.base.mint)
|
||||
}
|
||||
|
||||
pub fn quote_vault(&self) -> Pubkey {
|
||||
get_associated_token_address(&self.pda().0, &self.quote.mint)
|
||||
}
|
||||
|
||||
pub fn sandwich_resistance_enabled(&self) -> bool {
|
||||
self.snapshot.enabled == 1
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
|
||||
pub struct TokenParams {
|
||||
/// Mint of the token.
|
||||
pub mint: Pubkey,
|
||||
|
||||
/// Amount of token held in liquidity.
|
||||
pub balance: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
|
||||
pub struct FeeParams {
|
||||
/// Cumulative protocol fees.
|
||||
pub cumulative: u64,
|
||||
|
||||
/// Fee rate in basis points.
|
||||
pub rate: u64,
|
||||
|
||||
/// Current uncollected protocol fees.
|
||||
pub uncollected: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
|
||||
pub struct Snapshot {
|
||||
/// Whether sandwich resistance is enabled.
|
||||
pub enabled: u64,
|
||||
|
||||
/// Base token balance at the time of the snapshot.
|
||||
pub base_balance: u64,
|
||||
|
||||
/// Quote token balance at the time of the snapshot.
|
||||
pub quote_balance: u64,
|
||||
|
||||
/// Slot at which the snapshot was taken.
|
||||
pub slot: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
|
||||
pub struct VirtualLimitOrder {
|
||||
/// Size of the virtual limit order in base tokens.
|
||||
pub size_in_base: u128,
|
||||
|
||||
/// Size of the virtual limit order in quote tokens.
|
||||
pub size_in_quote: u128,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
|
||||
pub enum SwapDirection {
|
||||
/// Swap quote tokens for base tokens.
|
||||
Buy = 0,
|
||||
|
||||
/// Swap base tokens for quote tokens.
|
||||
Sell = 1,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
|
||||
pub enum SwapPrecision {
|
||||
/// Swap with precision exact in amount.
|
||||
ExactIn = 0,
|
||||
|
||||
/// Swap with precision exact out amount.
|
||||
ExactOut = 1,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
|
||||
pub enum TokenType {
|
||||
/// Base token.
|
||||
Base = 0,
|
||||
|
||||
/// Quote token.
|
||||
Quote = 1,
|
||||
}
|
||||
|
||||
account!(OreAccount, Market);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::consts::FEE_RATE_BPS;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_fees_buy_exact_in() {
|
||||
let mut market = new_market();
|
||||
let swap = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(swap.quote_via_curve, 99_000);
|
||||
assert_eq!(market.fee.uncollected, 1000); // Protocol fee is 1%
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fees_sell_exact_in() {
|
||||
let mut market = new_market();
|
||||
let swap = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(swap.quote_via_curve, 98_991);
|
||||
assert_eq!(market.fee.uncollected, 999);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fees_buy_exact_out() {
|
||||
let mut market = new_market();
|
||||
let swap = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactOut,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(swap.quote_via_curve, 100_011);
|
||||
assert_eq!(market.fee.uncollected, 1010);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fees_sell_exact_out() {
|
||||
let mut market = new_market();
|
||||
let swap = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactOut,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(swap.quote_via_curve, 101_010);
|
||||
assert_eq!(market.fee.uncollected, 1010);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fills() {
|
||||
let mut market = new_market();
|
||||
|
||||
// Small buy
|
||||
// Assert swap is filled via curve.
|
||||
// Post swap, price is above snapshot.
|
||||
let swap_1 = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_1.base_via_curve > 0 && swap_1.quote_via_curve > 0);
|
||||
assert!(swap_1.base_via_order == 0 && swap_1.quote_via_order == 0);
|
||||
|
||||
// Large sell
|
||||
// Assert swap is partially filled via order and partially filled via curve.
|
||||
// Post swap, price is below snapshot.
|
||||
let swap_2 = market
|
||||
.swap(
|
||||
1_000_000,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0);
|
||||
assert!(swap_2.base_via_order > 0 && swap_2.quote_via_order > 0);
|
||||
|
||||
// Small buy
|
||||
// Assert swap is filled via order
|
||||
// Post swap, price is still below snapshot.
|
||||
let swap_3 = market
|
||||
.swap(
|
||||
1_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_3.base_via_curve == 0 && swap_3.quote_via_curve == 0);
|
||||
assert!(swap_3.base_via_order > 0 && swap_3.quote_via_order > 0);
|
||||
|
||||
// Large buy
|
||||
// Assert swap is partially filled via order and partially filled via curve.
|
||||
// Post swap, price is above snapshot.
|
||||
let swap_4 = market
|
||||
.swap(
|
||||
1_000_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_4.base_via_curve > 0 && swap_4.quote_via_curve > 0);
|
||||
assert!(swap_4.base_via_order > 0 && swap_4.quote_via_order > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sandwich() {
|
||||
let mut market = new_market();
|
||||
market.fee.rate = 0;
|
||||
market.snapshot = Snapshot {
|
||||
enabled: 0,
|
||||
base_balance: 0,
|
||||
quote_balance: 0,
|
||||
slot: 0,
|
||||
};
|
||||
|
||||
// Open sandwich
|
||||
let swap_1 = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let amount_base_1 = swap_1.base_to_transfer;
|
||||
assert!(swap_1.base_via_curve > 0 && swap_1.quote_via_curve > 0);
|
||||
assert!(swap_1.base_via_order == 0 && swap_1.quote_via_order == 0);
|
||||
|
||||
// Victim buys
|
||||
let swap_2 = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0);
|
||||
assert!(swap_2.base_via_order == 0 && swap_2.quote_via_order == 0);
|
||||
|
||||
// Close sandwich
|
||||
let swap_3 = market
|
||||
.swap(
|
||||
amount_base_1,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_3.base_via_curve > 0 && swap_3.quote_via_curve > 0);
|
||||
assert!(swap_3.base_via_order == 0 && swap_3.quote_via_order == 0);
|
||||
|
||||
// Assert sandwich attack succeeded
|
||||
assert!(swap_3.quote_to_transfer > swap_1.quote_to_transfer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sandwich_resistance() {
|
||||
let mut market = new_market();
|
||||
market.fee.rate = 0;
|
||||
|
||||
// Open sandwich
|
||||
let swap_1 = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let amount_base_1 = swap_1.base_to_transfer;
|
||||
assert!(swap_1.base_via_curve > 0 && swap_1.quote_via_curve > 0);
|
||||
assert!(swap_1.base_via_order == 0 && swap_1.quote_via_order == 0);
|
||||
|
||||
// Victim buys
|
||||
let swap_2 = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0);
|
||||
assert!(swap_2.base_via_order == 0 && swap_2.quote_via_order == 0);
|
||||
|
||||
// Close sandwich
|
||||
let swap_3 = market
|
||||
.swap(
|
||||
amount_base_1,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_3.base_via_curve == 0 && swap_3.quote_via_curve == 0);
|
||||
assert!(swap_3.base_via_order > 0 && swap_3.quote_via_order > 0);
|
||||
|
||||
// Assert sandwich attack failed
|
||||
assert!(swap_3.quote_to_transfer <= swap_1.quote_to_transfer);
|
||||
}
|
||||
|
||||
fn new_market() -> Market {
|
||||
Market {
|
||||
base: TokenParams {
|
||||
mint: Pubkey::new_unique(),
|
||||
balance: 1_000_000_000,
|
||||
},
|
||||
quote: TokenParams {
|
||||
mint: Pubkey::new_unique(),
|
||||
balance: 1_000_000_000,
|
||||
},
|
||||
fee: FeeParams {
|
||||
cumulative: 0,
|
||||
uncollected: 0,
|
||||
rate: FEE_RATE_BPS,
|
||||
},
|
||||
snapshot: Snapshot {
|
||||
enabled: 1,
|
||||
base_balance: 1_000_000_000,
|
||||
quote_balance: 1_000_000_000,
|
||||
slot: 0,
|
||||
},
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user