MP-1705: Borrow limit (#48)

* update borrow funds hooks using credit manager client

* update calculate max borrow and withdraw hooks to consider future debt
This commit is contained in:
Gustavo Mauricio 2022-11-07 17:14:16 +00:00 committed by GitHub
parent 9bc09c68af
commit 8bde02c2e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 86 additions and 58 deletions

View File

@ -3,7 +3,6 @@ import { useMemo } from 'react'
import { toast } from 'react-toastify'
import useWalletStore from 'stores/useWalletStore'
import { contractAddresses } from 'config/contracts'
import { hardcodedFee } from 'utils/contants'
import useCreditManagerStore from 'stores/useCreditManagerStore'
import { queryKeys } from 'types/query-keys-factory'
@ -14,61 +13,49 @@ const useBorrowFunds = (
withdraw = false,
options: Omit<UseMutationOptions, 'onError'>
) => {
const signingClient = useWalletStore((s) => s.signingClient)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
const creditManagerClient = useWalletStore((s) => s.clients.creditManager)
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount ?? '')
const address = useWalletStore((s) => s.address)
const queryClient = useQueryClient()
const executeMsg = useMemo(() => {
const actions = useMemo(() => {
if (!withdraw) {
return {
update_credit_account: {
account_id: selectedAccount,
actions: [
{
borrow: {
denom: denom,
amount: String(amount),
},
},
],
return [
{
borrow: {
denom: denom,
amount: String(amount),
},
},
}
]
}
return {
update_credit_account: {
account_id: selectedAccount,
actions: [
{
borrow: {
denom: denom,
amount: String(amount),
},
},
{
withdraw: {
denom: denom,
amount: String(amount),
},
},
],
return [
{
borrow: {
denom: denom,
amount: String(amount),
},
},
}
}, [withdraw, selectedAccount, denom, amount])
{
withdraw: {
denom: denom,
amount: String(amount),
},
},
]
}, [withdraw, denom, amount])
return useMutation(
async () =>
await signingClient?.execute(
address,
contractAddresses.creditManager,
executeMsg,
await creditManagerClient?.updateCreditAccount(
{ accountId: selectedAccount, actions },
hardcodedFee
),
{
onSettled: () => {
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount ?? ''))
queryClient.invalidateQueries(queryKeys.creditAccountsPositions(selectedAccount))
queryClient.invalidateQueries(queryKeys.redbankBalances())
// if withdrawing to wallet, need to explicility invalidate balances queries

View File

@ -1,4 +1,4 @@
import { useMemo } from 'react'
import { useCallback, useMemo } from 'react'
import BigNumber from 'bignumber.js'
import useCreditManagerStore from 'stores/useCreditManagerStore'
@ -8,6 +8,12 @@ import useMarkets from './useMarkets'
import useTokenPrices from './useTokenPrices'
import useRedbankBalances from './useRedbankBalances'
const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
const hourlyAPY = BigNumber(borrowAPY).div(24 * 365)
return hourlyAPY.times(amount).toNumber()
}
const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boolean) => {
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
@ -16,33 +22,40 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
const { data: tokenPrices } = useTokenPrices()
const { data: redbankBalances } = useRedbankBalances()
const getTokenValue = useCallback(
(amount: string, denom: string) => {
if (!tokenPrices) return 0
return BigNumber(amount).times(tokenPrices[denom]).toNumber()
},
[tokenPrices]
)
return useMemo(() => {
if (!marketsData || !tokenPrices || !positionsData || !redbankBalances) return 0
const getTokenTotalUSDValue = (amount: string, denom: string) => {
// early return if prices are not fetched yet
if (!tokenPrices) return 0
return BigNumber(amount)
.div(10 ** getTokenDecimals(denom))
.times(tokenPrices[denom])
.toNumber()
}
// max ltv adjusted collateral
const totalWeightedPositions = positionsData?.coins.reduce((acc, coin) => {
const tokenWeightedValue = BigNumber(getTokenTotalUSDValue(coin.amount, coin.denom)).times(
const tokenWeightedValue = BigNumber(getTokenValue(coin.amount, coin.denom)).times(
Number(marketsData[coin.denom].max_loan_to_value)
)
return tokenWeightedValue.plus(acc).toNumber()
}, 0)
// total debt value
// approximate debt value in an hour timespan to avoid throwing on smart contract level
// due to debt interest being applied
const totalLiabilitiesValue = positionsData?.debts.reduce((acc, coin) => {
const tokenUSDValue = BigNumber(getTokenTotalUSDValue(coin.amount, coin.denom))
const estimatedInterestAmount = getApproximateHourlyInterest(
coin.amount,
marketsData[coin.denom].borrow_rate
)
return tokenUSDValue.plus(acc).toNumber()
const tokenDebtValue = BigNumber(getTokenValue(coin.amount, coin.denom)).plus(
estimatedInterestAmount
)
return tokenDebtValue.plus(acc).toNumber()
}, 0)
const borrowTokenPrice = tokenPrices[denom]
@ -54,6 +67,7 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
maxValue = BigNumber(totalLiabilitiesValue)
.minus(totalWeightedPositions)
.div(borrowTokenPrice * Number(marketsData[denom].max_loan_to_value) - borrowTokenPrice)
.div(10 ** tokenDecimals)
.decimalPlaces(tokenDecimals)
.toNumber()
} else {
@ -61,6 +75,7 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
maxValue = BigNumber(totalWeightedPositions)
.minus(totalLiabilitiesValue)
.div(borrowTokenPrice)
.div(10 ** tokenDecimals)
.decimalPlaces(tokenDecimals)
.toNumber()
}
@ -72,7 +87,15 @@ const useCalculateMaxBorrowAmount = (denom: string, isUnderCollateralized: boole
if (marketLiquidity < maxValue) return marketLiquidity
return maxValue > 0 ? maxValue : 0
}, [denom, isUnderCollateralized, marketsData, positionsData, redbankBalances, tokenPrices])
}, [
denom,
getTokenValue,
isUnderCollateralized,
marketsData,
positionsData,
redbankBalances,
tokenPrices,
])
}
export default useCalculateMaxBorrowAmount

View File

@ -7,6 +7,12 @@ import useCreditManagerStore from 'stores/useCreditManagerStore'
import useTokenPrices from './useTokenPrices'
import useMarkets from './useMarkets'
const getApproximateHourlyInterest = (amount: string, borrowAPY: string) => {
const hourlyAPY = BigNumber(borrowAPY).div(24 * 365)
return hourlyAPY.times(amount).toNumber()
}
const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
const selectedAccount = useCreditManagerStore((s) => s.selectedAccount)
@ -37,10 +43,18 @@ const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
const borrowTokenPrice = tokenPrices[denom]
const borrowTokenMaxLTV = Number(marketsData[denom].max_loan_to_value)
// approximate debt value in an hour timespan to avoid throwing on smart contract level
// due to debt interest being applied
const totalLiabilitiesValue = positionsData?.debts.reduce((acc, coin) => {
const tokenUSDValue = BigNumber(getTokenValue(coin.amount, coin.denom))
const estimatedInterestAmount = getApproximateHourlyInterest(
coin.amount,
marketsData[coin.denom].borrow_rate
)
const tokenDebtValue = BigNumber(getTokenValue(coin.amount, coin.denom)).plus(
estimatedInterestAmount
)
return tokenUSDValue.plus(acc).toNumber()
return tokenDebtValue.plus(acc).toNumber()
}, 0)
const positionsWeightedAverageWithoutAsset = positionsData?.coins.reduce((acc, coin) => {
@ -77,6 +91,10 @@ const useCalculateMaxWithdrawAmount = (denom: string, borrow: boolean) => {
.minus(requiredCollateral)
.decimalPlaces(tokenDecimals)
.toNumber()
// required collateral might be greater than the amount in the credit account which will result
// in a negative max amount capacity
maxAmountCapacity = maxAmountCapacity < 0 ? 0 : maxAmountCapacity
}
const isCapacityHigherThanBalance = BigNumber(maxAmountCapacity).gt(tokenAmountInCreditAccount)