Serum settle funds V2: fees can go to users (#484)
Co-authored-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
1950d8c84a
commit
252210d194
|
@ -60,3 +60,21 @@ pub struct Serum3SettleFunds<'info> {
|
|||
|
||||
pub token_program: Program<'info, Token>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Serum3SettleFundsV2Extra<'info> {
|
||||
/// CHECK: The oracle can be one of several different account types and the pubkey is checked in the parent
|
||||
pub quote_oracle: UncheckedAccount<'info>,
|
||||
/// CHECK: The oracle can be one of several different account types and the pubkey is checked in the parent
|
||||
pub base_oracle: UncheckedAccount<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Serum3SettleFundsV2<'info> {
|
||||
pub v1: Serum3SettleFunds<'info>,
|
||||
#[account(
|
||||
constraint = v2.quote_oracle.key() == v1.quote_bank.load()?.oracle,
|
||||
constraint = v2.base_oracle.key() == v1.base_bank.load()?.oracle,
|
||||
)]
|
||||
pub v2: Serum3SettleFundsV2Extra<'info>,
|
||||
}
|
||||
|
|
|
@ -158,6 +158,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
after_quote_vault,
|
||||
&after_oo,
|
||||
Some(&mut health_cache),
|
||||
true,
|
||||
)?;
|
||||
|
||||
//
|
||||
|
@ -201,6 +202,7 @@ fn cpi_settle_funds(ctx: &Serum3LiqForceCancelOrders) -> Result<()> {
|
|||
user_quote_wallet: ctx.quote_vault.to_account_info(),
|
||||
vault_signer: ctx.market_vault_signer.to_account_info(),
|
||||
token_program: ctx.token_program.to_account_info(),
|
||||
rebates_quote_wallet: ctx.quote_vault.to_account_info(),
|
||||
}
|
||||
.call(&group)
|
||||
}
|
||||
|
|
|
@ -424,22 +424,26 @@ pub fn apply_settle_changes(
|
|||
after_quote_vault: u64,
|
||||
after_oo: &OpenOrdersSlim,
|
||||
health_cache: Option<&mut HealthCache>,
|
||||
fees_to_dao: bool,
|
||||
) -> 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());
|
||||
quote_bank.collected_fees_native += I80F48::from(received_fees);
|
||||
let mut received_fees = 0;
|
||||
if fees_to_dao {
|
||||
// Example: rebates go from 100 -> 10. That means we credit 90 in fees.
|
||||
received_fees = before_oo
|
||||
.native_rebates()
|
||||
.saturating_sub(after_oo.native_rebates());
|
||||
quote_bank.collected_fees_native += I80F48::from(received_fees);
|
||||
|
||||
// Ideally we could credit buyback_fees at the current value of the received fees,
|
||||
// but the settle_funds instruction currently doesn't receive the oracle account
|
||||
// that would be needed for it.
|
||||
if quote_bank.token_index == QUOTE_TOKEN_INDEX {
|
||||
let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
account
|
||||
.fixed
|
||||
.expire_buyback_fees(now_ts, group.buyback_fees_expiry_interval);
|
||||
account.fixed.accrue_buyback_fees(received_fees);
|
||||
// Ideally we could credit buyback_fees at the current value of the received fees,
|
||||
// but the settle_funds instruction currently doesn't receive the oracle account
|
||||
// that would be needed for it.
|
||||
if quote_bank.token_index == QUOTE_TOKEN_INDEX {
|
||||
let now_ts = Clock::get()?.unix_timestamp.try_into().unwrap();
|
||||
account
|
||||
.fixed
|
||||
.expire_buyback_fees(now_ts, group.buyback_fees_expiry_interval);
|
||||
account.fixed.accrue_buyback_fees(received_fees);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't count the referrer rebate fees as part of the vault change that should be
|
||||
|
|
|
@ -16,17 +16,21 @@ use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
|
|||
///
|
||||
/// There will be free funds on open_orders when an order was triggered.
|
||||
///
|
||||
pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
||||
let serum_market = ctx.accounts.serum_market.load()?;
|
||||
pub fn serum3_settle_funds<'info>(
|
||||
accounts: &mut Serum3SettleFunds<'info>,
|
||||
v2: Option<&mut Serum3SettleFundsV2Extra<'info>>,
|
||||
fees_to_dao: bool,
|
||||
) -> Result<()> {
|
||||
let serum_market = accounts.serum_market.load()?;
|
||||
|
||||
//
|
||||
// Validation
|
||||
//
|
||||
{
|
||||
let account = ctx.accounts.account.load_full()?;
|
||||
let account = accounts.account.load_full()?;
|
||||
// account constraint #1
|
||||
require!(
|
||||
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
|
||||
account.fixed.is_owner_or_delegate(accounts.owner.key()),
|
||||
MangoError::SomeError
|
||||
);
|
||||
|
||||
|
@ -35,23 +39,23 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
account
|
||||
.serum3_orders(serum_market.market_index)?
|
||||
.open_orders
|
||||
== ctx.accounts.open_orders.key(),
|
||||
== accounts.open_orders.key(),
|
||||
MangoError::SomeError
|
||||
);
|
||||
|
||||
// Validate banks and vaults #3
|
||||
let quote_bank = ctx.accounts.quote_bank.load()?;
|
||||
let quote_bank = accounts.quote_bank.load()?;
|
||||
require!(
|
||||
quote_bank.vault == ctx.accounts.quote_vault.key(),
|
||||
quote_bank.vault == accounts.quote_vault.key(),
|
||||
MangoError::SomeError
|
||||
);
|
||||
require!(
|
||||
quote_bank.token_index == serum_market.quote_token_index,
|
||||
MangoError::SomeError
|
||||
);
|
||||
let base_bank = ctx.accounts.base_bank.load()?;
|
||||
let base_bank = accounts.base_bank.load()?;
|
||||
require!(
|
||||
base_bank.vault == ctx.accounts.base_vault.key(),
|
||||
base_bank.vault == accounts.base_vault.key(),
|
||||
MangoError::SomeError
|
||||
);
|
||||
require!(
|
||||
|
@ -65,14 +69,14 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
//
|
||||
let before_oo;
|
||||
{
|
||||
let open_orders = load_open_orders_ref(ctx.accounts.open_orders.as_ref())?;
|
||||
let open_orders = load_open_orders_ref(accounts.open_orders.as_ref())?;
|
||||
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()?;
|
||||
let mut account = accounts.account.load_full_mut()?;
|
||||
let mut base_bank = accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = accounts.quote_bank.load_mut()?;
|
||||
charge_loan_origination_fees(
|
||||
&ctx.accounts.group.key(),
|
||||
&ctx.accounts.account.key(),
|
||||
&accounts.group.key(),
|
||||
&accounts.account.key(),
|
||||
serum_market.market_index,
|
||||
&mut base_bank,
|
||||
&mut quote_bank,
|
||||
|
@ -84,32 +88,32 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
//
|
||||
// Settle
|
||||
//
|
||||
let before_base_vault = ctx.accounts.base_vault.amount;
|
||||
let before_quote_vault = ctx.accounts.quote_vault.amount;
|
||||
let before_base_vault = accounts.base_vault.amount;
|
||||
let before_quote_vault = accounts.quote_vault.amount;
|
||||
|
||||
cpi_settle_funds(ctx.accounts)?;
|
||||
cpi_settle_funds(accounts)?;
|
||||
|
||||
//
|
||||
// After-settle tracking
|
||||
//
|
||||
let after_oo = {
|
||||
let oo_ai = &ctx.accounts.open_orders.as_ref();
|
||||
let oo_ai = &accounts.open_orders.as_ref();
|
||||
let open_orders = load_open_orders_ref(oo_ai)?;
|
||||
OpenOrdersSlim::from_oo(&open_orders)
|
||||
};
|
||||
|
||||
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;
|
||||
accounts.base_vault.reload()?;
|
||||
accounts.quote_vault.reload()?;
|
||||
let after_base_vault = accounts.base_vault.amount;
|
||||
let after_quote_vault = accounts.quote_vault.amount;
|
||||
|
||||
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()?;
|
||||
let group = ctx.accounts.group.load()?;
|
||||
let mut account = accounts.account.load_full_mut()?;
|
||||
let mut base_bank = accounts.base_bank.load_mut()?;
|
||||
let mut quote_bank = accounts.quote_bank.load_mut()?;
|
||||
let group = accounts.group.load()?;
|
||||
apply_settle_changes(
|
||||
&group,
|
||||
ctx.accounts.account.key(),
|
||||
accounts.account.key(),
|
||||
&mut account.borrow_mut(),
|
||||
&mut base_bank,
|
||||
&mut quote_bank,
|
||||
|
@ -121,11 +125,12 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
|||
after_quote_vault,
|
||||
&after_oo,
|
||||
None,
|
||||
fees_to_dao,
|
||||
)?;
|
||||
|
||||
emit!(Serum3OpenOrdersBalanceLogV2 {
|
||||
mango_group: ctx.accounts.group.key(),
|
||||
mango_account: ctx.accounts.account.key(),
|
||||
mango_group: accounts.group.key(),
|
||||
mango_account: accounts.account.key(),
|
||||
market_index: serum_market.market_index,
|
||||
base_token_index: serum_market.base_token_index,
|
||||
quote_token_index: serum_market.quote_token_index,
|
||||
|
@ -211,7 +216,7 @@ pub fn charge_loan_origination_fees(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn cpi_settle_funds(ctx: &Serum3SettleFunds) -> Result<()> {
|
||||
fn cpi_settle_funds<'info>(ctx: &Serum3SettleFunds<'info>) -> Result<()> {
|
||||
use crate::serum3_cpi;
|
||||
let group = ctx.group.load()?;
|
||||
serum3_cpi::SettleFunds {
|
||||
|
@ -225,6 +230,7 @@ fn cpi_settle_funds(ctx: &Serum3SettleFunds) -> Result<()> {
|
|||
user_quote_wallet: ctx.quote_vault.to_account_info(),
|
||||
vault_signer: ctx.market_vault_signer.to_account_info(),
|
||||
token_program: ctx.token_program.to_account_info(),
|
||||
rebates_quote_wallet: ctx.quote_vault.to_account_info(),
|
||||
}
|
||||
.call(&group)
|
||||
}
|
||||
|
|
|
@ -464,9 +464,27 @@ pub mod mango_v4 {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Settles all free funds from the OpenOrders account into the MangoAccount.
|
||||
///
|
||||
/// Any serum "referrer rebates" (ui fees) are considered Mango fees.
|
||||
pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::serum3_settle_funds(ctx)?;
|
||||
instructions::serum3_settle_funds(ctx.accounts, None, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Like Serum3SettleFunds, but `fees_to_dao` determines if referrer rebates are considered fees
|
||||
/// or are credited to the MangoAccount.
|
||||
pub fn serum3_settle_funds_v2(
|
||||
ctx: Context<Serum3SettleFundsV2>,
|
||||
fees_to_dao: bool,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::serum3_settle_funds(
|
||||
&mut ctx.accounts.v1,
|
||||
Some(&mut ctx.accounts.v2),
|
||||
fees_to_dao,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,11 @@ pub fn load_open_orders_ref<'a>(
|
|||
}
|
||||
|
||||
pub fn load_open_orders(acc: &impl AccountReader) -> Result<&serum_dex::state::OpenOrders> {
|
||||
Ok(bytemuck::from_bytes(strip_dex_padding(acc.data())?))
|
||||
load_open_orders_bytes(acc.data())
|
||||
}
|
||||
|
||||
pub fn load_open_orders_bytes(bytes: &[u8]) -> Result<&serum_dex::state::OpenOrders> {
|
||||
Ok(bytemuck::from_bytes(strip_dex_padding(bytes)?))
|
||||
}
|
||||
|
||||
pub fn pubkey_from_u64_array(d: [u64; 4]) -> Pubkey {
|
||||
|
@ -226,6 +230,8 @@ pub struct SettleFunds<'info> {
|
|||
pub vault_signer: AccountInfo<'info>,
|
||||
/// CHECK: cpi
|
||||
pub token_program: AccountInfo<'info>,
|
||||
/// CHECK: cpi
|
||||
pub rebates_quote_wallet: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
impl<'a> SettleFunds<'a> {
|
||||
|
@ -244,7 +250,7 @@ impl<'a> SettleFunds<'a> {
|
|||
AccountMeta::new(*self.user_quote_wallet.key, false),
|
||||
AccountMeta::new_readonly(*self.vault_signer.key, false),
|
||||
AccountMeta::new_readonly(*self.token_program.key, false),
|
||||
AccountMeta::new(*self.user_quote_wallet.key, false),
|
||||
AccountMeta::new(*self.rebates_quote_wallet.key, false),
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -256,10 +262,10 @@ impl<'a> SettleFunds<'a> {
|
|||
self.base_vault,
|
||||
self.quote_vault,
|
||||
self.user_base_wallet,
|
||||
self.user_quote_wallet.clone(),
|
||||
self.user_quote_wallet,
|
||||
self.vault_signer,
|
||||
self.token_program,
|
||||
self.user_quote_wallet,
|
||||
self.rebates_quote_wallet,
|
||||
];
|
||||
|
||||
let seeds = group_seeds!(group);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::*;
|
||||
|
||||
use mango_v4::accounts_ix::{Serum3OrderType, Serum3SelfTradeBehavior, Serum3Side};
|
||||
use mango_v4::{instructions::OpenOrdersSlim, serum3_cpi::load_open_orders_bytes};
|
||||
use std::sync::Arc;
|
||||
|
||||
struct SerumOrderPlacer {
|
||||
|
@ -119,6 +120,20 @@ impl SerumOrderPlacer {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
async fn settle_v2(&self, fees_to_dao: bool) {
|
||||
send_tx(
|
||||
&self.solana,
|
||||
Serum3SettleFundsV2Instruction {
|
||||
account: self.account,
|
||||
owner: self.owner,
|
||||
serum_market: self.serum_market,
|
||||
fees_to_dao,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn mango_serum_orders(&self) -> Serum3Orders {
|
||||
let account_data = get_mango_account(&self.solana, self.account).await;
|
||||
let orders = account_data
|
||||
|
@ -127,6 +142,15 @@ impl SerumOrderPlacer {
|
|||
.unwrap();
|
||||
orders.clone()
|
||||
}
|
||||
|
||||
async fn _open_orders(&self) -> OpenOrdersSlim {
|
||||
let data = self
|
||||
.solana
|
||||
.get_account_data(self.open_orders)
|
||||
.await
|
||||
.unwrap();
|
||||
OpenOrdersSlim::from_oo(load_open_orders_bytes(&data).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -317,16 +341,455 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
//
|
||||
// SETUP: Create a group, accounts, market etc
|
||||
//
|
||||
let deposit_amount = 180000;
|
||||
let CommonSetup {
|
||||
serum_market_cookie,
|
||||
quote_bank,
|
||||
base_bank,
|
||||
mut order_placer,
|
||||
mut order_placer2,
|
||||
..
|
||||
} = common_setup(&context, deposit_amount).await;
|
||||
let account = order_placer.account;
|
||||
let account2 = order_placer2.account;
|
||||
|
||||
//
|
||||
// TEST: Placing and canceling an order does not take loan origination fees even if borrows are needed
|
||||
//
|
||||
{
|
||||
let (bid_order_id, _) = order_placer.bid(1.0, 200000).await.unwrap();
|
||||
let (ask_order_id, _) = order_placer.ask(2.0, 200000).await.unwrap();
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 19999); // rounded
|
||||
assert_eq!(o.quote_borrows_without_fee, 19999);
|
||||
|
||||
order_placer.cancel(bid_order_id).await;
|
||||
order_placer.cancel(ask_order_id).await;
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 19999); // unchanged
|
||||
assert_eq!(o.quote_borrows_without_fee, 19999);
|
||||
|
||||
// placing new, slightly larger orders increases the borrow_without_fee amount only by a small amount
|
||||
let (bid_order_id, _) = order_placer.bid(1.0, 210000).await.unwrap();
|
||||
let (ask_order_id, _) = order_placer.ask(2.0, 300000).await.unwrap();
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 119998); // rounded
|
||||
assert_eq!(o.quote_borrows_without_fee, 29998);
|
||||
|
||||
order_placer.cancel(bid_order_id).await;
|
||||
order_placer.cancel(ask_order_id).await;
|
||||
|
||||
// returns all the funds
|
||||
order_placer.settle().await;
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 0);
|
||||
assert_eq!(o.quote_borrows_without_fee, 0);
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account, quote_bank).await,
|
||||
deposit_amount as i64
|
||||
);
|
||||
assert_eq!(
|
||||
account_position(solana, account, base_bank).await,
|
||||
deposit_amount as i64
|
||||
);
|
||||
|
||||
// consume all the out events from the cancels
|
||||
context
|
||||
.serum
|
||||
.consume_spot_events(&serum_market_cookie, &[order_placer.open_orders])
|
||||
.await;
|
||||
}
|
||||
|
||||
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.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;
|
||||
|
||||
//
|
||||
// TEST: Order execution and settling charges borrow fee
|
||||
//
|
||||
{
|
||||
let deposit_amount = deposit_amount as i64;
|
||||
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();
|
||||
|
||||
// account takes
|
||||
order_placer.ask(1.0, ask_amount as u64).await.unwrap();
|
||||
order_placer.settle().await;
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
// parts of the order ended up on the book an may cause loan origination fees later
|
||||
assert_eq!(
|
||||
o.base_borrows_without_fee,
|
||||
(ask_amount - fill_amount) as u64
|
||||
);
|
||||
assert_eq!(o.quote_borrows_without_fee, 0);
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account, quote_bank).await,
|
||||
deposit_amount + without_serum_taker_fee(fill_amount)
|
||||
);
|
||||
assert_eq!(
|
||||
account_position(solana, account, base_bank).await,
|
||||
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
|
||||
.consume_spot_events(
|
||||
&serum_market_cookie,
|
||||
&[order_placer.open_orders, order_placer2.open_orders],
|
||||
)
|
||||
.await;
|
||||
order_placer2.settle().await;
|
||||
|
||||
let o = order_placer2.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 0);
|
||||
assert_eq!(o.quote_borrows_without_fee, 0);
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account2, base_bank).await,
|
||||
deposit_amount + fill_amount
|
||||
);
|
||||
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) - 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
|
||||
));
|
||||
|
||||
let account_data = solana.get_account::<MangoAccount>(account).await;
|
||||
assert_eq!(
|
||||
account_data.buyback_fees_accrued_current,
|
||||
serum_fee(fill_amount) as u64
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account, quote_bank).await,
|
||||
deposit_amount + without_serum_taker_fee(fill_amount)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_serum_settle_v1() -> Result<(), TransportError> {
|
||||
let mut test_builder = TestContextBuilder::new();
|
||||
test_builder.test().set_compute_max_units(95_000); // Serum3PlaceOrder needs 92.8k
|
||||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
//
|
||||
// SETUP: Create a group, accounts, market etc
|
||||
//
|
||||
let deposit_amount = 160000;
|
||||
let CommonSetup {
|
||||
serum_market_cookie,
|
||||
quote_bank,
|
||||
base_bank,
|
||||
mut order_placer,
|
||||
mut order_placer2,
|
||||
..
|
||||
} = common_setup(&context, deposit_amount).await;
|
||||
let account = order_placer.account;
|
||||
let account2 = order_placer2.account;
|
||||
|
||||
let serum_taker_fee = |amount: i64| (amount as f64 * 0.0004).trunc() as i64;
|
||||
let serum_maker_rebate = |amount: i64| (amount as f64 * 0.0002).floor() as i64;
|
||||
let serum_referrer_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;
|
||||
|
||||
//
|
||||
// TEST: Use v1 serum3_settle_funds
|
||||
//
|
||||
let deposit_amount = deposit_amount as i64;
|
||||
let amount = 200000;
|
||||
let quote_fees_start = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
let quote_start = account_position(solana, account, quote_bank).await;
|
||||
let quote2_start = account_position(solana, account2, quote_bank).await;
|
||||
let base_start = account_position(solana, account, base_bank).await;
|
||||
let base2_start = account_position(solana, account2, base_bank).await;
|
||||
|
||||
// account2 has an order on the book, account takes
|
||||
order_placer2.bid(1.0, amount as u64).await.unwrap();
|
||||
order_placer.ask(1.0, amount as u64).await.unwrap();
|
||||
|
||||
context
|
||||
.serum
|
||||
.consume_spot_events(
|
||||
&serum_market_cookie,
|
||||
&[order_placer.open_orders, order_placer2.open_orders],
|
||||
)
|
||||
.await;
|
||||
|
||||
order_placer.settle().await;
|
||||
order_placer2.settle().await;
|
||||
|
||||
let quote_end = account_position(solana, account, quote_bank).await;
|
||||
let quote2_end = account_position(solana, account2, quote_bank).await;
|
||||
let base_end = account_position(solana, account, base_bank).await;
|
||||
let base2_end = account_position(solana, account2, base_bank).await;
|
||||
|
||||
let lof = loan_origination_fee(amount - deposit_amount);
|
||||
assert_eq!(base_start - amount - lof, base_end);
|
||||
assert_eq!(base2_start + amount, base2_end);
|
||||
assert_eq!(quote_start + amount - serum_taker_fee(amount), quote_end);
|
||||
assert_eq!(
|
||||
quote2_start - amount + serum_maker_rebate(amount) - lof - 1,
|
||||
quote2_end
|
||||
);
|
||||
|
||||
let quote_fees_end = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
assert!(assert_equal(
|
||||
quote_fees_end - quote_fees_start,
|
||||
(lof + serum_referrer_fee(amount)) as f64,
|
||||
0.1
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_serum_settle_v2_to_dao() -> Result<(), TransportError> {
|
||||
let mut test_builder = TestContextBuilder::new();
|
||||
test_builder.test().set_compute_max_units(95_000); // Serum3PlaceOrder needs 92.8k
|
||||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
//
|
||||
// SETUP: Create a group, accounts, market etc
|
||||
//
|
||||
let deposit_amount = 160000;
|
||||
let CommonSetup {
|
||||
serum_market_cookie,
|
||||
quote_bank,
|
||||
base_bank,
|
||||
mut order_placer,
|
||||
mut order_placer2,
|
||||
..
|
||||
} = common_setup(&context, deposit_amount).await;
|
||||
let account = order_placer.account;
|
||||
let account2 = order_placer2.account;
|
||||
|
||||
let serum_taker_fee = |amount: i64| (amount as f64 * 0.0004).trunc() as i64;
|
||||
let serum_maker_rebate = |amount: i64| (amount as f64 * 0.0002).floor() as i64;
|
||||
let serum_referrer_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;
|
||||
|
||||
//
|
||||
// TEST: Use v1 serum3_settle_funds
|
||||
//
|
||||
let deposit_amount = deposit_amount as i64;
|
||||
let amount = 200000;
|
||||
let quote_fees_start = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
let quote_start = account_position(solana, account, quote_bank).await;
|
||||
let quote2_start = account_position(solana, account2, quote_bank).await;
|
||||
let base_start = account_position(solana, account, base_bank).await;
|
||||
let base2_start = account_position(solana, account2, base_bank).await;
|
||||
|
||||
// account2 has an order on the book, account takes
|
||||
order_placer2.bid(1.0, amount as u64).await.unwrap();
|
||||
order_placer.ask(1.0, amount as u64).await.unwrap();
|
||||
|
||||
context
|
||||
.serum
|
||||
.consume_spot_events(
|
||||
&serum_market_cookie,
|
||||
&[order_placer.open_orders, order_placer2.open_orders],
|
||||
)
|
||||
.await;
|
||||
|
||||
order_placer.settle_v2(true).await;
|
||||
order_placer2.settle_v2(true).await;
|
||||
|
||||
let quote_end = account_position(solana, account, quote_bank).await;
|
||||
let quote2_end = account_position(solana, account2, quote_bank).await;
|
||||
let base_end = account_position(solana, account, base_bank).await;
|
||||
let base2_end = account_position(solana, account2, base_bank).await;
|
||||
|
||||
let lof = loan_origination_fee(amount - deposit_amount);
|
||||
assert_eq!(base_start - amount - lof, base_end);
|
||||
assert_eq!(base2_start + amount, base2_end);
|
||||
assert_eq!(quote_start + amount - serum_taker_fee(amount), quote_end);
|
||||
assert_eq!(
|
||||
quote2_start - amount + serum_maker_rebate(amount) - lof - 1,
|
||||
quote2_end
|
||||
);
|
||||
|
||||
let quote_fees_end = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
assert!(assert_equal(
|
||||
quote_fees_end - quote_fees_start,
|
||||
(lof + serum_referrer_fee(amount)) as f64,
|
||||
0.1
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_serum_settle_v2_to_account() -> Result<(), TransportError> {
|
||||
let mut test_builder = TestContextBuilder::new();
|
||||
test_builder.test().set_compute_max_units(95_000); // Serum3PlaceOrder needs 92.8k
|
||||
let context = test_builder.start_default().await;
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
//
|
||||
// SETUP: Create a group, accounts, market etc
|
||||
//
|
||||
let deposit_amount = 160000;
|
||||
let CommonSetup {
|
||||
serum_market_cookie,
|
||||
quote_bank,
|
||||
base_bank,
|
||||
mut order_placer,
|
||||
mut order_placer2,
|
||||
..
|
||||
} = common_setup(&context, deposit_amount).await;
|
||||
let account = order_placer.account;
|
||||
let account2 = order_placer2.account;
|
||||
|
||||
let serum_taker_fee = |amount: i64| (amount as f64 * 0.0004).trunc() as i64;
|
||||
let serum_maker_rebate = |amount: i64| (amount as f64 * 0.0002).floor() as i64;
|
||||
let serum_referrer_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;
|
||||
|
||||
//
|
||||
// TEST: Use v1 serum3_settle_funds
|
||||
//
|
||||
let deposit_amount = deposit_amount as i64;
|
||||
let amount = 200000;
|
||||
let quote_fees_start = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
let quote_start = account_position(solana, account, quote_bank).await;
|
||||
let quote2_start = account_position(solana, account2, quote_bank).await;
|
||||
let base_start = account_position(solana, account, base_bank).await;
|
||||
let base2_start = account_position(solana, account2, base_bank).await;
|
||||
|
||||
// account2 has an order on the book, account takes
|
||||
order_placer2.bid(1.0, amount as u64).await.unwrap();
|
||||
order_placer.ask(1.0, amount as u64).await.unwrap();
|
||||
|
||||
context
|
||||
.serum
|
||||
.consume_spot_events(
|
||||
&serum_market_cookie,
|
||||
&[order_placer.open_orders, order_placer2.open_orders],
|
||||
)
|
||||
.await;
|
||||
|
||||
order_placer.settle_v2(false).await;
|
||||
order_placer2.settle_v2(false).await;
|
||||
|
||||
let quote_end = account_position(solana, account, quote_bank).await;
|
||||
let quote2_end = account_position(solana, account2, quote_bank).await;
|
||||
let base_end = account_position(solana, account, base_bank).await;
|
||||
let base2_end = account_position(solana, account2, base_bank).await;
|
||||
|
||||
let lof = loan_origination_fee(amount - deposit_amount);
|
||||
assert_eq!(base_start - amount - lof, base_end);
|
||||
assert_eq!(base2_start + amount, base2_end);
|
||||
assert_eq!(
|
||||
quote_start + amount - serum_taker_fee(amount) + serum_referrer_fee(amount),
|
||||
quote_end
|
||||
);
|
||||
assert_eq!(
|
||||
quote2_start - amount + serum_maker_rebate(amount) - lof - 1,
|
||||
quote2_end
|
||||
);
|
||||
|
||||
let quote_fees_end = solana
|
||||
.get_account::<Bank>(quote_bank)
|
||||
.await
|
||||
.collected_fees_native;
|
||||
assert!(assert_equal(
|
||||
quote_fees_end - quote_fees_start,
|
||||
lof as f64,
|
||||
0.1
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct CommonSetup {
|
||||
_group_with_tokens: GroupWithTokens,
|
||||
serum_market_cookie: SpotMarketCookie,
|
||||
quote_bank: Pubkey,
|
||||
base_bank: Pubkey,
|
||||
order_placer: SerumOrderPlacer,
|
||||
order_placer2: SerumOrderPlacer,
|
||||
}
|
||||
|
||||
async fn common_setup(context: &TestContext, deposit_amount: u64) -> CommonSetup {
|
||||
let admin = TestKeypair::new();
|
||||
let owner = context.users[0].key;
|
||||
let payer = context.users[1].key;
|
||||
let mints = &context.mints[0..3];
|
||||
|
||||
//
|
||||
// SETUP: Create a group and an account
|
||||
//
|
||||
let solana = &context.solana.clone();
|
||||
|
||||
let GroupWithTokens { group, tokens, .. } = GroupWithTokensConfig {
|
||||
let group_with_tokens = GroupWithTokensConfig {
|
||||
admin,
|
||||
payer,
|
||||
mints: mints.to_vec(),
|
||||
|
@ -334,6 +797,8 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
}
|
||||
.create(solana)
|
||||
.await;
|
||||
let group = group_with_tokens.group;
|
||||
let tokens = group_with_tokens.tokens.clone();
|
||||
let base_token = &tokens[1];
|
||||
let base_bank = base_token.bank;
|
||||
let quote_token = &tokens[0];
|
||||
|
@ -370,7 +835,6 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
//
|
||||
// SETUP: Create accounts
|
||||
//
|
||||
let deposit_amount = 180000;
|
||||
let account = create_funded_account(
|
||||
&solana,
|
||||
group,
|
||||
|
@ -432,7 +896,7 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
.unwrap()
|
||||
.open_orders;
|
||||
|
||||
let mut order_placer = SerumOrderPlacer {
|
||||
let order_placer = SerumOrderPlacer {
|
||||
solana: solana.clone(),
|
||||
serum: context.serum.clone(),
|
||||
account,
|
||||
|
@ -441,7 +905,7 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
open_orders,
|
||||
next_client_order_id: 0,
|
||||
};
|
||||
let mut order_placer2 = SerumOrderPlacer {
|
||||
let order_placer2 = SerumOrderPlacer {
|
||||
solana: solana.clone(),
|
||||
serum: context.serum.clone(),
|
||||
account: account2,
|
||||
|
@ -451,163 +915,12 @@ async fn test_serum_loan_origination_fees() -> Result<(), TransportError> {
|
|||
next_client_order_id: 100000,
|
||||
};
|
||||
|
||||
//
|
||||
// TEST: Placing and canceling an order does not take loan origination fees even if borrows are needed
|
||||
//
|
||||
{
|
||||
let (bid_order_id, _) = order_placer.bid(1.0, 200000).await.unwrap();
|
||||
let (ask_order_id, _) = order_placer.ask(2.0, 200000).await.unwrap();
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 19999); // rounded
|
||||
assert_eq!(o.quote_borrows_without_fee, 19999);
|
||||
|
||||
order_placer.cancel(bid_order_id).await;
|
||||
order_placer.cancel(ask_order_id).await;
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 19999); // unchanged
|
||||
assert_eq!(o.quote_borrows_without_fee, 19999);
|
||||
|
||||
// placing new, slightly larger orders increases the borrow_without_fee amount only by a small amount
|
||||
let (bid_order_id, _) = order_placer.bid(1.0, 210000).await.unwrap();
|
||||
let (ask_order_id, _) = order_placer.ask(2.0, 300000).await.unwrap();
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 119998); // rounded
|
||||
assert_eq!(o.quote_borrows_without_fee, 29998);
|
||||
|
||||
order_placer.cancel(bid_order_id).await;
|
||||
order_placer.cancel(ask_order_id).await;
|
||||
|
||||
// returns all the funds
|
||||
order_placer.settle().await;
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 0);
|
||||
assert_eq!(o.quote_borrows_without_fee, 0);
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account, quote_bank).await,
|
||||
deposit_amount as i64
|
||||
);
|
||||
assert_eq!(
|
||||
account_position(solana, account, base_bank).await,
|
||||
deposit_amount as i64
|
||||
);
|
||||
|
||||
// consume all the out events from the cancels
|
||||
context
|
||||
.serum
|
||||
.consume_spot_events(&serum_market_cookie, &[open_orders])
|
||||
.await;
|
||||
CommonSetup {
|
||||
_group_with_tokens: group_with_tokens,
|
||||
serum_market_cookie,
|
||||
quote_bank,
|
||||
base_bank,
|
||||
order_placer,
|
||||
order_placer2,
|
||||
}
|
||||
|
||||
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.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;
|
||||
|
||||
//
|
||||
// TEST: Order execution and settling charges borrow fee
|
||||
//
|
||||
{
|
||||
let deposit_amount = deposit_amount as i64;
|
||||
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();
|
||||
|
||||
// account takes
|
||||
order_placer.ask(1.0, ask_amount as u64).await.unwrap();
|
||||
order_placer.settle().await;
|
||||
|
||||
let o = order_placer.mango_serum_orders().await;
|
||||
// parts of the order ended up on the book an may cause loan origination fees later
|
||||
assert_eq!(
|
||||
o.base_borrows_without_fee,
|
||||
(ask_amount - fill_amount) as u64
|
||||
);
|
||||
assert_eq!(o.quote_borrows_without_fee, 0);
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account, quote_bank).await,
|
||||
deposit_amount + without_serum_taker_fee(fill_amount)
|
||||
);
|
||||
assert_eq!(
|
||||
account_position(solana, account, base_bank).await,
|
||||
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
|
||||
.consume_spot_events(&serum_market_cookie, &[open_orders, open_orders2])
|
||||
.await;
|
||||
order_placer2.settle().await;
|
||||
|
||||
let o = order_placer2.mango_serum_orders().await;
|
||||
assert_eq!(o.base_borrows_without_fee, 0);
|
||||
assert_eq!(o.quote_borrows_without_fee, 0);
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account2, base_bank).await,
|
||||
deposit_amount + fill_amount
|
||||
);
|
||||
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) - 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
|
||||
));
|
||||
|
||||
let account_data = solana.get_account::<MangoAccount>(account).await;
|
||||
assert_eq!(
|
||||
account_data.buyback_fees_accrued_current,
|
||||
serum_fee(fill_amount) as u64
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
account_position(solana, account, quote_bank).await,
|
||||
deposit_amount + without_serum_taker_fee(fill_amount)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -2417,6 +2417,92 @@ impl ClientInstruction for Serum3SettleFundsInstruction {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Serum3SettleFundsV2Instruction {
|
||||
pub account: Pubkey,
|
||||
pub owner: TestKeypair,
|
||||
|
||||
pub serum_market: Pubkey,
|
||||
pub fees_to_dao: bool,
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl ClientInstruction for Serum3SettleFundsV2Instruction {
|
||||
type Accounts = mango_v4::accounts::Serum3SettleFundsV2;
|
||||
type Instruction = mango_v4::instruction::Serum3SettleFundsV2;
|
||||
async fn to_instruction(
|
||||
&self,
|
||||
account_loader: impl ClientAccountLoader + 'async_trait,
|
||||
) -> (Self::Accounts, instruction::Instruction) {
|
||||
let program_id = mango_v4::id();
|
||||
let instruction = Self::Instruction {
|
||||
fees_to_dao: self.fees_to_dao,
|
||||
};
|
||||
|
||||
let account = account_loader
|
||||
.load_mango_account(&self.account)
|
||||
.await
|
||||
.unwrap();
|
||||
let serum_market: Serum3Market = account_loader.load(&self.serum_market).await.unwrap();
|
||||
let open_orders = account
|
||||
.serum3_orders(serum_market.market_index)
|
||||
.unwrap()
|
||||
.open_orders;
|
||||
let quote_info =
|
||||
get_mint_info_by_token_index(&account_loader, &account, serum_market.quote_token_index)
|
||||
.await;
|
||||
let base_info =
|
||||
get_mint_info_by_token_index(&account_loader, &account, serum_market.base_token_index)
|
||||
.await;
|
||||
|
||||
let market_external_bytes = account_loader
|
||||
.load_bytes(&serum_market.serum_market_external)
|
||||
.await
|
||||
.unwrap();
|
||||
let market_external: &serum_dex::state::MarketState = bytemuck::from_bytes(
|
||||
&market_external_bytes[5..5 + std::mem::size_of::<serum_dex::state::MarketState>()],
|
||||
);
|
||||
// unpack the data, to avoid unaligned references
|
||||
let coin_vault = market_external.coin_vault;
|
||||
let pc_vault = market_external.pc_vault;
|
||||
let vault_signer = serum_dex::state::gen_vault_signer_key(
|
||||
market_external.vault_signer_nonce,
|
||||
&serum_market.serum_market_external,
|
||||
&serum_market.serum_program,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let accounts = Self::Accounts {
|
||||
v1: mango_v4::accounts::Serum3SettleFunds {
|
||||
group: account.fixed.group,
|
||||
account: self.account,
|
||||
open_orders,
|
||||
quote_bank: quote_info.first_bank(),
|
||||
quote_vault: quote_info.first_vault(),
|
||||
base_bank: base_info.first_bank(),
|
||||
base_vault: base_info.first_vault(),
|
||||
serum_market: self.serum_market,
|
||||
serum_program: serum_market.serum_program,
|
||||
serum_market_external: serum_market.serum_market_external,
|
||||
market_base_vault: from_serum_style_pubkey(&coin_vault),
|
||||
market_quote_vault: from_serum_style_pubkey(&pc_vault),
|
||||
market_vault_signer: vault_signer,
|
||||
owner: self.owner.pubkey(),
|
||||
token_program: Token::id(),
|
||||
},
|
||||
v2: mango_v4::accounts::Serum3SettleFundsV2Extra {
|
||||
quote_oracle: quote_info.oracle,
|
||||
base_oracle: base_info.oracle,
|
||||
},
|
||||
};
|
||||
|
||||
let instruction = make_instruction(program_id, &accounts, instruction);
|
||||
(accounts, instruction)
|
||||
}
|
||||
|
||||
fn signers(&self) -> Vec<TestKeypair> {
|
||||
vec![self.owner]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Serum3LiqForceCancelOrdersInstruction {
|
||||
pub account: Pubkey,
|
||||
pub serum_market: Pubkey,
|
||||
|
|
|
@ -77,6 +77,7 @@ export type MangoClientOptions = {
|
|||
postSendTxCallback?: ({ txid }: { txid: string }) => void;
|
||||
prioritizationFee?: number;
|
||||
txConfirmationCommitment?: Commitment;
|
||||
openbookFeesToDao?: boolean;
|
||||
};
|
||||
|
||||
export class MangoClient {
|
||||
|
@ -84,6 +85,7 @@ export class MangoClient {
|
|||
private postSendTxCallback?: ({ txid }) => void;
|
||||
private prioritizationFee: number;
|
||||
private txConfirmationCommitment: Commitment;
|
||||
private openbookFeesToDao: boolean;
|
||||
|
||||
constructor(
|
||||
public program: Program<MangoV4>,
|
||||
|
@ -94,6 +96,7 @@ export class MangoClient {
|
|||
this.idsSource = opts?.idsSource || 'get-program-accounts';
|
||||
this.prioritizationFee = opts?.prioritizationFee || 0;
|
||||
this.postSendTxCallback = opts?.postSendTxCallback;
|
||||
this.openbookFeesToDao = opts?.openbookFeesToDao ?? true;
|
||||
this.txConfirmationCommitment =
|
||||
opts?.txConfirmationCommitment ??
|
||||
(program.provider as AnchorProvider).opts.commitment ??
|
||||
|
@ -1638,6 +1641,12 @@ export class MangoClient {
|
|||
mangoAccount: MangoAccount,
|
||||
externalMarketPk: PublicKey,
|
||||
): Promise<TransactionInstruction> {
|
||||
if (this.openbookFeesToDao == false) {
|
||||
throw new Error(
|
||||
`openbookFeesToDao is set to false, please use serum3SettleFundsV2Ix`,
|
||||
);
|
||||
}
|
||||
|
||||
const serum3Market = group.serum3MarketsMapByExternal.get(
|
||||
externalMarketPk.toBase58(),
|
||||
)!;
|
||||
|
@ -1682,12 +1691,73 @@ export class MangoClient {
|
|||
return ix;
|
||||
}
|
||||
|
||||
public async serum3SettleFundsV2Ix(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
externalMarketPk: PublicKey,
|
||||
): Promise<TransactionInstruction> {
|
||||
const serum3Market = group.serum3MarketsMapByExternal.get(
|
||||
externalMarketPk.toBase58(),
|
||||
)!;
|
||||
const serum3MarketExternal = group.serum3ExternalMarketsMap.get(
|
||||
externalMarketPk.toBase58(),
|
||||
)!;
|
||||
|
||||
const [serum3MarketExternalVaultSigner, openOrderPublicKey] =
|
||||
await Promise.all([
|
||||
generateSerum3MarketExternalVaultSignerAddress(
|
||||
this.cluster,
|
||||
serum3Market,
|
||||
serum3MarketExternal,
|
||||
),
|
||||
serum3Market.findOoPda(this.program.programId, mangoAccount.publicKey),
|
||||
]);
|
||||
|
||||
const ix = await this.program.methods
|
||||
.serum3SettleFundsV2(this.openbookFeesToDao)
|
||||
.accounts({
|
||||
v1: {
|
||||
group: group.publicKey,
|
||||
account: mangoAccount.publicKey,
|
||||
owner: (this.program.provider as AnchorProvider).wallet.publicKey,
|
||||
openOrders: openOrderPublicKey,
|
||||
serumMarket: serum3Market.publicKey,
|
||||
serumProgram: OPENBOOK_PROGRAM_ID[this.cluster],
|
||||
serumMarketExternal: serum3Market.serumMarketExternal,
|
||||
marketBaseVault: serum3MarketExternal.decoded.baseVault,
|
||||
marketQuoteVault: serum3MarketExternal.decoded.quoteVault,
|
||||
marketVaultSigner: serum3MarketExternalVaultSigner,
|
||||
quoteBank: group.getFirstBankByTokenIndex(
|
||||
serum3Market.quoteTokenIndex,
|
||||
).publicKey,
|
||||
quoteVault: group.getFirstBankByTokenIndex(
|
||||
serum3Market.quoteTokenIndex,
|
||||
).vault,
|
||||
baseBank: group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex)
|
||||
.publicKey,
|
||||
baseVault: group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex)
|
||||
.vault,
|
||||
},
|
||||
v2: {
|
||||
quoteOracle: group.getFirstBankByTokenIndex(
|
||||
serum3Market.quoteTokenIndex,
|
||||
).oracle,
|
||||
baseOracle: group.getFirstBankByTokenIndex(
|
||||
serum3Market.baseTokenIndex,
|
||||
).oracle,
|
||||
},
|
||||
})
|
||||
.instruction();
|
||||
|
||||
return ix;
|
||||
}
|
||||
|
||||
public async serum3SettleFunds(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
externalMarketPk: PublicKey,
|
||||
): Promise<TransactionSignature> {
|
||||
const ix = await this.serum3SettleFundsIx(
|
||||
const ix = await this.serum3SettleFundsV2Ix(
|
||||
group,
|
||||
mangoAccount,
|
||||
externalMarketPk,
|
||||
|
@ -1745,7 +1815,7 @@ export class MangoClient {
|
|||
side,
|
||||
orderId,
|
||||
),
|
||||
this.serum3SettleFundsIx(group, mangoAccount, externalMarketPk),
|
||||
this.serum3SettleFundsV2Ix(group, mangoAccount, externalMarketPk),
|
||||
]);
|
||||
|
||||
return await this.sendAndConfirmTransactionForGroup(group, ixes);
|
||||
|
@ -3036,7 +3106,7 @@ export class MangoClient {
|
|||
side,
|
||||
orderId,
|
||||
),
|
||||
this.serum3SettleFundsIx(group, mangoAccount, externalMarketPk),
|
||||
this.serum3SettleFundsV2Ix(group, mangoAccount, externalMarketPk),
|
||||
this.serum3PlaceOrderIx(
|
||||
group,
|
||||
mangoAccount,
|
||||
|
|
|
@ -2139,6 +2139,11 @@ export type MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "serum3SettleFunds",
|
||||
"docs": [
|
||||
"Settles all free funds from the OpenOrders account into the MangoAccount.",
|
||||
"",
|
||||
"Any serum \"referrer rebates\" (ui fees) are considered Mango fees."
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
|
@ -2221,6 +2226,119 @@ export type MangoV4 = {
|
|||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "serum3SettleFundsV2",
|
||||
"docs": [
|
||||
"Like Serum3SettleFunds, but `fees_to_dao` determines if referrer rebates are considered fees",
|
||||
"or are credited to the MangoAccount."
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "v1",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "openOrders",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarket",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarketExternal",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketBaseVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketQuoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketVaultSigner",
|
||||
"isMut": false,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"needed for the automatic settle_funds call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "quoteBank",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "quoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "baseBank",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "baseVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "v2",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "quoteOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "baseOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "feesToDao",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3LiqForceCancelOrders",
|
||||
"accounts": [
|
||||
|
@ -10634,6 +10752,11 @@ export const IDL: MangoV4 = {
|
|||
},
|
||||
{
|
||||
"name": "serum3SettleFunds",
|
||||
"docs": [
|
||||
"Settles all free funds from the OpenOrders account into the MangoAccount.",
|
||||
"",
|
||||
"Any serum \"referrer rebates\" (ui fees) are considered Mango fees."
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
|
@ -10716,6 +10839,119 @@ export const IDL: MangoV4 = {
|
|||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "serum3SettleFundsV2",
|
||||
"docs": [
|
||||
"Like Serum3SettleFunds, but `fees_to_dao` determines if referrer rebates are considered fees",
|
||||
"or are credited to the MangoAccount."
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "v1",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "group",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "owner",
|
||||
"isMut": false,
|
||||
"isSigner": true
|
||||
},
|
||||
{
|
||||
"name": "openOrders",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarket",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "serumMarketExternal",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketBaseVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketQuoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "marketVaultSigner",
|
||||
"isMut": false,
|
||||
"isSigner": false,
|
||||
"docs": [
|
||||
"needed for the automatic settle_funds call"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "quoteBank",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "quoteVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "baseBank",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "baseVault",
|
||||
"isMut": true,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "tokenProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "v2",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "quoteOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
},
|
||||
{
|
||||
"name": "baseOracle",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "feesToDao",
|
||||
"type": "bool"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "serum3LiqForceCancelOrders",
|
||||
"accounts": [
|
||||
|
|
Loading…
Reference in New Issue