Clarkeni/loan fee logging (#180)

Logging for loan origination fees and token bankruptcy
This commit is contained in:
Nicholas Clarke 2022-08-19 18:50:54 -07:00 committed by GitHub
parent 6814701046
commit 704dfcaa27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 663 additions and 199 deletions

2
anchor

@ -1 +1 @@
Subproject commit b52f23614601652a99ec6c27aec77bd327363b31 Subproject commit 9e546f52d967e95fea4ae105f4bc7bf3720b9464

View File

@ -10,6 +10,11 @@ use crate::state::ScanningAccountRetriever;
use crate::state::*; use crate::state::*;
use crate::util::checked_math as cm; use crate::util::checked_math as cm;
use crate::logs::{
LiquidateTokenBankruptcyLog, LoanOriginationFeeInstruction, TokenBalanceLog,
WithdrawLoanOriginationFeeLog,
};
// Remaining accounts: // Remaining accounts:
// - all banks for liab_mint_info (writable) // - all banks for liab_mint_info (writable)
// - merged health accounts for liqor+liqee // - merged health accounts for liqor+liqee
@ -98,7 +103,8 @@ pub fn liq_token_bankruptcy(
let (liab_bank, liab_price, opt_quote_bank_and_price) = let (liab_bank, liab_price, opt_quote_bank_and_price) =
account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?; account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?;
let liab_deposit_index = liab_bank.deposit_index; let mut liab_deposit_index = liab_bank.deposit_index;
let liab_borrow_index = liab_bank.borrow_index;
let (liqee_liab, liqee_raw_token_index) = liqee.token_position_mut(liab_token_index)?; let (liqee_liab, liqee_raw_token_index) = liqee.token_position_mut(liab_token_index)?;
let initial_liab_native = liqee_liab.native(&liab_bank); let initial_liab_native = liqee_liab.native(&liab_bank);
let mut remaining_liab_loss = -initial_liab_native; let mut remaining_liab_loss = -initial_liab_native;
@ -151,26 +157,52 @@ pub fn liq_token_bankruptcy(
)?; )?;
// move quote assets into liqor and withdraw liab assets // move quote assets into liqor and withdraw liab assets
if let Some((quote_bank, _)) = opt_quote_bank_and_price { if let Some((quote_bank, quote_price)) = opt_quote_bank_and_price {
// account constraint #2 a) // account constraint #2 a)
require_keys_eq!(quote_bank.vault, ctx.accounts.quote_vault.key()); require_keys_eq!(quote_bank.vault, ctx.accounts.quote_vault.key());
require_keys_eq!(quote_bank.mint, ctx.accounts.insurance_vault.mint); require_keys_eq!(quote_bank.mint, ctx.accounts.insurance_vault.mint);
let quote_deposit_index = quote_bank.deposit_index;
let quote_borrow_index = quote_bank.borrow_index;
// credit the liqor // credit the liqor
let (liqor_quote, liqor_quote_raw_token_index, _) = let (liqor_quote, liqor_quote_raw_token_index, _) =
liqor.ensure_token_position(QUOTE_TOKEN_INDEX)?; liqor.ensure_token_position(QUOTE_TOKEN_INDEX)?;
let liqor_quote_active = quote_bank.deposit(liqor_quote, insurance_transfer_i80f48)?; let liqor_quote_active = quote_bank.deposit(liqor_quote, insurance_transfer_i80f48)?;
let liqor_quote_indexed_position = liqor_quote.indexed_position;
// transfer liab from liqee to liqor // transfer liab from liqee to liqor
let (liqor_liab, liqor_liab_raw_token_index, _) = let (liqor_liab, liqor_liab_raw_token_index, _) =
liqor.ensure_token_position(liab_token_index)?; liqor.ensure_token_position(liab_token_index)?;
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab, liab_transfer)?; let (liqor_liab_active, loan_origination_fee) =
liab_bank.withdraw_with_fee(liqor_liab, liab_transfer)?;
// Check liqor's health // Check liqor's health
let liqor_health = let liqor_health =
compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)?; compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive); require!(liqor_health >= 0, MangoError::HealthMustBePositive);
// liqor quote
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: QUOTE_TOKEN_INDEX,
indexed_position: liqor_quote_indexed_position.to_bits(),
deposit_index: quote_deposit_index.to_bits(),
borrow_index: quote_borrow_index.to_bits(),
price: quote_price.to_bits(),
});
if loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
loan_origination_fee: loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::LiqTokenBankruptcy
});
}
if !liqor_quote_active { if !liqor_quote_active {
liqor.deactivate_token_position(liqor_quote_raw_token_index); liqor.deactivate_token_position(liqor_quote_raw_token_index);
} }
@ -191,6 +223,7 @@ pub fn liq_token_bankruptcy(
// Socialize loss if there's more loss and noone else could use the // Socialize loss if there's more loss and noone else could use the
// insurance fund to cover it. // insurance fund to cover it.
let mut socialized_loss = I80F48::ZERO;
if insurance_fund_exhausted && remaining_liab_loss.is_positive() { if insurance_fund_exhausted && remaining_liab_loss.is_positive() {
// find the total deposits // find the total deposits
let mut indexed_total_deposits = I80F48::ZERO; let mut indexed_total_deposits = I80F48::ZERO;
@ -205,6 +238,8 @@ pub fn liq_token_bankruptcy(
// Probably not. // Probably not.
let new_deposit_index = let new_deposit_index =
cm!(liab_deposit_index - remaining_liab_loss / indexed_total_deposits); cm!(liab_deposit_index - remaining_liab_loss / indexed_total_deposits);
liab_deposit_index = new_deposit_index;
socialized_loss = remaining_liab_loss;
let mut amount_to_credit = remaining_liab_loss; let mut amount_to_credit = remaining_liab_loss;
for bank_ai in bank_ais.iter() { for bank_ai in bank_ais.iter() {
@ -228,6 +263,28 @@ pub fn liq_token_bankruptcy(
require_eq!(liqee_liab.indexed_position, I80F48::ZERO); require_eq!(liqee_liab.indexed_position, I80F48::ZERO);
} }
// liqor liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
indexed_position: liqee_liab.indexed_position.to_bits(),
deposit_index: liab_deposit_index.to_bits(),
borrow_index: liab_borrow_index.to_bits(),
price: liab_price.to_bits(),
});
// liqee liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: liab_token_index,
indexed_position: liqee_liab.indexed_position.to_bits(),
deposit_index: liab_deposit_index.to_bits(),
borrow_index: liab_borrow_index.to_bits(),
price: liab_price.to_bits(),
});
let liab_bank = bank_ais[0].load::<Bank>()?; let liab_bank = bank_ais[0].load::<Bank>()?;
let end_liab_native = liqee_liab.native(&liab_bank); let end_liab_native = liqee_liab.native(&liab_bank);
liqee_health_cache liqee_health_cache
@ -243,5 +300,17 @@ pub fn liq_token_bankruptcy(
liqee.deactivate_token_position(liqee_raw_token_index); liqee.deactivate_token_position(liqee_raw_token_index);
} }
emit!(LiquidateTokenBankruptcyLog {
mango_group: ctx.accounts.group.key(),
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
liab_token_index: liab_token_index,
initial_liab_native: initial_liab_native.to_bits(),
liab_price: liab_price.to_bits(),
insurance_token_index: QUOTE_TOKEN_INDEX,
insurance_transfer: insurance_transfer_i80f48.to_bits(),
socialized_loss: socialized_loss.to_bits()
});
Ok(()) Ok(())
} }

