mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 01:47:27 +00:00 
			
		
		
		
	Hard: suppliers earn interest (#749)
* update to borrow interest factor * add supply interest factor to accrue interest * supply interest factor keeper methods * fix potential bug with user borrow indexing * sync supply interest on deposit/withdraw * separate withdraw/deposit * relocate interest sync methods * update comment * simplify supply interest statement * check truncated int not zero * add .sub(storedAmount) back * add store key suppliedcoins * increment/decrement supplied coins * update withdraw with new accounting * update withdraw test * catch repay edge case * unit tests * TestSupplyInterest scaffolding * test notes * temp: interest test * example test * changes, test checks more state * fix: calculate supply interest directly * fix: catch divide by zero * add state checks back into interest test * add snapshot test cases * test owed supplied interest paid at correct ratio * test user supply syncs user's borrow interest * remove print statements and clean up * refactor indented logic * test supply/borrow multiple coins * update decoder test Co-authored-by: karzak <kjydavis3@gmail.com>
This commit is contained in:
		
							parent
							
								
									e9f5043c84
								
							
						
					
					
						commit
						f7a73c9245
					
				@ -53,7 +53,8 @@ var (
 | 
				
			|||||||
	NewQuerier                       = keeper.NewQuerier
 | 
						NewQuerier                       = keeper.NewQuerier
 | 
				
			||||||
	CalculateUtilizationRatio        = keeper.CalculateUtilizationRatio
 | 
						CalculateUtilizationRatio        = keeper.CalculateUtilizationRatio
 | 
				
			||||||
	CalculateBorrowRate              = keeper.CalculateBorrowRate
 | 
						CalculateBorrowRate              = keeper.CalculateBorrowRate
 | 
				
			||||||
	CalculateInterestFactor          = keeper.CalculateInterestFactor
 | 
						CalculateBorrowInterestFactor    = keeper.CalculateBorrowInterestFactor
 | 
				
			||||||
 | 
						CalculateSupplyInterestFactor    = keeper.CalculateSupplyInterestFactor
 | 
				
			||||||
	APYToSPY                         = keeper.APYToSPY
 | 
						APYToSPY                         = keeper.APYToSPY
 | 
				
			||||||
	ClaimKey                         = types.ClaimKey
 | 
						ClaimKey                         = types.ClaimKey
 | 
				
			||||||
	DefaultGenesisState              = types.DefaultGenesisState
 | 
						DefaultGenesisState              = types.DefaultGenesisState
 | 
				
			||||||
 | 
				
			|||||||
@ -13,11 +13,11 @@ import (
 | 
				
			|||||||
func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins) error {
 | 
					func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins) error {
 | 
				
			||||||
	// Set any new denoms' global borrow index to 1.0
 | 
						// Set any new denoms' global borrow index to 1.0
 | 
				
			||||||
	for _, coin := range coins {
 | 
						for _, coin := range coins {
 | 
				
			||||||
		_, foundInterestFactor := k.GetInterestFactor(ctx, coin.Denom)
 | 
							_, foundInterestFactor := k.GetBorrowInterestFactor(ctx, coin.Denom)
 | 
				
			||||||
		if !foundInterestFactor {
 | 
							if !foundInterestFactor {
 | 
				
			||||||
			_, foundMm := k.GetMoneyMarket(ctx, coin.Denom)
 | 
								_, foundMm := k.GetMoneyMarket(ctx, coin.Denom)
 | 
				
			||||||
			if foundMm {
 | 
								if foundMm {
 | 
				
			||||||
				k.SetInterestFactor(ctx, coin.Denom, sdk.OneDec())
 | 
									k.SetBorrowInterestFactor(ctx, coin.Denom, sdk.OneDec())
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -28,11 +28,8 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If the user has an existing borrow, sync its outstanding interest
 | 
						// Sync any outstanding interest
 | 
				
			||||||
	_, found := k.GetBorrow(ctx, borrower)
 | 
						k.SyncBorrowInterest(ctx, borrower)
 | 
				
			||||||
	if found {
 | 
					 | 
				
			||||||
		k.SyncOutstandingInterest(ctx, borrower)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Validate borrow amount within user and protocol limits
 | 
						// Validate borrow amount within user and protocol limits
 | 
				
			||||||
	err = k.ValidateBorrow(ctx, borrower, coins)
 | 
						err = k.ValidateBorrow(ctx, borrower, coins)
 | 
				
			||||||
@ -57,22 +54,39 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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)
 | 
				
			||||||
	// On user's first borrow, build borrow index list containing denoms and current global borrow index value
 | 
						// On user's first borrow, build borrow index list containing denoms and current global borrow index value
 | 
				
			||||||
	// We use a list of BorrowIndexItem here because Amino doesn't support marshaling maps.
 | 
						if foundBorrow {
 | 
				
			||||||
	if !found {
 | 
							// If the coin denom to be borrowed is not in the user's existing borrow, we add it borrow index
 | 
				
			||||||
		var interestFactors types.InterestFactors
 | 
					 | 
				
			||||||
		for _, coin := range coins {
 | 
							for _, coin := range coins {
 | 
				
			||||||
			interestFactorValue, _ := k.GetInterestFactor(ctx, coin.Denom)
 | 
								if !sdk.NewCoins(coin).DenomsSubsetOf(currBorrow.Amount) {
 | 
				
			||||||
			interestFactor := types.NewInterestFactor(coin.Denom, interestFactorValue)
 | 
									borrowInterestFactorValue, _ := k.GetBorrowInterestFactor(ctx, coin.Denom)
 | 
				
			||||||
			interestFactors = append(interestFactors, interestFactor)
 | 
									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)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		borrow := types.NewBorrow(borrower, sdk.Coins{}, interestFactors)
 | 
					 | 
				
			||||||
		k.SetBorrow(ctx, borrow)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Add the newly borrowed coins to the user's borrow object
 | 
						// Calculate new borrow amount
 | 
				
			||||||
	borrow, _ := k.GetBorrow(ctx, borrower)
 | 
						var amount sdk.Coins
 | 
				
			||||||
	borrow.Amount = borrow.Amount.Add(coins...)
 | 
						if foundBorrow {
 | 
				
			||||||
 | 
							amount = currBorrow.Amount.Add(coins...)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							amount = coins
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update the borrower's amount and borrow interest factors in the store
 | 
				
			||||||
 | 
						borrow := types.NewBorrow(borrower, amount, borrowInterestFactors)
 | 
				
			||||||
	k.SetBorrow(ctx, borrow)
 | 
						k.SetBorrow(ctx, borrow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, borrower)
 | 
						k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, borrower)
 | 
				
			||||||
@ -92,46 +106,6 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SyncOutstandingInterest updates the user's owed interest on newly borrowed coins to the latest global state
 | 
					 | 
				
			||||||
func (k Keeper) SyncOutstandingInterest(ctx sdk.Context, addr sdk.AccAddress) {
 | 
					 | 
				
			||||||
	totalNewInterest := sdk.Coins{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Update user's borrow index list for each asset in the 'coins' array.
 | 
					 | 
				
			||||||
	// We use a list of BorrowIndexItem here because Amino doesn't support marshaling maps.
 | 
					 | 
				
			||||||
	borrow, found := k.GetBorrow(ctx, addr)
 | 
					 | 
				
			||||||
	if !found {
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, coin := range borrow.Amount {
 | 
					 | 
				
			||||||
		// Locate the borrow index item by coin denom in the user's list of borrow indexes
 | 
					 | 
				
			||||||
		foundAtIndex := -1
 | 
					 | 
				
			||||||
		for i := range borrow.Index {
 | 
					 | 
				
			||||||
			if borrow.Index[i].Denom == coin.Denom {
 | 
					 | 
				
			||||||
				foundAtIndex = i
 | 
					 | 
				
			||||||
				break
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		interestFactorValue, _ := k.GetInterestFactor(ctx, coin.Denom)
 | 
					 | 
				
			||||||
		if foundAtIndex == -1 { // First time user has borrowed this denom
 | 
					 | 
				
			||||||
			borrow.Index = append(borrow.Index, types.NewInterestFactor(coin.Denom, interestFactorValue))
 | 
					 | 
				
			||||||
		} else { // User has an existing borrow index for this denom
 | 
					 | 
				
			||||||
			// Calculate interest owed by user since asset's last borrow index update
 | 
					 | 
				
			||||||
			storedAmount := sdk.NewDecFromInt(borrow.Amount.AmountOf(coin.Denom))
 | 
					 | 
				
			||||||
			userLastInterestFactor := borrow.Index[foundAtIndex].Value
 | 
					 | 
				
			||||||
			interest := (storedAmount.Quo(userLastInterestFactor).Mul(interestFactorValue)).Sub(storedAmount)
 | 
					 | 
				
			||||||
			totalNewInterest = totalNewInterest.Add(sdk.NewCoin(coin.Denom, interest.TruncateInt()))
 | 
					 | 
				
			||||||
			// We're synced up, so update user's borrow index value to match the current global borrow index value
 | 
					 | 
				
			||||||
			borrow.Index[foundAtIndex].Value = interestFactorValue
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	// Add all pending interest to user's borrow
 | 
					 | 
				
			||||||
	borrow.Amount = borrow.Amount.Add(totalNewInterest...)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Update user's borrow in the store
 | 
					 | 
				
			||||||
	k.SetBorrow(ctx, borrow)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ValidateBorrow validates a borrow request against borrower and protocol requirements
 | 
					// ValidateBorrow validates a borrow request against borrower and protocol requirements
 | 
				
			||||||
func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount sdk.Coins) error {
 | 
					func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount sdk.Coins) error {
 | 
				
			||||||
	if amount.IsZero() {
 | 
						if amount.IsZero() {
 | 
				
			||||||
@ -240,7 +214,7 @@ func (k Keeper) ValidateBorrow(ctx sdk.Context, borrower sdk.AccAddress, amount
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IncrementBorrowedCoins increments the amount of borrowed coins by the newCoins parameter
 | 
					// IncrementBorrowedCoins increments the total amount of borrowed coins by the newCoins parameter
 | 
				
			||||||
func (k Keeper) IncrementBorrowedCoins(ctx sdk.Context, newCoins sdk.Coins) {
 | 
					func (k Keeper) IncrementBorrowedCoins(ctx sdk.Context, newCoins sdk.Coins) {
 | 
				
			||||||
	borrowedCoins, found := k.GetBorrowedCoins(ctx)
 | 
						borrowedCoins, found := k.GetBorrowedCoins(ctx)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
@ -252,7 +226,7 @@ func (k Keeper) IncrementBorrowedCoins(ctx sdk.Context, newCoins sdk.Coins) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DecrementBorrowedCoins decrements the amount of borrowed coins by the coins parameter
 | 
					// DecrementBorrowedCoins decrements the total amount of borrowed coins by the coins parameter
 | 
				
			||||||
func (k Keeper) DecrementBorrowedCoins(ctx sdk.Context, coins sdk.Coins) error {
 | 
					func (k Keeper) DecrementBorrowedCoins(ctx sdk.Context, coins sdk.Coins) error {
 | 
				
			||||||
	borrowedCoins, found := k.GetBorrowedCoins(ctx)
 | 
						borrowedCoins, found := k.GetBorrowedCoins(ctx)
 | 
				
			||||||
	if !found {
 | 
						if !found {
 | 
				
			||||||
@ -272,29 +246,31 @@ func (k Keeper) DecrementBorrowedCoins(ctx sdk.Context, coins sdk.Coins) error {
 | 
				
			|||||||
func (k Keeper) GetBorrowBalance(ctx sdk.Context, borrower sdk.AccAddress) sdk.Coins {
 | 
					func (k Keeper) GetBorrowBalance(ctx sdk.Context, borrower sdk.AccAddress) sdk.Coins {
 | 
				
			||||||
	borrowBalance := sdk.Coins{}
 | 
						borrowBalance := sdk.Coins{}
 | 
				
			||||||
	borrow, found := k.GetBorrow(ctx, borrower)
 | 
						borrow, found := k.GetBorrow(ctx, borrower)
 | 
				
			||||||
	if found {
 | 
						if !found {
 | 
				
			||||||
		totalNewInterest := sdk.Coins{}
 | 
							return borrowBalance
 | 
				
			||||||
		for _, coin := range borrow.Amount {
 | 
						}
 | 
				
			||||||
			interestFactorValue, foundInterestFactorValue := k.GetInterestFactor(ctx, coin.Denom)
 | 
					
 | 
				
			||||||
			if foundInterestFactorValue {
 | 
						totalNewInterest := sdk.Coins{}
 | 
				
			||||||
				// Locate the interest factor by coin denom in the user's list of interest factors
 | 
						for _, coin := range borrow.Amount {
 | 
				
			||||||
				foundAtIndex := -1
 | 
							interestFactorValue, foundInterestFactorValue := k.GetBorrowInterestFactor(ctx, coin.Denom)
 | 
				
			||||||
				for i := range borrow.Index {
 | 
							if foundInterestFactorValue {
 | 
				
			||||||
					if borrow.Index[i].Denom == coin.Denom {
 | 
								// Locate the interest factor by coin denom in the user's list of interest factors
 | 
				
			||||||
						foundAtIndex = i
 | 
								foundAtIndex := -1
 | 
				
			||||||
						break
 | 
								for i := range borrow.Index {
 | 
				
			||||||
					}
 | 
									if borrow.Index[i].Denom == coin.Denom {
 | 
				
			||||||
				}
 | 
										foundAtIndex = i
 | 
				
			||||||
				// Calculate interest owed by user for this asset
 | 
										break
 | 
				
			||||||
				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()))
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								// 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()))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		borrowBalance = borrow.Amount.Add(totalNewInterest...)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return borrowBalance
 | 
					
 | 
				
			||||||
 | 
						return borrow.Amount.Add(totalNewInterest...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -12,13 +12,26 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Deposit deposit
 | 
					// Deposit deposit
 | 
				
			||||||
func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error {
 | 
					func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error {
 | 
				
			||||||
 | 
						// Set any new denoms' global supply index to 1.0
 | 
				
			||||||
 | 
						for _, coin := range coins {
 | 
				
			||||||
 | 
							_, foundInterestFactor := k.GetSupplyInterestFactor(ctx, coin.Denom)
 | 
				
			||||||
 | 
							if !foundInterestFactor {
 | 
				
			||||||
 | 
								_, foundMm := k.GetMoneyMarket(ctx, coin.Denom)
 | 
				
			||||||
 | 
								if foundMm {
 | 
				
			||||||
 | 
									k.SetSupplyInterestFactor(ctx, coin.Denom, sdk.OneDec())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get current stored LTV based on stored borrows/deposits
 | 
						// Get current stored LTV based on stored borrows/deposits
 | 
				
			||||||
	prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor)
 | 
						prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k.SyncOutstandingInterest(ctx, depositor)
 | 
						// Sync any outstanding interest
 | 
				
			||||||
 | 
						k.SyncBorrowInterest(ctx, depositor)
 | 
				
			||||||
 | 
						k.SyncSupplyInterest(ctx, depositor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	err = k.ValidateDeposit(ctx, coins)
 | 
						err = k.ValidateDeposit(ctx, coins)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@ -44,17 +57,47 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	deposit, found := k.GetDeposit(ctx, depositor)
 | 
						// The first time a user deposits a denom we add it the user's supply interest factor index
 | 
				
			||||||
	if !found {
 | 
						var supplyInterestFactors types.SupplyInterestFactors
 | 
				
			||||||
		deposit = types.NewDeposit(depositor, coins)
 | 
						currDeposit, foundDeposit := k.GetDeposit(ctx, depositor)
 | 
				
			||||||
 | 
						// On user's first deposit, build deposit index list containing denoms and current global deposit index value
 | 
				
			||||||
 | 
						if foundDeposit {
 | 
				
			||||||
 | 
							// If the coin denom to be deposited is not in the user's existing deposit, we add it deposit index
 | 
				
			||||||
 | 
							for _, coin := range coins {
 | 
				
			||||||
 | 
								if !sdk.NewCoins(coin).DenomsSubsetOf(currDeposit.Amount) {
 | 
				
			||||||
 | 
									supplyInterestFactorValue, _ := k.GetSupplyInterestFactor(ctx, coin.Denom)
 | 
				
			||||||
 | 
									supplyInterestFactor := types.NewSupplyInterestFactor(coin.Denom, supplyInterestFactorValue)
 | 
				
			||||||
 | 
									supplyInterestFactors = append(supplyInterestFactors, supplyInterestFactor)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Concatenate new deposit interest factors to existing deposit interest factors
 | 
				
			||||||
 | 
							supplyInterestFactors = append(supplyInterestFactors, currDeposit.Index...)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		deposit.Amount = deposit.Amount.Add(coins...)
 | 
							for _, coin := range coins {
 | 
				
			||||||
 | 
								supplyInterestFactorValue, _ := k.GetSupplyInterestFactor(ctx, coin.Denom)
 | 
				
			||||||
 | 
								supplyInterestFactor := types.NewSupplyInterestFactor(coin.Denom, supplyInterestFactorValue)
 | 
				
			||||||
 | 
								supplyInterestFactors = append(supplyInterestFactors, supplyInterestFactor)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate new deposit amount
 | 
				
			||||||
 | 
						var amount sdk.Coins
 | 
				
			||||||
 | 
						if foundDeposit {
 | 
				
			||||||
 | 
							amount = currDeposit.Amount.Add(coins...)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							amount = coins
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update the depositer's amount and supply interest factors in the store
 | 
				
			||||||
 | 
						deposit := types.NewDeposit(depositor, amount, supplyInterestFactors)
 | 
				
			||||||
	k.SetDeposit(ctx, deposit)
 | 
						k.SetDeposit(ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, depositor)
 | 
						k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, depositor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update total supplied amount by newly supplied coins. Don't add user's pending interest as
 | 
				
			||||||
 | 
						// it has already been included in the total supplied coins by the BeginBlocker.
 | 
				
			||||||
 | 
						k.IncrementSuppliedCoins(ctx, coins)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.EventManager().EmitEvent(
 | 
						ctx.EventManager().EmitEvent(
 | 
				
			||||||
		sdk.NewEvent(
 | 
							sdk.NewEvent(
 | 
				
			||||||
			types.EventTypeHardDeposit,
 | 
								types.EventTypeHardDeposit,
 | 
				
			||||||
@ -84,76 +127,37 @@ func (k Keeper) ValidateDeposit(ctx sdk.Context, coins sdk.Coins) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Withdraw returns some or all of a deposit back to original depositor
 | 
					 | 
				
			||||||
func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error {
 | 
					 | 
				
			||||||
	deposit, found := k.GetDeposit(ctx, depositor)
 | 
					 | 
				
			||||||
	if !found {
 | 
					 | 
				
			||||||
		return sdkerrors.Wrapf(types.ErrDepositNotFound, "no deposit found for %s", depositor)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get current stored LTV based on stored borrows/deposits
 | 
					 | 
				
			||||||
	prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	k.SyncOutstandingInterest(ctx, depositor)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	borrow, found := k.GetBorrow(ctx, depositor)
 | 
					 | 
				
			||||||
	if !found {
 | 
					 | 
				
			||||||
		borrow = types.Borrow{}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	proposedDepositAmount, isNegative := deposit.Amount.SafeSub(coins)
 | 
					 | 
				
			||||||
	if isNegative {
 | 
					 | 
				
			||||||
		return types.ErrNegativeBorrowedCoins
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	proposedDeposit := types.NewDeposit(deposit.Depositor, proposedDepositAmount)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	valid, err := k.IsWithinValidLtvRange(ctx, proposedDeposit, borrow)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if !valid {
 | 
					 | 
				
			||||||
		return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "proposed withdraw outside loan-to-value range")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, depositor, coins)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ctx.EventManager().EmitEvent(
 | 
					 | 
				
			||||||
		sdk.NewEvent(
 | 
					 | 
				
			||||||
			types.EventTypeHardWithdrawal,
 | 
					 | 
				
			||||||
			sdk.NewAttribute(sdk.AttributeKeyAmount, coins.String()),
 | 
					 | 
				
			||||||
			sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()),
 | 
					 | 
				
			||||||
		),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if deposit.Amount.IsEqual(coins) {
 | 
					 | 
				
			||||||
		ctx.EventManager().EmitEvent(
 | 
					 | 
				
			||||||
			sdk.NewEvent(
 | 
					 | 
				
			||||||
				types.EventTypeDeleteHardDeposit,
 | 
					 | 
				
			||||||
				sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()),
 | 
					 | 
				
			||||||
			),
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
		k.DeleteDeposit(ctx, deposit)
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	deposit.Amount = deposit.Amount.Sub(coins)
 | 
					 | 
				
			||||||
	k.SetDeposit(ctx, deposit)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, depositor)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetTotalDeposited returns the total amount deposited for the input deposit type and deposit denom
 | 
					// GetTotalDeposited returns the total amount deposited for the input deposit type and deposit denom
 | 
				
			||||||
func (k Keeper) GetTotalDeposited(ctx sdk.Context, depositDenom string) (total sdk.Int) {
 | 
					func (k Keeper) GetTotalDeposited(ctx sdk.Context, depositDenom string) (total sdk.Int) {
 | 
				
			||||||
	var macc supplyExported.ModuleAccountI
 | 
						var macc supplyExported.ModuleAccountI
 | 
				
			||||||
	macc = k.supplyKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
 | 
						macc = k.supplyKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
 | 
				
			||||||
	return macc.GetCoins().AmountOf(depositDenom)
 | 
						return macc.GetCoins().AmountOf(depositDenom)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IncrementSuppliedCoins increments the total amount of supplied coins by the newCoins parameter
 | 
				
			||||||
 | 
					func (k Keeper) IncrementSuppliedCoins(ctx sdk.Context, newCoins sdk.Coins) {
 | 
				
			||||||
 | 
						suppliedCoins, found := k.GetSuppliedCoins(ctx)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							if !newCoins.Empty() {
 | 
				
			||||||
 | 
								k.SetSuppliedCoins(ctx, newCoins)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							k.SetSuppliedCoins(ctx, suppliedCoins.Add(newCoins...))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DecrementSuppliedCoins decrements the total amount of supplied coins by the coins parameter
 | 
				
			||||||
 | 
					func (k Keeper) DecrementSuppliedCoins(ctx sdk.Context, coins sdk.Coins) error {
 | 
				
			||||||
 | 
						suppliedCoins, found := k.GetSuppliedCoins(ctx)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							return sdkerrors.Wrapf(types.ErrSuppliedCoinsNotFound, "cannot withdraw if no coins are deposited")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						updatedSuppliedCoins, isAnyNegative := suppliedCoins.SafeSub(coins)
 | 
				
			||||||
 | 
						if isAnyNegative {
 | 
				
			||||||
 | 
							return types.ErrNegativeSuppliedCoins
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						k.SetSuppliedCoins(ctx, updatedSuppliedCoins)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -196,360 +196,3 @@ func (suite *KeeperTestSuite) TestDeposit() {
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func (suite *KeeperTestSuite) TestWithdraw() {
 | 
					 | 
				
			||||||
	type args struct {
 | 
					 | 
				
			||||||
		depositor                 sdk.AccAddress
 | 
					 | 
				
			||||||
		depositAmount             sdk.Coins
 | 
					 | 
				
			||||||
		withdrawAmount            sdk.Coins
 | 
					 | 
				
			||||||
		createDeposit             bool
 | 
					 | 
				
			||||||
		expectedAccountBalance    sdk.Coins
 | 
					 | 
				
			||||||
		expectedModAccountBalance sdk.Coins
 | 
					 | 
				
			||||||
		depositExists             bool
 | 
					 | 
				
			||||||
		finalDepositAmount        sdk.Coins
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	type errArgs struct {
 | 
					 | 
				
			||||||
		expectPass bool
 | 
					 | 
				
			||||||
		contains   string
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	type withdrawTest struct {
 | 
					 | 
				
			||||||
		name    string
 | 
					 | 
				
			||||||
		args    args
 | 
					 | 
				
			||||||
		errArgs errArgs
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	testCases := []withdrawTest{
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"valid partial withdraw",
 | 
					 | 
				
			||||||
			args{
 | 
					 | 
				
			||||||
				depositor:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
					 | 
				
			||||||
				depositAmount:             sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
					 | 
				
			||||||
				withdrawAmount:            sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
 | 
					 | 
				
			||||||
				createDeposit:             true,
 | 
					 | 
				
			||||||
				expectedAccountBalance:    sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(900)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
 | 
					 | 
				
			||||||
				expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
 | 
					 | 
				
			||||||
				depositExists:             true,
 | 
					 | 
				
			||||||
				finalDepositAmount:        sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errArgs{
 | 
					 | 
				
			||||||
				expectPass: true,
 | 
					 | 
				
			||||||
				contains:   "",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"valid full withdraw",
 | 
					 | 
				
			||||||
			args{
 | 
					 | 
				
			||||||
				depositor:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
					 | 
				
			||||||
				depositAmount:             sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
					 | 
				
			||||||
				withdrawAmount:            sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
					 | 
				
			||||||
				createDeposit:             true,
 | 
					 | 
				
			||||||
				expectedAccountBalance:    sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
 | 
					 | 
				
			||||||
				expectedModAccountBalance: sdk.Coins(nil),
 | 
					 | 
				
			||||||
				depositExists:             false,
 | 
					 | 
				
			||||||
				finalDepositAmount:        sdk.Coins{},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errArgs{
 | 
					 | 
				
			||||||
				expectPass: true,
 | 
					 | 
				
			||||||
				contains:   "",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"withdraw invalid, denom not found",
 | 
					 | 
				
			||||||
			args{
 | 
					 | 
				
			||||||
				depositor:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
					 | 
				
			||||||
				depositAmount:             sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
					 | 
				
			||||||
				withdrawAmount:            sdk.NewCoins(sdk.NewCoin("btcb", sdk.NewInt(200))),
 | 
					 | 
				
			||||||
				createDeposit:             true,
 | 
					 | 
				
			||||||
				expectedAccountBalance:    sdk.Coins{},
 | 
					 | 
				
			||||||
				expectedModAccountBalance: sdk.Coins{},
 | 
					 | 
				
			||||||
				depositExists:             false,
 | 
					 | 
				
			||||||
				finalDepositAmount:        sdk.Coins{},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errArgs{
 | 
					 | 
				
			||||||
				expectPass: false,
 | 
					 | 
				
			||||||
				contains:   "subtraction results in negative borrow amount",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"withdraw exceeds deposit",
 | 
					 | 
				
			||||||
			args{
 | 
					 | 
				
			||||||
				depositor:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
					 | 
				
			||||||
				depositAmount:             sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
					 | 
				
			||||||
				withdrawAmount:            sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(300))),
 | 
					 | 
				
			||||||
				createDeposit:             true,
 | 
					 | 
				
			||||||
				expectedAccountBalance:    sdk.Coins{},
 | 
					 | 
				
			||||||
				expectedModAccountBalance: sdk.Coins{},
 | 
					 | 
				
			||||||
				depositExists:             false,
 | 
					 | 
				
			||||||
				finalDepositAmount:        sdk.Coins{},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errArgs{
 | 
					 | 
				
			||||||
				expectPass: false,
 | 
					 | 
				
			||||||
				contains:   "subtraction results in negative borrow amount",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	for _, tc := range testCases {
 | 
					 | 
				
			||||||
		suite.Run(tc.name, func() {
 | 
					 | 
				
			||||||
			// create new app with one funded account
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Initialize test app and set context
 | 
					 | 
				
			||||||
			tApp := app.NewTestApp()
 | 
					 | 
				
			||||||
			ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
 | 
					 | 
				
			||||||
			authGS := app.NewAuthGenState([]sdk.AccAddress{tc.args.depositor}, []sdk.Coins{sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000)))})
 | 
					 | 
				
			||||||
			loanToValue := sdk.MustNewDecFromStr("0.6")
 | 
					 | 
				
			||||||
			hardGS := types.NewGenesisState(types.NewParams(
 | 
					 | 
				
			||||||
				true,
 | 
					 | 
				
			||||||
				types.DistributionSchedules{
 | 
					 | 
				
			||||||
					types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
 | 
					 | 
				
			||||||
					types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
					 | 
				
			||||||
					time.Hour*24,
 | 
					 | 
				
			||||||
				),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				types.MoneyMarkets{
 | 
					 | 
				
			||||||
					types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
 | 
					 | 
				
			||||||
					types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
 | 
					 | 
				
			||||||
					types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(100000000), sdk.NewInt(BNB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				0, // LTV counter
 | 
					 | 
				
			||||||
			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Pricefeed module genesis state
 | 
					 | 
				
			||||||
			pricefeedGS := pricefeed.GenesisState{
 | 
					 | 
				
			||||||
				Params: pricefeed.Params{
 | 
					 | 
				
			||||||
					Markets: []pricefeed.Market{
 | 
					 | 
				
			||||||
						{MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
					 | 
				
			||||||
						{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
					 | 
				
			||||||
						{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				PostedPrices: []pricefeed.PostedPrice{
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						MarketID:      "usdx:usd",
 | 
					 | 
				
			||||||
						OracleAddress: sdk.AccAddress{},
 | 
					 | 
				
			||||||
						Price:         sdk.MustNewDecFromStr("1.00"),
 | 
					 | 
				
			||||||
						Expiry:        time.Now().Add(100 * time.Hour),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						MarketID:      "kava:usd",
 | 
					 | 
				
			||||||
						OracleAddress: sdk.AccAddress{},
 | 
					 | 
				
			||||||
						Price:         sdk.MustNewDecFromStr("2.00"),
 | 
					 | 
				
			||||||
						Expiry:        time.Now().Add(100 * time.Hour),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						MarketID:      "bnb:usd",
 | 
					 | 
				
			||||||
						OracleAddress: sdk.AccAddress{},
 | 
					 | 
				
			||||||
						Price:         sdk.MustNewDecFromStr("10.00"),
 | 
					 | 
				
			||||||
						Expiry:        time.Now().Add(100 * time.Hour),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			tApp.InitializeFromGenesisStates(authGS,
 | 
					 | 
				
			||||||
				app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)},
 | 
					 | 
				
			||||||
				app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)})
 | 
					 | 
				
			||||||
			keeper := tApp.GetHardKeeper()
 | 
					 | 
				
			||||||
			suite.app = tApp
 | 
					 | 
				
			||||||
			suite.ctx = ctx
 | 
					 | 
				
			||||||
			suite.keeper = keeper
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if tc.args.createDeposit {
 | 
					 | 
				
			||||||
				err := suite.keeper.Deposit(suite.ctx, tc.args.depositor, tc.args.depositAmount)
 | 
					 | 
				
			||||||
				suite.Require().NoError(err)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			err := suite.keeper.Withdraw(suite.ctx, tc.args.depositor, tc.args.withdrawAmount)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if tc.errArgs.expectPass {
 | 
					 | 
				
			||||||
				suite.Require().NoError(err)
 | 
					 | 
				
			||||||
				acc := suite.getAccount(tc.args.depositor)
 | 
					 | 
				
			||||||
				suite.Require().Equal(tc.args.expectedAccountBalance, acc.GetCoins())
 | 
					 | 
				
			||||||
				mAcc := suite.getModuleAccount(types.ModuleAccountName)
 | 
					 | 
				
			||||||
				suite.Require().Equal(tc.args.expectedModAccountBalance, mAcc.GetCoins())
 | 
					 | 
				
			||||||
				testDeposit, f := suite.keeper.GetDeposit(suite.ctx, tc.args.depositor)
 | 
					 | 
				
			||||||
				if tc.args.depositExists {
 | 
					 | 
				
			||||||
					suite.Require().True(f)
 | 
					 | 
				
			||||||
					suite.Require().Equal(tc.args.finalDepositAmount, testDeposit.Amount)
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					suite.Require().False(f)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				suite.Require().Error(err)
 | 
					 | 
				
			||||||
				suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (suite *KeeperTestSuite) TestLtvWithdraw() {
 | 
					 | 
				
			||||||
	type args struct {
 | 
					 | 
				
			||||||
		borrower             sdk.AccAddress
 | 
					 | 
				
			||||||
		keeper               sdk.AccAddress
 | 
					 | 
				
			||||||
		initialModuleCoins   sdk.Coins
 | 
					 | 
				
			||||||
		initialBorrowerCoins sdk.Coins
 | 
					 | 
				
			||||||
		initialKeeperCoins   sdk.Coins
 | 
					 | 
				
			||||||
		depositCoins         []sdk.Coin
 | 
					 | 
				
			||||||
		borrowCoins          sdk.Coins
 | 
					 | 
				
			||||||
		futureTime           int64
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	type errArgs struct {
 | 
					 | 
				
			||||||
		expectPass bool
 | 
					 | 
				
			||||||
		contains   string
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	type liqTest struct {
 | 
					 | 
				
			||||||
		name    string
 | 
					 | 
				
			||||||
		args    args
 | 
					 | 
				
			||||||
		errArgs errArgs
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Set up test constants
 | 
					 | 
				
			||||||
	model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0"), sdk.MustNewDecFromStr("0.1"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("0.5"))
 | 
					 | 
				
			||||||
	reserveFactor := sdk.MustNewDecFromStr("0.05")
 | 
					 | 
				
			||||||
	oneMonthInSeconds := int64(2592000)
 | 
					 | 
				
			||||||
	borrower := sdk.AccAddress(crypto.AddressHash([]byte("testborrower")))
 | 
					 | 
				
			||||||
	keeper := sdk.AccAddress(crypto.AddressHash([]byte("testkeeper")))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	testCases := []liqTest{
 | 
					 | 
				
			||||||
		{
 | 
					 | 
				
			||||||
			"invalid: withdraw is outside loan-to-value range",
 | 
					 | 
				
			||||||
			args{
 | 
					 | 
				
			||||||
				borrower:             borrower,
 | 
					 | 
				
			||||||
				keeper:               keeper,
 | 
					 | 
				
			||||||
				initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
					 | 
				
			||||||
				initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
					 | 
				
			||||||
				initialKeeperCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
					 | 
				
			||||||
				depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))),
 | 
					 | 
				
			||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))),
 | 
					 | 
				
			||||||
				futureTime:           oneMonthInSeconds,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			errArgs{
 | 
					 | 
				
			||||||
				expectPass: false,
 | 
					 | 
				
			||||||
				contains:   "proposed withdraw outside loan-to-value range",
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, tc := range testCases {
 | 
					 | 
				
			||||||
		suite.Run(tc.name, func() {
 | 
					 | 
				
			||||||
			// Initialize test app and set context
 | 
					 | 
				
			||||||
			tApp := app.NewTestApp()
 | 
					 | 
				
			||||||
			ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Auth module genesis state
 | 
					 | 
				
			||||||
			authGS := app.NewAuthGenState(
 | 
					 | 
				
			||||||
				[]sdk.AccAddress{tc.args.borrower, tc.args.keeper},
 | 
					 | 
				
			||||||
				[]sdk.Coins{tc.args.initialBorrowerCoins, tc.args.initialKeeperCoins},
 | 
					 | 
				
			||||||
			)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Harvest module genesis state
 | 
					 | 
				
			||||||
			harvestGS := types.NewGenesisState(types.NewParams(
 | 
					 | 
				
			||||||
				true,
 | 
					 | 
				
			||||||
				types.DistributionSchedules{
 | 
					 | 
				
			||||||
					types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
 | 
					 | 
				
			||||||
					types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
					 | 
				
			||||||
					time.Hour*24,
 | 
					 | 
				
			||||||
				),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				types.MoneyMarkets{
 | 
					 | 
				
			||||||
					types.NewMoneyMarket("ukava",
 | 
					 | 
				
			||||||
						types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
 | 
					 | 
				
			||||||
						"kava:usd",                     // Market ID
 | 
					 | 
				
			||||||
						sdk.NewInt(KAVA_CF),            // Conversion Factor
 | 
					 | 
				
			||||||
						sdk.NewInt(100000000*KAVA_CF),  // Auction Size
 | 
					 | 
				
			||||||
						model,                          // Interest Rate Model
 | 
					 | 
				
			||||||
						reserveFactor,                  // Reserve Factor
 | 
					 | 
				
			||||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				0, // LTV counter
 | 
					 | 
				
			||||||
			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Pricefeed module genesis state
 | 
					 | 
				
			||||||
			pricefeedGS := pricefeed.GenesisState{
 | 
					 | 
				
			||||||
				Params: pricefeed.Params{
 | 
					 | 
				
			||||||
					Markets: []pricefeed.Market{
 | 
					 | 
				
			||||||
						{MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
					 | 
				
			||||||
						{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
				PostedPrices: []pricefeed.PostedPrice{
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						MarketID:      "usdx:usd",
 | 
					 | 
				
			||||||
						OracleAddress: sdk.AccAddress{},
 | 
					 | 
				
			||||||
						Price:         sdk.MustNewDecFromStr("1.00"),
 | 
					 | 
				
			||||||
						Expiry:        time.Now().Add(100 * time.Hour),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
					{
 | 
					 | 
				
			||||||
						MarketID:      "kava:usd",
 | 
					 | 
				
			||||||
						OracleAddress: sdk.AccAddress{},
 | 
					 | 
				
			||||||
						Price:         sdk.MustNewDecFromStr("2.00"),
 | 
					 | 
				
			||||||
						Expiry:        time.Now().Add(100 * time.Hour),
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Initialize test application
 | 
					 | 
				
			||||||
			tApp.InitializeFromGenesisStates(authGS,
 | 
					 | 
				
			||||||
				app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)},
 | 
					 | 
				
			||||||
				app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Mint coins to Harvest module account
 | 
					 | 
				
			||||||
			supplyKeeper := tApp.GetSupplyKeeper()
 | 
					 | 
				
			||||||
			supplyKeeper.MintCoins(ctx, types.ModuleAccountName, tc.args.initialModuleCoins)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			auctionKeeper := tApp.GetAuctionKeeper()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			keeper := tApp.GetHardKeeper()
 | 
					 | 
				
			||||||
			suite.app = tApp
 | 
					 | 
				
			||||||
			suite.ctx = ctx
 | 
					 | 
				
			||||||
			suite.keeper = keeper
 | 
					 | 
				
			||||||
			suite.auctionKeeper = auctionKeeper
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var err error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Run begin blocker to set up state
 | 
					 | 
				
			||||||
			hard.BeginBlocker(suite.ctx, suite.keeper)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Deposit coins
 | 
					 | 
				
			||||||
			err = suite.keeper.Deposit(suite.ctx, tc.args.borrower, tc.args.depositCoins)
 | 
					 | 
				
			||||||
			suite.Require().NoError(err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Borrow coins
 | 
					 | 
				
			||||||
			err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
 | 
					 | 
				
			||||||
			suite.Require().NoError(err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Attempting to withdraw fails
 | 
					 | 
				
			||||||
			err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, sdk.NewCoins(sdk.NewCoin("ukava", sdk.OneInt())))
 | 
					 | 
				
			||||||
			suite.Require().Error(err)
 | 
					 | 
				
			||||||
			suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Set up future chain context and run begin blocker, increasing user's owed borrow balance
 | 
					 | 
				
			||||||
			runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.futureTime), 0)
 | 
					 | 
				
			||||||
			liqCtx := suite.ctx.WithBlockTime(runAtTime)
 | 
					 | 
				
			||||||
			hard.BeginBlocker(liqCtx, suite.keeper)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Attempted withdraw of 1 coin still fails
 | 
					 | 
				
			||||||
			err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, sdk.NewCoins(sdk.NewCoin("ukava", sdk.OneInt())))
 | 
					 | 
				
			||||||
			suite.Require().Error(err)
 | 
					 | 
				
			||||||
			suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Repay the initial principal
 | 
					 | 
				
			||||||
			err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
 | 
					 | 
				
			||||||
			suite.Require().NoError(err)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Attempted withdraw of all deposited coins fails as user hasn't repaid interest debt
 | 
					 | 
				
			||||||
			err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, tc.args.depositCoins)
 | 
					 | 
				
			||||||
			suite.Require().Error(err)
 | 
					 | 
				
			||||||
			suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Withdrawing half the coins should succeed
 | 
					 | 
				
			||||||
			withdrawCoins := sdk.NewCoins(sdk.NewCoin("ukava", tc.args.depositCoins[0].Amount.Quo(sdk.NewInt(2))))
 | 
					 | 
				
			||||||
			err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, withdrawCoins)
 | 
					 | 
				
			||||||
			suite.Require().NoError(err)
 | 
					 | 
				
			||||||
		})
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -69,14 +69,13 @@ func (k Keeper) AccrueInterest(ctx sdk.Context, denom string) error {
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get available hard module account cash on hand
 | 
						// Get current protocol state and hold in memory as 'prior'
 | 
				
			||||||
	cashPrior := k.supplyKeeper.GetModuleAccount(ctx, types.ModuleName).GetCoins().AmountOf(denom)
 | 
						cashPrior := k.supplyKeeper.GetModuleAccount(ctx, types.ModuleName).GetCoins().AmountOf(denom)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get prior borrows
 | 
						borrowedPrior := sdk.NewCoin(denom, sdk.ZeroInt())
 | 
				
			||||||
	borrowsPrior := sdk.NewCoin(denom, sdk.ZeroInt())
 | 
						borrowedCoinsPrior, foundBorrowedCoinsPrior := k.GetBorrowedCoins(ctx)
 | 
				
			||||||
	borrowCoinsPrior, foundBorrowCoinsPrior := k.GetBorrowedCoins(ctx)
 | 
						if foundBorrowedCoinsPrior {
 | 
				
			||||||
	if foundBorrowCoinsPrior {
 | 
							borrowedPrior = sdk.NewCoin(denom, borrowedCoinsPrior.AmountOf(denom))
 | 
				
			||||||
		borrowsPrior = sdk.NewCoin(denom, borrowCoinsPrior.AmountOf(denom))
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	reservesPrior, foundReservesPrior := k.GetTotalReserves(ctx, denom)
 | 
						reservesPrior, foundReservesPrior := k.GetTotalReserves(ctx, denom)
 | 
				
			||||||
@ -86,11 +85,18 @@ func (k Keeper) AccrueInterest(ctx sdk.Context, denom string) error {
 | 
				
			|||||||
		reservesPrior = newReservesPrior
 | 
							reservesPrior = newReservesPrior
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	interestFactorPrior, foundInterestFactorPrior := k.GetInterestFactor(ctx, denom)
 | 
						borrowInterestFactorPrior, foundBorrowInterestFactorPrior := k.GetBorrowInterestFactor(ctx, denom)
 | 
				
			||||||
	if !foundInterestFactorPrior {
 | 
						if !foundBorrowInterestFactorPrior {
 | 
				
			||||||
		newInterestFactorPrior := sdk.MustNewDecFromStr("1.0")
 | 
							newBorrowInterestFactorPrior := sdk.MustNewDecFromStr("1.0")
 | 
				
			||||||
		k.SetInterestFactor(ctx, denom, newInterestFactorPrior)
 | 
							k.SetBorrowInterestFactor(ctx, denom, newBorrowInterestFactorPrior)
 | 
				
			||||||
		interestFactorPrior = newInterestFactorPrior
 | 
							borrowInterestFactorPrior = newBorrowInterestFactorPrior
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						supplyInterestFactorPrior, foundSupplyInterestFactorPrior := k.GetSupplyInterestFactor(ctx, denom)
 | 
				
			||||||
 | 
						if !foundSupplyInterestFactorPrior {
 | 
				
			||||||
 | 
							newSupplyInterestFactorPrior := sdk.MustNewDecFromStr("1.0")
 | 
				
			||||||
 | 
							k.SetSupplyInterestFactor(ctx, denom, newSupplyInterestFactorPrior)
 | 
				
			||||||
 | 
							supplyInterestFactorPrior = newSupplyInterestFactorPrior
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Fetch money market from the store
 | 
						// Fetch money market from the store
 | 
				
			||||||
@ -100,7 +106,7 @@ func (k Keeper) AccrueInterest(ctx sdk.Context, denom string) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// GetBorrowRate calculates the current interest rate based on utilization (the fraction of supply that has been borrowed)
 | 
						// GetBorrowRate calculates the current interest rate based on utilization (the fraction of supply that has been borrowed)
 | 
				
			||||||
	borrowRateApy, err := CalculateBorrowRate(mm.InterestRateModel, sdk.NewDecFromInt(cashPrior), sdk.NewDecFromInt(borrowsPrior.Amount), sdk.NewDecFromInt(reservesPrior.Amount))
 | 
						borrowRateApy, err := CalculateBorrowRate(mm.InterestRateModel, sdk.NewDecFromInt(cashPrior), sdk.NewDecFromInt(borrowedPrior.Amount), sdk.NewDecFromInt(reservesPrior.Amount))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -111,16 +117,27 @@ func (k Keeper) AccrueInterest(ctx sdk.Context, denom string) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	interestFactor := CalculateInterestFactor(borrowRateSpy, sdk.NewInt(timeElapsed))
 | 
						// Calculate borrow interest factor and update
 | 
				
			||||||
	interestAccumulated := (interestFactor.Mul(sdk.NewDecFromInt(borrowsPrior.Amount)).TruncateInt()).Sub(borrowsPrior.Amount)
 | 
						borrowInterestFactor := CalculateBorrowInterestFactor(borrowRateSpy, sdk.NewInt(timeElapsed))
 | 
				
			||||||
	totalBorrowInterestAccumulated := sdk.NewCoins(sdk.NewCoin(denom, interestAccumulated))
 | 
						interestBorrowAccumulated := (borrowInterestFactor.Mul(sdk.NewDecFromInt(borrowedPrior.Amount)).TruncateInt()).Sub(borrowedPrior.Amount)
 | 
				
			||||||
	totalReservesNew := reservesPrior.Add(sdk.NewCoin(denom, sdk.NewDecFromInt(interestAccumulated).Mul(mm.ReserveFactor).TruncateInt()))
 | 
						totalBorrowInterestAccumulated := sdk.NewCoins(sdk.NewCoin(denom, interestBorrowAccumulated))
 | 
				
			||||||
	interestFactorNew := interestFactorPrior.Mul(interestFactor)
 | 
						reservesNew := interestBorrowAccumulated.ToDec().Mul(mm.ReserveFactor).TruncateInt()
 | 
				
			||||||
 | 
						borrowInterestFactorNew := borrowInterestFactorPrior.Mul(borrowInterestFactor)
 | 
				
			||||||
 | 
						k.SetBorrowInterestFactor(ctx, denom, borrowInterestFactorNew)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k.SetInterestFactor(ctx, denom, interestFactorNew)
 | 
						// Calculate supply interest factor and update
 | 
				
			||||||
 | 
						supplyInterestNew := interestBorrowAccumulated.Sub(reservesNew)
 | 
				
			||||||
 | 
						supplyInterestFactor := CalculateSupplyInterestFactor(supplyInterestNew.ToDec(), cashPrior.ToDec(), borrowedPrior.Amount.ToDec(), reservesPrior.Amount.ToDec())
 | 
				
			||||||
 | 
						supplyInterestFactorNew := supplyInterestFactorPrior.Mul(supplyInterestFactor)
 | 
				
			||||||
 | 
						k.SetSupplyInterestFactor(ctx, denom, supplyInterestFactorNew)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update accural keys in store
 | 
				
			||||||
	k.IncrementBorrowedCoins(ctx, totalBorrowInterestAccumulated)
 | 
						k.IncrementBorrowedCoins(ctx, totalBorrowInterestAccumulated)
 | 
				
			||||||
	k.SetTotalReserves(ctx, denom, totalReservesNew)
 | 
						k.IncrementSuppliedCoins(ctx, sdk.NewCoins(sdk.NewCoin(denom, supplyInterestNew)))
 | 
				
			||||||
 | 
						k.SetTotalReserves(ctx, denom, reservesPrior.Add(sdk.NewCoin(mm.Denom, reservesNew)))
 | 
				
			||||||
 | 
						k.SetSupplyInterestFactor(ctx, denom, supplyInterestFactorNew)
 | 
				
			||||||
	k.SetPreviousAccrualTime(ctx, denom, ctx.BlockTime())
 | 
						k.SetPreviousAccrualTime(ctx, denom, ctx.BlockTime())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -155,10 +172,10 @@ func CalculateUtilizationRatio(cash, borrows, reserves sdk.Dec) sdk.Dec {
 | 
				
			|||||||
	return sdk.MinDec(sdk.OneDec(), borrows.Quo(totalSupply))
 | 
						return sdk.MinDec(sdk.OneDec(), borrows.Quo(totalSupply))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CalculateInterestFactor calculates the simple interest scaling factor,
 | 
					// CalculateBorrowInterestFactor calculates the simple interest scaling factor,
 | 
				
			||||||
// which is equal to: (per-second interest rate * number of seconds elapsed)
 | 
					// which is equal to: (per-second interest rate * number of seconds elapsed)
 | 
				
			||||||
// Will return 1.000x, multiply by principal to get new principal with added interest
 | 
					// Will return 1.000x, multiply by principal to get new principal with added interest
 | 
				
			||||||
func CalculateInterestFactor(perSecondInterestRate sdk.Dec, secondsElapsed sdk.Int) sdk.Dec {
 | 
					func CalculateBorrowInterestFactor(perSecondInterestRate sdk.Dec, secondsElapsed sdk.Int) sdk.Dec {
 | 
				
			||||||
	scalingFactorUint := sdk.NewUint(uint64(scalingFactor))
 | 
						scalingFactorUint := sdk.NewUint(uint64(scalingFactor))
 | 
				
			||||||
	scalingFactorInt := sdk.NewInt(int64(scalingFactor))
 | 
						scalingFactorInt := sdk.NewInt(int64(scalingFactor))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -173,6 +190,100 @@ func CalculateInterestFactor(perSecondInterestRate sdk.Dec, secondsElapsed sdk.I
 | 
				
			|||||||
	return sdk.NewDecFromBigInt(interestFactorMantissa.BigInt()).QuoInt(scalingFactorInt)
 | 
						return sdk.NewDecFromBigInt(interestFactorMantissa.BigInt()).QuoInt(scalingFactorInt)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CalculateSupplyInterestFactor calculates the supply interest factor, which is the percentage of borrow interest
 | 
				
			||||||
 | 
					// that flows to each unit of supply, i.e. at 50% utilization and 0% reserve factor, a 5% borrow interest will
 | 
				
			||||||
 | 
					// correspond to a 2.5% supply interest.
 | 
				
			||||||
 | 
					func CalculateSupplyInterestFactor(newInterest, cash, borrows, reserves sdk.Dec) sdk.Dec {
 | 
				
			||||||
 | 
						totalSupply := cash.Add(borrows).Sub(reserves)
 | 
				
			||||||
 | 
						if totalSupply.IsZero() {
 | 
				
			||||||
 | 
							return sdk.OneDec()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return (newInterest.Quo(totalSupply)).Add(sdk.OneDec())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SyncBorrowInterest updates the user's owed interest on newly borrowed coins to the latest global state
 | 
				
			||||||
 | 
					func (k Keeper) SyncBorrowInterest(ctx sdk.Context, addr sdk.AccAddress) {
 | 
				
			||||||
 | 
						totalNewInterest := sdk.Coins{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update user's borrow interest factor list for each asset in the 'coins' array.
 | 
				
			||||||
 | 
						// We use a list of BorrowInterestFactors here because Amino doesn't support marshaling maps.
 | 
				
			||||||
 | 
						borrow, found := k.GetBorrow(ctx, addr)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, coin := range borrow.Amount {
 | 
				
			||||||
 | 
							// Locate the borrow interest factor item by coin denom in the user's list of borrow indexes
 | 
				
			||||||
 | 
							foundAtIndex := -1
 | 
				
			||||||
 | 
							for i := range borrow.Index {
 | 
				
			||||||
 | 
								if borrow.Index[i].Denom == coin.Denom {
 | 
				
			||||||
 | 
									foundAtIndex = i
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							interestFactorValue, _ := k.GetBorrowInterestFactor(ctx, coin.Denom)
 | 
				
			||||||
 | 
							if foundAtIndex == -1 { // First time user has borrowed this denom
 | 
				
			||||||
 | 
								borrow.Index = append(borrow.Index, types.NewBorrowInterestFactor(coin.Denom, interestFactorValue))
 | 
				
			||||||
 | 
							} else { // User has an existing borrow index for this denom
 | 
				
			||||||
 | 
								// Calculate interest owed by user since asset's last borrow index update
 | 
				
			||||||
 | 
								storedAmount := sdk.NewDecFromInt(borrow.Amount.AmountOf(coin.Denom))
 | 
				
			||||||
 | 
								userLastInterestFactor := borrow.Index[foundAtIndex].Value
 | 
				
			||||||
 | 
								interest := (storedAmount.Quo(userLastInterestFactor).Mul(interestFactorValue)).Sub(storedAmount)
 | 
				
			||||||
 | 
								totalNewInterest = totalNewInterest.Add(sdk.NewCoin(coin.Denom, interest.TruncateInt()))
 | 
				
			||||||
 | 
								// We're synced up, so update user's borrow index value to match the current global borrow index value
 | 
				
			||||||
 | 
								borrow.Index[foundAtIndex].Value = interestFactorValue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Add all pending interest to user's borrow
 | 
				
			||||||
 | 
						borrow.Amount = borrow.Amount.Add(totalNewInterest...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update user's borrow in the store
 | 
				
			||||||
 | 
						k.SetBorrow(ctx, borrow)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SyncSupplyInterest updates the user's earned interest on supplied coins based on the latest global state
 | 
				
			||||||
 | 
					func (k Keeper) SyncSupplyInterest(ctx sdk.Context, addr sdk.AccAddress) {
 | 
				
			||||||
 | 
						totalNewInterest := sdk.Coins{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update user's supply index list for each asset in the 'coins' array.
 | 
				
			||||||
 | 
						// We use a list of SupplyInterestFactors here because Amino doesn't support marshaling maps.
 | 
				
			||||||
 | 
						deposit, found := k.GetDeposit(ctx, addr)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, coin := range deposit.Amount {
 | 
				
			||||||
 | 
							// Locate the deposit index item by coin denom in the user's list of deposit indexes
 | 
				
			||||||
 | 
							foundAtIndex := -1
 | 
				
			||||||
 | 
							for i := range deposit.Index {
 | 
				
			||||||
 | 
								if deposit.Index[i].Denom == coin.Denom {
 | 
				
			||||||
 | 
									foundAtIndex = i
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							interestFactorValue, _ := k.GetSupplyInterestFactor(ctx, coin.Denom)
 | 
				
			||||||
 | 
							if foundAtIndex == -1 { // First time user has supplied this denom
 | 
				
			||||||
 | 
								deposit.Index = append(deposit.Index, types.NewSupplyInterestFactor(coin.Denom, interestFactorValue))
 | 
				
			||||||
 | 
							} else { // User has an existing supply index for this denom
 | 
				
			||||||
 | 
								// Calculate interest earned by user since asset's last deposit index update
 | 
				
			||||||
 | 
								storedAmount := sdk.NewDecFromInt(deposit.Amount.AmountOf(coin.Denom))
 | 
				
			||||||
 | 
								userLastInterestFactor := deposit.Index[foundAtIndex].Value
 | 
				
			||||||
 | 
								interest := (storedAmount.Mul(interestFactorValue).Quo(userLastInterestFactor)).Sub(storedAmount)
 | 
				
			||||||
 | 
								if interest.TruncateInt().GT(sdk.ZeroInt()) {
 | 
				
			||||||
 | 
									totalNewInterest = totalNewInterest.Add(sdk.NewCoin(coin.Denom, interest.TruncateInt()))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// We're synced up, so update user's deposit index value to match the current global deposit index value
 | 
				
			||||||
 | 
								deposit.Index[foundAtIndex].Value = interestFactorValue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Add all pending interest to user's deposit
 | 
				
			||||||
 | 
						deposit.Amount = deposit.Amount.Add(totalNewInterest...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update user's deposit in the store
 | 
				
			||||||
 | 
						k.SetDeposit(ctx, deposit)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// APYToSPY converts the input annual interest rate. For example, 10% apy would be passed as 1.10.
 | 
					// APYToSPY converts the input annual interest rate. For example, 10% apy would be passed as 1.10.
 | 
				
			||||||
// SPY = Per second compounded interest rate is how cosmos mathematically represents APY.
 | 
					// SPY = Per second compounded interest rate is how cosmos mathematically represents APY.
 | 
				
			||||||
func APYToSPY(apy sdk.Dec) (sdk.Dec, error) {
 | 
					func APYToSPY(apy sdk.Dec) (sdk.Dec, error) {
 | 
				
			||||||
 | 
				
			|||||||
@ -202,7 +202,7 @@ func (suite *InterestTestSuite) TestCalculateBorrowRate() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *InterestTestSuite) TestCalculateInterestFactor() {
 | 
					func (suite *InterestTestSuite) TestCalculateBorrowInterestFactor() {
 | 
				
			||||||
	type args struct {
 | 
						type args struct {
 | 
				
			||||||
		perSecondInterestRate sdk.Dec
 | 
							perSecondInterestRate sdk.Dec
 | 
				
			||||||
		timeElapsed           sdk.Int
 | 
							timeElapsed           sdk.Int
 | 
				
			||||||
@ -302,7 +302,65 @@ func (suite *InterestTestSuite) TestCalculateInterestFactor() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tc := range testCases {
 | 
						for _, tc := range testCases {
 | 
				
			||||||
		interestFactor := hard.CalculateInterestFactor(tc.args.perSecondInterestRate, tc.args.timeElapsed)
 | 
							interestFactor := hard.CalculateBorrowInterestFactor(tc.args.perSecondInterestRate, tc.args.timeElapsed)
 | 
				
			||||||
 | 
							suite.Require().Equal(tc.args.expectedValue, interestFactor)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *InterestTestSuite) TestCalculateSupplyInterestFactor() {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							newInterest   sdk.Dec
 | 
				
			||||||
 | 
							cash          sdk.Dec
 | 
				
			||||||
 | 
							borrows       sdk.Dec
 | 
				
			||||||
 | 
							reserves      sdk.Dec
 | 
				
			||||||
 | 
							reserveFactor sdk.Dec
 | 
				
			||||||
 | 
							expectedValue sdk.Dec
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type test struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
							args args
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testCases := []test{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"low new interest",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									newInterest:   sdk.MustNewDecFromStr("1"),
 | 
				
			||||||
 | 
									cash:          sdk.MustNewDecFromStr("100.0"),
 | 
				
			||||||
 | 
									borrows:       sdk.MustNewDecFromStr("1000.0"),
 | 
				
			||||||
 | 
									reserves:      sdk.MustNewDecFromStr("10.0"),
 | 
				
			||||||
 | 
									reserveFactor: sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedValue: sdk.MustNewDecFromStr("1.000917431192660550"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"medium new interest",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									newInterest:   sdk.MustNewDecFromStr("5"),
 | 
				
			||||||
 | 
									cash:          sdk.MustNewDecFromStr("100.0"),
 | 
				
			||||||
 | 
									borrows:       sdk.MustNewDecFromStr("1000.0"),
 | 
				
			||||||
 | 
									reserves:      sdk.MustNewDecFromStr("10.0"),
 | 
				
			||||||
 | 
									reserveFactor: sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedValue: sdk.MustNewDecFromStr("1.004587155963302752"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"high new interest",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									newInterest:   sdk.MustNewDecFromStr("10"),
 | 
				
			||||||
 | 
									cash:          sdk.MustNewDecFromStr("100.0"),
 | 
				
			||||||
 | 
									borrows:       sdk.MustNewDecFromStr("1000.0"),
 | 
				
			||||||
 | 
									reserves:      sdk.MustNewDecFromStr("10.0"),
 | 
				
			||||||
 | 
									reserveFactor: sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedValue: sdk.MustNewDecFromStr("1.009174311926605505"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							interestFactor := hard.CalculateSupplyInterestFactor(tc.args.newInterest,
 | 
				
			||||||
 | 
								tc.args.cash, tc.args.borrows, tc.args.reserves)
 | 
				
			||||||
		suite.Require().Equal(tc.args.expectedValue, interestFactor)
 | 
							suite.Require().Equal(tc.args.expectedValue, interestFactor)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -377,7 +435,6 @@ func (suite *InterestTestSuite) TestAPYToSPY() {
 | 
				
			|||||||
			true,
 | 
								true,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	for _, tc := range testCases {
 | 
						for _, tc := range testCases {
 | 
				
			||||||
		suite.Run(tc.name, func() {
 | 
							suite.Run(tc.name, func() {
 | 
				
			||||||
			spy, err := hard.APYToSPY(tc.args.apy)
 | 
								spy, err := hard.APYToSPY(tc.args.apy)
 | 
				
			||||||
@ -391,13 +448,13 @@ func (suite *InterestTestSuite) TestAPYToSPY() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ExpectedInterest struct {
 | 
					type ExpectedBorrowInterest struct {
 | 
				
			||||||
	elapsedTime  int64
 | 
						elapsedTime  int64
 | 
				
			||||||
	shouldBorrow bool
 | 
						shouldBorrow bool
 | 
				
			||||||
	borrowCoin   sdk.Coin
 | 
						borrowCoin   sdk.Coin
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *KeeperTestSuite) TestInterest() {
 | 
					func (suite *KeeperTestSuite) TestBorrowInterest() {
 | 
				
			||||||
	type args struct {
 | 
						type args struct {
 | 
				
			||||||
		user                     sdk.AccAddress
 | 
							user                     sdk.AccAddress
 | 
				
			||||||
		initialBorrowerCoins     sdk.Coins
 | 
							initialBorrowerCoins     sdk.Coins
 | 
				
			||||||
@ -406,7 +463,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
		borrowCoins              sdk.Coins
 | 
							borrowCoins              sdk.Coins
 | 
				
			||||||
		interestRateModel        types.InterestRateModel
 | 
							interestRateModel        types.InterestRateModel
 | 
				
			||||||
		reserveFactor            sdk.Dec
 | 
							reserveFactor            sdk.Dec
 | 
				
			||||||
		expectedInterestSnaphots []ExpectedInterest
 | 
							expectedInterestSnaphots []ExpectedBorrowInterest
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	type errArgs struct {
 | 
						type errArgs struct {
 | 
				
			||||||
@ -438,7 +495,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
				interestRateModel:    normalModel,
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
				reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
				expectedInterestSnaphots: []ExpectedInterest{
 | 
									expectedInterestSnaphots: []ExpectedBorrowInterest{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						elapsedTime:  oneDayInSeconds,
 | 
											elapsedTime:  oneDayInSeconds,
 | 
				
			||||||
						shouldBorrow: false,
 | 
											shouldBorrow: false,
 | 
				
			||||||
@ -461,7 +518,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
				interestRateModel:    normalModel,
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
				reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
				expectedInterestSnaphots: []ExpectedInterest{
 | 
									expectedInterestSnaphots: []ExpectedBorrowInterest{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						elapsedTime:  oneWeekInSeconds,
 | 
											elapsedTime:  oneWeekInSeconds,
 | 
				
			||||||
						shouldBorrow: false,
 | 
											shouldBorrow: false,
 | 
				
			||||||
@ -484,7 +541,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
				interestRateModel:    normalModel,
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
				reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
				expectedInterestSnaphots: []ExpectedInterest{
 | 
									expectedInterestSnaphots: []ExpectedBorrowInterest{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						elapsedTime:  oneMonthInSeconds,
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
						shouldBorrow: false,
 | 
											shouldBorrow: false,
 | 
				
			||||||
@ -507,7 +564,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
				interestRateModel:    normalModel,
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
				reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
				expectedInterestSnaphots: []ExpectedInterest{
 | 
									expectedInterestSnaphots: []ExpectedBorrowInterest{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						elapsedTime:  oneYearInSeconds,
 | 
											elapsedTime:  oneYearInSeconds,
 | 
				
			||||||
						shouldBorrow: false,
 | 
											shouldBorrow: false,
 | 
				
			||||||
@ -530,7 +587,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
				interestRateModel:    normalModel,
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
				reserveFactor:        sdk.MustNewDecFromStr("0"),
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0"),
 | 
				
			||||||
				expectedInterestSnaphots: []ExpectedInterest{
 | 
									expectedInterestSnaphots: []ExpectedBorrowInterest{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						elapsedTime:  oneYearInSeconds,
 | 
											elapsedTime:  oneYearInSeconds,
 | 
				
			||||||
						shouldBorrow: false,
 | 
											shouldBorrow: false,
 | 
				
			||||||
@ -553,7 +610,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
				interestRateModel:    normalModel,
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
				reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
				expectedInterestSnaphots: []ExpectedInterest{
 | 
									expectedInterestSnaphots: []ExpectedBorrowInterest{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						elapsedTime:  oneYearInSeconds,
 | 
											elapsedTime:  oneYearInSeconds,
 | 
				
			||||||
						shouldBorrow: true,
 | 
											shouldBorrow: true,
 | 
				
			||||||
@ -576,7 +633,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
				interestRateModel:    normalModel,
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
				reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
				expectedInterestSnaphots: []ExpectedInterest{
 | 
									expectedInterestSnaphots: []ExpectedBorrowInterest{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						elapsedTime:  oneMonthInSeconds,
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
						shouldBorrow: false,
 | 
											shouldBorrow: false,
 | 
				
			||||||
@ -604,7 +661,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
				interestRateModel:    normalModel,
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
				reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
				expectedInterestSnaphots: []ExpectedInterest{
 | 
									expectedInterestSnaphots: []ExpectedBorrowInterest{
 | 
				
			||||||
					{
 | 
										{
 | 
				
			||||||
						elapsedTime:  oneDayInSeconds,
 | 
											elapsedTime:  oneDayInSeconds,
 | 
				
			||||||
						shouldBorrow: false,
 | 
											shouldBorrow: false,
 | 
				
			||||||
@ -633,6 +690,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, tc := range testCases {
 | 
						for _, tc := range testCases {
 | 
				
			||||||
		suite.Run(tc.name, func() {
 | 
							suite.Run(tc.name, func() {
 | 
				
			||||||
			// Initialize test app and set context
 | 
								// Initialize test app and set context
 | 
				
			||||||
@ -737,7 +795,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
					reservesPrior = sdk.NewCoin(tc.args.borrowCoinDenom, sdk.ZeroInt())
 | 
										reservesPrior = sdk.NewCoin(tc.args.borrowCoinDenom, sdk.ZeroInt())
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				interestFactorPrior, foundInterestFactorPrior := suite.keeper.GetInterestFactor(prevCtx, tc.args.borrowCoinDenom)
 | 
									interestFactorPrior, foundInterestFactorPrior := suite.keeper.GetBorrowInterestFactor(prevCtx, tc.args.borrowCoinDenom)
 | 
				
			||||||
				suite.Require().True(foundInterestFactorPrior)
 | 
									suite.Require().True(foundInterestFactorPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// 2. Calculate expected interest owed
 | 
									// 2. Calculate expected interest owed
 | 
				
			||||||
@ -748,7 +806,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				borrowRateSpy, err := hard.APYToSPY(sdk.OneDec().Add(borrowRateApy))
 | 
									borrowRateSpy, err := hard.APYToSPY(sdk.OneDec().Add(borrowRateApy))
 | 
				
			||||||
				suite.Require().NoError(err)
 | 
									suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				interestFactor := hard.CalculateInterestFactor(borrowRateSpy, sdk.NewInt(snapshot.elapsedTime))
 | 
									interestFactor := hard.CalculateBorrowInterestFactor(borrowRateSpy, sdk.NewInt(snapshot.elapsedTime))
 | 
				
			||||||
				expectedInterest := (interestFactor.Mul(sdk.NewDecFromInt(borrowCoinPriorAmount)).TruncateInt()).Sub(borrowCoinPriorAmount)
 | 
									expectedInterest := (interestFactor.Mul(sdk.NewDecFromInt(borrowCoinPriorAmount)).TruncateInt()).Sub(borrowCoinPriorAmount)
 | 
				
			||||||
				expectedReserves := reservesPrior.Add(sdk.NewCoin(tc.args.borrowCoinDenom, sdk.NewDecFromInt(expectedInterest).Mul(tc.args.reserveFactor).TruncateInt()))
 | 
									expectedReserves := reservesPrior.Add(sdk.NewCoin(tc.args.borrowCoinDenom, sdk.NewDecFromInt(expectedInterest).Mul(tc.args.reserveFactor).TruncateInt()))
 | 
				
			||||||
				expectedInterestFactor := interestFactorPrior.Mul(interestFactor)
 | 
									expectedInterestFactor := interestFactorPrior.Mul(interestFactor)
 | 
				
			||||||
@ -769,7 +827,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
				suite.Require().Equal(expectedReserves, currTotalReserves)
 | 
									suite.Require().Equal(expectedReserves, currTotalReserves)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Check that the borrow index has increased as expected
 | 
									// Check that the borrow index has increased as expected
 | 
				
			||||||
				currIndexPrior, _ := suite.keeper.GetInterestFactor(snapshotCtx, tc.args.borrowCoinDenom)
 | 
									currIndexPrior, _ := suite.keeper.GetBorrowInterestFactor(snapshotCtx, tc.args.borrowCoinDenom)
 | 
				
			||||||
				suite.Require().Equal(expectedInterestFactor, currIndexPrior)
 | 
									suite.Require().Equal(expectedInterestFactor, currIndexPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// After borrowing again user's borrow balance should have any outstanding interest applied
 | 
									// After borrowing again user's borrow balance should have any outstanding interest applied
 | 
				
			||||||
@ -791,6 +849,495 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ExpectedSupplyInterest struct {
 | 
				
			||||||
 | 
						elapsedTime  int64
 | 
				
			||||||
 | 
						shouldSupply bool
 | 
				
			||||||
 | 
						supplyCoin   sdk.Coin
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *KeeperTestSuite) TestSupplyInterest() {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							user                     sdk.AccAddress
 | 
				
			||||||
 | 
							initialSupplierCoins     sdk.Coins
 | 
				
			||||||
 | 
							initialBorrowerCoins     sdk.Coins
 | 
				
			||||||
 | 
							initialModuleCoins       sdk.Coins
 | 
				
			||||||
 | 
							depositCoins             sdk.Coins
 | 
				
			||||||
 | 
							coinDenoms               []string
 | 
				
			||||||
 | 
							borrowCoins              sdk.Coins
 | 
				
			||||||
 | 
							interestRateModel        types.InterestRateModel
 | 
				
			||||||
 | 
							reserveFactor            sdk.Dec
 | 
				
			||||||
 | 
							expectedInterestSnaphots []ExpectedSupplyInterest
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type errArgs struct {
 | 
				
			||||||
 | 
							expectPass bool
 | 
				
			||||||
 | 
							contains   string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type interestTest struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							errArgs errArgs
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						normalModel := types.NewInterestRateModel(sdk.MustNewDecFromStr("0"), sdk.MustNewDecFromStr("0.1"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("0.5"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						oneDayInSeconds := int64(86400)
 | 
				
			||||||
 | 
						oneWeekInSeconds := int64(604800)
 | 
				
			||||||
 | 
						oneMonthInSeconds := int64(2592000)
 | 
				
			||||||
 | 
						oneYearInSeconds := int64(31536000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testCases := []interestTest{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"one day",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									user:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									coinDenoms:           []string{"ukava"},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedInterestSnaphots: []ExpectedSupplyInterest{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneDayInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"one week",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									user:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									coinDenoms:           []string{"ukava"},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedInterestSnaphots: []ExpectedSupplyInterest{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneWeekInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"one month",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									user:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									coinDenoms:           []string{"ukava"},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedInterestSnaphots: []ExpectedSupplyInterest{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"one year",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									user:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									coinDenoms:           []string{"ukava"},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedInterestSnaphots: []ExpectedSupplyInterest{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneYearInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"supply/borrow multiple coins",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									user:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(100*BNB_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(100*BNB_CF))),
 | 
				
			||||||
 | 
									coinDenoms:           []string{"ukava"},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(20*BNB_CF))),
 | 
				
			||||||
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedInterestSnaphots: []ExpectedSupplyInterest{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"supply during snapshot",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									user:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									coinDenoms:           []string{"ukava"},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF))),
 | 
				
			||||||
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedInterestSnaphots: []ExpectedSupplyInterest{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: true,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.NewCoin("ukava", sdk.NewInt(20*KAVA_CF)),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"multiple snapshots",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									user:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									coinDenoms:           []string{"ukava"},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(80*KAVA_CF))),
 | 
				
			||||||
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedInterestSnaphots: []ExpectedSupplyInterest{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"varied snapshots",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									user:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									coinDenoms:           []string{"ukava"},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(50*KAVA_CF))),
 | 
				
			||||||
 | 
									interestRateModel:    normalModel,
 | 
				
			||||||
 | 
									reserveFactor:        sdk.MustNewDecFromStr("0.05"),
 | 
				
			||||||
 | 
									expectedInterestSnaphots: []ExpectedSupplyInterest{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneMonthInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneDayInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneYearInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											elapsedTime:  oneWeekInSeconds,
 | 
				
			||||||
 | 
											shouldSupply: false,
 | 
				
			||||||
 | 
											supplyCoin:   sdk.Coin{},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							suite.Run(tc.name, func() {
 | 
				
			||||||
 | 
								// Initialize test app and set context
 | 
				
			||||||
 | 
								tApp := app.NewTestApp()
 | 
				
			||||||
 | 
								ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Auth module genesis state
 | 
				
			||||||
 | 
								authGS := app.NewAuthGenState(
 | 
				
			||||||
 | 
									[]sdk.AccAddress{tc.args.user},
 | 
				
			||||||
 | 
									[]sdk.Coins{tc.args.initialBorrowerCoins},
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Hard module genesis state
 | 
				
			||||||
 | 
								hardGS := types.NewGenesisState(types.NewParams(
 | 
				
			||||||
 | 
									true,
 | 
				
			||||||
 | 
									types.DistributionSchedules{
 | 
				
			||||||
 | 
										types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
				
			||||||
 | 
										types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
 | 
				
			||||||
 | 
										types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
				
			||||||
 | 
										time.Hour*24,
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									types.MoneyMarkets{
 | 
				
			||||||
 | 
										types.NewMoneyMarket("ukava",
 | 
				
			||||||
 | 
											types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
 | 
				
			||||||
 | 
											"kava:usd",                // Market ID
 | 
				
			||||||
 | 
											sdk.NewInt(KAVA_CF),       // Conversion Factor
 | 
				
			||||||
 | 
											sdk.NewInt(USDX_CF*1000),  // Auction Size
 | 
				
			||||||
 | 
											tc.args.interestRateModel, // Interest Rate Model
 | 
				
			||||||
 | 
											tc.args.reserveFactor,     // Reserve Factor
 | 
				
			||||||
 | 
											sdk.ZeroDec()),            // Keeper Reward Percentage
 | 
				
			||||||
 | 
										types.NewMoneyMarket("bnb",
 | 
				
			||||||
 | 
											types.NewBorrowLimit(false, sdk.NewDec(100000000*BNB_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
 | 
				
			||||||
 | 
											"bnb:usd",                 // Market ID
 | 
				
			||||||
 | 
											sdk.NewInt(BNB_CF),        // Conversion Factor
 | 
				
			||||||
 | 
											sdk.NewInt(USDX_CF*1000),  // Auction Size
 | 
				
			||||||
 | 
											tc.args.interestRateModel, // Interest Rate Model
 | 
				
			||||||
 | 
											tc.args.reserveFactor,     // Reserve Factor
 | 
				
			||||||
 | 
											sdk.ZeroDec()),            // Keeper Reward Percentage
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									0, // LTV counter
 | 
				
			||||||
 | 
								), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Pricefeed module genesis state
 | 
				
			||||||
 | 
								pricefeedGS := pricefeed.GenesisState{
 | 
				
			||||||
 | 
									Params: pricefeed.Params{
 | 
				
			||||||
 | 
										Markets: []pricefeed.Market{
 | 
				
			||||||
 | 
											{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
				
			||||||
 | 
											{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									PostedPrices: []pricefeed.PostedPrice{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MarketID:      "kava:usd",
 | 
				
			||||||
 | 
											OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
 | 
											Price:         sdk.MustNewDecFromStr("2.00"),
 | 
				
			||||||
 | 
											Expiry:        time.Now().Add(100 * time.Hour),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MarketID:      "bnb:usd",
 | 
				
			||||||
 | 
											OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
 | 
											Price:         sdk.MustNewDecFromStr("20.00"),
 | 
				
			||||||
 | 
											Expiry:        time.Now().Add(100 * time.Hour),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Initialize test application
 | 
				
			||||||
 | 
								tApp.InitializeFromGenesisStates(authGS,
 | 
				
			||||||
 | 
									app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)},
 | 
				
			||||||
 | 
									app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Mint coins to Hard module account
 | 
				
			||||||
 | 
								supplyKeeper := tApp.GetSupplyKeeper()
 | 
				
			||||||
 | 
								supplyKeeper.MintCoins(ctx, types.ModuleAccountName, tc.args.initialModuleCoins)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								keeper := tApp.GetHardKeeper()
 | 
				
			||||||
 | 
								suite.app = tApp
 | 
				
			||||||
 | 
								suite.ctx = ctx
 | 
				
			||||||
 | 
								suite.keeper = keeper
 | 
				
			||||||
 | 
								suite.keeper.SetSuppliedCoins(ctx, tc.args.initialModuleCoins)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Run begin blocker
 | 
				
			||||||
 | 
								hard.BeginBlocker(suite.ctx, suite.keeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// // Deposit coins
 | 
				
			||||||
 | 
								err = suite.keeper.Deposit(suite.ctx, tc.args.user, tc.args.depositCoins)
 | 
				
			||||||
 | 
								suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Borrow coins
 | 
				
			||||||
 | 
								err = suite.keeper.Borrow(suite.ctx, tc.args.user, tc.args.borrowCoins)
 | 
				
			||||||
 | 
								suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Check interest levels for each snapshot
 | 
				
			||||||
 | 
								prevCtx := suite.ctx
 | 
				
			||||||
 | 
								for _, snapshot := range tc.args.expectedInterestSnaphots {
 | 
				
			||||||
 | 
									for _, coinDenom := range tc.args.coinDenoms {
 | 
				
			||||||
 | 
										// ---------------------------- Calculate expected supply interest ----------------------------
 | 
				
			||||||
 | 
										// 1. Get cash, borrows, reserves, and borrow index
 | 
				
			||||||
 | 
										cashPrior := suite.getModuleAccountAtCtx(types.ModuleName, prevCtx).GetCoins().AmountOf(coinDenom)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										var borrowCoinPriorAmount sdk.Int
 | 
				
			||||||
 | 
										borrowCoinsPrior, borrowCoinsPriorFound := suite.keeper.GetBorrowedCoins(prevCtx)
 | 
				
			||||||
 | 
										suite.Require().True(borrowCoinsPriorFound)
 | 
				
			||||||
 | 
										borrowCoinPriorAmount = borrowCoinsPrior.AmountOf(coinDenom)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										var supplyCoinPriorAmount sdk.Int
 | 
				
			||||||
 | 
										supplyCoinsPrior, supplyCoinsPriorFound := suite.keeper.GetSuppliedCoins(prevCtx)
 | 
				
			||||||
 | 
										suite.Require().True(supplyCoinsPriorFound)
 | 
				
			||||||
 | 
										supplyCoinPriorAmount = supplyCoinsPrior.AmountOf(coinDenom)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										reservesPrior, foundReservesPrior := suite.keeper.GetTotalReserves(prevCtx, coinDenom)
 | 
				
			||||||
 | 
										if !foundReservesPrior {
 | 
				
			||||||
 | 
											reservesPrior = sdk.NewCoin(coinDenom, sdk.ZeroInt())
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										borrowInterestFactorPrior, foundBorrowInterestFactorPrior := suite.keeper.GetBorrowInterestFactor(prevCtx, coinDenom)
 | 
				
			||||||
 | 
										suite.Require().True(foundBorrowInterestFactorPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										supplyInterestFactorPrior, foundSupplyInterestFactorPrior := suite.keeper.GetSupplyInterestFactor(prevCtx, coinDenom)
 | 
				
			||||||
 | 
										suite.Require().True(foundSupplyInterestFactorPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// 2. Calculate expected borrow interest owed
 | 
				
			||||||
 | 
										borrowRateApy, err := hard.CalculateBorrowRate(tc.args.interestRateModel, sdk.NewDecFromInt(cashPrior), sdk.NewDecFromInt(borrowCoinPriorAmount), sdk.NewDecFromInt(reservesPrior.Amount))
 | 
				
			||||||
 | 
										suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Convert from APY to SPY, expressed as (1 + borrow rate)
 | 
				
			||||||
 | 
										borrowRateSpy, err := hard.APYToSPY(sdk.OneDec().Add(borrowRateApy))
 | 
				
			||||||
 | 
										suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										newBorrowInterestFactor := hard.CalculateBorrowInterestFactor(borrowRateSpy, sdk.NewInt(snapshot.elapsedTime))
 | 
				
			||||||
 | 
										expectedBorrowInterest := (newBorrowInterestFactor.Mul(sdk.NewDecFromInt(borrowCoinPriorAmount)).TruncateInt()).Sub(borrowCoinPriorAmount)
 | 
				
			||||||
 | 
										expectedReserves := reservesPrior.Add(sdk.NewCoin(coinDenom, sdk.NewDecFromInt(expectedBorrowInterest).Mul(tc.args.reserveFactor).TruncateInt())).Sub(reservesPrior)
 | 
				
			||||||
 | 
										expectedTotalReserves := expectedReserves.Add(reservesPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										expectedBorrowInterestFactor := borrowInterestFactorPrior.Mul(newBorrowInterestFactor)
 | 
				
			||||||
 | 
										expectedSupplyInterest := expectedBorrowInterest.Sub(expectedReserves.Amount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										newSupplyInterestFactor := hard.CalculateSupplyInterestFactor(expectedSupplyInterest.ToDec(), sdk.NewDecFromInt(cashPrior), sdk.NewDecFromInt(borrowCoinPriorAmount), sdk.NewDecFromInt(reservesPrior.Amount))
 | 
				
			||||||
 | 
										expectedSupplyInterestFactor := supplyInterestFactorPrior.Mul(newSupplyInterestFactor)
 | 
				
			||||||
 | 
										// -------------------------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Set up snapshot chain context and run begin blocker
 | 
				
			||||||
 | 
										runAtTime := time.Unix(prevCtx.BlockTime().Unix()+(snapshot.elapsedTime), 0)
 | 
				
			||||||
 | 
										snapshotCtx := prevCtx.WithBlockTime(runAtTime)
 | 
				
			||||||
 | 
										hard.BeginBlocker(snapshotCtx, suite.keeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										borrowInterestFactor, _ := suite.keeper.GetBorrowInterestFactor(ctx, coinDenom)
 | 
				
			||||||
 | 
										suite.Require().Equal(expectedBorrowInterestFactor, borrowInterestFactor)
 | 
				
			||||||
 | 
										suite.Require().Equal(expectedBorrowInterest, expectedSupplyInterest.Add(expectedReserves.Amount))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Check that the total amount of borrowed coins has increased by expected borrow interest amount
 | 
				
			||||||
 | 
										borrowCoinsPost, _ := suite.keeper.GetBorrowedCoins(snapshotCtx)
 | 
				
			||||||
 | 
										borrowCoinPostAmount := borrowCoinsPost.AmountOf(coinDenom)
 | 
				
			||||||
 | 
										suite.Require().Equal(borrowCoinPostAmount, borrowCoinPriorAmount.Add(expectedBorrowInterest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Check that the total amount of supplied coins has increased by expected supply interest amount
 | 
				
			||||||
 | 
										supplyCoinsPost, _ := suite.keeper.GetSuppliedCoins(prevCtx)
 | 
				
			||||||
 | 
										supplyCoinPostAmount := supplyCoinsPost.AmountOf(coinDenom)
 | 
				
			||||||
 | 
										suite.Require().Equal(supplyCoinPostAmount, supplyCoinPriorAmount.Add(expectedSupplyInterest))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Check current total reserves
 | 
				
			||||||
 | 
										totalReserves, _ := suite.keeper.GetTotalReserves(snapshotCtx, coinDenom)
 | 
				
			||||||
 | 
										suite.Require().Equal(expectedTotalReserves, totalReserves)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Check that the supply index has increased as expected
 | 
				
			||||||
 | 
										currSupplyIndexPrior, _ := suite.keeper.GetSupplyInterestFactor(snapshotCtx, coinDenom)
 | 
				
			||||||
 | 
										suite.Require().Equal(expectedSupplyInterestFactor, currSupplyIndexPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// // Check that the borrow index has increased as expected
 | 
				
			||||||
 | 
										currBorrowIndexPrior, _ := suite.keeper.GetBorrowInterestFactor(snapshotCtx, coinDenom)
 | 
				
			||||||
 | 
										suite.Require().Equal(expectedBorrowInterestFactor, currBorrowIndexPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// After supplying again user's supplied balance should have owed supply interest applied
 | 
				
			||||||
 | 
										if snapshot.shouldSupply {
 | 
				
			||||||
 | 
											// Calculate percentage of supply interest profits owed to user
 | 
				
			||||||
 | 
											userSupplyBefore, _ := suite.keeper.GetDeposit(snapshotCtx, tc.args.user)
 | 
				
			||||||
 | 
											userSupplyCoinAmount := userSupplyBefore.Amount.AmountOf(coinDenom)
 | 
				
			||||||
 | 
											userPercentOfTotalSupplied := userSupplyCoinAmount.ToDec().Quo(supplyCoinPriorAmount.ToDec())
 | 
				
			||||||
 | 
											userExpectedSupplyInterestCoin := sdk.NewCoin(coinDenom, userPercentOfTotalSupplied.MulInt(expectedSupplyInterest).TruncateInt())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Calculate percentage of borrow interest profits owed to user
 | 
				
			||||||
 | 
											userBorrowBefore, _ := suite.keeper.GetBorrow(snapshotCtx, tc.args.user)
 | 
				
			||||||
 | 
											userBorrowCoinAmount := userBorrowBefore.Amount.AmountOf(coinDenom)
 | 
				
			||||||
 | 
											userPercentOfTotalBorrowed := userBorrowCoinAmount.ToDec().Quo(borrowCoinPriorAmount.ToDec())
 | 
				
			||||||
 | 
											userExpectedBorrowInterestCoin := sdk.NewCoin(coinDenom, userPercentOfTotalBorrowed.MulInt(expectedBorrowInterest).TruncateInt())
 | 
				
			||||||
 | 
											expectedBorrowCoinsAfter := userBorrowBefore.Amount.Add(userExpectedBorrowInterestCoin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Supplying syncs user's owed supply and borrow interest
 | 
				
			||||||
 | 
											err = suite.keeper.Deposit(snapshotCtx, tc.args.user, sdk.NewCoins(snapshot.supplyCoin))
 | 
				
			||||||
 | 
											suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Fetch user's new borrow and supply balance post-interaction
 | 
				
			||||||
 | 
											userSupplyAfter, _ := suite.keeper.GetDeposit(snapshotCtx, tc.args.user)
 | 
				
			||||||
 | 
											userBorrowAfter, _ := suite.keeper.GetBorrow(snapshotCtx, tc.args.user)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Confirm that user's supply index for the denom has increased as expected
 | 
				
			||||||
 | 
											var userSupplyAfterIndexFactor sdk.Dec
 | 
				
			||||||
 | 
											for _, indexFactor := range userSupplyAfter.Index {
 | 
				
			||||||
 | 
												if indexFactor.Denom == coinDenom {
 | 
				
			||||||
 | 
													userSupplyAfterIndexFactor = indexFactor.Value
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											suite.Require().Equal(userSupplyAfterIndexFactor, currSupplyIndexPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Check user's supplied amount increased by supply interest owed + the newly supplied coins
 | 
				
			||||||
 | 
											expectedSupplyCoinsAfter := userSupplyBefore.Amount.Add(snapshot.supplyCoin).Add(userExpectedSupplyInterestCoin)
 | 
				
			||||||
 | 
											suite.Require().Equal(expectedSupplyCoinsAfter, userSupplyAfter.Amount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Confirm that user's borrow index for the denom has increased as expected
 | 
				
			||||||
 | 
											var userBorrowAfterIndexFactor sdk.Dec
 | 
				
			||||||
 | 
											for _, indexFactor := range userBorrowAfter.Index {
 | 
				
			||||||
 | 
												if indexFactor.Denom == coinDenom {
 | 
				
			||||||
 | 
													userBorrowAfterIndexFactor = indexFactor.Value
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											suite.Require().Equal(userBorrowAfterIndexFactor, currBorrowIndexPrior)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Check user's borrowed amount increased by borrow interest owed
 | 
				
			||||||
 | 
											suite.Require().Equal(expectedBorrowCoinsAfter, userBorrowAfter.Amount)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										prevCtx = snapshotCtx
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestInterestTestSuite(t *testing.T) {
 | 
					func TestInterestTestSuite(t *testing.T) {
 | 
				
			||||||
	suite.Run(t, new(InterestTestSuite))
 | 
						suite.Run(t, new(InterestTestSuite))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -248,6 +248,29 @@ func (k Keeper) GetBorrowedCoins(ctx sdk.Context) (sdk.Coins, bool) {
 | 
				
			|||||||
	return borrowedCoins, true
 | 
						return borrowedCoins, true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetSuppliedCoins sets the total amount of coins currently supplied in the store
 | 
				
			||||||
 | 
					func (k Keeper) SetSuppliedCoins(ctx sdk.Context, suppliedCoins sdk.Coins) {
 | 
				
			||||||
 | 
						store := prefix.NewStore(ctx.KVStore(k.key), types.SuppliedCoinsPrefix)
 | 
				
			||||||
 | 
						if suppliedCoins.Empty() {
 | 
				
			||||||
 | 
							store.Set([]byte{}, []byte{})
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							bz := k.cdc.MustMarshalBinaryBare(suppliedCoins)
 | 
				
			||||||
 | 
							store.Set([]byte{}, bz)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetSuppliedCoins returns an sdk.Coins object from the store representing all currently supplied coins
 | 
				
			||||||
 | 
					func (k Keeper) GetSuppliedCoins(ctx sdk.Context) (sdk.Coins, bool) {
 | 
				
			||||||
 | 
						store := prefix.NewStore(ctx.KVStore(k.key), types.SuppliedCoinsPrefix)
 | 
				
			||||||
 | 
						bz := store.Get([]byte{})
 | 
				
			||||||
 | 
						if bz == nil {
 | 
				
			||||||
 | 
							return sdk.Coins{}, false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var suppliedCoins sdk.Coins
 | 
				
			||||||
 | 
						k.cdc.MustUnmarshalBinaryBare(bz, &suppliedCoins)
 | 
				
			||||||
 | 
						return suppliedCoins, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetMoneyMarket returns a money market from the store for a denom
 | 
					// GetMoneyMarket returns a money market from the store for a denom
 | 
				
			||||||
func (k Keeper) GetMoneyMarket(ctx sdk.Context, denom string) (types.MoneyMarket, bool) {
 | 
					func (k Keeper) GetMoneyMarket(ctx sdk.Context, denom string) (types.MoneyMarket, bool) {
 | 
				
			||||||
	store := prefix.NewStore(ctx.KVStore(k.key), types.MoneyMarketsPrefix)
 | 
						store := prefix.NewStore(ctx.KVStore(k.key), types.MoneyMarketsPrefix)
 | 
				
			||||||
@ -326,22 +349,41 @@ func (k Keeper) SetTotalReserves(ctx sdk.Context, denom string, coin sdk.Coin) {
 | 
				
			|||||||
	store.Set([]byte(denom), bz)
 | 
						store.Set([]byte(denom), bz)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetInterestFactor returns the current interest factor for an individual market
 | 
					// GetBorrowInterestFactor returns the current borrow interest factor for an individual market
 | 
				
			||||||
func (k Keeper) GetInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) {
 | 
					func (k Keeper) GetBorrowInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) {
 | 
				
			||||||
	store := prefix.NewStore(ctx.KVStore(k.key), types.InterestFactorPrefix)
 | 
						store := prefix.NewStore(ctx.KVStore(k.key), types.BorrowInterestFactorPrefix)
 | 
				
			||||||
	bz := store.Get([]byte(denom))
 | 
						bz := store.Get([]byte(denom))
 | 
				
			||||||
	if bz == nil {
 | 
						if bz == nil {
 | 
				
			||||||
		return sdk.ZeroDec(), false
 | 
							return sdk.ZeroDec(), false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	var interestFactor sdk.Dec
 | 
						var borrowInterestFactor sdk.Dec
 | 
				
			||||||
	k.cdc.MustUnmarshalBinaryBare(bz, &interestFactor)
 | 
						k.cdc.MustUnmarshalBinaryBare(bz, &borrowInterestFactor)
 | 
				
			||||||
	return interestFactor, true
 | 
						return borrowInterestFactor, true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetInterestFactor sets the current interest factor for an individual market
 | 
					// SetBorrowInterestFactor sets the current borrow interest factor for an individual market
 | 
				
			||||||
func (k Keeper) SetInterestFactor(ctx sdk.Context, denom string, borrowIndex sdk.Dec) {
 | 
					func (k Keeper) SetBorrowInterestFactor(ctx sdk.Context, denom string, borrowInterestFactor sdk.Dec) {
 | 
				
			||||||
	store := prefix.NewStore(ctx.KVStore(k.key), types.InterestFactorPrefix)
 | 
						store := prefix.NewStore(ctx.KVStore(k.key), types.BorrowInterestFactorPrefix)
 | 
				
			||||||
	bz := k.cdc.MustMarshalBinaryBare(borrowIndex)
 | 
						bz := k.cdc.MustMarshalBinaryBare(borrowInterestFactor)
 | 
				
			||||||
 | 
						store.Set([]byte(denom), bz)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetSupplyInterestFactor returns the current supply interest factor for an individual market
 | 
				
			||||||
 | 
					func (k Keeper) GetSupplyInterestFactor(ctx sdk.Context, denom string) (sdk.Dec, bool) {
 | 
				
			||||||
 | 
						store := prefix.NewStore(ctx.KVStore(k.key), types.SupplyInterestFactorPrefix)
 | 
				
			||||||
 | 
						bz := store.Get([]byte(denom))
 | 
				
			||||||
 | 
						if bz == nil {
 | 
				
			||||||
 | 
							return sdk.ZeroDec(), false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var supplyInterestFactor sdk.Dec
 | 
				
			||||||
 | 
						k.cdc.MustUnmarshalBinaryBare(bz, &supplyInterestFactor)
 | 
				
			||||||
 | 
						return supplyInterestFactor, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetSupplyInterestFactor sets the current supply interest factor for an individual market
 | 
				
			||||||
 | 
					func (k Keeper) SetSupplyInterestFactor(ctx sdk.Context, denom string, supplyInterestFactor sdk.Dec) {
 | 
				
			||||||
 | 
						store := prefix.NewStore(ctx.KVStore(k.key), types.SupplyInterestFactorPrefix)
 | 
				
			||||||
 | 
						bz := k.cdc.MustMarshalBinaryBare(supplyInterestFactor)
 | 
				
			||||||
	store.Set([]byte(denom), bz)
 | 
						store.Set([]byte(denom), bz)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -76,7 +76,8 @@ func (suite *KeeperTestSuite) TestGetSetPreviousDelegatorDistribution() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (suite *KeeperTestSuite) TestGetSetDeleteDeposit() {
 | 
					func (suite *KeeperTestSuite) TestGetSetDeleteDeposit() {
 | 
				
			||||||
	dep := types.NewDeposit(sdk.AccAddress("test"), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))))
 | 
						dep := types.NewDeposit(sdk.AccAddress("test"), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
 | 
				
			||||||
 | 
							types.SupplyInterestFactors{types.NewSupplyInterestFactor("", sdk.MustNewDecFromStr("0"))})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, f := suite.keeper.GetDeposit(suite.ctx, sdk.AccAddress("test"))
 | 
						_, f := suite.keeper.GetDeposit(suite.ctx, sdk.AccAddress("test"))
 | 
				
			||||||
	suite.Require().False(f)
 | 
						suite.Require().False(f)
 | 
				
			||||||
@ -96,7 +97,7 @@ func (suite *KeeperTestSuite) TestGetSetDeleteDeposit() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (suite *KeeperTestSuite) TestIterateDeposits() {
 | 
					func (suite *KeeperTestSuite) TestIterateDeposits() {
 | 
				
			||||||
	for i := 0; i < 5; i++ {
 | 
						for i := 0; i < 5; i++ {
 | 
				
			||||||
		dep := types.NewDeposit(sdk.AccAddress("test"+fmt.Sprint(i)), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))))
 | 
							dep := types.NewDeposit(sdk.AccAddress("test"+fmt.Sprint(i)), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))), types.SupplyInterestFactors{})
 | 
				
			||||||
		suite.Require().NotPanics(func() { suite.keeper.SetDeposit(suite.ctx, dep) })
 | 
							suite.Require().NotPanics(func() { suite.keeper.SetDeposit(suite.ctx, dep) })
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	var deposits []types.Deposit
 | 
						var deposits []types.Deposit
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
 | 
				
			|||||||
		return false, err
 | 
							return false, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k.SyncOutstandingInterest(ctx, borrower)
 | 
						k.SyncBorrowInterest(ctx, borrower)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	k.UpdateItemInLtvIndex(ctx, prevLtv, shouldInsertIndex, borrower)
 | 
						k.UpdateItemInLtvIndex(ctx, prevLtv, shouldInsertIndex, borrower)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,8 +15,8 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Sync interest so loan is up-to-date
 | 
						// Sync borrow interest so loan is up-to-date
 | 
				
			||||||
	k.SyncOutstandingInterest(ctx, sender)
 | 
						k.SyncBorrowInterest(ctx, sender)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Validate requested repay
 | 
						// Validate requested repay
 | 
				
			||||||
	err = k.ValidateRepay(ctx, sender, coins)
 | 
						err = k.ValidateRepay(ctx, sender, coins)
 | 
				
			||||||
@ -30,7 +30,10 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
 | 
				
			|||||||
		return types.ErrBorrowNotFound
 | 
							return types.ErrBorrowNotFound
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	payment := k.CalculatePaymentAmount(borrow.Amount, coins)
 | 
						payment, err := k.CalculatePaymentAmount(borrow.Amount, coins)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Sends coins from user to Hard module account
 | 
						// Sends coins from user to Hard module account
 | 
				
			||||||
	err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleAccountName, payment)
 | 
						err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleAccountName, payment)
 | 
				
			||||||
@ -73,8 +76,13 @@ func (k Keeper) ValidateRepay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CalculatePaymentAmount prevents overpayment when repaying borrowed coins
 | 
					// CalculatePaymentAmount prevents overpayment when repaying borrowed coins
 | 
				
			||||||
func (k Keeper) CalculatePaymentAmount(owed sdk.Coins, payment sdk.Coins) sdk.Coins {
 | 
					func (k Keeper) CalculatePaymentAmount(owed sdk.Coins, payment sdk.Coins) (sdk.Coins, error) {
 | 
				
			||||||
	repayment := sdk.Coins{}
 | 
						repayment := sdk.Coins{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !payment.DenomsSubsetOf(owed) {
 | 
				
			||||||
 | 
							return repayment, types.ErrInvalidRepaymentDenom
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, coin := range payment {
 | 
						for _, coin := range payment {
 | 
				
			||||||
		if coin.Amount.GT(owed.AmountOf(coin.Denom)) {
 | 
							if coin.Amount.GT(owed.AmountOf(coin.Denom)) {
 | 
				
			||||||
			repayment = append(repayment, sdk.NewCoin(coin.Denom, owed.AmountOf(coin.Denom)))
 | 
								repayment = append(repayment, sdk.NewCoin(coin.Denom, owed.AmountOf(coin.Denom)))
 | 
				
			||||||
@ -82,5 +90,5 @@ func (k Keeper) CalculatePaymentAmount(owed sdk.Coins, payment sdk.Coins) sdk.Co
 | 
				
			|||||||
			repayment = append(repayment, coin)
 | 
								repayment = append(repayment, coin)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return repayment
 | 
						return repayment, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,21 @@ func (suite *KeeperTestSuite) TestRepay() {
 | 
				
			|||||||
				contains:   "",
 | 
									contains:   "",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"invalid: attempt to repay non-supplied coin",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									borrower:             sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000*KAVA_CF)), sdk.NewCoin("usdx", sdk.NewInt(1000*USDX_CF))),
 | 
				
			||||||
 | 
									depositCoins:         []sdk.Coin{sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))},
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(50*KAVA_CF))),
 | 
				
			||||||
 | 
									repayCoins:           sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(10*KAVA_CF))),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: false,
 | 
				
			||||||
 | 
									contains:   "account can only repay up to 0bnb",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			"invalid: insufficent balance for repay",
 | 
								"invalid: insufficent balance for repay",
 | 
				
			||||||
			args{
 | 
								args{
 | 
				
			||||||
@ -98,7 +113,7 @@ func (suite *KeeperTestSuite) TestRepay() {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			errArgs{
 | 
								errArgs{
 | 
				
			||||||
				expectPass: false,
 | 
									expectPass: false,
 | 
				
			||||||
				contains:   "account can only repay up to",
 | 
									contains:   "account can only repay up to 50000000ukava",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -203,7 +218,8 @@ func (suite *KeeperTestSuite) TestRepay() {
 | 
				
			|||||||
			if tc.errArgs.expectPass {
 | 
								if tc.errArgs.expectPass {
 | 
				
			||||||
				suite.Require().NoError(err)
 | 
									suite.Require().NoError(err)
 | 
				
			||||||
				// If we overpaid expect an adjustment
 | 
									// If we overpaid expect an adjustment
 | 
				
			||||||
				repaymentCoins := suite.keeper.CalculatePaymentAmount(tc.args.borrowCoins, tc.args.repayCoins)
 | 
									repaymentCoins, err := suite.keeper.CalculatePaymentAmount(tc.args.borrowCoins, tc.args.repayCoins)
 | 
				
			||||||
 | 
									suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Check borrower balance
 | 
									// Check borrower balance
 | 
				
			||||||
				expectedBorrowerCoins := tc.args.initialBorrowerCoins.Sub(tc.args.depositCoins).Add(tc.args.borrowCoins...).Sub(repaymentCoins)
 | 
									expectedBorrowerCoins := tc.args.initialBorrowerCoins.Sub(tc.args.depositCoins).Add(tc.args.borrowCoins...).Sub(repaymentCoins)
 | 
				
			||||||
 | 
				
			|||||||
@ -84,7 +84,7 @@ func (suite *KeeperTestSuite) TestApplyDepositRewards() {
 | 
				
			|||||||
			supplyKeeper := tApp.GetSupplyKeeper()
 | 
								supplyKeeper := tApp.GetSupplyKeeper()
 | 
				
			||||||
			supplyKeeper.MintCoins(ctx, types.ModuleAccountName, cs(tc.args.totalDeposits))
 | 
								supplyKeeper.MintCoins(ctx, types.ModuleAccountName, cs(tc.args.totalDeposits))
 | 
				
			||||||
			keeper := tApp.GetHardKeeper()
 | 
								keeper := tApp.GetHardKeeper()
 | 
				
			||||||
			deposit := types.NewDeposit(tc.args.depositor, tc.args.depositAmount)
 | 
								deposit := types.NewDeposit(tc.args.depositor, tc.args.depositAmount, types.SupplyInterestFactors{})
 | 
				
			||||||
			keeper.SetDeposit(ctx, deposit)
 | 
								keeper.SetDeposit(ctx, deposit)
 | 
				
			||||||
			suite.app = tApp
 | 
								suite.app = tApp
 | 
				
			||||||
			suite.ctx = ctx
 | 
								suite.ctx = ctx
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										98
									
								
								x/hard/keeper/withdraw.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								x/hard/keeper/withdraw.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					package keeper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
						sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/hard/types"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Withdraw returns some or all of a deposit back to original depositor
 | 
				
			||||||
 | 
					func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error {
 | 
				
			||||||
 | 
						// Get current stored LTV based on stored borrows/deposits
 | 
				
			||||||
 | 
						prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						k.SyncBorrowInterest(ctx, depositor)
 | 
				
			||||||
 | 
						k.SyncSupplyInterest(ctx, depositor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						deposit, found := k.GetDeposit(ctx, depositor)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							return sdkerrors.Wrapf(types.ErrDepositNotFound, "no deposit found for %s", depositor)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						amount, err := k.CalculateWithdrawAmount(deposit.Amount, coins)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						proposedDeposit := types.NewDeposit(deposit.Depositor, deposit.Amount.Sub(amount), types.SupplyInterestFactors{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						borrow, found := k.GetBorrow(ctx, depositor)
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							borrow = types.Borrow{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						valid, err := k.IsWithinValidLtvRange(ctx, proposedDeposit, borrow)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !valid {
 | 
				
			||||||
 | 
							return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "proposed withdraw outside loan-to-value range")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, depositor, amount)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if deposit.Amount.IsEqual(amount) {
 | 
				
			||||||
 | 
							ctx.EventManager().EmitEvent(
 | 
				
			||||||
 | 
								sdk.NewEvent(
 | 
				
			||||||
 | 
									types.EventTypeDeleteHardDeposit,
 | 
				
			||||||
 | 
									sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()),
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							k.DeleteDeposit(ctx, deposit)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						deposit.Amount = deposit.Amount.Sub(amount)
 | 
				
			||||||
 | 
						k.SetDeposit(ctx, deposit)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, depositor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update total supplied amount
 | 
				
			||||||
 | 
						k.DecrementBorrowedCoins(ctx, amount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.EventManager().EmitEvent(
 | 
				
			||||||
 | 
							sdk.NewEvent(
 | 
				
			||||||
 | 
								types.EventTypeHardWithdrawal,
 | 
				
			||||||
 | 
								sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()),
 | 
				
			||||||
 | 
								sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()),
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CalculateWithdrawAmount enables full withdraw of deposited coins by adjusting withdraw amount
 | 
				
			||||||
 | 
					// to equal total deposit amount if the requested withdraw amount > current deposit amount
 | 
				
			||||||
 | 
					func (k Keeper) CalculateWithdrawAmount(available sdk.Coins, request sdk.Coins) (sdk.Coins, error) {
 | 
				
			||||||
 | 
						result := sdk.Coins{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !request.DenomsSubsetOf(available) {
 | 
				
			||||||
 | 
							return result, types.ErrInvalidWithdrawDenom
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, coin := range request {
 | 
				
			||||||
 | 
							if coin.Amount.GT(available.AmountOf(coin.Denom)) {
 | 
				
			||||||
 | 
								result = append(result, sdk.NewCoin(coin.Denom, available.AmountOf(coin.Denom)))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								result = append(result, coin)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										390
									
								
								x/hard/keeper/withdraw_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										390
									
								
								x/hard/keeper/withdraw_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,390 @@
 | 
				
			|||||||
 | 
					package keeper_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
 | 
						abci "github.com/tendermint/tendermint/abci/types"
 | 
				
			||||||
 | 
						"github.com/tendermint/tendermint/crypto"
 | 
				
			||||||
 | 
						tmtime "github.com/tendermint/tendermint/types/time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/app"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/hard"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/hard/types"
 | 
				
			||||||
 | 
						"github.com/kava-labs/kava/x/pricefeed"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *KeeperTestSuite) TestWithdraw() {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							depositor                 sdk.AccAddress
 | 
				
			||||||
 | 
							initialModAccountBalance  sdk.Coins
 | 
				
			||||||
 | 
							depositAmount             sdk.Coins
 | 
				
			||||||
 | 
							withdrawAmount            sdk.Coins
 | 
				
			||||||
 | 
							createDeposit             bool
 | 
				
			||||||
 | 
							expectedAccountBalance    sdk.Coins
 | 
				
			||||||
 | 
							expectedModAccountBalance sdk.Coins
 | 
				
			||||||
 | 
							depositExists             bool
 | 
				
			||||||
 | 
							finalDepositAmount        sdk.Coins
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						type errArgs struct {
 | 
				
			||||||
 | 
							expectPass bool
 | 
				
			||||||
 | 
							contains   string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						type withdrawTest struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							errArgs errArgs
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						testCases := []withdrawTest{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"valid: partial withdraw",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									depositor:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialModAccountBalance:  sdk.Coins(nil),
 | 
				
			||||||
 | 
									depositAmount:             sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
				
			||||||
 | 
									withdrawAmount:            sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
 | 
				
			||||||
 | 
									createDeposit:             true,
 | 
				
			||||||
 | 
									expectedAccountBalance:    sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(900)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
 | 
				
			||||||
 | 
									expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
 | 
				
			||||||
 | 
									depositExists:             true,
 | 
				
			||||||
 | 
									finalDepositAmount:        sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(100))),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"valid: full withdraw",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									depositor:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialModAccountBalance:  sdk.Coins(nil),
 | 
				
			||||||
 | 
									depositAmount:             sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
				
			||||||
 | 
									withdrawAmount:            sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
				
			||||||
 | 
									createDeposit:             true,
 | 
				
			||||||
 | 
									expectedAccountBalance:    sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
 | 
				
			||||||
 | 
									expectedModAccountBalance: sdk.Coins(nil),
 | 
				
			||||||
 | 
									depositExists:             false,
 | 
				
			||||||
 | 
									finalDepositAmount:        sdk.Coins{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"valid: withdraw exceeds deposit but is adjusted to match max deposit",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									depositor:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialModAccountBalance:  sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000))),
 | 
				
			||||||
 | 
									depositAmount:             sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
				
			||||||
 | 
									withdrawAmount:            sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(300))),
 | 
				
			||||||
 | 
									createDeposit:             true,
 | 
				
			||||||
 | 
									expectedAccountBalance:    sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000))),
 | 
				
			||||||
 | 
									expectedModAccountBalance: sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000))),
 | 
				
			||||||
 | 
									depositExists:             false,
 | 
				
			||||||
 | 
									finalDepositAmount:        sdk.Coins{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: true,
 | 
				
			||||||
 | 
									contains:   "",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"invalid: withdraw non-supplied coin type",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									depositor:                 sdk.AccAddress(crypto.AddressHash([]byte("test"))),
 | 
				
			||||||
 | 
									initialModAccountBalance:  sdk.Coins(nil),
 | 
				
			||||||
 | 
									depositAmount:             sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(200))),
 | 
				
			||||||
 | 
									withdrawAmount:            sdk.NewCoins(sdk.NewCoin("btcb", sdk.NewInt(200))),
 | 
				
			||||||
 | 
									createDeposit:             true,
 | 
				
			||||||
 | 
									expectedAccountBalance:    sdk.Coins{},
 | 
				
			||||||
 | 
									expectedModAccountBalance: sdk.Coins{},
 | 
				
			||||||
 | 
									depositExists:             false,
 | 
				
			||||||
 | 
									finalDepositAmount:        sdk.Coins{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: false,
 | 
				
			||||||
 | 
									contains:   "no coins of this type deposited",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							suite.Run(tc.name, func() {
 | 
				
			||||||
 | 
								// create new app with one funded account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Initialize test app and set context
 | 
				
			||||||
 | 
								tApp := app.NewTestApp()
 | 
				
			||||||
 | 
								ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
 | 
				
			||||||
 | 
								authGS := app.NewAuthGenState(
 | 
				
			||||||
 | 
									[]sdk.AccAddress{tc.args.depositor},
 | 
				
			||||||
 | 
									[]sdk.Coins{sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1000)), sdk.NewCoin("btcb", sdk.NewInt(1000)))},
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								loanToValue := sdk.MustNewDecFromStr("0.6")
 | 
				
			||||||
 | 
								hardGS := types.NewGenesisState(types.NewParams(
 | 
				
			||||||
 | 
									true,
 | 
				
			||||||
 | 
									types.DistributionSchedules{
 | 
				
			||||||
 | 
										types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
 | 
				
			||||||
 | 
										types.NewDistributionSchedule(true, "bnb", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
				
			||||||
 | 
										time.Hour*24,
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									types.MoneyMarkets{
 | 
				
			||||||
 | 
										types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
 | 
				
			||||||
 | 
										types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
 | 
				
			||||||
 | 
										types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(100000000), sdk.NewInt(BNB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									0, // LTV counter
 | 
				
			||||||
 | 
								), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Pricefeed module genesis state
 | 
				
			||||||
 | 
								pricefeedGS := pricefeed.GenesisState{
 | 
				
			||||||
 | 
									Params: pricefeed.Params{
 | 
				
			||||||
 | 
										Markets: []pricefeed.Market{
 | 
				
			||||||
 | 
											{MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
				
			||||||
 | 
											{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
				
			||||||
 | 
											{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									PostedPrices: []pricefeed.PostedPrice{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MarketID:      "usdx:usd",
 | 
				
			||||||
 | 
											OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
 | 
											Price:         sdk.MustNewDecFromStr("1.00"),
 | 
				
			||||||
 | 
											Expiry:        time.Now().Add(100 * time.Hour),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MarketID:      "kava:usd",
 | 
				
			||||||
 | 
											OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
 | 
											Price:         sdk.MustNewDecFromStr("2.00"),
 | 
				
			||||||
 | 
											Expiry:        time.Now().Add(100 * time.Hour),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MarketID:      "bnb:usd",
 | 
				
			||||||
 | 
											OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
 | 
											Price:         sdk.MustNewDecFromStr("10.00"),
 | 
				
			||||||
 | 
											Expiry:        time.Now().Add(100 * time.Hour),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								tApp.InitializeFromGenesisStates(authGS,
 | 
				
			||||||
 | 
									app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)},
 | 
				
			||||||
 | 
									app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)})
 | 
				
			||||||
 | 
								keeper := tApp.GetHardKeeper()
 | 
				
			||||||
 | 
								suite.app = tApp
 | 
				
			||||||
 | 
								suite.ctx = ctx
 | 
				
			||||||
 | 
								suite.keeper = keeper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Mint coins to Hard module account
 | 
				
			||||||
 | 
								supplyKeeper := tApp.GetSupplyKeeper()
 | 
				
			||||||
 | 
								supplyKeeper.MintCoins(ctx, types.ModuleAccountName, tc.args.initialModAccountBalance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tc.args.createDeposit {
 | 
				
			||||||
 | 
									err := suite.keeper.Deposit(suite.ctx, tc.args.depositor, tc.args.depositAmount)
 | 
				
			||||||
 | 
									suite.Require().NoError(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								err := suite.keeper.Withdraw(suite.ctx, tc.args.depositor, tc.args.withdrawAmount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tc.errArgs.expectPass {
 | 
				
			||||||
 | 
									suite.Require().NoError(err)
 | 
				
			||||||
 | 
									acc := suite.getAccount(tc.args.depositor)
 | 
				
			||||||
 | 
									suite.Require().Equal(tc.args.expectedAccountBalance, acc.GetCoins())
 | 
				
			||||||
 | 
									mAcc := suite.getModuleAccount(types.ModuleAccountName)
 | 
				
			||||||
 | 
									suite.Require().Equal(tc.args.expectedModAccountBalance, mAcc.GetCoins())
 | 
				
			||||||
 | 
									testDeposit, f := suite.keeper.GetDeposit(suite.ctx, tc.args.depositor)
 | 
				
			||||||
 | 
									if tc.args.depositExists {
 | 
				
			||||||
 | 
										suite.Require().True(f)
 | 
				
			||||||
 | 
										suite.Require().Equal(tc.args.finalDepositAmount, testDeposit.Amount)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										suite.Require().False(f)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									suite.Require().Error(err)
 | 
				
			||||||
 | 
									suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (suite *KeeperTestSuite) TestLtvWithdraw() {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							borrower             sdk.AccAddress
 | 
				
			||||||
 | 
							initialModuleCoins   sdk.Coins
 | 
				
			||||||
 | 
							initialBorrowerCoins sdk.Coins
 | 
				
			||||||
 | 
							depositCoins         sdk.Coins
 | 
				
			||||||
 | 
							borrowCoins          sdk.Coins
 | 
				
			||||||
 | 
							futureTime           int64
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type errArgs struct {
 | 
				
			||||||
 | 
							expectPass bool
 | 
				
			||||||
 | 
							contains   string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type liqTest struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							errArgs errArgs
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set up test constants
 | 
				
			||||||
 | 
						model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0"), sdk.MustNewDecFromStr("0.1"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("0.5"))
 | 
				
			||||||
 | 
						reserveFactor := sdk.MustNewDecFromStr("0.05")
 | 
				
			||||||
 | 
						oneMonthInSeconds := int64(2592000)
 | 
				
			||||||
 | 
						borrower := sdk.AccAddress(crypto.AddressHash([]byte("testborrower")))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testCases := []liqTest{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								"invalid: withdraw is outside loan-to-value range",
 | 
				
			||||||
 | 
								args{
 | 
				
			||||||
 | 
									borrower:             borrower,
 | 
				
			||||||
 | 
									initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF)), sdk.NewCoin("usdx", sdk.NewInt(100*KAVA_CF))),
 | 
				
			||||||
 | 
									depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))), // 10 * 2 = $20
 | 
				
			||||||
 | 
									borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))),  // 8 * 2 = $16
 | 
				
			||||||
 | 
									futureTime:           oneMonthInSeconds,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								errArgs{
 | 
				
			||||||
 | 
									expectPass: false,
 | 
				
			||||||
 | 
									contains:   "proposed withdraw outside loan-to-value range",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							suite.Run(tc.name, func() {
 | 
				
			||||||
 | 
								// Initialize test app and set context
 | 
				
			||||||
 | 
								tApp := app.NewTestApp()
 | 
				
			||||||
 | 
								ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Auth module genesis state
 | 
				
			||||||
 | 
								authGS := app.NewAuthGenState(
 | 
				
			||||||
 | 
									[]sdk.AccAddress{tc.args.borrower},
 | 
				
			||||||
 | 
									[]sdk.Coins{tc.args.initialBorrowerCoins},
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Harvest module genesis state
 | 
				
			||||||
 | 
								harvestGS := types.NewGenesisState(types.NewParams(
 | 
				
			||||||
 | 
									true,
 | 
				
			||||||
 | 
									types.DistributionSchedules{
 | 
				
			||||||
 | 
										types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
				
			||||||
 | 
										types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule(
 | 
				
			||||||
 | 
										types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}),
 | 
				
			||||||
 | 
										time.Hour*24,
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									types.MoneyMarkets{
 | 
				
			||||||
 | 
										types.NewMoneyMarket("ukava",
 | 
				
			||||||
 | 
											types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
 | 
				
			||||||
 | 
											"kava:usd",                     // Market ID
 | 
				
			||||||
 | 
											sdk.NewInt(KAVA_CF),            // Conversion Factor
 | 
				
			||||||
 | 
											sdk.NewInt(100000000*KAVA_CF),  // Auction Size
 | 
				
			||||||
 | 
											model,                          // Interest Rate Model
 | 
				
			||||||
 | 
											reserveFactor,                  // Reserve Factor
 | 
				
			||||||
 | 
											sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
				
			||||||
 | 
										types.NewMoneyMarket("usdx",
 | 
				
			||||||
 | 
											types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
 | 
				
			||||||
 | 
											"usdx:usd",                     // Market ID
 | 
				
			||||||
 | 
											sdk.NewInt(KAVA_CF),            // Conversion Factor
 | 
				
			||||||
 | 
											sdk.NewInt(100000000*KAVA_CF),  // Auction Size
 | 
				
			||||||
 | 
											model,                          // Interest Rate Model
 | 
				
			||||||
 | 
											reserveFactor,                  // Reserve Factor
 | 
				
			||||||
 | 
											sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									0, // LTV counter
 | 
				
			||||||
 | 
								), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Pricefeed module genesis state
 | 
				
			||||||
 | 
								pricefeedGS := pricefeed.GenesisState{
 | 
				
			||||||
 | 
									Params: pricefeed.Params{
 | 
				
			||||||
 | 
										Markets: []pricefeed.Market{
 | 
				
			||||||
 | 
											{MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
				
			||||||
 | 
											{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									PostedPrices: []pricefeed.PostedPrice{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MarketID:      "usdx:usd",
 | 
				
			||||||
 | 
											OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
 | 
											Price:         sdk.MustNewDecFromStr("1.00"),
 | 
				
			||||||
 | 
											Expiry:        time.Now().Add(100 * time.Hour),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											MarketID:      "kava:usd",
 | 
				
			||||||
 | 
											OracleAddress: sdk.AccAddress{},
 | 
				
			||||||
 | 
											Price:         sdk.MustNewDecFromStr("2.00"),
 | 
				
			||||||
 | 
											Expiry:        time.Now().Add(100 * time.Hour),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Initialize test application
 | 
				
			||||||
 | 
								tApp.InitializeFromGenesisStates(authGS,
 | 
				
			||||||
 | 
									app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)},
 | 
				
			||||||
 | 
									app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Mint coins to Harvest module account
 | 
				
			||||||
 | 
								supplyKeeper := tApp.GetSupplyKeeper()
 | 
				
			||||||
 | 
								supplyKeeper.MintCoins(ctx, types.ModuleAccountName, tc.args.initialModuleCoins)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auctionKeeper := tApp.GetAuctionKeeper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								keeper := tApp.GetHardKeeper()
 | 
				
			||||||
 | 
								suite.app = tApp
 | 
				
			||||||
 | 
								suite.ctx = ctx
 | 
				
			||||||
 | 
								suite.keeper = keeper
 | 
				
			||||||
 | 
								suite.auctionKeeper = auctionKeeper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Run begin blocker to set up state
 | 
				
			||||||
 | 
								hard.BeginBlocker(suite.ctx, suite.keeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Borrower deposits coins
 | 
				
			||||||
 | 
								err = suite.keeper.Deposit(suite.ctx, tc.args.borrower, tc.args.depositCoins)
 | 
				
			||||||
 | 
								suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Borrower borrows coins
 | 
				
			||||||
 | 
								err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
 | 
				
			||||||
 | 
								suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Attempting to withdraw fails
 | 
				
			||||||
 | 
								err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, sdk.NewCoins(sdk.NewCoin("ukava", sdk.OneInt())))
 | 
				
			||||||
 | 
								suite.Require().Error(err)
 | 
				
			||||||
 | 
								suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Set up future chain context and run begin blocker, increasing user's owed borrow balance
 | 
				
			||||||
 | 
								runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.futureTime), 0)
 | 
				
			||||||
 | 
								liqCtx := suite.ctx.WithBlockTime(runAtTime)
 | 
				
			||||||
 | 
								hard.BeginBlocker(liqCtx, suite.keeper)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Attempted withdraw of 1 coin still fails
 | 
				
			||||||
 | 
								err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, sdk.NewCoins(sdk.NewCoin("ukava", sdk.OneInt())))
 | 
				
			||||||
 | 
								suite.Require().Error(err)
 | 
				
			||||||
 | 
								suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Repay the initial principal
 | 
				
			||||||
 | 
								err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
 | 
				
			||||||
 | 
								suite.Require().NoError(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Attempted withdraw of all deposited coins fails as user hasn't repaid interest debt
 | 
				
			||||||
 | 
								err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, tc.args.depositCoins)
 | 
				
			||||||
 | 
								suite.Require().Error(err)
 | 
				
			||||||
 | 
								suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Withdrawing half the coins should succeed
 | 
				
			||||||
 | 
								withdrawCoins := sdk.NewCoins(sdk.NewCoin("ukava", tc.args.depositCoins[0].Amount.Quo(sdk.NewInt(2))))
 | 
				
			||||||
 | 
								err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, withdrawCoins)
 | 
				
			||||||
 | 
								suite.Require().NoError(err)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -27,7 +27,9 @@ func TestDecodeDistributionStore(t *testing.T) {
 | 
				
			|||||||
	cdc := makeTestCodec()
 | 
						cdc := makeTestCodec()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	prevBlockTime := time.Now().UTC()
 | 
						prevBlockTime := time.Now().UTC()
 | 
				
			||||||
	deposit := types.NewDeposit(sdk.AccAddress("test"), sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1))))
 | 
						deposit := types.NewDeposit(sdk.AccAddress("test"),
 | 
				
			||||||
 | 
							sdk.NewCoins(sdk.NewCoin("bnb", sdk.NewInt(1))),
 | 
				
			||||||
 | 
							types.SupplyInterestFactors{types.NewSupplyInterestFactor("bnb", sdk.OneDec())})
 | 
				
			||||||
	claim := types.NewClaim(sdk.AccAddress("test"), "bnb", sdk.NewCoin("hard", sdk.NewInt(100)), "stake")
 | 
						claim := types.NewClaim(sdk.AccAddress("test"), "bnb", sdk.NewCoin("hard", sdk.NewInt(100)), "stake")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	kvPairs := kv.Pairs{
 | 
						kvPairs := kv.Pairs{
 | 
				
			||||||
 | 
				
			|||||||
@ -4,32 +4,15 @@ import (
 | 
				
			|||||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						sdk "github.com/cosmos/cosmos-sdk/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// BorrowIndexItem defines an individual borrow index
 | 
					 | 
				
			||||||
type BorrowIndexItem struct {
 | 
					 | 
				
			||||||
	Denom string  `json:"denom" yaml:"denom"`
 | 
					 | 
				
			||||||
	Value sdk.Dec `json:"value" yaml:"value"`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewBorrowIndexItem returns a new BorrowIndexItem instance
 | 
					 | 
				
			||||||
func NewBorrowIndexItem(denom string, value sdk.Dec) BorrowIndexItem {
 | 
					 | 
				
			||||||
	return BorrowIndexItem{
 | 
					 | 
				
			||||||
		Denom: denom,
 | 
					 | 
				
			||||||
		Value: value,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// BorrowIndexes is a slice of BorrowIndexItem, because Amino won't marshal maps
 | 
					 | 
				
			||||||
type BorrowIndexes []BorrowIndexItem
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Borrow defines an amount of coins borrowed from a hard module account
 | 
					// Borrow defines an amount of coins borrowed from a hard module account
 | 
				
			||||||
type Borrow struct {
 | 
					type Borrow struct {
 | 
				
			||||||
	Borrower sdk.AccAddress  `json:"borrower" yaml:"borrower"`
 | 
						Borrower sdk.AccAddress        `json:"borrower" yaml:"borrower"`
 | 
				
			||||||
	Amount   sdk.Coins       `json:"amount" yaml:"amount"`
 | 
						Amount   sdk.Coins             `json:"amount" yaml:"amount"`
 | 
				
			||||||
	Index    InterestFactors `json:"index" yaml:"index"`
 | 
						Index    BorrowInterestFactors `json:"index" yaml:"index"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewBorrow returns a new Borrow instance
 | 
					// NewBorrow returns a new Borrow instance
 | 
				
			||||||
func NewBorrow(borrower sdk.AccAddress, amount sdk.Coins, index InterestFactors) Borrow {
 | 
					func NewBorrow(borrower sdk.AccAddress, amount sdk.Coins, index BorrowInterestFactors) Borrow {
 | 
				
			||||||
	return Borrow{
 | 
						return Borrow{
 | 
				
			||||||
		Borrower: borrower,
 | 
							Borrower: borrower,
 | 
				
			||||||
		Amount:   amount,
 | 
							Amount:   amount,
 | 
				
			||||||
@ -37,19 +20,19 @@ func NewBorrow(borrower sdk.AccAddress, amount sdk.Coins, index InterestFactors)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// InterestFactor defines an individual interest factor
 | 
					// BorrowInterestFactor defines an individual borrow interest factor
 | 
				
			||||||
type InterestFactor struct {
 | 
					type BorrowInterestFactor struct {
 | 
				
			||||||
	Denom string  `json:"denom" yaml:"denom"`
 | 
						Denom string  `json:"denom" yaml:"denom"`
 | 
				
			||||||
	Value sdk.Dec `json:"value" yaml:"value"`
 | 
						Value sdk.Dec `json:"value" yaml:"value"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewInterestFactor returns a new InterestFactor instance
 | 
					// NewBorrowInterestFactor returns a new BorrowInterestFactor instance
 | 
				
			||||||
func NewInterestFactor(denom string, value sdk.Dec) InterestFactor {
 | 
					func NewBorrowInterestFactor(denom string, value sdk.Dec) BorrowInterestFactor {
 | 
				
			||||||
	return InterestFactor{
 | 
						return BorrowInterestFactor{
 | 
				
			||||||
		Denom: denom,
 | 
							Denom: denom,
 | 
				
			||||||
		Value: value,
 | 
							Value: value,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// InterestFactors is a slice of InterestFactor, because Amino won't marshal maps
 | 
					// BorrowInterestFactors is a slice of BorrowInterestFactor, because Amino won't marshal maps
 | 
				
			||||||
type InterestFactors []InterestFactor
 | 
					type BorrowInterestFactors []BorrowInterestFactor
 | 
				
			||||||
 | 
				
			|||||||
@ -6,14 +6,33 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Deposit defines an amount of coins deposited into a hard module account
 | 
					// Deposit defines an amount of coins deposited into a hard module account
 | 
				
			||||||
type Deposit struct {
 | 
					type Deposit struct {
 | 
				
			||||||
	Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
 | 
						Depositor sdk.AccAddress        `json:"depositor" yaml:"depositor"`
 | 
				
			||||||
	Amount    sdk.Coins      `json:"amount" yaml:"amount"`
 | 
						Amount    sdk.Coins             `json:"amount" yaml:"amount"`
 | 
				
			||||||
 | 
						Index     SupplyInterestFactors `json:"index" yaml:"index"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewDeposit returns a new deposit
 | 
					// NewDeposit returns a new deposit
 | 
				
			||||||
func NewDeposit(depositor sdk.AccAddress, amount sdk.Coins) Deposit {
 | 
					func NewDeposit(depositor sdk.AccAddress, amount sdk.Coins, indexes SupplyInterestFactors) Deposit {
 | 
				
			||||||
	return Deposit{
 | 
						return Deposit{
 | 
				
			||||||
		Depositor: depositor,
 | 
							Depositor: depositor,
 | 
				
			||||||
		Amount:    amount,
 | 
							Amount:    amount,
 | 
				
			||||||
 | 
							Index:     indexes,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SupplyInterestFactor defines an individual borrow interest factor
 | 
				
			||||||
 | 
					type SupplyInterestFactor struct {
 | 
				
			||||||
 | 
						Denom string  `json:"denom" yaml:"denom"`
 | 
				
			||||||
 | 
						Value sdk.Dec `json:"value" yaml:"value"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewSupplyInterestFactor returns a new SupplyInterestFactor instance
 | 
				
			||||||
 | 
					func NewSupplyInterestFactor(denom string, value sdk.Dec) SupplyInterestFactor {
 | 
				
			||||||
 | 
						return SupplyInterestFactor{
 | 
				
			||||||
 | 
							Denom: denom,
 | 
				
			||||||
 | 
							Value: value,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SupplyInterestFactors is a slice of SupplyInterestFactor, because Amino won't marshal maps
 | 
				
			||||||
 | 
					type SupplyInterestFactors []SupplyInterestFactor
 | 
				
			||||||
 | 
				
			|||||||
@ -67,4 +67,12 @@ var (
 | 
				
			|||||||
	ErrInsufficientCoins = sdkerrors.Register(ModuleName, 30, "unrecoverable state - insufficient coins")
 | 
						ErrInsufficientCoins = sdkerrors.Register(ModuleName, 30, "unrecoverable state - insufficient coins")
 | 
				
			||||||
	// ErrInsufficientBalanceForBorrow error for when the requested borrow exceeds user's balance
 | 
						// ErrInsufficientBalanceForBorrow error for when the requested borrow exceeds user's balance
 | 
				
			||||||
	ErrInsufficientBalanceForBorrow = sdkerrors.Register(ModuleName, 31, "insufficient balance")
 | 
						ErrInsufficientBalanceForBorrow = sdkerrors.Register(ModuleName, 31, "insufficient balance")
 | 
				
			||||||
 | 
						// ErrSuppliedCoinsNotFound error for when the total amount of supplied coins cannot be found
 | 
				
			||||||
 | 
						ErrSuppliedCoinsNotFound = sdkerrors.Register(ModuleName, 32, "no supplied coins found")
 | 
				
			||||||
 | 
						// ErrNegativeSuppliedCoins error for when substracting coins from the total supplied balance results in a negative amount
 | 
				
			||||||
 | 
						ErrNegativeSuppliedCoins = sdkerrors.Register(ModuleName, 33, "subtraction results in negative supplied amount")
 | 
				
			||||||
 | 
						// ErrInvalidWithdrawDenom error for when user attempts to withdraw a non-supplied coin type
 | 
				
			||||||
 | 
						ErrInvalidWithdrawDenom = sdkerrors.Register(ModuleName, 34, "no coins of this type deposited")
 | 
				
			||||||
 | 
						// ErrInvalidRepaymentDenom error for when user attempts to repay a non-borrowed coin type
 | 
				
			||||||
 | 
						ErrInvalidRepaymentDenom = sdkerrors.Register(ModuleName, 35, "no coins of this type borrowed")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -40,11 +40,13 @@ var (
 | 
				
			|||||||
	ClaimsKeyPrefix                   = []byte{0x04}
 | 
						ClaimsKeyPrefix                   = []byte{0x04}
 | 
				
			||||||
	BorrowsKeyPrefix                  = []byte{0x05}
 | 
						BorrowsKeyPrefix                  = []byte{0x05}
 | 
				
			||||||
	BorrowedCoinsPrefix               = []byte{0x06}
 | 
						BorrowedCoinsPrefix               = []byte{0x06}
 | 
				
			||||||
	MoneyMarketsPrefix                = []byte{0x07}
 | 
						SuppliedCoinsPrefix               = []byte{0x07}
 | 
				
			||||||
	PreviousAccrualTimePrefix         = []byte{0x08} // denom -> time
 | 
						MoneyMarketsPrefix                = []byte{0x08}
 | 
				
			||||||
	TotalReservesPrefix               = []byte{0x09} // denom -> sdk.Coin
 | 
						PreviousAccrualTimePrefix         = []byte{0x09} // denom -> time
 | 
				
			||||||
	InterestFactorPrefix              = []byte{0x10} // denom -> sdk.Dec
 | 
						TotalReservesPrefix               = []byte{0x10} // denom -> sdk.Coin
 | 
				
			||||||
	LtvIndexPrefix                    = []byte{0x11}
 | 
						BorrowInterestFactorPrefix        = []byte{0x11} // denom -> sdk.Dec
 | 
				
			||||||
 | 
						SupplyInterestFactorPrefix        = []byte{0x12} // denom -> sdk.Dec
 | 
				
			||||||
 | 
						LtvIndexPrefix                    = []byte{0x13}
 | 
				
			||||||
	sep                               = []byte(":")
 | 
						sep                               = []byte(":")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user