mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 04:37:26 +00:00 
			
		
		
		
	Hard: automatic liquidation by LTV index (#743)
* hotfix * update params, keys * liquidation by keeper * refactor GetPendingBorrowBalance * fix app build * elegant handling of denom arrays * auction deposit in lots * add error msg * update tests with new params * happy path liquidation test * update liquidator macc name * refactor reward % to money market params * refactor tests for updated params * compile: harvest liquidator module account * add liquidate msg * liquidation approach * update liquidations * return remaining deposit coins to original borrowr * check keeper reward before sending * introduce ValuationMap * convert Ints <> Decs * implement double-loop * ModuleAccountName * sort keys for deterministic auctions * test: correct auctions created * test: preset keeper coins * ensure deterministic iteration * test cases * update repay test * auction fixes, tests * LTV index * user actions sync interest and update ltv index * tests: all deposits must have money markets * reorder borrow logic * ltv index liquidation logic * test specific items in ltv index * index liquidation tests * update repay to spendable coins * revisions * remove address sort method * merge master test package
This commit is contained in:
		
							parent
							
								
									6c0890d5ff
								
							
						
					
					
						commit
						83a5f51c11
					
				@ -12,5 +12,6 @@ func BeginBlocker(ctx sdk.Context, k Keeper) {
 | 
			
		||||
		k.SetPreviousDelegationDistribution(ctx, ctx.BlockTime(), k.BondDenom(ctx))
 | 
			
		||||
	}
 | 
			
		||||
	k.ApplyInterestRateUpdates(ctx)
 | 
			
		||||
	k.AttemptIndexLiquidations(ctx)
 | 
			
		||||
	k.SetPreviousBlockTime(ctx, ctx.BlockTime())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get current stored LTV based on stored borrows/deposits
 | 
			
		||||
	prevLtv, shouldRemoveIndex, err := k.GetCurrentLTV(ctx, borrower)
 | 
			
		||||
	prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, borrower)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
