mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-10-31 20:57:33 +00:00 
			
		
		
		
	Hard: withdraws limited by ltv (#747)
* liquidations refactor * validate withdraws above ltv * set mm in initgenesis * add ltv limited withdraw test * address revisions * resolve diff
This commit is contained in:
		
							parent
							
								
									a4bbea1ec4
								
							
						
					
					
						commit
						477b937039
					
				| @ -27,6 +27,10 @@ func InitGenesis(ctx sdk.Context, k Keeper, supplyKeeper types.SupplyKeeper, gs | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for _, mm := range gs.Params.MoneyMarkets { | ||||
| 		k.SetMoneyMarket(ctx, mm.Denom, mm) | ||||
| 	} | ||||
| 
 | ||||
| 	// check if the module account exists
 | ||||
| 	LPModuleAcc := supplyKeeper.GetModuleAccount(ctx, LPAccount) | ||||
| 	if LPModuleAcc == nil { | ||||
|  | ||||
| @ -91,11 +91,35 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co | ||||
| 		return sdkerrors.Wrapf(types.ErrDepositNotFound, "no deposit found for %s", depositor) | ||||
| 	} | ||||
| 
 | ||||
| 	if !deposit.Amount.IsAllGTE(coins) { // TODO test that this works how I think it does
 | ||||
| 		return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "%s>%s", coins, deposit.Amount) | ||||
| 	// Get current stored LTV based on stored borrows/deposits
 | ||||
| 	prevLtv, shouldRemoveIndex, err := k.GetStoreLTV(ctx, depositor) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, depositor, coins) | ||||
| 	k.SyncOutstandingInterest(ctx, depositor) | ||||
| 
 | ||||
| 	borrow, found := k.GetBorrow(ctx, depositor) | ||||
| 	if !found { | ||||
| 		borrow = types.Borrow{} | ||||
| 	} | ||||
| 
 | ||||
| 	proposedDepositAmount, isNegative := deposit.Amount.SafeSub(coins) | ||||
| 	if isNegative { | ||||
| 		return types.ErrNegativeBorrowedCoins | ||||
| 	} | ||||
| 	proposedDeposit := types.NewDeposit(deposit.Depositor, proposedDepositAmount) | ||||
| 
 | ||||
| 	valid, err := k.IsWithinValidLtvRange(ctx, proposedDeposit, borrow) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if !valid { | ||||
| 		return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "proposed withdraw outside loan-to-value range") | ||||
| 	} | ||||
| 
 | ||||
| 	err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, depositor, coins) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -122,6 +146,8 @@ func (k Keeper) Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Co | ||||
| 	deposit.Amount = deposit.Amount.Sub(coins) | ||||
| 	k.SetDeposit(ctx, deposit) | ||||
| 
 | ||||
