0g-chain/x/swap/keeper/withdraw.go
Kevin Davis d45fa58f5c
Swap Genesis State (#960)
* wip: add swap state persistent to genesis

* separate pool record constructors; add tests for json and yaml encoding
of record structs

* beef up validation checks for state records

* fix integration with master - renamed method

* add test coverage for basic state array validations

* extra test around pool record reserve and id ordering to ensure no
regressions in the future

* add validations to ensure pool records and share records are unique
within the collection types

* test genesis json and yaml encoding

* validate in genesis that the total shares owned for each pool is equal
to the total shares of each pool

* update alias

* nit lint

* test genesis init and export

* add migration todo

Co-authored-by: Nick DeLuca <nickdeluca08@gmail.com>
2021-07-15 09:42:30 -05:00

93 lines
3.3 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/swap/types"
)
// Withdraw removes liquidity from an existing pool from an owners deposit, converting the provided shares for
// the returned pool liquidity.
//
// If 100% of the owners shares are removed, then the deposit is deleted. In addition, if all the pool shares
// are removed then the pool is deleted.
//
// The number of shares must be large enough to result in at least 1 unit of the smallest reserve in the pool.
// If the share input is below the minimum required for positive liquidity to be remove from both reserves, a
// insufficient error is returned.
//
// In addition, if the withdrawn liquidity for each reserve is below the provided minimum, a slippage exceeded
// error is returned.
func (k Keeper) Withdraw(ctx sdk.Context, owner sdk.AccAddress, shares sdk.Int, minCoinA, minCoinB sdk.Coin) error {
poolID := types.PoolID(minCoinA.Denom, minCoinB.Denom)
shareRecord, found := k.GetDepositorShares(ctx, owner, poolID)
if !found {
return sdkerrors.Wrapf(types.ErrDepositNotFound, "no deposit for account %s and pool %s", owner, poolID)
}
if shares.GT(shareRecord.SharesOwned) {
return sdkerrors.Wrapf(types.ErrInvalidShares, "withdraw of %s shares greater than %s shares owned", shares, shareRecord.SharesOwned)
}
poolRecord, found := k.GetPool(ctx, poolID)
if !found {
panic(fmt.Sprintf("pool %s not found", poolID))
}
pool, err := types.NewDenominatedPoolWithExistingShares(poolRecord.Reserves(), poolRecord.TotalShares)
if err != nil {
panic(fmt.Sprintf("invalid pool %s: %s", poolID, err))
}
withdrawnAmount := pool.RemoveLiquidity(shares)
if withdrawnAmount.AmountOf(minCoinA.Denom).IsZero() || withdrawnAmount.AmountOf(minCoinB.Denom).IsZero() {
return sdkerrors.Wrap(types.ErrInsufficientLiquidity, "shares must be increased")
}
if withdrawnAmount.AmountOf(minCoinA.Denom).LT(minCoinA.Amount) || withdrawnAmount.AmountOf(minCoinB.Denom).LT(minCoinB.Amount) {
return sdkerrors.Wrap(types.ErrSlippageExceeded, "minimum withdraw not met")
}
k.updatePool(ctx, poolID, pool)
k.hooks.BeforePoolDepositModified(ctx, poolID, owner, shareRecord.SharesOwned)
k.updateShares(ctx, owner, poolID, shareRecord.SharesOwned.Sub(shares))
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, owner, withdrawnAmount)
if err != nil {
panic(err)
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeSwapWithdraw,
sdk.NewAttribute(types.AttributeKeyPoolID, poolID),
sdk.NewAttribute(types.AttributeKeyOwner, owner.String()),
sdk.NewAttribute(sdk.AttributeKeyAmount, withdrawnAmount.String()),
sdk.NewAttribute(types.AttributeKeyShares, shares.String()),
),
)
return nil
}
func (k Keeper) updatePool(ctx sdk.Context, poolID string, pool *types.DenominatedPool) {
if pool.TotalShares().IsZero() {
k.DeletePool(ctx, poolID)
} else {
k.SetPool(ctx, types.NewPoolRecordFromPool(pool))
}
}
func (k Keeper) updateShares(ctx sdk.Context, owner sdk.AccAddress, poolID string, shares sdk.Int) {
if shares.IsZero() {
k.DeleteDepositorShares(ctx, owner, poolID)
} else {
shareRecord := types.NewShareRecord(owner, poolID, shares)
k.SetDepositorShares(ctx, shareRecord)
}
}