Enforce safety limits while borrowing, while computing max swap source, and max spot order base/quote (#642)
* --wip-- [skip ci] * enforce safety limits while borrowing Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * --wip-- [skip ci] * --wip-- [skip ci] * --wip-- [skip ci] * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> --------- Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
23b72b54a3
commit
e623b8c276
|
@ -1,7 +1,7 @@
|
|||
import { BN } from '@coral-xyz/anchor';
|
||||
import { utf8 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { I80F48, I80F48Dto, ZERO_I80F48 } from '../numbers/I80F48';
|
||||
import { I80F48, I80F48Dto, ONE_I80F48, ZERO_I80F48 } from '../numbers/I80F48';
|
||||
import { As, toUiDecimals } from '../utils';
|
||||
import { OracleProvider } from './oracle';
|
||||
|
||||
|
@ -397,17 +397,11 @@ export class Bank implements BankForHealth {
|
|||
}
|
||||
|
||||
uiDeposits(): number {
|
||||
return toUiDecimals(
|
||||
this.indexedDeposits.mul(this.depositIndex),
|
||||
this.mintDecimals,
|
||||
);
|
||||
return toUiDecimals(this.nativeDeposits(), this.mintDecimals);
|
||||
}
|
||||
|
||||
uiBorrows(): number {
|
||||
return toUiDecimals(
|
||||
this.indexedBorrows.mul(this.borrowIndex),
|
||||
this.mintDecimals,
|
||||
);
|
||||
return toUiDecimals(this.nativeBorrows(), this.mintDecimals);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -481,6 +475,48 @@ export class Bank implements BankForHealth {
|
|||
getDepositRateUi(): number {
|
||||
return this.getDepositRate().toNumber() * 100;
|
||||
}
|
||||
|
||||
getNetBorrowLimitPerWindow(): I80F48 {
|
||||
return I80F48.fromI64(this.netBorrowLimitPerWindowQuote).div(this.price);
|
||||
}
|
||||
|
||||
getBorrowLimitLeftInWindow(): I80F48 {
|
||||
return this.getNetBorrowLimitPerWindow()
|
||||
.sub(I80F48.fromI64(this.netBorrowsInWindow))
|
||||
.max(ZERO_I80F48());
|
||||
}
|
||||
|
||||
getNetBorrowLimitPerWindowUi(): number {
|
||||
return toUiDecimals(this.getNetBorrowLimitPerWindow(), this.mintDecimals);
|
||||
}
|
||||
|
||||
getMaxWithdraw(vaultBalance: BN, userDeposits = ZERO_I80F48()): I80F48 {
|
||||
userDeposits = userDeposits.max(ZERO_I80F48());
|
||||
|
||||
// any borrow must respect the minVaultToDepositsRatio
|
||||
const minVaultBalanceRequired = this.nativeDeposits().mul(
|
||||
I80F48.fromNumber(this.minVaultToDepositsRatio),
|
||||
);
|
||||
const maxBorrowFromVault = I80F48.fromI64(vaultBalance)
|
||||
.sub(minVaultBalanceRequired)
|
||||
.max(ZERO_I80F48());
|
||||
// User deposits can exceed maxWithdrawFromVault
|
||||
let maxBorrow = maxBorrowFromVault.sub(userDeposits).max(ZERO_I80F48());
|
||||
// any borrow must respect the limit left in window
|
||||
maxBorrow = maxBorrow.min(this.getBorrowLimitLeftInWindow());
|
||||
// borrows would be applied a fee
|
||||
maxBorrow = maxBorrow.div(ONE_I80F48().add(this.loanOriginationFeeRate));
|
||||
|
||||
// user deposits can always be withdrawn
|
||||
// even if vaults can be depleted
|
||||
return maxBorrow.add(userDeposits).min(I80F48.fromI64(vaultBalance));
|
||||
}
|
||||
|
||||
getTimeToNextBorrowLimitWindowStartsTs(): number {
|
||||
return this.netBorrowLimitWindowSizeTs
|
||||
.sub(new BN(Date.now() / 1000).sub(this.lastNetBorrowsWindowStartTs))
|
||||
.toNumber();
|
||||
}
|
||||
}
|
||||
|
||||
export class MintInfo {
|
||||
|
|
|
@ -488,12 +488,7 @@ export class Group {
|
|||
return this.getFirstBankByTokenIndex(0 as TokenIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mintPk
|
||||
* @returns sum of ui balances of vaults for all banks for a token
|
||||
*/
|
||||
public getTokenVaultBalanceByMintUi(mintPk: PublicKey): number {
|
||||
public getTokenVaultBalanceByMint(mintPk: PublicKey): BN {
|
||||
const banks = this.banksMapByMint.get(mintPk.toBase58());
|
||||
if (!banks) {
|
||||
throw new Error(`No bank found for mint ${mintPk}!`);
|
||||
|
@ -509,7 +504,19 @@ export class Group {
|
|||
totalAmount.iadd(amount);
|
||||
}
|
||||
|
||||
return toUiDecimals(totalAmount, this.getMintDecimals(mintPk));
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param mintPk
|
||||
* @returns sum of ui balances of vaults for all banks for a token
|
||||
*/
|
||||
public getTokenVaultBalanceByMintUi(mintPk: PublicKey): number {
|
||||
return toUiDecimals(
|
||||
this.getTokenVaultBalanceByMint(mintPk),
|
||||
this.getMintDecimals(mintPk),
|
||||
);
|
||||
}
|
||||
|
||||
public getSerum3MarketByMarketIndex(marketIndex: MarketIndex): Serum3Market {
|
||||
|
|
|
@ -594,22 +594,11 @@ export class MangoAccount {
|
|||
),
|
||||
);
|
||||
const sourceBalance = this.getEffectiveTokenBalance(group, sourceBank);
|
||||
if (maxSource.gt(sourceBalance)) {
|
||||
// Cannot borrow more than the window limit
|
||||
let sourceBorrow = maxSource.sub(sourceBalance);
|
||||
const netBorrowLimitPerWindow = I80F48.fromI64(
|
||||
sourceBank.netBorrowLimitPerWindowQuote,
|
||||
).div(sourceBank.price);
|
||||
sourceBorrow = sourceBorrow.min(netBorrowLimitPerWindow);
|
||||
|
||||
maxSource = sourceBalance.add(
|
||||
sourceBorrow.div(ONE_I80F48().add(sourceBank.loanOriginationFeeRate)),
|
||||
const maxWithdrawNative = sourceBank.getMaxWithdraw(
|
||||
group.getTokenVaultBalanceByMint(sourceBank.mint),
|
||||
sourceBalance,
|
||||
);
|
||||
}
|
||||
|
||||
// Cannot swap more than total deposits in mango for the token
|
||||
maxSource = maxSource.min(sourceBank.nativeDeposits());
|
||||
|
||||
maxSource = maxSource.min(maxWithdrawNative);
|
||||
return toUiDecimals(maxSource, group.getMintDecimals(sourceMintPk));
|
||||
}
|
||||
|
||||
|
@ -740,12 +729,11 @@ export class MangoAccount {
|
|||
// If its a bid then the reserved fund and potential loan is in base
|
||||
// also keep some buffer for fees, use taker fees for worst case simulation.
|
||||
const quoteBalance = this.getEffectiveTokenBalance(group, quoteBank);
|
||||
if (quoteAmount.gt(quoteBalance)) {
|
||||
const quoteBorrow = quoteAmount.sub(quoteBalance);
|
||||
quoteAmount = quoteBalance.add(
|
||||
quoteBorrow.div(ONE_I80F48().add(quoteBank.loanOriginationFeeRate)),
|
||||
const maxWithdrawNative = quoteBank.getMaxWithdraw(
|
||||
group.getTokenVaultBalanceByMint(quoteBank.mint),
|
||||
quoteBalance,
|
||||
);
|
||||
}
|
||||
quoteAmount = quoteAmount.min(maxWithdrawNative);
|
||||
quoteAmount = quoteAmount.div(
|
||||
ONE_I80F48().add(I80F48.fromNumber(serum3Market.getFeeRates(true))),
|
||||
);
|
||||
|
@ -782,12 +770,11 @@ export class MangoAccount {
|
|||
// If its a ask then the reserved fund and potential loan is in base
|
||||
// also keep some buffer for fees, use taker fees for worst case simulation.
|
||||
const baseBalance = this.getEffectiveTokenBalance(group, baseBank);
|
||||
if (baseAmount.gt(baseBalance)) {
|
||||
const baseBorrow = baseAmount.sub(baseBalance);
|
||||
baseAmount = baseBalance.add(
|
||||
baseBorrow.div(ONE_I80F48().add(baseBank.loanOriginationFeeRate)),
|
||||
const maxWithdrawNative = baseBank.getMaxWithdraw(
|
||||
group.getTokenVaultBalanceByMint(baseBank.mint),
|
||||
baseBalance,
|
||||
);
|
||||
}
|
||||
baseAmount = baseAmount.min(maxWithdrawNative);
|
||||
baseAmount = baseAmount.div(
|
||||
ONE_I80F48().add(I80F48.fromNumber(serum3Market.getFeeRates(true))),
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue