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); let rates = get_fee_rates(fee_tier);
(s3.market.pc_lot_size as f64 * (1f64 + rates.0)) as u64 * (limit_price * max_base_qty) (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() self.program()
.request() .request()
@ -616,10 +620,8 @@ impl MangoClient {
group: self.group(), group: self.group(),
account: self.mango_account_address, account: self.mango_account_address,
open_orders, open_orders,
quote_bank: s3.quote.mint_info.first_bank(), payer_bank: payer_mint_info.first_bank(),
quote_vault: s3.quote.mint_info.first_vault(), payer_vault: payer_mint_info.first_vault(),
base_bank: s3.base.mint_info.first_bank(),
base_vault: s3.base.mint_info.first_vault(),
serum_market: s3.market.address, serum_market: s3.market.address,
serum_program: s3.market.market.serum_program, serum_program: s3.market.market.serum_program,
serum_market_external: s3.market.market.serum_market_external, 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, &mut base_bank,
after_base_vault, after_base_vault,
before_base_vault, before_base_vault,
)?;
apply_vault_difference(
&mut account.borrow_mut(),
serum_market.market_index,
&mut quote_bank, &mut quote_bank,
after_quote_vault, after_quote_vault,
before_quote_vault, before_quote_vault,

View File

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

View File

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

View File

@ -1600,14 +1600,17 @@ impl<'keypair> ClientInstruction for Serum3PlaceOrderInstruction<'keypair> {
) )
.await; .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 { let accounts = Self::Accounts {
group: account.fixed.group, group: account.fixed.group,
account: self.account, account: self.account,
open_orders, open_orders,
quote_bank: quote_info.first_bank(), payer_bank,
quote_vault: quote_info.first_vault(), payer_vault,
base_bank: base_info.first_bank(),
base_vault: base_info.first_vault(),
serum_market: self.serum_market, serum_market: self.serum_market,
serum_program: serum_market.serum_program, serum_program: serum_market.serum_program,
serum_market_external: serum_market.serum_market_external, serum_market_external: serum_market.serum_market_external,

View File

@ -1079,6 +1079,13 @@ export class MangoClient {
.baseSizeNumberToLots(size) .baseSizeNumberToLots(size)
.mul(serum3MarketExternal.priceNumberToLots(price)), .mul(serum3MarketExternal.priceNumberToLots(price)),
); );
const payerTokenIndex = (() => {
if (side == Serum3Side.bid) {
return serum3Market.quoteTokenIndex;
} else {
return serum3Market.baseTokenIndex;
}
})();
return await this.program.methods return await this.program.methods
.serum3PlaceOrder( .serum3PlaceOrder(
@ -1107,14 +1114,8 @@ export class MangoClient {
marketBaseVault: serum3MarketExternal.decoded.baseVault, marketBaseVault: serum3MarketExternal.decoded.baseVault,
marketQuoteVault: serum3MarketExternal.decoded.quoteVault, marketQuoteVault: serum3MarketExternal.decoded.quoteVault,
marketVaultSigner: serum3MarketExternalVaultSigner, marketVaultSigner: serum3MarketExternalVaultSigner,
quoteBank: group.getFirstBankByTokenIndex(serum3Market.quoteTokenIndex) payerBank: group.getFirstBankByTokenIndex(payerTokenIndex).publicKey,
.publicKey, payerVault: group.getFirstBankByTokenIndex(payerTokenIndex).vault,
quoteVault: group.getFirstBankByTokenIndex(serum3Market.quoteTokenIndex)
.vault,
baseBank: group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex)
.publicKey,
baseVault: group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex)
.vault,
}) })
.remainingAccounts( .remainingAccounts(
healthRemainingAccounts.map( healthRemainingAccounts.map(

View File

@ -1605,24 +1605,20 @@ export type MangoV4 = {
] ]
}, },
{ {
"name": "quoteBank", "name": "payerBank",
"isMut": true, "isMut": true,
"isSigner": false "isSigner": false,
"docs": [
"The bank that pays for the order, if necessary"
]
}, },
{ {
"name": "quoteVault", "name": "payerVault",
"isMut": true, "isMut": true,
"isSigner": false "isSigner": false,
}, "docs": [
{ "The bank vault that pays for the order, if necessary"
"name": "baseBank", ]
"isMut": true,
"isSigner": false
},
{
"name": "baseVault",
"isMut": true,
"isSigner": false
}, },
{ {
"name": "tokenProgram", "name": "tokenProgram",
@ -4015,11 +4011,17 @@ export type MangoV4 = {
"type": "publicKey" "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" "type": "u64"
}, },
{ {
"name": "previousNativePcReserved", "name": "quoteBorrowsWithoutFee",
"type": "u64" "type": "u64"
}, },
{ {
@ -6887,24 +6889,20 @@ export const IDL: MangoV4 = {
] ]
}, },
{ {
"name": "quoteBank", "name": "payerBank",
"isMut": true, "isMut": true,
"isSigner": false "isSigner": false,
"docs": [
"The bank that pays for the order, if necessary"
]
}, },
{ {
"name": "quoteVault", "name": "payerVault",
"isMut": true, "isMut": true,
"isSigner": false "isSigner": false,
}, "docs": [
{ "The bank vault that pays for the order, if necessary"
"name": "baseBank", ]
"isMut": true,
"isSigner": false
},
{
"name": "baseVault",
"isMut": true,
"isSigner": false
}, },
{ {
"name": "tokenProgram", "name": "tokenProgram",
@ -9297,11 +9295,17 @@ export const IDL: MangoV4 = {
"type": "publicKey" "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" "type": "u64"
}, },
{ {
"name": "previousNativePcReserved", "name": "quoteBorrowsWithoutFee",
"type": "u64" "type": "u64"
}, },
{ {