Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat!: Add informative response fields for leverage messages #1236

Merged
merged 27 commits into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- [1140](https://github.com/umee-network/umee/pull/1140) Rename MarketSize query to TotalSuppliedValue, and TokenMarketSize to TotalSupplied.
- [1188](https://github.com/umee-network/umee/pull/1188) Remove all individual queries which duplicate market_summary fields.
- [1199](https://github.com/umee-network/umee/pull/1199) Move all queries which require address input (e.g. `supplied`, `collateral_value`, `borrow_limit`) into aggregate queries `acccount_summary` or `account_balances`.
- [1236](https://github.com/umee-network/umee/pull/1236) Add more response fields to leverage messages.
- [1222](https://github.com/umee-network/umee/pull/1222) Add leverage parameter DirectLiquidationFee.

### Features
Expand Down Expand Up @@ -91,6 +92,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- [967](https://github.com/umee-network/umee/pull/962) Use taylor series of e^x for more accurate interest at high APY.
- [987](https://github.com/umee-network/umee/pull/987) Streamline x/leverage CLI tests
- [1012](https://github.com/umee-network/umee/pull/1012) Improve negative time elapsed error message
- [1236](https://github.com/umee-network/umee/pull/1236) Improve leverage event fields.

### Bug Fixes

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ containerProtoGen=$(PROJECT_NAME)-proto-gen-$(containerProtoVer)
containerProtoFmt=$(PROJECT_NAME)-proto-fmt-$(containerProtoVer)
containerProtoGenSwagger=$(PROJECT_NAME)-proto-gen-swagger-$(containerProtoVer)

proto-all: proto-gen proto-lint proto-check-breaking proto-format
.PHONY: proto-all proto-gen proto-lint proto-check-breaking proto-format
proto-all: proto-format proto-lint proto-gen proto-swagger-gen
.PHONY: proto-all proto-gen proto-lint proto-check-breaking proto-format proto-swagger-gen

proto-gen:
@echo "Generating Protobuf files"
Expand Down
23 changes: 14 additions & 9 deletions proto/umee/leverage/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ service Msg {
rpc Liquidate(MsgLiquidate) returns (MsgLiquidateResponse);
}

// MsgSupply is the request structure for the Supply RPC.
// MsgSupply represents a user's request to supply assets to the module.
message MsgSupply {
// Supplier is the account address supplying assets and the signer of the message.
string supplier = 1;
Expand All @@ -56,15 +56,15 @@ message MsgWithdraw {
message MsgCollateralize {
// Borrower is the account address adding collateral and the signer of the message.
string borrower = 1;
cosmos.base.v1beta1.Coin coin = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin asset = 2 [(gogoproto.nullable) = false];
}

// MsgDecollateralize represents a user's request to disable selected
// uTokens as collateral.
message MsgDecollateralize {
// Borrower is the account address removing collateral and the signer of the message.
string borrower = 1;
cosmos.base.v1beta1.Coin coin = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin asset = 2 [(gogoproto.nullable) = false];
}

// MsgBorrow represents a user's request to borrow a base asset type
Expand Down Expand Up @@ -104,10 +104,16 @@ message MsgLiquidate {
}

// MsgSupplyResponse defines the Msg/Supply response type.
message MsgSupplyResponse {}
message MsgSupplyResponse {
// Received is the amount of uTokens received.
cosmos.base.v1beta1.Coin received = 1 [(gogoproto.nullable) = false];
}

// MsgWithdrawResponse defines the Msg/Withdraw response type.
message MsgWithdrawResponse {}
message MsgWithdrawResponse {
// Received is the amount of base tokens received.
cosmos.base.v1beta1.Coin received = 1 [(gogoproto.nullable) = false];
}

// MsgCollateralizeResponse defines the Msg/Collateralize response type.
message MsgCollateralizeResponse {}
Expand All @@ -120,18 +126,17 @@ message MsgBorrowResponse {}

// MsgRepayResponse defines the Msg/Repay response type.
message MsgRepayResponse {
// Repaid is the amount of debt, in base tokens, that was repaid to the
// module by the borrower.
// Repaid is the amount of base tokens repaid to the module.
cosmos.base.v1beta1.Coin repaid = 1 [(gogoproto.nullable) = false];
}

// MsgLiquidateResponse defines the Msg/Liquidate response type.
message MsgLiquidateResponse {
// Repaid is the amount of debt, in base tokens, that liquidator repaid
// Repaid is the amount of borrowed base tokens that the liquidator repaid
// to the module on behalf of the borrower.
cosmos.base.v1beta1.Coin repaid = 1 [(gogoproto.nullable) = false];
// Collateral is the amount of the borrower's uToken collateral that
// was converted to the reward tokens as a result of liquidation.
// was liquidated.
cosmos.base.v1beta1.Coin collateral = 2 [(gogoproto.nullable) = false];
// Reward is the amount of base tokens that the liquidator received from
// the module as reward for the liquidation.
Expand Down
72 changes: 36 additions & 36 deletions x/leverage/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,64 +75,65 @@ func (k Keeper) ModuleBalance(ctx sdk.Context, denom string) sdk.Int {

// Supply attempts to deposit assets into the leverage module account in
// exchange for uTokens. If asset type is invalid or account balance is
// insufficient, we return an error.
func (k Keeper) Supply(ctx sdk.Context, supplierAddr sdk.AccAddress, loan sdk.Coin) error {
if err := k.validateSupply(ctx, loan); err != nil {
return err
// insufficient, we return an error. Returns the amount of uTokens minted.
func (k Keeper) Supply(ctx sdk.Context, supplierAddr sdk.AccAddress, coin sdk.Coin) (sdk.Coin, error) {
if err := k.validateSupply(ctx, coin); err != nil {
return sdk.Coin{}, err
}

// determine uToken amount to mint
uToken, err := k.ExchangeToken(ctx, loan)
uToken, err := k.ExchangeToken(ctx, coin)
if err != nil {
return err
return sdk.Coin{}, err
}

// send token balance to leverage module account
loanTokens := sdk.NewCoins(loan)
if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, supplierAddr, types.ModuleName, loanTokens); err != nil {
return err
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, supplierAddr, types.ModuleName, sdk.NewCoins(coin))
if err != nil {
return sdk.Coin{}, err
}

// mint uToken and set new total uToken supply
uTokens := sdk.NewCoins(uToken)
if err = k.bankKeeper.MintCoins(ctx, types.ModuleName, uTokens); err != nil {
return err
return sdk.Coin{}, err
}
if err = k.setUTokenSupply(ctx, k.GetUTokenSupply(ctx, uToken.Denom).Add(uToken)); err != nil {
return err
return sdk.Coin{}, err
}

// The uTokens are sent to supplier address
if err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, supplierAddr, uTokens); err != nil {
return err
return sdk.Coin{}, err
}

return nil
return uToken, nil
}

// Withdraw attempts to deposit uTokens into the leverage module in exchange for base tokens.
// If there are not enough uTokens in balance, Withdraw will attempt to withdraw uToken collateral
// to make up the difference (as long as borrow limit allows). If the uToken denom is invalid or
// balances are insufficient to withdraw the full amount requested, returns an error.
func (k Keeper) Withdraw(ctx sdk.Context, supplierAddr sdk.AccAddress, uToken sdk.Coin) error {
// Returns the amount of base tokens received.
func (k Keeper) Withdraw(ctx sdk.Context, supplierAddr sdk.AccAddress, uToken sdk.Coin) (sdk.Coin, error) {
if err := uToken.Validate(); err != nil {
return err
return sdk.Coin{}, err
}
if !types.HasUTokenPrefix(uToken.Denom) {
return types.ErrNotUToken.Wrap(uToken.Denom)
return sdk.Coin{}, types.ErrNotUToken.Wrap(uToken.Denom)
}

// calculate base asset amount to withdraw
token, err := k.ExchangeUToken(ctx, uToken)
if err != nil {
return err
return sdk.Coin{}, err
}

// Ensure module account has sufficient unreserved tokens to withdraw
reservedAmount := k.GetReserveAmount(ctx, token.Denom)
availableAmount := k.ModuleBalance(ctx, token.Denom)
if token.Amount.GT(availableAmount.Sub(reservedAmount)) {
return sdkerrors.Wrap(types.ErrLendingPoolInsufficient, token.String())
return sdk.Coin{}, sdkerrors.Wrap(types.ErrLendingPoolInsufficient, token.String())
}

// Withdraw will first attempt to use any uTokens in the supplier's wallet
Expand All @@ -145,59 +146,58 @@ func (k Keeper) Withdraw(ctx sdk.Context, supplierAddr sdk.AccAddress, uToken sd
borrowed := k.GetBorrowerBorrows(ctx, supplierAddr)
borrowedValue, err := k.TotalTokenValue(ctx, borrowed)
if err != nil {
return err
return sdk.Coin{}, err
}

// Check for sufficient collateral
collateral := k.GetBorrowerCollateral(ctx, supplierAddr)
if collateral.AmountOf(uToken.Denom).LT(amountFromCollateral) {
return types.ErrInsufficientBalance.Wrapf("%s uToken balance + %s from collateral is less than %s to withdraw",
amountFromWallet.String(),
collateral.AmountOf(uToken.Denom).String(),
uToken.String())
return sdk.Coin{}, types.ErrInsufficientBalance.Wrapf(
"%s uToken balance + %s from collateral is less than %s to withdraw",
amountFromWallet, collateral.AmountOf(uToken.Denom), uToken)
}

// Calculate what borrow limit will be AFTER this withdrawal
collateralToWithdraw := sdk.NewCoins(sdk.NewCoin(uToken.Denom, amountFromCollateral))
newBorrowLimit, err := k.CalculateBorrowLimit(ctx, collateral.Sub(collateralToWithdraw))
if err != nil {
return err
return sdk.Coin{}, err
}

// Return error if borrow limit would drop below borrowed value
if borrowedValue.GT(newBorrowLimit) {
return types.ErrUndercollaterized.Wrapf(
return sdk.Coin{}, types.ErrUndercollaterized.Wrapf(
"withdraw would decrease borrow limit to %s, below the current borrowed value %s", newBorrowLimit, borrowedValue)
}

// reduce the supplier's collateral by amountFromCollateral
newCollateral := sdk.NewCoin(uToken.Denom, collateral.AmountOf(uToken.Denom).Sub(amountFromCollateral))
if err = k.setCollateralAmount(ctx, supplierAddr, newCollateral); err != nil {
return err
return sdk.Coin{}, err
}
}

// transfer amountFromWallet uTokens to the module account
uTokens := sdk.NewCoins(sdk.NewCoin(uToken.Denom, amountFromWallet))
if err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, supplierAddr, types.ModuleName, uTokens); err != nil {
return err
return sdk.Coin{}, err
}

// send the base assets to supplier
tokens := sdk.NewCoins(token)
if err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, supplierAddr, tokens); err != nil {
return err
return sdk.Coin{}, err
}

// burn the uTokens and set the new total uToken supply
if err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(uToken)); err != nil {
return err
return sdk.Coin{}, err
}
if err = k.setUTokenSupply(ctx, k.GetUTokenSupply(ctx, uToken.Denom).Sub(uToken)); err != nil {
return err
return sdk.Coin{}, err
}

return nil
return token, nil
}

// Borrow attempts to borrow tokens from the leverage module account using
Expand Down Expand Up @@ -255,25 +255,25 @@ func (k Keeper) Borrow(ctx sdk.Context, borrowerAddr sdk.AccAddress, borrow sdk.
// Additionally, if the amount provided is greater than the full repayment amount, only the
// necessary amount is transferred. Because amount repaid may be less than the repayment attempted,
// Repay returns the actual amount repaid.
func (k Keeper) Repay(ctx sdk.Context, borrowerAddr sdk.AccAddress, payment sdk.Coin) (sdk.Int, error) {
func (k Keeper) Repay(ctx sdk.Context, borrowerAddr sdk.AccAddress, payment sdk.Coin) (sdk.Coin, error) {
if err := payment.Validate(); err != nil {
return sdk.ZeroInt(), err
return sdk.Coin{}, err
}

// determine amount of selected denom currently owed
owed := k.GetBorrow(ctx, borrowerAddr, payment.Denom)
if owed.IsZero() {
return sdk.ZeroInt(), types.ErrInvalidRepayment.Wrapf("No %s borrowed ", payment.Denom)
return sdk.Coin{}, types.ErrInvalidRepayment.Wrapf("No %s borrowed ", payment.Denom)
}

// prevent overpaying
payment.Amount = sdk.MinInt(owed.Amount, payment.Amount)

// send payment to leverage module account
if err := k.repayBorrow(ctx, borrowerAddr, borrowerAddr, payment); err != nil {
return sdk.ZeroInt(), err
return sdk.Coin{}, err
}
return payment.Amount, nil
return payment, nil
}

// Collateralize enables selected uTokens for use as collateral by a single borrower.
Expand Down
27 changes: 14 additions & 13 deletions x/leverage/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (s *IntegrationTestSuite) setupAccount(denom string, mintAmount, supplyAmou

if supplyAmount > 0 {
// account supplies supplyAmount tokens and receives uTokens
err := s.app.LeverageKeeper.Supply(s.ctx, addr, sdk.NewInt64Coin(denom, supplyAmount))
_, err := s.app.LeverageKeeper.Supply(s.ctx, addr, sdk.NewInt64Coin(denom, supplyAmount))
s.Require().NoError(err)
}

Expand Down Expand Up @@ -145,7 +145,7 @@ func (s *IntegrationTestSuite) TestSupply_InvalidAsset() {
s.Require().NoError(s.app.BankKeeper.SendCoinsFromModuleToAccount(s.ctx, minttypes.ModuleName, addr, invalidCoins))

// supplying should fail as we have not registered token "uabcd"
err := s.app.LeverageKeeper.Supply(s.ctx, addr, invalidCoin)
_, err := s.app.LeverageKeeper.Supply(s.ctx, addr, invalidCoin)
s.Require().Error(err)
}

Expand All @@ -161,7 +161,7 @@ func (s *IntegrationTestSuite) TestSupply_Valid() {
s.Require().NoError(app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, initCoins))

// supply asset
err := s.app.LeverageKeeper.Supply(ctx, addr, sdk.NewInt64Coin(umeeapp.BondDenom, 1000000000)) // 1k umee
_, err := s.app.LeverageKeeper.Supply(ctx, addr, sdk.NewInt64Coin(umeeapp.BondDenom, 1000000000)) // 1k umee
s.Require().NoError(err)

// verify the total supply of the minted uToken
Expand Down Expand Up @@ -190,7 +190,7 @@ func (s *IntegrationTestSuite) TestWithdraw_Valid() {
s.Require().NoError(app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, initCoins))

// supply asset
err := s.app.LeverageKeeper.Supply(ctx, addr, sdk.NewInt64Coin(umeeapp.BondDenom, 1000000000)) // 1k umee
_, err := s.app.LeverageKeeper.Supply(ctx, addr, sdk.NewInt64Coin(umeeapp.BondDenom, 1000000000)) // 1k umee
s.Require().NoError(err)

// verify the total supply of the minted uToken
Expand All @@ -201,7 +201,7 @@ func (s *IntegrationTestSuite) TestWithdraw_Valid() {

// withdraw the total amount of assets supplied
uToken := expected
err = s.app.LeverageKeeper.Withdraw(ctx, addr, uToken)
_, err = s.app.LeverageKeeper.Withdraw(ctx, addr, uToken)
s.Require().NoError(err)

// verify total supply of the uTokens
Expand Down Expand Up @@ -277,7 +277,7 @@ func (s *IntegrationTestSuite) initBorrowScenario() (supplier, bum sdk.AccAddres

// supplier supplies 1000 umee and receives 1k u/umee
supplyCoin := sdk.NewInt64Coin(umeeapp.BondDenom, 1000000000)
err := s.app.LeverageKeeper.Supply(ctx, supplierAddr, supplyCoin)
_, err := s.app.LeverageKeeper.Supply(ctx, supplierAddr, supplyCoin)
s.Require().NoError(err)

// supplier enables u/umee as collateral
Expand Down Expand Up @@ -305,7 +305,7 @@ func (s *IntegrationTestSuite) mintAndSupplyAtom(mintTo sdk.AccAddress, amountTo

// user supplies amountToSupply atom and receives amountToSupply u/atom
supplyCoin := sdk.NewInt64Coin(atomIBCDenom, amountToSupply)
err := s.app.LeverageKeeper.Supply(ctx, mintTo, supplyCoin)
_, err := s.app.LeverageKeeper.Supply(ctx, mintTo, supplyCoin)
s.Require().NoError(err)

// user enables u/atom as collateral
Expand Down Expand Up @@ -419,7 +419,7 @@ func (s *IntegrationTestSuite) TestBorrow_BorrowLimit() {
s.Require().Error(err)

// user tries to withdraw all its u/umee, fails due to borrow limit
err = s.app.LeverageKeeper.Withdraw(s.ctx, addr, sdk.NewCoin(uDenom, collateral.Amount))
_, err = s.app.LeverageKeeper.Withdraw(s.ctx, addr, sdk.NewCoin(uDenom, collateral.Amount))
s.Require().Error(err)
}

Expand Down Expand Up @@ -462,7 +462,7 @@ func (s *IntegrationTestSuite) TestRepay_Valid() {
// user repays 8 umee
repaid, err := s.app.LeverageKeeper.Repay(ctx, addr, sdk.NewInt64Coin(umeeapp.BondDenom, 8000000))
s.Require().NoError(err)
s.Require().Equal(sdk.NewInt(8000000), repaid)
s.Require().Equal(sdk.NewInt64Coin(umeeDenom, 8000000), repaid)

// verify user's new loan amount (12 umee)
loanBalance := s.app.LeverageKeeper.GetBorrow(ctx, addr, umeeapp.BondDenom)
Expand All @@ -483,7 +483,7 @@ func (s *IntegrationTestSuite) TestRepay_Valid() {
// user repays 12 umee (loan repaid in full)
repaid, err = s.app.LeverageKeeper.Repay(ctx, addr, sdk.NewInt64Coin(umeeapp.BondDenom, 12000000))
s.Require().NoError(err)
s.Require().Equal(sdk.NewInt(12000000), repaid)
s.Require().Equal(sdk.NewInt64Coin(umeeDenom, 12000000), repaid)

// verify user's new loan amount in the correct denom (zero)
loanBalance = s.app.LeverageKeeper.GetBorrow(ctx, addr, umeeapp.BondDenom)
Expand Down Expand Up @@ -516,7 +516,7 @@ func (s *IntegrationTestSuite) TestRepay_Overpay() {
coinToRepay := sdk.NewInt64Coin(umeeapp.BondDenom, 30000000)
repaid, err := s.app.LeverageKeeper.Repay(ctx, addr, coinToRepay)
s.Require().NoError(err)
s.Require().Equal(sdk.NewInt(20000000), repaid)
s.Require().Equal(sdk.NewInt64Coin(umeeDenom, 20000000), repaid)

// verify that coinToRepay has not been modified
s.Require().Equal(sdk.NewInt(30000000), coinToRepay.Amount)
Expand Down Expand Up @@ -858,7 +858,7 @@ func (s *IntegrationTestSuite) TestCollateralAmountInvariant() {
uTokenDenom := types.ToUTokenDenom(umeeapp.BondDenom)

// withdraw the supplyed umee in the initBorrowScenario
err := s.app.LeverageKeeper.Withdraw(s.ctx, addr, sdk.NewInt64Coin(uTokenDenom, 1000000000))
_, err := s.app.LeverageKeeper.Withdraw(s.ctx, addr, sdk.NewInt64Coin(uTokenDenom, 1000000000))
s.Require().NoError(err)

// check invariant
Expand Down Expand Up @@ -906,7 +906,8 @@ func (s *IntegrationTestSuite) TestWithdraw_InsufficientCollateral() {

// withdraw more collateral than available
uToken := collateral.Add(sdk.NewInt64Coin(uTokenDenom, 1))
err := s.app.LeverageKeeper.Withdraw(s.ctx, supplierAddr, uToken)

_, err := s.app.LeverageKeeper.Withdraw(s.ctx, supplierAddr, uToken)
s.Require().EqualError(err,
"0 uToken balance + 1000000 from collateral is less than 1000001u/uumee to withdraw: insufficient balance",
)
Expand Down
Loading