Merge pull request #99 from blockworks-foundation/ckamm/deactivate-token-positions

Deactivate token positions for liquidation and serum instructions
This commit is contained in:
Christian Kamm 2022-07-08 09:11:55 +02:00 committed by GitHub
commit 420132cf89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 141 additions and 124 deletions

View File

@ -87,8 +87,7 @@ pub fn liq_token_bankruptcy(
let liab_bank = bank_ais[0].load::<Bank>()?;
let liab_deposit_index = liab_bank.deposit_index;
let (liqee_liab, liqee_raw_token_index, _) =
liqee.tokens.get_mut_or_create(liab_token_index)?;
let (liqee_liab, liqee_raw_token_index) = liqee.tokens.get_mut(liab_token_index)?;
let mut remaining_liab_loss = -liqee_liab.native(&liab_bank);
require_gt!(remaining_liab_loss, I80F48::ZERO);
drop(liab_bank);

View File

@ -72,13 +72,14 @@ pub fn liq_token_with_token(
account_retriever.banks_mut_and_oracles(asset_token_index, liab_token_index)?;
let (liab_bank, liab_price) = opt_liab_bank_and_price.unwrap();
let liqee_assets_native = liqee
.tokens
.get(asset_bank.token_index)?
.native(&asset_bank);
// The main complication here is that we can't keep the liqee_asset_position and liqee_liab_position
// borrows alive at the same time. Possibly adding get_mut_pair() would be helpful.
let (liqee_asset_position, liqee_asset_raw_index) = liqee.tokens.get(asset_token_index)?;
let liqee_assets_native = liqee_asset_position.native(&asset_bank);
require!(liqee_assets_native.is_positive(), MangoError::SomeError);
let liqee_liab_native = liqee.tokens.get(liab_bank.token_index)?.native(&liab_bank);
let (liqee_liab_position, liqee_liab_raw_index) = liqee.tokens.get(liab_token_index)?;
let liqee_liab_native = liqee_liab_position.native(&liab_bank);
require!(liqee_liab_native.is_negative(), MangoError::SomeError);
// TODO why sum of both tokens liquidation fees? Add comment
@ -116,18 +117,24 @@ pub fn liq_token_with_token(
let asset_transfer = cm!(liab_transfer * liab_price_adjusted / asset_price);
// Apply the balance changes to the liqor and liqee accounts
liab_bank.deposit(liqee.tokens.get_mut(liab_token_index)?, liab_transfer)?;
liab_bank.withdraw_with_fee(
liqor.tokens.get_mut_or_create(liab_token_index)?.0,
liab_transfer,
)?;
let liqee_liab_position = liqee.tokens.get_mut_raw(liqee_liab_raw_index);
let liqee_liab_active = liab_bank.deposit(liqee_liab_position, liab_transfer)?;
let liqee_liab_position_indexed = liqee_liab_position.indexed_position;
asset_bank.deposit(
liqor.tokens.get_mut_or_create(asset_token_index)?.0,
asset_transfer,
)?;
asset_bank
.withdraw_without_fee(liqee.tokens.get_mut(asset_token_index)?, asset_transfer)?;
let (liqor_liab_position, liqor_liab_raw_index, _) =
liqor.tokens.get_mut_or_create(liab_token_index)?;
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
let liqor_liab_position_indexed = liqor_liab_position.indexed_position;
let (liqor_asset_position, liqor_asset_raw_index, _) =
liqor.tokens.get_mut_or_create(asset_token_index)?;
let liqor_asset_active = asset_bank.deposit(liqor_asset_position, asset_transfer)?;
let liqor_asset_position_indexed = liqor_asset_position.indexed_position;
let liqee_asset_position = liqee.tokens.get_mut_raw(liqee_asset_raw_index);
let liqee_asset_active =
asset_bank.withdraw_without_fee(liqee_asset_position, asset_transfer)?;
let liqee_asset_position_indexed = liqee_asset_position.indexed_position;
// Update the health cache
liqee_health_cache.adjust_token_balance(liab_token_index, liab_transfer)?;
@ -155,11 +162,7 @@ pub fn liq_token_with_token(
emit!(TokenBalanceLog {
mango_account: ctx.accounts.liqee.key(),
token_index: asset_token_index,
indexed_position: liqee
.tokens
.get_mut(asset_token_index)?
.indexed_position
.to_bits(),
indexed_position: liqee_asset_position_indexed.to_bits(),
deposit_index: asset_bank.deposit_index.to_bits(),
borrow_index: asset_bank.borrow_index.to_bits(),
price: asset_price.to_bits(),
@ -168,11 +171,7 @@ pub fn liq_token_with_token(
emit!(TokenBalanceLog {
mango_account: ctx.accounts.liqee.key(),
token_index: liab_token_index,
indexed_position: liqee
.tokens
.get_mut(liab_token_index)?
.indexed_position
.to_bits(),
indexed_position: liqee_liab_position_indexed.to_bits(),
deposit_index: liab_bank.deposit_index.to_bits(),
borrow_index: liab_bank.borrow_index.to_bits(),
price: liab_price.to_bits(),
@ -181,11 +180,7 @@ pub fn liq_token_with_token(
emit!(TokenBalanceLog {
mango_account: ctx.accounts.liqor.key(),
token_index: asset_token_index,
indexed_position: liqor
.tokens
.get_mut(asset_token_index)?
.indexed_position
.to_bits(),
indexed_position: liqor_asset_position_indexed.to_bits(),
deposit_index: asset_bank.deposit_index.to_bits(),
borrow_index: asset_bank.borrow_index.to_bits(),
price: asset_price.to_bits(),
@ -194,15 +189,25 @@ pub fn liq_token_with_token(
emit!(TokenBalanceLog {
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
indexed_position: liqor
.tokens
.get_mut(liab_token_index)?
.indexed_position
.to_bits(),
indexed_position: liqor_liab_position_indexed.to_bits(),
deposit_index: liab_bank.deposit_index.to_bits(),
borrow_index: liab_bank.borrow_index.to_bits(),
price: liab_price.to_bits(),
});
// Since we use a scanning account retriever, it's safe to deactivate inactive token positions
if !liqee_asset_active {
liqee.tokens.deactivate(liqee_asset_raw_index);
}
if !liqee_liab_active {
liqee.tokens.deactivate(liqee_liab_raw_index);
}
if !liqor_asset_active {
liqor.tokens.deactivate(liqor_asset_raw_index);
}
if !liqor_liab_active {
liqor.tokens.deactivate(liqor_liab_raw_index)
}
}
// Check liqee health again
@ -221,7 +226,5 @@ pub fn liq_token_with_token(
let liqor_health = compute_health(&liqor, HealthType::Init, &account_retriever)?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
// TOOD: this must deactivate token accounts if the deposit/withdraw calls above call for it
Ok(())
}

View File

@ -1,8 +1,8 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{Token, TokenAccount};
use fixed::types::I80F48;
use crate::error::*;
use crate::instructions::apply_vault_difference;
use crate::state::*;
#[derive(Accounts)]
@ -139,23 +139,19 @@ pub fn serum3_liq_force_cancel_orders(
let after_quote_vault = ctx.accounts.quote_vault.amount;
// Charge the difference in vault balances to the user's account
{
let mut account = ctx.accounts.account.load_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let base_position = account.tokens.get_mut(base_bank.token_index)?;
base_bank.deposit(
base_position,
I80F48::from(after_base_vault) - I80F48::from(before_base_vault),
)?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
let quote_position = account.tokens.get_mut(quote_bank.token_index)?;
quote_bank.deposit(
quote_position,
I80F48::from(after_quote_vault) - I80F48::from(before_quote_vault),
)?;
}
let mut account = ctx.accounts.account.load_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference(
&mut account,
&mut base_bank,
after_base_vault,
before_base_vault,
&mut quote_bank,
after_quote_vault,
before_quote_vault,
)?
.deactivate_inactive_token_accounts(&mut account);
Ok(())
}

View File

@ -260,25 +260,31 @@ pub fn serum3_place_order(
let after_quote_vault = ctx.accounts.quote_vault.amount;
// Charge the difference in vault balances to the user's account
apply_vault_difference(
ctx.accounts.account.load_mut()?,
ctx.accounts.base_bank.load_mut()?,
after_base_vault,
before_base_vault,
ctx.accounts.quote_bank.load_mut()?,
after_quote_vault,
before_quote_vault,
)?;
let mut account = ctx.accounts.account.load_mut()?;
let vault_difference_result = {
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference(
&mut account,
&mut base_bank,
after_base_vault,
before_base_vault,
&mut quote_bank,
after_quote_vault,
before_quote_vault,
)?
};
//
// Health check
//
let account = ctx.accounts.account.load()?;
let retriever = new_fixed_order_account_retriever(ctx.remaining_accounts, &account)?;
let health = compute_health(&account, HealthType::Init, &retriever)?;
msg!("health: {}", health);
require!(health >= 0, MangoError::HealthMustBePositive);
vault_difference_result.deactivate_inactive_token_accounts(&mut account);
Ok(())
}
@ -305,28 +311,51 @@ pub fn inc_maybe_loan(
}
}
pub struct VaultDifferenceResult {
base_raw_index: usize,
base_active: bool,
quote_raw_index: usize,
quote_active: bool,
}
impl VaultDifferenceResult {
pub fn deactivate_inactive_token_accounts(&self, account: &mut MangoAccount) {
if !self.base_active {
account.tokens.deactivate(self.base_raw_index);
}
if !self.quote_active {
account.tokens.deactivate(self.quote_raw_index);
}
}
}
pub fn apply_vault_difference(
mut account: std::cell::RefMut<MangoAccount>,
mut base_bank: std::cell::RefMut<Bank>,
account: &mut MangoAccount,
base_bank: &mut Bank,
after_base_vault: u64,
before_base_vault: u64,
mut quote_bank: std::cell::RefMut<Bank>,
quote_bank: &mut Bank,
after_quote_vault: u64,
before_quote_vault: u64,
) -> Result<()> {
) -> Result<VaultDifferenceResult> {
// TODO: Applying the loan origination fee here may be too early: it should only be
// charged if an order executes and the loan materializes? Otherwise MMs that place
// an order without having the funds will be charged for each place_order!
let base_position = account.tokens.get_mut(base_bank.token_index)?;
let (base_position, base_raw_index) = account.tokens.get_mut(base_bank.token_index)?;
let base_change = I80F48::from(after_base_vault) - I80F48::from(before_base_vault);
base_bank.change_with_fee(base_position, base_change)?;
let base_active = base_bank.change_with_fee(base_position, base_change)?;
let quote_position = account.tokens.get_mut(quote_bank.token_index)?;
let (quote_position, quote_raw_index) = account.tokens.get_mut(quote_bank.token_index)?;
let quote_change = I80F48::from(after_quote_vault) - I80F48::from(before_quote_vault);
quote_bank.change_with_fee(quote_position, quote_change)?;
let quote_active = quote_bank.change_with_fee(quote_position, quote_change)?;
Ok(())
Ok(VaultDifferenceResult {
base_raw_index,
base_active,
quote_raw_index,
quote_active,
})
}
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> {

View File

@ -149,17 +149,19 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
let after_quote_vault = ctx.accounts.quote_vault.amount;
// Charge the difference in vault balances to the user's account
let base_bank = ctx.accounts.base_bank.load_mut()?;
let quote_bank = ctx.accounts.quote_bank.load_mut()?;
let mut account = ctx.accounts.account.load_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.load_mut()?,
base_bank,
&mut account,
&mut base_bank,
after_base_vault,
before_base_vault,
quote_bank,
&mut quote_bank,
after_quote_vault,
before_quote_vault,
)?;
)?
.deactivate_inactive_token_accounts(&mut account);
}
Ok(())
@ -185,7 +187,7 @@ pub fn charge_maybe_fees(
serum3_account.previous_native_coin_reserved = after_oo.native_coin_reserved();
// loan origination fees
let coin_token_account = account.tokens.get_mut(coin_bank.token_index)?;
let coin_token_account = account.tokens.get_mut(coin_bank.token_index)?.0;
let coin_token_native = coin_token_account.native(&coin_bank);
if coin_token_native.is_negative() {
@ -195,7 +197,7 @@ pub fn charge_maybe_fees(
// charge the loan origination fee
coin_bank
.borrow_mut()
.charge_loan_origination_fee(coin_token_account, actualized_loan)?;
.withdraw_loan_origination_fee(coin_token_account, actualized_loan)?;
}
}
@ -209,7 +211,7 @@ pub fn charge_maybe_fees(
serum3_account.previous_native_pc_reserved = after_oo.native_pc_reserved();
// loan origination fees
let pc_token_account = account.tokens.get_mut(pc_bank.token_index)?;
let pc_token_account = account.tokens.get_mut(pc_bank.token_index)?.0;
let pc_token_native = pc_token_account.native(&pc_bank);
if pc_token_native.is_negative() {
@ -219,7 +221,7 @@ pub fn charge_maybe_fees(
// charge the loan origination fee
pc_bank
.borrow_mut()
.charge_loan_origination_fee(pc_token_account, actualized_loan)?;
.withdraw_loan_origination_fee(pc_token_account, actualized_loan)?;
}
}

View File

@ -320,7 +320,9 @@ impl Bank {
}
if with_loan_origination_fee {
self.charge_loan_origination_fee(position, native_amount)?;
let loan_origination_fee = cm!(self.loan_origination_fee_rate * native_amount);
self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee);
native_amount = cm!(native_amount + loan_origination_fee);
}
// add to borrows
@ -331,21 +333,17 @@ impl Bank {
Ok(true)
}
// charge only loan origination fee, assuming borrow has already happened
pub fn charge_loan_origination_fee(
// withdraw the loan origination fee for a borrow that happenend earlier
pub fn withdraw_loan_origination_fee(
&mut self,
position: &mut TokenPosition,
already_borrowed_native_amount: I80F48,
) -> Result<()> {
) -> Result<bool> {
let loan_origination_fee =
cm!(self.loan_origination_fee_rate * already_borrowed_native_amount);
self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee);
let indexed_change = cm!(loan_origination_fee / self.borrow_index);
self.indexed_borrows = cm!(self.indexed_borrows + indexed_change);
position.indexed_position = cm!(position.indexed_position - indexed_change);
Ok(())
self.withdraw_internal(position, loan_origination_fee, false)
}
/// Change a position without applying the loan origination fee

View File

@ -121,17 +121,25 @@ impl MangoAccountTokenPositions {
}
}
pub fn get(&self, token_index: TokenIndex) -> Result<&TokenPosition> {
/// Returns
/// - the position
/// - the raw index into the token positions list (for use with get_raw/deactivate)
pub fn get(&self, token_index: TokenIndex) -> Result<(&TokenPosition, usize)> {
self.values
.iter()
.find(|p| p.is_active_for_token(token_index))
.enumerate()
.find_map(|(raw_index, p)| p.is_active_for_token(token_index).then(|| (p, raw_index)))
.ok_or_else(|| error!(MangoError::SomeError)) // TODO: not found error
}
pub fn get_mut(&mut self, token_index: TokenIndex) -> Result<&mut TokenPosition> {
/// Returns
/// - the position
/// - the raw index into the token positions list (for use with get_raw/deactivate)
pub fn get_mut(&mut self, token_index: TokenIndex) -> Result<(&mut TokenPosition, usize)> {
self.values
.iter_mut()
.find(|p| p.is_active_for_token(token_index))
.enumerate()
.find_map(|(raw_index, p)| p.is_active_for_token(token_index).then(|| (p, raw_index)))
.ok_or_else(|| error!(MangoError::SomeError)) // TODO: not found error
}

View File

@ -205,10 +205,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
)
.await
.unwrap();
assert_eq!(
account_position(solana, account, collateral_token1.bank).await,
0
);
assert!(account_position_closed(solana, account, collateral_token1.bank).await);
assert_eq!(
account_position(solana, account, borrow_token1.bank).await,
(-350.0f64 + (1000.0 / 20.0 / 1.04)).round() as i64
@ -233,10 +230,7 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
)
.await
.unwrap();
assert_eq!(
account_position(solana, account, collateral_token2.bank).await,
0
);
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let borrow1_after_liq = -350.0f64 + (1000.0 / 20.0 / 1.04) + (20.0 / 20.0 / 1.04);
assert_eq!(
account_position(solana, account, borrow_token1.bank).await,
@ -518,10 +512,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
)
.await
.unwrap();
assert_eq!(
account_position(solana, account, collateral_token1.bank).await,
0
);
assert!(account_position_closed(solana, account, collateral_token1.bank).await);
let liqee: MangoAccount = solana.get_account(account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
@ -542,10 +533,7 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
)
.await
.unwrap();
assert_eq!(
account_position(solana, account, collateral_token2.bank).await,
0
);
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let liqee: MangoAccount = solana.get_account(account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());

View File

@ -387,10 +387,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
account_position(solana, account, borrow_token2.bank).await,
-50 + 19
);
assert_eq!(
account_position(solana, account, collateral_token2.bank).await,
0
);
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let liqee: MangoAccount = solana.get_account(account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
@ -416,10 +413,7 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
.unwrap();
// the asset cost for 50-19=31 borrow2 is 31 * 1.04 = 32.24
assert_eq!(
account_position(solana, account, borrow_token2.bank).await,
0
);
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
assert_eq!(
account_position(solana, account, collateral_token1.bank).await,
1000 - 32