| 	k.UpdateItemInLtvIndex(ctx, prevLtv, shouldRemoveIndex, depositor) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| package keeper_test | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| @ -267,7 +266,7 @@ func (suite *KeeperTestSuite) TestWithdraw() { | ||||
| 			}, | ||||
| 			errArgs{ | ||||
| 				expectPass: false, | ||||
| 				contains:   "invalid withdrawal amount", | ||||
| 				contains:   "subtraction results in negative borrow amount", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| @ -284,7 +283,7 @@ func (suite *KeeperTestSuite) TestWithdraw() { | ||||
| 			}, | ||||
| 			errArgs{ | ||||
| 				expectPass: false, | ||||
| 				contains:   "invalid withdrawal amount", | ||||
| 				contains:   "subtraction results in negative borrow amount", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| @ -310,10 +309,45 @@ func (suite *KeeperTestSuite) TestWithdraw() { | ||||
| 				types.MoneyMarkets{ | ||||
| 					types.NewMoneyMarket("usdx", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "usdx:usd", sdk.NewInt(1000000), sdk.NewInt(USDX_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), | ||||
| 					types.NewMoneyMarket("ukava", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "kava:usd", sdk.NewInt(1000000), sdk.NewInt(KAVA_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), | ||||
| 					types.NewMoneyMarket("bnb", types.NewBorrowLimit(false, sdk.NewDec(1000000000000000), loanToValue), "bnb:usd", sdk.NewInt(100000000), sdk.NewInt(BNB_CF*1000), types.NewInterestRateModel(sdk.MustNewDecFromStr("0.05"), sdk.MustNewDecFromStr("2"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("10")), sdk.MustNewDecFromStr("0.05"), sdk.ZeroDec()), | ||||
| 				}, | ||||
| 				0, // LTV counter
 | ||||
| 			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) | ||||
| 			tApp.InitializeFromGenesisStates(authGS, app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)}) | ||||
| 
 | ||||
| 			// Pricefeed module genesis state
 | ||||
| 			pricefeedGS := pricefeed.GenesisState{ | ||||
| 				Params: pricefeed.Params{ | ||||
| 					Markets: []pricefeed.Market{ | ||||
| 						{MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, | ||||
| 						{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, | ||||
| 						{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				PostedPrices: []pricefeed.PostedPrice{ | ||||
| 					{ | ||||
| 						MarketID:      "usdx:usd", | ||||
| 						OracleAddress: sdk.AccAddress{}, | ||||
| 						Price:         sdk.MustNewDecFromStr("1.00"), | ||||
| 						Expiry:        time.Now().Add(100 * time.Hour), | ||||
| 					}, | ||||
| 					{ | ||||
| 						MarketID:      "kava:usd", | ||||
| 						OracleAddress: sdk.AccAddress{}, | ||||
| 						Price:         sdk.MustNewDecFromStr("2.00"), | ||||
| 						Expiry:        time.Now().Add(100 * time.Hour), | ||||
| 					}, | ||||
| 					{ | ||||
| 						MarketID:      "bnb:usd", | ||||
| 						OracleAddress: sdk.AccAddress{}, | ||||
| 						Price:         sdk.MustNewDecFromStr("10.00"), | ||||
| 						Expiry:        time.Now().Add(100 * time.Hour), | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
| 
 | ||||
| 			tApp.InitializeFromGenesisStates(authGS, | ||||
| 				app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)}, | ||||
| 				app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(hardGS)}) | ||||
| 			keeper := tApp.GetHardKeeper() | ||||
| 			suite.app = tApp | ||||
| 			suite.ctx = ctx | ||||
| @ -341,10 +375,181 @@ func (suite *KeeperTestSuite) TestWithdraw() { | ||||
| 				} | ||||
| 			} else { | ||||
| 				suite.Require().Error(err) | ||||
| 				fmt.Printf("%s\n", err.Error()) | ||||
| 				suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) | ||||
| 			} | ||||
| 		}) | ||||
| 
 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (suite *KeeperTestSuite) TestLtvWithdraw() { | ||||
| 	type args struct { | ||||
| 		borrower             sdk.AccAddress | ||||
| 		keeper               sdk.AccAddress | ||||
| 		initialModuleCoins   sdk.Coins | ||||
| 		initialBorrowerCoins sdk.Coins | ||||
| 		initialKeeperCoins   sdk.Coins | ||||
| 		depositCoins         []sdk.Coin | ||||
| 		borrowCoins          sdk.Coins | ||||
| 		futureTime           int64 | ||||
| 	} | ||||
| 
 | ||||
| 	type errArgs struct { | ||||
| 		expectPass bool | ||||
| 		contains   string | ||||
| 	} | ||||
| 
 | ||||
| 	type liqTest struct { | ||||
| 		name    string | ||||
| 		args    args | ||||
| 		errArgs errArgs | ||||
| 	} | ||||
| 
 | ||||
| 	// Set up test constants
 | ||||
| 	model := types.NewInterestRateModel(sdk.MustNewDecFromStr("0"), sdk.MustNewDecFromStr("0.1"), sdk.MustNewDecFromStr("0.8"), sdk.MustNewDecFromStr("0.5")) | ||||
| 	reserveFactor := sdk.MustNewDecFromStr("0.05") | ||||
| 	oneMonthInSeconds := int64(2592000) | ||||
| 	borrower := sdk.AccAddress(crypto.AddressHash([]byte("testborrower"))) | ||||
| 	keeper := sdk.AccAddress(crypto.AddressHash([]byte("testkeeper"))) | ||||
| 
 | ||||
| 	testCases := []liqTest{ | ||||
| 		{ | ||||
| 			"invalid: withdraw is outside loan-to-value range", | ||||
| 			args{ | ||||
| 				borrower:             borrower, | ||||
| 				keeper:               keeper, | ||||
| 				initialModuleCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))), | ||||
| 				initialBorrowerCoins: sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))), | ||||
| 				initialKeeperCoins:   sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(100*KAVA_CF))), | ||||
| 				depositCoins:         sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(10*KAVA_CF))), | ||||
| 				borrowCoins:          sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(8*KAVA_CF))), | ||||
| 				futureTime:           oneMonthInSeconds, | ||||
| 			}, | ||||
| 			errArgs{ | ||||
| 				expectPass: false, | ||||
| 				contains:   "proposed withdraw outside loan-to-value range", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, tc := range testCases { | ||||
| 		suite.Run(tc.name, func() { | ||||
| 			// Initialize test app and set context
 | ||||
| 			tApp := app.NewTestApp() | ||||
| 			ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) | ||||
| 
 | ||||
| 			// Auth module genesis state
 | ||||
| 			authGS := app.NewAuthGenState( | ||||
| 				[]sdk.AccAddress{tc.args.borrower, tc.args.keeper}, | ||||
| 				[]sdk.Coins{tc.args.initialBorrowerCoins, tc.args.initialKeeperCoins}, | ||||
| 			) | ||||
| 
 | ||||
| 			// Harvest module genesis state
 | ||||
| 			harvestGS := types.NewGenesisState(types.NewParams( | ||||
| 				true, | ||||
| 				types.DistributionSchedules{ | ||||
| 					types.NewDistributionSchedule(true, "ukava", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2020, 11, 22, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(5000)), time.Date(2021, 11, 22, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), | ||||
| 				}, | ||||
| 				types.DelegatorDistributionSchedules{types.NewDelegatorDistributionSchedule( | ||||
| 					types.NewDistributionSchedule(true, "usdx", time.Date(2020, 10, 8, 14, 0, 0, 0, time.UTC), time.Date(2025, 10, 8, 14, 0, 0, 0, time.UTC), sdk.NewCoin("hard", sdk.NewInt(500)), time.Date(2026, 10, 8, 14, 0, 0, 0, time.UTC), types.Multipliers{types.NewMultiplier(types.Small, 0, sdk.MustNewDecFromStr("0.33")), types.NewMultiplier(types.Medium, 6, sdk.MustNewDecFromStr("0.5")), types.NewMultiplier(types.Medium, 24, sdk.OneDec())}), | ||||
| 					time.Hour*24, | ||||
| 				), | ||||
| 				}, | ||||
| 				types.MoneyMarkets{ | ||||
| 					types.NewMoneyMarket("ukava", | ||||
| 						types.NewBorrowLimit(false, sdk.NewDec(100000000*KAVA_CF), sdk.MustNewDecFromStr("0.8")), // Borrow Limit
 | ||||
| 						"kava:usd",                     // Market ID
 | ||||
| 						sdk.NewInt(KAVA_CF),            // Conversion Factor
 | ||||
| 						sdk.NewInt(100000000*KAVA_CF),  // Auction Size
 | ||||
| 						model,                          // Interest Rate Model
 | ||||
| 						reserveFactor,                  // Reserve Factor
 | ||||
| 						sdk.MustNewDecFromStr("0.05")), // Keeper Reward Percent
 | ||||
| 				}, | ||||
| 				0, // LTV counter
 | ||||
| 			), types.DefaultPreviousBlockTime, types.DefaultDistributionTimes) | ||||
| 
 | ||||
| 			// Pricefeed module genesis state
 | ||||
| 			pricefeedGS := pricefeed.GenesisState{ | ||||
| 				Params: pricefeed.Params{ | ||||
| 					Markets: []pricefeed.Market{ | ||||
| 						{MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, | ||||
| 						{MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				PostedPrices: []pricefeed.PostedPrice{ | ||||
| 					{ | ||||
| 						MarketID:      "usdx:usd", | ||||
| 						OracleAddress: sdk.AccAddress{}, | ||||
| 						Price:         sdk.MustNewDecFromStr("1.00"), | ||||
| 						Expiry:        time.Now().Add(100 * time.Hour), | ||||
| 					}, | ||||
| 					{ | ||||
| 						MarketID:      "kava:usd", | ||||
| 						OracleAddress: sdk.AccAddress{}, | ||||
| 						Price:         sdk.MustNewDecFromStr("2.00"), | ||||
| 						Expiry:        time.Now().Add(100 * time.Hour), | ||||
| 					}, | ||||
| 				}, | ||||
| 			} | ||||
| 
 | ||||
| 			// Initialize test application
 | ||||
| 			tApp.InitializeFromGenesisStates(authGS, | ||||
| 				app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)}, | ||||
| 				app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(harvestGS)}) | ||||
| 
 | ||||
| 			// Mint coins to Harvest module account
 | ||||
| 			supplyKeeper := tApp.GetSupplyKeeper() | ||||
| 			supplyKeeper.MintCoins(ctx, types.ModuleAccountName, tc.args.initialModuleCoins) | ||||
| 
 | ||||
| 			auctionKeeper := tApp.GetAuctionKeeper() | ||||
| 
 | ||||
| 			keeper := tApp.GetHardKeeper() | ||||
| 			suite.app = tApp | ||||
| 			suite.ctx = ctx | ||||
| 			suite.keeper = keeper | ||||
| 			suite.auctionKeeper = auctionKeeper | ||||
| 
 | ||||
| 			var err error | ||||
| 
 | ||||
| 			// Run begin blocker to set up state
 | ||||
| 			hard.BeginBlocker(suite.ctx, suite.keeper) | ||||
| 
 | ||||
| 			// Deposit coins
 | ||||
| 			err = suite.keeper.Deposit(suite.ctx, tc.args.borrower, tc.args.depositCoins) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			// Borrow coins
 | ||||
| 			err = suite.keeper.Borrow(suite.ctx, tc.args.borrower, tc.args.borrowCoins) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			// Attempting to withdraw fails
 | ||||
| 			err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, sdk.NewCoins(sdk.NewCoin("ukava", sdk.OneInt()))) | ||||
| 			suite.Require().Error(err) | ||||
| 			suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) | ||||
| 
 | ||||
| 			// Set up future chain context and run begin blocker, increasing user's owed borrow balance
 | ||||
| 			runAtTime := time.Unix(suite.ctx.BlockTime().Unix()+(tc.args.futureTime), 0) | ||||
| 			liqCtx := suite.ctx.WithBlockTime(runAtTime) | ||||
| 			hard.BeginBlocker(liqCtx, suite.keeper) | ||||
| 
 | ||||
| 			// Attempted withdraw of 1 coin still fails
 | ||||
| 			err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, sdk.NewCoins(sdk.NewCoin("ukava", sdk.OneInt()))) | ||||
| 			suite.Require().Error(err) | ||||
| 			suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) | ||||
| 
 | ||||
