diff --git a/CHANGELOG.md b/CHANGELOG.md index 103ad33d1f..d4e243fd0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ - [1330](https://github.com/umee-network/umee/pull/1330) Implemented MaxSupplyUtilization. - [1319](https://github.com/umee-network/umee/pull/1319) Implemented MaxSupply. - [1331](https://github.com/umee-network/umee/pull/1331) Implemented MinCollateralLiquidity. +- [1343](https://github.com/umee-network/umee/pull/1343) RepayBadDebt and Liquidate automatically clear blacklisted collateral. ### Improvements diff --git a/x/leverage/keeper/filter.go b/x/leverage/keeper/filter.go index 0b1a276c41..864c958273 100644 --- a/x/leverage/keeper/filter.go +++ b/x/leverage/keeper/filter.go @@ -24,13 +24,3 @@ func (k Keeper) filterAcceptedCoins(ctx sdk.Context, coins sdk.Coins) sdk.Coins }, ) } - -// filterAcceptedUTokens returns the subset of an sdk.Coins that are accepted, non-blacklisted uTokens -func (k Keeper) filterAcceptedUTokens(ctx sdk.Context, coins sdk.Coins) sdk.Coins { - return k.filterCoins( - coins, - func(c sdk.Coin) bool { - return k.validateAcceptedUToken(ctx, c) == nil - }, - ) -} diff --git a/x/leverage/keeper/iter.go b/x/leverage/keeper/iter.go index 2fdcc4f444..c521f5bfd9 100644 --- a/x/leverage/keeper/iter.go +++ b/x/leverage/keeper/iter.go @@ -232,11 +232,13 @@ func (k Keeper) SweepBadDebts(ctx sdk.Context) error { addr := types.AddressFromKey(key, prefix) denom := types.DenomFromKeyWithAddress(key, prefix) - // first check if the borrower has gained collateral since the bad debt was identified - done := k.HasCollateral(ctx, addr) - // TODO #1223: Decollateralize any blacklisted collateral and proceed + // clear blacklisted collateral while checking for any remaining (valid) collateral + done, err := k.clearBlacklistedCollateral(ctx, addr) + if err != nil { + return err + } - // if collateral is still zero, attempt to repay a single address's debt in this denom + // if remaining collateral is zero, attempt to repay this bad debt if !done { var err error done, err = k.RepayBadDebt(ctx, addr, denom) diff --git a/x/leverage/keeper/reserves.go b/x/leverage/keeper/reserves.go index cd42eecb09..2ba7ae0af3 100644 --- a/x/leverage/keeper/reserves.go +++ b/x/leverage/keeper/reserves.go @@ -6,14 +6,46 @@ import ( "github.com/umee-network/umee/v3/x/leverage/types" ) +// clearBlacklistedCollateral decollateralizes any blacklisted uTokens +// from a borrower's collateral. It is used during liquidations and before +// repaying bad debts with reserves to make any subsequent checks for +// remaining collateral on the borrower's address more efficient. +// Also returns a boolean indicating whether valid collateral remains. +func (k Keeper) clearBlacklistedCollateral(ctx sdk.Context, borrowerAddr sdk.AccAddress) (bool, error) { + collateral := k.GetBorrowerCollateral(ctx, borrowerAddr) + hasCollateral := false + for _, coin := range collateral { + denom := types.ToTokenDenom(coin.Denom) + token, err := k.GetTokenSettings(ctx, denom) + if err != nil { + return false, err + } + if token.Blacklist { + // Decollateralize any blacklisted uTokens encountered + err := k.decollateralize(ctx, borrowerAddr, borrowerAddr, coin) + if err != nil { + return false, err + } + } else { + // At least one non-blacklisted uToken was found + hasCollateral = true + } + } + // Any remaining collateral is non-blacklisted + return hasCollateral, nil +} + // checkBadDebt detects if a borrower has zero non-blacklisted collateral, // and marks any remaining borrowed tokens as bad debt. func (k Keeper) checkBadDebt(ctx sdk.Context, borrowerAddr sdk.AccAddress) error { - // get remaining collateral uTokens, ignoring blacklisted - remainingCollateral := k.filterAcceptedUTokens(ctx, k.GetBorrowerCollateral(ctx, borrowerAddr)) + // clear blacklisted collateral while checking for any remaining (valid) collateral + hasCollateral, err := k.clearBlacklistedCollateral(ctx, borrowerAddr) + if err != nil { + return err + } - // detect bad debt if collateral is completely exhausted - if remainingCollateral.IsZero() { + // mark bad debt if collateral is completely exhausted + if !hasCollateral { for _, coin := range k.GetBorrowerBorrows(ctx, borrowerAddr) { // set a bad debt flag for each borrowed denom if err := k.setBadDebtAddress(ctx, borrowerAddr, coin.Denom, true); err != nil { @@ -27,6 +59,8 @@ func (k Keeper) checkBadDebt(ctx sdk.Context, borrowerAddr sdk.AccAddress) error // RepayBadDebt uses reserves to repay borrower's debts of a given denom. // It returns a boolean representing whether full repayment was achieved. +// This function assumes the borrower has already been verified to have +// no collateral remaining. func (k Keeper) RepayBadDebt(ctx sdk.Context, borrowerAddr sdk.AccAddress, denom string) (bool, error) { borrowed := k.GetBorrow(ctx, borrowerAddr, denom) borrower := borrowerAddr.String() diff --git a/x/leverage/keeper/validate.go b/x/leverage/keeper/validate.go index 752468da09..1fb4938894 100644 --- a/x/leverage/keeper/validate.go +++ b/x/leverage/keeper/validate.go @@ -19,15 +19,6 @@ func (k Keeper) validateAcceptedDenom(ctx sdk.Context, denom string) error { return token.AssertNotBlacklisted() } -// validateAcceptedUTokenDenom validates an sdk.Coin and ensures it is a uToken -// associated with a registered Token with Blacklisted == false -func (k Keeper) validateAcceptedUTokenDenom(ctx sdk.Context, udenom string) error { - if !types.HasUTokenPrefix(udenom) { - return types.ErrNotUToken.Wrap(udenom) - } - return k.validateAcceptedDenom(ctx, types.ToTokenDenom(udenom)) -} - // validateAcceptedAsset validates an sdk.Coin and ensures it is a registered Token // with Blacklisted == false func (k Keeper) validateAcceptedAsset(ctx sdk.Context, coin sdk.Coin) error { @@ -37,15 +28,6 @@ func (k Keeper) validateAcceptedAsset(ctx sdk.Context, coin sdk.Coin) error { return k.validateAcceptedDenom(ctx, coin.Denom) } -// validateAcceptedUToken validates an sdk.Coin and ensures it is a uToken -// associated with a registered Token with Blacklisted == false -func (k Keeper) validateAcceptedUToken(ctx sdk.Context, coin sdk.Coin) error { - if err := coin.Validate(); err != nil { - return err - } - return k.validateAcceptedUTokenDenom(ctx, coin.Denom) -} - // validateSupply validates an sdk.Coin and ensures its Denom is a Token with EnableMsgSupply func (k Keeper) validateSupply(ctx sdk.Context, coin sdk.Coin) error { if err := validateBaseToken(coin); err != nil {