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
}
}
2021-02-08 12:23:37 +00:00
// Call incentive hooks
existingDeposit , hasExistingDeposit := k . GetDeposit ( ctx , borrower )
if hasExistingDeposit {
k . BeforeDepositModified ( ctx , existingDeposit )
2020-12-16 21:08:29 +00:00
}
2021-01-21 13:52:09 +00:00
existingBorrow , hasExistingBorrow := k . GetBorrow ( ctx , borrower )
if hasExistingBorrow {
k . BeforeBorrowModified ( ctx , existingBorrow )
}
2021-02-04 16:54:13 +00:00
k . SyncSupplyInterest ( ctx , borrower )
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
2021-02-08 12:23:37 +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-02-15 15:30:41 +00:00
if err != nil {
return err
}
2020-10-30 09:59:47 +00:00
2021-02-05 11:31:38 +00:00
interestFactors := types . BorrowInterestFactors { }
2021-01-07 10:23:05 +00:00
currBorrow , foundBorrow := k . GetBorrow ( ctx , borrower )
if foundBorrow {
2021-02-05 11:31:38 +00:00
interestFactors = currBorrow . Index
}
for _ , coin := range coins {
interestFactorValue , foundValue := k . GetBorrowInterestFactor ( ctx , coin . Denom )
if foundValue {
interestFactors = interestFactors . SetInterestFactor ( coin . Denom , interestFactorValue )
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-02-08 12:23:37 +00:00
2021-01-07 21:40:25 +00:00
// Construct the user's new/updated borrow with amount and interest factors
2021-02-05 11:31:38 +00:00
borrow := types . NewBorrow ( borrower , amount , interestFactors )
2021-02-08 12:23:37 +00:00
if borrow . Amount . Empty ( ) {
k . DeleteBorrow ( ctx , borrow )
} else {
k . SetBorrow ( ctx , borrow )
2021-01-07 21:40:25 +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
}
2021-02-16 14:45:57 +00:00
// The reserve coins aren't available for users to borrow
hardMaccCoins := k . supplyKeeper . GetModuleAccount ( ctx , types . ModuleName ) . GetCoins ( )
reserveCoins , foundReserveCoins := k . GetTotalReserves ( ctx )
if ! foundReserveCoins {
reserveCoins = sdk . NewCoins ( )
}
fundsAvailableToBorrow , isNegative := hardMaccCoins . SafeSub ( reserveCoins )
if isNegative {
return sdkerrors . Wrapf ( types . ErrReservesExceedCash , "reserves %s > cash %s" , reserveCoins , hardMaccCoins )
}
if amount . IsAnyGT ( fundsAvailableToBorrow ) {
return sdkerrors . Wrapf ( types . ErrExceedsProtocolBorrowableBalance , "requested borrow %s > available to borrow %s" , amount , fundsAvailableToBorrow )
}
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
}
}
2021-02-12 15:28:05 +00:00
// Borrow's updated total USD value must be greater than the minimum global USD borrow limit
totalBorrowUSDValue := proprosedBorrowUSDValue . Add ( existingBorrowUSDValue )
if totalBorrowUSDValue . LT ( k . GetMinimumBorrowUSDValue ( ctx ) ) {
return sdkerrors . Wrapf ( types . ErrBelowMinimumBorrowValue , "the proposed borrow's USD value $%s is below the minimum borrow limit $%s" , totalBorrowUSDValue , k . GetMinimumBorrowUSDValue ( ctx ) )
}
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" )
}
2021-02-12 20:07:32 +00:00
updatedBorrowedCoins := sdk . NewCoins ( )
for _ , coin := range coins {
// If amount is greater than total borrowed amount due to rounding, set total borrowed amount to 0
// by skipping the coin such that it's not included in the updatedBorrowedCoins object
if coin . Amount . GTE ( borrowedCoins . AmountOf ( coin . Denom ) ) {
continue
}
updatedBorrowCoin := sdk . NewCoin ( coin . Denom , borrowedCoins . AmountOf ( coin . Denom ) . Sub ( coin . Amount ) )
updatedBorrowedCoins = updatedBorrowedCoins . Add ( updatedBorrowCoin )
2020-11-12 15:50:54 +00:00
}
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
}