| 			// Repay the initial principal
 | ||||
| 			err = suite.keeper.Repay(suite.ctx, tc.args.borrower, tc.args.borrowCoins) | ||||
| 			suite.Require().NoError(err) | ||||
| 
 | ||||
| 			// Attempted withdraw of all deposited coins fails as user hasn't repaid interest debt
 | ||||
| 			err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, tc.args.depositCoins) | ||||
| 			suite.Require().Error(err) | ||||
| 			suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains)) | ||||
| 
 | ||||
| 			// Withdrawing half the coins should succeed
 | ||||
| 			withdrawCoins := sdk.NewCoins(sdk.NewCoin("ukava", tc.args.depositCoins[0].Amount.Quo(sdk.NewInt(2)))) | ||||
| 			err = suite.keeper.Withdraw(suite.ctx, tc.args.borrower, withdrawCoins) | ||||
| 			suite.Require().NoError(err) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -48,60 +48,25 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, | ||||
| 	if !found { | ||||
| 		return false, sdkerrors.Wrapf(types.ErrDepositsNotFound, "no deposits found for %s", borrower) | ||||
| 	} | ||||
| 	depositDenoms := []string{} | ||||
| 	for _, depCoin := range deposit.Amount { | ||||
| 		depositDenoms = append(depositDenoms, depCoin.Denom) | ||||
| 	} | ||||
| 
 | ||||
