Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add SupplyFromModule and WithdrawFromModule to leverage #2170

Merged
merged 6 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- [2159](https://github.com/umee-network/umee/pull/2159) Add hard market cap for token emission.
- [2155](https://github.com/umee-network/umee/pull/2155) `bpmath`: basis points math package.
- [2166](https://github.com/umee-network/umee/pull/2166) Basis Points: `MulDec`
- [2170](https://github.com/umee-network/umee/pull/2170) Add SupplyFromModule and WithdrawToModule to leverage keeper.

### Improvements

Expand Down
108 changes: 92 additions & 16 deletions x/leverage/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/umee-network/umee/v5/util/coin"
"github.com/umee-network/umee/v5/x/leverage/types"
"github.com/umee-network/umee/v5/x/metoken"
)

type Keeper struct {
Expand Down Expand Up @@ -85,6 +84,7 @@ func (k Keeper) ModuleBalance(ctx sdk.Context, denom string) sdk.Coin {
// 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. Returns the amount of uTokens minted.
// Note: For supplying from a module account instead of a user, use SupplyFromModule.
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
Expand Down Expand Up @@ -112,18 +112,52 @@ func (k Keeper) Supply(ctx sdk.Context, supplierAddr sdk.AccAddress, coin sdk.Co
}

// The uTokens are sent to supplier address
// Only base accounts and x/metoken module account are supported.
if supplierAddr.Equals(k.meTokenAddr) {
err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, metoken.ModuleName, uTokens)
} else {
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, supplierAddr, uTokens)
if err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, supplierAddr, uTokens); err != nil {
return sdk.Coin{}, err
}

return uToken, nil
}

// SupplyFromModule attempts to deposit assets into the leverage module account in
// exchange for uTokens on behalf of another module. In addition to the regular error
// return, also returns a boolean which indicates whether the error was recoverable.
// A recoverable = true error means SupplyFromModule was aborted without harming state.
func (k Keeper) SupplyFromModule(ctx sdk.Context, fromModule string, coin sdk.Coin) (sdk.Coin, bool, error) {
if err := k.validateSupply(ctx, coin); err != nil {
return sdk.Coin{}, true, err
}

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

return uToken, nil
// All errors past this point are considered non-recoverable

// send token balance to leverage module account
err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, fromModule, types.ModuleName, sdk.NewCoins(coin))
if err != nil {
return sdk.Coin{}, false, 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 sdk.Coin{}, false, err
}
if err = k.setUTokenSupply(ctx, k.GetUTokenSupply(ctx, uToken.Denom).Add(uToken)); err != nil {
return sdk.Coin{}, false, err
}

// The uTokens are sent to supplier module
if err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, fromModule, uTokens); err != nil {
return sdk.Coin{}, false, err
}

// On nil error, recoverable is set to true
return uToken, true, nil
}

// Withdraw attempts to redeem uTokens from the leverage module in exchange for base tokens.
Expand All @@ -133,6 +167,7 @@ func (k Keeper) Supply(ctx sdk.Context, supplierAddr sdk.AccAddress, coin sdk.Co
// This function does NOT check that a borrower remains under their borrow limit or that
// collateral liquidity remains healthy - those assertions have been moved to MsgServer.
// Returns a boolean which is true if some or all of the withdrawn uTokens were from collateral.
// Note: For withdrawing to a module account instead of a user, use WithdrawToModule.
func (k Keeper) Withdraw(ctx sdk.Context, supplierAddr sdk.AccAddress, uToken sdk.Coin) (sdk.Coin, bool, error) {
isFromCollateral := false

Expand Down Expand Up @@ -193,15 +228,8 @@ func (k Keeper) Withdraw(ctx sdk.Context, supplierAddr sdk.AccAddress, uToken sd
}

// send the base assets to supplier
// Only base accounts and x/metoken module account are supported.
tokens := sdk.NewCoins(token)
if supplierAddr.Equals(k.meTokenAddr) {
err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, metoken.ModuleName, tokens)
} else {
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, supplierAddr, tokens)
}

if err != nil {
if err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, supplierAddr, tokens); err != nil {
toteki marked this conversation as resolved.
Show resolved Hide resolved
return sdk.Coin{}, isFromCollateral, err
}

Expand All @@ -216,6 +244,54 @@ func (k Keeper) Withdraw(ctx sdk.Context, supplierAddr sdk.AccAddress, uToken sd
return token, isFromCollateral, nil
}

