0g-chain/x/earn/keeper/withdraw.go
Derrick Lee 88d4868316
Implement Hard strategy for Earn vaults (#1278)
* Simplify strategies to lend and savings

* Add hard and savings keepers

* Add ctx to strategy interface, fill in lend strategy

* Rename lend strategy to hard

* Fix hard deposit query, fix withdraw bank send

* Fix misleading borrow instead of withdraw for hard

* Remove liquidateall strategy method

* Withdraw tests

* Add hard gs to testutil suite

* Update withdraw tests with working hard strategy, clean strategy interface methods

* Check allowed denom for strategy

* Update GetVaultTotalValue doc note

* Update error wrap message for unsupported denom

* Remove unnecessary viewvault keeper

* Withdraw amount from account value, not supplied value

* Test value > supplied withdraw

* Use dec when dividing for withdrawAmountPercent

* Use the correct store prefix for vault shares

* Update swap references to earn

* Simplify vault shares, use a single share for all coins per address
2022-07-28 09:39:57 -07:00

119 lines
3.5 KiB
Go

package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/earn/types"
)
// Withdraw removes the amount of supplied tokens from a vault and transfers it
// back to the account.
func (k *Keeper) Withdraw(ctx sdk.Context, from sdk.AccAddress, wantAmount sdk.Coin) error {
// Get AllowedVault, if not found (not a valid vault), return error
allowedVault, found := k.GetAllowedVault(ctx, wantAmount.Denom)
if !found {
return types.ErrInvalidVaultDenom
}
if wantAmount.IsZero() {
return types.ErrInsufficientAmount
}
// Check if VaultRecord exists
vaultRecord, found := k.GetVaultRecord(ctx, wantAmount.Denom)
if !found {
return types.ErrVaultRecordNotFound
}
// Get account value for vault
vaultAccValue, err := k.GetVaultAccountValue(ctx, wantAmount.Denom, from)
if err != nil {
return err
}
if vaultAccValue.IsZero() {
panic("vault account value is zero")
}
// Get account share record for the vault
vaultShareRecord, found := k.GetVaultShareRecord(ctx, from)
if !found {
return types.ErrVaultShareRecordNotFound
}
// Percent of vault account value the account is withdrawing
// This is the total account value, not just the supplied amount.
withdrawAmountPercent := wantAmount.Amount.ToDec().Quo(vaultAccValue.Amount.ToDec())
// Check if account is not withdrawing more than they have
// account value < want withdraw amount
if vaultAccValue.Amount.LT(wantAmount.Amount) {
return sdkerrors.Wrapf(
types.ErrInsufficientValue,
"account vault value of %s is less than %s desired withdraw amount",
vaultAccValue,
wantAmount,
)
}
// Get the strategy for the vault
strategy, err := k.GetStrategy(allowedVault.VaultStrategy)
if err != nil {
return err
}
// Not necessary to check if amount denom is allowed for the strategy, as
// there would be no vault record if it weren't allowed.
// Withdraw the wantAmount from the strategy
if err := strategy.Withdraw(ctx, wantAmount); err != nil {
return fmt.Errorf("failed to withdraw from strategy: %w", err)
}
// Send coins back to account, must withdraw from strategy first or the
// module account may not have any funds to send.
if err := k.bankKeeper.SendCoinsFromModuleToAccount(
ctx,
types.ModuleName,
from,
sdk.NewCoins(wantAmount),
); err != nil {
return err
}
// Shares withdrawn from vault
// For example:
// account supplied = 10hard
// account value = 20hard
// wantAmount = 10hard
// withdrawAmountPercent = 10hard / 20hard = 0.5
// sharesWithdrawn = 0.5 * 10hard = 5hard
vaultShareAmount := vaultShareRecord.AmountSupplied.AmountOf(wantAmount.Denom)
sharesWithdrawn := sdk.NewCoin(wantAmount.Denom, vaultShareAmount.
ToDec().
Mul(withdrawAmountPercent).
TruncateInt())
// Decrement VaultRecord and VaultShareRecord supplies
vaultRecord.TotalSupply = vaultRecord.TotalSupply.Sub(sharesWithdrawn)
vaultShareRecord.AmountSupplied = vaultShareRecord.AmountSupplied.Sub(sdk.NewCoins(sharesWithdrawn))
// Update VaultRecord and VaultShareRecord, deletes if zero supply
k.UpdateVaultRecord(ctx, vaultRecord)
k.UpdateVaultShareRecord(ctx, vaultShareRecord)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeVaultWithdraw,
sdk.NewAttribute(types.AttributeKeyVaultDenom, wantAmount.Denom),
sdk.NewAttribute(types.AttributeKeyOwner, from.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, wantAmount.Amount.String()),
),
)
return nil
}