| 	// Fetch borrow balances and parse coin denoms
 | ||||
| 	borrows, found := k.GetBorrow(ctx, borrower) | ||||
| 	borrow, found := k.GetBorrow(ctx, borrower) | ||||
| 	if !found { | ||||
| 		return false, types.ErrBorrowNotFound | ||||
| 	} | ||||
| 	borrowDenoms := getDenoms(borrows.Amount) | ||||
| 
 | ||||
| 	liqMap := make(map[string]LiqData) | ||||
| 
 | ||||
| 	// Load required liquidation data for every deposit/borrow denom
 | ||||
| 	denoms := removeDuplicates(borrowDenoms, depositDenoms) | ||||
| 	for _, denom := range denoms { | ||||
| 		mm, found := k.GetMoneyMarket(ctx, denom) | ||||
| 		if !found { | ||||
| 			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 false, err | ||||
| 		} | ||||
| 
 | ||||
| 		liqMap[denom] = LiqData{priceData.Price, mm.BorrowLimit.LoanToValue, mm.ConversionFactor} | ||||
| 	isWithinRange, err := k.IsWithinValidLtvRange(ctx, deposit, borrow) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	totalBorrowableUSDAmount := sdk.ZeroDec() | ||||
| 	totalDepositedUSDAmount := sdk.ZeroDec() | ||||
| 	for _, depCoin := range deposit.Amount { | ||||
| 		lData := liqMap[depCoin.Denom] | ||||
| 		usdValue := sdk.NewDecFromInt(depCoin.Amount).Quo(sdk.NewDecFromInt(lData.conversionFactor)).Mul(lData.price) | ||||
| 		totalDepositedUSDAmount = totalDepositedUSDAmount.Add(usdValue) | ||||
| 		borrowableUSDAmountForDeposit := usdValue.Mul(lData.ltv) | ||||
| 		totalBorrowableUSDAmount = totalBorrowableUSDAmount.Add(borrowableUSDAmountForDeposit) | ||||
| 	} | ||||
| 
 | ||||
| 	totalBorrowedUSDAmount := sdk.ZeroDec() | ||||
| 	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) | ||||
| 	} | ||||
| 
 | ||||