// WithdrawToModule attempts to redeem uTokens from the leverage module in exchange for base tokens.
// This is done on behalf of another module, not by a user account. Modules do not have collateral.
// If the uToken denom is invalid or balances are insufficient to withdraw the amount requested,
// returns an error. Returns the amount of base tokens received. In addition to the regular error
// return, also returns a boolean which indicates whether the error was recoverable.
// A recoverable = true error means WithdrawToModule was aborted without harming state.
func (k Keeper) WithdrawToModule(ctx sdk.Context, toModule string, uToken sdk.Coin) (sdk.Coin, bool, error) {
if err := validateUToken(uToken); err != nil {
return sdk.Coin{}, true, err
}

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

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

// All errors past this point are considered non-recoverable

// transfer uTokens to the leverage module account
if err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, toModule, types.ModuleName, sdk.NewCoins(uToken)); err != nil {
return sdk.Coin{}, false, err
}

// send the base assets to withdrawing module
tokens := sdk.NewCoins(token)
if err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, toModule, tokens); err != nil {
return sdk.Coin{}, false, 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 sdk.Coin{}, false, err
}
if err = k.setUTokenSupply(ctx, k.GetUTokenSupply(ctx, uToken.Denom).Sub(uToken)); err != nil {
return sdk.Coin{}, false, err
}

// On nil error, recoverable is set to true
return token, true, nil
}

// Borrow attempts to borrow tokens from the leverage module account using
// collateral uTokens. If asset type is invalid, or module balance is insufficient,
// we return an error.
Expand Down
4 changes: 2 additions & 2 deletions x/metoken/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ type LeverageKeeper interface {
GetTokenSettings(ctx sdk.Context, denom string) (ltypes.Token, error)
ExchangeToken(ctx sdk.Context, token sdk.Coin) (sdk.Coin, error)
ExchangeUToken(ctx sdk.Context, uToken sdk.Coin) (sdk.Coin, error)
Supply(ctx sdk.Context, supplierAddr sdk.AccAddress, coin sdk.Coin) (sdk.Coin, error)
Withdraw(ctx sdk.Context, supplierAddr sdk.AccAddress, uToken sdk.Coin) (sdk.Coin, bool, error)
SupplyFromModule(ctx sdk.Context, fromModule string, coin sdk.Coin) (sdk.Coin, bool, error)
WithdrawToModule(ctx sdk.Context, toModule string, uToken sdk.Coin) (sdk.Coin, bool, error)
ModuleMaxWithdraw(ctx sdk.Context, spendableUTokens sdk.Coin) (sdkmath.Int, error)
GetTotalSupply(ctx sdk.Context, denom string) (sdk.Coin, error)
GetAllSupplied(ctx sdk.Context, supplierAddr sdk.AccAddress) (sdk.Coins, error)
Expand Down
6 changes: 3 additions & 3 deletions x/metoken/keeper/redeem.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,13 @@ func (k Keeper) withdrawFromLeverage(tokensToWithdraw sdk.Coin) (sdk.Coin, error
return coin.Zero(tokensToWithdraw.Denom), nil
}

tokensWithdrawn, _, err := k.leverageKeeper.Withdraw(
tokensWithdrawn, recoverable, err := k.leverageKeeper.WithdrawToModule(
*k.ctx,
ModuleAddr(),
metoken.ModuleName,
sdk.NewCoin(uTokensFromLeverage.Denom, sdk.MinInt(availableUTokensFromLeverage, uTokensFromLeverage.Amount)),
)
if err != nil {
return sdk.Coin{}, errors.Wrap(err, false)
return sdk.Coin{}, errors.Wrap(err, recoverable)
}

return tokensWithdrawn, nil
Expand Down
6 changes: 3 additions & 3 deletions x/metoken/keeper/swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,12 @@ func (k Keeper) supplyToLeverage(tokensToSupply sdk.Coin) (sdkmath.Int, error) {
}
}

if _, err = k.leverageKeeper.Supply(
if _, recoverable, err := k.leverageKeeper.SupplyFromModule(
*k.ctx,
ModuleAddr(),
metoken.ModuleName,
tokensToSupply,
); err != nil {
return sdkmath.Int{}, errors.Wrap(err, false)
return sdkmath.Int{}, errors.Wrap(err, recoverable)
}

return tokensToSupply.Amount, nil
Expand Down
Loading