diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c4a518ab..94ca20625b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +- [2121](https://github.com/umee-network/umee/pull/2121) Allow `MsgLeveragedLiquidate` to auto-select repay and reward denoms if request fields left blank. - [2114](https://github.com/umee-network/umee/pull/2114) Add borrow factor to `x/leverage` - [2102](https://github.com/umee-network/umee/pull/2102) and [2106](https://github.com/umee-network/umee/pull/2106) Add `MsgLeveragedLiquidate` to `x/leverage` - [2085](https://github.com/umee-network/umee/pull/2085) Add `inspect` query to leverage module, which msut be enabled on a node by running with `-l` liquidator query flag. diff --git a/proto/umee/leverage/v1/tx.proto b/proto/umee/leverage/v1/tx.proto index a4e62a0dfd..2019ee8a57 100644 --- a/proto/umee/leverage/v1/tx.proto +++ b/proto/umee/leverage/v1/tx.proto @@ -55,7 +55,9 @@ service Msg { // this initial borrow to exceed the liquidator's borrow limit as long as it is healthy by the end // of the transaction. Repay amount is calculated automatically, so the liquidator only specifies // repay and reward token denoms. For safety, the liquidator cannot exceed 80% of their borrow limit when - // executing this transaction, instead of the regular 100%. + // executing this transaction, instead of the regular 100%. Also allows repayment and reward denoms to + // be left blank - if not specified, the module will automatically select the first (alphabetically by denom) + // borrow and/or collateral on the target account and the proceed normally. rpc LeveragedLiquidate(MsgLeveragedLiquidate) returns (MsgLeveragedLiquidateResponse); // SupplyCollateral combines the Supply and Collateralize actions. diff --git a/x/leverage/client/cli/tx.go b/x/leverage/client/cli/tx.go index 4eeb43767a..d7f6c372a3 100644 --- a/x/leverage/client/cli/tx.go +++ b/x/leverage/client/cli/tx.go @@ -322,7 +322,7 @@ $ umeed tx leverage liquidate %s 50000000uumee u/uumee --from mykey`, func GetCmdLeveragedLiquidate() *cobra.Command { cmd := &cobra.Command{ Use: "lev-liquidate [borrower] [repay-denom] [reward-denom]", - Args: cobra.ExactArgs(3), + Args: cobra.RangeArgs(1, 3), Short: "Liquidates by moving borrower debt to the liquidator and immediately collateralizes the reward.", Long: strings.TrimSpace( fmt.Sprintf(` @@ -349,8 +349,14 @@ $ umeed tx leverage lev-liquidate %s uumee uumee --from mykey`, return err } - repayDenom := args[1] - rewardDenom := args[2] + var repayDenom, rewardDenom string + if len(args) > 1 { + repayDenom = args[1] + } + + if len(args) > 2 { + rewardDenom = args[2] + } msg := types.NewMsgLeveragedLiquidate(clientCtx.GetFromAddress(), borrowerAddr, repayDenom, rewardDenom) if err = msg.ValidateBasic(); err != nil { diff --git a/x/leverage/keeper/keeper.go b/x/leverage/keeper/keeper.go index b11563e5c2..19de4d66fe 100644 --- a/x/leverage/keeper/keeper.go +++ b/x/leverage/keeper/keeper.go @@ -372,6 +372,21 @@ func (k Keeper) Liquidate( func (k Keeper) LeveragedLiquidate( ctx sdk.Context, liquidatorAddr, borrowerAddr sdk.AccAddress, repayDenom, rewardDenom string, ) (repaid sdk.Coin, reward sdk.Coin, err error) { + // If the message did not specify repay or reward denoms, select one arbitrarily (first in + // denom alphabetical order) from borrower position. Then proceed normally with the transaction. + if repayDenom == "" { + borrowed := k.GetBorrowerBorrows(ctx, borrowerAddr) + if !borrowed.IsZero() { + repayDenom = borrowed[0].Denom + } + } + if rewardDenom == "" { + collateral := k.GetBorrowerCollateral(ctx, borrowerAddr) + if !collateral.IsZero() { + rewardDenom = types.ToTokenDenom(collateral[0].Denom) + } + } + if err := k.validateAcceptedDenom(ctx, repayDenom); err != nil { return sdk.Coin{}, sdk.Coin{}, err } diff --git a/x/leverage/keeper/msg_server_test.go b/x/leverage/keeper/msg_server_test.go index 859e73995a..62931a5b46 100644 --- a/x/leverage/keeper/msg_server_test.go +++ b/x/leverage/keeper/msg_server_test.go @@ -2226,11 +2226,11 @@ func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { coin.New("u/"+atomDenom, 3_527933), nil, }, { - "close factor < 1", + "close factor < 1 with auto-selected repay and reward denoms", liquidator, closeBorrower, - umeeDenom, - umeeDenom, + "", + "", coin.New(umeeDenom, 8_150541), coin.New("u/"+umeeDenom, 8_965596), nil, @@ -2248,7 +2248,22 @@ func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { _, err := srv.LeveragedLiquidate(ctx, msg) require.ErrorIs(err, tc.err, tc.msg) } else { - baseRewardDenom := types.ToTokenDenom(tc.expectedReward.Denom) + // borrower initial state + biBalance := app.BankKeeper.GetAllBalances(ctx, tc.borrower) + biCollateral := app.LeverageKeeper.GetBorrowerCollateral(ctx, tc.borrower) + biBorrowed := app.LeverageKeeper.GetBorrowerBorrows(ctx, tc.borrower) + + // adjust test case in empty-input scenarios, while preserving the msg + if msg.RepayDenom == "" { + if !biBorrowed.IsZero() { + tc.repayDenom = biBorrowed[0].Denom + } + } + if msg.RewardDenom == "" { + if !biCollateral.IsZero() { + tc.rewardDenom = types.ToTokenDenom(biCollateral[0].Denom) + } + } // initial state (borrowed denom) biUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) @@ -2256,12 +2271,7 @@ func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { // initial state (liquidated denom) liUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) - liExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, baseRewardDenom) - - // borrower initial state - biBalance := app.BankKeeper.GetAllBalances(ctx, tc.borrower) - biCollateral := app.LeverageKeeper.GetBorrowerCollateral(ctx, tc.borrower) - biBorrowed := app.LeverageKeeper.GetBorrowerBorrows(ctx, tc.borrower) + liExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, tc.rewardDenom) // liquidator initial state liBalance := app.BankKeeper.GetAllBalances(ctx, tc.liquidator) @@ -2276,7 +2286,7 @@ func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { // final state (liquidated denom) lfUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) - lfExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, baseRewardDenom) + lfExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, tc.rewardDenom) // borrower final state bfBalance := app.BankKeeper.GetAllBalances(ctx, tc.borrower) diff --git a/x/leverage/types/tx.pb.go b/x/leverage/types/tx.pb.go index 962699b6f2..2cb9394199 100644 --- a/x/leverage/types/tx.pb.go +++ b/x/leverage/types/tx.pb.go @@ -1303,7 +1303,9 @@ type MsgClient interface { // this initial borrow to exceed the liquidator's borrow limit as long as it is healthy by the end // of the transaction. Repay amount is calculated automatically, so the liquidator only specifies // repay and reward token denoms. For safety, the liquidator cannot exceed 80% of their borrow limit when - // executing this transaction, instead of the regular 100%. + // executing this transaction, instead of the regular 100%. Also allows repayment and reward denoms to + // be left blank - if not specified, the module will automatically select the first (alphabetically by denom) + // borrow and/or collateral on the target account and the proceed normally. LeveragedLiquidate(ctx context.Context, in *MsgLeveragedLiquidate, opts ...grpc.CallOption) (*MsgLeveragedLiquidateResponse, error) // SupplyCollateral combines the Supply and Collateralize actions. SupplyCollateral(ctx context.Context, in *MsgSupplyCollateral, opts ...grpc.CallOption) (*MsgSupplyCollateralResponse, error) @@ -1462,7 +1464,9 @@ type MsgServer interface { // this initial borrow to exceed the liquidator's borrow limit as long as it is healthy by the end // of the transaction. Repay amount is calculated automatically, so the liquidator only specifies // repay and reward token denoms. For safety, the liquidator cannot exceed 80% of their borrow limit when - // executing this transaction, instead of the regular 100%. + // executing this transaction, instead of the regular 100%. Also allows repayment and reward denoms to + // be left blank - if not specified, the module will automatically select the first (alphabetically by denom) + // borrow and/or collateral on the target account and the proceed normally. LeveragedLiquidate(context.Context, *MsgLeveragedLiquidate) (*MsgLeveragedLiquidateResponse, error) // SupplyCollateral combines the Supply and Collateralize actions. SupplyCollateral(context.Context, *MsgSupplyCollateral) (*MsgSupplyCollateralResponse, error)