| 	// Validate that the proposed borrow's USD value is within user's borrowable limit
 | ||||
| 	if totalBorrowedUSDAmount.LTE(totalBorrowableUSDAmount) { | ||||
| 		return false, sdkerrors.Wrapf(types.ErrBorrowNotLiquidatable, "borrowed %s <= borrowable %s", totalBorrowedUSDAmount, totalBorrowableUSDAmount) | ||||
| 	if isWithinRange { | ||||
| 		return false, sdkerrors.Wrapf(types.ErrBorrowNotLiquidatable, "position is within valid LTV range") | ||||
| 	} | ||||
| 
 | ||||
| 	// Sending coins to auction module with keeper address getting % of the profits
 | ||||
| 	err = k.SeizeDeposits(ctx, keeper, liqMap, deposit, borrows.Amount, depositDenoms, borrowDenoms) | ||||
| 	borrowDenoms := getDenoms(borrow.Amount) | ||||
| 	depositDenoms := getDenoms(deposit.Amount) | ||||
| 	err = k.SeizeDeposits(ctx, keeper, deposit, borrow, depositDenoms, borrowDenoms) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| @ -112,7 +77,6 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, | ||||
| 	} | ||||
| 	k.RemoveFromLtvIndex(ctx, currLtv, borrower) | ||||
| 
 | ||||
| 	borrow, _ := k.GetBorrow(ctx, borrower) | ||||
| 	k.DeleteBorrow(ctx, borrow) | ||||
| 	k.DeleteDeposit(ctx, deposit) | ||||
| 
 | ||||
| @ -120,8 +84,12 @@ func (k Keeper) AttemptKeeperLiquidation(ctx sdk.Context, keeper sdk.AccAddress, | ||||
| } | ||||
| 
 | ||||
| // SeizeDeposits seizes a list of deposits and sends them to auction
 | ||||