View File

@ -3,7 +3,10 @@ use fixed::types::I80F48;
use std::cmp::min; use std::cmp::min;
use crate::error::*; use crate::error::*;
use crate::logs::{LiquidateTokenAndTokenLog, TokenBalanceLog}; use crate::logs::{
LiquidateTokenAndTokenLog, LoanOriginationFeeInstruction, TokenBalanceLog,
WithdrawLoanOriginationFeeLog,
};
use crate::state::ScanningAccountRetriever; use crate::state::ScanningAccountRetriever;
use crate::state::*; use crate::state::*;
use crate::util::checked_math as cm; use crate::util::checked_math as cm;
@ -77,171 +80,168 @@ pub fn liq_token_with_token(
// Transfer some liab_token from liqor to liqee and // Transfer some liab_token from liqor to liqee and
// transfer some asset_token from liqee to liqor. // transfer some asset_token from liqee to liqor.
// //
{
// Get the mut banks and oracle prices
//
// This must happen _after_ the health computation, since immutable borrows of
// the bank are not allowed at the same time.
let (asset_bank, asset_price, opt_liab_bank_and_price) =
account_retriever.banks_mut_and_oracles(asset_token_index, liab_token_index)?;
let (liab_bank, liab_price) = opt_liab_bank_and_price.unwrap();
// The main complication here is that we can't keep the liqee_asset_position and liqee_liab_position // Get the mut banks and oracle prices
// borrows alive at the same time. Possibly adding get_mut_pair() would be helpful. //
let (liqee_asset_position, liqee_asset_raw_index) = // This must happen _after_ the health computation, since immutable borrows of
liqee.token_position_and_raw_index(asset_token_index)?; // the bank are not allowed at the same time.
let liqee_asset_native = liqee_asset_position.native(asset_bank); let (asset_bank, asset_price, opt_liab_bank_and_price) =
require!(liqee_asset_native.is_positive(), MangoError::SomeError); 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_liab_position, liqee_liab_raw_index) = // The main complication here is that we can't keep the liqee_asset_position and liqee_liab_position
liqee.token_position_and_raw_index(liab_token_index)?; // borrows alive at the same time. Possibly adding get_mut_pair() would be helpful.
let liqee_liab_native = liqee_liab_position.native(liab_bank); let (liqee_asset_position, liqee_asset_raw_index) =
require!(liqee_liab_native.is_negative(), MangoError::SomeError); liqee.token_position_and_raw_index(asset_token_index)?;
let liqee_asset_native = liqee_asset_position.native(asset_bank);
require!(liqee_asset_native.is_positive(), MangoError::SomeError);
// TODO why sum of both tokens liquidation fees? Add comment let (liqee_liab_position, liqee_liab_raw_index) =
let fee_factor = I80F48::ONE + asset_bank.liquidation_fee + liab_bank.liquidation_fee; liqee.token_position_and_raw_index(liab_token_index)?;
let liab_price_adjusted = liab_price * fee_factor; let liqee_liab_native = liqee_liab_position.native(liab_bank);
require!(liqee_liab_native.is_negative(), MangoError::SomeError);
let init_asset_weight = asset_bank.init_asset_weight; // TODO why sum of both tokens liquidation fees? Add comment
let init_liab_weight = liab_bank.init_liab_weight; let fee_factor = I80F48::ONE + asset_bank.liquidation_fee + liab_bank.liquidation_fee;
let liab_price_adjusted = liab_price * fee_factor;
// How much asset would need to be exchanged to liab in order to bring health to 0? let init_asset_weight = asset_bank.init_asset_weight;
// let init_liab_weight = liab_bank.init_liab_weight;
// That means: what is x (unit: native liab tokens) such that
// init_health + x * ilw * lp - y * iaw * ap = 0 // How much asset would need to be exchanged to liab in order to bring health to 0?
// where //
// ilw = init_liab_weight, lp = liab_price // That means: what is x (unit: native liab tokens) such that
// iap = init_asset_weight, ap = asset_price // init_health + x * ilw * lp - y * iaw * ap = 0
// ff = fee_factor, lpa = lp * ff // where
// and the asset cost of getting x native units of liab is: // ilw = init_liab_weight, lp = liab_price
// y = x * lp / ap * ff = x * lpa / ap (native asset tokens) // iap = init_asset_weight, ap = asset_price
// // ff = fee_factor, lpa = lp * ff
// Result: x = -init_health / (lp * ilw - iaw * lpa) // and the asset cost of getting x native units of liab is:
let liab_needed = cm!(-init_health // y = x * lp / ap * ff = x * lpa / ap (native asset tokens)
//
// Result: x = -init_health / (lp * ilw - iaw * lpa)
let liab_needed =
cm!(-init_health
/ (liab_price * init_liab_weight - init_asset_weight * liab_price_adjusted)); / (liab_price * init_liab_weight - init_asset_weight * liab_price_adjusted));
// How much liab can we get at most for the asset balance? // How much liab can we get at most for the asset balance?
let liab_possible = cm!(liqee_asset_native * asset_price / liab_price_adjusted); let liab_possible = cm!(liqee_asset_native * asset_price / liab_price_adjusted);
// The amount of liab native tokens we will transfer // The amount of liab native tokens we will transfer
let liab_transfer = min( let liab_transfer = min(
min(min(liab_needed, -liqee_liab_native), liab_possible), min(min(liab_needed, -liqee_liab_native), liab_possible),
max_liab_transfer, max_liab_transfer,
); );
// The amount of asset native tokens we will give up for them // The amount of asset native tokens we will give up for them
let asset_transfer = cm!(liab_transfer * liab_price_adjusted / asset_price); let asset_transfer = cm!(liab_transfer * liab_price_adjusted / asset_price);
// During liquidation, we mustn't leave small positive balances in the liqee. Those // During liquidation, we mustn't leave small positive balances in the liqee. Those
// could break bankruptcy-detection. Thus we dust them even if the token position // could break bankruptcy-detection. Thus we dust them even if the token position
// is nominally in-use. // is nominally in-use.
// Apply the balance changes to the liqor and liqee accounts // Apply the balance changes to the liqor and liqee accounts
let liqee_liab_position = liqee.token_position_mut_by_raw_index(liqee_liab_raw_index); let liqee_liab_position = liqee.token_position_mut_by_raw_index(liqee_liab_raw_index);
let liqee_liab_active = let liqee_liab_active = liab_bank.deposit_with_dusting(liqee_liab_position, liab_transfer)?;
liab_bank.deposit_with_dusting(liqee_liab_position, liab_transfer)?; let liqee_liab_position_indexed = liqee_liab_position.indexed_position;
let liqee_liab_position_indexed = liqee_liab_position.indexed_position;
let (liqor_liab_position, liqor_liab_raw_index, _) = let (liqor_liab_position, liqor_liab_raw_index, _) =
liqor.ensure_token_position(liab_token_index)?; liqor.ensure_token_position(liab_token_index)?;
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?; let (liqor_liab_active, loan_origination_fee) =
let liqor_liab_position_indexed = liqor_liab_position.indexed_position; liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
let liqee_liab_native_after = liqee_liab_position.native(&liab_bank); let liqor_liab_position_indexed = liqor_liab_position.indexed_position;
let liqee_liab_native_after = liqee_liab_position.native(&liab_bank);
let (liqor_asset_position, liqor_asset_raw_index, _) = let (liqor_asset_position, liqor_asset_raw_index, _) =
liqor.ensure_token_position(asset_token_index)?; liqor.ensure_token_position(asset_token_index)?;
let liqor_asset_active = asset_bank.deposit(liqor_asset_position, asset_transfer)?; let liqor_asset_active = asset_bank.deposit(liqor_asset_position, asset_transfer)?;
let liqor_asset_position_indexed = liqor_asset_position.indexed_position; let liqor_asset_position_indexed = liqor_asset_position.indexed_position;
let liqee_asset_position = liqee.token_position_mut_by_raw_index(liqee_asset_raw_index); let liqee_asset_position = liqee.token_position_mut_by_raw_index(liqee_asset_raw_index);
let liqee_asset_active = let liqee_asset_active =
asset_bank.withdraw_without_fee_with_dusting(liqee_asset_position, asset_transfer)?; asset_bank.withdraw_without_fee_with_dusting(liqee_asset_position, asset_transfer)?;
let liqee_asset_position_indexed = liqee_asset_position.indexed_position; let liqee_asset_position_indexed = liqee_asset_position.indexed_position;
let liqee_assets_native_after = liqee_asset_position.native(&asset_bank); let liqee_assets_native_after = liqee_asset_position.native(&asset_bank);
// Update the health cache // Update the health cache
liqee_health_cache.adjust_token_balance( liqee_health_cache.adjust_token_balance(
liab_token_index, liab_token_index,
cm!(liqee_liab_native_after - liqee_liab_native), cm!(liqee_liab_native_after - liqee_liab_native),
)?; )?;
liqee_health_cache.adjust_token_balance( liqee_health_cache.adjust_token_balance(
asset_token_index, asset_token_index,
cm!(liqee_assets_native_after - liqee_asset_native), cm!(liqee_assets_native_after - liqee_asset_native),
)?; )?;
msg!( msg!(
"liquidated {} liab for {} asset", "liquidated {} liab for {} asset",
liab_transfer, liab_transfer,
asset_transfer asset_transfer
); );
emit!(LiquidateTokenAndTokenLog { // liqee asset
mango_group: ctx.accounts.group.key(), emit!(TokenBalanceLog {
liqee: ctx.accounts.liqee.key(), mango_group: ctx.accounts.group.key(),
liqor: ctx.accounts.liqor.key(), mango_account: ctx.accounts.liqee.key(),
asset_token_index, token_index: asset_token_index,
liab_token_index, indexed_position: liqee_asset_position_indexed.to_bits(),
asset_transfer: asset_transfer.to_bits(), deposit_index: asset_bank.deposit_index.to_bits(),
liab_transfer: liab_transfer.to_bits(), borrow_index: asset_bank.borrow_index.to_bits(),
asset_price: asset_price.to_bits(), price: asset_price.to_bits(),
liab_price: liab_price.to_bits(), });
// bankruptcy: // liqee liab
}); emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: liab_token_index,
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(),
});
// liqor asset
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: asset_token_index,
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(),
});
// liqor liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
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(),
});
// liqee asset if loan_origination_fee.is_positive() {
emit!(TokenBalanceLog { emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: asset_token_index,
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(),
});
// liqee liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: liab_token_index,
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(),
});
// liqor asset
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: asset_token_index,
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(),
});
// liqor liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(), mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(), mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index, token_index: liab_token_index,
indexed_position: liqor_liab_position_indexed.to_bits(), loan_origination_fee: loan_origination_fee.to_bits(),
deposit_index: liab_bank.deposit_index.to_bits(), instruction: LoanOriginationFeeInstruction::LiqTokenWithToken
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 // Since we use a scanning account retriever, it's safe to deactivate inactive token positions
if !liqee_asset_active { if !liqee_asset_active {
liqee.deactivate_token_position(liqee_asset_raw_index); liqee.deactivate_token_position(liqee_asset_raw_index);
} }
if !liqee_liab_active { if !liqee_liab_active {
liqee.deactivate_token_position(liqee_liab_raw_index); liqee.deactivate_token_position(liqee_liab_raw_index);
} }
if !liqor_asset_active { if !liqor_asset_active {
liqor.deactivate_token_position(liqor_asset_raw_index); liqor.deactivate_token_position(liqor_asset_raw_index);
} }
if !liqor_liab_active { if !liqor_liab_active {
liqor.deactivate_token_position(liqor_liab_raw_index) liqor.deactivate_token_position(liqor_liab_raw_index)
}
} }
// Check liqee health again // Check liqee health again
@ -255,5 +255,18 @@ pub fn liq_token_with_token(
.context("compute liqor health")?; .context("compute liqor health")?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive); require!(liqor_health >= 0, MangoError::HealthMustBePositive);
emit!(LiquidateTokenAndTokenLog {
mango_group: ctx.accounts.group.key(),
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
asset_token_index,
liab_token_index,
asset_transfer: asset_transfer.to_bits(),
liab_transfer: liab_transfer.to_bits(),
asset_price: asset_price.to_bits(),
liab_price: liab_price.to_bits(),
bankruptcy: !liqee_health_cache.has_liquidatable_assets() & liqee_init_health.is_negative()
});
Ok(()) Ok(())
} }

