Serum: Track referrer rebates as Mango platform fees (#469)
And start sharing code between serum3 settle and force cancel
This commit is contained in:
parent
d3c5817a45
commit
f75e3ee656
|
@ -3,10 +3,8 @@ use anchor_lang::prelude::*;
|
|||
use crate::accounts_ix::*;
|
||||
use crate::error::*;
|
||||
use crate::health::*;
|
||||
use crate::instructions::{
|
||||
apply_vault_difference, charge_loan_origination_fees, OODifference, OpenOrdersAmounts,
|
||||
OpenOrdersSlim,
|
||||
};
|
||||
use crate::instructions::apply_settle_changes;
|
||||
use crate::instructions::{charge_loan_origination_fees, OpenOrdersAmounts, OpenOrdersSlim};
|
||||
use crate::logs::Serum3OpenOrdersBalanceLogV2;
|
||||
use crate::serum3_cpi::load_open_orders_ref;
|
||||
use crate::state::*;
|
||||
|
@ -117,10 +115,11 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
//
|
||||
// After-settle tracking
|
||||
//
|
||||
let after_oo;
|
||||
{
|
||||
let oo_ai = &ctx.accounts.open_orders.as_ref();
|
||||
let open_orders = load_open_orders_ref(oo_ai)?;
|
||||
let after_oo = OpenOrdersSlim::from_oo(&open_orders);
|
||||
after_oo = OpenOrdersSlim::from_oo(&open_orders);
|
||||
|
||||
emit!(Serum3OpenOrdersBalanceLogV2 {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
|
@ -134,9 +133,6 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
quote_free: after_oo.native_quote_free(),
|
||||
referrer_rebates_accrued: after_oo.native_rebates(),
|
||||
});
|
||||
|
||||
OODifference::new(&before_oo, &after_oo)
|
||||
.adjust_health_cache(&mut health_cache, &serum_market)?;
|
||||
};
|
||||
|
||||
ctx.accounts.base_vault.reload()?;
|
||||
|
@ -144,34 +140,23 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
let after_base_vault = ctx.accounts.base_vault.amount;
|
||||
let after_quote_vault = ctx.accounts.quote_vault.amount;
|
||||
|
||||
// Settle cannot decrease vault balances
|
||||
require_gte!(after_base_vault, before_base_vault);
|
||||
require_gte!(after_quote_vault, before_quote_vault);
|
||||
|
||||
// Credit the difference in vault balances to the user's account
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
apply_vault_difference(
|
||||
apply_settle_changes(
|
||||
ctx.accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
serum_market.market_index,
|
||||
&mut base_bank,
|
||||
after_base_vault,
|
||||
before_base_vault,
|
||||
None, // guaranteed to deposit into bank
|
||||
)?
|
||||
.adjust_health_cache(&mut health_cache, &base_bank)?;
|
||||
apply_vault_difference(
|
||||
ctx.accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
serum_market.market_index,
|
||||
&mut quote_bank,
|
||||
after_quote_vault,
|
||||
&serum_market,
|
||||
before_base_vault,
|
||||
before_quote_vault,
|
||||
None, // guaranteed to deposit into bank
|
||||
)?
|
||||
.adjust_health_cache(&mut health_cache, "e_bank)?;
|
||||
&before_oo,
|
||||
after_base_vault,
|
||||
after_quote_vault,
|
||||
&after_oo,
|
||||
Some(&mut health_cache),
|
||||
)?;
|
||||
|
||||
//
|
||||
// Health check at the end
|
||||
|
|
|
@ -38,10 +38,8 @@ pub trait OpenOrdersAmounts {
|
|||
fn native_quote_reserved(&self) -> u64;
|
||||
fn native_base_free(&self) -> u64;
|
||||
fn native_quote_free(&self) -> u64;
|
||||
fn native_quote_free_plus_rebates(&self) -> u64;
|
||||
fn native_base_total(&self) -> u64;
|
||||
fn native_quote_total(&self) -> u64;
|
||||
fn native_quote_total_plus_rebates(&self) -> u64;
|
||||
fn native_rebates(&self) -> u64;
|
||||
}
|
||||
|
||||
|
@ -58,18 +56,12 @@ impl OpenOrdersAmounts for OpenOrdersSlim {
|
|||
fn native_quote_free(&self) -> u64 {
|
||||
self.native_pc_free
|
||||
}
|
||||
fn native_quote_free_plus_rebates(&self) -> u64 {
|
||||
cm!(self.native_pc_free + self.referrer_rebates_accrued)
|
||||
}
|
||||
fn native_base_total(&self) -> u64 {
|
||||
self.native_coin_total
|
||||
}
|
||||
fn native_quote_total(&self) -> u64 {
|
||||
self.native_pc_total
|
||||
}
|
||||
fn native_quote_total_plus_rebates(&self) -> u64 {
|
||||
cm!(self.native_pc_total + self.referrer_rebates_accrued)
|
||||
}
|
||||
fn native_rebates(&self) -> u64 {
|
||||
self.referrer_rebates_accrued
|
||||
}
|
||||
|
@ -88,18 +80,12 @@ impl OpenOrdersAmounts for OpenOrders {
|
|||
fn native_quote_free(&self) -> u64 {
|
||||
self.native_pc_free
|
||||
}
|
||||
fn native_quote_free_plus_rebates(&self) -> u64 {
|
||||
cm!(self.native_pc_free + self.referrer_rebates_accrued)
|
||||
}
|
||||
fn native_base_total(&self) -> u64 {
|
||||
self.native_coin_total
|
||||
}
|
||||
fn native_quote_total(&self) -> u64 {
|
||||
self.native_pc_total
|
||||
}
|
||||
fn native_quote_total_plus_rebates(&self) -> u64 {
|
||||
cm!(self.native_pc_total + self.referrer_rebates_accrued)
|
||||
}
|
||||
fn native_rebates(&self) -> u64 {
|
||||
self.referrer_rebates_accrued
|
||||
}
|
||||
|
@ -286,8 +272,8 @@ pub fn serum3_place_order(
|
|||
// Health check
|
||||
//
|
||||
if let Some((mut health_cache, pre_init_health)) = pre_health_opt {
|
||||
vault_difference.adjust_health_cache(&mut health_cache, &payer_bank)?;
|
||||
oo_difference.adjust_health_cache(&mut health_cache, &serum_market)?;
|
||||
vault_difference.adjust_health_cache_token_balance(&mut health_cache, &payer_bank)?;
|
||||
oo_difference.adjust_health_cache_serum3_state(&mut health_cache, &serum_market)?;
|
||||
account.check_health_post(&health_cache, pre_init_health)?;
|
||||
}
|
||||
|
||||
|
@ -312,12 +298,12 @@ impl OODifference {
|
|||
- I80F48::from(before_oo.native_quote_reserved())),
|
||||
free_base_change: cm!(I80F48::from(after_oo.native_base_free())
|
||||
- I80F48::from(before_oo.native_base_free())),
|
||||
free_quote_change: cm!(I80F48::from(after_oo.native_quote_free_plus_rebates())
|
||||
- I80F48::from(before_oo.native_quote_free_plus_rebates())),
|
||||
free_quote_change: cm!(I80F48::from(after_oo.native_quote_free())
|
||||
- I80F48::from(before_oo.native_quote_free())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn adjust_health_cache(
|
||||
pub fn adjust_health_cache_serum3_state(
|
||||
&self,
|
||||
health_cache: &mut HealthCache,
|
||||
market: &Serum3Market,
|
||||
|
@ -340,17 +326,21 @@ pub struct VaultDifference {
|
|||
}
|
||||
|
||||
impl VaultDifference {
|
||||
pub fn adjust_health_cache(&self, health_cache: &mut HealthCache, bank: &Bank) -> Result<()> {
|
||||
pub fn adjust_health_cache_token_balance(
|
||||
&self,
|
||||
health_cache: &mut HealthCache,
|
||||
bank: &Bank,
|
||||
) -> Result<()> {
|
||||
assert_eq!(bank.token_index, self.token_index);
|
||||
health_cache.adjust_token_balance(bank, self.native_change)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Called in settle_funds, place_order, liq_force_cancel to adjust token positions after
|
||||
/// Called in apply_settle_changes() and place_order to adjust token positions after
|
||||
/// changing the vault balances
|
||||
/// Also logs changes to token balances
|
||||
pub fn apply_vault_difference(
|
||||
fn apply_vault_difference(
|
||||
account_pk: Pubkey,
|
||||
account: &mut MangoAccountRefMut,
|
||||
serum_market_index: Serum3MarketIndex,
|
||||
|
@ -418,6 +408,67 @@ pub fn apply_vault_difference(
|
|||
})
|
||||
}
|
||||
|
||||
/// Uses the changes in OpenOrders and vaults to adjust the user token position,
|
||||
/// collect fees and optionally adjusts the HealthCache.
|
||||
pub fn apply_settle_changes(
|
||||
account_pk: Pubkey,
|
||||
account: &mut MangoAccountRefMut,
|
||||
base_bank: &mut Bank,
|
||||
quote_bank: &mut Bank,
|
||||
serum_market: &Serum3Market,
|
||||
before_base_vault: u64,
|
||||
before_quote_vault: u64,
|
||||
before_oo: &OpenOrdersSlim,
|
||||
after_base_vault: u64,
|
||||
after_quote_vault: u64,
|
||||
after_oo: &OpenOrdersSlim,
|
||||
health_cache: Option<&mut HealthCache>,
|
||||
) -> Result<()> {
|
||||
// Example: rebates go from 100 -> 10. That means we credit 90 in fees.
|
||||
let received_fees = before_oo
|
||||
.native_rebates()
|
||||
.saturating_sub(after_oo.native_rebates());
|
||||
cm!(quote_bank.collected_fees_native += I80F48::from(received_fees));
|
||||
|
||||
// Don't count the referrer rebate fees as part of the vault change that should be
|
||||
// credited to the user.
|
||||
let after_quote_vault_adjusted = after_quote_vault - received_fees;
|
||||
|
||||
// Settle cannot decrease vault balances
|
||||
require_gte!(after_base_vault, before_base_vault);
|
||||
require_gte!(after_quote_vault_adjusted, before_quote_vault);
|
||||
|
||||
// Credit the difference in vault balances to the user's account
|
||||
let base_difference = apply_vault_difference(
|
||||
account_pk,
|
||||
account,
|
||||
serum_market.market_index,
|
||||
base_bank,
|
||||
after_base_vault,
|
||||
before_base_vault,
|
||||
None, // guaranteed to deposit into bank
|
||||
)?;
|
||||
let quote_difference = apply_vault_difference(
|
||||
account_pk,
|
||||
account,
|
||||
serum_market.market_index,
|
||||
quote_bank,
|
||||
after_quote_vault_adjusted,
|
||||
before_quote_vault,
|
||||
None, // guaranteed to deposit into bank
|
||||
)?;
|
||||
|
||||
if let Some(health_cache) = health_cache {
|
||||
base_difference.adjust_health_cache_token_balance(health_cache, &base_bank)?;
|
||||
quote_difference.adjust_health_cache_token_balance(health_cache, "e_bank)?;
|
||||
|
||||
OODifference::new(&before_oo, &after_oo)
|
||||
.adjust_health_cache_serum3_state(health_cache, serum_market)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> {
|
||||
use crate::serum3_cpi;
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::error::*;
|
|||
use crate::serum3_cpi::load_open_orders_ref;
|
||||
use crate::state::*;
|
||||
|
||||
use super::{apply_vault_difference, OpenOrdersAmounts, OpenOrdersSlim};
|
||||
use super::{apply_settle_changes, OpenOrdersAmounts, OpenOrdersSlim};
|
||||
use crate::accounts_ix::*;
|
||||
use crate::logs::Serum3OpenOrdersBalanceLogV2;
|
||||
use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
|
||||
|
@ -63,9 +63,10 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
//
|
||||
// Charge any open loan origination fees
|
||||
//
|
||||
let before_oo;
|
||||
{
|
||||
let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?;
|
||||
let before_oo = OpenOrdersSlim::from_oo(&open_orders);
|
||||
before_oo = OpenOrdersSlim::from_oo(&open_orders);
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
|
@ -91,46 +92,35 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
//
|
||||
// After-settle tracking
|
||||
//
|
||||
{
|
||||
ctx.accounts.base_vault.reload()?;
|
||||
ctx.accounts.quote_vault.reload()?;
|
||||
let after_base_vault = ctx.accounts.base_vault.amount;
|
||||
let after_quote_vault = ctx.accounts.quote_vault.amount;
|
||||
let after_oo = {
|
||||
let oo_ai = &ctx.accounts.open_orders.as_ref();
|
||||
let open_orders = load_open_orders_ref(oo_ai)?;
|
||||
OpenOrdersSlim::from_oo(&open_orders)
|
||||
};
|
||||
|
||||
// Settle cannot decrease vault balances
|
||||
require_gte!(after_base_vault, before_base_vault);
|
||||
require_gte!(after_quote_vault, before_quote_vault);
|
||||
ctx.accounts.base_vault.reload()?;
|
||||
ctx.accounts.quote_vault.reload()?;
|
||||
let after_base_vault = ctx.accounts.base_vault.amount;
|
||||
let after_quote_vault = ctx.accounts.quote_vault.amount;
|
||||
|
||||
// Credit the difference in vault balances to the user's account
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
apply_vault_difference(
|
||||
ctx.accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
serum_market.market_index,
|
||||
&mut base_bank,
|
||||
after_base_vault,
|
||||
before_base_vault,
|
||||
// Since after >= before, we know this can be a deposit
|
||||
// and no net borrow check will be necessary, meaning
|
||||
// we don't need an oracle price.
|
||||
None,
|
||||
)?;
|
||||
apply_vault_difference(
|
||||
ctx.accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
serum_market.market_index,
|
||||
&mut quote_bank,
|
||||
after_quote_vault,
|
||||
before_quote_vault,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
|
||||
apply_settle_changes(
|
||||
ctx.accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
&mut base_bank,
|
||||
&mut quote_bank,
|
||||
&serum_market,
|
||||
before_base_vault,
|
||||
before_quote_vault,
|
||||
&before_oo,
|
||||
after_base_vault,
|
||||
after_quote_vault,
|
||||
&after_oo,
|
||||
None,
|
||||
)?;
|
||||
|
||||
let oo_ai = &ctx.accounts.open_orders.as_ref();
|
||||
let open_orders = load_open_orders_ref(oo_ai)?;
|
||||
let after_oo = OpenOrdersSlim::from_oo(&open_orders);
|
||||
emit!(Serum3OpenOrdersBalanceLogV2 {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
|
@ -189,7 +179,7 @@ pub fn charge_loan_origination_fees(
|
|||
}
|
||||
|
||||
let serum3_account = account.serum3_orders_mut(market_index).unwrap();
|
||||
let oo_quote_total = before_oo.native_quote_total_plus_rebates();
|
||||
let oo_quote_total = before_oo.native_quote_total();
|
||||
let actualized_quote_loan = I80F48::from_num::<u64>(
|
||||
serum3_account
|
||||
.quote_borrows_without_fee
|
||||
|
|
|
@ -198,7 +198,6 @@ pub struct Serum3OpenOrdersBalanceLog {
|
|||
pub quote_token_index: u16,
|
||||
pub base_total: u64,
|
||||
pub base_free: u64,
|
||||
/// this field does not include the referrer_rebates; need to add that in to get true total
|
||||
pub quote_total: u64,
|
||||
pub quote_free: u64,
|
||||
pub referrer_rebates_accrued: u64,
|
||||
|
@ -213,7 +212,6 @@ pub struct Serum3OpenOrdersBalanceLogV2 {
|
|||
pub quote_token_index: u16,
|
||||
pub base_total: u64,
|
||||
pub base_free: u64,
|
||||
/// this field does not include the referrer_rebates; need to add that in to get true total
|
||||
pub quote_total: u64,
|
||||
pub quote_free: u64,
|
||||
pub referrer_rebates_accrued: u64,
|
||||
|
|
|
@ -34,8 +34,7 @@ pub fn compute_equity(
|
|||
let accumulated_equity = token_equity_map
|
||||
.get(&serum_account.base_token_index)
|
||||
.unwrap_or(&I80F48::ZERO);
|
||||
let native_coin_total_i80f48 =
|
||||
I80F48::from_num(oo.native_coin_total + oo.referrer_rebates_accrued);
|
||||
let native_coin_total_i80f48 = I80F48::from_num(oo.native_coin_total);
|
||||
let new_equity = cm!(accumulated_equity + native_coin_total_i80f48 * oracle_price);
|
||||
token_equity_map.insert(serum_account.base_token_index, new_equity);
|
||||
|
||||
|
|
|
@ -504,7 +504,8 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
}
|
||||
|
||||
let without_serum_taker_fee = |amount: i64| (amount as f64 * (1.0 - 0.0004)).trunc() as i64;
|
||||
let serum_maker_rebate = |amount: i64| (amount as f64 * 0.0).round() as i64;
|
||||
let serum_maker_rebate = |amount: i64| (amount as f64 * 0.0002).floor() as i64;
|
||||
let serum_fee = |amount: i64| (amount as f64 * 0.0002).trunc() as i64;
|
||||
let loan_origination_fee = |amount: i64| (amount as f64 * 0.0005).trunc() as i64;
|
||||
|
||||
//
|
||||
|
@ -515,6 +516,10 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
let bid_amount = 200000;
|
||||
let ask_amount = 210000;
|
||||
let fill_amount = 200000;
|
||||
let quote_fees1 = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
|
||||
// account2 has an order on the book
|
||||
order_placer2.bid(1.0, bid_amount as u64).await.unwrap();
|
||||
|
@ -540,6 +545,13 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
deposit_amount - ask_amount - loan_origination_fee(fill_amount - deposit_amount)
|
||||
);
|
||||
|
||||
// Serum referrer rebates only accrue once the events are executed
|
||||
let quote_fees2 = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
assert!(assert_equal(quote_fees2 - quote_fees1, 0.0, 0.1));
|
||||
|
||||
// check account2 balances too
|
||||
context
|
||||
.serum
|
||||
|
@ -558,7 +570,36 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
assert_eq!(
|
||||
account_position(solana, account2, quote_bank).await,
|
||||
deposit_amount - fill_amount - loan_origination_fee(fill_amount - deposit_amount)
|
||||
+ serum_maker_rebate(fill_amount)
|
||||
+ (serum_maker_rebate(fill_amount) - 1) // unclear where the -1 comes from?
|
||||
);
|
||||
|
||||
// Serum referrer rebates accrue on the taker side
|
||||
let quote_fees3 = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
assert!(assert_equal(
|
||||
quote_fees3 - quote_fees1,
|
||||
loan_origination_fee(fill_amount - deposit_amount) as f64,
|
||||
0.1
|
||||
));
|
||||
|
||||
order_placer.settle().await;
|
||||
|
||||
// Now rebates got collected as Mango fees, but user balances are unchanged
|
||||
let quote_fees4 = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
assert!(assert_equal(
|
||||
quote_fees4 - quote_fees3,
|
||||
serum_fee(fill_amount) as f64,
|
||||
0.1
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account, quote_bank).await,
|
||||
deposit_amount + without_serum_taker_fee(fill_amount)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
|
@ -188,9 +188,12 @@ impl SerumCookie {
|
|||
spot_market_cookie: &SpotMarketCookie,
|
||||
open_orders: &[Pubkey],
|
||||
) {
|
||||
let mut sorted_oos = open_orders.to_vec();
|
||||
sorted_oos.sort_by_key(|key| serum_dex::state::ToAlignedBytes::to_aligned_bytes(key));
|
||||
|
||||
let instructions = [serum_dex::instruction::consume_events(
|
||||
&self.program_id,
|
||||
open_orders.iter().collect(),
|
||||
sorted_oos.iter().collect(),
|
||||
&spot_market_cookie.market,
|
||||
&spot_market_cookie.event_q,
|
||||
&spot_market_cookie.coin_fee_account,
|
||||
|
|
Loading…
Reference in New Issue