| func (k Keeper) SeizeDeposits(ctx sdk.Context, keeper sdk.AccAddress, liqMap map[string]LiqData, | ||||
| 	deposit types.Deposit, borrowBalances sdk.Coins, dDenoms, bDenoms []string) error { | ||||
| func (k Keeper) SeizeDeposits(ctx sdk.Context, keeper sdk.AccAddress, deposit types.Deposit, | ||||
| 	borrow types.Borrow, dDenoms, bDenoms []string) error { | ||||
| 	liqMap, err := k.LoadLiquidationData(ctx, deposit, borrow) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Seize % of every deposit and send to the keeper
 | ||||
| 	aucDeposits := sdk.Coins{} | ||||
| @ -158,7 +126,7 @@ func (k Keeper) SeizeDeposits(ctx sdk.Context, keeper sdk.AccAddress, liqMap map | ||||
| 
 | ||||
| 	// Build valuation map to hold borrow coin USD valuations
 | ||||
| 	borrowCoinValues := types.NewValuationMap() | ||||
| 	for _, bCoin := range borrowBalances { | ||||
| 	for _, bCoin := range borrow.Amount { | ||||
| 		bData := liqMap[bCoin.Denom] | ||||
| 		bCoinUsdValue := sdk.NewDecFromInt(bCoin.Amount).Quo(sdk.NewDecFromInt(bData.conversionFactor)).Mul(bData.price) | ||||
| 		borrowCoinValues.Increment(bCoin.Denom, bCoinUsdValue) | ||||
| @ -167,7 +135,7 @@ func (k Keeper) SeizeDeposits(ctx sdk.Context, keeper sdk.AccAddress, liqMap map | ||||
| 	// Loan-to-Value ratio after sending keeper their reward
 | ||||
| 	ltv := borrowCoinValues.Sum().Quo(depositCoinValues.Sum()) | ||||
| 
 | ||||
| 	err := k.StartAuctions(ctx, deposit.Depositor, borrowBalances, aucDeposits, depositCoinValues, borrowCoinValues, ltv, liqMap) | ||||
| 	err = k.StartAuctions(ctx, deposit.Depositor, borrow.Amount, aucDeposits, depositCoinValues, borrowCoinValues, ltv, liqMap) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @ -276,68 +244,36 @@ func (k Keeper) StartAuctions(ctx sdk.Context, borrower sdk.AccAddress, borrows, | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // 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
 | ||||
| 	deposit, found := k.GetDeposit(ctx, addr) | ||||
| 	if !found { | ||||
| 		return sdk.ZeroDec(), false, nil | ||||
| // IsWithinValidLtvRange compares a borrow and deposit to see if it's within a valid LTV range at current prices
 | ||||
| func (k Keeper) IsWithinValidLtvRange(ctx sdk.Context, deposit types.Deposit, borrow types.Borrow) (bool, error) { | ||||
| 	liqMap, err := k.LoadLiquidationData(ctx, deposit, borrow) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	depositDenoms := []string{} | ||||
| 
 | ||||
| 	totalBorrowableUSDAmount := sdk.ZeroDec() | ||||
| 	totalDepositedUSDAmount := sdk.ZeroDec() | ||||
| 	for _, depCoin := range deposit.Amount { | ||||
| 		depositDenoms = append(depositDenoms, depCoin.Denom) | ||||
| 		lData := liqMap[depCoin.Denom] | ||||
| 		usdValue := sdk.NewDecFromInt(depCoin.Amount).Quo(sdk.NewDecFromInt(lData.conversionFactor)).Mul(lData.price) | ||||
| 		totalDepositedUSDAmount = totalDepositedUSDAmount.Add(usdValue) | ||||
| 		borrowableUSDAmountForDeposit := usdValue.Mul(lData.ltv) | ||||
| 		totalBorrowableUSDAmount = totalBorrowableUSDAmount.Add(borrowableUSDAmountForDeposit) | ||||
| 	} | ||||
| 
 | ||||
| 	// Fetch borrow balances and parse coin denoms
 | ||||
| 	borrows, found := k.GetBorrow(ctx, addr) | ||||
| 	if !found { | ||||
| 		return sdk.ZeroDec(), false, nil | ||||
| 	} | ||||
| 	borrowDenoms := getDenoms(borrows.Amount) | ||||
| 
 | ||||
| 	liqMap := make(map[string]LiqData) | ||||
| 
 | ||||
| 	// Load required liquidation data for every deposit/borrow denom
 | ||||
| 	denoms := removeDuplicates(borrowDenoms, depositDenoms) | ||||
| 	for _, denom := range denoms { | ||||
| 		mm, found := k.GetMoneyMarket(ctx, denom) | ||||
| 		if !found { | ||||
| 			return sdk.ZeroDec(), false, sdkerrors.Wrapf(types.ErrMarketNotFound, "no market found for denom %s", denom) | ||||
| 		} | ||||
| 
 | ||||
| 		priceData, err := k.pricefeedKeeper.GetCurrentPrice(ctx, mm.SpotMarketID) | ||||
| 		if err != nil { | ||||
| 			return sdk.ZeroDec(), false, err | ||||
| 		} | ||||
| 
 | ||||
| 		liqMap[denom] = LiqData{priceData.Price, mm.BorrowLimit.LoanToValue, mm.ConversionFactor} | ||||
| 	totalBorrowedUSDAmount := sdk.ZeroDec() | ||||
| 	for _, coin := range borrow.Amount { | ||||
| 		lData := liqMap[coin.Denom] | ||||
| 		usdValue := sdk.NewDecFromInt(coin.Amount).Quo(sdk.NewDecFromInt(lData.conversionFactor)).Mul(lData.price) | ||||
| 		totalBorrowedUSDAmount = totalBorrowedUSDAmount.Add(usdValue) | ||||
| 	} | ||||
| 
 | ||||
| 	// Build valuation map to hold deposit coin USD valuations
 | ||||
| 	depositCoinValues := types.NewValuationMap() | ||||
| 	for _, depCoin := range deposit.Amount { | ||||
| 		dData := liqMap[depCoin.Denom] | ||||
| 		dCoinUsdValue := sdk.NewDecFromInt(depCoin.Amount).Quo(sdk.NewDecFromInt(dData.conversionFactor)).Mul(dData.price) | ||||
| 		depositCoinValues.Increment(depCoin.Denom, dCoinUsdValue) | ||||
| 	// Check if the user's has borrowed more than they're allowed to
 | ||||
| 	if totalBorrowedUSDAmount.GT(totalBorrowableUSDAmount) { | ||||
| 		return false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Build valuation map to hold borrow coin USD valuations
 | ||||
| 	borrowCoinValues := types.NewValuationMap() | ||||
| 	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) | ||||
| 	} | ||||
| 
 | ||||
| 	// User doesn't have any deposits, catch divide by 0 error
 | ||||
| 	sumDeposits := depositCoinValues.Sum() | ||||
| 	if sumDeposits.Equal(sdk.ZeroDec()) { | ||||
| 		return sdk.ZeroDec(), false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Loan-to-Value ratio
 | ||||
| 	return borrowCoinValues.Sum().Quo(sumDeposits), true, nil | ||||
| 	return true, nil | ||||
| } | ||||
| 
 | ||||
| // UpdateItemInLtvIndex updates the key a borrower's address is stored under in the LTV index
 | ||||
| @ -357,6 +293,86 @@ func (k Keeper) UpdateItemInLtvIndex(ctx sdk.Context, prevLtv sdk.Dec, | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // 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
 | ||||
| 	deposit, found := k.GetDeposit(ctx, addr) | ||||
| 	if !found { | ||||
| 		return sdk.ZeroDec(), false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Fetch borrow balances and parse coin denoms
 | ||||
| 	borrow, found := k.GetBorrow(ctx, addr) | ||||
| 	if !found { | ||||
| 		return sdk.ZeroDec(), false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	return k.CalculateLtv(ctx, deposit, borrow) | ||||
| } | ||||
| 
 | ||||
| // CalculateLtv calculates the potential LTV given a user's deposits and borrows.
 | ||||
| // The boolean returned indicates if the LTV should be added to the store's LTV index.
 | ||||
| func (k Keeper) CalculateLtv(ctx sdk.Context, deposit types.Deposit, borrow types.Borrow) (sdk.Dec, bool, error) { | ||||
| 	// Load required liquidation data for every deposit/borrow denom
 | ||||
| 	liqMap, err := k.LoadLiquidationData(ctx, deposit, borrow) | ||||
| 	if err != nil { | ||||
| 		return sdk.ZeroDec(), false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Build valuation map to hold deposit coin USD valuations
 | ||||
| 	depositCoinValues := types.NewValuationMap() | ||||
| 	for _, depCoin := range deposit.Amount { | ||||
| 		dData := liqMap[depCoin.Denom] | ||||
| 		dCoinUsdValue := sdk.NewDecFromInt(depCoin.Amount).Quo(sdk.NewDecFromInt(dData.conversionFactor)).Mul(dData.price) | ||||
| 		depositCoinValues.Increment(depCoin.Denom, dCoinUsdValue) | ||||
| 	} | ||||
| 
 | ||||
| 	// Build valuation map to hold borrow coin USD valuations
 | ||||
| 	borrowCoinValues := types.NewValuationMap() | ||||
| 	for _, bCoin := range borrow.Amount { | ||||
| 		bData := liqMap[bCoin.Denom] | ||||
| 		bCoinUsdValue := sdk.NewDecFromInt(bCoin.Amount).Quo(sdk.NewDecFromInt(bData.conversionFactor)).Mul(bData.price) | ||||
| 		borrowCoinValues.Increment(bCoin.Denom, bCoinUsdValue) | ||||
| 	} | ||||
| 
 | ||||
| 	// User doesn't have any deposits, catch divide by 0 error
 | ||||
| 	sumDeposits := depositCoinValues.Sum() | ||||
| 	if sumDeposits.Equal(sdk.ZeroDec()) { | ||||
| 		return sdk.ZeroDec(), false, nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Loan-to-Value ratio
 | ||||
| 	return borrowCoinValues.Sum().Quo(sumDeposits), true, nil | ||||
| } | ||||
| 
 | ||||
| // LoadLiquidationData returns liquidation data, deposit, borrow
 | ||||
| func (k Keeper) LoadLiquidationData(ctx sdk.Context, deposit types.Deposit, borrow types.Borrow) (map[string]LiqData, error) { | ||||
| 	liqMap := make(map[string]LiqData) | ||||
| 
 | ||||
| 	borrowDenoms := getDenoms(borrow.Amount) | ||||
| 	depositDenoms := getDenoms(deposit.Amount) | ||||
| 	denoms := removeDuplicates(borrowDenoms, depositDenoms) | ||||
| 
 | ||||
| 	// Load required liquidation data for every deposit/borrow denom
 | ||||
| 	for _, denom := range denoms { | ||||
| 		mm, found := k.GetMoneyMarket(ctx, denom) | ||||
| 		if !found { | ||||
| 			return liqMap, sdkerrors.Wrapf(types.ErrMarketNotFound, "no market found for denom %s", denom) | ||||
| 
 | ||||
| 		} | ||||
| 
 | ||||
| 		priceData, err := k.pricefeedKeeper.GetCurrentPrice(ctx, mm.SpotMarketID) | ||||
| 		if err != nil { | ||||
| 			return liqMap, err | ||||
| 		} | ||||
| 
 | ||||
| 		liqMap[denom] = LiqData{priceData.Price, mm.BorrowLimit.LoanToValue, mm.ConversionFactor} | ||||
| 	} | ||||
| 
 | ||||
| 	return liqMap, nil | ||||
| } | ||||
| 
 | ||||
| func getDenoms(coins sdk.Coins) []string { | ||||
| 	denoms := []string{} | ||||
| 	for _, coin := range coins { | ||||
|  | ||||
| @ -412,7 +412,7 @@ func (suite *KeeperTestSuite) TestFullIndexLiquidation() { | ||||
| 							MaxEndTime:      endTime, | ||||
| 						}, | ||||
| 						CorrespondingDebt: sdk.NewInt64Coin("debt", 0), | ||||
| 						MaxBid:            sdk.NewInt64Coin("ukava", 8013492), // TODO: why isn't this 8004766
 | ||||
| 						MaxBid:            sdk.NewInt64Coin("ukava", 8013492), | ||||
| 						LotReturns:        lotReturns, | ||||
| 					}, | ||||
| 				}, | ||||
| @ -449,7 +449,7 @@ func (suite *KeeperTestSuite) TestFullIndexLiquidation() { | ||||
| 							MaxEndTime:      endTime, | ||||
| 						}, | ||||
| 						CorrespondingDebt: sdk.NewInt64Coin("debt", 0), | ||||
| 						MaxBid:            sdk.NewInt64Coin("ukava", 8014873), // TODO: Why isn't this 8013492
 | ||||
| 						MaxBid:            sdk.NewInt64Coin("ukava", 8014873), | ||||
| 						LotReturns:        otherBorrower3LotReturns, | ||||
| 					}, | ||||
| 					auctypes.CollateralAuction{ | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Denali Marsh
						Denali Marsh