diff --git a/x/hard/abci.go b/x/hard/abci.go index 32914ce3..f66b3410 100644 --- a/x/hard/abci.go +++ b/x/hard/abci.go @@ -7,5 +7,4 @@ import ( // BeginBlocker updates interest rates and attempts liquidations func BeginBlocker(ctx sdk.Context, k Keeper) { k.ApplyInterestRateUpdates(ctx) - k.AttemptIndexLiquidations(ctx) } diff --git a/x/hard/alias.go b/x/hard/alias.go index 31605563..8eab46d7 100644 --- a/x/hard/alias.go +++ b/x/hard/alias.go @@ -55,7 +55,6 @@ var ( DefaultGenesisState = types.DefaultGenesisState DefaultParams = types.DefaultParams DepositTypeIteratorKey = types.DepositTypeIteratorKey - GetBorrowByLtvKey = types.GetBorrowByLtvKey GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength NewBorrow = types.NewBorrow NewBorrowInterestFactor = types.NewBorrowInterestFactor @@ -89,7 +88,6 @@ var ( BorrowsKeyPrefix = types.BorrowsKeyPrefix DefaultAccumulationTimes = types.DefaultAccumulationTimes DefaultBorrows = types.DefaultBorrows - DefaultCheckLtvIndexCount = types.DefaultCheckLtvIndexCount DefaultDeposits = types.DefaultDeposits DefaultMoneyMarkets = types.DefaultMoneyMarkets DefaultTotalBorrowed = types.DefaultTotalBorrowed @@ -124,9 +122,7 @@ var ( ErrPriceNotFound = types.ErrPriceNotFound ErrSuppliedCoinsNotFound = types.ErrSuppliedCoinsNotFound GovDenom = types.GovDenom - KeyCheckLtvIndexCount = types.KeyCheckLtvIndexCount KeyMoneyMarkets = types.KeyMoneyMarkets - LtvIndexPrefix = types.LtvIndexPrefix ModuleCdc = types.ModuleCdc MoneyMarketsPrefix = types.MoneyMarketsPrefix PreviousAccrualTimePrefix = types.PreviousAccrualTimePrefix diff --git a/x/hard/client/cli/query.go b/x/hard/client/cli/query.go index b2594a8c..c63bc6d7 100644 --- a/x/hard/client/cli/query.go +++ b/x/hard/client/cli/query.go @@ -142,8 +142,6 @@ func queryDepositsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { owner = depositOwner } - // Note: The 10 users with the lowest LTV ratio have their outstanding interest applied each block, so if - // testing with 10 or less addresses they'll all show their latest balance including outstanding interest. page := viper.GetInt(flags.FlagPage) limit := viper.GetInt(flags.FlagLimit) @@ -203,8 +201,6 @@ func queryBorrowsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { owner = borrowOwner } - // Note: The 10 users with the lowest LTV ratio have their outstanding debt applied each block, so if - // testing with 10 or less addresses they'll all show their latest balance including outstanding debt. page := viper.GetInt(flags.FlagPage) limit := viper.GetInt(flags.FlagLimit) diff --git a/x/hard/keeper/borrow.go b/x/hard/keeper/borrow.go index 8d7018dd..b17f2726 100644 --- a/x/hard/keeper/borrow.go +++ b/x/hard/keeper/borrow.go @@ -22,13 +22,11 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins } } - // Get current stored LTV based on stored borrows/deposits - prevLtv, err := k.GetStoreLTV(ctx, borrower) - if err != nil { - return err + // Call incentive hooks + existingDeposit, hasExistingDeposit := k.GetDeposit(ctx, borrower) + if hasExistingDeposit { + k.BeforeDepositModified(ctx, existingDeposit) } - - // Call incentive hook existingBorrow, hasExistingBorrow := k.GetBorrow(ctx, borrower) if hasExistingBorrow { k.BeforeBorrowModified(ctx, existingBorrow) @@ -38,7 +36,7 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins k.SyncBorrowInterest(ctx, borrower) // Validate borrow amount within user and protocol limits - err = k.ValidateBorrow(ctx, borrower, coins) + err := k.ValidateBorrow(ctx, borrower, coins) if err != nil { return err } @@ -79,20 +77,14 @@ func (k Keeper) Borrow(ctx sdk.Context, borrower sdk.AccAddress, coins sdk.Coins } else { amount = coins } + // Construct the user's new/updated borrow with amount and interest factors borrow := types.NewBorrow(borrower, amount, interestFactors) - - // Calculate the new Loan-to-Value ratio of Deposit-to-Borrow - deposit, foundDeposit := k.GetDeposit(ctx, borrower) - if !foundDeposit { - return types.ErrDepositNotFound + if borrow.Amount.Empty() { + k.DeleteBorrow(ctx, borrow) + } else { + k.SetBorrow(ctx, borrow) } - newLtv, err := k.CalculateLtv(ctx, deposit, borrow) - if err != nil { - return err - } - - k.UpdateBorrowAndLtvIndex(ctx, borrow, newLtv, prevLtv) // Update total borrowed amount by newly borrowed coins. Don't add user's pending interest as // it has already been included in the total borrowed coins by the BeginBlocker. diff --git a/x/hard/keeper/borrow_test.go b/x/hard/keeper/borrow_test.go index 150c1c45..6369db73 100644 --- a/x/hard/keeper/borrow_test.go +++ b/x/hard/keeper/borrow_test.go @@ -269,7 +269,6 @@ 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.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) diff --git a/x/hard/keeper/deposit.go b/x/hard/keeper/deposit.go index 021bf8c8..3562ca62 100644 --- a/x/hard/keeper/deposit.go +++ b/x/hard/keeper/deposit.go @@ -23,12 +23,6 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi } } - // Get current stored LTV based on stored borrows/deposits - prevLtv, err := k.GetStoreLTV(ctx, depositor) - if err != nil { - return err - } - // Call incentive hook existingDeposit, hasExistingDeposit := k.GetDeposit(ctx, depositor) if hasExistingDeposit { @@ -39,7 +33,7 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi k.SyncBorrowInterest(ctx, depositor) k.SyncSupplyInterest(ctx, depositor) - err = k.ValidateDeposit(ctx, coins) + err := k.ValidateDeposit(ctx, coins) if err != nil { return err } @@ -85,14 +79,12 @@ func (k Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coi // Update the depositer's amount and supply interest factors in the store deposit := types.NewDeposit(depositor, amount, interestFactors) - // Calculate the new Loan-to-Value ratio of Deposit-to-Borrow - borrow, _ := k.GetBorrow(ctx, depositor) - newLtv, err := k.CalculateLtv(ctx, deposit, borrow) - if err != nil { - return err + if deposit.Amount.Empty() { + k.DeleteDeposit(ctx, deposit) + } else { + k.SetDeposit(ctx, deposit) } - k.UpdateDepositAndLtvIndex(ctx, deposit, newLtv, prevLtv) k.IncrementSuppliedCoins(ctx, coins) if !foundDeposit { // User's first deposit k.AfterDepositCreated(ctx, deposit) diff --git a/x/hard/keeper/deposit_test.go b/x/hard/keeper/deposit_test.go index de34fc23..a5d3c500 100644 --- a/x/hard/keeper/deposit_test.go +++ b/x/hard/keeper/deposit_test.go @@ -111,7 +111,6 @@ 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.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) diff --git a/x/hard/keeper/interest_test.go b/x/hard/keeper/interest_test.go index 8ed31d49..caf8314f 100644 --- a/x/hard/keeper/interest_test.go +++ b/x/hard/keeper/interest_test.go @@ -721,7 +721,6 @@ func (suite *KeeperTestSuite) TestBorrowInterest() { tc.args.reserveFactor, // Reserve Factor sdk.ZeroDec()), // Keeper Reward Percentage }, - 0, // LTV counter ), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) @@ -1137,7 +1136,6 @@ func (suite *KeeperTestSuite) TestSupplyInterest() { tc.args.reserveFactor, // Reserve Factor sdk.ZeroDec()), // Keeper Reward Percentage }, - 0, // LTV counter ), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) diff --git a/x/hard/keeper/keeper.go b/x/hard/keeper/keeper.go index 643e57c9..6398337d 100644 --- a/x/hard/keeper/keeper.go +++ b/x/hard/keeper/keeper.go @@ -315,46 +315,3 @@ func (k Keeper) SetSupplyInterestFactor(ctx sdk.Context, denom string, supplyInt bz := k.cdc.MustMarshalBinaryBare(supplyInterestFactor) store.Set([]byte(denom), bz) } - -// InsertIntoLtvIndex indexes a user's borrow object by its current LTV -func (k Keeper) InsertIntoLtvIndex(ctx sdk.Context, ltv sdk.Dec, borrower sdk.AccAddress) { - store := prefix.NewStore(ctx.KVStore(k.key), types.LtvIndexPrefix) - store.Set(types.GetBorrowByLtvKey(ltv, borrower), borrower) -} - -// RemoveFromLtvIndex removes a user's borrow object from the LTV index -func (k Keeper) RemoveFromLtvIndex(ctx sdk.Context, ltv sdk.Dec, borrower sdk.AccAddress) { - store := prefix.NewStore(ctx.KVStore(k.key), types.LtvIndexPrefix) - store.Delete(types.GetBorrowByLtvKey(ltv, borrower)) -} - -// IterateLtvIndex provides an iterator over the borrowers ordered by LTV. -// For results found before the cutoff count, the cb will be called and the item returned. -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.ReverseIterator(nil, nil) - count := 0 - - defer iterator.Close() - for ; iterator.Valid(); iterator.Next() { - - // Stop iteration after first 10 items - count = count + 1 - if count > cutoffCount { - break - } - - id := iterator.Value() - cb(id) - } -} - -// GetLtvIndexSlice returns the first 10 items in the LTV index from the store -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 - }) - return -} diff --git a/x/hard/keeper/keeper_test.go b/x/hard/keeper/keeper_test.go index 272962a9..e318ab53 100644 --- a/x/hard/keeper/keeper_test.go +++ b/x/hard/keeper/keeper_test.go @@ -134,69 +134,6 @@ func (suite *KeeperTestSuite) TestIterateInterestRateModels() { suite.Require().Equal(setDenoms, seenDenoms) } -func (suite *KeeperTestSuite) TestSetDeleteLtvIndex() { - // LTV index should have 0 items - firstAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx, 10) - suite.Require().Equal(0, len(firstAddrs)) - - // Add an item to the LTV index - addr := sdk.AccAddress("test") - ltv := sdk.MustNewDecFromStr("1.1") - suite.Require().NotPanics(func() { suite.keeper.InsertIntoLtvIndex(suite.ctx, ltv, addr) }) - - // LTV index should have 1 item - secondAddrs := suite.keeper.GetLtvIndexSlice(suite.ctx, 10) - suite.Require().Equal(1, len(secondAddrs)) - - // Attempt to remove invalid item from LTV index - fakeLtv := sdk.MustNewDecFromStr("1.2") - suite.Require().NotPanics(func() { suite.keeper.RemoveFromLtvIndex(suite.ctx, fakeLtv, addr) }) - - // LTV index should still have 1 item - 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, 10) - suite.Require().Equal(0, len(fourthAddrs)) -} - -func (suite *KeeperTestSuite) TestIterateLtvIndex() { - var setAddrs []sdk.AccAddress - for i := 1; i <= 20; i++ { - addr := sdk.AccAddress("test" + fmt.Sprint(i)) - incrementalDec := sdk.NewDec(int64(i)).Quo(sdk.NewDec(10)) - ltv := sdk.OneDec().Add(incrementalDec) - - // Set the ltv-address pair in the store - suite.Require().NotPanics(func() { suite.keeper.InsertIntoLtvIndex(suite.ctx, ltv, addr) }) - - setAddrs = append(setAddrs, addr) - } - - // Only the first 10 addresses should be returned - 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 { ak := suite.app.GetAccountKeeper() return ak.GetAccount(suite.ctx, addr) diff --git a/x/hard/keeper/liquidation.go b/x/hard/keeper/liquidation.go index 88f0d75a..c18ffcdc 100644 --- a/x/hard/keeper/liquidation.go +++ b/x/hard/keeper/liquidation.go @@ -1,8 +1,6 @@ package keeper import ( - "errors" - sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -16,29 +14,8 @@ type LiqData struct { conversionFactor sdk.Int } -// AttemptIndexLiquidations attempts to liquidate the lowest LTV borrows -func (k Keeper) AttemptIndexLiquidations(ctx sdk.Context) error { - 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) && !errors.Is(err, types.ErrBorrowNotFound) { - 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 { - prevLtv, err := k.GetStoreLTV(ctx, borrower) - if err != nil { - return err - } - deposit, found := k.GetDeposit(ctx, borrower) if !found { return types.ErrDepositNotFound @@ -82,7 +59,8 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, return err } - k.DeleteDepositBorrowAndLtvIndex(ctx, deposit, borrow, prevLtv) + k.DeleteDeposit(ctx, deposit) + k.DeleteBorrow(ctx, borrow) return nil } @@ -321,35 +299,6 @@ func (k Keeper) IsWithinValidLtvRange(ctx sdk.Context, deposit types.Deposit, bo return true, nil } -// UpdateBorrowAndLtvIndex updates a borrow and its LTV index value in the store -func (k Keeper) UpdateBorrowAndLtvIndex(ctx sdk.Context, borrow types.Borrow, newLtv, oldLtv sdk.Dec) { - k.RemoveFromLtvIndex(ctx, oldLtv, borrow.Borrower) - if borrow.Amount.Empty() { - k.DeleteBorrow(ctx, borrow) - return - } - k.SetBorrow(ctx, borrow) - k.InsertIntoLtvIndex(ctx, newLtv, borrow.Borrower) -} - -// UpdateDepositAndLtvIndex updates a deposit and its LTV index value in the store -func (k Keeper) UpdateDepositAndLtvIndex(ctx sdk.Context, deposit types.Deposit, newLtv, oldLtv sdk.Dec) { - k.RemoveFromLtvIndex(ctx, oldLtv, deposit.Depositor) - if deposit.Amount.Empty() { - k.DeleteDeposit(ctx, deposit) - return - } - k.SetDeposit(ctx, deposit) - k.InsertIntoLtvIndex(ctx, newLtv, deposit.Depositor) -} - -// DeleteDepositBorrowAndLtvIndex deletes deposit, borrow, and ltv index -func (k Keeper) DeleteDepositBorrowAndLtvIndex(ctx sdk.Context, deposit types.Deposit, borrow types.Borrow, oldLtv sdk.Dec) { - k.RemoveFromLtvIndex(ctx, oldLtv, deposit.Depositor) - k.DeleteDeposit(ctx, deposit) - k.DeleteBorrow(ctx, borrow) -} - // 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, error) { diff --git a/x/hard/keeper/liquidation_test.go b/x/hard/keeper/liquidation_test.go index 1f896a73..81bcab48 100644 --- a/x/hard/keeper/liquidation_test.go +++ b/x/hard/keeper/liquidation_test.go @@ -16,1182 +16,6 @@ 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.Coins - 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.NewCoins(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: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 10000411), - Bidder: nil, - Bid: sdk.NewInt64Coin("ukava", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("ukava", 8004765), - LotReturns: lotReturns, - }, - }, - }, - errArgs{ - expectLiquidate: true, - contains: "", - }, - }, - { - "valid: HARD module account starts with no coins", - args{ - borrower: borrower, - initialModuleCoins: sdk.Coins{}, - initialBorrowerCoins: 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))), - 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: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 2000000), - Bidder: nil, - Bid: sdk.NewInt64Coin("ukava", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("ukava", 8050763), - LotReturns: lotReturns, - }, - }, - }, - errArgs{ - expectLiquidate: true, - contains: "", - }, - }, - { - "valid: LTV index liquidates borrow with multiple coin types", - 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("bnb", sdk.NewInt(100*BNB_CF))), - depositCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(10*BNB_CF))), - borrowCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(8*BNB_CF))), - beginBlockerTime: oneMonthInSeconds, - ltvIndexCount: int(10), - expectedBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(98.000001*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(98*BNB_CF))), // initial - deposit + borrow + liquidation leftovers - expectedAuctions: auctypes.Auctions{ - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 1, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("bnb", 200000000), - Bidder: nil, - Bid: sdk.NewInt64Coin("bnb", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("bnb", 804948248), - LotReturns: lotReturns, - }, - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 2, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 8004), - Bidder: nil, - Bid: sdk.NewInt64Coin("bnb", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("bnb", 128242), - LotReturns: lotReturns, - }, - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 3, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 9992406), - 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: "", - }, - }, - { - "valid: HARD module account starts with no coins, LTV index liquidates borrow with multiple coin types", - args{ - borrower: borrower, - initialModuleCoins: sdk.Coins{}, - initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(100*BNB_CF))), - depositCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(10*BNB_CF))), - borrowCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(8*BNB_CF))), - beginBlockerTime: oneMonthInSeconds, - ltvIndexCount: int(10), - expectedBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(98*KAVA_CF)), sdk.NewCoin("bnb", sdk.NewInt(98*BNB_CF))), // initial - deposit + borrow + liquidation leftovers - expectedAuctions: auctypes.Auctions{ - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 1, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("bnb", 200000000), - Bidder: nil, - Bid: sdk.NewInt64Coin("bnb", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("bnb", 805076483), - LotReturns: lotReturns, - }, - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 2, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 2000000), - Bidder: nil, - Bid: sdk.NewInt64Coin("ukava", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("ukava", 8050764), - 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.NewCoins(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}, - ) - - // Hard module genesis state - hardGS := types.NewGenesisState(types.NewParams( - 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.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, - types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, - ) - - // 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(hardGS)}) - - // Mint coins to Hard 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) - - // 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 - _, foundDepositBefore := suite.keeper.GetDeposit(suite.ctx, tc.args.borrower) - 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) - hard.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 - _, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, tc.args.borrower) - 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 - _, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, tc.args.borrower) - 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) TestPricefeedLiquidation() { - type step struct { - action string - moveTimeForward int64 // Seconds to increase blocktime before taking action - sender sdk.AccAddress - coins sdk.Coins - } - - type args struct { - users []sdk.AccAddress - initialUserCoins []sdk.Coins - steps []step - liquidateUser sdk.AccAddress - oracle sdk.AccAddress - pricefeedMarket string - pricefeedPrice sdk.Dec - expectedUserCoins 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") - oracle := sdk.AccAddress(crypto.AddressHash([]byte("oracleaddr"))) - - userOne := sdk.AccAddress(crypto.AddressHash([]byte("useraddrone"))) - userTwo := sdk.AccAddress(crypto.AddressHash([]byte("useraddrtwo"))) - userThree := sdk.AccAddress(crypto.AddressHash([]byte("useraddrthree"))) - users := []sdk.AccAddress{userOne, userTwo, userThree} - - // All users start with 100 KAVA, 100 USDX, 100 HARD - initialCoins := sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF)), sdk.NewCoin("usdx", sdk.NewInt(100*KAVA_CF)), sdk.NewCoin("hard", sdk.NewInt(100*KAVA_CF))) - initialUserCoins := []sdk.Coins{initialCoins, initialCoins, initialCoins} - - // 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{userOne}, []sdk.Int{sdk.NewInt(100)}) - - testCases := []liqTest{ - { - "scenario 1: solvent", - args{ - users: users, - initialUserCoins: initialUserCoins, - steps: []step{ - { // User one deposits 10 KAVA - action: "deposit", - moveTimeForward: 0, - sender: userOne, - coins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))), - }, - { // User two deposits 10 USDX - action: "deposit", - moveTimeForward: 0, - sender: userTwo, - coins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(10*KAVA_CF))), - }, - { // User one borrows 8 USDX - action: "borrow", - moveTimeForward: 0, - sender: userOne, - coins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(8*KAVA_CF))), - }, - }, - liquidateUser: userOne, - oracle: oracle, - pricefeedMarket: "kava:usd", - pricefeedPrice: sdk.MustNewDecFromStr("0.99"), - expectedUserCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(90.000001*KAVA_CF)), sdk.NewCoin("usdx", sdk.NewInt(108*KAVA_CF)), sdk.NewCoin("hard", sdk.NewInt(100*KAVA_CF))), // initial - deposit + borrow + liquidation leftovers - expectedAuctions: auctypes.Auctions{ - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ // Expect: lot = 10 KAVA, max bid = 8 USDX - ID: 1, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 9999999), - Bidder: nil, - Bid: sdk.NewInt64Coin("usdx", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("usdx", 8*KAVA_CF), - LotReturns: lotReturns, - }, - }, - }, - errArgs{ - expectLiquidate: true, - contains: "", - }, - }, - { - "scenario 2: insolvent", - args{ - users: users, - initialUserCoins: initialUserCoins, - steps: []step{ - { // User one deposits 10 KAVA - action: "deposit", - moveTimeForward: 0, - sender: userOne, - coins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))), - }, - { // User two deposits 10 USDX - action: "deposit", - moveTimeForward: 0, - sender: userTwo, - coins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(10*KAVA_CF))), - }, - { // User one borrows 8 USDX - action: "borrow", - moveTimeForward: 0, - sender: userOne, - coins: sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(8*KAVA_CF))), - }, - { // User three deposits 20 HARD - action: "deposit", - moveTimeForward: 0, - sender: userThree, - coins: sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(20*KAVA_CF))), - }, - { // User three borrows 8 KAVA - action: "borrow", - moveTimeForward: 0, - sender: userThree, - coins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))), - }, - }, - liquidateUser: userOne, - oracle: oracle, - pricefeedMarket: "kava:usd", - pricefeedPrice: sdk.MustNewDecFromStr("0.99"), - expectedUserCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(90.000000*KAVA_CF)), sdk.NewCoin("usdx", sdk.NewInt(108*KAVA_CF)), sdk.NewCoin("hard", sdk.NewInt(100*KAVA_CF))), // initial - deposit + borrow + liquidation leftovers - expectedAuctions: auctypes.Auctions{ // Expect: lot = 2 KAVA, max bid = 8 USDX - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 1, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 2*KAVA_CF), - Bidder: nil, - Bid: sdk.NewInt64Coin("usdx", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("usdx", 8*KAVA_CF), - LotReturns: lotReturns, - }, - }, - }, - errArgs{ - expectLiquidate: 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(users, initialUserCoins) - - // Hard module genesis state - hardGS := types.NewGenesisState(types.NewParams( - types.MoneyMarkets{ - 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(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("hard", - types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit - "hard: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 - }, - 10, // LTV counter - ), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves) - - // Pricefeed module genesis state - pricefeedGS := pricefeed.GenesisState{ - Params: pricefeed.Params{ - Markets: []pricefeed.Market{ - {MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{oracle}, Active: true}, - {MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{oracle}, Active: true}, - {MarketID: "hard:usd", BaseAsset: "hard", QuoteAsset: "usd", Oracles: []sdk.AccAddress{oracle}, Active: true}, - }, - }, - PostedPrices: []pricefeed.PostedPrice{ - { - MarketID: "usdx:usd", - OracleAddress: oracle, - Price: sdk.MustNewDecFromStr("1.00"), - Expiry: time.Now().Add(100 * time.Hour), - }, - { - MarketID: "kava:usd", - OracleAddress: oracle, - Price: sdk.MustNewDecFromStr("2.00"), - Expiry: time.Now().Add(100 * time.Hour), - }, - { - MarketID: "hard:usd", - OracleAddress: oracle, - Price: sdk.MustNewDecFromStr("1.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)}) - // Load keepers - auctionKeeper := tApp.GetAuctionKeeper() - pricefeedKeeper := tApp.GetPriceFeedKeeper() - keeper := tApp.GetHardKeeper() - // Set test suite globals - suite.app = tApp - suite.ctx = ctx - suite.keeper = keeper - suite.auctionKeeper = auctionKeeper - suite.pricefeedKeeper = pricefeedKeeper - - var err error - - // Run begin blocker to set up state - hard.BeginBlocker(suite.ctx, suite.keeper) - - currCtx := suite.ctx - for _, step := range tc.args.steps { - currCtx = currCtx.WithBlockTime(time.Unix(currCtx.BlockTime().Unix()+(step.moveTimeForward), 0)) - switch step.action { - case "deposit": - err = suite.keeper.Deposit(currCtx, step.sender, step.coins) - suite.Require().NoError(err) - case "borrow": - err = suite.keeper.Borrow(currCtx, step.sender, step.coins) - suite.Require().NoError(err) - default: - suite.Fail("each define action for each step") - } - } - - // Update asset's price in price feed - expiryTime := currCtx.BlockTime().Add(100 * time.Hour) - _, err = suite.pricefeedKeeper.SetPrice(currCtx, tc.args.oracle, tc.args.pricefeedMarket, tc.args.pricefeedPrice, expiryTime) - suite.Require().NoError(err) - err = suite.pricefeedKeeper.SetCurrentPrices(currCtx, tc.args.pricefeedMarket) - suite.Require().NoError(err) - priceInfo, err := suite.pricefeedKeeper.GetCurrentPrice(ctx, tc.args.pricefeedMarket) - suite.Require().NoError(err) - suite.Require().Equal(tc.args.pricefeedPrice, priceInfo.Price) - - // Liquidate the borrow by running begin blocker - hard.BeginBlocker(currCtx, suite.keeper) - - if tc.errArgs.expectLiquidate { - // Check borrow does not exist after liquidation - _, foundBorrowAfter := suite.keeper.GetBorrow(currCtx, tc.args.liquidateUser) - suite.Require().False(foundBorrowAfter) - // Check deposits do not exist after liquidation - _, foundDepositAfter := suite.keeper.GetDeposit(currCtx, tc.args.liquidateUser) - suite.Require().False(foundDepositAfter) - - // Check that borrower's balance contains the expected coins - accBorrower := suite.getAccountAtCtx(tc.args.liquidateUser, currCtx) - suite.Require().Equal(tc.args.expectedUserCoins, accBorrower.GetCoins()) - - // Check that the expected auctions have been created - auctions := suite.auctionKeeper.GetAllAuctions(currCtx) - 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(currCtx, tc.args.liquidateUser) - suite.Require().True(foundBorrowAfter) - // Check that the user's deposits exist - _, foundDepositAfter := suite.keeper.GetDeposit(currCtx, tc.args.liquidateUser) - suite.Require().True(foundDepositAfter) - - // Check that no auctions have been created - auctions := suite.auctionKeeper.GetAllAuctions(currCtx) - 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(98000001))), // initial - (deposit + borrow) + liquidation leftovers + supply interest - expectedAuctions: auctypes.Auctions{ - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 1, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 10003317), - Bidder: nil, - Bid: sdk.NewInt64Coin("ukava", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("ukava", 8013492), - 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: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 10004037), - Bidder: nil, - Bid: sdk.NewInt64Coin("ukava", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("ukava", 8014872), - LotReturns: otherBorrower3LotReturns, - }, - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 2, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 10004037), - Bidder: nil, - Bid: sdk.NewInt64Coin("ukava", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("ukava", 8014872), - LotReturns: otherBorrower2LotReturns, - }, - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 3, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 10004037), - Bidder: nil, - Bid: sdk.NewInt64Coin("ukava", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("ukava", 8014872), - LotReturns: lotReturns, - }, - auctypes.CollateralAuction{ - BaseAuction: auctypes.BaseAuction{ - ID: 4, - Initiator: "hard_liquidator", - Lot: sdk.NewInt64Coin("ukava", 10004037), - Bidder: nil, - Bid: sdk.NewInt64Coin("ukava", 0), - HasReceivedBids: false, - EndTime: endTime, - MaxEndTime: endTime, - }, - CorrespondingDebt: sdk.NewInt64Coin("debt", 0), - MaxBid: sdk.NewInt64Coin("ukava", 8014872), - 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) - - // Hard module genesis state - hardGS := types.NewGenesisState(types.NewParams( - 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.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, - types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, - ) - - // 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(hardGS)}) - - // Mint coins to Hard 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) - - // ----------- Users get inserted into the LTV index ----------- - - // Other borrowers take out positions by depositing and borrowing coins - for _, otherBorrower := range tc.args.otherBorrowers { - err = suite.keeper.Deposit(suite.ctx, otherBorrower, tc.args.depositCoins) - suite.Require().NoError(err) - - err = suite.keeper.Borrow(suite.ctx, otherBorrower, tc.args.otherBorrowCoins) - suite.Require().NoError(err) - } - - // Primary borrower deposits and borrows - err = suite.keeper.Deposit(suite.ctx, tc.args.borrower, tc.args.depositCoins) - 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) - - _, foundDepositBefore := suite.keeper.GetDeposit(suite.ctx, otherBorrower) - suite.Require().True(foundDepositBefore) - } - - // Primary borrower - _, foundBorrowBefore := suite.keeper.GetBorrow(suite.ctx, tc.args.borrower) - suite.Require().True(foundBorrowBefore) - - _, foundDepositBefore := suite.keeper.GetDeposit(suite.ctx, tc.args.borrower) - 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) - hard.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 - _, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, tc.args.borrower) - 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 - _, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, tc.args.borrower) - 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 - - _, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, otherBorrower) - 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 - - _, foundDepositAfter := suite.keeper.GetDeposit(liqCtx, otherBorrower) - 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 @@ -1687,7 +511,6 @@ func (suite *KeeperTestSuite) TestKeeperLiquidation() { reserveFactor, // Reserve Factor tc.args.keeperRewardPercent), // Keeper Reward Percent }, - 0, // LTV counter ), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) diff --git a/x/hard/keeper/repay.go b/x/hard/keeper/repay.go index f35b4d6c..50ac19a6 100644 --- a/x/hard/keeper/repay.go +++ b/x/hard/keeper/repay.go @@ -9,12 +9,6 @@ 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, err := k.GetStoreLTV(ctx, sender) - if err != nil { - return err - } - // Check borrow exists here to avoid duplicating store read in ValidateRepay borrow, found := k.GetBorrow(ctx, sender) if !found { @@ -27,7 +21,7 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e k.SyncBorrowInterest(ctx, sender) // Validate requested repay - err = k.ValidateRepay(ctx, sender, coins) + err := k.ValidateRepay(ctx, sender, coins) if err != nil { return err } @@ -57,17 +51,11 @@ func (k Keeper) Repay(ctx sdk.Context, sender sdk.AccAddress, coins sdk.Coins) e // Update user's borrow in store borrow.Amount = borrow.Amount.Sub(payment) - // Calculate the new Loan-to-Value ratio of Deposit-to-Borrow - deposit, foundDeposit := k.GetDeposit(ctx, sender) - if !foundDeposit { - return types.ErrDepositNotFound + if borrow.Amount.Empty() { + k.DeleteBorrow(ctx, borrow) + } else { + k.SetBorrow(ctx, borrow) } - newLtv, err := k.CalculateLtv(ctx, deposit, borrow) - if err != nil { - return err - } - - k.UpdateBorrowAndLtvIndex(ctx, borrow, newLtv, prevLtv) // Update total borrowed amount k.DecrementBorrowedCoins(ctx, payment) diff --git a/x/hard/keeper/repay_test.go b/x/hard/keeper/repay_test.go index 0facae5b..b83df3b7 100644 --- a/x/hard/keeper/repay_test.go +++ b/x/hard/keeper/repay_test.go @@ -155,7 +155,6 @@ func (suite *KeeperTestSuite) TestRepay() { sdk.MustNewDecFromStr("0.05"), // Reserve Factor sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent }, - 0, // LTV counter ), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) diff --git a/x/hard/keeper/timelock_test.go b/x/hard/keeper/timelock_test.go index 8e043a35..3e2cf3ca 100644 --- a/x/hard/keeper/timelock_test.go +++ b/x/hard/keeper/timelock_test.go @@ -285,7 +285,6 @@ 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.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) diff --git a/x/hard/keeper/withdraw.go b/x/hard/keeper/withdraw.go index 4d056b50..3a57dec4 100644 --- a/x/hard/keeper/withdraw.go +++ b/x/hard/keeper/withdraw.go @@ -9,18 +9,16 @@ import ( // 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, err := k.GetStoreLTV(ctx, depositor) - if err != nil { - return err - } - deposit, found := k.GetDeposit(ctx, depositor) if !found { return sdkerrors.Wrapf(types.ErrDepositNotFound, "no deposit found for %s", depositor) } - // Call incentive hook + // Call incentive hooks k.BeforeDepositModified(ctx, deposit) + existingBorrow, hasExistingBorrow := k.GetBorrow(ctx, depositor) + if hasExistingBorrow { + k.BeforeBorrowModified(ctx, existingBorrow) + } k.SyncBorrowInterest(ctx, depositor) k.SyncSupplyInterest(ctx, depositor) @@ -61,12 +59,11 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co } deposit.Amount = deposit.Amount.Sub(amount) - newLtv, err := k.CalculateLtv(ctx, deposit, borrow) - if err != nil { - return err + if deposit.Amount.Empty() { + k.DeleteDeposit(ctx, deposit) + } else { + k.SetDeposit(ctx, deposit) } - k.UpdateDepositAndLtvIndex(ctx, deposit, newLtv, prevLtv) - // Update total supplied amount k.DecrementSuppliedCoins(ctx, amount) diff --git a/x/hard/keeper/withdraw_test.go b/x/hard/keeper/withdraw_test.go index d4be9d5c..99b3781c 100644 --- a/x/hard/keeper/withdraw_test.go +++ b/x/hard/keeper/withdraw_test.go @@ -129,7 +129,6 @@ func (suite *KeeperTestSuite) TestWithdraw() { 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.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) @@ -283,7 +282,6 @@ func (suite *KeeperTestSuite) TestLtvWithdraw() { reserveFactor, // Reserve Factor sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent }, - 0, // LTV counter ), types.DefaultAccumulationTimes, types.DefaultDeposits, types.DefaultBorrows, types.DefaultTotalSupplied, types.DefaultTotalBorrowed, types.DefaultTotalReserves, ) diff --git a/x/hard/types/genesis_test.go b/x/hard/types/genesis_test.go index b9db4e1b..69fd3bc0 100644 --- a/x/hard/types/genesis_test.go +++ b/x/hard/types/genesis_test.go @@ -61,7 +61,6 @@ func (suite *GenesisTestSuite) TestGenesisValidation() { types.MoneyMarkets{ types.NewMoneyMarket("usdx", types.NewBorrowLimit(true, sdk.MustNewDecFromStr("100000000000"), sdk.MustNewDecFromStr("1")), "usdx:usd", sdk.NewInt(USDX_CF), 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()), }, - 10, ), gats: types.GenesisAccumulationTimes{ types.NewGenesisAccumulationTime("usdx", time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), sdk.OneDec(), sdk.OneDec()), diff --git a/x/hard/types/keys.go b/x/hard/types/keys.go index 210a953b..268deb67 100644 --- a/x/hard/types/keys.go +++ b/x/hard/types/keys.go @@ -1,9 +1,5 @@ package types -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - const ( // ModuleName name that will be used throughout the module ModuleName = "hard" @@ -38,7 +34,6 @@ var ( BorrowInterestFactorPrefix = []byte{0x08} // denom -> sdk.Dec SupplyInterestFactorPrefix = []byte{0x09} // denom -> sdk.Dec DelegatorInterestFactorPrefix = []byte{0x10} // denom -> sdk.Dec - LtvIndexPrefix = []byte{0x11} sep = []byte(":") ) @@ -47,11 +42,6 @@ func DepositTypeIteratorKey(denom string) []byte { return createKey([]byte(denom)) } -// GetBorrowByLtvKey is used by the LTV index -func GetBorrowByLtvKey(ltv sdk.Dec, borrower sdk.AccAddress) []byte { - return append(ltv.Bytes(), borrower...) -} - func createKey(bytes ...[]byte) (r []byte) { for _, b := range bytes { r = append(r, b...) diff --git a/x/hard/types/params.go b/x/hard/types/params.go index 731dfdba..79f11167 100644 --- a/x/hard/types/params.go +++ b/x/hard/types/params.go @@ -11,23 +11,20 @@ import ( // Parameter keys and default values var ( - KeyMoneyMarkets = []byte("MoneyMarkets") - KeyCheckLtvIndexCount = []byte("CheckLtvIndexCount") - DefaultMoneyMarkets = MoneyMarkets{} - DefaultCheckLtvIndexCount = 10 - GovDenom = cdptypes.DefaultGovDenom - DefaultAccumulationTimes = GenesisAccumulationTimes{} - DefaultTotalSupplied = sdk.Coins{} - DefaultTotalBorrowed = sdk.Coins{} - DefaultTotalReserves = sdk.Coins{} - DefaultDeposits = Deposits{} - DefaultBorrows = Borrows{} + KeyMoneyMarkets = []byte("MoneyMarkets") + DefaultMoneyMarkets = MoneyMarkets{} + GovDenom = cdptypes.DefaultGovDenom + DefaultAccumulationTimes = GenesisAccumulationTimes{} + DefaultTotalSupplied = sdk.Coins{} + DefaultTotalBorrowed = sdk.Coins{} + DefaultTotalReserves = sdk.Coins{} + DefaultDeposits = Deposits{} + DefaultBorrows = Borrows{} ) // Params governance parameters for hard module type Params struct { - MoneyMarkets MoneyMarkets `json:"money_markets" yaml:"money_markets"` - CheckLtvIndexCount int `json:"check_ltv_index_count" yaml:"check_ltv_index_count"` + MoneyMarkets MoneyMarkets `json:"money_markets" yaml:"money_markets"` } // BorrowLimit enforces restrictions on a money market @@ -232,24 +229,22 @@ func (irm InterestRateModel) Equal(irmCompareTo InterestRateModel) bool { type InterestRateModels []InterestRateModel // NewParams returns a new params object -func NewParams(moneyMarkets MoneyMarkets, checkLtvIndexCount int) Params { +func NewParams(moneyMarkets MoneyMarkets) Params { return Params{ - MoneyMarkets: moneyMarkets, - CheckLtvIndexCount: checkLtvIndexCount, + MoneyMarkets: moneyMarkets, } } // DefaultParams returns default params for hard module func DefaultParams() Params { - return NewParams(DefaultMoneyMarkets, DefaultCheckLtvIndexCount) + return NewParams(DefaultMoneyMarkets) } // String implements fmt.Stringer func (p Params) String() string { return fmt.Sprintf(`Params: - Money Markets %v - Check LTV Index Count: %v`, - p.MoneyMarkets, p.CheckLtvIndexCount) + Money Markets %v`, + p.MoneyMarkets) } // ParamKeyTable Key declaration for parameters @@ -261,18 +256,12 @@ func ParamKeyTable() params.KeyTable { func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ params.NewParamSetPair(KeyMoneyMarkets, &p.MoneyMarkets, validateMoneyMarketParams), - params.NewParamSetPair(KeyCheckLtvIndexCount, &p.CheckLtvIndexCount, validateCheckLtvIndexCount), } } // Validate checks that the parameters have valid values. func (p Params) Validate() error { - - if err := validateMoneyMarketParams(p.MoneyMarkets); err != nil { - return err - } - - return validateCheckLtvIndexCount(p.CheckLtvIndexCount) + return validateMoneyMarketParams(p.MoneyMarkets) } func validateMoneyMarketParams(i interface{}) error { @@ -283,16 +272,3 @@ 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 -} diff --git a/x/hard/types/params_test.go b/x/hard/types/params_test.go index 87d51426..4ca160cd 100644 --- a/x/hard/types/params_test.go +++ b/x/hard/types/params_test.go @@ -15,8 +15,7 @@ type ParamTestSuite struct { func (suite *ParamTestSuite) TestParamValidation() { type args struct { - mms types.MoneyMarkets - ltvCounter int + mms types.MoneyMarkets } testCases := []struct { name string @@ -27,8 +26,7 @@ func (suite *ParamTestSuite) TestParamValidation() { { name: "default", args: args{ - mms: types.DefaultMoneyMarkets, - ltvCounter: types.DefaultCheckLtvIndexCount, + mms: types.DefaultMoneyMarkets, }, expectPass: true, expectedErr: "", @@ -36,7 +34,7 @@ func (suite *ParamTestSuite) TestParamValidation() { } for _, tc := range testCases { suite.Run(tc.name, func() { - params := types.NewParams(tc.args.mms, tc.args.ltvCounter) + params := types.NewParams(tc.args.mms) err := params.Validate() if tc.expectPass { suite.NoError(err) diff --git a/x/incentive/keeper/integration_test.go b/x/incentive/keeper/integration_test.go index 71375982..3653ecf1 100644 --- a/x/incentive/keeper/integration_test.go +++ b/x/incentive/keeper/integration_test.go @@ -164,7 +164,6 @@ func NewHardGenStateMulti() app.GenesisState { hard.NewMoneyMarket("btcb", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "btc:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), hard.NewMoneyMarket("xrp", hard.NewBorrowLimit(false, borrowLimit, loanToValue), "xrp:usd", sdk.NewInt(1000000), sdk.NewInt(BTCB_CF*1000), hard.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), }, - 0, // LTV counter ), hard.DefaultAccumulationTimes, hard.DefaultDeposits, hard.DefaultBorrows, hard.DefaultTotalSupplied, hard.DefaultTotalBorrowed, hard.DefaultTotalReserves, )