From 8770d41e28b75d3a75e02b8a570dcf835f99d78c Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Mon, 27 Mar 2023 11:13:49 -0300 Subject: [PATCH 01/14] fix MaxBorrow --- x/leverage/keeper/borrows.go | 57 +++++++++++++++++++++++++++++++++ x/leverage/keeper/collateral.go | 9 +++--- x/leverage/keeper/grpc_query.go | 2 +- x/leverage/keeper/limits.go | 4 +-- x/leverage/keeper/msg_server.go | 12 ++++++- 5 files changed, 75 insertions(+), 9 deletions(-) diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index f980d403b3..92cfc5c295 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -3,6 +3,7 @@ package keeper import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/umee-network/umee/v4/util/coin" "github.com/umee-network/umee/v4/x/leverage/types" ) @@ -218,3 +219,59 @@ func (k Keeper) checkSupplyUtilization(ctx sdk.Context, denom string) error { } return nil } + +// moduleMaxBorrow calculates maximum amount of Token to borrow from the module given the maximum amount of Token the +// user can borrow. The calculation first finds the maximum amount of Token that can be borrowed from the module, +// respecting the min_collateral_liquidity parameter, then determines the maximum amount of Token that can be borrowed +// from the module, respecting the max_supply_utilization parameter. The minimum between these three values is +// selected, given that the min_collateral_liquidity and max_supply_utilization are both limiting factors. +func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Coin, error) { + // Get module liquidity for the denom + liquidity := k.AvailableLiquidity(ctx, userMaxBorrow.Denom) + + // Get module collateral for the uDenom + totalCollateral := k.GetTotalCollateral(ctx, userMaxBorrow.Denom) + totalTokenCollateral, err := k.ExchangeUTokens(ctx, sdk.NewCoins(totalCollateral)) + if err != nil { + return coin.Zero(userMaxBorrow.Denom), err + } + + // Get min_collateral_liquidity for the denom + token, err := k.GetTokenSettings(ctx, userMaxBorrow.Denom) + if err != nil { + return coin.Zero(userMaxBorrow.Denom), err + } + minCollateralLiquidity := token.MinCollateralLiquidity + + // The formula to calculate the available_module_liquidity is as follows: + // + // min_collateral_liquidity = (module_liquidity - available_module_liquidity) / module_collateral + // available_module_liquidity = module_liquidity - min_collateral_liquidity * module_collateral + availableModuleLiquidity := + sdk.NewDec(liquidity.Int64()).Sub(minCollateralLiquidity.MulInt(totalTokenCollateral.AmountOf(userMaxBorrow.Denom))) + + // If available_module_liquidity is 0 or less, we cannot borrow anything + if availableModuleLiquidity.LTE(sdk.ZeroDec()) { + return coin.Zero(userMaxBorrow.Denom), nil + } + + // Use the minimum of the user's max borrow and the available from module liquidity + moduleMaxBorrow := sdk.MinInt(availableModuleLiquidity.TruncateInt(), userMaxBorrow.Amount) + + // Get max_supply_utilization for the denom + maxSupplyUtilization := token.MaxSupplyUtilization + + // Get total_borrowed from module for the denom + totalBorrowed := k.GetTotalBorrowed(ctx, userMaxBorrow.Denom).Amount + + // The formula to calculate max_borrow respecting the max_supply_utilization is as follows: + // + // max_supply_utilization = (total_borrowed + max_borrow) / (module_liquidity + total_borrowed) + // max_borrow = max_supply_utilization * module_liquidity + max_supply_utilization * total_borrowed - total_borrowed + maxBorrow := maxSupplyUtilization.MulInt(liquidity).Add(maxSupplyUtilization.MulInt(totalBorrowed)).Sub( + sdk.NewDec(totalBorrowed.Int64()), + ) + + // Use the minimum between max borrow applying min_collateral_liquidity and max_supply_utilization + return sdk.NewCoin(userMaxBorrow.Denom, sdk.MinInt(moduleMaxBorrow, maxBorrow.TruncateInt())), nil +} diff --git a/x/leverage/keeper/collateral.go b/x/leverage/keeper/collateral.go index 8e33e2b151..98ecbd0e1c 100644 --- a/x/leverage/keeper/collateral.go +++ b/x/leverage/keeper/collateral.go @@ -206,11 +206,10 @@ func (k *Keeper) checkCollateralShare(ctx sdk.Context, denom string) error { return nil } -// moduleMaxWithdraw calculates the maximum available amount of uToken to withdraw -// from the module given a token denom and a user's address. The calculation first finds the maximum -// amount of non-collateral uTokens the user can withdraw up to the amount in their wallet, then -// determines how much collateral can be withdrawn in addition to that. The returned value is the sum -// of the two values. +// moduleMaxWithdraw calculates the maximum available amount of uToken to withdraw from the module given the amount of +// user's spendable tokens. The calculation first finds the maximum amount of non-collateral uTokens the user can +// withdraw up to the amount in their wallet, then determines how much collateral can be withdrawn in addition to that. +// The returned value is the sum of the two values. func (k Keeper) moduleMaxWithdraw(ctx sdk.Context, spendableUTokens sdk.Coin) ( sdkmath.Int, error, diff --git a/x/leverage/keeper/grpc_query.go b/x/leverage/keeper/grpc_query.go index cf691b6f55..c7d19e90fb 100644 --- a/x/leverage/keeper/grpc_query.go +++ b/x/leverage/keeper/grpc_query.go @@ -397,7 +397,7 @@ func (q Querier) MaxBorrow( // will be nil and the resulting value will be what // can safely be borrowed even with missing prices. // On non-nil error here, max borrow is zero. - maxBorrow, err := q.Keeper.maxBorrow(ctx, addr, denom) + maxBorrow, err := q.Keeper.userMaxBorrow(ctx, addr, denom) if err == nil && maxBorrow.IsPositive() { maxTokens = maxTokens.Add(maxBorrow) } diff --git a/x/leverage/keeper/limits.go b/x/leverage/keeper/limits.go index 1da5466f79..db7429101e 100644 --- a/x/leverage/keeper/limits.go +++ b/x/leverage/keeper/limits.go @@ -93,10 +93,10 @@ func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom str return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, walletUtokens), nil } -// maxBorrow calculates the maximum amount of a given token an account can currently borrow. +// 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 -func (k *Keeper) maxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom string) (sdk.Coin, error) { +func (k *Keeper) userMaxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom string) (sdk.Coin, error) { if types.HasUTokenPrefix(denom) { return sdk.Coin{}, types.ErrUToken } diff --git a/x/leverage/keeper/msg_server.go b/x/leverage/keeper/msg_server.go index 0786d00a49..8b14309cfd 100644 --- a/x/leverage/keeper/msg_server.go +++ b/x/leverage/keeper/msg_server.go @@ -367,7 +367,16 @@ func (s msgServer) MaxBorrow( // but not this token or any of their borrows, error // will be nil and the resulting value will be what // can safely be borrowed even with missing prices. - maxBorrow, err := s.keeper.maxBorrow(ctx, borrowerAddr, msg.Denom) + userMaxBorrow, err := s.keeper.userMaxBorrow(ctx, borrowerAddr, msg.Denom) + if err != nil { + return nil, err + } + if userMaxBorrow.IsZero() { + return &types.MsgMaxBorrowResponse{Borrowed: coin.Zero(msg.Denom)}, nil + } + + // Get the max available to borrow from the module + maxBorrow, err := s.keeper.moduleMaxBorrow(ctx, userMaxBorrow) if err != nil { return nil, err } @@ -375,6 +384,7 @@ func (s msgServer) MaxBorrow( return &types.MsgMaxBorrowResponse{Borrowed: coin.Zero(msg.Denom)}, nil } + // Proceed to borrow if err := s.keeper.Borrow(ctx, borrowerAddr, maxBorrow); err != nil { return nil, err } From b7cc7a9b068883faa0db3b5896b36b230b0246a2 Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Mon, 27 Mar 2023 11:17:23 -0300 Subject: [PATCH 02/14] fix imports --- x/leverage/keeper/borrows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index 92cfc5c295..bbd8293cfb 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -3,8 +3,8 @@ package keeper import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/umee-network/umee/v4/util/coin" + "github.com/umee-network/umee/v4/util/coin" "github.com/umee-network/umee/v4/x/leverage/types" ) From a40b68b6d0a4d009f71203ff590c1a8f4d19e16c Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Mon, 27 Mar 2023 13:43:50 -0300 Subject: [PATCH 03/14] refactor reducing duplicated code --- x/leverage/keeper/borrows.go | 35 ++++++---------- x/leverage/keeper/collateral.go | 74 ++++++++++++++++----------------- x/leverage/keeper/limits.go | 30 +++++++++++++ 3 files changed, 77 insertions(+), 62 deletions(-) diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index bbd8293cfb..6e49d21543 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -226,44 +226,33 @@ func (k Keeper) checkSupplyUtilization(ctx sdk.Context, denom string) error { // from the module, respecting the max_supply_utilization parameter. The minimum between these three values is // selected, given that the min_collateral_liquidity and max_supply_utilization are both limiting factors. func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Coin, error) { - // Get module liquidity for the denom - liquidity := k.AvailableLiquidity(ctx, userMaxBorrow.Denom) - - // Get module collateral for the uDenom - totalCollateral := k.GetTotalCollateral(ctx, userMaxBorrow.Denom) - totalTokenCollateral, err := k.ExchangeUTokens(ctx, sdk.NewCoins(totalCollateral)) + // Get the module_available_liquidity + moduleAvailableLiquidity, err := k.moduleAvailableLiquidity(ctx, userMaxBorrow.Denom) if err != nil { return coin.Zero(userMaxBorrow.Denom), err } - // Get min_collateral_liquidity for the denom - token, err := k.GetTokenSettings(ctx, userMaxBorrow.Denom) - if err != nil { - return coin.Zero(userMaxBorrow.Denom), err - } - minCollateralLiquidity := token.MinCollateralLiquidity - - // The formula to calculate the available_module_liquidity is as follows: - // - // min_collateral_liquidity = (module_liquidity - available_module_liquidity) / module_collateral - // available_module_liquidity = module_liquidity - min_collateral_liquidity * module_collateral - availableModuleLiquidity := - sdk.NewDec(liquidity.Int64()).Sub(minCollateralLiquidity.MulInt(totalTokenCollateral.AmountOf(userMaxBorrow.Denom))) - - // If available_module_liquidity is 0 or less, we cannot borrow anything - if availableModuleLiquidity.LTE(sdk.ZeroDec()) { + // If module_available_liquidity is 0 or less, we cannot borrow anything + if moduleAvailableLiquidity.LTE(sdkmath.ZeroInt()) { return coin.Zero(userMaxBorrow.Denom), nil } // Use the minimum of the user's max borrow and the available from module liquidity - moduleMaxBorrow := sdk.MinInt(availableModuleLiquidity.TruncateInt(), userMaxBorrow.Amount) + moduleMaxBorrow := sdk.MinInt(moduleAvailableLiquidity, userMaxBorrow.Amount) // Get max_supply_utilization for the denom + token, err := k.GetTokenSettings(ctx, userMaxBorrow.Denom) + if err != nil { + return coin.Zero(userMaxBorrow.Denom), err + } maxSupplyUtilization := token.MaxSupplyUtilization // Get total_borrowed from module for the denom totalBorrowed := k.GetTotalBorrowed(ctx, userMaxBorrow.Denom).Amount + // Get module liquidity for the denom + liquidity := k.AvailableLiquidity(ctx, userMaxBorrow.Denom) + // The formula to calculate max_borrow respecting the max_supply_utilization is as follows: // // max_supply_utilization = (total_borrowed + max_borrow) / (module_liquidity + total_borrowed) diff --git a/x/leverage/keeper/collateral.go b/x/leverage/keeper/collateral.go index 98ecbd0e1c..485c38b8e9 100644 --- a/x/leverage/keeper/collateral.go +++ b/x/leverage/keeper/collateral.go @@ -210,70 +210,66 @@ func (k *Keeper) checkCollateralShare(ctx sdk.Context, denom string) error { // user's spendable tokens. The calculation first finds the maximum amount of non-collateral uTokens the user can // withdraw up to the amount in their wallet, then determines how much collateral can be withdrawn in addition to that. // The returned value is the sum of the two values. -func (k Keeper) moduleMaxWithdraw(ctx sdk.Context, spendableUTokens sdk.Coin) ( - sdkmath.Int, - error, -) { +func (k Keeper) moduleMaxWithdraw(ctx sdk.Context, spendableUTokens sdk.Coin) (sdkmath.Int, error) { denom := types.ToTokenDenom(spendableUTokens.Denom) - // Get module liquidity for the denom - liquidity := k.AvailableLiquidity(ctx, denom) - - // Get module collateral for the uDenom - totalCollateral := k.GetTotalCollateral(ctx, spendableUTokens.Denom) - totalTokenCollateral, err := k.ExchangeUTokens(ctx, sdk.NewCoins(totalCollateral)) + // Get the module_available_liquidity + moduleAvailableLiquidity, err := k.moduleAvailableLiquidity(ctx, denom) if err != nil { return sdk.ZeroInt(), err } - // Get min_collateral_liquidity for the denom - token, err := k.GetTokenSettings(ctx, denom) - if err != nil { - return sdk.ZeroInt(), err + // If module_available_liquidity is 0 or less, we cannot withdraw anything + if moduleAvailableLiquidity.LTE(sdkmath.ZeroInt()) { + return sdkmath.ZeroInt(), nil } - minCollateralLiquidity := token.MinCollateralLiquidity - // The formula to calculate the available_module_liquidity is as follows: - // - // min_collateral_liquidity = (module_liquidity - available_module_liquidity) / module_collateral - // available_module_liquidity = module_liquidity - min_collateral_liquidity * module_collateral - availableModuleLiquidity := - sdk.NewDec(liquidity.Int64()).Sub(minCollateralLiquidity.MulInt(totalTokenCollateral.AmountOf(denom))) - - // If available_module_liquidity is 0 or less, we cannot withdraw anything - if availableModuleLiquidity.LTE(sdk.ZeroDec()) { - return sdkmath.ZeroInt(), nil + // If user_spendable_utokens >= module_available_liquidity we can only withdraw + // module_available_liquidity. + if spendableUTokens.Amount.GTE(moduleAvailableLiquidity) { + return moduleAvailableLiquidity, nil } - // If user_spendable_utokens >= available_module_liquidity we can only withdraw - // available_module_liquidity. - if spendableUTokens.Amount.GTE(availableModuleLiquidity.TruncateInt()) { - return availableModuleLiquidity.TruncateInt(), nil + // Get module collateral for the uDenom + totalCollateral := k.GetTotalCollateral(ctx, spendableUTokens.Denom) + totalTokenCollateral, err := k.ExchangeUTokens(ctx, sdk.NewCoins(totalCollateral)) + if err != nil { + return sdk.ZeroInt(), err } - // If after subtracting all the user_spendable_utokens from the available_module_liquidity, + // If after subtracting all the user_spendable_utokens from the module_available_liquidity, // the result is higher than the total module_collateral, // we can withdraw user_spendable_utokens + module_collateral. - if availableModuleLiquidity.TruncateInt().Sub(spendableUTokens.Amount).GTE(totalTokenCollateral.AmountOf(denom)) { + if moduleAvailableLiquidity.Sub(spendableUTokens.Amount).GTE(totalTokenCollateral.AmountOf(denom)) { return spendableUTokens.Amount.Add(totalTokenCollateral.AmountOf(denom)), nil } - // At this point we know that there is enough available_module_liquidity to withdraw user_spendable_utokens. - // Now we need to get the available_module_collateral after withdrawing user_spendable_utokens: + // Get module liquidity for the denom + liquidity := k.AvailableLiquidity(ctx, denom) + + // Get min_collateral_liquidity for the denom + token, err := k.GetTokenSettings(ctx, denom) + if err != nil { + return sdk.ZeroInt(), err + } + minCollateralLiquidity := token.MinCollateralLiquidity + + // At this point we know that there is enough module_available_liquidity to withdraw user_spendable_utokens. + // Now we need to get the module_available_collateral after withdrawing user_spendable_utokens: // - // min_collateral_liquidity = (module_liquidity - user_spendable_utokens - available_module_collateral) - // / (module_collateral - available_module_collateral) + // min_collateral_liquidity = (module_liquidity - user_spendable_utokens - module_available_collateral) + // / (module_collateral - module_available_collateral) // - // available_module_collateral = (module_liquidity - user_spendable_utokens - min_collateral_liquidity + // module_available_collateral = (module_liquidity - user_spendable_utokens - min_collateral_liquidity // * module_collateral) / (1 - min_collateral_liquidity) - availableModuleCollateral := + moduleAvailableCollateral := (sdk.NewDec(liquidity.Sub(spendableUTokens.Amount).Int64()).Sub( minCollateralLiquidity.MulInt( totalTokenCollateral.AmountOf(denom), ), )).Quo(sdk.NewDec(1).Sub(minCollateralLiquidity)) - // Adding (user_spendable_utokens + available_module_collateral) we obtain the max uTokens the account can + // Adding (user_spendable_utokens + module_available_collateral) we obtain the max uTokens the account can // withdraw from the module. - return spendableUTokens.Amount.Add(availableModuleCollateral.TruncateInt()), nil + return spendableUTokens.Amount.Add(moduleAvailableCollateral.TruncateInt()), nil } diff --git a/x/leverage/keeper/limits.go b/x/leverage/keeper/limits.go index db7429101e..db7c022cbe 100644 --- a/x/leverage/keeper/limits.go +++ b/x/leverage/keeper/limits.go @@ -190,3 +190,33 @@ func (k *Keeper) maxCollateralFromShare(ctx sdk.Context, denom string) (sdkmath. // return the computed maximum or the current uToken supply, whichever is smaller return sdk.MinInt(k.GetUTokenSupply(ctx, denom).Amount, maxUTokens.Amount), nil } + +// moduleAvailableLiquidity calculates the maximum available liquidity of a Token denom from the module can be used, +// respecting the MinCollateralLiquidity set for given Token. +func (k Keeper) moduleAvailableLiquidity(ctx sdk.Context, denom string) (sdkmath.Int, error) { + // Get module liquidity for the denom + liquidity := k.AvailableLiquidity(ctx, denom) + + // Get module collateral for the uDenom + totalCollateral := k.GetTotalCollateral(ctx, denom) + totalTokenCollateral, err := k.ExchangeUTokens(ctx, sdk.NewCoins(totalCollateral)) + if err != nil { + return sdkmath.Int{}, err + } + + // Get min_collateral_liquidity for the denom + token, err := k.GetTokenSettings(ctx, denom) + if err != nil { + return sdkmath.Int{}, err + } + minCollateralLiquidity := token.MinCollateralLiquidity + + // The formula to calculate the module_available_liquidity is as follows: + // + // min_collateral_liquidity = (module_liquidity - module_available_liquidity) / module_collateral + // module_available_liquidity = module_liquidity - min_collateral_liquidity * module_collateral + moduleAvailableLiquidity := + sdk.NewDec(liquidity.Int64()).Sub(minCollateralLiquidity.MulInt(totalTokenCollateral.AmountOf(denom))) + + return moduleAvailableLiquidity.TruncateInt(), nil +} From 8b9345d1d778494b5d82afdba9f9128d89f36f19 Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Mon, 27 Mar 2023 14:06:48 -0300 Subject: [PATCH 04/14] fix bug in limits --- x/leverage/keeper/limits.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x/leverage/keeper/limits.go b/x/leverage/keeper/limits.go index db7c022cbe..716bc4705f 100644 --- a/x/leverage/keeper/limits.go +++ b/x/leverage/keeper/limits.go @@ -197,8 +197,11 @@ func (k Keeper) moduleAvailableLiquidity(ctx sdk.Context, denom string) (sdkmath // Get module liquidity for the denom liquidity := k.AvailableLiquidity(ctx, denom) + // Get uDenom + udenom := types.ToUTokenDenom(denom) + // Get module collateral for the uDenom - totalCollateral := k.GetTotalCollateral(ctx, denom) + totalCollateral := k.GetTotalCollateral(ctx, udenom) totalTokenCollateral, err := k.ExchangeUTokens(ctx, sdk.NewCoins(totalCollateral)) if err != nil { return sdkmath.Int{}, err From 846bafcb9e133473d1e0d1b00b136536dc158c68 Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Mon, 27 Mar 2023 18:26:49 -0300 Subject: [PATCH 05/14] adding unit tests and minor tweaks --- x/leverage/keeper/borrows.go | 17 ++- x/leverage/keeper/msg_server_test.go | 178 ++++++++++++++++++++++++++- 2 files changed, 188 insertions(+), 7 deletions(-) diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index 6e49d21543..064404ccfe 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -238,7 +238,7 @@ func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Co } // Use the minimum of the user's max borrow and the available from module liquidity - moduleMaxBorrow := sdk.MinInt(moduleAvailableLiquidity, userMaxBorrow.Amount) + amountToBorrow := sdk.MinInt(moduleAvailableLiquidity, userMaxBorrow.Amount) // Get max_supply_utilization for the denom token, err := k.GetTokenSettings(ctx, userMaxBorrow.Denom) @@ -255,12 +255,17 @@ func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Co // The formula to calculate max_borrow respecting the max_supply_utilization is as follows: // - // max_supply_utilization = (total_borrowed + max_borrow) / (module_liquidity + total_borrowed) - // max_borrow = max_supply_utilization * module_liquidity + max_supply_utilization * total_borrowed - total_borrowed - maxBorrow := maxSupplyUtilization.MulInt(liquidity).Add(maxSupplyUtilization.MulInt(totalBorrowed)).Sub( + // max_supply_utilization = (total_borrowed + module_max_borrow) / (module_liquidity + total_borrowed) + // module_max_borrow = max_supply_utilization * module_liquidity + max_supply_utilization * total_borrowed - total_borrowed + moduleMaxBorrow := maxSupplyUtilization.MulInt(liquidity).Add(maxSupplyUtilization.MulInt(totalBorrowed)).Sub( sdk.NewDec(totalBorrowed.Int64()), ) - // Use the minimum between max borrow applying min_collateral_liquidity and max_supply_utilization - return sdk.NewCoin(userMaxBorrow.Denom, sdk.MinInt(moduleMaxBorrow, maxBorrow.TruncateInt())), nil + // If module_max_borrow is 0 or less, we cannot borrow anything + if moduleMaxBorrow.LTE(sdk.ZeroDec()) { + return coin.Zero(userMaxBorrow.Denom), nil + } + + // Use the minimum between module_max_borrow and (module_available_liquidity or user_max_borrow) + return sdk.NewCoin(userMaxBorrow.Denom, sdk.MinInt(amountToBorrow, moduleMaxBorrow.TruncateInt())), nil } diff --git a/x/leverage/keeper/msg_server_test.go b/x/leverage/keeper/msg_server_test.go index db54e2e4b6..99f949437d 100644 --- a/x/leverage/keeper/msg_server_test.go +++ b/x/leverage/keeper/msg_server_test.go @@ -767,7 +767,7 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw_NoAvailableModuleLiquidity() { s.collateralize(other, coin.New("u/"+umeeDenom, 200_000000)) // create a borrower with 2000 ATOM, then collateralize 2000 of supplied ATOM - // borrow 750 UMEE + // borrow 800 UMEE borrower := s.newAccount(coin.New(atomDenom, 2000_000000)) s.supply(borrower, coin.New(atomDenom, 2000_000000)) s.collateralize(borrower, coin.New("u/"+atomDenom, 2000_000000)) @@ -1497,6 +1497,182 @@ func (s *IntegrationTestSuite) TestMsgMaxBorrow() { } } +func (s *IntegrationTestSuite) TestMsgMaxBorrow_NoAvailableModuleLiquidity() { + app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() + + // overriding UMEE token settings, changing MinCollateralLiquidity to 0.2 + umeeToken := newToken(umeeDenom, "UMEE", 6) + umeeToken.MinCollateralLiquidity = sdk.MustNewDecFromStr("0.2") + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, umeeToken)) + + // create and fund a supplier with 800 UMEE, then collateralize 800 of supplied UMEE + supplier := s.newAccount(coin.New(umeeDenom, 800_000000)) + s.supply(supplier, coin.New(umeeDenom, 800_000000)) + s.collateralize(supplier, coin.New("u/"+umeeDenom, 800_000000)) + + // create and fund another supplier with 200 UMEE + // collateralize 200 UMEE + other := s.newAccount(coin.New(umeeDenom, 200_000000)) + s.supply(other, coin.New(umeeDenom, 200_000000)) + s.collateralize(other, coin.New("u/"+umeeDenom, 200_000000)) + + // create a borrower with 2000 ATOM, then collateralize 2000 of supplied ATOM + // borrow 800 UMEE + borrower := s.newAccount(coin.New(atomDenom, 2000_000000)) + s.supply(borrower, coin.New(atomDenom, 2000_000000)) + s.collateralize(borrower, coin.New("u/"+atomDenom, 2000_000000)) + s.borrow(borrower, coin.New(umeeDenom, 800_000000)) + + // the other user executes MaxBorrow + msg := &types.MsgMaxBorrow{ + Borrower: other.String(), + Denom: umeeDenom, + } + + // expected UMEE borrow amount is 0, given there is no available liquidity in the module: + // = liquidity - min_collateral_liquidity * collateral + // = 200 - 0.2*1000 + // = 0 + // since available_module_liquidity is zero, nothing to withdraw + + // verify the outputs of borrow function + resp, err := srv.MaxBorrow(ctx, msg) + require.NoError(err) + require.Equal(coin.New(umeeDenom, 0), resp.Borrowed) +} + +func (s *IntegrationTestSuite) TestMsgMaxBorrow_UserMaxBorrowGreaterModuleMaxBorrow() { + app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() + + // overriding UMEE token settings, changing MinCollateralLiquidity to 0.2 + umeeToken := newToken(umeeDenom, "UMEE", 6) + umeeToken.MinCollateralLiquidity = sdk.MustNewDecFromStr("0.2") + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, umeeToken)) + + // create and fund a supplier with 1000 UMEE, then collateralize 800 of supplied UMEE + supplier := s.newAccount(coin.New(umeeDenom, 1000_000000)) + s.supply(supplier, coin.New(umeeDenom, 1000_000000)) + s.collateralize(supplier, coin.New("u/"+umeeDenom, 800_000000)) + + // create a borrower with 2000 ATOM, then collateralize 2000 of supplied ATOM + borrower := s.newAccount(coin.New(atomDenom, 2000_000000)) + s.supply(borrower, coin.New(atomDenom, 2000_000000)) + s.collateralize(borrower, coin.New("u/"+atomDenom, 2000_000000)) + + // the borrower user executes MaxBorrow + msg := &types.MsgMaxBorrow{ + Borrower: borrower.String(), + Denom: umeeDenom, + } + + // expected UMEE borrow amount 840: + // = liquidity - min_collateral_liquidity * collateral + // = 1000 - 0.2*800 + // = 840 + // available_module_liquidity = 840 + + // verify the outputs of borrow function + resp, err := srv.MaxBorrow(ctx, msg) + require.NoError(err) + require.Equal(coin.New(umeeDenom, 840_000000), resp.Borrowed) +} + +func (s *IntegrationTestSuite) TestMsgMaxBorrow_DecreasingMaxSupplyUtilization() { + app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() + + // overriding UMEE token settings, changing MinCollateralLiquidity to 0.2 + // and MaxSupplyUtilization to 0.7 + umeeToken := newToken(umeeDenom, "UMEE", 6) + umeeToken.MinCollateralLiquidity = sdk.MustNewDecFromStr("0.2") + umeeToken.MaxSupplyUtilization = sdk.MustNewDecFromStr("0.7") + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, umeeToken)) + + // create and fund a supplier with 1000 UMEE, then collateralize 800 of supplied UMEE + // also borrow 100 UMEE + supplier := s.newAccount(coin.New(umeeDenom, 1000_000000)) + s.supply(supplier, coin.New(umeeDenom, 1000_000000)) + s.collateralize(supplier, coin.New("u/"+umeeDenom, 800_000000)) + s.borrow(supplier, coin.New(umeeDenom, 100_000000)) + + // create a borrower with 2000 ATOM, then collateralize 2000 of supplied ATOM + borrower := s.newAccount(coin.New(atomDenom, 2000_000000)) + s.supply(borrower, coin.New(atomDenom, 2000_000000)) + s.collateralize(borrower, coin.New("u/"+atomDenom, 2000_000000)) + + // the borrower user executes MaxBorrow + msg := &types.MsgMaxBorrow{ + Borrower: borrower.String(), + Denom: umeeDenom, + } + + // expected UMEE borrow amount 600: + // = liquidity - min_collateral_liquidity * collateral + // = 900 - 0.2*800 + // = 740 + // available_module_liquidity = 740 + // + // the max_supply_utilization is set to 0.7, the max amount to borrow based on that is as follows: + // = max_supply_utilization * available_liquidity + max_supply_utilization * total_borrowed - total_borrowed + // = 0.7 * 900 + 0.7 * 100 - 100 + // = 600 + // the user can only borrow 600 UMEE + + // verify the outputs of borrow function + resp, err := srv.MaxBorrow(ctx, msg) + require.NoError(err) + require.Equal(coin.New(umeeDenom, 600_000000), resp.Borrowed) +} + +func (s *IntegrationTestSuite) TestMsgMaxBorrow_ZeroAvailableBasedOnMaxSupplyUtilization() { + app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() + + // overriding UMEE token settings, changing MinCollateralLiquidity to 0.2 + // and MaxSupplyUtilization to 0.5 + umeeToken := newToken(umeeDenom, "UMEE", 6) + umeeToken.MinCollateralLiquidity = sdk.MustNewDecFromStr("0.2") + umeeToken.MaxSupplyUtilization = sdk.MustNewDecFromStr("0.5") + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, umeeToken)) + + // create and fund a supplier with 3000 UMEE, then collateralize 2500 of supplied UMEE + supplier := s.newAccount(coin.New(umeeDenom, 3000_000000)) + s.supply(supplier, coin.New(umeeDenom, 3000_000000)) + s.collateralize(supplier, coin.New("u/"+umeeDenom, 2500_000000)) + + // create a borrower with 2000 ATOM, then collateralize 2000 of supplied ATOM + borrower := s.newAccount(coin.New(atomDenom, 2000_000000)) + s.supply(borrower, coin.New(atomDenom, 2000_000000)) + s.collateralize(borrower, coin.New("u/"+atomDenom, 2000_000000)) + + // the borrower user executes MaxBorrow + msg := &types.MsgMaxBorrow{ + Borrower: borrower.String(), + Denom: umeeDenom, + } + + // borrow up to maximum available given the max_supply_utilization = 0.5 + resp, err := srv.MaxBorrow(ctx, msg) + require.NoError(err) + require.Equal(coin.New(umeeDenom, 1500_000000), resp.Borrowed) + + // after that try to MaxBorrow again + // expected UMEE borrow amount 0: + // = liquidity - min_collateral_liquidity * collateral + // = 1500 - 0.2*2500 + // = 1000 + // available_module_liquidity = 1000 + // + // the max_supply_utilization is set to 0.5, the max amount to borrow based on that is as follows: + // = max_supply_utilization * available_liquidity + max_supply_utilization * total_borrowed - total_borrowed + // = 0.5 * 1500 + 0.5 * 1500 - 1500 + // = 0 + // there is nothing to borrow because the module reached max_supply_utilization with the previous borrow + + // verify the outputs of borrow function + resp, err = srv.MaxBorrow(ctx, msg) + require.NoError(err) + require.Equal(coin.New(umeeDenom, 0), resp.Borrowed) +} + func (s *IntegrationTestSuite) TestMsgRepay() { app, ctx, srv, require := s.app, s.ctx, s.msgSrvr, s.Require() From f8ac16a51cbbabcd56fd7adb8dbf0a63247918ec Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Mon, 27 Mar 2023 19:25:27 -0300 Subject: [PATCH 06/14] changelog and readme --- CHANGELOG.md | 2 ++ x/leverage/README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce01071da3..07277c171d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Fixes - [1929](https://github.com/umee-network/umee/pull/1929) Leverage: `MaxWithdraw` now accounts for `MinCollateralLiquidity` +- [1954](https://github.com/umee-network/umee/pull/1954) Leverage: `MaxBorrow` now accounts for + `MinCollateralLiquidity` and `MaxSupplyUtilization` ## [v4.2.0](https://github.com/umee-network/umee/releases/tag/v4.2.0) - 2023-03-15 diff --git a/x/leverage/README.md b/x/leverage/README.md index 5a2c0e3a8e..57cfcc29b8 100644 --- a/x/leverage/README.md +++ b/x/leverage/README.md @@ -81,7 +81,7 @@ Users have the following actions available to them: Interest will accrue on borrows for as long as they are not paid off, with the amount owed increasing at a rate of the asset's [Borrow APY](#borrow-apy). -- `MsgMaxBorrow` borrows assets by automatically calculating the maximum amount that can be borrowed. +- `MsgMaxBorrow` borrows assets by automatically calculating the maximum amount that can be borrowed. This amount is calculated taking into account users borrows and his borrow limit, the available liquidity that can be borrowed respecting the `min_collateral_liquidity` and `max_supply_utilization` of the `Token`. - `MsgRepay` assets of a borrowed type, directly reducing the amount owed. From 4d4c7e4ca9ff08dba84fef226192e0efaae25abe Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Mon, 27 Mar 2023 19:43:58 -0300 Subject: [PATCH 07/14] lint fixes --- x/leverage/README.md | 2 +- x/leverage/keeper/borrows.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x/leverage/README.md b/x/leverage/README.md index 57cfcc29b8..fb2507f725 100644 --- a/x/leverage/README.md +++ b/x/leverage/README.md @@ -81,7 +81,7 @@ Users have the following actions available to them: Interest will accrue on borrows for as long as they are not paid off, with the amount owed increasing at a rate of the asset's [Borrow APY](#borrow-apy). -- `MsgMaxBorrow` borrows assets by automatically calculating the maximum amount that can be borrowed. This amount is calculated taking into account users borrows and his borrow limit, the available liquidity that can be borrowed respecting the `min_collateral_liquidity` and `max_supply_utilization` of the `Token`. +- `MsgMaxBorrow` borrows assets by automatically calculating the maximum amount that can be borrowed. This amount is calculated taking into account users borrows and his borrow limit, the available liquidity that can be borrowed respecting the `min_collateral_liquidity` and `max_supply_utilization` of the `Token`. - `MsgRepay` assets of a borrowed type, directly reducing the amount owed. diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index 064404ccfe..93fd421db3 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -256,7 +256,8 @@ func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Co // The formula to calculate max_borrow respecting the max_supply_utilization is as follows: // // max_supply_utilization = (total_borrowed + module_max_borrow) / (module_liquidity + total_borrowed) - // module_max_borrow = max_supply_utilization * module_liquidity + max_supply_utilization * total_borrowed - total_borrowed + // module_max_borrow = max_supply_utilization * module_liquidity + max_supply_utilization * total_borrowed + // - total_borrowed moduleMaxBorrow := maxSupplyUtilization.MulInt(liquidity).Add(maxSupplyUtilization.MulInt(totalBorrowed)).Sub( sdk.NewDec(totalBorrowed.Int64()), ) From abfa3275b856c52c533f4ffa48574f370b125ab6 Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Mon, 27 Mar 2023 19:48:17 -0300 Subject: [PATCH 08/14] markdown lint fix --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07277c171d..517d681752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Fixes - [1929](https://github.com/umee-network/umee/pull/1929) Leverage: `MaxWithdraw` now accounts for `MinCollateralLiquidity` -- [1954](https://github.com/umee-network/umee/pull/1954) Leverage: `MaxBorrow` now accounts for +- [1954](https://github.com/umee-network/umee/pull/1954) Leverage: `MaxBorrow` now accounts for `MinCollateralLiquidity` and `MaxSupplyUtilization` ## [v4.2.0](https://github.com/umee-network/umee/releases/tag/v4.2.0) - 2023-03-15 From 53b8e0efbb23a0d9eeeba1ef60599beeab99c038 Mon Sep 17 00:00:00 2001 From: kosegor <30661385+kosegor@users.noreply.github.com> Date: Tue, 28 Mar 2023 13:08:52 -0300 Subject: [PATCH 09/14] Update x/leverage/README.md Co-authored-by: Adam Moser <63419657+toteki@users.noreply.github.com> --- x/leverage/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/leverage/README.md b/x/leverage/README.md index fb2507f725..e58b70bc2c 100644 --- a/x/leverage/README.md +++ b/x/leverage/README.md @@ -81,7 +81,7 @@ Users have the following actions available to them: Interest will accrue on borrows for as long as they are not paid off, with the amount owed increasing at a rate of the asset's [Borrow APY](#borrow-apy). -- `MsgMaxBorrow` borrows assets by automatically calculating the maximum amount that can be borrowed. This amount is calculated taking into account users borrows and his borrow limit, the available liquidity that can be borrowed respecting the `min_collateral_liquidity` and `max_supply_utilization` of the `Token`. +- `MsgMaxBorrow` borrows assets by automatically calculating the maximum amount that can be borrowed. This amount is calculated taking into account the user's borrow limit and the module's available liquidity respecting the `min_collateral_liquidity` and `max_supply_utilization` of the `Token`. - `MsgRepay` assets of a borrowed type, directly reducing the amount owed. From c5a50c12dbd64bfa59fb65e3573a4af33bfc3ffb Mon Sep 17 00:00:00 2001 From: kosegor <30661385+kosegor@users.noreply.github.com> Date: Tue, 28 Mar 2023 13:09:11 -0300 Subject: [PATCH 10/14] Update x/leverage/keeper/limits.go Co-authored-by: Adam Moser <63419657+toteki@users.noreply.github.com> --- x/leverage/keeper/limits.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/x/leverage/keeper/limits.go b/x/leverage/keeper/limits.go index 716bc4705f..d209926f49 100644 --- a/x/leverage/keeper/limits.go +++ b/x/leverage/keeper/limits.go @@ -194,14 +194,11 @@ func (k *Keeper) maxCollateralFromShare(ctx sdk.Context, denom string) (sdkmath. // moduleAvailableLiquidity calculates the maximum available liquidity of a Token denom from the module can be used, // respecting the MinCollateralLiquidity set for given Token. func (k Keeper) moduleAvailableLiquidity(ctx sdk.Context, denom string) (sdkmath.Int, error) { - // Get module liquidity for the denom + // Get module liquidity for the Token liquidity := k.AvailableLiquidity(ctx, denom) - - // Get uDenom - udenom := types.ToUTokenDenom(denom) - - // Get module collateral for the uDenom - totalCollateral := k.GetTotalCollateral(ctx, udenom) + + // Get module collateral for the associated uToken + totalCollateral := k.GetTotalCollateral(ctx, types.ToUTokenDenom(denom)) totalTokenCollateral, err := k.ExchangeUTokens(ctx, sdk.NewCoins(totalCollateral)) if err != nil { return sdkmath.Int{}, err From 7dfa9d10bc58434952473f0d143ff15983dc12db Mon Sep 17 00:00:00 2001 From: kosegor <30661385+kosegor@users.noreply.github.com> Date: Tue, 28 Mar 2023 13:09:48 -0300 Subject: [PATCH 11/14] Update x/leverage/keeper/limits.go Co-authored-by: Adam Moser <63419657+toteki@users.noreply.github.com> --- x/leverage/keeper/limits.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/leverage/keeper/limits.go b/x/leverage/keeper/limits.go index d209926f49..4c345ffae0 100644 --- a/x/leverage/keeper/limits.go +++ b/x/leverage/keeper/limits.go @@ -218,5 +218,5 @@ func (k Keeper) moduleAvailableLiquidity(ctx sdk.Context, denom string) (sdkmath moduleAvailableLiquidity := sdk.NewDec(liquidity.Int64()).Sub(minCollateralLiquidity.MulInt(totalTokenCollateral.AmountOf(denom))) - return moduleAvailableLiquidity.TruncateInt(), nil + return sdk.MaxInt(moduleAvailableLiquidity.TruncateInt(),sdk.ZeroInt()), nil } From b780375efab1e4a28c1efb19e146752c31d61bce Mon Sep 17 00:00:00 2001 From: kosegor <30661385+kosegor@users.noreply.github.com> Date: Tue, 28 Mar 2023 13:16:14 -0300 Subject: [PATCH 12/14] Update x/leverage/keeper/borrows.go Co-authored-by: Adam Moser <63419657+toteki@users.noreply.github.com> --- x/leverage/keeper/borrows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index 93fd421db3..1db3fcc509 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -233,7 +233,7 @@ func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Co } // If module_available_liquidity is 0 or less, we cannot borrow anything - if moduleAvailableLiquidity.LTE(sdkmath.ZeroInt()) { + if !moduleAvailableLiquidity.IsPositive() { return coin.Zero(userMaxBorrow.Denom), nil } From f1b87ce8b31985a79267411114423c8e1d847772 Mon Sep 17 00:00:00 2001 From: kosegor <30661385+kosegor@users.noreply.github.com> Date: Tue, 28 Mar 2023 13:16:30 -0300 Subject: [PATCH 13/14] Update x/leverage/keeper/borrows.go Co-authored-by: Adam Moser <63419657+toteki@users.noreply.github.com> --- x/leverage/keeper/borrows.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index 1db3fcc509..b8d4585b37 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -262,8 +262,8 @@ func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Co sdk.NewDec(totalBorrowed.Int64()), ) - // If module_max_borrow is 0 or less, we cannot borrow anything - if moduleMaxBorrow.LTE(sdk.ZeroDec()) { + // If module_max_borrow is zero, we cannot borrow anything + if !moduleMaxBorrow.IsPositive() { return coin.Zero(userMaxBorrow.Denom), nil } From fe6db0a3d796132ef6161f312d937c1d8f0ea96f Mon Sep 17 00:00:00 2001 From: Egor Kostetskiy Date: Tue, 28 Mar 2023 13:52:48 -0300 Subject: [PATCH 14/14] pr comments --- x/leverage/keeper/borrows.go | 34 +++++++++++++++------------------ x/leverage/keeper/collateral.go | 4 ++-- x/leverage/keeper/limits.go | 4 ++-- x/leverage/keeper/msg_server.go | 19 ++++++++++-------- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index b8d4585b37..1f93b83d8e 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -4,7 +4,6 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/umee-network/umee/v4/util/coin" "github.com/umee-network/umee/v4/x/leverage/types" ) @@ -220,38 +219,35 @@ func (k Keeper) checkSupplyUtilization(ctx sdk.Context, denom string) error { return nil } -// moduleMaxBorrow calculates maximum amount of Token to borrow from the module given the maximum amount of Token the -// user can borrow. The calculation first finds the maximum amount of Token that can be borrowed from the module, +// moduleMaxBorrow calculates maximum amount of Token to borrow from the module. +// The calculation first finds the maximum amount of Token that can be borrowed from the module, // respecting the min_collateral_liquidity parameter, then determines the maximum amount of Token that can be borrowed -// from the module, respecting the max_supply_utilization parameter. The minimum between these three values is +// from the module, respecting the max_supply_utilization parameter. The minimum between these two values is // selected, given that the min_collateral_liquidity and max_supply_utilization are both limiting factors. -func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Coin, error) { +func (k Keeper) moduleMaxBorrow(ctx sdk.Context, denom string) (sdkmath.Int, error) { // Get the module_available_liquidity - moduleAvailableLiquidity, err := k.moduleAvailableLiquidity(ctx, userMaxBorrow.Denom) + moduleAvailableLiquidity, err := k.moduleAvailableLiquidity(ctx, denom) if err != nil { - return coin.Zero(userMaxBorrow.Denom), err + return sdk.ZeroInt(), err } - // If module_available_liquidity is 0 or less, we cannot borrow anything + // If module_available_liquidity is zero, we cannot borrow anything if !moduleAvailableLiquidity.IsPositive() { - return coin.Zero(userMaxBorrow.Denom), nil + return sdk.ZeroInt(), nil } - // Use the minimum of the user's max borrow and the available from module liquidity - amountToBorrow := sdk.MinInt(moduleAvailableLiquidity, userMaxBorrow.Amount) - // Get max_supply_utilization for the denom - token, err := k.GetTokenSettings(ctx, userMaxBorrow.Denom) + token, err := k.GetTokenSettings(ctx, denom) if err != nil { - return coin.Zero(userMaxBorrow.Denom), err + return sdk.ZeroInt(), err } maxSupplyUtilization := token.MaxSupplyUtilization // Get total_borrowed from module for the denom - totalBorrowed := k.GetTotalBorrowed(ctx, userMaxBorrow.Denom).Amount + totalBorrowed := k.GetTotalBorrowed(ctx, denom).Amount // Get module liquidity for the denom - liquidity := k.AvailableLiquidity(ctx, userMaxBorrow.Denom) + liquidity := k.AvailableLiquidity(ctx, denom) // The formula to calculate max_borrow respecting the max_supply_utilization is as follows: // @@ -264,9 +260,9 @@ func (k Keeper) moduleMaxBorrow(ctx sdk.Context, userMaxBorrow sdk.Coin) (sdk.Co // If module_max_borrow is zero, we cannot borrow anything if !moduleMaxBorrow.IsPositive() { - return coin.Zero(userMaxBorrow.Denom), nil + return sdk.ZeroInt(), nil } - // Use the minimum between module_max_borrow and (module_available_liquidity or user_max_borrow) - return sdk.NewCoin(userMaxBorrow.Denom, sdk.MinInt(amountToBorrow, moduleMaxBorrow.TruncateInt())), nil + // Use the minimum between module_max_borrow and module_available_liquidity + return sdk.MinInt(moduleAvailableLiquidity, moduleMaxBorrow.TruncateInt()), nil } diff --git a/x/leverage/keeper/collateral.go b/x/leverage/keeper/collateral.go index 485c38b8e9..b38b45d7b2 100644 --- a/x/leverage/keeper/collateral.go +++ b/x/leverage/keeper/collateral.go @@ -219,8 +219,8 @@ func (k Keeper) moduleMaxWithdraw(ctx sdk.Context, spendableUTokens sdk.Coin) (s return sdk.ZeroInt(), err } - // If module_available_liquidity is 0 or less, we cannot withdraw anything - if moduleAvailableLiquidity.LTE(sdkmath.ZeroInt()) { + // If module_available_liquidity is zero, we cannot withdraw anything + if !moduleAvailableLiquidity.IsPositive() { return sdkmath.ZeroInt(), nil } diff --git a/x/leverage/keeper/limits.go b/x/leverage/keeper/limits.go index 4c345ffae0..f7bfe6b1fe 100644 --- a/x/leverage/keeper/limits.go +++ b/x/leverage/keeper/limits.go @@ -196,7 +196,7 @@ func (k *Keeper) maxCollateralFromShare(ctx sdk.Context, denom string) (sdkmath. func (k Keeper) moduleAvailableLiquidity(ctx sdk.Context, denom string) (sdkmath.Int, error) { // Get module liquidity for the Token liquidity := k.AvailableLiquidity(ctx, denom) - + // Get module collateral for the associated uToken totalCollateral := k.GetTotalCollateral(ctx, types.ToUTokenDenom(denom)) totalTokenCollateral, err := k.ExchangeUTokens(ctx, sdk.NewCoins(totalCollateral)) @@ -218,5 +218,5 @@ func (k Keeper) moduleAvailableLiquidity(ctx sdk.Context, denom string) (sdkmath moduleAvailableLiquidity := sdk.NewDec(liquidity.Int64()).Sub(minCollateralLiquidity.MulInt(totalTokenCollateral.AmountOf(denom))) - return sdk.MaxInt(moduleAvailableLiquidity.TruncateInt(),sdk.ZeroInt()), nil + return sdk.MaxInt(moduleAvailableLiquidity.TruncateInt(), sdk.ZeroInt()), nil } diff --git a/x/leverage/keeper/msg_server.go b/x/leverage/keeper/msg_server.go index 8b14309cfd..ed9bb64887 100644 --- a/x/leverage/keeper/msg_server.go +++ b/x/leverage/keeper/msg_server.go @@ -376,16 +376,19 @@ func (s msgServer) MaxBorrow( } // Get the max available to borrow from the module - maxBorrow, err := s.keeper.moduleMaxBorrow(ctx, userMaxBorrow) + moduleMaxBorrow, err := s.keeper.moduleMaxBorrow(ctx, msg.Denom) if err != nil { return nil, err } - if maxBorrow.IsZero() { + if moduleMaxBorrow.IsZero() { return &types.MsgMaxBorrowResponse{Borrowed: coin.Zero(msg.Denom)}, nil } + // Select the minimum between user_max_borrow and module_max_borrow + userMaxBorrow.Amount = sdk.MinInt(userMaxBorrow.Amount, moduleMaxBorrow) + // Proceed to borrow - if err := s.keeper.Borrow(ctx, borrowerAddr, maxBorrow); err != nil { + if err := s.keeper.Borrow(ctx, borrowerAddr, userMaxBorrow); err != nil { return nil, err } @@ -397,26 +400,26 @@ func (s msgServer) MaxBorrow( } // Check MaxSupplyUtilization after transaction - if err = s.keeper.checkSupplyUtilization(ctx, maxBorrow.Denom); err != nil { + if err = s.keeper.checkSupplyUtilization(ctx, userMaxBorrow.Denom); err != nil { return nil, err } // Check MinCollateralLiquidity is still satisfied after the transaction - if err = s.keeper.checkCollateralLiquidity(ctx, maxBorrow.Denom); err != nil { + if err = s.keeper.checkCollateralLiquidity(ctx, userMaxBorrow.Denom); err != nil { return nil, err } s.keeper.Logger(ctx).Debug( "assets borrowed", "borrower", msg.Borrower, - "amount", maxBorrow.String(), + "amount", moduleMaxBorrow.String(), ) sdkutil.Emit(&ctx, &types.EventBorrow{ Borrower: msg.Borrower, - Asset: maxBorrow, + Asset: userMaxBorrow, }) return &types.MsgMaxBorrowResponse{ - Borrowed: maxBorrow, + Borrowed: userMaxBorrow, }, nil }