View File

@ -5,6 +5,8 @@ use crate::error::*;
use crate::instructions::apply_vault_difference; use crate::instructions::apply_vault_difference;
use crate::state::*; use crate::state::*;
use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
#[derive(Accounts)] #[derive(Accounts)]
pub struct Serum3LiqForceCancelOrders<'info> { pub struct Serum3LiqForceCancelOrders<'info> {
pub group: AccountLoader<'info, Group>, pub group: AccountLoader<'info, Group>,
@ -68,9 +70,9 @@ pub fn serum3_liq_force_cancel_orders(
// //
// Validation // Validation
// //
let serum_market = ctx.accounts.serum_market.load()?;
{ {
let account = ctx.accounts.account.load()?; let account = ctx.accounts.account.load()?;
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders // Validate open_orders
require!( require!(
@ -138,16 +140,36 @@ pub fn serum3_liq_force_cancel_orders(
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference( let (vault_difference_result, base_loan_origination_fee, quote_loan_origination_fee) =
&mut account.borrow_mut(), apply_vault_difference(
&mut base_bank, &mut account.borrow_mut(),
after_base_vault, &mut base_bank,
before_base_vault, after_base_vault,
&mut quote_bank, before_base_vault,
after_quote_vault, &mut quote_bank,
before_quote_vault, after_quote_vault,
)? before_quote_vault,
.deactivate_inactive_token_accounts(&mut account.borrow_mut()); )?;
vault_difference_result.deactivate_inactive_token_accounts(&mut account.borrow_mut());
if base_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.base_token_index,
loan_origination_fee: base_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3LiqForceCancelOrders
});
}
if quote_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.quote_token_index,
loan_origination_fee: quote_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3LiqForceCancelOrders
});
}
Ok(()) Ok(())
} }

