Serum: Track referrer rebates as Mango platform fees (#469)

And start sharing code between serum3 settle and force cancel
This commit is contained in:
Christian Kamm 2023-02-22 12:04:21 +01:00 committed by GitHub
parent d3c5817a45
commit f75e3ee656
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 164 additions and 97 deletions

View File

@ -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, &quote_bank)?;
&before_oo,
after_base_vault,
after_quote_vault,
&after_oo,
Some(&mut health_cache),
)?;
//
// Health check at the end

View File

@ -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, &quote_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;

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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.

View File

@ -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,