diff --git a/x/cdp/keeper/seize.go b/x/cdp/keeper/seize.go index ed7fdaa4..d038a19f 100644 --- a/x/cdp/keeper/seize.go +++ b/x/cdp/keeper/seize.go @@ -124,7 +124,7 @@ func (k Keeper) ValidateLiquidation(ctx sdk.Context, collateral sdk.Coin, collat return err } liquidationRatio := k.getLiquidationRatio(ctx, collateralType) - if collateralizationRatio.GT(liquidationRatio) { + if collateralizationRatio.GTE(liquidationRatio) { return sdkerrors.Wrapf(types.ErrNotLiquidatable, "collateral %s, collateral ratio %s, liquidation ratio %s", collateral.Denom, collateralizationRatio, liquidationRatio) } return nil diff --git a/x/cdp/keeper/seize_test.go b/x/cdp/keeper/seize_test.go index bdda2e69..276f92e8 100644 --- a/x/cdp/keeper/seize_test.go +++ b/x/cdp/keeper/seize_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "errors" + "fmt" "math/rand" "strings" "testing" @@ -364,16 +365,38 @@ func (suite *SeizeTestSuite) TestKeeperLiquidation() { "collateral ratio not below liquidation ratio", }, }, + { + "invalid - collateralization ratio equal to liquidation ratio", + args{ + ctype: "xrp-a", + blockTime: time.Date(2020, 12, 15, 14, 0, 0, 0, time.UTC), + initialPrice: d("1.00"), // we are allowed to create a cdp with an exact ratio + finalPrice: d("1.00"), + finalTwapPrice: d("1.00"), // and it should not be able to be liquidated + collateral: c("xrp", 100000000), + principal: c("usdx", 50000000), + expectedKeeperCoins: cs(), + expectedAuctions: []auctiontypes.Auction{}, + }, + errArgs{ + false, + "collateral ratio not below liquidation ratio", + }, + }, } for _, tc := range testCases { suite.Run(tc.name, func() { suite.SetupTest() + + spotMarket := fmt.Sprintf("%s:usd", tc.args.collateral.Denom) + liquidationMarket := fmt.Sprintf("%s:30", spotMarket) + // setup pricefeed pk := suite.app.GetPriceFeedKeeper() - _, err := pk.SetPrice(suite.ctx, sdk.AccAddress{}, "btc:usd", tc.args.initialPrice, suite.ctx.BlockTime().Add(time.Hour*24)) + _, err := pk.SetPrice(suite.ctx, sdk.AccAddress{}, spotMarket, tc.args.initialPrice, suite.ctx.BlockTime().Add(time.Hour*24)) suite.Require().NoError(err) - err = pk.SetCurrentPrices(suite.ctx, "btc:usd") + err = pk.SetCurrentPrices(suite.ctx, spotMarket) suite.Require().NoError(err) // setup cdp state @@ -384,15 +407,15 @@ func (suite *SeizeTestSuite) TestKeeperLiquidation() { // update pricefeed // spot market - _, err = pk.SetPrice(suite.ctx, sdk.AccAddress{}, "btc:usd", tc.args.finalPrice, suite.ctx.BlockTime().Add(time.Hour*24)) + _, err = pk.SetPrice(suite.ctx, sdk.AccAddress{}, spotMarket, tc.args.finalPrice, suite.ctx.BlockTime().Add(time.Hour*24)) suite.Require().NoError(err) // liquidate market - _, err = pk.SetPrice(suite.ctx, sdk.AccAddress{}, "btc:usd:30", tc.args.finalTwapPrice, suite.ctx.BlockTime().Add(time.Hour*24)) + _, err = pk.SetPrice(suite.ctx, sdk.AccAddress{}, liquidationMarket, tc.args.finalTwapPrice, suite.ctx.BlockTime().Add(time.Hour*24)) suite.Require().NoError(err) - err = pk.SetCurrentPrices(suite.ctx, "btc:usd") + err = pk.SetCurrentPrices(suite.ctx, spotMarket) suite.Require().NoError(err) - err = pk.SetCurrentPrices(suite.ctx, "btc:usd:30") + err = pk.SetCurrentPrices(suite.ctx, liquidationMarket) suite.Require().NoError(err) _, found := suite.keeper.GetCdpByOwnerAndCollateralType(suite.ctx, suite.addrs[0], tc.args.ctype) diff --git a/x/cdp/spec/03_messages.md b/x/cdp/spec/03_messages.md index d0c01b50..1fe7fd02 100644 --- a/x/cdp/spec/03_messages.md +++ b/x/cdp/spec/03_messages.md @@ -102,6 +102,8 @@ State Changes: Liquidate enables Keepers to liquidate a Borrower's CDP. If the CDP is below its Loan-to-Value obligations, the CDP's deposits are seized: a small percentage of the seized funds are sent to the Keeper with the rest auctioned off to recover the CDP's outstanding borrowed amount. Any deposited funds leftover that weren't needed to cover the Borrower's debts are returned to the Borrower. +Note: In kava v0.21.x and below, CDP's that have a collateral ratio exactly equal to the liquidation ratio can be liquidated through this method. + ```go // MsgLiquidate attempts to liquidate a borrower's cdp type MsgLiquidate struct {