SerumPlaceOrder: Only pass the payer bank/vault

This commit is contained in:
Christian Kamm 2022-08-30 09:55:19 +02:00
parent 36723792a1
commit e0437305ee
7 changed files with 127 additions and 165 deletions

View File

@ -605,6 +605,10 @@ impl MangoClient {
let rates = get_fee_rates(fee_tier);
(s3.market.pc_lot_size as f64 * (1f64 + rates.0)) as u64 * (limit_price * max_base_qty)
};
let payer_mint_info = match side {
Serum3Side::Bid => s3.quote.mint_info,
Serum3Side::Ask => s3.base.mint_info,
};
self.program()
.request()
@ -616,10 +620,8 @@ impl MangoClient {
group: self.group(),
account: self.mango_account_address,
open_orders,
quote_bank: s3.quote.mint_info.first_bank(),
quote_vault: s3.quote.mint_info.first_vault(),
base_bank: s3.base.mint_info.first_bank(),
base_vault: s3.base.mint_info.first_vault(),
payer_bank: payer_mint_info.first_bank(),
payer_vault: payer_mint_info.first_vault(),
serum_market: s3.market.address,
serum_program: s3.market.market.serum_program,
serum_market_external: s3.market.market.serum_market_external,

View File

@ -169,6 +169,10 @@ pub fn serum3_liq_force_cancel_orders(
&mut base_bank,
after_base_vault,
before_base_vault,
)?;
apply_vault_difference(
&mut account.borrow_mut(),
serum_market.market_index,
&mut quote_bank,
after_quote_vault,
before_quote_vault,

View File

@ -9,7 +9,6 @@ use fixed::types::I80F48;
use num_enum::IntoPrimitive;
use num_enum::TryFromPrimitive;
use serum_dex::instruction::NewOrderInstructionV3;
use serum_dex::matching::Side;
use serum_dex::state::OpenOrders;
/// For loan origination fees bookkeeping purposes
@ -169,19 +168,13 @@ pub struct Serum3PlaceOrder<'info> {
/// CHECK: Validated by the serum cpi call
pub market_vault_signer: UncheckedAccount<'info>,
// TODO: do we need to pass both, or just payer?
// TODO: Can we reduce the number of accounts by requiring the banks
// to be in the remainingAccounts (where they need to be anyway, for
// health checks - but they need to be mut)
// token_index and bank.vault == vault is validated inline at #3
/// The bank that pays for the order, if necessary
// token_index and payer_bank.vault == payer_vault is validated inline at #3
#[account(mut, has_one = group)]
pub quote_bank: AccountLoader<'info, Bank>,
pub payer_bank: AccountLoader<'info, Bank>,
/// The bank vault that pays for the order, if necessary
#[account(mut)]
pub quote_vault: Box<Account<'info, TokenAccount>>,
#[account(mut, has_one = group)]
pub base_bank: AccountLoader<'info, Bank>,
#[account(mut)]
pub base_vault: Box<Account<'info, TokenAccount>>,
pub payer_vault: Box<Account<'info, TokenAccount>>,
pub token_program: Program<'info, Token>,
}
@ -221,25 +214,14 @@ pub fn serum3_place_order(
MangoError::SomeError
);
// Validate banks and vaults #3
let quote_bank = ctx.accounts.quote_bank.load()?;
require!(
quote_bank.vault == ctx.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()?;
require!(
base_bank.vault == ctx.accounts.base_vault.key(),
MangoError::SomeError
);
require!(
base_bank.token_index == serum_market.base_token_index,
MangoError::SomeError
);
// Validate bank and vault #3
let payer_bank = ctx.accounts.payer_bank.load()?;
require_keys_eq!(payer_bank.vault, ctx.accounts.payer_vault.key());
let payer_token_index = match side {
Serum3Side::Bid => serum_market.quote_token_index,
Serum3Side::Ask => serum_market.base_token_index,
};
require_eq!(payer_bank.token_index, payer_token_index);
}
//
@ -261,8 +243,7 @@ pub fn serum3_place_order(
// Before-order tracking
//
let before_base_vault = ctx.accounts.base_vault.amount;
let before_quote_vault = ctx.accounts.quote_vault.amount;
let before_vault = ctx.accounts.payer_vault.amount;
let before_oo = {
let oo_ai = &ctx.accounts.open_orders.as_ref();
@ -271,21 +252,17 @@ pub fn serum3_place_order(
};
// Provide a readable error message in case the vault doesn't have enough tokens
let (vault_amount, needed_amount) = match side {
Serum3Side::Ask => (
before_base_vault,
max_base_qty.saturating_sub(before_oo.native_base_free()),
),
Serum3Side::Bid => (
before_quote_vault,
max_native_quote_qty_including_fees.saturating_sub(before_oo.native_quote_free()),
),
let needed_amount = match side {
Serum3Side::Ask => max_base_qty.saturating_sub(before_oo.native_base_free()),
Serum3Side::Bid => {
max_native_quote_qty_including_fees.saturating_sub(before_oo.native_quote_free())
}
};
if vault_amount < needed_amount {
if before_vault < needed_amount {
return err!(MangoError::InsufficentBankVaultFunds).with_context(|| {
format!(
"bank vault does not have enough tokens, need {} but have {}",
needed_amount, vault_amount
needed_amount, before_vault
)
});
}
@ -318,29 +295,21 @@ pub fn serum3_place_order(
//
// After-order 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;
ctx.accounts.payer_vault.reload()?;
let after_vault = ctx.accounts.payer_vault.amount;
// Placing an order cannot increase vault balances
require_gte!(before_base_vault, after_base_vault);
require_gte!(before_quote_vault, after_quote_vault);
// Placing an order cannot increase vault balance
require_gte!(before_vault, after_vault);
// Charge the difference in vault balances to the user's account
// Charge the difference in vault balance to the user's account
let vault_difference = {
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
let mut payer_bank = ctx.accounts.payer_bank.load_mut()?;
apply_vault_difference(
&mut account.borrow_mut(),
serum_market.market_index,
&mut base_bank,
after_base_vault,
before_base_vault,
&mut quote_bank,
after_quote_vault,
before_quote_vault,
&mut payer_bank,
after_vault,
before_vault,
)?
};
@ -395,16 +364,13 @@ impl OODifference {
}
pub struct VaultDifference {
base_index: TokenIndex,
quote_index: TokenIndex,
base_native_change: I80F48,
quote_native_change: I80F48,
token_index: TokenIndex,
native_change: I80F48,
}
impl VaultDifference {
pub fn adjust_health_cache(&self, health_cache: &mut HealthCache) -> Result<()> {
health_cache.adjust_token_balance(self.base_index, self.base_native_change)?;
health_cache.adjust_token_balance(self.quote_index, self.quote_native_change)?;
health_cache.adjust_token_balance(self.token_index, self.native_change)?;
Ok(())
}
}
@ -414,74 +380,52 @@ impl VaultDifference {
pub fn apply_vault_difference(
account: &mut MangoAccountRefMut,
serum_market_index: Serum3MarketIndex,
base_bank: &mut Bank,
after_base_vault: u64,
before_base_vault: u64,
quote_bank: &mut Bank,
after_quote_vault: u64,
before_quote_vault: u64,
bank: &mut Bank,
vault_after: u64,
vault_before: u64,
) -> Result<VaultDifference> {
let base_needed_change = cm!(I80F48::from(after_base_vault) - I80F48::from(before_base_vault));
let needed_change = cm!(I80F48::from(vault_after) - I80F48::from(vault_before));
let (base_position, _) = account.token_position_mut(base_bank.token_index)?;
let base_native_before = base_position.native(&base_bank);
base_bank.change_without_fee(base_position, base_needed_change)?;
let base_native_after = base_position.native(&base_bank);
let base_native_change = cm!(base_native_after - base_native_before);
let base_borrows = base_native_change
.max(base_native_after)
.min(I80F48::ZERO)
.abs()
.to_num::<u64>();
let quote_needed_change =
cm!(I80F48::from(after_quote_vault) - I80F48::from(before_quote_vault));
let (quote_position, _) = account.token_position_mut(quote_bank.token_index)?;
let quote_native_before = quote_position.native(&quote_bank);
quote_bank.change_without_fee(quote_position, quote_needed_change)?;
let quote_native_after = quote_position.native(&quote_bank);
let quote_native_change = cm!(quote_native_after - quote_native_before);
let quote_borrows = quote_native_change
.max(quote_native_after)
let (position, _) = account.token_position_mut(bank.token_index)?;
let native_before = position.native(&bank);
bank.change_without_fee(position, needed_change)?;
let native_after = position.native(&bank);
let native_change = cm!(native_after - native_before);
let new_borrows = native_change
.max(native_after)
.min(I80F48::ZERO)
.abs()
.to_num::<u64>();
let market = account.serum3_orders_mut(serum_market_index).unwrap();
let borrows_without_fee = if bank.token_index == market.base_token_index {
&mut market.base_borrows_without_fee
} else if bank.token_index == market.quote_token_index {
&mut market.quote_borrows_without_fee
} else {
return Err(error_msg!(
"assert failed: apply_vault_difference called with bad token index"
));
};
// Only for place: Add to potential borrow amounts
market.base_borrows_without_fee = cm!(market.base_borrows_without_fee + base_borrows);
market.quote_borrows_without_fee = cm!(market.quote_borrows_without_fee + quote_borrows);
// Only for place: Add to potential borrow amount
let old_value = *borrows_without_fee;
*borrows_without_fee = cm!(old_value + new_borrows);
// Only for settle/liq_force_cancel: Reduce the potential borrow amounts
if base_needed_change > 0 {
market.base_borrows_without_fee = market
.base_borrows_without_fee
.saturating_sub(base_needed_change.to_num::<u64>());
}
if quote_needed_change > 0 {
market.quote_borrows_without_fee = market
.quote_borrows_without_fee
.saturating_sub(quote_needed_change.to_num::<u64>());
if needed_change > 0 {
*borrows_without_fee = (*borrows_without_fee).saturating_sub(needed_change.to_num::<u64>());
}
Ok(VaultDifference {
base_index: base_bank.token_index,
quote_index: quote_bank.token_index,
base_native_change,
quote_native_change,
token_index: bank.token_index,
native_change,
})
}
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> {
use crate::serum3_cpi;
let order_payer_token_account = match order.side {
Side::Bid => &ctx.quote_vault,
Side::Ask => &ctx.base_vault,
};
let group = ctx.group.load()?;
serum3_cpi::PlaceOrder {
program: ctx.serum_program.to_account_info(),
@ -495,7 +439,7 @@ fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Resu
token_program: ctx.token_program.to_account_info(),
open_orders: ctx.open_orders.to_account_info(),
order_payer_token_account: order_payer_token_account.to_account_info(),
order_payer_token_account: ctx.payer_vault.to_account_info(),
user_authority: ctx.group.to_account_info(),
}
.call(&group, order)

View File

@ -164,6 +164,10 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
&mut base_bank,
after_base_vault,
before_base_vault,
)?;
apply_vault_difference(
&mut account.borrow_mut(),
serum_market.market_index,
&mut quote_bank,
after_quote_vault,
before_quote_vault,

View File

@ -1600,14 +1600,17 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
)
.await;
let (payer_bank, payer_vault) = match self.side {
Serum3Side::Bid => (quote_info.first_bank(), quote_info.first_vault()),
Serum3Side::Ask => (base_info.first_bank(), base_info.first_vault()),
};
let accounts = Self::Accounts {
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(),
payer_bank,
payer_vault,
serum_market: self.serum_market,
serum_program: serum_market.serum_program,
serum_market_external: serum_market.serum_market_external,

View File

@ -1079,6 +1079,13 @@ export class MangoClient {
.baseSizeNumberToLots(size)
.mul(serum3MarketExternal.priceNumberToLots(price)),
);
const payerTokenIndex = (() => {
if (side == Serum3Side.bid) {
return serum3Market.quoteTokenIndex;
} else {
return serum3Market.baseTokenIndex;
}
})();
return await this.program.methods
.serum3PlaceOrder(
@ -1107,14 +1114,8 @@ export class MangoClient {
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,
payerBank: group.getFirstBankByTokenIndex(payerTokenIndex).publicKey,
payerVault: group.getFirstBankByTokenIndex(payerTokenIndex).vault,
})
.remainingAccounts(
healthRemainingAccounts.map(

View File

@ -1605,24 +1605,20 @@ export type MangoV4 = {
]
},
{
"name": "quoteBank",
"name": "payerBank",
"isMut": true,
"isSigner": false
"isSigner": false,
"docs": [
"The bank that pays for the order, if necessary"
]
},
{
"name": "quoteVault",
"name": "payerVault",
"isMut": true,
"isSigner": false
},
{
"name": "baseBank",
"isMut": true,
"isSigner": false
},
{
"name": "baseVault",
"isMut": true,
"isSigner": false
"isSigner": false,
"docs": [
"The bank vault that pays for the order, if necessary"
]
},
{
"name": "tokenProgram",
@ -4015,11 +4011,17 @@ export type MangoV4 = {
"type": "publicKey"
},
{
"name": "previousNativeCoinReserved",
"name": "baseBorrowsWithoutFee",
"docs": [
"Tracks the amount of borrows that have flowed into the serum open orders account.",
"These borrows did not have the loan origination fee applied, and that may happen",
"later (in serum3_settle_funds) if we can guarantee that the funds were used.",
"In particular a place-on-book, cancel, settle should not cost fees."
],
"type": "u64"
},
{
"name": "previousNativePcReserved",
"name": "quoteBorrowsWithoutFee",
"type": "u64"
},
{
@ -6887,24 +6889,20 @@ export const IDL: MangoV4 = {
]
},
{
"name": "quoteBank",
"name": "payerBank",
"isMut": true,
"isSigner": false
"isSigner": false,
"docs": [
"The bank that pays for the order, if necessary"
]
},
{
"name": "quoteVault",
"name": "payerVault",
"isMut": true,
"isSigner": false
},
{
"name": "baseBank",
"isMut": true,
"isSigner": false
},
{
"name": "baseVault",
"isMut": true,
"isSigner": false
"isSigner": false,
"docs": [
"The bank vault that pays for the order, if necessary"
]
},
{
"name": "tokenProgram",
@ -9297,11 +9295,17 @@ export const IDL: MangoV4 = {
"type": "publicKey"
},
{
"name": "previousNativeCoinReserved",
"name": "baseBorrowsWithoutFee",
"docs": [
"Tracks the amount of borrows that have flowed into the serum open orders account.",
"These borrows did not have the loan origination fee applied, and that may happen",
"later (in serum3_settle_funds) if we can guarantee that the funds were used.",
"In particular a place-on-book, cancel, settle should not cost fees."
],
"type": "u64"
},
{
"name": "previousNativePcReserved",
"name": "quoteBorrowsWithoutFee",
"type": "u64"
},
{