mirror of
https://github.com/d0zingcat/ore.git
synced 2026-05-14 07:26:51 +00:00
virtual liquidity
This commit is contained in:
@@ -24,7 +24,7 @@ impl Market {
|
||||
// Fill entire swap via curve.
|
||||
let quote_via_curve = quote_in_post_fee;
|
||||
let base_via_curve = self.get_base_out(quote_via_curve);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy)?;
|
||||
(0, 0, base_via_curve, quote_via_curve)
|
||||
} else if ask_size_in_quote >= quote_in_post_fee {
|
||||
// Fill entire swap via virtual limit order.
|
||||
@@ -34,18 +34,18 @@ impl Market {
|
||||
SwapDirection::Buy,
|
||||
TokenType::Quote,
|
||||
);
|
||||
self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy);
|
||||
self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy)?;
|
||||
(base_via_ask, quote_via_ask, 0, 0)
|
||||
} else {
|
||||
// Partially fill swap via virtual limit order.
|
||||
let base_via_ask = ask_size_in_base;
|
||||
let quote_via_ask = ask_size_in_quote;
|
||||
self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy);
|
||||
self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy)?;
|
||||
|
||||
// Fill remaining swap amount via curve.
|
||||
let quote_via_curve = quote_in_post_fee - ask_size_in_quote;
|
||||
let base_via_curve = self.get_base_out(quote_via_curve);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy)?;
|
||||
(base_via_ask, quote_via_ask, base_via_curve, quote_via_curve)
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ impl Market {
|
||||
// Fill entire swap via curve.
|
||||
let base_via_curve = base_out;
|
||||
let quote_via_curve = self.get_quote_in(base_via_curve)?;
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy)?;
|
||||
(0, 0, base_via_curve, quote_via_curve)
|
||||
} else if ask_size_in_base >= base_out {
|
||||
// Fill entire swap through virtual limit order.
|
||||
@@ -35,18 +35,18 @@ impl Market {
|
||||
SwapDirection::Buy,
|
||||
TokenType::Base,
|
||||
);
|
||||
self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy);
|
||||
self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy)?;
|
||||
(base_via_ask, quote_via_ask, 0, 0)
|
||||
} else {
|
||||
// Partially fill swap through virtual limit order.
|
||||
let base_via_ask = ask_size_in_base;
|
||||
let quote_via_ask = ask_size_in_quote;
|
||||
self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy);
|
||||
self.update_reserves(base_via_ask, quote_via_ask, SwapDirection::Buy)?;
|
||||
|
||||
// Fill remaining swap amount through pool.
|
||||
let base_via_curve = base_out - base_via_ask;
|
||||
let quote_via_curve = self.get_quote_in(base_via_curve)?;
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Buy)?;
|
||||
(base_via_ask, quote_via_ask, base_via_curve, quote_via_curve)
|
||||
};
|
||||
|
||||
|
||||
@@ -7,46 +7,40 @@ use super::Market;
|
||||
impl Market {
|
||||
/// Returns the constant product invariant.
|
||||
pub(crate) fn k(&self) -> u128 {
|
||||
let base_reserves = self.base.balance as u128;
|
||||
let quote_reserves = self.quote.balance as u128;
|
||||
(base_reserves * quote_reserves).saturating_sub(1)
|
||||
(self.base.reserves() * self.quote.reserves()).saturating_sub(1)
|
||||
}
|
||||
|
||||
/// Returns the amount of base tokens that can be bought from a given amount of quote tokens.
|
||||
pub fn get_base_out(&self, quote_in: u128) -> u128 {
|
||||
let base_reserves = self.base.balance as u128;
|
||||
let quote_reserves = self.quote.balance as u128;
|
||||
let base_out = base_reserves - (self.k() / (quote_reserves + quote_in)).saturating_add(1);
|
||||
let base_out = self.base.reserves()
|
||||
- (self.k() / (self.quote.reserves() + quote_in)).saturating_add(1);
|
||||
base_out
|
||||
}
|
||||
|
||||
/// Returns the amount of quote tokens received from selling a given amount of base tokens.
|
||||
pub fn get_quote_out(&self, base_in: u128) -> u128 {
|
||||
let base_reserves = self.base.balance as u128;
|
||||
let quote_reserves = self.quote.balance as u128;
|
||||
let quote_out = quote_reserves - (self.k() / (base_reserves + base_in)).saturating_add(1);
|
||||
let quote_out =
|
||||
self.quote.reserves() - (self.k() / (self.base.reserves() + base_in)).saturating_add(1);
|
||||
quote_out
|
||||
}
|
||||
|
||||
/// Returns the amount of quote tokens needed to buy a given amount of base tokens.
|
||||
pub fn get_quote_in(&self, base_out: u128) -> Result<u128, OreError> {
|
||||
let base_reserves = self.base.balance as u128;
|
||||
let quote_reserves = self.quote.balance as u128;
|
||||
if base_out >= base_reserves {
|
||||
if base_out >= self.base.reserves() {
|
||||
return Err(OreError::InsufficientVaultReserves.into());
|
||||
}
|
||||
let quote_in = (self.k() / (base_reserves - base_out)).saturating_add(1) - quote_reserves;
|
||||
let quote_in = (self.k() / (self.base.reserves() - base_out)).saturating_add(1)
|
||||
- self.quote.reserves();
|
||||
Ok(quote_in)
|
||||
}
|
||||
|
||||
/// Returns the amount of base tokens which must be sold to receive a given amount of quote tokens.
|
||||
pub fn get_base_in(&self, quote_out: u128) -> Result<u128, OreError> {
|
||||
let base_reserves = self.base.balance as u128;
|
||||
let quote_reserves = self.quote.balance as u128;
|
||||
if quote_out >= quote_reserves {
|
||||
if quote_out >= self.quote.reserves() {
|
||||
return Err(OreError::InsufficientVaultReserves.into());
|
||||
}
|
||||
let base_in = (self.k() / (quote_reserves - quote_out)).saturating_add(1) - base_reserves;
|
||||
let base_in = (self.k() / (self.quote.reserves() - quote_out)).saturating_add(1)
|
||||
- self.base.reserves();
|
||||
Ok(base_in)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,17 @@ pub struct TokenParams {
|
||||
/// Mint of the token.
|
||||
pub mint: Pubkey,
|
||||
|
||||
/// Amount of token held in liquidity.
|
||||
/// Amount of tokens held in liquidity.
|
||||
pub balance: u64,
|
||||
|
||||
/// Amount of virtual tokens held in liquidity.
|
||||
pub balance_virtual: u64,
|
||||
}
|
||||
|
||||
impl TokenParams {
|
||||
pub fn reserves(&self) -> u128 {
|
||||
(self.balance + self.balance_virtual) as u128
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@@ -192,6 +201,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_fills() {
|
||||
let mut market = new_market();
|
||||
let mut clock = Clock::default();
|
||||
clock.slot = 10;
|
||||
|
||||
// Small buy
|
||||
// Assert swap is filled via curve.
|
||||
@@ -201,7 +212,7 @@ mod tests {
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_1.base_via_curve > 0 && swap_1.quote_via_curve > 0);
|
||||
@@ -215,7 +226,7 @@ mod tests {
|
||||
1_000_000,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0);
|
||||
@@ -229,7 +240,7 @@ mod tests {
|
||||
1_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_3.base_via_curve == 0 && swap_3.quote_via_curve == 0);
|
||||
@@ -243,7 +254,7 @@ mod tests {
|
||||
1_000_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_4.base_via_curve > 0 && swap_4.quote_via_curve > 0);
|
||||
@@ -261,13 +272,16 @@ mod tests {
|
||||
slot: 0,
|
||||
};
|
||||
|
||||
let mut clock = Clock::default();
|
||||
clock.slot = 10;
|
||||
|
||||
// Open sandwich
|
||||
let swap_1 = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let amount_base_1 = swap_1.base_to_transfer;
|
||||
@@ -280,7 +294,7 @@ mod tests {
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0);
|
||||
@@ -292,7 +306,7 @@ mod tests {
|
||||
amount_base_1,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_3.base_via_curve > 0 && swap_3.quote_via_curve > 0);
|
||||
@@ -307,13 +321,16 @@ mod tests {
|
||||
let mut market = new_market();
|
||||
market.fee.rate = 0;
|
||||
|
||||
let mut clock = Clock::default();
|
||||
clock.slot = 10;
|
||||
|
||||
// Open sandwich
|
||||
let swap_1 = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let amount_base_1 = swap_1.base_to_transfer;
|
||||
@@ -326,7 +343,7 @@ mod tests {
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_2.base_via_curve > 0 && swap_2.quote_via_curve > 0);
|
||||
@@ -338,7 +355,7 @@ mod tests {
|
||||
amount_base_1,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactIn,
|
||||
Clock::default(),
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap_3.base_via_curve == 0 && swap_3.quote_via_curve == 0);
|
||||
@@ -348,15 +365,78 @@ mod tests {
|
||||
assert!(swap_3.quote_to_transfer <= swap_1.quote_to_transfer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_virtual_liquidity() {
|
||||
let mut market = new_market();
|
||||
market.fee.rate = 0;
|
||||
market.quote.balance_virtual = 1_000_000_000;
|
||||
market.quote.balance = 0;
|
||||
|
||||
let mut clock = Clock::default();
|
||||
clock.slot = 10;
|
||||
|
||||
// Sell
|
||||
// Assert swap fails without real liquidity to satisfy order.
|
||||
let swap = market.swap(
|
||||
100_000,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactIn,
|
||||
clock.clone(),
|
||||
);
|
||||
assert!(swap.is_err());
|
||||
|
||||
// Buy
|
||||
// Assert buy succeeds adding liquidity.
|
||||
let swap = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Buy,
|
||||
SwapPrecision::ExactIn,
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap.base_via_curve > 0 && swap.quote_via_curve > 0);
|
||||
assert!(swap.base_via_order == 0 && swap.quote_via_order == 0);
|
||||
assert_eq!(market.quote.balance, 100_000);
|
||||
assert_eq!(market.quote.balance_virtual, 1_000_000_000);
|
||||
|
||||
// Sell
|
||||
// Assert sell fails if there is insufficient liquidity.
|
||||
let swap = market.swap(
|
||||
100_001,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactOut,
|
||||
clock.clone(),
|
||||
);
|
||||
assert!(swap.is_err());
|
||||
|
||||
// Sell
|
||||
// Assert sell succeeds removing liquidity.
|
||||
let swap = market
|
||||
.swap(
|
||||
100_000,
|
||||
SwapDirection::Sell,
|
||||
SwapPrecision::ExactOut,
|
||||
clock.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(swap.base_via_curve > 0 && swap.quote_via_curve > 0);
|
||||
assert!(swap.base_via_order > 0 && swap.quote_via_order > 0);
|
||||
assert_eq!(market.quote.balance, 0);
|
||||
assert_eq!(market.quote.balance_virtual, 1_000_000_000);
|
||||
}
|
||||
|
||||
fn new_market() -> Market {
|
||||
Market {
|
||||
base: TokenParams {
|
||||
mint: Pubkey::new_unique(),
|
||||
balance: 1_000_000_000,
|
||||
balance_virtual: 0,
|
||||
},
|
||||
quote: TokenParams {
|
||||
mint: Pubkey::new_unique(),
|
||||
balance: 1_000_000_000,
|
||||
balance_virtual: 0,
|
||||
},
|
||||
fee: FeeParams {
|
||||
cumulative: 0,
|
||||
@@ -365,8 +445,8 @@ mod tests {
|
||||
},
|
||||
snapshot: Snapshot {
|
||||
enabled: 1,
|
||||
base_balance: 1_000_000_000,
|
||||
quote_balance: 1_000_000_000,
|
||||
base_balance: 0,
|
||||
quote_balance: 0,
|
||||
slot: 0,
|
||||
},
|
||||
id: 0,
|
||||
|
||||
@@ -23,7 +23,7 @@ impl Market {
|
||||
// Fill entire swap via curve.
|
||||
let base_via_curve = base_in;
|
||||
let mut quote_via_curve = self.get_quote_out(base_via_curve);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell)?;
|
||||
let swap_fee = self.fee(quote_via_curve as u64);
|
||||
quote_fee += swap_fee;
|
||||
quote_via_curve -= swap_fee as u128;
|
||||
@@ -37,7 +37,7 @@ impl Market {
|
||||
TokenType::Base,
|
||||
);
|
||||
quote_fee += self.fee(quote_via_bid as u64);
|
||||
self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell);
|
||||
self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell)?;
|
||||
quote_via_bid -= quote_fee as u128;
|
||||
(base_via_bid, quote_via_bid, 0, 0)
|
||||
} else {
|
||||
@@ -45,13 +45,13 @@ impl Market {
|
||||
let base_via_bid = bid_size_in_base;
|
||||
let mut quote_via_bid = bid_size_in_quote;
|
||||
quote_fee += self.fee(quote_via_bid as u64);
|
||||
self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell);
|
||||
self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell)?;
|
||||
quote_via_bid -= quote_fee as u128;
|
||||
|
||||
// Fill remaining swap through pool.
|
||||
let base_via_curve = base_in - base_via_bid;
|
||||
let mut quote_via_curve = self.get_quote_out(base_via_curve);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell)?;
|
||||
let swap_fee = self.fee(quote_via_curve as u64);
|
||||
quote_fee += swap_fee;
|
||||
quote_via_curve -= swap_fee as u128;
|
||||
|
||||
@@ -29,7 +29,7 @@ impl Market {
|
||||
// Fill entire swap via curve.
|
||||
let quote_via_curve = quote_out_pre_fee;
|
||||
let base_via_curve = self.get_base_in(quote_via_curve)?;
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell)?;
|
||||
(0, 0, base_via_curve, quote_via_curve)
|
||||
} else if bid_size_in_quote >= quote_out {
|
||||
// Fill entire swap through virtual limit order.
|
||||
@@ -39,18 +39,18 @@ impl Market {
|
||||
SwapDirection::Sell,
|
||||
TokenType::Quote,
|
||||
);
|
||||
self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell);
|
||||
self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell)?;
|
||||
(base_via_bid, quote_via_bid, 0, 0)
|
||||
} else {
|
||||
// Partially fill swap through virtual limit order.
|
||||
let base_via_bid = bid_size_in_base;
|
||||
let quote_via_bid = bid_size_in_quote;
|
||||
self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell);
|
||||
self.update_reserves(base_via_bid, quote_via_bid, SwapDirection::Sell)?;
|
||||
|
||||
// Fill remaining swap amount through pool.
|
||||
let quote_via_curve = quote_out_pre_fee - quote_via_bid;
|
||||
let base_via_curve = self.get_base_in(quote_via_curve)?;
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell);
|
||||
self.update_reserves(base_via_curve, quote_via_curve, SwapDirection::Sell)?;
|
||||
(base_via_bid, quote_via_bid, base_via_curve, quote_via_curve)
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use steel::Clock;
|
||||
|
||||
use crate::consts::SLOT_WINDOW;
|
||||
use crate::{consts::SLOT_WINDOW, error::OreError};
|
||||
|
||||
use super::{Market, SwapDirection, TokenType, VirtualLimitOrder};
|
||||
|
||||
@@ -41,8 +41,8 @@ impl Market {
|
||||
/// ```
|
||||
pub fn get_virtual_limit_order(&self, direction: SwapDirection) -> VirtualLimitOrder {
|
||||
// Upcast data.
|
||||
let base_balance = self.base.balance as u128;
|
||||
let quote_balance = self.quote.balance as u128;
|
||||
let base_balance = self.base.reserves();
|
||||
let quote_balance = self.quote.reserves();
|
||||
let base_snapshot = self.snapshot.base_balance as u128;
|
||||
let quote_snapshot = self.snapshot.quote_balance as u128;
|
||||
|
||||
@@ -126,21 +126,33 @@ impl Market {
|
||||
let snapshot_slot = (slot / SLOT_WINDOW) * SLOT_WINDOW;
|
||||
if snapshot_slot != self.snapshot.slot {
|
||||
self.snapshot.slot = snapshot_slot;
|
||||
self.snapshot.base_balance = self.base.balance;
|
||||
self.snapshot.quote_balance = self.quote.balance;
|
||||
self.snapshot.base_balance = self.base.reserves() as u64;
|
||||
self.snapshot.quote_balance = self.quote.reserves() as u64;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_reserves(&mut self, base: u128, quote: u128, direction: SwapDirection) {
|
||||
pub(crate) fn update_reserves(
|
||||
&mut self,
|
||||
base: u128,
|
||||
quote: u128,
|
||||
direction: SwapDirection,
|
||||
) -> Result<(), OreError> {
|
||||
match direction {
|
||||
SwapDirection::Buy => {
|
||||
if base > self.base.balance as u128 {
|
||||
return Err(OreError::InsufficientVaultReserves.into());
|
||||
}
|
||||
self.base.balance -= base as u64;
|
||||
self.quote.balance += quote as u64;
|
||||
}
|
||||
SwapDirection::Sell => {
|
||||
if quote > self.quote.balance as u128 {
|
||||
return Err(OreError::InsufficientVaultReserves.into());
|
||||
}
|
||||
self.base.balance += base as u64;
|
||||
self.quote.balance -= quote as u64;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,27 @@ pub fn process_open(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult
|
||||
&[MARKET, &id.to_le_bytes()],
|
||||
)?;
|
||||
let market = market_info.as_account_mut::<Market>(&ore_api::ID)?;
|
||||
market.base = TokenParams {
|
||||
mint: Pubkey::default(),
|
||||
balance: 0,
|
||||
balance_virtual: 0,
|
||||
};
|
||||
market.quote = TokenParams {
|
||||
mint: Pubkey::default(),
|
||||
balance: 0,
|
||||
balance_virtual: 0,
|
||||
};
|
||||
market.fee = FeeParams {
|
||||
rate: FEE_RATE_BPS,
|
||||
uncollected: 0,
|
||||
cumulative: 0,
|
||||
};
|
||||
market.snapshot = Snapshot {
|
||||
enabled: 1,
|
||||
base_balance: 0,
|
||||
quote_balance: 0,
|
||||
slot: 0,
|
||||
};
|
||||
market.id = id;
|
||||
|
||||
// Initialize hash token mint.
|
||||
|
||||
Reference in New Issue
Block a user