View File

@ -12,6 +12,8 @@ use serum_dex::instruction::NewOrderInstructionV3;
use serum_dex::matching::Side; use serum_dex::matching::Side;
use serum_dex::state::OpenOrders; use serum_dex::state::OpenOrders;
use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
/// For loan origination fees bookkeeping purposes /// For loan origination fees bookkeeping purposes
pub struct OpenOrdersSlim { pub struct OpenOrdersSlim {
pub native_coin_free: u64, pub native_coin_free: u64,
@ -273,9 +275,10 @@ pub fn serum3_place_order(
// Charge the difference in vault balances to the user's account // Charge the difference in vault balances to the user's account
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_mut()?;
let vault_difference_result = { let (vault_difference_result, base_loan_origination_fee, quote_loan_origination_fee) = {
let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_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(),
&mut base_bank, &mut base_bank,
@ -298,6 +301,25 @@ pub fn serum3_place_order(
vault_difference_result.deactivate_inactive_token_accounts(&mut account.borrow_mut()); vault_difference_result.deactivate_inactive_token_accounts(&mut account.borrow_mut());
if base_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.base_token_index,
loan_origination_fee: base_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3PlaceOrder
});
}
if quote_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.quote_token_index,
loan_origination_fee: quote_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3PlaceOrder
});
}
Ok(()) Ok(())
} }
@ -350,25 +372,31 @@ pub fn apply_vault_difference(
quote_bank: &mut Bank, quote_bank: &mut Bank,
after_quote_vault: u64, after_quote_vault: u64,
before_quote_vault: u64, before_quote_vault: u64,
) -> Result<VaultDifferenceResult> { ) -> Result<(VaultDifferenceResult, I80F48, I80F48)> {
// TODO: Applying the loan origination fee here may be too early: it should only be // 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 // 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! // an order without having the funds will be charged for each place_order!
let (base_position, base_raw_index) = account.token_position_mut(base_bank.token_index)?; let (base_position, base_raw_index) = account.token_position_mut(base_bank.token_index)?;
let base_change = I80F48::from(after_base_vault) - I80F48::from(before_base_vault); let base_change = I80F48::from(after_base_vault) - I80F48::from(before_base_vault);
let base_active = base_bank.change_with_fee(base_position, base_change)?; let (base_active, base_loan_origination_fee) =
base_bank.change_with_fee(base_position, base_change)?;
let (quote_position, quote_raw_index) = account.token_position_mut(quote_bank.token_index)?; let (quote_position, quote_raw_index) = account.token_position_mut(quote_bank.token_index)?;
let quote_change = I80F48::from(after_quote_vault) - I80F48::from(before_quote_vault); let quote_change = I80F48::from(after_quote_vault) - I80F48::from(before_quote_vault);
let quote_active = quote_bank.change_with_fee(quote_position, quote_change)?; let (quote_active, quote_loan_origination_fee) =
quote_bank.change_with_fee(quote_position, quote_change)?;
Ok(VaultDifferenceResult { Ok((
base_raw_index, VaultDifferenceResult {
base_active, base_raw_index,
quote_raw_index, base_active,
quote_active, quote_raw_index,
}) quote_active,
},
base_loan_origination_fee,
quote_loan_origination_fee,
))
} }
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> { fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> {

View File

@ -10,6 +10,7 @@ use crate::serum3_cpi::load_open_orders_ref;
use crate::state::*; use crate::state::*;
use super::{apply_vault_difference, OpenOrdersReserved, OpenOrdersSlim}; use super::{apply_vault_difference, OpenOrdersReserved, OpenOrdersSlim};
use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
#[derive(Accounts)] #[derive(Accounts)]
pub struct Serum3SettleFunds<'info> { pub struct Serum3SettleFunds<'info> {
@ -149,16 +150,36 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?; let mut account = ctx.accounts.account.load_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?; let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?; let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference( let (vault_difference_result, base_loan_origination_fee, quote_loan_origination_fee) =
&mut account.borrow_mut(), apply_vault_difference(
&mut base_bank, &mut account.borrow_mut(),
after_base_vault, &mut base_bank,
before_base_vault, after_base_vault,
&mut quote_bank, before_base_vault,
after_quote_vault, &mut quote_bank,
before_quote_vault, after_quote_vault,
)? before_quote_vault,
.deactivate_inactive_token_accounts(&mut account.borrow_mut()); )?;
vault_difference_result.deactivate_inactive_token_accounts(&mut account.borrow_mut());
if base_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.base_token_index,
loan_origination_fee: base_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3SettleFunds
});
}
if quote_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.quote_token_index,
loan_origination_fee: quote_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3SettleFunds
});
}
} }
Ok(()) Ok(())

