From ba73f6968845bd11d8c821af2dcdebc1e0411992 Mon Sep 17 00:00:00 2001 From: Nick DeLuca Date: Tue, 7 Mar 2023 20:19:29 -0700 Subject: [PATCH] Fix CDP keeper liquidation collateral ratio check (#1488) * add test for exact collateral ratio; fix bug that allows cdps created at the limit to be liquidated by a keeper; update spec * touch up spec to be more clear * adjust test name to better reflect what we are testing --- x/cdp/keeper/seize.go | 2 +- x/cdp/keeper/seize_test.go | 35 +++++++++++++++++++++++++++++------ x/cdp/spec/03_messages.md | 2 ++ 3 files changed, 32 insertions(+), 7 deletions(-) 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 {