From 1972cf9835525235e6ff86db7ed909a4daeb9ac1 Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Fri, 23 Jun 2023 13:52:17 +0200 Subject: [PATCH 1/7] fix: docker build (#2109) * fix dockerfile * test trigger --- contrib/images/umee.e2e.dockerfile | 2 +- contrib/images/umeed.dockerfile | 7 +++---- util/store/iter_test.go | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/contrib/images/umee.e2e.dockerfile b/contrib/images/umee.e2e.dockerfile index b7adabc004..6a0bec8bb6 100644 --- a/contrib/images/umee.e2e.dockerfile +++ b/contrib/images/umee.e2e.dockerfile @@ -21,7 +21,7 @@ RUN if [ "$EXPERIMENTAL" = "true" ] ; then echo "Installing experimental build"; RUN BUILD_TAGS=badgerdb make install ## Prepare the final clear binary -FROM ubuntu:rolling +FROM ubuntu:23.04 EXPOSE 26656 26657 1317 9090 7171 ENTRYPOINT ["umeed", "start"] diff --git a/contrib/images/umeed.dockerfile b/contrib/images/umeed.dockerfile index 2f996ee392..5e0988419e 100644 --- a/contrib/images/umeed.dockerfile +++ b/contrib/images/umeed.dockerfile @@ -14,15 +14,14 @@ RUN LEDGER_ENABLED=false BUILD_TAGS=badgerdb make install # Stage-2: copy binary and required artifacts to a fresh image # we need to use debian compatible system. -FROM ubuntu:rolling +FROM ubuntu:23.04 EXPOSE 26656 26657 1317 9090 -CMD ["umeed"] # Run umeed by default, omit entrypoint to ease using container with CLI +CMD ["umeed"] STOPSIGNAL SIGTERM RUN apt-get update && apt-get install ca-certificates -y \ - && addgroup --gid 1000 umee \ - && useradd -u 1000 -g umee -m umee + && groupadd umee && useradd -g umee -m umee COPY --from=builder /go/bin/umeed /usr/local/bin/ COPY --from=builder /go/pkg/mod/github.com/\!cosm\!wasm/wasmvm\@v*/internal/api/libwasmvm.*.so /usr/lib/ diff --git a/util/store/iter_test.go b/util/store/iter_test.go index 84908264be..7322d41956 100644 --- a/util/store/iter_test.go +++ b/util/store/iter_test.go @@ -53,13 +53,13 @@ func TestSumCoins(t *testing.T) { }{ {"atom", 1}, {"umee", 8}, - {"atom", 8}, // we overwrite + {"atom", 7}, // overwrite the previous value {"ato", 2}, {"atoma", 3}, } expected := sdk.NewCoins( sdk.NewInt64Coin("ato", 2), - sdk.NewInt64Coin("atom", 8), + sdk.NewInt64Coin("atom", 7), sdk.NewInt64Coin("atoma", 3), sdk.NewInt64Coin("umee", 8)) From 0fc0a4d9e41c77128bf4fbc20dfbe70292979ba0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:53:19 +0530 Subject: [PATCH 2/7] build(deps): Bump tj-actions/changed-files from 36 to 37 (#2111) Bumps [tj-actions/changed-files](https://github.com/tj-actions/changed-files) from 36 to 37. - [Release notes](https://github.com/tj-actions/changed-files/releases) - [Changelog](https://github.com/tj-actions/changed-files/blob/main/HISTORY.md) - [Commits](https://github.com/tj-actions/changed-files/compare/v36...v37) --- updated-dependencies: - dependency-name: tj-actions/changed-files dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7082e40fe1..453abb631e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -40,7 +40,7 @@ jobs: with: fetch-depth: 0 # lint only changed files - - uses: tj-actions/changed-files@v36 + - uses: tj-actions/changed-files@v37 id: changed-files with: files: "**/*.md" From f899adfa06673b3624b65404605dce9fb1dec0b2 Mon Sep 17 00:00:00 2001 From: Adam Moser <63419657+toteki@users.noreply.github.com> Date: Mon, 26 Jun 2023 07:09:53 -0600 Subject: [PATCH 3/7] feat: implement fast-liquidation (#2106) * save progress pre-refactor * godoc-- * proto comment update * cleanup PR = empty function * make proto-all * rename to FastLiquidate * reoute returns empty string * changelog * lint * add safety param to assertBorrowHealth * comment++ * ++ * refactor: postLiquidate * implement FastLiquidate msg and adjust rounding * cli and lint * tests * cli doc++ * 80% limit and doc * rename to LeveragedLiquidate * ++ * Update proto/umee/leverage/v1/tx.proto * rename++ * rename++ * lint and docs * lint * Update x/leverage/README.md Co-authored-by: Robert Zaremba * Update x/leverage/client/cli/tx.go Co-authored-by: Robert Zaremba * Update x/leverage/README.md Co-authored-by: Robert Zaremba --------- Co-authored-by: Robert Zaremba --- CHANGELOG.md | 2 +- proto/umee/leverage/v1/tx.proto | 19 +- x/leverage/README.md | 29 ++- x/leverage/client/cli/tx.go | 50 +++++ x/leverage/client/tests/tests.go | 11 ++ x/leverage/keeper/borrows.go | 19 +- x/leverage/keeper/collateral.go | 10 + x/leverage/keeper/keeper.go | 56 ++++-- x/leverage/keeper/liquidate.go | 59 +++--- x/leverage/keeper/liquidate_test.go | 10 +- x/leverage/keeper/msg_server.go | 26 +-- x/leverage/keeper/msg_server_test.go | 238 ++++++++++++++++++++++- x/leverage/types/tx.go | 15 +- x/leverage/types/tx.pb.go | 278 ++++++++++++++------------- x/leverage/types/tx_test.go | 4 +- 15 files changed, 607 insertions(+), 219 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0abe865018..a46ea81b20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features -- [2192](https://github.com/umee-network/umee/pull/2102) Add `MsgFastLiquidate` 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. - [1952](https://github.com/umee-network/umee/pull/1952) Add `x/incentive` module. - [2015](https://github.com/umee-network/umee/pull/2015), [2050](https://github.com/umee-network/umee/pull/2050) Add `x/ugov` module. diff --git a/proto/umee/leverage/v1/tx.proto b/proto/umee/leverage/v1/tx.proto index b146d9d3e9..a4e62a0dfd 100644 --- a/proto/umee/leverage/v1/tx.proto +++ b/proto/umee/leverage/v1/tx.proto @@ -48,14 +48,15 @@ service Msg { // of the target's collateral. rpc Liquidate(MsgLiquidate) returns (MsgLiquidateResponse); - // FastLiquidate allows a user to repay a different user's borrowed coins in exchange for some - // of the target's collateral. For flash liquidations, the tokens to repay are borrowed instead of + // LeveragedLiquidate allows a user to repay a different user's borrowed coins in exchange for some + // of the target's collateral. For leveraged liquidations, the tokens to repay are borrowed instead of // being taken from the liquidator's wallet, and the reward is immediately collateralized. Borrow // limit checks for the liquidator are deferred until after the reward is collateralized, allowing // 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. - rpc FastLiquidate(MsgFastLiquidate) returns (MsgFastLiquidateResponse); + // 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%. + rpc LeveragedLiquidate(MsgLeveragedLiquidate) returns (MsgLeveragedLiquidateResponse); // SupplyCollateral combines the Supply and Collateralize actions. rpc SupplyCollateral(MsgSupplyCollateral) returns (MsgSupplyCollateralResponse); @@ -149,15 +150,15 @@ message MsgLiquidate { string reward_denom = 4; } -// MsgFastLiquidate is the request structure for the FastLiquidate RPC. -message MsgFastLiquidate { +// MsgLeveragedLiquidate is the request structure for the LeveragedLiquidate RPC. +message MsgLeveragedLiquidate { // Liquidator is the account address performing a liquidation and the signer // of the message. string liquidator = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // Borrower is the account whose borrow is being repaid, and collateral consumed, // by the liquidation. It does not sign the message. string borrower = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - // RepayDenom is the base token that the liquidator will borrow and repay on behalf of + // RepayDenom is the base token that the liquidator will borrow in order to repay on behalf of // the borrower. string repay_denom = 3; // RewardDenom is the uToken denom that the liquidator will receive as a liquidation reward @@ -226,8 +227,8 @@ message MsgLiquidateResponse { cosmos.base.v1beta1.Coin reward = 3 [(gogoproto.nullable) = false]; } -// MsgFastLiquidateResponse defines the Msg/FastLiquidate response type. -message MsgFastLiquidateResponse { +// MsgLeveragedLiquidateResponse defines the Msg/LeveragedLiquidate response type. +message MsgLeveragedLiquidateResponse { // Repaid is the amount of base tokens that the liquidator borrowed and repaid // to the module on behalf of the borrower. cosmos.base.v1beta1.Coin repaid = 1 [(gogoproto.nullable) = false]; diff --git a/x/leverage/README.md b/x/leverage/README.md index 99bf7fcac0..7b59cc6dc4 100644 --- a/x/leverage/README.md +++ b/x/leverage/README.md @@ -295,7 +295,34 @@ umeed start ## Messages -See [leverage tx proto](https://github.com/umee-network/umee/blob/main/proto/umee/leverage/v1/tx.proto#L11) for list of supported messages. +See [leverage tx proto](https://github.com/umee-network/umee/blob/main/proto/umee/leverage/v1/tx.proto#L11) for full documentation of supported messages. + +Here are their basic functions: + +### Supplying + +- `MsgSupply`: Supplies base tokens to the module and receives uTokens in exchange. UTokens can later be used to withdraw. +- `MsgWithdraw`: Exchanges uTokens for the base tokens originally supplied, plus interest. UTokens withdrawn can be any combination of wallet uTokens (from supply) or collateral uTokens. When withdrawing collateral, borrow limit cannot be exceeded or the withdrawal will fail. +- `MsgMaxWithdraw`: Withdraws the maximum allowed amount of uTokens, respecting the user's borrow limit and the module's liquidity requirements, if any. + +### Collateralizing + +- `MsgCollateralize`: Sends uTokens to the module as the collateral. Collateral increases a user's borrow limit, but can be siezed in a liquidation if borrowed value exceeds a certain threshold above borrow limit due to price movements or interest owed. Collateral tokens still earn supply interest while collateralized. +- `MsgSupplyCollateral`: Combines `MsgSupply` and `MsgCollateralize`. +- `MsgDecollateralize`: Returns some collateral uTokens to wallet balance, without withdrawing base tokens. Borrow limit cannot be exceeded or the decollateralize will fail. + +### Borrowing + +- `MsgBorrow` Borrows base tokens from the module. Borrow limit cannot be exceeded or the transaction will fail. +- `MsgRepay` Repays borrowed tokens to the module, plus interest owed. + +### Liquidation + +- `MsgLiquidate` Liquidates a borrower whose borrowed value has exceeded their liquidation threshold (which is a certain amount above their borrow limit). The liquidator repays a portion of their debt using base tokens, and receives uTokens from the target's collateral, or the equivalent base tokens. The maximum liquidation amount is restricted by both the liquidator's specified amount and the borrower's liquidation eligibility, which may be partial. +- `MsgLeveragedLiquidate` Liquidates a borrower, but instead of repaying with base tokens from the liquidator wallet balance, moves the debt from the borrower to the liquidator and creates a new borrow position for the liquidator. Liquidator receives uTokens from the bororwer's collateral, and immediately collateralizes them to secure the liquidator positoin. + +This transaction will succeed even if the liquidator could not afford to borrow the initial tokens (thanks to the new collateral position acquired from the borrower), as long as they are below 80% usage of their new borrow limit after the reward collateral is added. +The liquidator is left with a new borrow that they must pay off, and new collateral which can eventually be withdrawn. ## Update Registry Proposal diff --git a/x/leverage/client/cli/tx.go b/x/leverage/client/cli/tx.go index ffd2007ba6..4eeb43767a 100644 --- a/x/leverage/client/cli/tx.go +++ b/x/leverage/client/cli/tx.go @@ -33,6 +33,7 @@ func GetTxCmd() *cobra.Command { GetCmdMaxBorrow(), GetCmdRepay(), GetCmdLiquidate(), + GetCmdLeveragedLiquidate(), GetCmdSupplyCollateral(), ) @@ -316,6 +317,55 @@ $ umeed tx leverage liquidate %s 50000000uumee u/uumee --from mykey`, return cmd } +// GetCmdLeveragedLiquidate creates a Cobra command to generate or broadcast a +// transaction with a MsgLeveragedLiquidate message. +func GetCmdLeveragedLiquidate() *cobra.Command { + cmd := &cobra.Command{ + Use: "lev-liquidate [borrower] [repay-denom] [reward-denom]", + Args: cobra.ExactArgs(3), + Short: "Liquidates by moving borrower debt to the liquidator and immediately collateralizes the reward.", + Long: strings.TrimSpace( + fmt.Sprintf(` +Borrow tokens to liquidate a borrower's debt and immediately collateralize the reward. + +Will attempt to repay the maximum amount allowed by the targeted borrower's debt and collateral positions. + +The transaction will fail if the liquidator, with new borrow and collateral positions, would be above 0.8 borrow limit. + +Example: +$ umeed tx leverage lev-liquidate %s uumee uumee --from mykey`, + "umee1qqy7cst5qm83ldupph2dcq0wypprkfpc9l3jg2", + ), + ), + + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + borrowerAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + repayDenom := args[1] + rewardDenom := args[2] + + msg := types.NewMsgLeveragedLiquidate(clientCtx.GetFromAddress(), borrowerAddr, repayDenom, rewardDenom) + if err = msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + + return cmd +} + // GetCmdSupplyCollateral creates a Cobra command to generate or broadcast a // transaction with a MsgSupply message. func GetCmdSupplyCollateral() *cobra.Command { diff --git a/x/leverage/client/tests/tests.go b/x/leverage/client/tests/tests.go index 483c8f6054..8d7f2201d0 100644 --- a/x/leverage/client/tests/tests.go +++ b/x/leverage/client/tests/tests.go @@ -235,6 +235,16 @@ func (s *IntegrationTests) TestLeverageScenario() { }, ExpectedErr: types.ErrLiquidationIneligible, } + leveragedLiquidate := itestsuite.TestTransaction{ + Name: "liquidate", + Command: cli.GetCmdLeveragedLiquidate(), + Args: []string{ + val.Address.String(), + "uumee", // borrower attempts to liquidate itself, but is ineligible + "uumee", + }, + ExpectedErr: types.ErrLiquidationIneligible, + } repay := itestsuite.TestTransaction{ Name: "repay", @@ -483,6 +493,7 @@ func (s *IntegrationTests) TestLeverageScenario() { // These transactions run after nonzero queries are finished s.RunTestTransactions( liquidate, + leveragedLiquidate, repay, removeCollateral, withdraw, diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index 14723bd40a..de73228f12 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -14,7 +14,10 @@ import ( // unless the remaining collateral is enough to cover all borrows. // This should be checked in msg_server.go at the end of any transaction which is restricted // by borrow limits, i.e. Borrow, Decollateralize, Withdraw, MaxWithdraw. -func (k Keeper) assertBorrowerHealth(ctx sdk.Context, borrowerAddr sdk.AccAddress) error { +// MaxUsage sets the maximum percent of a user's borrow limit that can be in use: set to 1 +// to allow up to 100% borrow limit, or a lower value (e.g. 0.9) if a transaction should fail +// if a safety margin is desired (e.g. <90% borrow limit). +func (k Keeper) assertBorrowerHealth(ctx sdk.Context, borrowerAddr sdk.AccAddress, maxUsage sdk.Dec) error { borrowed := k.GetBorrowerBorrows(ctx, borrowerAddr) collateral := k.GetBorrowerCollateral(ctx, borrowerAddr) @@ -26,9 +29,9 @@ func (k Keeper) assertBorrowerHealth(ctx sdk.Context, borrowerAddr sdk.AccAddres if err != nil { return err } - if value.GT(limit) { + if value.GT(limit.Mul(maxUsage)) { return types.ErrUndercollaterized.Wrapf( - "borrowed: %s, limit: %s", value, limit) + "borrowed: %s, limit: %s, max usage %s", value, limit, maxUsage) } return nil } @@ -52,6 +55,16 @@ func (k Keeper) repayBorrow(ctx sdk.Context, fromAddr, borrowAddr sdk.AccAddress return k.setBorrow(ctx, borrowAddr, k.GetBorrow(ctx, borrowAddr, repay.Denom).Sub(repay)) } +// moveBorrow transfers a debt from fromAddr to toAddr without moving any tokens. This occurs during +// fast liquidations, where a liquidator takes on a borrower's debt. +func (k Keeper) moveBorrow(ctx sdk.Context, fromAddr, toAddr sdk.AccAddress, repay sdk.Coin) error { + err := k.setBorrow(ctx, fromAddr, k.GetBorrow(ctx, fromAddr, repay.Denom).Sub(repay)) + if err != nil { + return err + } + return k.setBorrow(ctx, toAddr, k.GetBorrow(ctx, toAddr, repay.Denom).Add(repay)) +} + // setBorrow sets the amount borrowed by an address in a given denom. // If the amount is zero, any stored value is cleared. func (k Keeper) setBorrow(ctx sdk.Context, borrowerAddr sdk.AccAddress, borrow sdk.Coin) error { diff --git a/x/leverage/keeper/collateral.go b/x/leverage/keeper/collateral.go index 316076e24c..018bf7b52f 100644 --- a/x/leverage/keeper/collateral.go +++ b/x/leverage/keeper/collateral.go @@ -50,6 +50,16 @@ func (k Keeper) decollateralize(ctx sdk.Context, fromAddr, toAddr sdk.AccAddress return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, toAddr, sdk.NewCoins(uToken)) } +// moveCollateral moves collateral from one address to another while keeping the uTokens in the module. +// It occurs during fast liquidations. +func (k Keeper) moveCollateral(ctx sdk.Context, fromAddr, toAddr sdk.AccAddress, uToken sdk.Coin) error { + err := k.setCollateral(ctx, fromAddr, k.GetCollateral(ctx, fromAddr, uToken.Denom).Sub(uToken)) + if err != nil { + return err + } + return k.setCollateral(ctx, toAddr, k.GetCollateral(ctx, toAddr, uToken.Denom).Add(uToken)) +} + // GetTotalCollateral returns an sdk.Coin representing how much of a given uToken // the x/leverage module account currently holds as collateral. Non-uTokens return zero. func (k Keeper) GetTotalCollateral(ctx sdk.Context, denom string) sdk.Coin { diff --git a/x/leverage/keeper/keeper.go b/x/leverage/keeper/keeper.go index ae19d4a25e..b11563e5c2 100644 --- a/x/leverage/keeper/keeper.go +++ b/x/leverage/keeper/keeper.go @@ -330,6 +330,7 @@ func (k Keeper) Liquidate( requestedRepay, rewardDenom, directLiquidation, + false, ) if err != nil { return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err @@ -355,16 +356,8 @@ func (k Keeper) Liquidate( return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err } - // if borrower's collateral has reached zero, mark any remaining borrows as bad debt - if err := k.checkBadDebt(ctx, borrowerAddr); err != nil { - return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err - } - - // finally, force incentive module to update bond and unbonding amounts if required, - // by ending existing unbondings early or instantly unbonding some bonded tokens - // until bonded + unbonding for the account is not greater than its collateral amount - err = k.reduceBondTo(ctx, borrowerAddr, k.GetCollateral(ctx, borrowerAddr, uTokenLiquidate.Denom)) - if err != nil { + // check for bad debt and trigger forced unbond hooks + if err := k.postLiquidate(ctx, borrowerAddr, uTokenLiquidate.Denom); err != nil { return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err } @@ -375,9 +368,44 @@ func (k Keeper) Liquidate( return tokenRepay, uTokenLiquidate, uTokenLiquidate, nil } -// FastLiquidate -func (k Keeper) FastLiquidate( - _ sdk.Context, _, _ sdk.AccAddress, _, _ string, +// LeveragedLiquidate +func (k Keeper) LeveragedLiquidate( + ctx sdk.Context, liquidatorAddr, borrowerAddr sdk.AccAddress, repayDenom, rewardDenom string, ) (repaid sdk.Coin, reward sdk.Coin, err error) { - return sdk.Coin{}, sdk.Coin{}, err + if err := k.validateAcceptedDenom(ctx, repayDenom); err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + if err := k.validateAcceptedDenom(ctx, rewardDenom); err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + uRewardDenom := types.ToUTokenDenom(rewardDenom) + + tokenRepay, uTokenReward, _, err := k.getLiquidationAmounts( + ctx, + liquidatorAddr, + borrowerAddr, + sdk.NewCoin(repayDenom, sdk.OneInt()), // amount is ignored for LeveragedLiquidate + rewardDenom, + false, + true, + ) + if err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + if tokenRepay.IsZero() || uTokenReward.IsZero() { + return sdk.Coin{}, sdk.Coin{}, types.ErrLiquidationRepayZero + } + + // directly move debt from borrower to liquidator without transferring any tokens between accounts + if err := k.moveBorrow(ctx, borrowerAddr, liquidatorAddr, tokenRepay); err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + + // directly move collateral from borrower to liquidator while keeping it collateralized + if err := k.moveCollateral(ctx, borrowerAddr, liquidatorAddr, uTokenReward); err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + + // check for bad debt and trigger forced unbond hooks + return tokenRepay, uTokenReward, k.postLiquidate(ctx, borrowerAddr, uRewardDenom) } diff --git a/x/leverage/keeper/liquidate.go b/x/leverage/keeper/liquidate.go index b441cbcfde..54100681e7 100644 --- a/x/leverage/keeper/liquidate.go +++ b/x/leverage/keeper/liquidate.go @@ -17,6 +17,7 @@ func (k Keeper) getLiquidationAmounts( requestedRepay sdk.Coin, rewardDenom string, directLiquidation bool, + leveragedLiquidate bool, ) (tokenRepay sdk.Coin, collateralLiquidate sdk.Coin, tokenReward sdk.Coin, err error) { repayDenom := requestedRepay.Denom collateralDenom := types.ToUTokenDenom(rewardDenom) @@ -24,7 +25,6 @@ func (k Keeper) getLiquidationAmounts( // get relevant liquidator, borrower, and module balances borrowerCollateral := k.GetBorrowerCollateral(ctx, targetAddr) totalBorrowed := k.GetBorrowerBorrows(ctx, targetAddr) - availableRepay := k.bankKeeper.SpendableCoins(ctx, liquidatorAddr).AmountOf(repayDenom) repayDenomBorrowed := sdk.NewCoin(repayDenom, totalBorrowed.AmountOf(repayDenom)) // calculate borrower health in USD values, using spot prices only (no historic) @@ -93,10 +93,15 @@ func (k Keeper) getLiquidationAmounts( } // max repayment amount is limited by a number of factors - maxRepay := requestedRepay.Amount // maximum allowed by liquidator - maxRepay = sdk.MinInt(maxRepay, availableRepay) // liquidator account balance - maxRepay = sdk.MinInt(maxRepay, totalBorrowed.AmountOf(repayDenom)) // borrower position - maxRepay = sdk.MinInt(maxRepay, maxRepayAfterCloseFactor) // close factor + maxRepay := totalBorrowed.AmountOf(repayDenom) // borrower position + if !leveragedLiquidate { + // for traditional liquidations, liquidator account balance limits repayment + availableRepay := k.bankKeeper.SpendableCoins(ctx, liquidatorAddr).AmountOf(repayDenom) + maxRepay = sdk.MinInt(maxRepay, availableRepay) + // maximum requested by liquidator + maxRepay = sdk.MinInt(maxRepay, requestedRepay.Amount) + } + maxRepay = sdk.MinInt(maxRepay, maxRepayAfterCloseFactor) // close factor // compute final liquidation amounts repay, burn, reward := ComputeLiquidation( @@ -137,7 +142,6 @@ func ComputeLiquidation( // Start with the maximum possible repayment amount, as a decimal maxRepay := toDec(availableRepay) // Determine the base maxReward amount that would result from maximum repayment - maxReward := maxRepay.Mul(priceRatio).Mul(sdk.OneDec().Add(liquidationIncentive)) // Determine the maxCollateral burn amount that corresponds to base reward amount maxCollateral := maxReward.Quo(uTokenExchangeRate) @@ -161,7 +165,9 @@ func ComputeLiquidation( toDec(availableReward).Quo(maxReward), ) // Catch edge cases - ratio = sdk.MaxDec(ratio, sdk.ZeroDec()) + if !ratio.IsPositive() { + return sdk.ZeroInt(), sdk.ZeroInt(), sdk.ZeroInt() + } // Reduce repay and collateral limits by the most severe limiting factor encountered maxRepay = maxRepay.Mul(ratio) @@ -175,16 +181,11 @@ func ComputeLiquidation( // the module. It also ensures borrow dust is always eliminated when encountered. tokenRepay = maxRepay.Ceil().RoundInt() - // Next, the amount of collateral uToken the borrower will lose is rounded down. - // This is favors the borrower over the liquidator, and also protects the module. - collateralBurn = maxCollateral.TruncateInt() - - // One danger to rounding collateral burn down is that of collateral dust. This - // can be considered in two scenarios: - // 1) If collateral was the limiting factor above, then it will have already been - // an integer amount and truncating is a no-op. - // 2) If collateral was not the limiting factor, then there will be a non-dust - // quantity left over anyway. + // Next, the amount of collateral uToken the borrower will lose is rounded up. + // This also eliminates dust, but favors the liquidator over the module slightly. + // This is safe as long as the gas price of a transaction is greater than the value + // of the smallest possible unit of the collateral token. + collateralBurn = maxCollateral.Ceil().RoundInt() // Finally, the base token reward amount is derived directly from the collateral // to burn. This will round down identically to MsgWithdraw, favoring the module @@ -215,11 +216,11 @@ func ComputeLiquidation( // Finally, if borrowedValue is less than smallLiquidationSize, // closeFactor will always be 1 as long as the borrower is eligible for liquidation. func ComputeCloseFactor( - borrowedValue sdk.Dec, - collateralValue sdk.Dec, - liquidationThreshold sdk.Dec, - smallLiquidationSize sdk.Dec, - minimumCloseFactor sdk.Dec, + borrowedValue, + collateralValue, + liquidationThreshold, + smallLiquidationSize, + minimumCloseFactor, completeLiquidationThreshold sdk.Dec, ) (closeFactor sdk.Dec) { if borrowedValue.LT(liquidationThreshold) { @@ -257,3 +258,17 @@ func ComputeCloseFactor( return closeFactor } + +// postLiquidate flags any bad debt and informs incentive module of any changes to an account's +// uToken collateral after a liquidation has occurred. +func (k Keeper) postLiquidate(ctx sdk.Context, borrowerAddr sdk.AccAddress, uDenom string) error { + // if borrower's collateral has reached zero, mark any remaining borrows as bad debt + if err := k.checkBadDebt(ctx, borrowerAddr); err != nil { + return err + } + + // finally, force incentive module to update bond and unbonding amounts if required, + // by ending existing unbondings early or instantly unbonding some bonded tokens + // until bonded + unbonding for the account is not greater than its collateral amount + return k.reduceBondTo(ctx, borrowerAddr, k.GetCollateral(ctx, borrowerAddr, uDenom)) +} diff --git a/x/leverage/keeper/liquidate_test.go b/x/leverage/keeper/liquidate_test.go index e62a8933fd..f8876d55c0 100644 --- a/x/leverage/keeper/liquidate_test.go +++ b/x/leverage/keeper/liquidate_test.go @@ -125,7 +125,7 @@ func TestComputeLiquidation(t *testing.T) { expensiveBorrowDustDown.repayTokenPrice = sdk.MustNewDecFromStr("39.9") expensiveBorrowDustDown.rewardTokenPrice = sdk.MustNewDecFromStr("2") expensiveBorrowDustDown.liquidationIncentive = sdk.MustNewDecFromStr("0") - runTestCase(expensiveBorrowDustDown, 1, 19, 19, "expensive borrow dust with price down") + runTestCase(expensiveBorrowDustDown, 1, 20, 20, "expensive borrow dust with price down") // borrow dust case, with high borrowed token value rounds collateral burn up expensiveBorrowDustUp := baseCase() @@ -133,7 +133,7 @@ func TestComputeLiquidation(t *testing.T) { expensiveBorrowDustUp.repayTokenPrice = sdk.MustNewDecFromStr("40.1") expensiveBorrowDustUp.rewardTokenPrice = sdk.MustNewDecFromStr("2") expensiveBorrowDustUp.liquidationIncentive = sdk.MustNewDecFromStr("0") - runTestCase(expensiveBorrowDustUp, 1, 20, 20, "expensive borrow dust with price up") + runTestCase(expensiveBorrowDustUp, 1, 21, 21, "expensive borrow dust with price up") // borrow dust case, with low borrowed token value rounds collateral burn and reward to zero cheapBorrowDust := baseCase() @@ -141,7 +141,7 @@ func TestComputeLiquidation(t *testing.T) { cheapBorrowDust.repayTokenPrice = sdk.MustNewDecFromStr("2") cheapBorrowDust.rewardTokenPrice = sdk.MustNewDecFromStr("40") cheapBorrowDust.liquidationIncentive = sdk.MustNewDecFromStr("0") - runTestCase(cheapBorrowDust, 1, 0, 0, "cheap borrow dust") + runTestCase(cheapBorrowDust, 1, 1, 1, "cheap borrow dust") // collateral dust case, with high collateral token value and no rounding expensiveCollateralDust := baseCase() @@ -157,7 +157,7 @@ func TestComputeLiquidation(t *testing.T) { expensiveCollateralDustUp.repayTokenPrice = sdk.MustNewDecFromStr("2") expensiveCollateralDustUp.rewardTokenPrice = sdk.MustNewDecFromStr("40.1") expensiveCollateralDustUp.liquidationIncentive = sdk.MustNewDecFromStr("0") - runTestCase(expensiveCollateralDustUp, 21, 0, 0, "expensive collateral dust with price up") + runTestCase(expensiveCollateralDustUp, 21, 1, 1, "expensive collateral dust with price up") // collateral dust case, with high collateral token value rounds required repayment up expensiveCollateralDustDown := baseCase() @@ -165,7 +165,7 @@ func TestComputeLiquidation(t *testing.T) { expensiveCollateralDustDown.repayTokenPrice = sdk.MustNewDecFromStr("2") expensiveCollateralDustDown.rewardTokenPrice = sdk.MustNewDecFromStr("39.9") expensiveCollateralDustDown.liquidationIncentive = sdk.MustNewDecFromStr("0") - runTestCase(expensiveCollateralDustDown, 20, 0, 0, "expensive collateral dust with price down") + runTestCase(expensiveCollateralDustDown, 20, 1, 1, "expensive collateral dust with price down") // collateral dust case, with low collateral token value rounds required repayment up cheapCollateralDust := baseCase() diff --git a/x/leverage/keeper/msg_server.go b/x/leverage/keeper/msg_server.go index 53f2bc51db..01d2973662 100644 --- a/x/leverage/keeper/msg_server.go +++ b/x/leverage/keeper/msg_server.go @@ -77,7 +77,7 @@ func (s msgServer) Withdraw( // Fail here if supplier ends up over their borrow limit under current or historic prices // Tolerates missing collateral prices if the rest of the borrower's collateral can cover all borrows if isFromCollateral { - err = s.keeper.assertBorrowerHealth(ctx, supplierAddr) + err = s.keeper.assertBorrowerHealth(ctx, supplierAddr, sdk.OneDec()) if err != nil { return nil, err } @@ -150,7 +150,7 @@ func (s msgServer) MaxWithdraw( // Fail here if supplier ends up over their borrow limit under current or historic prices // Tolerates missing collateral prices if the rest of the borrower's collateral can cover all borrows if isFromCollateral { - err = s.keeper.assertBorrowerHealth(ctx, supplierAddr) + err = s.keeper.assertBorrowerHealth(ctx, supplierAddr, sdk.OneDec()) if err != nil { return nil, err } @@ -288,7 +288,7 @@ func (s msgServer) Decollateralize( // Fail here if borrower ends up over their borrow limit under current or historic prices // Tolerates missing collateral prices if the rest of the borrower's collateral can cover all borrows - err = s.keeper.assertBorrowerHealth(ctx, borrowerAddr) + err = s.keeper.assertBorrowerHealth(ctx, borrowerAddr, sdk.OneDec()) if err != nil { return nil, err } @@ -321,7 +321,7 @@ func (s msgServer) Borrow( // Fail here if borrower ends up over their borrow limit under current or historic prices // Tolerates missing collateral prices if the rest of the borrower's collateral can cover all borrows - err = s.keeper.assertBorrowerHealth(ctx, borrowerAddr) + err = s.keeper.assertBorrowerHealth(ctx, borrowerAddr, sdk.OneDec()) if err != nil { return nil, err } @@ -390,7 +390,7 @@ func (s msgServer) MaxBorrow( // Fail here if borrower ends up over their borrow limit under current or historic prices // Tolerates missing collateral prices if the rest of the borrower's collateral can cover all borrows - err = s.keeper.assertBorrowerHealth(ctx, borrowerAddr) + err = s.keeper.assertBorrowerHealth(ctx, borrowerAddr, sdk.OneDec()) if err != nil { return nil, err } @@ -488,10 +488,10 @@ func (s msgServer) Liquidate( }, nil } -func (s msgServer) FastLiquidate( +func (s msgServer) LeveragedLiquidate( goCtx context.Context, - msg *types.MsgFastLiquidate, -) (*types.MsgFastLiquidateResponse, error) { + msg *types.MsgLeveragedLiquidate, +) (*types.MsgLeveragedLiquidateResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) liquidator, err := sdk.AccAddressFromBech32(msg.Liquidator) @@ -503,20 +503,20 @@ func (s msgServer) FastLiquidate( return nil, err } - repaid, reward, err := s.keeper.FastLiquidate(ctx, liquidator, borrower, msg.RepayDenom, msg.RewardDenom) + repaid, reward, err := s.keeper.LeveragedLiquidate(ctx, liquidator, borrower, msg.RepayDenom, msg.RewardDenom) if err != nil { return nil, err } - // Fail here if liquidator ends up over their borrow limit under current or historic prices + // Fail here if liquidator ends up over 80% their borrow limit under current or historic prices // Tolerates missing collateral prices if the rest of the liquidator's collateral can cover all borrows - err = s.keeper.assertBorrowerHealth(ctx, liquidator) + err = s.keeper.assertBorrowerHealth(ctx, liquidator, sdk.MustNewDecFromStr("0.8")) if err != nil { return nil, err } s.keeper.Logger(ctx).Debug( - "unhealthy borrower fast-liquidated", + "unhealthy borrower leverage-liquidated", "liquidator", msg.Liquidator, "borrower", msg.Borrower, "repaid", repaid.String(), @@ -527,7 +527,7 @@ func (s msgServer) FastLiquidate( Borrower: msg.Borrower, Liquidated: reward, }) - return &types.MsgFastLiquidateResponse{ + return &types.MsgLeveragedLiquidateResponse{ Repaid: repaid, Reward: reward, }, nil diff --git a/x/leverage/keeper/msg_server_test.go b/x/leverage/keeper/msg_server_test.go index 1766b14e8b..628a4b343c 100644 --- a/x/leverage/keeper/msg_server_test.go +++ b/x/leverage/keeper/msg_server_test.go @@ -1921,8 +1921,8 @@ func (s *IntegrationTestSuite) TestMsgLiquidate() { coin.New(umeeDenom, 200_000000), "u/" + atomDenom, coin.New(umeeDenom, 30_000000), - coin.New("u/"+atomDenom, 3_527932), - coin.New("u/"+atomDenom, 3_527932), + coin.New("u/"+atomDenom, 3_527933), + coin.New("u/"+atomDenom, 3_527933), nil, }, { "close factor < 1", @@ -1931,8 +1931,8 @@ func (s *IntegrationTestSuite) TestMsgLiquidate() { coin.New(umeeDenom, 200_000000), "u/" + umeeDenom, coin.New(umeeDenom, 8_150541), - coin.New("u/"+umeeDenom, 8_965595), - coin.New("u/"+umeeDenom, 8_965595), + coin.New("u/"+umeeDenom, 8_965596), + coin.New("u/"+umeeDenom, 8_965596), nil, }, } @@ -2031,6 +2031,236 @@ func (s *IntegrationTestSuite) TestMsgLiquidate() { } } +func (s *IntegrationTestSuite) TestMsgLeveragedLiquidate() { + app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() + + // create and fund a liquidator which supplies plenty of UMEE and ATOM to the module + liquidator := s.newAccount(coin.New(umeeDenom, 10000_000000), coin.New(atomDenom, 10000_000000)) + s.supply(liquidator, coin.New(umeeDenom, 10000_000000), coin.New(atomDenom, 10000_000000)) + s.collateralize(liquidator, coin.New("u/"+umeeDenom, 10000_000000), coin.New("u/"+atomDenom, 10000_000000)) + + // create a healthy borrower + healthyBorrower := s.newAccount(coin.New(umeeDenom, 100_000000)) + s.supply(healthyBorrower, coin.New(umeeDenom, 100_000000)) + s.collateralize(healthyBorrower, coin.New("u/"+umeeDenom, 100_000000)) + s.borrow(healthyBorrower, coin.New(umeeDenom, 10_000000)) + + // create a borrower which supplies and collateralizes 1000 ATOM + atomBorrower := s.newAccount(coin.New(atomDenom, 1000_000000)) + s.supply(atomBorrower, coin.New(atomDenom, 1000_000000)) + s.collateralize(atomBorrower, coin.New("u/"+atomDenom, 1000_000000)) + // artificially borrow 500 ATOM - this can be liquidated without bad debt + s.forceBorrow(atomBorrower, coin.New(atomDenom, 500_000000)) + + // create a borrower which collateralizes 110 UMEE + umeeBorrower := s.newAccount(coin.New(umeeDenom, 300_000000)) + s.supply(umeeBorrower, coin.New(umeeDenom, 200_000000)) + s.collateralize(umeeBorrower, coin.New("u/"+umeeDenom, 110_000000)) + // artificially borrow 200 UMEE - this will create a bad debt when liquidated + s.forceBorrow(umeeBorrower, coin.New(umeeDenom, 200_000000)) + + // creates a complex borrower with multiple denoms active + complexBorrower := s.newAccount(coin.New(umeeDenom, 100_000000), coin.New(atomDenom, 100_000000)) + s.supply(complexBorrower, coin.New(umeeDenom, 100_000000), coin.New(atomDenom, 100_000000)) + s.collateralize(complexBorrower, coin.New("u/"+umeeDenom, 100_000000), coin.New("u/"+atomDenom, 100_000000)) + // artificially borrow multiple denoms + s.forceBorrow(complexBorrower, coin.New(atomDenom, 30_000000), coin.New(umeeDenom, 30_000000)) + + // creates a realistic borrower with 400 UMEE collateral which will have a close factor < 1 + closeBorrower := s.newAccount(coin.New(umeeDenom, 400_000000)) + s.supply(closeBorrower, coin.New(umeeDenom, 400_000000)) + s.collateralize(closeBorrower, coin.New("u/"+umeeDenom, 400_000000)) + // artificially borrow just barely above liquidation threshold to simulate interest accruing + s.forceBorrow(closeBorrower, coin.New(umeeDenom, 106_000000)) + + tcs := []struct { + msg string + liquidator sdk.AccAddress + borrower sdk.AccAddress + repayDenom string + rewardDenom string + expectedRepay sdk.Coin + expectedReward sdk.Coin + err error + }{ + { + "healthy borrower", + liquidator, + healthyBorrower, + atomDenom, + atomDenom, + sdk.Coin{}, + sdk.Coin{}, + types.ErrLiquidationIneligible, + }, { + "uToken repay", + liquidator, + umeeBorrower, + "u/" + umeeDenom, + umeeDenom, + sdk.Coin{}, + sdk.Coin{}, + types.ErrUToken, + }, { + "uToken reward", + liquidator, + umeeBorrower, + umeeDenom, + "u/" + umeeDenom, + sdk.Coin{}, + sdk.Coin{}, + types.ErrUToken, + }, { + "not registered repay", + liquidator, + umeeBorrower, + "foo", + umeeDenom, + sdk.Coin{}, + sdk.Coin{}, + types.ErrNotRegisteredToken, + }, { + "not registered reward", + liquidator, + umeeBorrower, + atomDenom, + "foo", + sdk.Coin{}, + sdk.Coin{}, + types.ErrNotRegisteredToken, + }, { + "not borrowed denom", + liquidator, + umeeBorrower, + atomDenom, + atomDenom, + sdk.Coin{}, + sdk.Coin{}, + types.ErrLiquidationRepayZero, + }, { + "complete u/atom liquidation", + liquidator, + atomBorrower, + atomDenom, + atomDenom, + coin.New(atomDenom, 500_000000), + coin.New("u/"+atomDenom, 550_000000), + nil, + }, { + "bad debt u/umee liquidation", + liquidator, + umeeBorrower, + umeeDenom, + umeeDenom, + coin.New(umeeDenom, 100_000000), + coin.New("u/"+umeeDenom, 110_000000), + nil, + }, { + "complex borrower", + liquidator, + complexBorrower, + umeeDenom, + atomDenom, + coin.New(umeeDenom, 30_000000), + coin.New("u/"+atomDenom, 3_527933), + nil, + }, { + "close factor < 1", + liquidator, + closeBorrower, + umeeDenom, + umeeDenom, + coin.New(umeeDenom, 8_150541), + coin.New("u/"+umeeDenom, 8_965596), + nil, + }, + } + + for _, tc := range tcs { + msg := &types.MsgLeveragedLiquidate{ + Liquidator: tc.liquidator.String(), + Borrower: tc.borrower.String(), + RepayDenom: tc.repayDenom, + RewardDenom: tc.rewardDenom, + } + if tc.err != nil { + _, err := srv.LeveragedLiquidate(ctx, msg) + require.ErrorIs(err, tc.err, tc.msg) + } else { + baseRewardDenom := types.ToTokenDenom(tc.expectedReward.Denom) + + // initial state (borrowed denom) + biUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) + biExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, tc.repayDenom) + + // 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) + + // liquidator initial state + liBalance := app.BankKeeper.GetAllBalances(ctx, tc.liquidator) + liCollateral := app.LeverageKeeper.GetBorrowerCollateral(ctx, tc.liquidator) + liBorrowed := app.LeverageKeeper.GetBorrowerBorrows(ctx, tc.liquidator) + + // verify the output of fast-liquidate function + resp, err := srv.LeveragedLiquidate(ctx, msg) + require.NoError(err, tc.msg) + require.Equal(tc.expectedRepay.String(), resp.Repaid.String(), tc.msg) + require.Equal(tc.expectedReward.String(), resp.Reward.String(), tc.msg) + + // final state (liquidated denom) + lfUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) + lfExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, baseRewardDenom) + + // borrower final state + bfBalance := app.BankKeeper.GetAllBalances(ctx, tc.borrower) + bfCollateral := app.LeverageKeeper.GetBorrowerCollateral(ctx, tc.borrower) + bfBorrowed := app.LeverageKeeper.GetBorrowerBorrows(ctx, tc.borrower) + + // liquidator final state + lfBalance := app.BankKeeper.GetAllBalances(ctx, tc.liquidator) + lfCollateral := app.LeverageKeeper.GetBorrowerCollateral(ctx, tc.liquidator) + lfBorrowed := app.LeverageKeeper.GetBorrowerBorrows(ctx, tc.liquidator) + + // final state (borrowed denom) + bfUTokenSupply := app.LeverageKeeper.GetAllUTokenSupply(ctx) + bfExchangeRate := app.LeverageKeeper.DeriveExchangeRate(ctx, tc.repayDenom) + + // verify borrowed denom uToken supply is unchanged + require.Equal(biUTokenSupply, bfUTokenSupply, "%s: %s", tc.msg, "uToken supply (borrowed denom") + // verify borrowed denom uToken exchange rate is unchanged + require.Equal(biExchangeRate, bfExchangeRate, "%s: %s", tc.msg, "uToken exchange rate (borrowed denom") + + // verify liquidated denom uToken supply is unchanged + require.Equal(liUTokenSupply, lfUTokenSupply, "%s: %s", tc.msg, "uToken supply (liquidated denom") + // verify liquidated denom uToken exchange rate is unchanged + require.Equal(liExchangeRate, lfExchangeRate, "%s: %s", tc.msg, "uToken exchange rate (liquidated denom") + + // verify borrower balances unchanged + require.Equal(biBalance, bfBalance, "%s: %s", tc.msg, "borrower balances") + // verify borrower collateral reduced by the expected amount + s.requireEqualCoins(biCollateral.Sub(tc.expectedReward), bfCollateral, "%s: %s", tc.msg, "borrower collateral") + // verify borrowed coins decreased by expected amount + s.requireEqualCoins(biBorrowed.Sub(tc.expectedRepay), bfBorrowed, "borrowed coins") + + // verify liquidator balance unchanged + require.Equal(liBalance, lfBalance, tc.msg, "liquidator balances") + // verify liquidator collateral has increased + require.Equal(liCollateral.Add(tc.expectedReward), lfCollateral, "%s: %s", tc.msg, "liquidator collateral") + // verify liquidator borrowed coins has increased + s.requireEqualCoins(liBorrowed.Add(tc.expectedRepay), lfBorrowed, "liquidator borrowed coins") + + // check all available invariants + s.checkInvariants(tc.msg) + } + } +} + func (s *IntegrationTestSuite) TestMaxCollateralShare() { app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() diff --git a/x/leverage/types/tx.go b/x/leverage/types/tx.go index 2e6dbc935b..eb8a227d59 100644 --- a/x/leverage/types/tx.go +++ b/x/leverage/types/tx.go @@ -255,8 +255,9 @@ func (msg *MsgLiquidate) GetSignBytes() []byte { return sdk.MustSortJSON(bz) } -func NewMsgFastLiquidate(liquidator, borrower sdk.AccAddress, repayDenom, rewardDenom string) *MsgFastLiquidate { - return &MsgFastLiquidate{ +func NewMsgLeveragedLiquidate(liquidator, borrower sdk.AccAddress, repayDenom, rewardDenom string, +) *MsgLeveragedLiquidate { + return &MsgLeveragedLiquidate{ Liquidator: liquidator.String(), Borrower: borrower.String(), RepayDenom: repayDenom, @@ -264,10 +265,10 @@ func NewMsgFastLiquidate(liquidator, borrower sdk.AccAddress, repayDenom, reward } } -func (msg MsgFastLiquidate) Route() string { return sdk.MsgTypeURL(&msg) } -func (msg MsgFastLiquidate) Type() string { return sdk.MsgTypeURL(&msg) } +func (msg MsgLeveragedLiquidate) Route() string { return sdk.MsgTypeURL(&msg) } +func (msg MsgLeveragedLiquidate) Type() string { return sdk.MsgTypeURL(&msg) } -func (msg *MsgFastLiquidate) ValidateBasic() error { +func (msg *MsgLeveragedLiquidate) ValidateBasic() error { if err := validateSenderAndDenom(msg.Borrower, msg.RewardDenom); err != nil { return err } @@ -278,12 +279,12 @@ func (msg *MsgFastLiquidate) ValidateBasic() error { return err } -func (msg *MsgFastLiquidate) GetSigners() []sdk.AccAddress { +func (msg *MsgLeveragedLiquidate) GetSigners() []sdk.AccAddress { return checkers.Signers(msg.Liquidator) } // GetSignBytes get the bytes for the message signer to sign on -func (msg *MsgFastLiquidate) GetSignBytes() []byte { +func (msg *MsgLeveragedLiquidate) GetSignBytes() []byte { bz := ModuleCdc.MustMarshalJSON(msg) return sdk.MustSortJSON(bz) } diff --git a/x/leverage/types/tx.pb.go b/x/leverage/types/tx.pb.go index ff19d1681f..962699b6f2 100644 --- a/x/leverage/types/tx.pb.go +++ b/x/leverage/types/tx.pb.go @@ -448,15 +448,15 @@ func (*MsgLiquidate) XXX_MessageName() string { return "umee.leverage.v1.MsgLiquidate" } -// MsgFastLiquidate is the request structure for the FastLiquidate RPC. -type MsgFastLiquidate struct { +// MsgLeveragedLiquidate is the request structure for the LeveragedLiquidate RPC. +type MsgLeveragedLiquidate struct { // Liquidator is the account address performing a liquidation and the signer // of the message. Liquidator string `protobuf:"bytes,1,opt,name=liquidator,proto3" json:"liquidator,omitempty"` // Borrower is the account whose borrow is being repaid, and collateral consumed, // by the liquidation. It does not sign the message. Borrower string `protobuf:"bytes,2,opt,name=borrower,proto3" json:"borrower,omitempty"` - // RepayDenom is the base token that the liquidator will borrow and repay on behalf of + // RepayDenom is the base token that the liquidator will borrow in order to repay on behalf of // the borrower. RepayDenom string `protobuf:"bytes,3,opt,name=repay_denom,json=repayDenom,proto3" json:"repay_denom,omitempty"` // RewardDenom is the uToken denom that the liquidator will receive as a liquidation reward @@ -464,18 +464,18 @@ type MsgFastLiquidate struct { RewardDenom string `protobuf:"bytes,4,opt,name=reward_denom,json=rewardDenom,proto3" json:"reward_denom,omitempty"` } -func (m *MsgFastLiquidate) Reset() { *m = MsgFastLiquidate{} } -func (m *MsgFastLiquidate) String() string { return proto.CompactTextString(m) } -func (*MsgFastLiquidate) ProtoMessage() {} -func (*MsgFastLiquidate) Descriptor() ([]byte, []int) { +func (m *MsgLeveragedLiquidate) Reset() { *m = MsgLeveragedLiquidate{} } +func (m *MsgLeveragedLiquidate) String() string { return proto.CompactTextString(m) } +func (*MsgLeveragedLiquidate) ProtoMessage() {} +func (*MsgLeveragedLiquidate) Descriptor() ([]byte, []int) { return fileDescriptor_72683128ee6e8843, []int{9} } -func (m *MsgFastLiquidate) XXX_Unmarshal(b []byte) error { +func (m *MsgLeveragedLiquidate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgFastLiquidate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgLeveragedLiquidate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgFastLiquidate.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgLeveragedLiquidate.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -485,20 +485,20 @@ func (m *MsgFastLiquidate) XXX_Marshal(b []byte, deterministic bool) ([]byte, er return b[:n], nil } } -func (m *MsgFastLiquidate) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgFastLiquidate.Merge(m, src) +func (m *MsgLeveragedLiquidate) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgLeveragedLiquidate.Merge(m, src) } -func (m *MsgFastLiquidate) XXX_Size() int { +func (m *MsgLeveragedLiquidate) XXX_Size() int { return m.Size() } -func (m *MsgFastLiquidate) XXX_DiscardUnknown() { - xxx_messageInfo_MsgFastLiquidate.DiscardUnknown(m) +func (m *MsgLeveragedLiquidate) XXX_DiscardUnknown() { + xxx_messageInfo_MsgLeveragedLiquidate.DiscardUnknown(m) } -var xxx_messageInfo_MsgFastLiquidate proto.InternalMessageInfo +var xxx_messageInfo_MsgLeveragedLiquidate proto.InternalMessageInfo -func (*MsgFastLiquidate) XXX_MessageName() string { - return "umee.leverage.v1.MsgFastLiquidate" +func (*MsgLeveragedLiquidate) XXX_MessageName() string { + return "umee.leverage.v1.MsgLeveragedLiquidate" } // MsgSupplyCollateral represents a user's request to supply and collateralize assets to the module. @@ -935,8 +935,8 @@ func (*MsgLiquidateResponse) XXX_MessageName() string { return "umee.leverage.v1.MsgLiquidateResponse" } -// MsgFastLiquidateResponse defines the Msg/FastLiquidate response type. -type MsgFastLiquidateResponse struct { +// MsgLeveragedLiquidateResponse defines the Msg/LeveragedLiquidate response type. +type MsgLeveragedLiquidateResponse struct { // Repaid is the amount of base tokens that the liquidator borrowed and repaid // to the module on behalf of the borrower. Repaid types.Coin `protobuf:"bytes,1,opt,name=repaid,proto3" json:"repaid"` @@ -945,18 +945,18 @@ type MsgFastLiquidateResponse struct { Reward types.Coin `protobuf:"bytes,2,opt,name=reward,proto3" json:"reward"` } -func (m *MsgFastLiquidateResponse) Reset() { *m = MsgFastLiquidateResponse{} } -func (m *MsgFastLiquidateResponse) String() string { return proto.CompactTextString(m) } -func (*MsgFastLiquidateResponse) ProtoMessage() {} -func (*MsgFastLiquidateResponse) Descriptor() ([]byte, []int) { +func (m *MsgLeveragedLiquidateResponse) Reset() { *m = MsgLeveragedLiquidateResponse{} } +func (m *MsgLeveragedLiquidateResponse) String() string { return proto.CompactTextString(m) } +func (*MsgLeveragedLiquidateResponse) ProtoMessage() {} +func (*MsgLeveragedLiquidateResponse) Descriptor() ([]byte, []int) { return fileDescriptor_72683128ee6e8843, []int{20} } -func (m *MsgFastLiquidateResponse) XXX_Unmarshal(b []byte) error { +func (m *MsgLeveragedLiquidateResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *MsgFastLiquidateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *MsgLeveragedLiquidateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_MsgFastLiquidateResponse.Marshal(b, m, deterministic) + return xxx_messageInfo_MsgLeveragedLiquidateResponse.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -966,20 +966,20 @@ func (m *MsgFastLiquidateResponse) XXX_Marshal(b []byte, deterministic bool) ([] return b[:n], nil } } -func (m *MsgFastLiquidateResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_MsgFastLiquidateResponse.Merge(m, src) +func (m *MsgLeveragedLiquidateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgLeveragedLiquidateResponse.Merge(m, src) } -func (m *MsgFastLiquidateResponse) XXX_Size() int { +func (m *MsgLeveragedLiquidateResponse) XXX_Size() int { return m.Size() } -func (m *MsgFastLiquidateResponse) XXX_DiscardUnknown() { - xxx_messageInfo_MsgFastLiquidateResponse.DiscardUnknown(m) +func (m *MsgLeveragedLiquidateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgLeveragedLiquidateResponse.DiscardUnknown(m) } -var xxx_messageInfo_MsgFastLiquidateResponse proto.InternalMessageInfo +var xxx_messageInfo_MsgLeveragedLiquidateResponse proto.InternalMessageInfo -func (*MsgFastLiquidateResponse) XXX_MessageName() string { - return "umee.leverage.v1.MsgFastLiquidateResponse" +func (*MsgLeveragedLiquidateResponse) XXX_MessageName() string { + return "umee.leverage.v1.MsgLeveragedLiquidateResponse" } // MsgSupplyCollateralResponse defines the Msg/SupplyCollateral response type. @@ -1123,7 +1123,7 @@ func init() { proto.RegisterType((*MsgMaxBorrow)(nil), "umee.leverage.v1.MsgMaxBorrow") proto.RegisterType((*MsgRepay)(nil), "umee.leverage.v1.MsgRepay") proto.RegisterType((*MsgLiquidate)(nil), "umee.leverage.v1.MsgLiquidate") - proto.RegisterType((*MsgFastLiquidate)(nil), "umee.leverage.v1.MsgFastLiquidate") + proto.RegisterType((*MsgLeveragedLiquidate)(nil), "umee.leverage.v1.MsgLeveragedLiquidate") proto.RegisterType((*MsgSupplyCollateral)(nil), "umee.leverage.v1.MsgSupplyCollateral") proto.RegisterType((*MsgSupplyResponse)(nil), "umee.leverage.v1.MsgSupplyResponse") proto.RegisterType((*MsgWithdrawResponse)(nil), "umee.leverage.v1.MsgWithdrawResponse") @@ -1134,7 +1134,7 @@ func init() { proto.RegisterType((*MsgMaxBorrowResponse)(nil), "umee.leverage.v1.MsgMaxBorrowResponse") proto.RegisterType((*MsgRepayResponse)(nil), "umee.leverage.v1.MsgRepayResponse") proto.RegisterType((*MsgLiquidateResponse)(nil), "umee.leverage.v1.MsgLiquidateResponse") - proto.RegisterType((*MsgFastLiquidateResponse)(nil), "umee.leverage.v1.MsgFastLiquidateResponse") + proto.RegisterType((*MsgLeveragedLiquidateResponse)(nil), "umee.leverage.v1.MsgLeveragedLiquidateResponse") proto.RegisterType((*MsgSupplyCollateralResponse)(nil), "umee.leverage.v1.MsgSupplyCollateralResponse") proto.RegisterType((*MsgGovUpdateRegistry)(nil), "umee.leverage.v1.MsgGovUpdateRegistry") proto.RegisterType((*MsgGovUpdateRegistryResponse)(nil), "umee.leverage.v1.MsgGovUpdateRegistryResponse") @@ -1143,73 +1143,73 @@ func init() { func init() { proto.RegisterFile("umee/leverage/v1/tx.proto", fileDescriptor_72683128ee6e8843) } var fileDescriptor_72683128ee6e8843 = []byte{ - // 1042 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xbf, 0x8f, 0xe3, 0x54, - 0x10, 0x8e, 0xb3, 0x3f, 0x94, 0x4c, 0x76, 0x8f, 0x3d, 0xdf, 0x8a, 0xcb, 0xfa, 0x0e, 0x27, 0x18, - 0x0e, 0xad, 0x4e, 0xac, 0xcd, 0x2e, 0x1c, 0x20, 0xe0, 0x04, 0xe4, 0x4e, 0x9c, 0x74, 0x10, 0xe9, - 0x94, 0x05, 0x21, 0x90, 0x20, 0x38, 0xf1, 0xc3, 0xb1, 0x36, 0xf1, 0x0b, 0x7e, 0x2f, 0xc9, 0x86, - 0x92, 0x8a, 0x82, 0x82, 0x82, 0x82, 0x72, 0x0b, 0x4a, 0x0a, 0x0a, 0x5a, 0x5a, 0xb4, 0x74, 0x27, - 0x2a, 0x2a, 0x04, 0x9b, 0x02, 0xfe, 0x0c, 0xe4, 0xe7, 0xe7, 0x67, 0x3b, 0x71, 0xb2, 0x3e, 0xf6, - 0xd2, 0xe5, 0xbd, 0x99, 0xf9, 0xe6, 0x9b, 0x99, 0x37, 0x93, 0x31, 0xec, 0x0c, 0x7a, 0x08, 0x19, - 0x5d, 0x34, 0x44, 0x9e, 0x69, 0x23, 0x63, 0xb8, 0x6f, 0xd0, 0x63, 0xbd, 0xef, 0x61, 0x8a, 0xe5, - 0x2d, 0x5f, 0xa4, 0x87, 0x22, 0x7d, 0xb8, 0xaf, 0xa8, 0x6d, 0x4c, 0x7a, 0x98, 0x18, 0x2d, 0x93, - 0xf8, 0xaa, 0x2d, 0x44, 0xcd, 0x7d, 0xa3, 0x8d, 0x1d, 0x37, 0xb0, 0x50, 0xae, 0x72, 0x79, 0x8f, - 0xd8, 0x3e, 0x52, 0x8f, 0xd8, 0x5c, 0xb0, 0x13, 0x08, 0x9a, 0xec, 0x64, 0x04, 0x07, 0x2e, 0xda, - 0xb6, 0xb1, 0x8d, 0x83, 0x7b, 0xff, 0x17, 0xbf, 0xad, 0xcc, 0xd0, 0x12, 0x3c, 0x98, 0x82, 0xf6, - 0x29, 0x14, 0xeb, 0xc4, 0x3e, 0x1c, 0xf4, 0xfb, 0xdd, 0xb1, 0xac, 0x40, 0x81, 0xf8, 0xbf, 0x1c, - 0xe4, 0x95, 0xa5, 0xaa, 0xb4, 0x5b, 0x6c, 0x88, 0xb3, 0x7c, 0x0b, 0xd6, 0x4c, 0x42, 0x10, 0x2d, - 0xe7, 0xab, 0xd2, 0x6e, 0xe9, 0x60, 0x47, 0xe7, 0xde, 0xfd, 0x18, 0x74, 0x1e, 0x83, 0x7e, 0x07, - 0x3b, 0x6e, 0x6d, 0xf5, 0xf4, 0xcf, 0x4a, 0xae, 0x11, 0x68, 0x6b, 0x9f, 0x41, 0xa9, 0x4e, 0xec, - 0x0f, 0x1d, 0xda, 0xb1, 0x3c, 0x73, 0xb4, 0x0c, 0x0f, 0x35, 0xb8, 0x54, 0x27, 0x76, 0xdd, 0x3c, - 0xce, 0xe4, 0x64, 0x1b, 0xd6, 0x2c, 0xe4, 0xe2, 0x1e, 0x73, 0x52, 0x6c, 0x04, 0x07, 0x0d, 0xc1, - 0x56, 0x9d, 0xd8, 0x77, 0x70, 0xb7, 0x6b, 0x52, 0xe4, 0x99, 0x5d, 0xe7, 0x4b, 0xe4, 0xa3, 0xb4, - 0xb0, 0xe7, 0xe1, 0x51, 0x84, 0x12, 0x9e, 0xff, 0x2f, 0x55, 0x1b, 0xe4, 0x3a, 0xb1, 0xef, 0xa2, - 0xf6, 0xb2, 0x1d, 0x05, 0x55, 0xad, 0x31, 0x94, 0x65, 0xe0, 0xbf, 0x05, 0x1b, 0x41, 0xce, 0x33, - 0xb8, 0x48, 0xcf, 0xf8, 0x27, 0x50, 0xa8, 0x13, 0xbb, 0x81, 0xfa, 0xe6, 0x78, 0x19, 0x04, 0x7f, - 0x94, 0x18, 0xc3, 0xf7, 0x9c, 0x2f, 0x06, 0x8e, 0x65, 0x52, 0x24, 0xab, 0x00, 0x5d, 0x7e, 0xc0, - 0xa1, 0x97, 0xd8, 0x4d, 0x82, 0x43, 0x7e, 0x8a, 0xc3, 0x6d, 0x28, 0x7a, 0x3e, 0xd1, 0x1e, 0x72, - 0x69, 0x79, 0x25, 0x1b, 0x8f, 0xc8, 0x42, 0x7e, 0x1a, 0x36, 0x3c, 0x34, 0x32, 0x3d, 0xab, 0x19, - 0xe4, 0x61, 0x95, 0xc1, 0x97, 0x82, 0xbb, 0xbb, 0x2c, 0x1b, 0xbf, 0x4a, 0xec, 0x01, 0xbe, 0x63, - 0x12, 0x1a, 0x51, 0x7e, 0x75, 0x96, 0x72, 0xad, 0xfc, 0xfb, 0xcf, 0x7b, 0xdb, 0xdc, 0xf5, 0xdb, - 0x96, 0xe5, 0x21, 0x42, 0x0e, 0xa9, 0xe7, 0xb8, 0x76, 0x22, 0x98, 0x97, 0xa6, 0x83, 0x59, 0x60, - 0x17, 0x85, 0x59, 0x81, 0x12, 0x23, 0xcd, 0x69, 0xae, 0x04, 0x39, 0x62, 0x57, 0x8c, 0x65, 0x96, - 0x40, 0x3a, 0x70, 0x45, 0x8c, 0x93, 0xa8, 0x9d, 0x96, 0xd1, 0xf6, 0x0f, 0xe0, 0xb2, 0xf0, 0xd4, - 0x40, 0xa4, 0x8f, 0x5d, 0x82, 0xe4, 0xd7, 0xa1, 0xe0, 0xa1, 0x36, 0x72, 0x86, 0xc8, 0x62, 0x7e, - 0x32, 0xc0, 0x09, 0x03, 0xad, 0xc1, 0xb8, 0x87, 0x53, 0xe4, 0xf1, 0x60, 0x7e, 0x27, 0xc1, 0x93, - 0xc9, 0xe9, 0x24, 0x70, 0x6f, 0x43, 0x71, 0xc4, 0xef, 0xdc, 0xac, 0xc0, 0x91, 0x45, 0x82, 0x56, - 0xfe, 0x51, 0x69, 0x29, 0x50, 0x9e, 0x9e, 0x77, 0x21, 0x2f, 0xed, 0x3a, 0x28, 0xb3, 0x43, 0x4a, - 0x48, 0xaf, 0xb0, 0xb4, 0x07, 0x6d, 0x2f, 0x2e, 0x0f, 0x61, 0x3b, 0x3e, 0x0e, 0xe2, 0xa9, 0xe3, - 0xaf, 0x2b, 0x7b, 0xea, 0x42, 0x03, 0xed, 0x5d, 0xd6, 0x12, 0x6c, 0x42, 0x08, 0xc0, 0x57, 0x60, - 0xdd, 0x7f, 0x8f, 0x4e, 0x66, 0x38, 0xae, 0xae, 0xfd, 0x26, 0x31, 0x8a, 0xa2, 0xb9, 0x2e, 0x8c, - 0x28, 0xbf, 0x09, 0x10, 0x65, 0x28, 0x6b, 0x05, 0x62, 0x26, 0x81, 0x67, 0xbf, 0x73, 0xb2, 0x8e, - 0x14, 0xae, 0xae, 0x7d, 0x23, 0xb1, 0xea, 0x25, 0x86, 0xc5, 0xc5, 0xe3, 0x89, 0xe8, 0xe4, 0x1f, - 0x8d, 0xce, 0xe7, 0x70, 0x2d, 0xa5, 0xe5, 0x05, 0xa1, 0x7b, 0x70, 0x29, 0xf1, 0x92, 0x32, 0x13, - 0x9b, 0x32, 0xd3, 0x7e, 0xc8, 0xb3, 0x12, 0xde, 0xc3, 0xc3, 0x0f, 0xfa, 0x41, 0xc8, 0xb6, 0x43, - 0xa8, 0x37, 0x96, 0x5f, 0x86, 0xa2, 0x39, 0xa0, 0x1d, 0xec, 0x39, 0x74, 0x7c, 0xee, 0x98, 0x8c, - 0x54, 0xfd, 0x3f, 0x26, 0xea, 0xd0, 0x2e, 0x0a, 0xff, 0x98, 0xd8, 0x41, 0xae, 0x42, 0xc9, 0x42, - 0xa4, 0xed, 0x39, 0x7d, 0xea, 0x60, 0x97, 0x4f, 0xc1, 0xf8, 0x95, 0xfc, 0x06, 0x80, 0x69, 0x59, - 0x4d, 0x8a, 0x8f, 0x90, 0x4b, 0xca, 0xab, 0xd5, 0x95, 0xdd, 0xd2, 0xc1, 0x55, 0x7d, 0x7a, 0xc9, - 0xd3, 0xdf, 0xf7, 0xe5, 0x61, 0xdf, 0x9a, 0x96, 0xc5, 0xce, 0x44, 0xae, 0xc1, 0xe6, 0x80, 0xf1, - 0x0f, 0x01, 0xd6, 0xb2, 0x00, 0x6c, 0x04, 0x36, 0x01, 0xc6, 0x6b, 0xca, 0xd7, 0x27, 0x95, 0xdc, - 0xf7, 0x27, 0x95, 0xdc, 0xbf, 0x27, 0x15, 0xe9, 0xab, 0x7f, 0x7e, 0xba, 0x19, 0x45, 0xa5, 0xa9, - 0x70, 0x3d, 0x2d, 0x4b, 0x61, 0x3d, 0x0e, 0x7e, 0x29, 0xc0, 0x4a, 0x9d, 0xd8, 0xf2, 0x7d, 0x58, - 0xe7, 0x5b, 0xdf, 0xb5, 0x59, 0xd7, 0xa2, 0xa0, 0xca, 0x33, 0x0b, 0x84, 0xa2, 0xc6, 0x0f, 0xa0, - 0x20, 0x96, 0xaf, 0xa7, 0x52, 0x0d, 0x42, 0xb1, 0x72, 0x63, 0xa1, 0x58, 0x20, 0x7e, 0x04, 0xa5, - 0xf8, 0x46, 0x57, 0x4d, 0xb5, 0x8a, 0x69, 0x28, 0xbb, 0xe7, 0x69, 0x08, 0xe8, 0x26, 0x6c, 0x26, - 0x17, 0x3d, 0x2d, 0xd5, 0x34, 0xa1, 0xa3, 0xdc, 0x3c, 0x5f, 0x47, 0x38, 0x40, 0xf0, 0xc4, 0xf4, - 0x8a, 0xf7, 0x6c, 0xaa, 0xf9, 0x94, 0x96, 0xf2, 0x7c, 0x16, 0x2d, 0xe1, 0xe6, 0x3e, 0xac, 0xf3, - 0xed, 0x2b, 0xbd, 0x80, 0x81, 0x70, 0x4e, 0x01, 0xa7, 0x06, 0xf5, 0x21, 0x14, 0xa3, 0x65, 0x4e, - 0x9d, 0x97, 0x4a, 0x8e, 0xf8, 0xdc, 0x62, 0x79, 0xac, 0xf3, 0xd7, 0xf8, 0x7e, 0x97, 0x6a, 0xc0, - 0x64, 0x8a, 0x36, 0x5f, 0x16, 0x67, 0x17, 0x5b, 0xe4, 0x52, 0x0d, 0x84, 0x7c, 0x0e, 0xbb, 0xd9, - 0x41, 0xd9, 0x84, 0xcd, 0xe4, 0xba, 0x95, 0xce, 0x24, 0xa1, 0x33, 0xe7, 0x19, 0xa4, 0x4f, 0xe2, - 0x0e, 0x6c, 0xcd, 0xec, 0x41, 0x37, 0x16, 0x74, 0x53, 0xa4, 0xa6, 0xec, 0x65, 0x52, 0x13, 0x9e, - 0x8e, 0xe0, 0xf2, 0xec, 0x54, 0x4c, 0xcf, 0xc3, 0x8c, 0x9e, 0xa2, 0x67, 0xd3, 0x0b, 0x9d, 0xd5, - 0x1a, 0xa7, 0x7f, 0xab, 0xb9, 0xd3, 0x33, 0x55, 0x7a, 0x78, 0xa6, 0x4a, 0x7f, 0x9d, 0xa9, 0xd2, - 0xb7, 0x13, 0x35, 0x77, 0x3a, 0x51, 0xa5, 0x87, 0x13, 0x35, 0xf7, 0xc7, 0x44, 0xcd, 0x7d, 0xfc, - 0x82, 0xed, 0xd0, 0xce, 0xa0, 0xa5, 0xb7, 0x71, 0xcf, 0xf0, 0xb1, 0xf7, 0x5c, 0x44, 0x47, 0xd8, - 0x3b, 0x62, 0x07, 0x63, 0x78, 0xcb, 0x38, 0x8e, 0x3e, 0x48, 0xe9, 0xb8, 0x8f, 0x48, 0x6b, 0x9d, - 0x7d, 0x8b, 0xbe, 0xf8, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x51, 0xd0, 0xc3, 0x5c, 0x45, 0x0f, - 0x00, 0x00, + // 1044 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x57, 0xcf, 0x8f, 0xdb, 0xc4, + 0x17, 0x8f, 0xb3, 0x3f, 0x94, 0xbc, 0x6c, 0xfb, 0xdd, 0xba, 0xfb, 0xa5, 0x59, 0xb7, 0x75, 0x82, + 0xa1, 0xb0, 0xaa, 0x58, 0x9b, 0x5d, 0x28, 0x20, 0xa0, 0x02, 0xd2, 0x4a, 0x95, 0x0a, 0x91, 0xaa, + 0x2c, 0x08, 0x81, 0x04, 0x8b, 0x13, 0x0f, 0x8e, 0xb5, 0x89, 0x27, 0x78, 0x26, 0xc9, 0x86, 0x23, + 0x27, 0x8e, 0x20, 0x71, 0xe0, 0xb8, 0x07, 0x8e, 0x1c, 0x38, 0xf0, 0x47, 0x2c, 0x82, 0x43, 0xc5, + 0x89, 0x13, 0x82, 0xdd, 0x03, 0xfc, 0x19, 0xc8, 0x33, 0xe3, 0xb1, 0x13, 0x7b, 0xb3, 0x2e, 0x25, + 0xb7, 0xcc, 0xbc, 0xcf, 0xfb, 0xbc, 0xcf, 0x7b, 0x93, 0xf7, 0xf2, 0x02, 0x9b, 0xc3, 0x3e, 0x42, + 0x56, 0x0f, 0x8d, 0x50, 0x60, 0xbb, 0xc8, 0x1a, 0xed, 0x58, 0xf4, 0xd0, 0x1c, 0x04, 0x98, 0x62, + 0x75, 0x3d, 0x34, 0x99, 0x91, 0xc9, 0x1c, 0xed, 0x68, 0x7a, 0x07, 0x93, 0x3e, 0x26, 0x56, 0xdb, + 0x26, 0x21, 0xb4, 0x8d, 0xa8, 0xbd, 0x63, 0x75, 0xb0, 0xe7, 0x73, 0x0f, 0xed, 0x8a, 0xb0, 0xf7, + 0x89, 0x1b, 0x32, 0xf5, 0x89, 0x2b, 0x0c, 0x9b, 0xdc, 0xb0, 0xcf, 0x4e, 0x16, 0x3f, 0x08, 0xd3, + 0x86, 0x8b, 0x5d, 0xcc, 0xef, 0xc3, 0x4f, 0xe2, 0xb6, 0x96, 0x92, 0x25, 0x75, 0x30, 0x80, 0xf1, + 0x31, 0x94, 0x9b, 0xc4, 0xdd, 0x1b, 0x0e, 0x06, 0xbd, 0x89, 0xaa, 0x41, 0x89, 0x84, 0x9f, 0x3c, + 0x14, 0x54, 0x95, 0xba, 0xb2, 0x55, 0x6e, 0xc9, 0xb3, 0x7a, 0x0b, 0x56, 0x6c, 0x42, 0x10, 0xad, + 0x16, 0xeb, 0xca, 0x56, 0x65, 0x77, 0xd3, 0x14, 0xd1, 0xc3, 0x1c, 0x4c, 0x91, 0x83, 0x79, 0x07, + 0x7b, 0x7e, 0x63, 0xf9, 0xf8, 0xf7, 0x5a, 0xa1, 0xc5, 0xd1, 0xc6, 0x27, 0x50, 0x69, 0x12, 0xf7, + 0x7d, 0x8f, 0x76, 0x9d, 0xc0, 0x1e, 0x2f, 0x22, 0x42, 0x03, 0x2e, 0x36, 0x89, 0xdb, 0xb4, 0x0f, + 0x73, 0x05, 0xd9, 0x80, 0x15, 0x07, 0xf9, 0xb8, 0xcf, 0x82, 0x94, 0x5b, 0xfc, 0x60, 0x20, 0x58, + 0x6f, 0x12, 0xf7, 0x0e, 0xee, 0xf5, 0x6c, 0x8a, 0x02, 0xbb, 0xe7, 0x7d, 0x8e, 0x42, 0x96, 0x36, + 0x0e, 0x02, 0x3c, 0x8e, 0x59, 0xa2, 0xf3, 0xbf, 0x95, 0xea, 0x82, 0xda, 0x24, 0xee, 0x5d, 0xd4, + 0x59, 0x74, 0x20, 0xfe, 0xaa, 0x0d, 0xc6, 0xb2, 0x08, 0xfe, 0x37, 0x61, 0x8d, 0xd7, 0x3c, 0x47, + 0x88, 0xec, 0x8a, 0x7f, 0x04, 0xa5, 0x26, 0x71, 0x5b, 0x68, 0x60, 0x4f, 0x16, 0x21, 0xf0, 0x7b, + 0x85, 0x29, 0x7c, 0xc7, 0xfb, 0x6c, 0xe8, 0x39, 0x36, 0x45, 0xaa, 0x0e, 0xd0, 0x13, 0x07, 0x1c, + 0x45, 0x49, 0xdc, 0x4c, 0x69, 0x28, 0xce, 0x68, 0xb8, 0x0d, 0xe5, 0x20, 0x14, 0xda, 0x47, 0x3e, + 0xad, 0x2e, 0xe5, 0xd3, 0x11, 0x7b, 0xa8, 0x4f, 0xc2, 0x5a, 0x80, 0xc6, 0x76, 0xe0, 0xec, 0xf3, + 0x3a, 0x2c, 0x33, 0xfa, 0x0a, 0xbf, 0xbb, 0xcb, 0xaa, 0xf1, 0xb3, 0x02, 0xff, 0x0f, 0xe5, 0x8a, + 0xde, 0x74, 0x62, 0xdd, 0xaf, 0xa4, 0x75, 0x37, 0xaa, 0xbf, 0xfe, 0xb8, 0xbd, 0x21, 0xe2, 0xbf, + 0xe5, 0x38, 0x01, 0x22, 0x64, 0x8f, 0x06, 0x9e, 0xef, 0x4e, 0x65, 0xf4, 0xe2, 0x6c, 0x46, 0x73, + 0xfc, 0xe2, 0x5c, 0x6b, 0x50, 0x61, 0xca, 0x85, 0xd6, 0x25, 0x5e, 0x28, 0x76, 0xc5, 0xa4, 0xe6, + 0xc9, 0xa6, 0x0b, 0x97, 0xe5, 0x4c, 0x89, 0x7b, 0x6a, 0x11, 0xbd, 0xff, 0x00, 0x2e, 0xc9, 0x48, + 0x2d, 0x44, 0x06, 0xd8, 0x27, 0x48, 0x7d, 0x0d, 0x4a, 0x01, 0xea, 0x20, 0x6f, 0x84, 0x1c, 0x16, + 0x27, 0x07, 0x9d, 0x74, 0x30, 0x5a, 0x4c, 0x7b, 0x34, 0x4a, 0xfe, 0x1b, 0xce, 0x6f, 0x14, 0x78, + 0x62, 0x7a, 0x44, 0x49, 0xde, 0xdb, 0x50, 0x1e, 0x8b, 0x3b, 0x3f, 0x2f, 0x71, 0xec, 0x31, 0x25, + 0xab, 0xf8, 0xa8, 0xb2, 0x34, 0xa8, 0xce, 0x0e, 0xbd, 0x48, 0x97, 0x71, 0x0d, 0xb4, 0xf4, 0xa4, + 0x92, 0xd6, 0xcb, 0xac, 0xec, 0xbc, 0xf7, 0xe5, 0xe5, 0x1e, 0x6c, 0x24, 0x67, 0x42, 0xb2, 0x74, + 0xe2, 0xdb, 0x95, 0xbf, 0x74, 0x91, 0x83, 0xf1, 0x36, 0x1b, 0xcc, 0x6c, 0x4c, 0x48, 0xc2, 0x97, + 0x61, 0x35, 0xfc, 0x3e, 0x7a, 0xb9, 0xe9, 0x04, 0xdc, 0xf8, 0x49, 0x61, 0x12, 0x65, 0x73, 0x3d, + 0x36, 0xa3, 0xfa, 0x06, 0x40, 0x5c, 0xa1, 0xbc, 0x2f, 0x90, 0x70, 0xe1, 0x91, 0xc3, 0xce, 0xc9, + 0x3b, 0x57, 0x04, 0xdc, 0xf8, 0x5a, 0x81, 0xeb, 0x99, 0x13, 0xe3, 0xf1, 0x93, 0x8a, 0x35, 0x15, + 0x1f, 0x4d, 0xd3, 0xa7, 0x70, 0x35, 0xa3, 0xef, 0xa5, 0xa0, 0x7b, 0x70, 0x71, 0xea, 0xeb, 0x94, + 0x5b, 0xd8, 0x8c, 0x9b, 0xf1, 0x5d, 0x91, 0xbd, 0xe3, 0x3d, 0x3c, 0x7a, 0x6f, 0xc0, 0x53, 0x76, + 0x3d, 0x42, 0x83, 0x89, 0xfa, 0x12, 0x94, 0xed, 0x21, 0xed, 0xe2, 0xc0, 0xa3, 0x93, 0x73, 0x67, + 0x65, 0x0c, 0x0d, 0x7f, 0xa2, 0xa8, 0x47, 0x7b, 0x28, 0xfa, 0x89, 0x62, 0x07, 0xb5, 0x0e, 0x15, + 0x07, 0x91, 0x4e, 0xe0, 0x0d, 0xa8, 0x87, 0x7d, 0x31, 0x0a, 0x93, 0x57, 0xea, 0xeb, 0x00, 0xb6, + 0xe3, 0xec, 0x53, 0x7c, 0x80, 0x7c, 0x52, 0x5d, 0xae, 0x2f, 0x6d, 0x55, 0x76, 0xaf, 0x98, 0xb3, + 0xeb, 0x9e, 0xf9, 0x6e, 0x68, 0x8f, 0x9a, 0xd7, 0x76, 0x1c, 0x76, 0x26, 0x6a, 0x03, 0x2e, 0x0c, + 0x99, 0xfe, 0x88, 0x60, 0x25, 0x0f, 0xc1, 0x1a, 0xf7, 0xe1, 0x1c, 0xaf, 0x6a, 0x5f, 0x1e, 0xd5, + 0x0a, 0xdf, 0x1e, 0xd5, 0x0a, 0x7f, 0x1f, 0xd5, 0x94, 0x2f, 0xfe, 0xfa, 0xe1, 0x66, 0x9c, 0x95, + 0xa1, 0xc3, 0xb5, 0xac, 0x2a, 0x45, 0xef, 0xb1, 0xfb, 0x4b, 0x09, 0x96, 0x9a, 0xc4, 0x55, 0xef, + 0xc3, 0xaa, 0xd8, 0xff, 0xae, 0xa6, 0x43, 0xcb, 0x07, 0xd5, 0x9e, 0x9a, 0x63, 0x94, 0x6f, 0xfc, + 0x00, 0x4a, 0x72, 0x0d, 0xbb, 0x9e, 0xe9, 0x10, 0x99, 0xb5, 0x1b, 0x73, 0xcd, 0x92, 0xf1, 0x03, + 0xa8, 0x24, 0x77, 0xbb, 0x7a, 0xa6, 0x57, 0x02, 0xa1, 0x6d, 0x9d, 0x87, 0x90, 0xd4, 0xfb, 0x70, + 0x61, 0x7a, 0xe5, 0x33, 0x32, 0x5d, 0xa7, 0x30, 0xda, 0xcd, 0xf3, 0x31, 0x32, 0x00, 0x82, 0xff, + 0xcd, 0x2e, 0x7b, 0x4f, 0x67, 0xba, 0xcf, 0xa0, 0xb4, 0xe7, 0xf2, 0xa0, 0x64, 0x98, 0xfb, 0xb0, + 0x2a, 0xf6, 0xb0, 0xec, 0x07, 0xe4, 0xc6, 0x33, 0x1e, 0x70, 0x66, 0x5a, 0xef, 0x41, 0x39, 0x5e, + 0xeb, 0xf4, 0xb3, 0x4a, 0x29, 0x18, 0x9f, 0x99, 0x6f, 0x4f, 0x74, 0xfe, 0x8a, 0xd8, 0xf4, 0x32, + 0x1d, 0x98, 0x4d, 0x33, 0xce, 0xb6, 0x25, 0xd5, 0x25, 0x56, 0xba, 0x4c, 0x07, 0x69, 0x3f, 0x43, + 0x5d, 0x7a, 0x50, 0xfa, 0xa0, 0x66, 0x2c, 0x5e, 0xcf, 0x66, 0x7b, 0xa7, 0x80, 0x9a, 0x95, 0x13, + 0x28, 0xe3, 0x75, 0x61, 0x3d, 0xb5, 0x1b, 0xdd, 0x98, 0xd3, 0x5c, 0x31, 0x4c, 0xdb, 0xce, 0x05, + 0x93, 0x91, 0x0e, 0xe0, 0x52, 0x7a, 0x48, 0x66, 0x97, 0x25, 0x85, 0xd3, 0xcc, 0x7c, 0xb8, 0x28, + 0x58, 0xa3, 0x75, 0xfc, 0xa7, 0x5e, 0x38, 0x3e, 0xd1, 0x95, 0x87, 0x27, 0xba, 0xf2, 0xc7, 0x89, + 0xae, 0x7c, 0x75, 0xaa, 0x17, 0x8e, 0x4f, 0x75, 0xe5, 0xe1, 0xa9, 0x5e, 0xf8, 0xed, 0x54, 0x2f, + 0x7c, 0xf8, 0xbc, 0xeb, 0xd1, 0xee, 0xb0, 0x6d, 0x76, 0x70, 0xdf, 0x0a, 0xb9, 0xb7, 0x7d, 0x44, + 0xc7, 0x38, 0x38, 0x60, 0x07, 0x6b, 0x74, 0xcb, 0x3a, 0x8c, 0xff, 0xa9, 0xd2, 0xc9, 0x00, 0x91, + 0xf6, 0x2a, 0xfb, 0x93, 0xfa, 0xc2, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x31, 0xf4, 0x11, 0x45, + 0x5e, 0x0f, 0x00, 0x00, } func (this *MsgGovUpdateRegistry) Equal(that interface{}) bool { @@ -1296,14 +1296,15 @@ type MsgClient interface { // Liquidate allows a user to repay a different user's borrowed coins in exchange for some // of the target's collateral. Liquidate(ctx context.Context, in *MsgLiquidate, opts ...grpc.CallOption) (*MsgLiquidateResponse, error) - // FastLiquidate allows a user to repay a different user's borrowed coins in exchange for some - // of the target's collateral. For flash liquidations, the tokens to repay are borrowed instead of + // LeveragedLiquidate allows a user to repay a different user's borrowed coins in exchange for some + // of the target's collateral. For leveraged liquidations, the tokens to repay are borrowed instead of // being taken from the liquidator's wallet, and the reward is immediately collateralized. Borrow // limit checks for the liquidator are deferred until after the reward is collateralized, allowing // 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. - FastLiquidate(ctx context.Context, in *MsgFastLiquidate, opts ...grpc.CallOption) (*MsgFastLiquidateResponse, error) + // 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%. + 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) // GovUpdateRegistry adds new tokens to the token registry or @@ -1400,9 +1401,9 @@ func (c *msgClient) Liquidate(ctx context.Context, in *MsgLiquidate, opts ...grp return out, nil } -func (c *msgClient) FastLiquidate(ctx context.Context, in *MsgFastLiquidate, opts ...grpc.CallOption) (*MsgFastLiquidateResponse, error) { - out := new(MsgFastLiquidateResponse) - err := c.cc.Invoke(ctx, "/umee.leverage.v1.Msg/FastLiquidate", in, out, opts...) +func (c *msgClient) LeveragedLiquidate(ctx context.Context, in *MsgLeveragedLiquidate, opts ...grpc.CallOption) (*MsgLeveragedLiquidateResponse, error) { + out := new(MsgLeveragedLiquidateResponse) + err := c.cc.Invoke(ctx, "/umee.leverage.v1.Msg/LeveragedLiquidate", in, out, opts...) if err != nil { return nil, err } @@ -1454,14 +1455,15 @@ type MsgServer interface { // Liquidate allows a user to repay a different user's borrowed coins in exchange for some // of the target's collateral. Liquidate(context.Context, *MsgLiquidate) (*MsgLiquidateResponse, error) - // FastLiquidate allows a user to repay a different user's borrowed coins in exchange for some - // of the target's collateral. For flash liquidations, the tokens to repay are borrowed instead of + // LeveragedLiquidate allows a user to repay a different user's borrowed coins in exchange for some + // of the target's collateral. For leveraged liquidations, the tokens to repay are borrowed instead of // being taken from the liquidator's wallet, and the reward is immediately collateralized. Borrow // limit checks for the liquidator are deferred until after the reward is collateralized, allowing // 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. - FastLiquidate(context.Context, *MsgFastLiquidate) (*MsgFastLiquidateResponse, error) + // 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%. + LeveragedLiquidate(context.Context, *MsgLeveragedLiquidate) (*MsgLeveragedLiquidateResponse, error) // SupplyCollateral combines the Supply and Collateralize actions. SupplyCollateral(context.Context, *MsgSupplyCollateral) (*MsgSupplyCollateralResponse, error) // GovUpdateRegistry adds new tokens to the token registry or @@ -1500,8 +1502,8 @@ func (*UnimplementedMsgServer) Repay(ctx context.Context, req *MsgRepay) (*MsgRe func (*UnimplementedMsgServer) Liquidate(ctx context.Context, req *MsgLiquidate) (*MsgLiquidateResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Liquidate not implemented") } -func (*UnimplementedMsgServer) FastLiquidate(ctx context.Context, req *MsgFastLiquidate) (*MsgFastLiquidateResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method FastLiquidate not implemented") +func (*UnimplementedMsgServer) LeveragedLiquidate(ctx context.Context, req *MsgLeveragedLiquidate) (*MsgLeveragedLiquidateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method LeveragedLiquidate not implemented") } func (*UnimplementedMsgServer) SupplyCollateral(ctx context.Context, req *MsgSupplyCollateral) (*MsgSupplyCollateralResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SupplyCollateral not implemented") @@ -1676,20 +1678,20 @@ func _Msg_Liquidate_Handler(srv interface{}, ctx context.Context, dec func(inter return interceptor(ctx, in, info, handler) } -func _Msg_FastLiquidate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(MsgFastLiquidate) +func _Msg_LeveragedLiquidate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgLeveragedLiquidate) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(MsgServer).FastLiquidate(ctx, in) + return srv.(MsgServer).LeveragedLiquidate(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/umee.leverage.v1.Msg/FastLiquidate", + FullMethod: "/umee.leverage.v1.Msg/LeveragedLiquidate", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(MsgServer).FastLiquidate(ctx, req.(*MsgFastLiquidate)) + return srv.(MsgServer).LeveragedLiquidate(ctx, req.(*MsgLeveragedLiquidate)) } return interceptor(ctx, in, info, handler) } @@ -1771,8 +1773,8 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Handler: _Msg_Liquidate_Handler, }, { - MethodName: "FastLiquidate", - Handler: _Msg_FastLiquidate_Handler, + MethodName: "LeveragedLiquidate", + Handler: _Msg_LeveragedLiquidate_Handler, }, { MethodName: "SupplyCollateral", @@ -2155,7 +2157,7 @@ func (m *MsgLiquidate) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgFastLiquidate) Marshal() (dAtA []byte, err error) { +func (m *MsgLeveragedLiquidate) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2165,12 +2167,12 @@ func (m *MsgFastLiquidate) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgFastLiquidate) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgLeveragedLiquidate) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgFastLiquidate) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgLeveragedLiquidate) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2543,7 +2545,7 @@ func (m *MsgLiquidateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *MsgFastLiquidateResponse) Marshal() (dAtA []byte, err error) { +func (m *MsgLeveragedLiquidateResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -2553,12 +2555,12 @@ func (m *MsgFastLiquidateResponse) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *MsgFastLiquidateResponse) MarshalTo(dAtA []byte) (int, error) { +func (m *MsgLeveragedLiquidateResponse) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgFastLiquidateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *MsgLeveragedLiquidateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -2872,7 +2874,7 @@ func (m *MsgLiquidate) Size() (n int) { return n } -func (m *MsgFastLiquidate) Size() (n int) { +func (m *MsgLeveragedLiquidate) Size() (n int) { if m == nil { return 0 } @@ -3011,7 +3013,7 @@ func (m *MsgLiquidateResponse) Size() (n int) { return n } -func (m *MsgFastLiquidateResponse) Size() (n int) { +func (m *MsgLeveragedLiquidateResponse) Size() (n int) { if m == nil { return 0 } @@ -4180,7 +4182,7 @@ func (m *MsgLiquidate) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgFastLiquidate) Unmarshal(dAtA []byte) error { +func (m *MsgLeveragedLiquidate) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4203,10 +4205,10 @@ func (m *MsgFastLiquidate) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgFastLiquidate: wiretype end group for non-group") + return fmt.Errorf("proto: MsgLeveragedLiquidate: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgFastLiquidate: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgLeveragedLiquidate: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -5220,7 +5222,7 @@ func (m *MsgLiquidateResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *MsgFastLiquidateResponse) Unmarshal(dAtA []byte) error { +func (m *MsgLeveragedLiquidateResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5243,10 +5245,10 @@ func (m *MsgFastLiquidateResponse) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: MsgFastLiquidateResponse: wiretype end group for non-group") + return fmt.Errorf("proto: MsgLeveragedLiquidateResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: MsgFastLiquidateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MsgLeveragedLiquidateResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: diff --git a/x/leverage/types/tx_test.go b/x/leverage/types/tx_test.go index 99a02ed072..2234cb98d7 100644 --- a/x/leverage/types/tx_test.go +++ b/x/leverage/types/tx_test.go @@ -34,7 +34,7 @@ func TestTxs(t *testing.T) { types.NewMsgMaxBorrow(testAddr, denom), types.NewMsgRepay(testAddr, token), types.NewMsgLiquidate(testAddr, testAddr, token, uDenom), - types.NewMsgFastLiquidate(testAddr, testAddr, token.Denom, uDenom), + types.NewMsgLeveragedLiquidate(testAddr, testAddr, token.Denom, uDenom), } for _, tx := range txs { @@ -65,7 +65,7 @@ func TestRoutes(t *testing.T) { types.NewMsgMaxBorrow(testAddr, denom), types.NewMsgRepay(testAddr, token), types.NewMsgLiquidate(testAddr, testAddr, token, uDenom), - types.NewMsgFastLiquidate(testAddr, testAddr, token.Denom, uDenom), + types.NewMsgLeveragedLiquidate(testAddr, testAddr, token.Denom, uDenom), } for _, tx := range txs { From 8505c7fe51b487135032c56a223ab17361049452 Mon Sep 17 00:00:00 2001 From: Sai Kumar <17549398+gsk967@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:44:23 +0530 Subject: [PATCH 4/7] tests: qa running with docker network (#2105) * cosmwasm qa running with docker live network * remove ths env variable check in e2e test suite setup --- client/client.go | 3 ++- sdkclient/client.go | 3 ++- sdkclient/tx/client.go | 7 ++++--- tests/e2e/setup/setup.go | 22 ++++++++++++++------- tests/qa/cw/config_example.yaml | 13 ------------- tests/qa/cw/cw_test.go | 34 +++++++++++++-------------------- tests/qa/cw/cw_utils.go | 19 ------------------ tests/qa/cw/network_config.go | 29 ---------------------------- tests/qa/cw/setup.go | 9 +++++++++ 9 files changed, 45 insertions(+), 94 deletions(-) delete mode 100644 tests/qa/cw/config_example.yaml delete mode 100644 tests/qa/cw/cw_utils.go delete mode 100644 tests/qa/cw/network_config.go create mode 100644 tests/qa/cw/setup.go diff --git a/client/client.go b/client/client.go index 8f4c7c6bf0..b442f33ec5 100644 --- a/client/client.go +++ b/client/client.go @@ -18,6 +18,7 @@ type Client struct { // Accounts are generated using the list of mnemonics. Each string must be a sequence of words, // eg: `["w11 w12 w13", "w21 w22 w23"]`. Keyring names for created accounts will be: val1, val2.... func NewClient( + chainDataDir, chainID, tmrpcEndpoint, grpcEndpoint string, @@ -25,7 +26,7 @@ func NewClient( gasAdjustment float64, encCfg sdkparams.EncodingConfig, ) (Client, error) { - c, err := sdkclient.NewClient(chainID, tmrpcEndpoint, grpcEndpoint, mnemonics, gasAdjustment, encCfg) + c, err := sdkclient.NewClient(chainDataDir, chainID, tmrpcEndpoint, grpcEndpoint, mnemonics, gasAdjustment, encCfg) if err != nil { return Client{}, err } diff --git a/sdkclient/client.go b/sdkclient/client.go index ea6a9cfab1..4c4334c970 100644 --- a/sdkclient/client.go +++ b/sdkclient/client.go @@ -23,6 +23,7 @@ type Client struct { } func NewClient( + chainDataDir, chainID, tmrpcEndpoint, grpcEndpoint string, @@ -35,7 +36,7 @@ func NewClient( if err != nil { return Client{}, err } - uc.Tx, err = tx.NewClient(chainID, tmrpcEndpoint, mnemonics, gasAdjustment, encCfg) + uc.Tx, err = tx.NewClient(chainDataDir, chainID, tmrpcEndpoint, mnemonics, gasAdjustment, encCfg) return uc, err } diff --git a/sdkclient/tx/client.go b/sdkclient/tx/client.go index f7bc793201..c074c4bde3 100644 --- a/sdkclient/tx/client.go +++ b/sdkclient/tx/client.go @@ -29,10 +29,11 @@ type Client struct { } // Initializes a cosmos sdk client context and transaction factory for -// signing and broadcasting transactions +// signing and broadcasting transactions by passing chainDataDir and remaining func arguments // Note: For signing the transactions accounts are created by names like this val0, val1.... func NewClient( - chainID string, + chainDataDir, + chainID, tmrpcEndpoint string, mnemonics map[string]string, gasAdjustment float64, @@ -45,7 +46,7 @@ func NewClient( encCfg: encCfg, } - c.keyringKeyring, err = keyring.New(keyringAppName, keyring.BackendTest, "", nil, encCfg.Codec) + c.keyringKeyring, err = keyring.New(keyringAppName, keyring.BackendTest, chainDataDir, nil, encCfg.Codec) if err != nil { return nil, err } diff --git a/tests/e2e/setup/setup.go b/tests/e2e/setup/setup.go index abf648a40b..6e89b63251 100644 --- a/tests/e2e/setup/setup.go +++ b/tests/e2e/setup/setup.go @@ -62,6 +62,8 @@ type E2ETestSuite struct { GravityContractAddr string Umee client.Client cdc codec.Codec + MinNetwork bool // MinNetwork defines which runs only validator wihtout price-feeder, gaia and ibc-relayer + } func (s *E2ETestSuite) SetupSuite() { @@ -110,9 +112,13 @@ func (s *E2ETestSuite) SetupSuite() { s.initGenesis() s.initValidatorConfigs() s.runValidators() - s.runPriceFeeder() - s.runGaiaNetwork() - s.runIBCRelayer() + if !s.MinNetwork { + s.runPriceFeeder() + s.runGaiaNetwork() + s.runIBCRelayer() + } else { + s.T().Log("running minimum network withut gaia,price-feeder and ibc-relayer") + } // s.runContractDeployment() // s.runOrchestrators() s.initUmeeClient() @@ -131,9 +137,11 @@ func (s *E2ETestSuite) TearDownSuite() { s.T().Log("tearing down e2e integration test suite...") // s.Require().NoError(s.DkrPool.Purge(s.ethResource)) - s.Require().NoError(s.DkrPool.Purge(s.GaiaResource)) - s.Require().NoError(s.DkrPool.Purge(s.HermesResource)) - s.Require().NoError(s.DkrPool.Purge(s.priceFeederResource)) + if !s.MinNetwork { + s.Require().NoError(s.DkrPool.Purge(s.GaiaResource)) + s.Require().NoError(s.DkrPool.Purge(s.HermesResource)) + s.Require().NoError(s.DkrPool.Purge(s.priceFeederResource)) + } for _, vc := range s.ValResources { s.Require().NoError(s.DkrPool.Purge(vc)) @@ -1062,8 +1070,8 @@ func (s *E2ETestSuite) initUmeeClient() { mnemonics[fmt.Sprintf("val%d", index)] = v.mnemonic } ecfg := app.MakeEncodingConfig() - s.Umee, err = client.NewClient( + s.Chain.dataDir, s.Chain.ID, "tcp://localhost:26657", "tcp://localhost:9090", diff --git a/tests/qa/cw/config_example.yaml b/tests/qa/cw/config_example.yaml deleted file mode 100644 index 596c7dac96..0000000000 --- a/tests/qa/cw/config_example.yaml +++ /dev/null @@ -1,13 +0,0 @@ -api: "http://localhost:1317" -grpc: "localhost:9090" -rpc: "http://localhost:26657" -chain_id: "test-1" -mnemonics: - acc1 : "copper push brief egg scan entry inform record adjust fossil boss egg comic alien upon aspect dry avoid interest fury window hint race symptom" - acc2 : "maximum display century economy unlock van census kite error heart snow filter midnight usage egg venture cash kick motor survey drastic edge muffin visual" - acc3 : "banner spread envelope side kite person disagree path silver will brother under couch edit food venture squirrel civil budget number acquire point work mass" - acc4 : "veteran try aware erosion drink dance decade comic dawn museum release episode original list ability owner size tuition surface ceiling depth seminar capable only" - acc5 : "vacuum burst ordinary enact leaf rabbit gather lend left chase park action dish danger green jeans lucky dish mesh language collect acquire waste load" - acc6 : "open attitude harsh casino rent attitude midnight debris describe spare cancel crisp olive ride elite gallery leaf buffalo sheriff filter rotate path begin soldier" - acc7 : "alley afraid soup fall idea toss can goose become valve initial strong forward bright dish figure check leopard decide warfare hub unusual join cart" - acc8 : "record gift you once hip style during joke field prize dust unique length more pencil transfer quit train device arrive energy sort steak upset" diff --git a/tests/qa/cw/cw_test.go b/tests/qa/cw/cw_test.go index 3c0017e680..4f0217e54e 100644 --- a/tests/qa/cw/cw_test.go +++ b/tests/qa/cw/cw_test.go @@ -6,19 +6,16 @@ package cw import ( "encoding/json" "math/rand" - "os" "strings" "sync" "testing" "time" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdkparams "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" "gotest.tools/v3/assert" - "github.com/umee-network/umee/v5/app" - "github.com/umee-network/umee/v5/client" cwutil "github.com/umee-network/umee/v5/tests/util" ) @@ -33,7 +30,15 @@ var ( cwGroupMsgExecFunc func(name string, msg GroupExecMsg, wg *sync.WaitGroup, accSeq uint64) ) -func TestCWPlusGroup(t *testing.T) { +func TestQA(t *testing.T) { + t.Log("Running qa test...") + qaTest := new(QATest) + qaTest.MinNetwork = true + suite.Run(t, qaTest) +} + +func (qaTest *QATest) TestCWPlusGroup() { + t := qaTest.T() accAddrs := make([]sdk.AccAddress, 0) for i := 0; i < TotalAccs; i++ { privateKey := secp256k1.GenPrivKey() @@ -41,20 +46,11 @@ func TestCWPlusGroup(t *testing.T) { accAddrs = append(accAddrs, sdk.AccAddress(pubKey.Address())) } - // remove if old keyring exists for testing - os.RemoveAll("./keyring-test") - encConfig := app.MakeEncodingConfig() - cc, err := ReadConfig("./config_example.yaml") - assert.NilError(t, err) - // umee client - client, err := UmeeClient(cc, encConfig) - assert.NilError(t, err) - - cw := cwutil.NewCosmwasmTestSuite(t, client) + cw := cwutil.NewCosmwasmTestSuite(qaTest.T(), qaTest.Umee) cw.DeployWasmContract(cwGroupPath) // sender is intital account - admin := client.Tx.SenderAddr() + admin := qaTest.Umee.Tx.SenderAddr() // instantiate Contract initMsg := GroupInitMsg{ Admin: admin.String(), @@ -89,7 +85,7 @@ func TestCWPlusGroup(t *testing.T) { // doing random txs to flood the cosmwasm network wg := &sync.WaitGroup{} - accSeq, err := client.QueryAuthSeq(admin.String()) + accSeq, err := qaTest.Umee.QueryAuthSeq(admin.String()) assert.NilError(t, err) total := 0 @@ -160,7 +156,3 @@ func TestCWPlusGroup(t *testing.T) { assert.NilError(t, err) assert.Equal(t, updateAdmin.UpdateAdmin.Admin, adminQuery.Admin) } - -func UmeeClient(cc *ChainConfig, encConfig sdkparams.EncodingConfig) (client.Client, error) { - return client.NewClient(cc.ChainID, cc.RPC, cc.GRPC, cc.Mnemonics, 1.5, encConfig) -} diff --git a/tests/qa/cw/cw_utils.go b/tests/qa/cw/cw_utils.go deleted file mode 100644 index 0f7908df07..0000000000 --- a/tests/qa/cw/cw_utils.go +++ /dev/null @@ -1,19 +0,0 @@ -package cw - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func GetAttributeValue(resp sdk.TxResponse, eventName, attrKey string) string { - var attrVal string - for _, event := range resp.Logs[0].Events { - if event.Type == eventName { - for _, attribute := range event.Attributes { - if attribute.Key == attrKey { - attrVal = attribute.Value - } - } - } - } - return attrVal -} diff --git a/tests/qa/cw/network_config.go b/tests/qa/cw/network_config.go deleted file mode 100644 index 905d5ecfee..0000000000 --- a/tests/qa/cw/network_config.go +++ /dev/null @@ -1,29 +0,0 @@ -package cw - -import ( - "os" - - "gopkg.in/yaml.v3" -) - -type ChainConfig struct { - RPC string `yaml:"rpc"` - GRPC string `yaml:"grpc"` - API string `yaml:"api"` - ChainID string `yaml:"chain_id"` - Mnemonics map[string]string `yaml:"mnemonics"` -} - -func ReadConfig(configFile string) (*ChainConfig, error) { - data, err := os.ReadFile(configFile) - if err != nil { - return nil, err - } - - cc := ChainConfig{} - err = yaml.Unmarshal(data, &cc) - if err != nil { - return nil, err - } - return &cc, nil -} diff --git a/tests/qa/cw/setup.go b/tests/qa/cw/setup.go new file mode 100644 index 0000000000..ba2825a60c --- /dev/null +++ b/tests/qa/cw/setup.go @@ -0,0 +1,9 @@ +package cw + +import ( + "github.com/umee-network/umee/v5/tests/e2e/setup" +) + +type QATest struct { + setup.E2ETestSuite +} From ec18ed4971084d07462acaade4ef22f2a6a4bb18 Mon Sep 17 00:00:00 2001 From: Sai Kumar <17549398+gsk967@users.noreply.github.com> Date: Tue, 27 Jun 2023 14:45:32 +0530 Subject: [PATCH 5/7] fix cw query for leverage max borrow (#2116) --- app/wasm/query/handle_leverage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/wasm/query/handle_leverage.go b/app/wasm/query/handle_leverage.go index 4fd4441a91..050f2dc2a7 100644 --- a/app/wasm/query/handle_leverage.go +++ b/app/wasm/query/handle_leverage.go @@ -86,6 +86,6 @@ func (q UmeeQuery) HandleMaxBorrow( ctx context.Context, qs lvtypes.QueryServer, ) (proto.Message, error) { - req := &lvtypes.QueryMaxBorrow{Address: q.MaxBorrow.Address, Denom: q.MaxBorrow.Address} + req := &lvtypes.QueryMaxBorrow{Address: q.MaxBorrow.Address, Denom: q.MaxBorrow.Denom} return qs.MaxBorrow(ctx, req) } From 55616601c96216edb2e68372430e083de7f4ca33 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 01:13:13 +0200 Subject: [PATCH 6/7] build(deps): Bump google.golang.org/protobuf from 1.30.0 to 1.31.0 (#2115) Bumps google.golang.org/protobuf from 1.30.0 to 1.31.0. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Adam Moser <63419657+toteki@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9ea37b4ec8..3a41fa5d4a 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/tendermint/tm-db v0.6.7 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 google.golang.org/grpc v1.56.1 - google.golang.org/protobuf v1.30.0 + google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.4.0 mvdan.cc/gofumpt v0.5.0 diff --git a/go.sum b/go.sum index 07502205a5..6d15ad5091 100644 --- a/go.sum +++ b/go.sum @@ -2254,8 +2254,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 426d498c640b12eec2aff952cf904c88c38093a4 Mon Sep 17 00:00:00 2001 From: Adam Moser <63419657+toteki@users.noreply.github.com> Date: Wed, 28 Jun 2023 02:29:58 +0100 Subject: [PATCH 7/7] feat: add borrow factor (#2114) * cl++ * cl++ * rephrase test - sign changes based on cpu architecture so strict equal avoided * implement borrow factor restriction * fix new uToken calc * Update x/leverage/keeper/oracle.go * Update x/leverage/types/token.go * revert * revert * implement borrow factor in userMaxBorrow * implement userMaxBorrow + cause failing wasm test * lint * add denom to test suite registry * max withdaw test * withdraw and max withdraw tests * borrow and maxborrow tests * renaming suggestion * halfDec * rename * README * Update x/leverage/README.md Co-authored-by: Robert Zaremba * readme suggestions --------- Co-authored-by: Robert Zaremba --- CHANGELOG.md | 1 + x/leverage/README.md | 16 ++++ x/leverage/keeper/borrows.go | 27 +++++-- x/leverage/keeper/collateral.go | 18 ++--- x/leverage/keeper/grpc_query.go | 2 +- x/leverage/keeper/grpc_query_test.go | 2 +- x/leverage/keeper/inspector.go | 2 +- x/leverage/keeper/inspector_test.go | 12 ++- x/leverage/keeper/limits.go | 105 +++++++++++++++++++++++---- x/leverage/keeper/liquidate.go | 2 +- x/leverage/keeper/msg_server_test.go | 85 +++++++++++++++++++--- x/leverage/keeper/oracle.go | 38 ++++++++++ x/leverage/keeper/oracle_test.go | 22 +++--- x/leverage/keeper/suite_test.go | 16 ++-- x/leverage/types/token.go | 10 +++ 15 files changed, 295 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a46ea81b20..09c4a518ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +- [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. - [1952](https://github.com/umee-network/umee/pull/1952) Add `x/incentive` module. diff --git a/x/leverage/README.md b/x/leverage/README.md index 7b59cc6dc4..dfc97a437f 100644 --- a/x/leverage/README.md +++ b/x/leverage/README.md @@ -20,6 +20,7 @@ The leverage module depends directly on `x/oracle` for asset prices, and interac - [uToken Exchange Rate](#utoken-exchange-rate) - [Supply Utilization](#supply-utilization) - [Borrow Limit](#borrow-limit) + - [Borrow Factor](#borrow-factor) - [Liquidation Threshold](#liquidation-threshold) - [Borrow APY](#borrow-apy) - [Supplying APY](#supplying-apy) @@ -168,6 +169,21 @@ A user's borrow limit is the sum of the contributions from each denomination of For tokens with hith historic prices enabled (indicated by a `HistoricMedians` parameter greater than zero), each collateral `TokenValue` is computed with `PriceModeLow`, i.e. the lower of either spot price or historic price is used. +#### Borrow Factor + +Each token in the `Token Registry` has a parameter called `CollateralWeight`, always less than 1, which determines the portion of the token's value that goes towards a user's borrow limit, when the token is used as collateral. + +An implied parameter `BorrowFactor` is derived from `CollateralWeight` - specifically, it is the minimum of `2.0` and `1/CollateralWeight`. +The maximum borrow factor of `2.0` allows risky or non-collateral assets (`0 <= CollateralWeight < 0.5`) to be borrowed to a certain minimum degree. + +When a user is borrowing, their borrow limit is whichever is more restrictive of the following two rules: + +- Borrowed value must be less than collateral value times `CollateralWeight` (sum over each collateral asset) +- Borrowed value times `BorrowFactor` (sum over each borrowed asset) must be less than collateral value. + +This means that when the original borrow limit based on collateral weight would allow a higher quality collateral to borrow a risky asset with a small margin of safety, the user's effective collateral weight is reduced to that of the riskier asset. +(Or `0.5` at the minimum.) + #### Historic Borrow Limit, Value The leverage module also makes use of the oracle's historic prices to enforce an additional restriction on borrowing. diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index de73228f12..dcf3419654 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -8,7 +8,8 @@ import ( ) // assertBorrowerHealth returns an error if a borrower is currently above their borrow limit, -// under either recent (historic median) or current prices. It returns an error if +// under either recent (historic median) or current prices. Checks using borrow limit based +// on collateral weight, then check separately for borrow limit using borrow factor. Error if // borrowed asset prices cannot be calculated, but will try to treat collateral whose prices are // unavailable as having zero value. This can still result in a borrow limit being too low, // unless the remaining collateral is enough to cover all borrows. @@ -21,18 +22,34 @@ func (k Keeper) assertBorrowerHealth(ctx sdk.Context, borrowerAddr sdk.AccAddres borrowed := k.GetBorrowerBorrows(ctx, borrowerAddr) collateral := k.GetBorrowerCollateral(ctx, borrowerAddr) - value, err := k.TotalTokenValue(ctx, borrowed, types.PriceModeHigh) + // check health using collateral weight + borrowValue, err := k.TotalTokenValue(ctx, borrowed, types.PriceModeHigh) if err != nil { return err } - limit, err := k.VisibleBorrowLimit(ctx, collateral) + borrowLimit, err := k.VisibleBorrowLimit(ctx, collateral) if err != nil { return err } - if value.GT(limit.Mul(maxUsage)) { + if borrowValue.GT(borrowLimit.Mul(maxUsage)) { return types.ErrUndercollaterized.Wrapf( - "borrowed: %s, limit: %s, max usage %s", value, limit, maxUsage) + "borrowed: %s, limit: %s, max usage %s", borrowValue, borrowLimit, maxUsage) } + + // check health using borrow factor + weightedBorrowValue, err := k.ValueWithBorrowFactor(ctx, borrowed, types.PriceModeHigh) + if err != nil { + return err + } + collateralValue, err := k.VisibleUTokensValue(ctx, collateral, types.PriceModeLow) + if err != nil { + return err + } + if weightedBorrowValue.GT(collateralValue.Mul(maxUsage)) { + return types.ErrUndercollaterized.Wrapf( + "weighted borrow: %s, collateral value: %s, max usage %s", weightedBorrowValue, collateralValue, maxUsage) + } + return nil } diff --git a/x/leverage/keeper/collateral.go b/x/leverage/keeper/collateral.go index 018bf7b52f..200fc78494 100644 --- a/x/leverage/keeper/collateral.go +++ b/x/leverage/keeper/collateral.go @@ -73,9 +73,9 @@ func (k Keeper) GetTotalCollateral(ctx sdk.Context, denom string) sdk.Coin { } // CalculateCollateralValue uses the price oracle to determine the value (in USD) provided by -// collateral sdk.Coins, using each token's uToken exchange rate. Always uses spot price. +// collateral sdk.Coins, using each token's uToken exchange rate. // An error is returned if any input coins are not uTokens or if value calculation fails. -func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins) (sdk.Dec, error) { +func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { total := sdk.ZeroDec() for _, coin := range collateral { @@ -86,12 +86,12 @@ func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins) } // get USD value of base assets - v, err := k.TokenValue(ctx, baseAsset, types.PriceModeSpot) + v, err := k.TokenValue(ctx, baseAsset, mode) if err != nil { return sdk.ZeroDec(), err } - // add each collateral coin's weighted value to borrow limit + // add each collateral coin's value to borrow limit total = total.Add(v) } @@ -99,10 +99,10 @@ func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins) } // VisibleCollateralValue uses the price oracle to determine the value (in USD) provided by -// collateral sdk.Coins, using each token's uToken exchange rate. Always uses spot price. +// collateral sdk.Coins, using each token's uToken exchange rate. // Unlike CalculateCollateralValue, this function will not return an error if value calculation // fails on a token - instead, that token will contribute zero value to the total. -func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins) (sdk.Dec, error) { +func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { total := sdk.ZeroDec() for _, coin := range collateral { @@ -113,7 +113,7 @@ func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins) (s } // get USD value of base assets - v, err := k.TokenValue(ctx, baseAsset, types.PriceModeSpot) + v, err := k.TokenValue(ctx, baseAsset, mode) if err == nil { // for coins that did not error, add their value to the total total = total.Add(v) @@ -169,13 +169,13 @@ func (k *Keeper) VisibleCollateralShare(ctx sdk.Context, denom string) (sdk.Dec, thisCollateral := sdk.NewCoins(sdk.NewCoin(denom, systemCollateral.AmountOf(denom))) // get USD collateral value for all uTokens combined, except those experiencing price outages - totalValue, err := k.VisibleCollateralValue(ctx, systemCollateral) + totalValue, err := k.VisibleCollateralValue(ctx, systemCollateral, types.PriceModeSpot) if err != nil { return sdk.ZeroDec(), err } // get USD collateral value for this uToken only - thisValue, err := k.CalculateCollateralValue(ctx, thisCollateral) + thisValue, err := k.CalculateCollateralValue(ctx, thisCollateral, types.PriceModeSpot) if err != nil { return sdk.ZeroDec(), err } diff --git a/x/leverage/keeper/grpc_query.go b/x/leverage/keeper/grpc_query.go index 3d63a4c639..16ad0d4621 100644 --- a/x/leverage/keeper/grpc_query.go +++ b/x/leverage/keeper/grpc_query.go @@ -228,7 +228,7 @@ func (q Querier) AccountSummary( } // collateral value always uses spot prices, and this line skips assets that are missing prices - collateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral) + collateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral, types.PriceModeSpot) if err != nil { return nil, err } diff --git a/x/leverage/keeper/grpc_query_test.go b/x/leverage/keeper/grpc_query_test.go index 28119e7db7..6c984c7cba 100644 --- a/x/leverage/keeper/grpc_query_test.go +++ b/x/leverage/keeper/grpc_query_test.go @@ -25,7 +25,7 @@ func (s *IntegrationTestSuite) TestQuerier_RegisteredTokens() { "valid: get the all registered tokens", "", types.QueryRegisteredTokens{}, - 5, + 6, }, { "valid: get the registered token info by base_denom", diff --git a/x/leverage/keeper/inspector.go b/x/leverage/keeper/inspector.go index d3058e6792..5a5f113f5f 100644 --- a/x/leverage/keeper/inspector.go +++ b/x/leverage/keeper/inspector.go @@ -68,7 +68,7 @@ func (q Querier) Inspect( borrowed := k.GetBorrowerBorrows(ctx, addr) borrowedValue, _ := k.TotalTokenValue(ctx, borrowed, types.PriceModeSpot) collateral := k.GetBorrowerCollateral(ctx, addr) - collateralValue, _ := k.CalculateCollateralValue(ctx, collateral) + collateralValue, _ := k.CalculateCollateralValue(ctx, collateral, types.PriceModeSpot) liquidationThreshold, _ := k.CalculateLiquidationThreshold(ctx, collateral) account := types.InspectAccount{ diff --git a/x/leverage/keeper/inspector_test.go b/x/leverage/keeper/inspector_test.go index e9bf57df06..e13d2c5a05 100644 --- a/x/leverage/keeper/inspector_test.go +++ b/x/leverage/keeper/inspector_test.go @@ -25,13 +25,17 @@ func TestNeat(t *testing.T) { "-12.555": -12.55, // truncates default to cent "-0.00123456789": -0.001234, // truncates <0.01 to millionth "-0.000000987654321": -0.000000987654321, // <0.000001 gets maximum precision - // edge case: >2^64 displays incorrectly - // this should be fine, since this is a display-only function (not used in transactions) - // which is used on dollar (not token) amounts - "123456789123456789123456789.123456789": -9.223372036854776e+21, } for s, f := range cases { assert.Equal(f, neat(sdk.MustNewDecFromStr(s))) } + + // edge case: >2^64 displays incorrectly + // this should be fine, since this is a display-only function (not used in transactions) + // which is used on dollar (not token) amounts + assert.NotEqual( + 123456789123456789123456789.123456789, + neat(sdk.MustNewDecFromStr("123456789123456789123456789.123456789")), + ) } diff --git a/x/leverage/keeper/limits.go b/x/leverage/keeper/limits.go index 5b0e28185e..16cb9030ba 100644 --- a/x/leverage/keeper/limits.go +++ b/x/leverage/keeper/limits.go @@ -7,9 +7,9 @@ import ( ) // userMaxWithdraw calculates the maximum amount of uTokens an account can currently withdraw and the amount of -// these uTokens is non-collateral. Input denom should be a base token. If oracle prices are missing for some of the -// borrower's collateral (other than the denom being withdrawn), computes the maximum safe withdraw allowed by only -// the collateral whose prices are known. +// these uTokens which are non-collateral. Input denom should be a base token. If oracle prices are missing for +// some of the borrower's collateral (other than the denom being withdrawn), computes the maximum safe withdraw +// allowed by only the collateral whose prices are known. func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom string) (sdk.Coin, sdk.Coin, error) { uDenom := types.ToUTokenDenom(denom) availableTokens := sdk.NewCoin(denom, k.AvailableLiquidity(ctx, denom)) @@ -37,6 +37,27 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil } + // calculate collateral value for the account, using the lower of spot or historic prices for each token + // will count collateral with missing prices as zero value without returning an error + collateralValue, err := k.VisibleCollateralValue(ctx, totalCollateral, types.PriceModeLow) + if err != nil { + // for errors besides a missing price, the whole transaction fails + return sdk.Coin{}, sdk.Coin{}, err + } + + // calculate weighted borrowed value - used by the borrow factor limit + weightedBorrowValue, err := k.ValueWithBorrowFactor(ctx, totalBorrowed, types.PriceModeHigh) + if nonOracleError(err) { + // for errors besides a missing price, the whole transaction fails + return sdk.Coin{}, sdk.Coin{}, err + } + if err != nil { + // for missing prices on borrowed assets, we can't withdraw any collateral + // but can withdraw non-collateral uTokens + withdrawAmount := sdk.MinInt(walletUtokens, availableUTokens.Amount) + return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil + } + // if no non-blacklisted tokens are borrowed, withdraw the maximum available amount if borrowedValue.IsZero() { withdrawAmount := walletUtokens.Add(unbondedCollateral.Amount) @@ -46,15 +67,24 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str // compute the borrower's borrow limit using all their collateral // except the denom being withdrawn (also excluding collateral missing oracle prices) - otherBorrowLimit, err := k.VisibleBorrowLimit(ctx, otherCollateral) + otherCollateralBorrowLimit, err := k.VisibleBorrowLimit(ctx, otherCollateral) if err != nil { return sdk.Coin{}, sdk.Coin{}, err } // if their other collateral fully covers all borrows, withdraw the maximum available amount - if borrowedValue.LT(otherBorrowLimit) { - withdrawAmount := walletUtokens.Add(unbondedCollateral.Amount) - withdrawAmount = sdk.MinInt(withdrawAmount, availableUTokens.Amount) - return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil + if borrowedValue.LT(otherCollateralBorrowLimit) { + // also check collateral value vs weighted borrow (borrow factor limit) + otherCollateralValue, err := k.VisibleCollateralValue(ctx, otherCollateral, types.PriceModeLow) + if err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + // if weighted borrow does not exceed other collateral value, this collateral can be fully withdrawn + if otherCollateralValue.GTE(weightedBorrowValue) { + // in this case, both borrow limits will not be exceeded even if all collateral is withdrawn + withdrawAmount := walletUtokens.Add(unbondedCollateral.Amount) + withdrawAmount = sdk.MinInt(withdrawAmount, availableUTokens.Amount) + return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil + } } // for nonzero borrows, calculations are based on unused borrow limit @@ -64,8 +94,8 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str if err != nil { return sdk.Coin{}, sdk.Coin{}, err } - // borrowers above their borrow limit cannot withdraw collateral, but can withdraw wallet uTokens - if borrowLimit.LTE(borrowedValue) { + // borrowers above either of their borrow limits cannot withdraw collateral, but can withdraw wallet uTokens + if borrowLimit.LTE(borrowedValue) || collateralValue.LTE(weightedBorrowValue) { withdrawAmount := sdk.MinInt(walletUtokens, availableUTokens.Amount) return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil } @@ -81,8 +111,19 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str return sdk.Coin{}, sdk.Coin{}, err } - // if only a portion of collateral is unused, withdraw only that portion + // if only a portion of collateral is unused, withdraw only that portion (regular borrow limit) unusedCollateralFraction := unusedBorrowLimit.Quo(specificBorrowLimit) + + // calculate value of this collateral specifically, which is used in borrow factor's borrow limit + specificCollateralValue, err := k.CalculateCollateralValue(ctx, sdk.NewCoins(thisCollateral), types.PriceModeLow) + if err != nil { + return sdk.Coin{}, sdk.Coin{}, err + } + unusedCollateralValue := collateralValue.Sub(weightedBorrowValue) + // Find the more restrictive of either borrow factor limit or borrow limit + unusedCollateralFraction = sdk.MinDec(unusedCollateralFraction, unusedCollateralValue.Quo(specificCollateralValue)) + + // Both borrow limits are satisfied by this withdrawal amount. The restrictions below relate to neither. unusedCollateral := unusedCollateralFraction.MulInt(thisCollateral.Amount).TruncateInt() // find the minimum of unused collateral (due to borrows) or unbonded collateral (incentive module) @@ -101,11 +142,16 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str // userMaxBorrow calculates the maximum amount of a given token an account can currently borrow. // input denom should be a base token. If oracle prices are missing for some of the borrower's -// collateral, computes the maximum safe borrow allowed by only the collateral whose prices are known +// collateral, computes the maximum safe borrow allowed by only the collateral whose prices are known. func (k *Keeper) userMaxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom string) (sdk.Coin, error) { if types.HasUTokenPrefix(denom) { return sdk.Coin{}, types.ErrUToken } + token, err := k.GetTokenSettings(ctx, denom) + if err != nil { + return sdk.Coin{}, err + } + availableTokens := k.AvailableLiquidity(ctx, denom) totalBorrowed := k.GetBorrowerBorrows(ctx, addr) @@ -122,6 +168,17 @@ func (k *Keeper) userMaxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom strin return sdk.NewCoin(denom, sdk.ZeroInt()), nil } + // calculate weighted borrowed value for the account, using the higher of spot or historic prices + weightedBorrowedValue, err := k.ValueWithBorrowFactor(ctx, totalBorrowed, types.PriceModeHigh) + if nonOracleError(err) { + // non-oracle errors fail the transaction (or query) + return sdk.Coin{}, err + } + if err != nil { + // oracle errors cause max borrow to be zero + return sdk.NewCoin(denom, sdk.ZeroInt()), nil + } + // calculate borrow limit for the account, using only collateral whose price is known borrowLimit, err := k.VisibleBorrowLimit(ctx, totalCollateral) if err != nil { @@ -132,11 +189,27 @@ func (k *Keeper) userMaxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom strin return sdk.NewCoin(denom, sdk.ZeroInt()), nil } + // calculate collateral value limit for the account, using only collateral whose price is known + collateralValue, err := k.VisibleCollateralValue(ctx, totalCollateral, types.PriceModeLow) + if err != nil { + return sdk.Coin{}, err + } + // borrowers above their borrow factor borrow limit cannot borrow + if collateralValue.LTE(weightedBorrowedValue) { + return sdk.NewCoin(denom, sdk.ZeroInt()), nil + } + // determine the USD amount of borrow limit that is currently unused unusedBorrowLimit := borrowLimit.Sub(borrowedValue) + // determine the USD amount that can be borrowed according to borrow factor limit + maxBorrowValueIncrease := collateralValue.Sub(weightedBorrowedValue).Quo(token.BorrowFactor()) + + // finds the most restrictive of regular borrow limit and borrow factor limit + valueToBorrow := sdk.MinDec(unusedBorrowLimit, maxBorrowValueIncrease) + // determine max borrow, using the higher of spot or historic prices for the token to borrow - maxBorrow, err := k.TokenWithValue(ctx, denom, unusedBorrowLimit, types.PriceModeHigh) + maxBorrow, err := k.TokenWithValue(ctx, denom, valueToBorrow, types.PriceModeHigh) if nonOracleError(err) { // non-oracle errors fail the transaction (or query) return sdk.Coin{}, err @@ -177,7 +250,11 @@ func (k *Keeper) maxCollateralFromShare(ctx sdk.Context, denom string) (sdkmath. thisDenomCollateral := sdk.NewCoin(denom, systemCollateral.AmountOf(denom)) // get USD collateral value for all other denoms, skipping those which are missing oracle prices - otherDenomsValue, err := k.VisibleCollateralValue(ctx, systemCollateral.Sub(thisDenomCollateral)) + otherDenomsValue, err := k.VisibleCollateralValue( + ctx, + systemCollateral.Sub(thisDenomCollateral), + types.PriceModeSpot, + ) if err != nil { return sdk.ZeroInt(), err } diff --git a/x/leverage/keeper/liquidate.go b/x/leverage/keeper/liquidate.go index 54100681e7..c346fec0e5 100644 --- a/x/leverage/keeper/liquidate.go +++ b/x/leverage/keeper/liquidate.go @@ -33,7 +33,7 @@ func (k Keeper) getLiquidationAmounts( if err != nil { return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err } - collateralValue, err := k.CalculateCollateralValue(ctx, borrowerCollateral) + collateralValue, err := k.CalculateCollateralValue(ctx, borrowerCollateral, types.PriceModeSpot) if err != nil { return sdk.Coin{}, sdk.Coin{}, sdk.Coin{}, err } diff --git a/x/leverage/keeper/msg_server_test.go b/x/leverage/keeper/msg_server_test.go index 628a4b343c..859e73995a 100644 --- a/x/leverage/keeper/msg_server_test.go +++ b/x/leverage/keeper/msg_server_test.go @@ -98,7 +98,7 @@ func (s *IntegrationTestSuite) TestAddTokensToRegistry() { s.Require().NoError(err) // no tokens should have been deleted tokens := s.app.LeverageKeeper.GetAllRegisteredTokens(s.ctx) - s.Require().Len(tokens, 6) + s.Require().Len(tokens, 7) token, err := s.app.LeverageKeeper.GetTokenSettings(s.ctx, ntA.BaseDenom) s.Require().NoError(err) @@ -170,7 +170,7 @@ func (s *IntegrationTestSuite) TestUpdateRegistry() { s.Require().NoError(err) // no tokens should have been deleted tokens := s.app.LeverageKeeper.GetAllRegisteredTokens(s.ctx) - s.Require().Len(tokens, 5) + s.Require().Len(tokens, 6) token, err := s.app.LeverageKeeper.GetTokenSettings(s.ctx, "uumee") s.Require().NoError(err) @@ -350,6 +350,13 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { // borrowed value is $10 (current) or $5 (historic) // collateral weights are always 0.25 in testing + // create an UMEE borrower using STABLE collateral + stableUmeeBorrower := s.newAccount(coin.New(stableDenom, 100_000000)) + s.supply(stableUmeeBorrower, coin.New(stableDenom, 100_000000)) + s.collateralize(stableUmeeBorrower, coin.New("u/"+stableDenom, 100_000000)) + s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) + // UMEE and STABLE have the same price but different collateral weights + tcs := []struct { msg string addr sdk.AccAddress @@ -455,6 +462,14 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { nil, sdk.Coin{}, types.ErrUndercollaterized, + }, { + "borrow limit (undercollateralized due to borrow factor but not collateral weight)", + stableUmeeBorrower, + coin.New("u/"+stableDenom, 50_000000), + nil, + nil, + sdk.Coin{}, + types.ErrUndercollaterized, }, } @@ -555,6 +570,13 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { // borrowed value is $10 (current) or $5 (historic) // collateral weights are always 0.25 in testing + // create an UMEE borrower using STABLE collateral + stableUmeeBorrower := s.newAccount(coin.New(stableDenom, 100_000000)) + s.supply(stableUmeeBorrower, coin.New(stableDenom, 100_000000)) + s.collateralize(stableUmeeBorrower, coin.New("u/"+stableDenom, 100_000000)) + s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) + // UMEE and STABLE have the same price but different collateral weights + zeroUmee := coin.Zero(umeeDenom) zeroUUmee := coin.New("u/"+umeeDenom, 0) tcs := []struct { @@ -574,7 +596,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { sdk.Coin{}, sdk.Coin{}, types.ErrNotRegisteredToken, - }, { + }, + { "can't borrow uToken", supplier, "u/" + umeeDenom, @@ -582,7 +605,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { sdk.Coin{}, sdk.Coin{}, types.ErrUToken, - }, { + }, + { "max withdraw umee", supplier, umeeDenom, @@ -590,7 +614,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New("u/"+umeeDenom, 75_000000), coin.New(umeeDenom, 100_000000), nil, - }, { + }, + { "duplicate max withdraw umee", supplier, umeeDenom, @@ -598,7 +623,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { zeroUUmee, zeroUmee, nil, - }, { + }, + { "max withdraw with borrow", other, umeeDenom, @@ -606,7 +632,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New("u/"+umeeDenom, 60_000000), coin.New(umeeDenom, 60_000000), nil, - }, { + }, + { "max withdrawal (dump borrower)", dumpborrower, pumpDenom, @@ -614,7 +641,8 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New("u/"+pumpDenom, 20_000000), coin.New(pumpDenom, 20_000000), nil, - }, { + }, + { "max withdrawal (pump borrower)", pumpborrower, dumpDenom, @@ -623,6 +651,15 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New(dumpDenom, 20_000000), nil, }, + { + "max withdrawal (borrow factor 2 with stablecoin collateral)", + stableUmeeBorrower, + stableDenom, + coin.New("u/"+stableDenom, 40_000000), + coin.New("u/"+stableDenom, 40_000000), + coin.New(stableDenom, 40_000000), + nil, + }, } for _, tc := range tcs { @@ -1273,6 +1310,13 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { // collateral value is $50 (current) or $100 (historic) // collateral weights are always 0.25 in testing + // create an UMEE borrower using STABLE collateral + stableUmeeBorrower := s.newAccount(coin.New(stableDenom, 40_000000)) + s.supply(stableUmeeBorrower, coin.New(stableDenom, 40_000000)) + s.collateralize(stableUmeeBorrower, coin.New("u/"+stableDenom, 40_000000)) + s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 3_000000)) + // UMEE and STABLE have the same price but different collateral weights + tcs := []testCase{ { "uToken", @@ -1295,14 +1339,19 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { coin.New(umeeDenom, 70_000000), nil, }, { - "additional borrow", - borrower, - coin.New(umeeDenom, 20_000000), + "stable umee borrower (acceptable)", + stableUmeeBorrower, + coin.New(umeeDenom, 17_000000), nil, + }, { + "stable umee borrower (borrow factor limit)", + stableUmeeBorrower, + coin.New(umeeDenom, 1_000000), + types.ErrUndercollaterized, }, { "max supply utilization", borrower, - coin.New(umeeDenom, 10_000000), + coin.New(umeeDenom, 9_000000), types.ErrMaxSupplyUtilization, }, { "atom borrow", @@ -1420,6 +1469,13 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow() { // collateral value is $50 (current) or $100 (historic) // collateral weights are always 0.25 in testing + // create an UMEE borrower using STABLE collateral + stableUmeeBorrower := s.newAccount(coin.New(stableDenom, 100_000000)) + s.supply(stableUmeeBorrower, coin.New(stableDenom, 100_000000)) + s.collateralize(stableUmeeBorrower, coin.New("u/"+stableDenom, 100_000000)) + s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) + // UMEE and STABLE have the same price but different collateral weights + tcs := []struct { msg string addr sdk.AccAddress @@ -1461,6 +1517,11 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow() { pumpborrower, coin.New(pumpDenom, 6_250000), nil, + }, { + "stable umee borrower", + stableUmeeBorrower, + coin.New(umeeDenom, 20_000000), + nil, }, } diff --git a/x/leverage/keeper/oracle.go b/x/leverage/keeper/oracle.go index 3ce0aaf0a1..7b27072b2e 100644 --- a/x/leverage/keeper/oracle.go +++ b/x/leverage/keeper/oracle.go @@ -119,6 +119,29 @@ func (k Keeper) TotalTokenValue(ctx sdk.Context, coins sdk.Coins, mode types.Pri return total, nil } +// ValueWithBorrowFactor returns the total value of all input tokens, each multiplied +// by borrow factor (which is the minimum of 2.0 and 1/collateral weight). It +// ignores unregistered and blacklisted tokens instead of returning an error, but +// will error on unavailable prices. +func (k Keeper) ValueWithBorrowFactor(ctx sdk.Context, coins sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { + total := sdk.ZeroDec() + + for _, c := range coins { + token, err := k.GetTokenSettings(ctx, c.Denom) + if err != nil { + continue + } + v, err := k.TokenValue(ctx, c, mode) + if err != nil { + return sdk.ZeroDec(), err + } + + total = total.Add(v.Mul(token.BorrowFactor())) + } + + return total, nil +} + // VisibleTokenValue functions like TotalTokenValue, but interprets missing oracle prices // as zero value instead of returning an error. func (k Keeper) VisibleTokenValue(ctx sdk.Context, coins sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { @@ -139,6 +162,21 @@ func (k Keeper) VisibleTokenValue(ctx sdk.Context, coins sdk.Coins, mode types.P return total, nil } +// VisibleUTokensValue converts uTokens to tokens and calls VisibleTokenValue. Errors on non-uTokens. +func (k Keeper) VisibleUTokensValue(ctx sdk.Context, uTokens sdk.Coins, mode types.PriceMode) (sdk.Dec, error) { + tokens := sdk.NewCoins() + + for _, u := range uTokens { + t, err := k.ExchangeUToken(ctx, u) + if err != nil { + return sdk.ZeroDec(), err + } + tokens = tokens.Add(t) + } + + return k.VisibleTokenValue(ctx, tokens, mode) +} + // TokenWithValue creates a token of a given denom with an given USD value. // Returns an error on invalid price or denom. Rounds down, i.e. the // value of the token returned may be slightly less than the requested value. diff --git a/x/leverage/keeper/oracle_test.go b/x/leverage/keeper/oracle_test.go index d594617c25..14f0281552 100644 --- a/x/leverage/keeper/oracle_test.go +++ b/x/leverage/keeper/oracle_test.go @@ -62,18 +62,20 @@ func (m *mockOracleKeeper) Clear(denom string) { // Reset restores the mock oracle's prices to its default values. func (m *mockOracleKeeper) Reset() { m.symbolExchangeRates = map[string]sdk.Dec{ - "UMEE": sdk.MustNewDecFromStr("4.21"), - "ATOM": sdk.MustNewDecFromStr("39.38"), - "DAI": sdk.MustNewDecFromStr("1.00"), - "DUMP": sdk.MustNewDecFromStr("0.50"), // A token which has recently halved in price - "PUMP": sdk.MustNewDecFromStr("2.00"), // A token which has recently doubled in price + "UMEE": sdk.MustNewDecFromStr("4.21"), + "ATOM": sdk.MustNewDecFromStr("39.38"), + "DAI": sdk.MustNewDecFromStr("1.00"), + "DUMP": sdk.MustNewDecFromStr("0.50"), // A token which has recently halved in price + "PUMP": sdk.MustNewDecFromStr("2.00"), // A token which has recently doubled in price + "STABLE": sdk.MustNewDecFromStr("4.21"), // Same price as umee } m.historicExchangeRates = map[string]sdk.Dec{ - "UMEE": sdk.MustNewDecFromStr("4.21"), - "ATOM": sdk.MustNewDecFromStr("39.38"), - "DAI": sdk.MustNewDecFromStr("1.00"), - "DUMP": sdk.MustNewDecFromStr("1.00"), - "PUMP": sdk.MustNewDecFromStr("1.00"), + "UMEE": sdk.MustNewDecFromStr("4.21"), + "ATOM": sdk.MustNewDecFromStr("39.38"), + "DAI": sdk.MustNewDecFromStr("1.00"), + "DUMP": sdk.MustNewDecFromStr("1.00"), + "PUMP": sdk.MustNewDecFromStr("1.00"), + "STABLE": sdk.MustNewDecFromStr("4.21"), } } diff --git a/x/leverage/keeper/suite_test.go b/x/leverage/keeper/suite_test.go index 48426aa23c..d35bcba1f8 100644 --- a/x/leverage/keeper/suite_test.go +++ b/x/leverage/keeper/suite_test.go @@ -22,11 +22,12 @@ import ( ) const ( - umeeDenom = appparams.BondDenom - atomDenom = fixtures.AtomDenom - daiDenom = fixtures.DaiDenom - pumpDenom = "upump" - dumpDenom = "udump" + umeeDenom = appparams.BondDenom + atomDenom = fixtures.AtomDenom + daiDenom = fixtures.DaiDenom + pumpDenom = "upump" + dumpDenom = "udump" + stableDenom = "stable" ) type IntegrationTestSuite struct { @@ -82,6 +83,11 @@ func (s *IntegrationTestSuite) SetupTest() { // additional tokens for historacle testing require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(dumpDenom, "DUMP", 6))) require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(pumpDenom, "PUMP", 6))) + // additional tokens for borrow factor testing + stable := newToken(stableDenom, "STABLE", 6) + stable.CollateralWeight = sdk.MustNewDecFromStr("0.8") + stable.LiquidationThreshold = sdk.MustNewDecFromStr("0.9") + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, stable)) // override DefaultGenesis params with fixtures.Params app.LeverageKeeper.SetParams(ctx, fixtures.Params()) diff --git a/x/leverage/types/token.go b/x/leverage/types/token.go index ae27e696d5..8020ab7f61 100644 --- a/x/leverage/types/token.go +++ b/x/leverage/types/token.go @@ -15,6 +15,8 @@ const ( UTokenPrefix = "u/" ) +var halfDec = sdk.MustNewDecFromStr("0.5") + // HasUTokenPrefix detects the uToken prefix on a denom. func HasUTokenPrefix(denom string) bool { return strings.HasPrefix(denom, UTokenPrefix) @@ -167,6 +169,14 @@ func (t Token) AssertNotBlacklisted() error { return nil } +// BorrowFactor returns the minimum of 2.0 or 1 / collateralWeight. +func (t Token) BorrowFactor() sdk.Dec { + if t.CollateralWeight.LTE(halfDec) { + return sdk.MustNewDecFromStr("2.0") + } + return sdk.OneDec().Quo(t.CollateralWeight) +} + func defaultUmeeToken() Token { return Token{ BaseDenom: appparams.BondDenom,