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:
microwavedcola1 2023-07-13 16:29:13 +02:00 committed by GitHub
parent 23b72b54a3
commit e623b8c276
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 44 deletions

View File

@ -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 {

View File

@ -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 {

View File

@ -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)),
);
}
// Cannot swap more than total deposits in mango for the token
maxSource = maxSource.min(sourceBank.nativeDeposits());
const maxWithdrawNative = sourceBank.getMaxWithdraw(
group.getTokenVaultBalanceByMint(sourceBank.mint),
sourceBalance,
);
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))),
);