@ -31,7 +31,7 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
 | 
			
		||||
	// If the user has an existing borrow, sync its outstanding interest
 | 
			
		||||
	_, found := k.GetBorrow(ctx, borrower)
 | 
			
		||||
	if found {
 | 
			
		||||
		k.SyncOustandingInterest(ctx, borrower)
 | 
			
		||||
		k.SyncOutstandingInterest(ctx, borrower)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Validate borrow amount within user and protocol limits
 | 
			
		||||
@ -92,8 +92,8 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SyncOustandingInterest updates the user's owed interest on newly borrowed coins to the latest global state
 | 
			
		||||
func (k Keeper) SyncOustandingInterest(ctx sdk.Context, addr sdk.AccAddress) {
 | 
			
		||||
// 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.
 | 
			
		||||
 | 
			
		||||
@ -283,6 +283,7 @@ func (suite *KeeperTestSuite) TestBorrow() {
 | 
			
		||||
					types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(100000000*BNB_CF), tc.args.loanToValueBNB), "bnb:usd", sdk.NewInt(BNB_CF), 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()),
 | 
			
		||||
					types.NewMoneyMarket("xyz", types.NewBorrowLimit(false, sdk.NewDec(1), tc.args.loanToValueBNB), "xyz:usd", sdk.NewInt(1), sdk.NewInt(1), 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
 | 
			
		||||
 | 
			
		||||
@ -268,6 +268,7 @@ func (suite *KeeperTestSuite) TestClaim() {
 | 
			
		||||
					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()),
 | 
			
		||||
				},
 | 
			
		||||
				0, // LTV counter
 | 
			
		||||
			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
			
		||||
			tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
 | 
			
		||||
			if tc.args.validatorVesting {
 | 
			
		||||
 | 
			
		||||
@ -11,12 +11,12 @@ import (
 | 
			
		||||
// Deposit deposit
 | 
			
		||||
func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, amount sdk.Coin) error {
 | 
			
		||||
	// Get current stored LTV based on stored borrows/deposits
 | 
			
		||||
	prevLtv, shouldRemoveIndex, err := k.GetCurrentLTV(ctx, depositor)
 | 
			
		||||
	prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	k.SyncOustandingInterest(ctx, depositor)
 | 
			
		||||
	k.SyncOutstandingInterest(ctx, depositor)
 | 
			
		||||
 | 
			
		||||
	err = k.ValidateDeposit(ctx, amount)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 | 
			
		||||
@ -115,6 +115,7 @@ func (suite *KeeperTestSuite) TestDeposit() {
 | 
			
		||||
					types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(1000000), 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()),
 | 
			
		||||
					types.NewMoneyMarket("btcb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "btcb:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_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
 | 
			
		||||
@ -303,6 +304,7 @@ func (suite *KeeperTestSuite) TestWithdraw() {
 | 
			
		||||
					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()),
 | 
			
		||||
				},
 | 
			
		||||
				0, // LTV counter
 | 
			
		||||
			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
			
		||||
			tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
 | 
			
		||||
			keeper := tApp.GetHarvestKeeper()
 | 
			
		||||
 | 
			
		||||
@ -666,6 +666,7 @@ func (suite *KeeperTestSuite) TestInterest() {
 | 
			
		||||
						tc.args.reserveFactor,     // Reserve Factor
 | 
			
		||||
						sdk.ZeroDec()),            // Keeper Reward Percentage
 | 
			
		||||
				},
 | 
			
		||||
				0, // LTV counter
 | 
			
		||||
			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
			
		||||
 | 
			
		||||
			// Pricefeed module genesis state
 | 
			
		||||
 | 
			
		||||
@ -376,7 +376,7 @@ func (k Keeper) RemoveFromLtvIndex(ctx sdk.Context, ltv sdk.Dec, borrower sdk.Ac
 | 
			
		||||
func (k Keeper) IterateLtvIndex(ctx sdk.Context, cutoffCount int,
 | 
			
		||||
	cb func(addr sdk.AccAddress) (stop bool)) {
 | 
			
		||||
	store := prefix.NewStore(ctx.KVStore(k.key), types.LtvIndexPrefix)
 | 
			
		||||
	iterator := store.Iterator(nil, nil)
 | 
			
		||||
	iterator := store.ReverseIterator(nil, nil)
 | 
			
		||||
	count := 0
 | 
			
		||||
 | 
			
		||||
	defer iterator.Close()
 | 
			
		||||
@ -394,8 +394,8 @@ func (k Keeper) IterateLtvIndex(ctx sdk.Context, cutoffCount int,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLtvIndexSlice returns the first 10 items in the LTV index from the store
 | 
			
		||||
func (k Keeper) GetLtvIndexSlice(ctx sdk.Context) (addrs []sdk.AccAddress) {
 | 
			
		||||
	k.IterateLtvIndex(ctx, 10, func(addr sdk.AccAddress) bool {
 | 
			
		||||
func (k Keeper) GetLtvIndexSlice(ctx sdk.Context, count int) (addrs []sdk.AccAddress) {
 | 
			
		||||
	k.IterateLtvIndex(ctx, count, func(addr sdk.AccAddress) bool {
 | 
			
		||||
		addrs = append(addrs, addr)
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package keeper_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
@ -160,7 +161,7 @@ func (suite *KeeperTestSuite) TestGetSetDeleteInterestRateModel() {
 | 
			
		||||
	denom := "test"
 | 
			
		||||
	model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))
 | 
			
		||||
	borrowLimit := types.NewBorrowLimit(false, sdk.MustNewDecFromStr("0.2"), sdk.MustNewDecFromStr("0.5"))
 | 
			
		||||
	moneyMarket := types.NewMoneyMarket(denom, borrowLimit, denom+":usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), model, sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec())
 | 
			
		||||
	moneyMarket := types.NewMoneyMarket(denom, borrowLimit, denom+":usd", sdk.NewInt(1000000), sdk.NewInt(1000000000), model, sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec())
 | 
			
		||||
 | 
			
		||||
	_, f := suite.keeper.GetMoneyMarket(suite.ctx, denom)
 | 
			
		||||
	suite.Require().False(f)
 | 
			
		||||
@ -186,7 +187,7 @@ func (suite *KeeperTestSuite) TestIterateInterestRateModels() {
 | 
			
		||||
		denom := testDenom + strconv.Itoa(i)
 | 
			
		||||
		model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10"))
 | 
			
		||||
		borrowLimit := types.NewBorrowLimit(false, sdk.MustNewDecFromStr("0.2"), sdk.MustNewDecFromStr("0.5"))
 | 
			
		||||
		moneyMarket := types.NewMoneyMarket(denom, borrowLimit, denom+":usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), model, sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec())
 | 
			
		||||
		moneyMarket := types.NewMoneyMarket(denom, borrowLimit, denom+":usd", sdk.NewInt(1000000), sdk.NewInt(1000000000), model, sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec())
 | 
			
		||||
 | 
			
		||||
		// Store money market in the module's store
 | 
			
		||||
		suite.Require().NotPanics(func() { suite.keeper.SetMoneyMarket(suite.ctx, denom, moneyMarket) })
 | 
			
		||||
@ -210,7 +211,7 @@ func (suite *KeeperTestSuite) TestIterateInterestRateModels() {
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) TestSetDeleteLtvIndex() {
 | 
			
		||||
	// LTV index should have 0 items
 | 
			
		||||
	firstAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx)
 | 
			
		||||
	firstAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx, 10)
 | 
			
		||||
	suite.Require().Equal(0, len(firstAddrs))
 | 
			
		||||
 | 
			
		||||
	// Add an item to the LTV index
 | 
			
		||||
@ -219,7 +220,7 @@ func (suite *KeeperTestSuite) TestSetDeleteLtvIndex() {
 | 
			
		||||
	suite.Require().NotPanics(func() { suite.keeper.InsertIntoLtvIndex(suite.ctx, ltv, addr) })
 | 
			
		||||
 | 
			
		||||
	// LTV index should have 1 item
 | 
			
		||||
	secondAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx)
 | 
			
		||||
	secondAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx, 10)
 | 
			
		||||
	suite.Require().Equal(1, len(secondAddrs))
 | 
			
		||||
 | 
			
		||||
	// Attempt to remove invalid item from LTV index
 | 
			
		||||
@ -227,14 +228,14 @@ func (suite *KeeperTestSuite) TestSetDeleteLtvIndex() {
 | 
			
		||||
	suite.Require().NotPanics(func() { suite.keeper.RemoveFromLtvIndex(suite.ctx, fakeLtv, addr) })
 | 
			
		||||
 | 
			
		||||
	// LTV index should still have 1 item
 | 
			
		||||
	thirdAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx)
 | 
			
		||||
	thirdAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx, 10)
 | 
			
		||||
	suite.Require().Equal(1, len(thirdAddrs))
 | 
			
		||||
 | 
			
		||||
	// Attempt to remove valid item from LTV index
 | 
			
		||||
	suite.Require().NotPanics(func() { suite.keeper.RemoveFromLtvIndex(suite.ctx, ltv, addr) })
 | 
			
		||||
 | 
			
		||||
	// LTV index should still have 0 items
 | 
			
		||||
	fourthAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx)
 | 
			
		||||
	fourthAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx, 10)
 | 
			
		||||
	suite.Require().Equal(0, len(fourthAddrs))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -252,8 +253,23 @@ func (suite *KeeperTestSuite) TestIterateLtvIndex() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Only the first 10 addresses should be returned
 | 
			
		||||
	sliceAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx)
 | 
			
		||||
	suite.Require().Equal(setAddrs[:10], sliceAddrs)
 | 
			
		||||
	sliceAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx, 10)
 | 
			
		||||
	suite.Require().Equal(addressSort(setAddrs[10:20]), addressSort(sliceAddrs))
 | 
			
		||||
 | 
			
		||||
	// Insert an additional item into the LTV index that should be returned in the first 10 elements
 | 
			
		||||
	addr := sdk.AccAddress("test" + fmt.Sprint(21))
 | 
			
		||||
	ltv := sdk.OneDec().Add(sdk.MustNewDecFromStr("15").Quo(sdk.NewDec(10)))
 | 
			
		||||
	suite.Require().NotPanics(func() { suite.keeper.InsertIntoLtvIndex(suite.ctx, ltv, addr) })
 | 
			
		||||
 | 
			
		||||
	// Fetch the updated LTV index
 | 
			
		||||
	updatedSliceAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx, 10)
 | 
			
		||||
	sawAddr := false
 | 
			
		||||
	for _, updatedSliceAddr := range updatedSliceAddrs {
 | 
			
		||||
		if updatedSliceAddr.Equals(addr) {
 | 
			
		||||
			sawAddr = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	suite.Require().Equal(true, sawAddr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) getAccount(addr sdk.AccAddress) authexported.Account {
 | 
			
		||||
@ -276,6 +292,21 @@ func (suite *KeeperTestSuite) getModuleAccountAtCtx(name string, ctx sdk.Context
 | 
			
		||||
	return sk.GetModuleAccount(ctx, name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addressSort(addrs []sdk.AccAddress) (sortedAddrs []sdk.AccAddress) {
 | 
			
		||||
	addrStrs := []string{}
 | 
			
		||||
	for _, addr := range addrs {
 | 
			
		||||
		addrStrs = append(addrStrs, addr.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Strings(addrStrs)
 | 
			
		||||
 | 
			
		||||
	for _, addrStr := range addrStrs {
 | 
			
		||||
		addr, _ := sdk.AccAddressFromBech32(addrStr)
 | 
			
		||||
		sortedAddrs = append(sortedAddrs, addr)
 | 
			
		||||
	}
 | 
			
		||||
	return sortedAddrs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestKeeperTestSuite(t *testing.T) {
 | 
			
		||||
	suite.Run(t, new(KeeperTestSuite))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
package keeper
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 | 
			
		||||
 | 
			
		||||
@ -16,23 +18,31 @@ type LiqData struct {
 | 
			
		||||
 | 
			
		||||
// AttemptIndexLiquidations attempts to liquidate the lowest LTV borrows
 | 
			
		||||
func (k Keeper) AttemptIndexLiquidations(ctx sdk.Context) error {
 | 
			
		||||
	// use moneyMarketCache := map[string]types.MoneyMarket{}
 | 
			
		||||
 | 
			
		||||
	// Iterate over index
 | 
			
		||||
	//		Get borrower's address
 | 
			
		||||
	//		Use borrower's address to fetch borrow object
 | 
			
		||||
	//		Calculate outstanding interest and add to borrow balances
 | 
			
		||||
	//		Use current asset prices from pricefeed to calculate current LTV for each asset
 | 
			
		||||
	//		If LTV of any asset is over the max, liquidate it by
 | 
			
		||||
	//			Sending coins to auction module
 | 
			
		||||
	//			(?) Removing borrow from the store
 | 
			
		||||
	//			(?) Removing borrow LTV from LTV index
 | 
			
		||||
	params := k.GetParams(ctx)
 | 
			
		||||
	borrowers := k.GetLtvIndexSlice(ctx, params.CheckLtvIndexCount)
 | 
			
		||||
 | 
			
		||||
	for _, borrower := range borrowers {
 | 
			
		||||
		_, err := k.AttemptKeeperLiquidation(ctx, sdk.AccAddress(types.LiquidatorAccount), borrower)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if !errors.Is(err, types.ErrBorrowNotLiquidatable) {
 | 
			
		||||
				panic(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AttemptKeeperLiquidation enables a keeper to liquidate an individual borrower's position
 | 
			
		||||
func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, borrower sdk.AccAddress) error {
 | 
			
		||||
func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, borrower sdk.AccAddress) (bool, error) {
 | 
			
		||||
	prevLtv, shouldInsertIndex, err := k.GetStoreLTV(ctx, borrower)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	k.SyncOutstandingInterest(ctx, borrower)
 | 
			
		||||
 | 
			
		||||
	k.UpdateItemInLtvIndex(ctx, prevLtv, shouldInsertIndex, borrower)
 | 
			
		||||
 | 
			
		||||
	// Fetch deposits and parse coin denoms
 | 
			
		||||
	deposits := k.GetDepositsByUser(ctx, borrower)
 | 
			
		||||
	depositDenoms := []string{}
 | 
			
		||||
@ -41,8 +51,11 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fetch borrow balances and parse coin denoms
 | 
			
		||||
	borrowBalances := k.GetBorrowBalance(ctx, borrower)
 | 
			
		||||
	borrowDenoms := getDenoms(borrowBalances)
 | 
			
		||||
	borrows, found := k.GetBorrow(ctx, borrower)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return false, types.ErrBorrowNotFound
 | 
			
		||||
	}
 | 
			
		||||
	borrowDenoms := getDenoms(borrows.Amount)
 | 
			
		||||
 | 
			
		||||
	liqMap := make(map[string]LiqData)
 | 
			
		||||
 | 
			
		||||
@ -51,12 +64,12 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
 | 
			
		||||
	for _, denom := range denoms {
 | 
			
		||||
		mm, found := k.GetMoneyMarket(ctx, denom)
 | 
			
		||||
		if !found {
 | 
			
		||||
			return sdkerrors.Wrapf(types.ErrMarketNotFound, "no market found for denom %s", denom)
 | 
			
		||||
			return false, sdkerrors.Wrapf(types.ErrMarketNotFound, "no market found for denom %s", denom)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		priceData, err := k.pricefeedKeeper.GetCurrentPrice(ctx, mm.SpotMarketID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		liqMap[denom] = LiqData{priceData.Price, mm.BorrowLimit.LoanToValue, mm.ConversionFactor}
 | 
			
		||||
@ -73,7 +86,7 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	totalBorrowedUSDAmount := sdk.ZeroDec()
 | 
			
		||||
	for _, coin := range borrowBalances {
 | 
			
		||||
	for _, coin := range borrows.Amount {
 | 
			
		||||
		lData := liqMap[coin.Denom]
 | 
			
		||||
		usdValue := sdk.NewDecFromInt(coin.Amount).Quo(sdk.NewDecFromInt(lData.conversionFactor)).Mul(lData.price)
 | 
			
		||||
		totalBorrowedUSDAmount = totalBorrowedUSDAmount.Add(usdValue)
 | 
			
		||||
@ -81,23 +94,29 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress,
 | 
			
		||||
 | 
			
		||||
	// Validate that the proposed borrow's USD value is within user's borrowable limit
 | 
			
		||||
	if totalBorrowedUSDAmount.LTE(totalBorrowableUSDAmount) {
 | 
			
		||||
		return sdkerrors.Wrapf(types.ErrBorrowNotLiquidatable, "borrowed %s <= borrowable %s", totalBorrowedUSDAmount, totalBorrowableUSDAmount)
 | 
			
		||||
		return false, sdkerrors.Wrapf(types.ErrBorrowNotLiquidatable, "borrowed %s <= borrowable %s", totalBorrowedUSDAmount, totalBorrowableUSDAmount)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sending coins to auction module with keeper address getting % of the profits
 | 
			
		||||
	borrow, _ := k.GetBorrow(ctx, borrower)
 | 
			
		||||
	err := k.SeizeDeposits(ctx, keeper, liqMap, deposits, borrowBalances, depositDenoms, borrowDenoms)
 | 
			
		||||
	// Seize deposits and auciton them off
 | 
			
		||||
	err = k.SeizeDeposits(ctx, keeper, liqMap, deposits, borrows.Amount, depositDenoms, borrowDenoms)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	currLtv, _, err := k.GetStoreLTV(ctx, borrower)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	k.RemoveFromLtvIndex(ctx, currLtv, borrower)
 | 
			
		||||
 | 
			
		||||
	borrow, _ := k.GetBorrow(ctx, borrower)
 | 
			
		||||
	k.DeleteBorrow(ctx, borrow)
 | 
			
		||||
 | 
			
		||||
	for _, oldDeposit := range deposits {
 | 
			
		||||
		k.DeleteDeposit(ctx, oldDeposit)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
	return true, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SeizeDeposits seizes a list of deposits and sends them to auction
 | 
			
		||||
@ -111,16 +130,20 @@ func (k Keeper) SeizeDeposits(ctx sdk.Context, keeper sdk.AccAddress, liqMap map
 | 
			
		||||
		amount := deposit.Amount.Amount
 | 
			
		||||
		mm, _ := k.GetMoneyMarket(ctx, denom)
 | 
			
		||||
 | 
			
		||||
		keeperReward := mm.KeeperRewardPercentage.MulInt(amount).TruncateInt()
 | 
			
		||||
		if keeperReward.GT(sdk.ZeroInt()) {
 | 
			
		||||
			// Send keeper their reward
 | 
			
		||||
			keeperCoin := sdk.NewCoin(denom, keeperReward)
 | 
			
		||||
			err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, keeper, sdk.NewCoins(keeperCoin))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
		// No rewards for anyone if liquidated by LTV index
 | 
			
		||||
		if !keeper.Equals(sdk.AccAddress(types.LiquidatorAccount)) {
 | 
			
		||||
			keeperReward := mm.KeeperRewardPercentage.MulInt(amount).TruncateInt()
 | 
			
		||||
			if keeperReward.GT(sdk.ZeroInt()) {
 | 
			
		||||
				// Send keeper their reward
 | 
			
		||||
				keeperCoin := sdk.NewCoin(denom, keeperReward)
 | 
			
		||||
				err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, keeper, sdk.NewCoins(keeperCoin))
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
				amount = amount.Sub(keeperReward)
 | 
			
		||||
			}
 | 
			
		||||
			amount = amount.Sub(keeperReward)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Add remaining deposit coin to aucDeposits
 | 
			
		||||
		aucDeposits = aucDeposits.Add(sdk.NewCoin(denom, amount))
 | 
			
		||||
	}
 | 
			
		||||
@ -253,8 +276,9 @@ func (k Keeper) StartAuctions(ctx sdk.Context, borrower sdk.AccAddress, borrows,
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCurrentLTV calculates the user's current LTV based on their deposits/borrows in the store
 | 
			
		||||
func (k Keeper) GetCurrentLTV(ctx sdk.Context, addr sdk.AccAddress) (sdk.Dec, bool, error) {
 | 
			
		||||
// GetStoreLTV calculates the user's current LTV based on their deposits/borrows in the store
 | 
			
		||||
// and does not include any outsanding interest.
 | 
			
		||||
func (k Keeper) GetStoreLTV(ctx sdk.Context, addr sdk.AccAddress) (sdk.Dec, bool, error) {
 | 
			
		||||
	// Fetch deposits and parse coin denoms
 | 
			
		||||
	deposits := k.GetDepositsByUser(ctx, addr)
 | 
			
		||||
	depositDenoms := []string{}
 | 
			
		||||
@ -263,11 +287,11 @@ func (k Keeper) GetCurrentLTV(ctx sdk.Context, addr sdk.AccAddress) (sdk.Dec, bo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fetch borrow balances and parse coin denoms
 | 
			
		||||
	borrowBalances := k.GetBorrowBalance(ctx, addr)
 | 
			
		||||
	if borrowBalances.IsZero() {
 | 
			
		||||
	borrows, found := k.GetBorrow(ctx, addr)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return sdk.ZeroDec(), false, nil
 | 
			
		||||
	}
 | 
			
		||||
	borrowDenoms := getDenoms(borrowBalances)
 | 
			
		||||
	borrowDenoms := getDenoms(borrows.Amount)
 | 
			
		||||
 | 
			
		||||
	liqMap := make(map[string]LiqData)
 | 
			
		||||
 | 
			
		||||
@ -297,7 +321,7 @@ func (k Keeper) GetCurrentLTV(ctx sdk.Context, addr sdk.AccAddress) (sdk.Dec, bo
 | 
			
		||||
 | 
			
		||||
	// Build valuation map to hold borrow coin USD valuations
 | 
			
		||||
	borrowCoinValues := types.NewValuationMap()
 | 
			
		||||
	for _, bCoin := range borrowBalances {
 | 
			
		||||
	for _, bCoin := range borrows.Amount {
 | 
			
		||||
		bData := liqMap[bCoin.Denom]
 | 
			
		||||
		bCoinUsdValue := sdk.NewDecFromInt(bCoin.Amount).Quo(sdk.NewDecFromInt(bData.conversionFactor)).Mul(bData.price)
 | 
			
		||||
		borrowCoinValues.Increment(bCoin.Denom, bCoinUsdValue)
 | 
			
		||||
@ -316,7 +340,7 @@ func (k Keeper) GetCurrentLTV(ctx sdk.Context, addr sdk.AccAddress) (sdk.Dec, bo
 | 
			
		||||
// UpdateItemInLtvIndex updates the key a borrower's address is stored under in the LTV index
 | 
			
		||||
func (k Keeper) UpdateItemInLtvIndex(ctx sdk.Context, prevLtv sdk.Dec,
 | 
			
		||||
	shouldRemoveIndex bool, borrower sdk.AccAddress) error {
 | 
			
		||||
	currLtv, shouldInsertIndex, err := k.GetCurrentLTV(ctx, borrower)
 | 
			
		||||
	currLtv, shouldInsertIndex, err := k.GetStoreLTV(ctx, borrower)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,755 @@ import (
 | 
			
		||||
	"github.com/kava-labs/kava/x/pricefeed"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) TestIndexLiquidation() {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		borrower              sdk.AccAddress
 | 
			
		||||
		initialModuleCoins    sdk.Coins
 | 
			
		||||
		initialBorrowerCoins  sdk.Coins
 | 
			
		||||
		depositCoins          []sdk.Coin
 | 
			
		||||
		borrowCoins           sdk.Coins
 | 
			
		||||
		beginBlockerTime      int64
 | 
			
		||||
		ltvIndexCount         int
 | 
			
		||||
		expectedBorrowerCoins sdk.Coins         // additional coins (if any) the borrower address should have after successfully liquidating position
 | 
			
		||||
		expectedAuctions      auctypes.Auctions // the auctions we should expect to find have been started
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type errArgs struct {
 | 
			
		||||
		expectLiquidate 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("randomaddr")))
 | 
			
		||||
 | 
			
		||||
	// Set up auction constants
 | 
			
		||||
	layout := "2006-01-02T15:04:05.000Z"
 | 
			
		||||
	endTimeStr := "9000-01-01T00:00:00.000Z"
 | 
			
		||||
	endTime, _ := time.Parse(layout, endTimeStr)
 | 
			
		||||
 | 
			
		||||
	lotReturns, _ := auctypes.NewWeightedAddresses([]sdk.AccAddress{borrower}, []sdk.Int{sdk.NewInt(100)})
 | 
			
		||||
 | 
			
		||||
	testCases := []liqTest{
 | 
			
		||||
		{
 | 
			
		||||
			"valid: LTV index liquidates borrow",
 | 
			
		||||
			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))),
 | 
			
		||||
				depositCoins:          []sdk.Coin{sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))},
 | 
			
		||||
				borrowCoins:           sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))),
 | 
			
		||||
				beginBlockerTime:      oneMonthInSeconds,
 | 
			
		||||
				ltvIndexCount:         int(10),
 | 
			
		||||
				expectedBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(98*KAVA_CF))), // initial - deposit + borrow + liquidation leftovers
 | 
			
		||||
				expectedAuctions: auctypes.Auctions{
 | 
			
		||||
					auctypes.CollateralAuction{
 | 
			
		||||
						BaseAuction: auctypes.BaseAuction{
 | 
			
		||||
							ID:              1,
 | 
			
		||||
							Initiator:       "harvest_liquidator",
 | 
			
		||||
							Lot:             sdk.NewInt64Coin("ukava", 10*KAVA_CF),
 | 
			
		||||
							Bidder:          nil,
 | 
			
		||||
							Bid:             sdk.NewInt64Coin("ukava", 0),
 | 
			
		||||
							HasReceivedBids: false,
 | 
			
		||||
							EndTime:         endTime,
 | 
			
		||||
							MaxEndTime:      endTime,
 | 
			
		||||
						},
 | 
			
		||||
						CorrespondingDebt: sdk.NewInt64Coin("debt", 0),
 | 
			
		||||
						MaxBid:            sdk.NewInt64Coin("ukava", 8004766),
 | 
			
		||||
						LotReturns:        lotReturns,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			errArgs{
 | 
			
		||||
				expectLiquidate: true,
 | 
			
		||||
				contains:        "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"invalid: borrow not over limit, LTV index does not liquidate",
 | 
			
		||||
			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))),
 | 
			
		||||
				depositCoins:          []sdk.Coin{sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))},
 | 
			
		||||
				borrowCoins:           sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(7*KAVA_CF))),
 | 
			
		||||
				beginBlockerTime:      oneMonthInSeconds,
 | 
			
		||||
				ltvIndexCount:         int(10),
 | 
			
		||||
				expectedBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(97*KAVA_CF))), // initial - deposit + borrow
 | 
			
		||||
				expectedAuctions:      auctypes.Auctions{},
 | 
			
		||||
			},
 | 
			
		||||
			errArgs{
 | 
			
		||||
				expectLiquidate: false,
 | 
			
		||||
				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.borrower},
 | 
			
		||||
				[]sdk.Coins{tc.args.initialBorrowerCoins},
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
			// Harvest module genesis state
 | 
			
		||||
			harvestGS := types.NewGenesisState(types.NewParams(
 | 
			
		||||
				true,
 | 
			
		||||
				types.DistributionSchedules{
 | 
			
		||||
					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.NewDistributionSchedule(true, "usdc", 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, "usdt", 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, "dai", 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, "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.NewDistributionSchedule(true, "btc", 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("usdx",
 | 
			
		||||
						types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit
 | 
			
		||||
						"usdx:usd",                     // Market ID
 | 
			
		||||
						sdk.NewInt(KAVA_CF),            // Conversion Factor
 | 
			
		||||
						sdk.NewInt(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
					types.NewMoneyMarket("usdt",
 | 
			
		||||
						types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit
 | 
			
		||||
						"usdt:usd",                     // Market ID
 | 
			
		||||
						sdk.NewInt(KAVA_CF),            // Conversion Factor
 | 
			
		||||
						sdk.NewInt(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
					types.NewMoneyMarket("usdc",
 | 
			
		||||
						types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit
 | 
			
		||||
						"usdc:usd",                     // Market ID
 | 
			
		||||
						sdk.NewInt(KAVA_CF),            // Conversion Factor
 | 
			
		||||
						sdk.NewInt(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
					types.NewMoneyMarket("dai",
 | 
			
		||||
						types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit
 | 
			
		||||
						"dai:usd",                      // Market ID
 | 
			
		||||
						sdk.NewInt(KAVA_CF),            // Conversion Factor
 | 
			
		||||
						sdk.NewInt(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
					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(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
					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(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
					types.NewMoneyMarket("btc",
 | 
			
		||||
						types.NewBorrowLimit(false, sdk.NewDec(100000000*BTCB_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
 | 
			
		||||
						"btc:usd",                      // Market ID
 | 
			
		||||
						sdk.NewInt(BTCB_CF),            // Conversion Factor
 | 
			
		||||
						sdk.NewInt(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
				},
 | 
			
		||||
				tc.args.ltvIndexCount, // 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: "usdt:usd", BaseAsset: "usdt", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
			
		||||
						{MarketID: "usdc:usd", BaseAsset: "usdc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true},
 | 
			
		||||
						{MarketID: "dai:usd", BaseAsset: "dai", 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},
 | 
			
		||||
						{MarketID: "btc:usd", BaseAsset: "btc", 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:      "usdt:usd",
 | 
			
		||||
						OracleAddress: sdk.AccAddress{},
 | 
			
		||||
						Price:         sdk.MustNewDecFromStr("1.00"),
 | 
			
		||||
						Expiry:        time.Now().Add(100 * time.Hour),
 | 
			
		||||
					},
 | 
			
		||||
					{
 | 
			
		||||
						MarketID:      "usdc:usd",
 | 
			
		||||
						OracleAddress: sdk.AccAddress{},
 | 
			
		||||
						Price:         sdk.MustNewDecFromStr("1.00"),
 | 
			
		||||
						Expiry:        time.Now().Add(100 * time.Hour),
 | 
			
		||||
					},
 | 
			
		||||
					{
 | 
			
		||||
						MarketID:      "dai: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),
 | 
			
		||||
					},
 | 
			
		||||
					{
 | 
			
		||||
						MarketID:      "btc:usd",
 | 
			
		||||
						OracleAddress: sdk.AccAddress{},
 | 
			
		||||
						Price:         sdk.MustNewDecFromStr("100.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.GetHarvestKeeper()
 | 
			
		||||
			suite.app = tApp
 | 
			
		||||
			suite.ctx = ctx
 | 
			
		||||
			suite.keeper = keeper
 | 
			
		||||
			suite.auctionKeeper = auctionKeeper
 | 
			
		||||
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			// Run begin blocker to set up state
 | 
			
		||||
			harvest.BeginBlocker(suite.ctx, suite.keeper)
 | 
			
		||||
 | 
			
		||||
			// Deposit coins
 | 
			
		||||
			for _, coin := range tc.args.depositCoins {
 | 
			
		||||
				err = suite.keeper.Deposit(suite.ctx, tc.args.borrower, coin)
 | 
			
		||||
				suite.Require().NoError(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Borrow coins
 | 
			
		||||
			err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
 | 
			
		||||
			suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
			// Check borrow exists before liquidation
 | 
			
		||||
			_, foundBorrowBefore := suite.keeper.GetBorrow(suite.ctx, tc.args.borrower)
 | 
			
		||||
			suite.Require().True(foundBorrowBefore)
 | 
			
		||||
 | 
			
		||||
			// Check that the user's deposits exist before liquidation
 | 
			
		||||
			for _, coin := range tc.args.depositCoins {
 | 
			
		||||
				_, foundDepositBefore := suite.keeper.GetDeposit(suite.ctx, tc.args.borrower, coin.Denom)
 | 
			
		||||
				suite.Require().True(foundDepositBefore)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Liquidate the borrow by running begin blocker
 | 
			
		||||
			runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.beginBlockerTime), 0)
 | 
			
		||||
			liqCtx := suite.ctx.WithBlockTime(runAtTime)
 | 
			
		||||
			harvest.BeginBlocker(liqCtx, suite.keeper)
 | 
			
		||||
 | 
			
		||||
			if tc.errArgs.expectLiquidate {
 | 
			
		||||
				// Check borrow does not exist after liquidation
 | 
			
		||||
				_, foundBorrowAfter := suite.keeper.GetBorrow(liqCtx, tc.args.borrower)
 | 
			
		||||
				suite.Require().False(foundBorrowAfter)
 | 
			
		||||
				// Check deposits do not exist after liquidation
 | 
			
		||||
				for _, coin := range tc.args.depositCoins {
 | 
			
		||||
					_, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, tc.args.borrower, coin.Denom)
 | 
			
		||||
					suite.Require().False(foundDepositAfter)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Check that borrower's balance contains the expected coins
 | 
			
		||||
				accBorrower := suite.getAccountAtCtx(tc.args.borrower, liqCtx)
 | 
			
		||||
				suite.Require().Equal(tc.args.expectedBorrowerCoins, accBorrower.GetCoins())
 | 
			
		||||
 | 
			
		||||
				// Check that the expected auctions have been created
 | 
			
		||||
				auctions := suite.auctionKeeper.GetAllAuctions(liqCtx)
 | 
			
		||||
				suite.Require().True(len(auctions) > 0)
 | 
			
		||||
				suite.Require().Equal(tc.args.expectedAuctions, auctions)
 | 
			
		||||
			} else {
 | 
			
		||||
				// Check that the user's borrow exists
 | 
			
		||||
				_, foundBorrowAfter := suite.keeper.GetBorrow(liqCtx, tc.args.borrower)
 | 
			
		||||
				suite.Require().True(foundBorrowAfter)
 | 
			
		||||
				// Check that the user's deposits exist
 | 
			
		||||
				for _, coin := range tc.args.depositCoins {
 | 
			
		||||
					_, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, tc.args.borrower, coin.Denom)
 | 
			
		||||
					suite.Require().True(foundDepositAfter)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Check that no auctions have been created
 | 
			
		||||
				auctions := suite.auctionKeeper.GetAllAuctions(liqCtx)
 | 
			
		||||
				suite.Require().True(len(auctions) == 0)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) TestFullIndexLiquidation() {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		borrower              sdk.AccAddress
 | 
			
		||||
		otherBorrowers        []sdk.AccAddress
 | 
			
		||||
		initialModuleCoins    sdk.Coins
 | 
			
		||||
		initialBorrowerCoins  sdk.Coins
 | 
			
		||||
		depositCoins          []sdk.Coin
 | 
			
		||||
		borrowCoins           sdk.Coins
 | 
			
		||||
		otherBorrowCoins      sdk.Coins
 | 
			
		||||
		beginBlockerTime      int64
 | 
			
		||||
		ltvIndexCount         int
 | 
			
		||||
		expectedBorrowerCoins sdk.Coins         // additional coins (if any) the borrower address should have after successfully liquidating position
 | 
			
		||||
		expectedAuctions      auctypes.Auctions // the auctions we should expect to find have been started
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type errArgs struct {
 | 
			
		||||
		expectLiquidate               bool
 | 
			
		||||
		expectLiquidateOtherBorrowers 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("randomaddr")))
 | 
			
		||||
	otherBorrower1 := sdk.AccAddress(crypto.AddressHash([]byte("AotherBorrower1")))
 | 
			
		||||
	otherBorrower2 := sdk.AccAddress(crypto.AddressHash([]byte("BotherBorrower2")))
 | 
			
		||||
	otherBorrower3 := sdk.AccAddress(crypto.AddressHash([]byte("CotherBorrower3")))
 | 
			
		||||
	otherBorrower4 := sdk.AccAddress(crypto.AddressHash([]byte("DotherBorrower4")))
 | 
			
		||||
	otherBorrower5 := sdk.AccAddress(crypto.AddressHash([]byte("EotherBorrower5")))
 | 
			
		||||
	otherBorrower6 := sdk.AccAddress(crypto.AddressHash([]byte("FotherBorrower6")))
 | 
			
		||||
	otherBorrower7 := sdk.AccAddress(crypto.AddressHash([]byte("GotherBorrower7")))
 | 
			
		||||
	otherBorrower8 := sdk.AccAddress(crypto.AddressHash([]byte("HotherBorrower8")))
 | 
			
		||||
	otherBorrower9 := sdk.AccAddress(crypto.AddressHash([]byte("IotherBorrower9")))
 | 
			
		||||
	otherBorrower10 := sdk.AccAddress(crypto.AddressHash([]byte("JotherBorrower10")))
 | 
			
		||||
 | 
			
		||||
	// Set up auction constants
 | 
			
		||||
	layout := "2006-01-02T15:04:05.000Z"
 | 
			
		||||
	endTimeStr := "9000-01-01T00:00:00.000Z"
 | 
			
		||||
	endTime, _ := time.Parse(layout, endTimeStr)
 | 
			
		||||
 | 
			
		||||
	lotReturns, _ := auctypes.NewWeightedAddresses([]sdk.AccAddress{borrower}, []sdk.Int{sdk.NewInt(100)})
 | 
			
		||||
	otherBorrower1LotReturns, _ := auctypes.NewWeightedAddresses([]sdk.AccAddress{otherBorrower1}, []sdk.Int{sdk.NewInt(100)})
 | 
			
		||||
	otherBorrower2LotReturns, _ := auctypes.NewWeightedAddresses([]sdk.AccAddress{otherBorrower2}, []sdk.Int{sdk.NewInt(100)})
 | 
			
		||||
	otherBorrower3LotReturns, _ := auctypes.NewWeightedAddresses([]sdk.AccAddress{otherBorrower3}, []sdk.Int{sdk.NewInt(100)})
 | 
			
		||||
 | 
			
		||||
	testCases := []liqTest{
 | 
			
		||||
		{
 | 
			
		||||
			"valid: LTV index only liquidates positions over LTV",
 | 
			
		||||
			args{
 | 
			
		||||
				borrower:              borrower,
 | 
			
		||||
				otherBorrowers:        []sdk.AccAddress{otherBorrower1, otherBorrower2, otherBorrower3},
 | 
			
		||||
				initialModuleCoins:    sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
			
		||||
				initialBorrowerCoins:  sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
			
		||||
				depositCoins:          []sdk.Coin{sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))},
 | 
			
		||||
				borrowCoins:           sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))),
 | 
			
		||||
				otherBorrowCoins:      sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(7*KAVA_CF))),
 | 
			
		||||
				beginBlockerTime:      oneMonthInSeconds,
 | 
			
		||||
				ltvIndexCount:         int(10),
 | 
			
		||||
				expectedBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(98*KAVA_CF))), // initial - deposit + borrow + liquidation leftovers
 | 
			
		||||
				expectedAuctions: auctypes.Auctions{
 | 
			
		||||
					auctypes.CollateralAuction{
 | 
			
		||||
						BaseAuction: auctypes.BaseAuction{
 | 
			
		||||
							ID:              1,
 | 
			
		||||
							Initiator:       "harvest_liquidator",
 | 
			
		||||
							Lot:             sdk.NewInt64Coin("ukava", 10*KAVA_CF),
 | 
			
		||||
							Bidder:          nil,
 | 
			
		||||
							Bid:             sdk.NewInt64Coin("ukava", 0),
 | 
			
		||||
							HasReceivedBids: false,
 | 
			
		||||
							EndTime:         endTime,
 | 
			
		||||
							MaxEndTime:      endTime,
 | 
			
		||||
						},
 | 
			
		||||
						CorrespondingDebt: sdk.NewInt64Coin("debt", 0),
 | 
			
		||||
						MaxBid:            sdk.NewInt64Coin("ukava", 8013492), // TODO: why isn't this 8004766
 | 
			
		||||
						LotReturns:        lotReturns,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			errArgs{
 | 
			
		||||
				expectLiquidate:               true,
 | 
			
		||||
				expectLiquidateOtherBorrowers: false,
 | 
			
		||||
				contains:                      "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"valid: LTV liquidates multiple positions over LTV",
 | 
			
		||||
			args{
 | 
			
		||||
				borrower:              borrower,
 | 
			
		||||
				otherBorrowers:        []sdk.AccAddress{otherBorrower1, otherBorrower2, otherBorrower3},
 | 
			
		||||
				initialModuleCoins:    sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
			
		||||
				initialBorrowerCoins:  sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
			
		||||
				depositCoins:          []sdk.Coin{sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))},
 | 
			
		||||
				borrowCoins:           sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))),
 | 
			
		||||
				otherBorrowCoins:      sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))),
 | 
			
		||||
				beginBlockerTime:      oneMonthInSeconds,
 | 
			
		||||
				ltvIndexCount:         int(10),
 | 
			
		||||
				expectedBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(98*KAVA_CF))), // initial - deposit + borrow + liquidation leftovers
 | 
			
		||||
				expectedAuctions: auctypes.Auctions{
 | 
			
		||||
					auctypes.CollateralAuction{
 | 
			
		||||
						BaseAuction: auctypes.BaseAuction{
 | 
			
		||||
							ID:              1,
 | 
			
		||||
							Initiator:       "harvest_liquidator",
 | 
			
		||||
							Lot:             sdk.NewInt64Coin("ukava", 10*KAVA_CF),
 | 
			
		||||
							Bidder:          nil,
 | 
			
		||||
							Bid:             sdk.NewInt64Coin("ukava", 0),
 | 
			
		||||
							HasReceivedBids: false,
 | 
			
		||||
							EndTime:         endTime,
 | 
			
		||||
							MaxEndTime:      endTime,
 | 
			
		||||
						},
 | 
			
		||||
						CorrespondingDebt: sdk.NewInt64Coin("debt", 0),
 | 
			
		||||
						MaxBid:            sdk.NewInt64Coin("ukava", 8014873), // TODO: Why isn't this 8013492
 | 
			
		||||
						LotReturns:        otherBorrower3LotReturns,
 | 
			
		||||
					},
 | 
			
		||||
					auctypes.CollateralAuction{
 | 
			
		||||
						BaseAuction: auctypes.BaseAuction{
 | 
			
		||||
							ID:              2,
 | 
			
		||||
							Initiator:       "harvest_liquidator",
 | 
			
		||||
							Lot:             sdk.NewInt64Coin("ukava", 10*KAVA_CF),
 | 
			
		||||
							Bidder:          nil,
 | 
			
		||||
							Bid:             sdk.NewInt64Coin("ukava", 0),
 | 
			
		||||
							HasReceivedBids: false,
 | 
			
		||||
							EndTime:         endTime,
 | 
			
		||||
							MaxEndTime:      endTime,
 | 
			
		||||
						},
 | 
			
		||||
						CorrespondingDebt: sdk.NewInt64Coin("debt", 0),
 | 
			
		||||
						MaxBid:            sdk.NewInt64Coin("ukava", 8014873),
 | 
			
		||||
						LotReturns:        otherBorrower2LotReturns,
 | 
			
		||||
					},
 | 
			
		||||
					auctypes.CollateralAuction{
 | 
			
		||||
						BaseAuction: auctypes.BaseAuction{
 | 
			
		||||
							ID:              3,
 | 
			
		||||
							Initiator:       "harvest_liquidator",
 | 
			
		||||
							Lot:             sdk.NewInt64Coin("ukava", 10*KAVA_CF),
 | 
			
		||||
							Bidder:          nil,
 | 
			
		||||
							Bid:             sdk.NewInt64Coin("ukava", 0),
 | 
			
		||||
							HasReceivedBids: false,
 | 
			
		||||
							EndTime:         endTime,
 | 
			
		||||
							MaxEndTime:      endTime,
 | 
			
		||||
						},
 | 
			
		||||
						CorrespondingDebt: sdk.NewInt64Coin("debt", 0),
 | 
			
		||||
						MaxBid:            sdk.NewInt64Coin("ukava", 8014873),
 | 
			
		||||
						LotReturns:        lotReturns,
 | 
			
		||||
					},
 | 
			
		||||
					auctypes.CollateralAuction{
 | 
			
		||||
						BaseAuction: auctypes.BaseAuction{
 | 
			
		||||
							ID:              4,
 | 
			
		||||
							Initiator:       "harvest_liquidator",
 | 
			
		||||
							Lot:             sdk.NewInt64Coin("ukava", 10*KAVA_CF),
 | 
			
		||||
							Bidder:          nil,
 | 
			
		||||
							Bid:             sdk.NewInt64Coin("ukava", 0),
 | 
			
		||||
							HasReceivedBids: false,
 | 
			
		||||
							EndTime:         endTime,
 | 
			
		||||
							MaxEndTime:      endTime,
 | 
			
		||||
						},
 | 
			
		||||
						CorrespondingDebt: sdk.NewInt64Coin("debt", 0),
 | 
			
		||||
						MaxBid:            sdk.NewInt64Coin("ukava", 8014873),
 | 
			
		||||
						LotReturns:        otherBorrower1LotReturns,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			errArgs{
 | 
			
		||||
				expectLiquidate:               true,
 | 
			
		||||
				expectLiquidateOtherBorrowers: true,
 | 
			
		||||
				contains:                      "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"valid: LTV index doesn't liquidate over limit positions outside of top 10",
 | 
			
		||||
			args{
 | 
			
		||||
				borrower:              borrower,
 | 
			
		||||
				otherBorrowers:        []sdk.AccAddress{otherBorrower1, otherBorrower2, otherBorrower3, otherBorrower4, otherBorrower5, otherBorrower6, otherBorrower7, otherBorrower8, otherBorrower9, otherBorrower10},
 | 
			
		||||
				initialModuleCoins:    sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
			
		||||
				initialBorrowerCoins:  sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))),
 | 
			
		||||
				depositCoins:          []sdk.Coin{sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))},
 | 
			
		||||
				borrowCoins:           sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(7.99*KAVA_CF))),
 | 
			
		||||
				otherBorrowCoins:      sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))),
 | 
			
		||||
				beginBlockerTime:      oneMonthInSeconds,
 | 
			
		||||
				ltvIndexCount:         int(10),
 | 
			
		||||
				expectedBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(98*KAVA_CF))), // initial - deposit + borrow + liquidation leftovers
 | 
			
		||||
				expectedAuctions:      auctypes.Auctions{},                                        // Ignoring other borrower auctions for this test
 | 
			
		||||
			},
 | 
			
		||||
			errArgs{
 | 
			
		||||
				expectLiquidate:               false,
 | 
			
		||||
				expectLiquidateOtherBorrowers: 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()})
 | 
			
		||||
 | 
			
		||||
			otherBorrowersCoins := make([]sdk.Coins, len(tc.args.otherBorrowers))
 | 
			
		||||
			i := 0
 | 
			
		||||
			for i < len(tc.args.otherBorrowers) {
 | 
			
		||||
				otherBorrowersCoins[i] = tc.args.initialBorrowerCoins
 | 
			
		||||
				i++
 | 
			
		||||
			}
 | 
			
		||||
			appCoins := append([]sdk.Coins{tc.args.initialBorrowerCoins}, otherBorrowersCoins...)
 | 
			
		||||
			appAddrs := append([]sdk.AccAddress{tc.args.borrower}, tc.args.otherBorrowers...)
 | 
			
		||||
 | 
			
		||||
			// Auth module genesis state
 | 
			
		||||
			authGS := app.NewAuthGenState(appAddrs, appCoins)
 | 
			
		||||
 | 
			
		||||
			// Harvest module genesis state
 | 
			
		||||
			harvestGS := types.NewGenesisState(types.NewParams(
 | 
			
		||||
				true,
 | 
			
		||||
				types.DistributionSchedules{
 | 
			
		||||
					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.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("usdx",
 | 
			
		||||
						types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.9")), // Borrow Limit
 | 
			
		||||
						"usdx:usd",                     // Market ID
 | 
			
		||||
						sdk.NewInt(KAVA_CF),            // Conversion Factor
 | 
			
		||||
						sdk.NewInt(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
					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(100000*KAVA_CF),     // Auction Size
 | 
			
		||||
						model,                          // Interest Rate Model
 | 
			
		||||
						reserveFactor,                  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
				},
 | 
			
		||||
				tc.args.ltvIndexCount, // 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.GetHarvestKeeper()
 | 
			
		||||
			suite.app = tApp
 | 
			
		||||
			suite.ctx = ctx
 | 
			
		||||
			suite.keeper = keeper
 | 
			
		||||
			suite.auctionKeeper = auctionKeeper
 | 
			
		||||
 | 
			
		||||
			var err error
 | 
			
		||||
 | 
			
		||||
			// Run begin blocker to set up state
 | 
			
		||||
			harvest.BeginBlocker(suite.ctx, suite.keeper)
 | 
			
		||||
 | 
			
		||||
			// ----------- Users get inserted into the LTV index -----------
 | 
			
		||||
 | 
			
		||||
			// Other borrowers take out positions by depositing and borrowing coins
 | 
			
		||||
			for _, otherBorrower := range tc.args.otherBorrowers {
 | 
			
		||||
				for _, coin := range tc.args.depositCoins {
 | 
			
		||||
					err = suite.keeper.Deposit(suite.ctx, otherBorrower, coin)
 | 
			
		||||
					suite.Require().NoError(err)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				err = suite.keeper.Borrow(suite.ctx, otherBorrower, tc.args.otherBorrowCoins)
 | 
			
		||||
				suite.Require().NoError(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Primary borrower deposits and borrows
 | 
			
		||||
			for _, coin := range tc.args.depositCoins {
 | 
			
		||||
				err = suite.keeper.Deposit(suite.ctx, tc.args.borrower, coin)
 | 
			
		||||
				suite.Require().NoError(err)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins)
 | 
			
		||||
			suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
			// ----------- Check state before liquidation -----------
 | 
			
		||||
			// Other borrowers
 | 
			
		||||
			for _, otherBorrower := range tc.args.otherBorrowers {
 | 
			
		||||
				_, foundBorrowBefore := suite.keeper.GetBorrow(suite.ctx, otherBorrower)
 | 
			
		||||
				suite.Require().True(foundBorrowBefore)
 | 
			
		||||
 | 
			
		||||
				for _, coin := range tc.args.depositCoins {
 | 
			
		||||
					_, foundDepositBefore := suite.keeper.GetDeposit(suite.ctx, otherBorrower, coin.Denom)
 | 
			
		||||
					suite.Require().True(foundDepositBefore)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Primary borrower
 | 
			
		||||
			_, foundBorrowBefore := suite.keeper.GetBorrow(suite.ctx, tc.args.borrower)
 | 
			
		||||
			suite.Require().True(foundBorrowBefore)
 | 
			
		||||
 | 
			
		||||
			for _, coin := range tc.args.depositCoins {
 | 
			
		||||
				_, foundDepositBefore := suite.keeper.GetDeposit(suite.ctx, tc.args.borrower, coin.Denom)
 | 
			
		||||
				suite.Require().True(foundDepositBefore)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// ----------- Liquidate and check state -----------
 | 
			
		||||
			// Liquidate the borrow by running begin blocker
 | 
			
		||||
			runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.beginBlockerTime), 0)
 | 
			
		||||
			liqCtx := suite.ctx.WithBlockTime(runAtTime)
 | 
			
		||||
			harvest.BeginBlocker(liqCtx, suite.keeper)
 | 
			
		||||
 | 
			
		||||
			if tc.errArgs.expectLiquidate {
 | 
			
		||||
				// Check borrow does not exist after liquidation
 | 
			
		||||
				_, foundBorrowAfter := suite.keeper.GetBorrow(liqCtx, tc.args.borrower)
 | 
			
		||||
				suite.Require().False(foundBorrowAfter)
 | 
			
		||||
				// Check deposits do not exist after liquidation
 | 
			
		||||
				for _, coin := range tc.args.depositCoins {
 | 
			
		||||
					_, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, tc.args.borrower, coin.Denom)
 | 
			
		||||
					suite.Require().False(foundDepositAfter)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// Check that borrower's balance contains the expected coins
 | 
			
		||||
				accBorrower := suite.getAccountAtCtx(tc.args.borrower, liqCtx)
 | 
			
		||||
				suite.Require().Equal(tc.args.expectedBorrowerCoins, accBorrower.GetCoins())
 | 
			
		||||
 | 
			
		||||
				// Check that the expected auctions have been created
 | 
			
		||||
				auctions := suite.auctionKeeper.GetAllAuctions(liqCtx)
 | 
			
		||||
				suite.Require().True(len(auctions) > 0)
 | 
			
		||||
				suite.Require().Equal(tc.args.expectedAuctions, auctions)
 | 
			
		||||
			} else {
 | 
			
		||||
				// Check that the user's borrow exists
 | 
			
		||||
				_, foundBorrowAfter := suite.keeper.GetBorrow(liqCtx, tc.args.borrower)
 | 
			
		||||
				suite.Require().True(foundBorrowAfter)
 | 
			
		||||
				// Check that the user's deposits exist
 | 
			
		||||
				for _, coin := range tc.args.depositCoins {
 | 
			
		||||
					_, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, tc.args.borrower, coin.Denom)
 | 
			
		||||
					suite.Require().True(foundDepositAfter)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if !tc.errArgs.expectLiquidateOtherBorrowers {
 | 
			
		||||
					// Check that no auctions have been created
 | 
			
		||||
					auctions := suite.auctionKeeper.GetAllAuctions(liqCtx)
 | 
			
		||||
					suite.Require().True(len(auctions) == 0)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Check other borrowers
 | 
			
		||||
			if tc.errArgs.expectLiquidateOtherBorrowers {
 | 
			
		||||
				for _, otherBorrower := range tc.args.otherBorrowers {
 | 
			
		||||
					// Check borrow does not exist after liquidation
 | 
			
		||||
					_, foundBorrowAfter := suite.keeper.GetBorrow(liqCtx, otherBorrower)
 | 
			
		||||
					suite.Require().False(foundBorrowAfter)
 | 
			
		||||
 | 
			
		||||
					// Check deposits do not exist after liquidation
 | 
			
		||||
					for _, coin := range tc.args.depositCoins {
 | 
			
		||||
						_, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, otherBorrower, coin.Denom)
 | 
			
		||||
						suite.Require().False(foundDepositAfter)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var expectedLtvIndexItemCount int
 | 
			
		||||
				if tc.errArgs.expectLiquidate {
 | 
			
		||||
					expectedLtvIndexItemCount = 0
 | 
			
		||||
				} else {
 | 
			
		||||
					expectedLtvIndexItemCount = 1
 | 
			
		||||
				}
 | 
			
		||||
				indexAddrs := suite.keeper.GetLtvIndexSlice(liqCtx, 1000) // Get all items in the index...
 | 
			
		||||
				suite.Require().Equal(expectedLtvIndexItemCount, len(indexAddrs))
 | 
			
		||||
			} else {
 | 
			
		||||
				for _, otherBorrower := range tc.args.otherBorrowers {
 | 
			
		||||
					// Check borrow does not exist after liquidation
 | 
			
		||||
					_, foundBorrowAfter := suite.keeper.GetBorrow(liqCtx, otherBorrower)
 | 
			
		||||
					suite.Require().True(foundBorrowAfter)
 | 
			
		||||
 | 
			
		||||
					// Check deposits do not exist after liquidation
 | 
			
		||||
					for _, coin := range tc.args.depositCoins {
 | 
			
		||||
						_, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, otherBorrower, coin.Denom)
 | 
			
		||||
						suite.Require().True(foundDepositAfter)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				var expectedLtvIndexItemCount int
 | 
			
		||||
				if tc.errArgs.expectLiquidate {
 | 
			
		||||
					expectedLtvIndexItemCount = len(tc.args.otherBorrowers)
 | 
			
		||||
				} else {
 | 
			
		||||
					expectedLtvIndexItemCount = len(tc.args.otherBorrowers) + 1
 | 
			
		||||
				}
 | 
			
		||||
				indexAddrs := suite.keeper.GetLtvIndexSlice(liqCtx, tc.args.ltvIndexCount)
 | 
			
		||||
				suite.Require().Equal(expectedLtvIndexItemCount, len(indexAddrs))
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) TestKeeperLiquidation() {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		borrower              sdk.AccAddress
 | 
			
		||||
@ -526,6 +1275,7 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
 | 
			
		||||
						reserveFactor,                // Reserve Factor
 | 
			
		||||
						tc.args.keeperRewardPercent), // Keeper Reward Percent
 | 
			
		||||
				},
 | 
			
		||||
				0, // LTV counter
 | 
			
		||||
			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
			
		||||
 | 
			
		||||
			// Pricefeed module genesis state
 | 
			
		||||
@ -634,8 +1384,9 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Attempt to liquidate
 | 
			
		||||
			err = suite.keeper.AttemptKeeperLiquidation(liqCtx, tc.args.keeper, tc.args.borrower)
 | 
			
		||||
			liquidated, err := suite.keeper.AttemptKeeperLiquidation(liqCtx, tc.args.keeper, tc.args.borrower)
 | 
			
		||||
			if tc.errArgs.expectPass {
 | 
			
		||||
				suite.Require().True(liquidated)
 | 
			
		||||
				suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
				// Check borrow does not exist after liquidation
 | 
			
		||||
@ -660,6 +1411,7 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() {
 | 
			
		||||
				suite.Require().True(len(auctions) > 0)
 | 
			
		||||
				suite.Require().Equal(tc.args.expectedAuctions, auctions)
 | 
			
		||||
			} else {
 | 
			
		||||
				suite.Require().False(liquidated)
 | 
			
		||||
				suite.Require().Error(err)
 | 
			
		||||
				suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,13 +10,13 @@ import (
 | 
			
		||||
// Repay borrowed funds
 | 
			
		||||
func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) error {
 | 
			
		||||
	// Get current stored LTV based on stored borrows/deposits
 | 
			
		||||
	prevLtv, shouldRemoveIndex, err := k.GetCurrentLTV(ctx, sender)
 | 
			
		||||
	prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, sender)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sync interest so loan is up-to-date
 | 
			
		||||
	k.SyncOustandingInterest(ctx, sender)
 | 
			
		||||
	k.SyncOutstandingInterest(ctx, sender)
 | 
			
		||||
 | 
			
		||||
	// Validate requested repay
 | 
			
		||||
	err = k.ValidateRepay(ctx, sender, coins)
 | 
			
		||||
@ -61,7 +61,8 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e
 | 
			
		||||
// ValidateRepay validates a requested loan repay
 | 
			
		||||
func (k Keeper) ValidateRepay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) error {
 | 
			
		||||
	senderAcc := k.accountKeeper.GetAccount(ctx, sender)
 | 
			
		||||
	senderCoins := senderAcc.GetCoins()
 | 
			
		||||
	senderCoins := senderAcc.SpendableCoins(ctx.BlockTime())
 | 
			
		||||
 | 
			
		||||
	for _, coin := range coins {
 | 
			
		||||
		if senderCoins.AmountOf(coin.Denom).LT(coin.Amount) {
 | 
			
		||||
			return sdkerrors.Wrapf(types.ErrInsufficientBalanceForRepay, "account can only repay up to %s%s", senderCoins.AmountOf(coin.Denom), coin.Denom)
 | 
			
		||||
 | 
			
		||||
@ -144,6 +144,7 @@ func (suite *KeeperTestSuite) TestRepay() {
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05"),  // Reserve Factor
 | 
			
		||||
						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | 
			
		||||
				},
 | 
			
		||||
				0, // LTV counter
 | 
			
		||||
			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
			
		||||
 | 
			
		||||
			// Pricefeed module genesis state
 | 
			
		||||
 | 
			
		||||
@ -78,6 +78,7 @@ func (suite *KeeperTestSuite) TestApplyDepositRewards() {
 | 
			
		||||
					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()),
 | 
			
		||||
				},
 | 
			
		||||
				0, // LTV counter
 | 
			
		||||
			), tc.args.previousBlockTime, types.DefaultDistributionTimes)
 | 
			
		||||
			tApp.InitializeFromGenesisStates(app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
 | 
			
		||||
			supplyKeeper := tApp.GetSupplyKeeper()
 | 
			
		||||
@ -446,6 +447,7 @@ func harvestGenesisState(rewardRate sdk.Coin) app.GenesisState {
 | 
			
		||||
				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()),
 | 
			
		||||
			},
 | 
			
		||||
			0, // LTV counter
 | 
			
		||||
		),
 | 
			
		||||
		types.DefaultPreviousBlockTime,
 | 
			
		||||
		types.DefaultDistributionTimes,
 | 
			
		||||
 | 
			
		||||
@ -294,6 +294,7 @@ func (suite *KeeperTestSuite) TestSendTimeLockedCoinsToAccount() {
 | 
			
		||||
					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()),
 | 
			
		||||
				},
 | 
			
		||||
				0, // LTV counter
 | 
			
		||||
			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes)
 | 
			
		||||
			tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)})
 | 
			
		||||
			if tc.args.accArgs.vestingAccountBefore {
 | 
			
		||||
 | 
			
		||||
@ -52,6 +52,7 @@ func (suite *GenesisTestSuite) TestGenesisValidation() {
 | 
			
		||||
					),
 | 
			
		||||
					},
 | 
			
		||||
					types.DefaultMoneyMarkets,
 | 
			
		||||
					types.DefaultCheckLtvIndexCount,
 | 
			
		||||
				),
 | 
			
		||||
				pbt: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC),
 | 
			
		||||
				pdts: types.GenesisDistributionTimes{
 | 
			
		||||
@ -75,6 +76,7 @@ func (suite *GenesisTestSuite) TestGenesisValidation() {
 | 
			
		||||
					),
 | 
			
		||||
					},
 | 
			
		||||
					types.DefaultMoneyMarkets,
 | 
			
		||||
					types.DefaultCheckLtvIndexCount,
 | 
			
		||||
				),
 | 
			
		||||
				pbt: time.Time{},
 | 
			
		||||
				pdts: types.GenesisDistributionTimes{
 | 
			
		||||
@ -98,6 +100,7 @@ func (suite *GenesisTestSuite) TestGenesisValidation() {
 | 
			
		||||
					),
 | 
			
		||||
					},
 | 
			
		||||
					types.DefaultMoneyMarkets,
 | 
			
		||||
					types.DefaultCheckLtvIndexCount,
 | 
			
		||||
				),
 | 
			
		||||
				pbt: time.Date(2020, 10, 8, 12, 0, 0, 0, time.UTC),
 | 
			
		||||
				pdts: types.GenesisDistributionTimes{
 | 
			
		||||
 | 
			
		||||
@ -17,11 +17,13 @@ var (
 | 
			
		||||
	KeyLPSchedules            = []byte("LPSchedules")
 | 
			
		||||
	KeyDelegatorSchedule      = []byte("DelegatorSchedule")
 | 
			
		||||
	KeyMoneyMarkets           = []byte("MoneyMarkets")
 | 
			
		||||
	KeyCheckLtvIndexCount     = []byte("CheckLtvIndexCount")
 | 
			
		||||
	DefaultActive             = true
 | 
			
		||||
	DefaultGovSchedules       = DistributionSchedules{}
 | 
			
		||||
	DefaultLPSchedules        = DistributionSchedules{}
 | 
			
		||||
	DefaultDelegatorSchedules = DelegatorDistributionSchedules{}
 | 
			
		||||
	DefaultMoneyMarkets       = MoneyMarkets{}
 | 
			
		||||
	DefaultCheckLtvIndexCount = 10
 | 
			
		||||
	GovDenom                  = cdptypes.DefaultGovDenom
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -31,6 +33,7 @@ type Params struct {
 | 
			
		||||
	LiquidityProviderSchedules     DistributionSchedules          `json:"liquidity_provider_schedules" yaml:"liquidity_provider_schedules"`
 | 
			
		||||
	DelegatorDistributionSchedules DelegatorDistributionSchedules `json:"delegator_distribution_schedules" yaml:"delegator_distribution_schedules"`
 | 
			
		||||
	MoneyMarkets                   MoneyMarkets                   `json:"money_markets" yaml:"money_markets"`
 | 
			
		||||
	CheckLtvIndexCount             int                            `json:"check_ltv_index_count" yaml:"check_ltv_index_count"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistributionSchedule distribution schedule for liquidity providers
 | 
			
		||||
@ -423,18 +426,21 @@ func (irm InterestRateModel) Equal(irmCompareTo InterestRateModel) bool {
 | 
			
		||||
type InterestRateModels []InterestRateModel
 | 
			
		||||
 | 
			
		||||
// NewParams returns a new params object
 | 
			
		||||
func NewParams(active bool, lps DistributionSchedules, dds DelegatorDistributionSchedules, moneyMarkets MoneyMarkets) Params {
 | 
			
		||||
func NewParams(active bool, lps DistributionSchedules, dds DelegatorDistributionSchedules,
 | 
			
		||||
	moneyMarkets MoneyMarkets, checkLtvIndexCount int) Params {
 | 
			
		||||
	return Params{
 | 
			
		||||
		Active:                         active,
 | 
			
		||||
		LiquidityProviderSchedules:     lps,
 | 
			
		||||
		DelegatorDistributionSchedules: dds,
 | 
			
		||||
		MoneyMarkets:                   moneyMarkets,
 | 
			
		||||
		CheckLtvIndexCount:             checkLtvIndexCount,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultParams returns default params for harvest module
 | 
			
		||||
func DefaultParams() Params {
 | 
			
		||||
	return NewParams(DefaultActive, DefaultLPSchedules, DefaultDelegatorSchedules, DefaultMoneyMarkets)
 | 
			
		||||
	return NewParams(DefaultActive, DefaultLPSchedules, DefaultDelegatorSchedules,
 | 
			
		||||
		DefaultMoneyMarkets, DefaultCheckLtvIndexCount)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// String implements fmt.Stringer
 | 
			
		||||
@ -443,8 +449,10 @@ func (p Params) String() string {
 | 
			
		||||
	Active: %t
 | 
			
		||||
	Liquidity Provider Distribution Schedules %s
 | 
			
		||||
	Delegator Distribution Schedule %s
 | 
			
		||||
	Money Markets %v`,
 | 
			
		||||
		p.Active, p.LiquidityProviderSchedules, p.DelegatorDistributionSchedules, p.MoneyMarkets)
 | 
			
		||||
	Money Markets %v
 | 
			
		||||
	Check LTV Index Count: %v`,
 | 
			
		||||
		p.Active, p.LiquidityProviderSchedules, p.DelegatorDistributionSchedules,
 | 
			
		||||
		p.MoneyMarkets, p.CheckLtvIndexCount)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParamKeyTable Key declaration for parameters
 | 
			
		||||
@ -459,6 +467,7 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
 | 
			
		||||
		params.NewParamSetPair(KeyLPSchedules, &p.LiquidityProviderSchedules, validateLPParams),
 | 
			
		||||
		params.NewParamSetPair(KeyDelegatorSchedule, &p.DelegatorDistributionSchedules, validateDelegatorParams),
 | 
			
		||||
		params.NewParamSetPair(KeyMoneyMarkets, &p.MoneyMarkets, validateMoneyMarketParams),
 | 
			
		||||
		params.NewParamSetPair(KeyCheckLtvIndexCount, &p.CheckLtvIndexCount, validateCheckLtvIndexCount),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -472,7 +481,15 @@ func (p Params) Validate() error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return validateLPParams(p.LiquidityProviderSchedules)
 | 
			
		||||
	if err := validateLPParams(p.LiquidityProviderSchedules); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateMoneyMarketParams(p.MoneyMarkets); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return validateCheckLtvIndexCount(p.CheckLtvIndexCount)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validateActiveParam(i interface{}) error {
 | 
			
		||||
@ -517,3 +534,16 @@ func validateMoneyMarketParams(i interface{}) error {
 | 
			
		||||
 | 
			
		||||
	return mm.Validate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func validateCheckLtvIndexCount(i interface{}) error {
 | 
			
		||||
	ltvCheckCount, ok := i.(int)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("invalid parameter type: %T", i)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ltvCheckCount < 0 {
 | 
			
		||||
		return fmt.Errorf("CheckLtvIndexCount param must be positive, got: %d", ltvCheckCount)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,12 +18,12 @@ type ParamTestSuite struct {
 | 
			
		||||
 | 
			
		||||
func (suite *ParamTestSuite) TestParamValidation() {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		lps    types.DistributionSchedules
 | 
			
		||||
		gds    types.DistributionSchedules
 | 
			
		||||
		dds    types.DelegatorDistributionSchedules
 | 
			
		||||
		mms    types.MoneyMarkets
 | 
			
		||||
		kpr    sdk.Dec
 | 
			
		||||
		active bool
 | 
			
		||||
		lps        types.DistributionSchedules
 | 
			
		||||
		gds        types.DistributionSchedules
 | 
			
		||||
		dds        types.DelegatorDistributionSchedules
 | 
			
		||||
		mms        types.MoneyMarkets
 | 
			
		||||
		ltvCounter int
 | 
			
		||||
		active     bool
 | 
			
		||||
	}
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		name        string
 | 
			
		||||
@ -52,9 +52,9 @@ func (suite *ParamTestSuite) TestParamValidation() {
 | 
			
		||||
					time.Hour*24,
 | 
			
		||||
				),
 | 
			
		||||
				},
 | 
			
		||||
				mms:    types.DefaultMoneyMarkets,
 | 
			
		||||
				kpr:    sdk.MustNewDecFromStr("0.05"),
 | 
			
		||||
				active: true,
 | 
			
		||||
				mms:        types.DefaultMoneyMarkets,
 | 
			
		||||
				ltvCounter: 10,
 | 
			
		||||
				active:     true,
 | 
			
		||||
			},
 | 
			
		||||
			expectPass:  true,
 | 
			
		||||
			expectedErr: "",
 | 
			
		||||
@ -70,9 +70,9 @@ func (suite *ParamTestSuite) TestParamValidation() {
 | 
			
		||||
					time.Hour*24,
 | 
			
		||||
				),
 | 
			
		||||
				},
 | 
			
		||||
				mms:    types.DefaultMoneyMarkets,
 | 
			
		||||
				kpr:    sdk.MustNewDecFromStr("0.05"),
 | 
			
		||||
				active: true,
 | 
			
		||||
				mms:        types.DefaultMoneyMarkets,
 | 
			
		||||
				ltvCounter: 10,
 | 
			
		||||
				active:     true,
 | 
			
		||||
			},
 | 
			
		||||
			expectPass:  false,
 | 
			
		||||
			expectedErr: "reward denom should be hard",
 | 
			
		||||
@ -80,7 +80,7 @@ func (suite *ParamTestSuite) TestParamValidation() {
 | 
			
		||||
	}
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		suite.Run(tc.name, func() {
 | 
			
		||||
			params := types.NewParams(tc.args.active, tc.args.lps, tc.args.dds, tc.args.mms)
 | 
			
		||||
			params := types.NewParams(tc.args.active, tc.args.lps, tc.args.dds, tc.args.mms, tc.args.ltvCounter)
 | 
			
		||||
			err := params.Validate()
 | 
			
		||||
			if tc.expectPass {
 | 
			
		||||
				suite.NoError(err)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user