View File

@ -117,7 +117,9 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
deposit_index: deposit_index.to_bits(), deposit_index: deposit_index.to_bits(),
borrow_index: borrow_index.to_bits(), borrow_index: borrow_index.to_bits(),
avg_utilization: new_avg_utilization.to_bits(), avg_utilization: new_avg_utilization.to_bits(),
price: price.to_bits() price: price.to_bits(),
collected_fees: some_bank.collected_fees_native.to_bits(),
loan_fee_rate: some_bank.loan_fee_rate.to_bits()
}); });
drop(some_bank); drop(some_bank);

View File

@ -6,7 +6,9 @@ use anchor_spl::token::Token;
use anchor_spl::token::TokenAccount; use anchor_spl::token::TokenAccount;
use fixed::types::I80F48; use fixed::types::I80F48;
use crate::logs::{TokenBalanceLog, WithdrawLog}; use crate::logs::{
LoanOriginationFeeInstruction, TokenBalanceLog, WithdrawLoanOriginationFeeLog, WithdrawLog,
};
use crate::state::new_fixed_order_account_retriever; use crate::state::new_fixed_order_account_retriever;
use crate::util::checked_math as cm; use crate::util::checked_math as cm;
@ -62,7 +64,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// The bank will also be passed in remainingAccounts. Use an explicit scope // The bank will also be passed in remainingAccounts. Use an explicit scope
// to drop the &mut before we borrow it immutably again later. // to drop the &mut before we borrow it immutably again later.
let (position_is_active, amount_i80f48) = { let (position_is_active, amount_i80f48, loan_origination_fee) = {
let mut bank = ctx.accounts.bank.load_mut()?; let mut bank = ctx.accounts.bank.load_mut()?;
let native_position = position.native(&bank); let native_position = position.native(&bank);
@ -87,7 +89,8 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
let amount_i80f48 = I80F48::from(amount); let amount_i80f48 = I80F48::from(amount);
// Update the bank and position // Update the bank and position
let position_is_active = bank.withdraw_with_fee(position, amount_i80f48)?; let (position_is_active, loan_origination_fee) =
bank.withdraw_with_fee(position, amount_i80f48)?;
// 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
if ctx.accounts.vault.amount < amount { if ctx.accounts.vault.amount < amount {
@ -106,7 +109,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
amount, amount,
)?; )?;
(position_is_active, amount_i80f48) (position_is_active, amount_i80f48, loan_origination_fee)
}; };
let indexed_position = position.indexed_position; let indexed_position = position.indexed_position;
@ -156,5 +159,15 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
price: oracle_price.to_bits(), price: oracle_price.to_bits(),
}); });
if loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index,
loan_origination_fee: loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::TokenWithdraw,
});
}
Ok(()) Ok(())
} }

