2020-10-30 09:59:47 +00:00
package keeper
import (
2020-11-11 15:05:17 +00:00
"strings"
2020-10-30 09:59:47 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
2020-11-03 09:46:08 +00:00
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
2020-10-30 09:59:47 +00:00
2020-12-21 17:18:55 +00:00
"github.com/kava-labs/kava/x/hard/types"
2020-10-30 09:59:47 +00:00
)
// Borrow funds
func ( k Keeper ) Borrow ( ctx sdk . Context , borrower sdk . AccAddress , coins sdk . Coins ) error {
2020-12-03 21:50:35 +00:00
// Set any new denoms' global borrow index to 1.0
for _ , coin := range coins {
2021-01-07 10:23:05 +00:00
_ , foundInterestFactor := k . GetBorrowInterestFactor ( ctx , coin . Denom )
2020-12-21 17:07:02 +00:00
if ! foundInterestFactor {
_ , foundMm := k . GetMoneyMarket ( ctx , coin . Denom )
if foundMm {
2021-01-07 10:23:05 +00:00
k . SetBorrowInterestFactor ( ctx , coin . Denom , sdk . OneDec ( ) )
2020-12-16 21:08:29 +00:00
}
2020-12-03 21:50:35 +00:00
}
}
2020-12-16 21:08:29 +00:00
// Get current stored LTV based on stored borrows/deposits
2021-01-07 21:40:25 +00:00
prevLtv , err := k . GetStoreLTV ( ctx , borrower )
2020-12-16 21:08:29 +00:00
if err != nil {
return err
}
2021-01-21 13:52:09 +00:00
// Call incentive hook
existingBorrow , hasExistingBorrow := k . GetBorrow ( ctx , borrower )
if hasExistingBorrow {
k . BeforeBorrowModified ( ctx , existingBorrow )
}
2021-01-07 10:23:05 +00:00
k . SyncBorrowInterest ( ctx , borrower )
2020-12-03 21:50:35 +00:00
2020-11-12 15:50:54 +00:00
// Validate borrow amount within user and protocol limits
2020-12-16 21:08:29 +00:00
err = k . ValidateBorrow ( ctx , borrower , coins )
2020-11-03 09:46:08 +00:00
if err != nil {
return err
}
2020-12-21 17:18:55 +00:00
// Sends coins from Hard module account to user
2020-11-03 09:46:08 +00:00
err = k . supplyKeeper . SendCoinsFromModuleToAccount ( ctx , types . ModuleAccountName , borrower , coins )
2020-10-30 09:59:47 +00:00
if err != nil {
2020-11-11 15:05:17 +00:00
if strings . Contains ( err . Error ( ) , "insufficient account funds" ) {
modAccCoins := k . supplyKeeper . GetModuleAccount ( ctx , types . ModuleAccountName ) . GetCoins ( )
for _ , coin := range coins {
_ , isNegative := modAccCoins . SafeSub ( sdk . NewCoins ( coin ) )
if isNegative {
return sdkerrors . Wrapf ( types . ErrBorrowExceedsAvailableBalance ,
"the requested borrow amount of %s exceeds the total amount of %s%s available to borrow" ,
coin , modAccCoins . AmountOf ( coin . Denom ) , coin . Denom ,
)
}
}
}
2020-10-30 09:59:47 +00:00
}
2021-01-07 10:23:05 +00:00
// The first time a user borrows a denom we add it the user's borrow interest factor index
var borrowInterestFactors types . BorrowInterestFactors
currBorrow , foundBorrow := k . GetBorrow ( ctx , borrower )
2020-12-16 21:08:29 +00:00
// On user's first borrow, build borrow index list containing denoms and current global borrow index value
2021-01-07 10:23:05 +00:00
if foundBorrow {
// If the coin denom to be borrowed is not in the user's existing borrow, we add it borrow index
2020-12-16 21:08:29 +00:00
for _ , coin := range coins {
2021-01-07 10:23:05 +00:00
if ! sdk . NewCoins ( coin ) . DenomsSubsetOf ( currBorrow . Amount ) {
borrowInterestFactorValue , _ := k . GetBorrowInterestFactor ( ctx , coin . Denom )
borrowInterestFactor := types . NewBorrowInterestFactor ( coin . Denom , borrowInterestFactorValue )
borrowInterestFactors = append ( borrowInterestFactors , borrowInterestFactor )
}
}
// Concatenate new borrow interest factors to existing borrow interest factors
borrowInterestFactors = append ( borrowInterestFactors , currBorrow . Index ... )
} else {
for _ , coin := range coins {
borrowInterestFactorValue , _ := k . GetBorrowInterestFactor ( ctx , coin . Denom )
borrowInterestFactor := types . NewBorrowInterestFactor ( coin . Denom , borrowInterestFactorValue )
borrowInterestFactors = append ( borrowInterestFactors , borrowInterestFactor )
2020-12-16 21:08:29 +00:00
}
2020-10-30 09:59:47 +00:00
}
2020-12-16 21:08:29 +00:00
2021-01-07 10:23:05 +00:00
// Calculate new borrow amount
var amount sdk . Coins
if foundBorrow {
amount = currBorrow . Amount . Add ( coins ... )
} else {
amount = coins
}
2021-01-07 21:40:25 +00:00
// Construct the user's new/updated borrow with amount and interest factors
2021-01-07 10:23:05 +00:00
borrow := types . NewBorrow ( borrower , amount , borrowInterestFactors )
2020-10-30 09:59:47 +00:00
2021-01-07 21:40:25 +00:00
// Calculate the new Loan-to-Value ratio of Deposit-to-Borrow
deposit , foundDeposit := k . GetDeposit ( ctx , borrower )
if ! foundDeposit {
return types . ErrDepositNotFound
}
newLtv , err := k . CalculateLtv ( ctx , deposit , borrow )
if err != nil {
return err
}
k . UpdateBorrowAndLtvIndex ( ctx , borrow , newLtv , prevLtv )
2020-12-16 21:08:29 +00:00
2020-12-03 21:50:35 +00:00
// Update total borrowed amount by newly borrowed coins. Don't add user's pending interest as
// it has already been included in the total borrowed coins by the BeginBlocker.
2020-11-12 15:50:54 +00:00
k . IncrementBorrowedCoins ( ctx , coins )
2021-01-21 13:52:09 +00:00
if ! hasExistingBorrow {
k . AfterBorrowCreated ( ctx , borrow )
} else {
k . AfterBorrowModified ( ctx , borrow )
}
2020-10-30 09:59:47 +00:00
ctx . EventManager ( ) . EmitEvent (
sdk . NewEvent (
2020-12-21 17:18:55 +00:00
types . EventTypeHardBorrow ,
2020-12-03 21:50:35 +00:00
sdk . NewAttribute ( types . AttributeKeyBorrower , borrower . String ( ) ) ,
2020-10-30 09:59:47 +00:00
sdk . NewAttribute ( types . AttributeKeyBorrowCoins , coins . String ( ) ) ,
) ,
)
return nil
}
2020-11-03 09:46:08 +00:00
// ValidateBorrow validates a borrow request against borrower and protocol requirements
2020-11-09 21:52:08 +00:00
func ( k Keeper ) ValidateBorrow ( ctx sdk . Context , borrower sdk . AccAddress , amount sdk . Coins ) error {
2020-11-12 15:50:54 +00:00
if amount . IsZero ( ) {
return types . ErrBorrowEmptyCoins
}
2020-11-09 21:52:08 +00:00
// Get the proposed borrow USD value
moneyMarketCache := map [ string ] types . MoneyMarket { }
proprosedBorrowUSDValue := sdk . ZeroDec ( )
for _ , coin := range amount {
moneyMarket , ok := moneyMarketCache [ coin . Denom ]
// Fetch money market and store in local cache
if ! ok {
2020-12-03 21:50:35 +00:00
newMoneyMarket , found := k . GetMoneyMarketParam ( ctx , coin . Denom )
2020-11-09 21:52:08 +00:00
if ! found {
return sdkerrors . Wrapf ( types . ErrMarketNotFound , "no market found for denom %s" , coin . Denom )
}
moneyMarketCache [ coin . Denom ] = newMoneyMarket
moneyMarket = newMoneyMarket
}
// Calculate this coin's USD value and add it borrow's total USD value
assetPriceInfo , err := k . pricefeedKeeper . GetCurrentPrice ( ctx , moneyMarket . SpotMarketID )
if err != nil {
return sdkerrors . Wrapf ( types . ErrPriceNotFound , "no price found for market %s" , moneyMarket . SpotMarketID )
}
coinUSDValue := sdk . NewDecFromInt ( coin . Amount ) . Quo ( sdk . NewDecFromInt ( moneyMarket . ConversionFactor ) ) . Mul ( assetPriceInfo . Price )
2020-11-12 15:50:54 +00:00
// Validate the requested borrow value for the asset against the money market's global borrow limit
if moneyMarket . BorrowLimit . HasMaxLimit {
var assetTotalBorrowedAmount sdk . Int
totalBorrowedCoins , found := k . GetBorrowedCoins ( ctx )
if ! found {
assetTotalBorrowedAmount = sdk . ZeroInt ( )
} else {
assetTotalBorrowedAmount = totalBorrowedCoins . AmountOf ( coin . Denom )
}
newProposedAssetTotalBorrowedAmount := sdk . NewDecFromInt ( assetTotalBorrowedAmount . Add ( coin . Amount ) )
if newProposedAssetTotalBorrowedAmount . GT ( moneyMarket . BorrowLimit . MaximumLimit ) {
return sdkerrors . Wrapf ( types . ErrGreaterThanAssetBorrowLimit ,
"proposed borrow would result in %s borrowed, but the maximum global asset borrow limit is %s" ,
newProposedAssetTotalBorrowedAmount , moneyMarket . BorrowLimit . MaximumLimit )
}
}
2020-11-09 21:52:08 +00:00
proprosedBorrowUSDValue = proprosedBorrowUSDValue . Add ( coinUSDValue )
2020-11-03 09:46:08 +00:00
}
2020-11-09 21:52:08 +00:00
// Get the total borrowable USD amount at user's existing deposits
2020-12-18 16:05:21 +00:00
deposit , found := k . GetDeposit ( ctx , borrower )
if ! found {
2020-11-03 09:46:08 +00:00
return sdkerrors . Wrapf ( types . ErrDepositsNotFound , "no deposits found for %s" , borrower )
}
2020-11-05 17:36:49 +00:00
totalBorrowableAmount := sdk . ZeroDec ( )
2020-12-18 16:05:21 +00:00
for _ , depCoin := range deposit . Amount {
moneyMarket , ok := moneyMarketCache [ depCoin . Denom ]
2020-11-09 21:52:08 +00:00
// Fetch money market and store in local cache
if ! ok {
2020-12-18 16:05:21 +00:00
newMoneyMarket , found := k . GetMoneyMarketParam ( ctx , depCoin . Denom )
2020-11-09 21:52:08 +00:00
if ! found {
2020-12-18 16:05:21 +00:00
return sdkerrors . Wrapf ( types . ErrMarketNotFound , "no market found for denom %s" , depCoin . Denom )
2020-11-09 21:52:08 +00:00
}
2020-12-18 16:05:21 +00:00
moneyMarketCache [ depCoin . Denom ] = newMoneyMarket
2020-11-09 21:52:08 +00:00
moneyMarket = newMoneyMarket
}
// Calculate the borrowable amount and add it to the user's total borrowable amount
assetPriceInfo , err := k . pricefeedKeeper . GetCurrentPrice ( ctx , moneyMarket . SpotMarketID )
2020-11-05 17:36:49 +00:00
if err != nil {
2021-02-04 16:53:50 +00:00
return sdkerrors . Wrapf ( types . ErrPriceNotFound , "no price found for market %s" , moneyMarket . SpotMarketID )
2020-11-05 17:36:49 +00:00
}
2020-12-18 16:05:21 +00:00
depositUSDValue := sdk . NewDecFromInt ( depCoin . Amount ) . Quo ( sdk . NewDecFromInt ( moneyMarket . ConversionFactor ) ) . Mul ( assetPriceInfo . Price )
2020-11-09 21:52:08 +00:00
borrowableAmountForDeposit := depositUSDValue . Mul ( moneyMarket . BorrowLimit . LoanToValue )
2020-11-05 17:36:49 +00:00
totalBorrowableAmount = totalBorrowableAmount . Add ( borrowableAmountForDeposit )
2020-11-03 09:46:08 +00:00
}
2020-11-09 21:52:08 +00:00
// Get the total USD value of user's existing borrows
existingBorrowUSDValue := sdk . ZeroDec ( )
existingBorrow , found := k . GetBorrow ( ctx , borrower )
2020-11-03 09:46:08 +00:00
if found {
2020-11-09 21:52:08 +00:00
for _ , borrowedCoin := range existingBorrow . Amount {
moneyMarket , ok := moneyMarketCache [ borrowedCoin . Denom ]
// Fetch money market and store in local cache
if ! ok {
2020-12-03 21:50:35 +00:00
newMoneyMarket , found := k . GetMoneyMarketParam ( ctx , borrowedCoin . Denom )
2020-11-09 21:52:08 +00:00
if ! found {
return sdkerrors . Wrapf ( types . ErrMarketNotFound , "no market found for denom %s" , borrowedCoin . Denom )
}
moneyMarketCache [ borrowedCoin . Denom ] = newMoneyMarket
moneyMarket = newMoneyMarket
}
// Calculate this borrow coin's USD value and add it to the total previous borrowed USD value
assetPriceInfo , err := k . pricefeedKeeper . GetCurrentPrice ( ctx , moneyMarket . SpotMarketID )
if err != nil {
return sdkerrors . Wrapf ( types . ErrPriceNotFound , "no price found for market %s" , moneyMarket . SpotMarketID )
}
coinUSDValue := sdk . NewDecFromInt ( borrowedCoin . Amount ) . Quo ( sdk . NewDecFromInt ( moneyMarket . ConversionFactor ) ) . Mul ( assetPriceInfo . Price )
existingBorrowUSDValue = existingBorrowUSDValue . Add ( coinUSDValue )
2020-11-03 09:46:08 +00:00
}
}
2020-11-05 17:36:49 +00:00
// Validate that the proposed borrow's USD value is within user's borrowable limit
2020-11-09 21:52:08 +00:00
if proprosedBorrowUSDValue . GT ( totalBorrowableAmount . Sub ( existingBorrowUSDValue ) ) {
2020-12-08 13:28:01 +00:00
return sdkerrors . Wrapf ( types . ErrInsufficientLoanToValue , "requested borrow %s exceeds the allowable amount as determined by the collateralization ratio" , amount )
2020-11-03 09:46:08 +00:00
}
return nil
}
2020-11-12 15:50:54 +00:00
2021-01-07 10:23:05 +00:00
// IncrementBorrowedCoins increments the total amount of borrowed coins by the newCoins parameter
2020-11-12 15:50:54 +00:00
func ( k Keeper ) IncrementBorrowedCoins ( ctx sdk . Context , newCoins sdk . Coins ) {
borrowedCoins , found := k . GetBorrowedCoins ( ctx )
if ! found {
2020-12-04 14:35:26 +00:00
if ! newCoins . Empty ( ) {
k . SetBorrowedCoins ( ctx , newCoins )
}
2020-11-12 15:50:54 +00:00
} else {
k . SetBorrowedCoins ( ctx , borrowedCoins . Add ( newCoins ... ) )
}
}
2021-01-07 10:23:05 +00:00
// DecrementBorrowedCoins decrements the total amount of borrowed coins by the coins parameter
2020-11-12 15:50:54 +00:00
func ( k Keeper ) DecrementBorrowedCoins ( ctx sdk . Context , coins sdk . Coins ) error {
borrowedCoins , found := k . GetBorrowedCoins ( ctx )
if ! found {
return sdkerrors . Wrapf ( types . ErrBorrowedCoinsNotFound , "cannot repay coins if no coins are currently borrowed" )
}
updatedBorrowedCoins , isAnyNegative := borrowedCoins . SafeSub ( coins )
if isAnyNegative {
return types . ErrNegativeBorrowedCoins
}
k . SetBorrowedCoins ( ctx , updatedBorrowedCoins )
return nil
}
2020-12-04 19:04:05 +00:00
2021-01-13 18:14:58 +00:00
// GetSyncedBorrow returns a borrow object containing current balances and indexes
func ( k Keeper ) GetSyncedBorrow ( ctx sdk . Context , borrower sdk . AccAddress ) ( types . Borrow , bool ) {
2020-12-04 19:04:05 +00:00
borrow , found := k . GetBorrow ( ctx , borrower )
2021-01-07 10:23:05 +00:00
if ! found {
2021-01-13 18:14:58 +00:00
return types . Borrow { } , false
2021-01-07 10:23:05 +00:00
}
2021-01-13 18:14:58 +00:00
return k . loadSyncedBorrow ( ctx , borrow ) , true
}
// loadSyncedBorrow calculates a user's synced borrow, but does not update state
func ( k Keeper ) loadSyncedBorrow ( ctx sdk . Context , borrow types . Borrow ) types . Borrow {
2021-01-07 10:23:05 +00:00
totalNewInterest := sdk . Coins { }
2021-01-13 18:14:58 +00:00
newBorrowIndexes := types . BorrowInterestFactors { }
2021-01-07 10:23:05 +00:00
for _ , coin := range borrow . Amount {
interestFactorValue , foundInterestFactorValue := k . GetBorrowInterestFactor ( ctx , coin . Denom )
if foundInterestFactorValue {
// Locate the interest factor by coin denom in the user's list of interest factors
foundAtIndex := - 1
for i := range borrow . Index {
if borrow . Index [ i ] . Denom == coin . Denom {
foundAtIndex = i
break
2020-12-04 19:04:05 +00:00
}
}
2021-01-13 18:14:58 +00:00
2021-01-07 10:23:05 +00:00
// Calculate interest owed by user for this asset
if foundAtIndex != - 1 {
storedAmount := sdk . NewDecFromInt ( borrow . Amount . AmountOf ( coin . Denom ) )
userLastInterestFactor := borrow . Index [ foundAtIndex ] . Value
coinInterest := ( storedAmount . Quo ( userLastInterestFactor ) . Mul ( interestFactorValue ) ) . Sub ( storedAmount )
totalNewInterest = totalNewInterest . Add ( sdk . NewCoin ( coin . Denom , coinInterest . TruncateInt ( ) ) )
}
2020-12-04 19:04:05 +00:00
}
2021-01-13 18:14:58 +00:00
borrowIndex := types . NewBorrowInterestFactor ( coin . Denom , interestFactorValue )
newBorrowIndexes = append ( newBorrowIndexes , borrowIndex )
2020-12-04 19:04:05 +00:00
}
2021-01-07 10:23:05 +00:00
2021-01-13 18:14:58 +00:00
return types . NewBorrow ( borrow . Borrower , borrow . Amount . Add ( totalNewInterest ... ) , newBorrowIndexes )
2020-12-04 19:04:05 +00:00
}