View File

@ -137,6 +137,8 @@ pub struct UpdateIndexLog {
pub borrow_index: i128, // I80F48 pub borrow_index: i128, // I80F48
pub avg_utilization: i128, // I80F48 pub avg_utilization: i128, // I80F48
pub price: i128, // I80F48 pub price: i128, // I80F48
pub collected_fees: i128, // I80F48
pub loan_fee_rate: i128, // I80F48
} }
#[event] #[event]
@ -159,7 +161,7 @@ pub struct LiquidateTokenAndTokenLog {
pub liab_transfer: i128, // I80F48 pub liab_transfer: i128, // I80F48
pub asset_price: i128, // I80F48 pub asset_price: i128, // I80F48
pub liab_price: i128, // I80F48 pub liab_price: i128, // I80F48
// pub bankruptcy: bool, pub bankruptcy: bool,
} }
#[event] #[event]
@ -175,3 +177,37 @@ pub struct OpenOrdersBalanceLog {
pub referrer_rebates_accrued: u64, pub referrer_rebates_accrued: u64,
pub price: i128, // I80F48 pub price: i128, // I80F48
} }
#[derive(PartialEq, Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize)]
#[repr(u8)]
pub enum LoanOriginationFeeInstruction {
Unknown,
LiqTokenBankruptcy,
LiqTokenWithToken,
Serum3LiqForceCancelOrders,
Serum3PlaceOrder,
Serum3SettleFunds,
TokenWithdraw,
}
#[event]
pub struct WithdrawLoanOriginationFeeLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub token_index: u16,
pub loan_origination_fee: i128, // I80F48
pub instruction: LoanOriginationFeeInstruction,
}
#[event]
pub struct LiquidateTokenBankruptcyLog {
pub mango_group: Pubkey,
pub liqee: Pubkey,
pub liqor: Pubkey,
pub liab_token_index: u16,
pub initial_liab_native: i128,
pub liab_price: i128,
pub insurance_token_index: u16,
pub insurance_transfer: i128,
pub socialized_loss: i128,
}

View File

@ -271,7 +271,10 @@ impl Bank {
position: &mut TokenPosition, position: &mut TokenPosition,
native_amount: I80F48, native_amount: I80F48,
) -> Result<bool> { ) -> Result<bool> {
self.withdraw_internal(position, native_amount, false, !position.is_in_use()) let (position_is_active, _) =
self.withdraw_internal(position, native_amount, false, !position.is_in_use())?;
return Ok(position_is_active);
} }
/// Like `withdraw_without_fee()` but allows dusting of in-use token accounts. /// Like `withdraw_without_fee()` but allows dusting of in-use token accounts.
@ -282,8 +285,9 @@ impl Bank {
position: &mut TokenPosition, position: &mut TokenPosition,
native_amount: I80F48, native_amount: I80F48,
) -> Result<bool> { ) -> Result<bool> {
self.withdraw_internal(position, native_amount, false, true) Ok(self
.map(|not_dusted| not_dusted || position.is_in_use()) .withdraw_internal(position, native_amount, false, true)
.map(|(not_dusted, _)| not_dusted || position.is_in_use())?)
} }
/// Withdraws `native_amount` while applying the loan origination fee if a borrow is created. /// Withdraws `native_amount` while applying the loan origination fee if a borrow is created.
@ -298,7 +302,7 @@ impl Bank {
&mut self, &mut self,
position: &mut TokenPosition, position: &mut TokenPosition,
native_amount: I80F48, native_amount: I80F48,
) -> Result<bool> { ) -> Result<(bool, I80F48)> {
self.withdraw_internal(position, native_amount, true, !position.is_in_use()) self.withdraw_internal(position, native_amount, true, !position.is_in_use())
} }
@ -309,7 +313,7 @@ impl Bank {
mut native_amount: I80F48, mut native_amount: I80F48,
with_loan_origination_fee: bool, with_loan_origination_fee: bool,
allow_dusting: bool, allow_dusting: bool,
) -> Result<bool> { ) -> Result<(bool, I80F48)> {
require_gte!(native_amount, 0); require_gte!(native_amount, 0);
let native_position = position.native(self); let native_position = position.native(self);
@ -322,13 +326,13 @@ impl Bank {
self.dust = cm!(self.dust + new_native_position); self.dust = cm!(self.dust + new_native_position);
self.indexed_deposits = cm!(self.indexed_deposits - position.indexed_position); self.indexed_deposits = cm!(self.indexed_deposits - position.indexed_position);
position.indexed_position = I80F48::ZERO; position.indexed_position = I80F48::ZERO;
return Ok(false); return Ok((false, I80F48::ZERO));
} else { } else {
// withdraw some deposits leaving a positive balance // withdraw some deposits leaving a positive balance
let indexed_change = cm!(native_amount / self.deposit_index); let indexed_change = cm!(native_amount / self.deposit_index);
self.indexed_deposits = cm!(self.indexed_deposits - indexed_change); self.indexed_deposits = cm!(self.indexed_deposits - indexed_change);
position.indexed_position = cm!(position.indexed_position - indexed_change); position.indexed_position = cm!(position.indexed_position - indexed_change);
return Ok(true); return Ok((true, I80F48::ZERO));
} }
} }
@ -339,8 +343,9 @@ impl Bank {
native_amount = -new_native_position; native_amount = -new_native_position;
} }
let mut loan_origination_fee = I80F48::ZERO;
if with_loan_origination_fee { if with_loan_origination_fee {
let loan_origination_fee = cm!(self.loan_origination_fee_rate * native_amount); loan_origination_fee = cm!(self.loan_origination_fee_rate * native_amount);
self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee); self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee);
native_amount = cm!(native_amount + loan_origination_fee); native_amount = cm!(native_amount + loan_origination_fee);
} }
@ -350,7 +355,7 @@ impl Bank {
self.indexed_borrows = cm!(self.indexed_borrows + indexed_change); self.indexed_borrows = cm!(self.indexed_borrows + indexed_change);
position.indexed_position = cm!(position.indexed_position - indexed_change); position.indexed_position = cm!(position.indexed_position - indexed_change);
Ok(true) Ok((true, loan_origination_fee))
} }
// withdraw the loan origination fee for a borrow that happenend earlier // withdraw the loan origination fee for a borrow that happenend earlier
@ -358,12 +363,15 @@ impl Bank {
&mut self, &mut self,
position: &mut TokenPosition, position: &mut TokenPosition,
already_borrowed_native_amount: I80F48, already_borrowed_native_amount: I80F48,
) -> Result<bool> { ) -> Result<(bool, I80F48)> {
let loan_origination_fee = let loan_origination_fee =
cm!(self.loan_origination_fee_rate * already_borrowed_native_amount); cm!(self.loan_origination_fee_rate * already_borrowed_native_amount);
self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee); self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee);
self.withdraw_internal(position, loan_origination_fee, false, !position.is_in_use()) let (position_is_active, _) =
self.withdraw_internal(position, loan_origination_fee, false, !position.is_in_use())?;
Ok((position_is_active, loan_origination_fee))
} }
/// Change a position without applying the loan origination fee /// Change a position without applying the loan origination fee
@ -384,9 +392,9 @@ impl Bank {
&mut self, &mut self,
position: &mut TokenPosition, position: &mut TokenPosition,
native_amount: I80F48, native_amount: I80F48,
) -> Result<bool> { ) -> Result<(bool, I80F48)> {
if native_amount >= 0 { if native_amount >= 0 {
self.deposit(position, native_amount) Ok((self.deposit(position, native_amount)?, I80F48::ZERO))
} else { } else {
self.withdraw_with_fee(position, -native_amount) self.withdraw_with_fee(position, -native_amount)
} }
@ -635,7 +643,7 @@ mod tests {
// //
let change = I80F48::from(change); let change = I80F48::from(change);
let is_active = bank.change_with_fee(&mut account, change)?; let (is_active, _) = bank.change_with_fee(&mut account, change)?;
let mut expected_native = start_native + change; let mut expected_native = start_native + change;
if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use { if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use {

View File

@ -109,7 +109,7 @@ impl TestContextBuilder {
})); }));
// intentionally set to as tight as possible, to catch potential problems early // intentionally set to as tight as possible, to catch potential problems early
test.set_compute_max_units(86000); test.set_compute_max_units(87000);
Self { Self {
test, test,

View File

@ -4322,6 +4322,35 @@ export type MangoV4 = {
] ]
} }
}, },
{
"name": "LoanOriginationFeeInstruction",
"type": {
"kind": "enum",
"variants": [
{
"name": "Unknown"
},
{
"name": "LiqTokenBankruptcy"
},
{
"name": "LiqTokenWithToken"
},
{
"name": "Serum3LiqForceCancelOrders"
},
{
"name": "Serum3PlaceOrder"
},
{
"name": "Serum3SettleFunds"
},
{
"name": "TokenWithdraw"
}
]
}
},
{ {
"name": "HealthType", "name": "HealthType",
"docs": [ "docs": [
@ -4848,6 +4877,16 @@ export type MangoV4 = {
"name": "price", "name": "price",
"type": "i128", "type": "i128",
"index": false "index": false
},
{
"name": "collectedFees",
"type": "i128",
"index": false
},
{
"name": "loanFeeRate",
"type": "i128",
"index": false
} }
] ]
}, },
@ -4928,6 +4967,11 @@ export type MangoV4 = {
"name": "liabPrice", "name": "liabPrice",
"type": "i128", "type": "i128",
"index": false "index": false
},
{
"name": "bankruptcy",
"type": "bool",
"index": false
} }
] ]
}, },
@ -4980,6 +5024,88 @@ export type MangoV4 = {
"index": false "index": false
} }
] ]
},
{
"name": "WithdrawLoanOriginationFeeLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "loanOriginationFee",
"type": "i128",
"index": false
},
{
"name": "instruction",
"type": {
"defined": "LoanOriginationFeeInstruction"
},
"index": false
}
]
},
{
"name": "LiquidateTokenBankruptcyLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "initialLiabNative",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "insuranceTokenIndex",
"type": "u16",
"index": false
},
{
"name": "insuranceTransfer",
"type": "i128",
"index": false
},
{
"name": "socializedLoss",
"type": "i128",
"index": false
}
]
} }
], ],
"errors": [ "errors": [
@ -9385,6 +9511,35 @@ export const IDL: MangoV4 = {
] ]
} }
}, },
{
"name": "LoanOriginationFeeInstruction",
"type": {
"kind": "enum",
"variants": [
{
"name": "Unknown"
},
{
"name": "LiqTokenBankruptcy"
},
{
"name": "LiqTokenWithToken"
},
{
"name": "Serum3LiqForceCancelOrders"
},
{
"name": "Serum3PlaceOrder"
},
{
"name": "Serum3SettleFunds"
},
{
"name": "TokenWithdraw"
}
]
}
},
{ {
"name": "HealthType", "name": "HealthType",
"docs": [ "docs": [
@ -9911,6 +10066,16 @@ export const IDL: MangoV4 = {
"name": "price", "name": "price",
"type": "i128", "type": "i128",
"index": false "index": false
},
{
"name": "collectedFees",
"type": "i128",
"index": false
},
{
"name": "loanFeeRate",
"type": "i128",
"index": false
} }
] ]
}, },
@ -9991,6 +10156,11 @@ export const IDL: MangoV4 = {
"name": "liabPrice", "name": "liabPrice",
"type": "i128", "type": "i128",
"index": false "index": false
},
{
"name": "bankruptcy",
"type": "bool",
"index": false
} }
] ]
}, },
@ -10043,6 +10213,88 @@ export const IDL: MangoV4 = {
"index": false "index": false
} }
] ]
},
{
"name": "WithdrawLoanOriginationFeeLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "loanOriginationFee",
"type": "i128",
"index": false
},
{
"name": "instruction",
"type": {
"defined": "LoanOriginationFeeInstruction"
},
"index": false
}
]
},
{
"name": "LiquidateTokenBankruptcyLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "initialLiabNative",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "insuranceTokenIndex",
"type": "u16",
"index": false
},
{
"name": "insuranceTransfer",
"type": "i128",
"index": false
},
{
"name": "socializedLoss",
"type": "i128",
"index": false
}
]
} }
], ],
"errors": [ "errors": [