Skip to content

Commit

Permalink
basic implementation of DecreaseLiquidity and IncreaseLiquidity
Browse files Browse the repository at this point in the history
  • Loading branch information
jununifi committed May 22, 2024
1 parent 5ae328c commit 6926e19
Show file tree
Hide file tree
Showing 8 changed files with 3,442 additions and 37 deletions.
2,123 changes: 2,123 additions & 0 deletions api/sunrise/liquiditypool/ticker.pulsar.go

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions proto/sunrise/liquiditypool/ticker.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
syntax = "proto3";

package sunrise.liquiditypool;

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";

option go_package = "github.com/sunriselayer/sunrise/x/liquiditypool/types";

message TickInfo {
uint64 pool_id = 1;
int64 tick_index = 2;
string liquidity_gross = 3 [
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.moretags) = "yaml:\"liquidity_gross\"",
(gogoproto.nullable) = false
];
string liquidity_net = 4 [
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.moretags) = "yaml:\"liquidity_net\"",
(gogoproto.nullable) = false
];
repeated cosmos.base.v1beta1.DecCoin fee_growth = 5 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins",
(gogoproto.nullable) = false
];
UptimeTrackers uptime_trackers = 6 [
(gogoproto.moretags) = "yaml:\"uptime_trackers\"",
(gogoproto.nullable) = false
];
}

message UptimeTrackers {
repeated UptimeTracker list = 1 [ (gogoproto.nullable) = false ];
}

message UptimeTracker {
repeated cosmos.base.v1beta1.DecCoin uptime_growth_outside = 1 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins",
(gogoproto.nullable) = false
];
}
203 changes: 166 additions & 37 deletions x/liquiditypool/keeper/msg_server_position.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,11 @@ func (k msgServer) CreatePosition(goCtx context.Context, msg *types.MsgCreatePos
amountBaseDesired := msg.TokenBase.Amount
amountQuoteDesired := msg.TokenQuote.Amount

// Transform the provided ticks to sqrtPrices.
sqrtPriceLowerTick, sqrtPriceUpperTick, err := types.TicksToSqrtPrice(msg.LowerTick, msg.UpperTick, pool.TickParams)
if err != nil {
return nil, err
}

// If this is the first position created in this pool, ensure that the position includes both assets.
hasPositions := pool.HasPosition(ctx)
if !hasPositions {
err := k.initFirstPositionForPool(ctx, pool, amountBaseDesired, amountQuoteDesired)
Expand All @@ -79,91 +77,173 @@ func (k msgServer) CreatePosition(goCtx context.Context, msg *types.MsgCreatePos
return nil, fmt.Errorf(`liquidityDelta got 0 value unexpectedly`)
}

// Add a position in the pool
// Add an empty position in the pool
var position = types.Position{
PoolId: msg.PoolId,
Address: msg.Sender,
LowerTick: msg.LowerTick,
UpperTick: msg.UpperTick,
Liquidity: liquidityDelta,
Liquidity: math.LegacyZeroDec(),
}
positionId := k.AppendPosition(ctx, position)

id := k.AppendPosition(ctx, position)

// calculate the actual amounts of tokens 0 and 1 that were added or removed from the pool.
amountBase, amountQuote, err := pool.CalcActualAmounts(ctx, msg.LowerTick, msg.UpperTick, liquidityDelta)
// Initialize / update the position in the pool based on the provided tick range and liquidity delta.
amountBase, amountQuote, _, _, err := k.UpdatePosition(ctx, position.PoolId, sender, position.LowerTick, position.UpperTick, liquidityDelta, positionId)
if err != nil {
return nil, err
}

// Check if the actual amounts are greater than or equal to minimum amounts
if amountBase.LT(msg.MinAmountBase.ToLegacyDec()) {
if amountBase.LT(msg.MinAmountBase) {
return nil, types.ErrInsufficientAmountPut
}
if amountQuote.LT(msg.MinAmountQuote.ToLegacyDec()) {
if amountQuote.LT(msg.MinAmountQuote) {
return nil, types.ErrInsufficientAmountPut
}

// Transfer amounts to the pool
coins := sdk.Coins{}
amountBaseInt := amountBase.TruncateInt()
amountQuoteInt := amountQuote.TruncateInt()
coins = coins.Add(sdk.NewCoin(msg.TokenBase.Denom, amountBaseInt))
coins = coins.Add(sdk.NewCoin(msg.TokenQuote.Denom, amountQuoteInt))
coins := sdk.Coins{sdk.NewCoin(msg.TokenBase.Denom, amountBase)}
coins = coins.Add(sdk.NewCoin(msg.TokenQuote.Denom, amountQuote))
err = k.bankKeeper.SendCoins(ctx, sender, pool.GetAddress(), coins)
if err != nil {
return nil, err
}

return &types.MsgCreatePositionResponse{
Id: id,
AmountBase: amountBaseInt,
AmountQuote: amountQuoteInt,
Id: positionId,
AmountBase: amountBase,
AmountQuote: amountQuote,
Liquidity: position.Liquidity,
}, nil
}

func (k msgServer) IncreaseLiquidity(goCtx context.Context, msg *types.MsgIncreaseLiquidity) (*types.MsgIncreaseLiquidityResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

var position = types.Position{
Address: msg.Sender,
Id: msg.Id,
}

// Checks that the element exists
val, found := k.GetPosition(ctx, msg.Id)
position, found := k.GetPosition(ctx, msg.Id)
if !found {
return nil, errorsmod.Wrap(sdkerrors.ErrKeyNotFound, fmt.Sprintf("key %d doesn't exist", msg.Id))
}

// Checks if the msg sender is the same as the current owner
if msg.Sender != val.Address {
if msg.Sender != position.Address {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner")
}

if msg.AmountBase.IsNegative() || msg.AmountQuote.IsNegative() {
return nil, types.ErrNegativeTokenAmount
}

if msg.AmountBase.IsZero() && msg.AmountQuote.IsZero() {
return nil, errorsmod.Wrapf(types.ErrInvalidTokenAmounts, "base amount %s, quote amount %s", msg.AmountBase.String(), msg.AmountQuote.String())
}

k.SetPosition(ctx, position)

return &types.MsgIncreaseLiquidityResponse{}, nil
}
// Remove full position liquidity
sender := sdk.MustAccAddressFromBech32(msg.Sender)
amountBaseWithdrawn, amountQuoteWithdrawn, err := k.Keeper.DecreaseLiquidity(ctx, sender, msg.Id, position.Liquidity)
if err != nil {
return nil, err
}

func (k msgServer) DecreaseLiquidity(goCtx context.Context, msg *types.MsgDecreaseLiquidity) (*types.MsgDecreaseLiquidityResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
pool, found := k.GetPool(ctx, position.PoolId)
if !found {
return nil, types.ErrPoolNotFound
}

// Create a new position with combined liquidity
amountBaseDesired := amountBaseWithdrawn.Add(msg.AmountBase)
amountQuoteDesired := amountQuoteWithdrawn.Add(msg.AmountQuote)
minimumAmountBase := amountBaseWithdrawn.Add(msg.MinAmountBase)
minimumAmountQuote := amountQuoteWithdrawn.Add(msg.MinAmountQuote)

res, err := k.CreatePosition(ctx, &types.MsgCreatePosition{
Sender: msg.Sender,
PoolId: position.PoolId,
LowerTick: position.LowerTick,
UpperTick: position.UpperTick,
TokenBase: sdk.NewCoin(pool.DenomBase, amountBaseDesired),
TokenQuote: sdk.NewCoin(pool.DenomQuote, amountQuoteDesired),
MinAmountBase: minimumAmountBase,
MinAmountQuote: minimumAmountQuote,
})
if err != nil {
return nil, err
}

return &types.MsgIncreaseLiquidityResponse{
AmountBase: res.AmountBase,
AmountQuote: res.AmountQuote,
}, nil
}

func (k Keeper) DecreaseLiquidity(ctx sdk.Context, sender sdk.AccAddress, positionId uint64, liquidity math.LegacyDec) (amountBase math.Int, amountQuote math.Int, err error) {
// Checks that the element exists
position, found := k.GetPosition(ctx, msg.Id)
position, found := k.GetPosition(ctx, positionId)
if !found {
return nil, errorsmod.Wrap(sdkerrors.ErrKeyNotFound, fmt.Sprintf("key %d doesn't exist", msg.Id))
return math.Int{}, math.Int{}, errorsmod.Wrap(types.ErrPositionNotFound, fmt.Sprintf("id: %d", positionId))
}

// Checks if the msg sender is the same as the current owner
if msg.Sender != position.Address {
return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner")
if sender.String() != position.Address {
return math.Int{}, math.Int{}, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner")
}

// Check if withdrawing negative amount
if liquidity.IsNegative() {
return math.Int{}, math.Int{}, types.ErrNegativeTokenAmount
}

if position.Liquidity.LT(liquidity) {
return math.Int{}, math.Int{}, types.ErrInsufficientLiquidity
}

k.RemovePosition(ctx, msg.Id)
pool, found := k.GetPool(ctx, position.PoolId)
if !found {
return math.Int{}, math.Int{}, errorsmod.Wrapf(types.ErrPoolNotFound, "pool id: %d", position.PoolId)
}

return &types.MsgDecreaseLiquidityResponse{}, nil
liquiditDelta := liquidity.Neg()
amountBase, amountQuote, lowerTickEmpty, upperTickEmpty, err := k.UpdatePosition(ctx, position.PoolId, sender, position.LowerTick, position.UpperTick, liquiditDelta, positionId)
if err != nil {
return math.Int{}, math.Int{}, err
}

coins := sdk.Coins{sdk.NewCoin(pool.DenomBase, amountBase)}
coins = coins.Add(sdk.NewCoin(pool.DenomQuote, amountQuote))
err = k.bankKeeper.SendCoins(ctx, sender, pool.GetAddress(), coins)
if err != nil {
return math.Int{}, math.Int{}, err
}

if liquidity.Equal(position.Liquidity) {
// TODO: collectFees
k.RemovePosition(ctx, position.Id)
k.resetPool(ctx, pool)
}

if lowerTickEmpty {
k.RemoveTickInfo(ctx, position.PoolId, position.LowerTick)
}
if upperTickEmpty {
k.RemoveTickInfo(ctx, position.PoolId, position.UpperTick)
}
return amountBase, amountQuote, nil
}

func (k msgServer) DecreaseLiquidity(goCtx context.Context, msg *types.MsgDecreaseLiquidity) (*types.MsgDecreaseLiquidityResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

sender := sdk.MustAccAddressFromBech32(msg.Sender)
amountBase, amountQuote, err := k.Keeper.DecreaseLiquidity(ctx, sender, msg.Id, msg.Liquidity)
if err != nil {
return nil, err
}

return &types.MsgDecreaseLiquidityResponse{
AmountBase: amountBase,
AmountQuote: amountQuote,
}, nil
}

func (k Keeper) initFirstPositionForPool(ctx sdk.Context, pool types.Pool, amountBaseDesired, amountQuoteDesired math.Int) error {
Expand Down Expand Up @@ -191,3 +271,52 @@ func (k Keeper) initFirstPositionForPool(ctx sdk.Context, pool types.Pool, amoun

return nil
}

func (k Keeper) resetPool(ctx sdk.Context, pool types.Pool) {
pool.CurrentSqrtPrice = math.LegacyZeroDec()
pool.CurrentTick = 0

k.SetPool(ctx, pool)
}

func (k Keeper) UpdatePosition(ctx sdk.Context, poolId uint64, owner sdk.AccAddress, lowerTick, upperTick int64, liquidityDelta math.LegacyDec, positionId uint64) (amountBase math.Int, amountQuote math.Int, lowerTickEmpty bool, upperTickEmpty bool, err error) {
lowerTickIsEmpty, err := k.initOrUpdateTick(ctx, poolId, lowerTick, liquidityDelta, false)
if err != nil {
return math.Int{}, math.Int{}, false, false, err
}

upperTickIsEmpty, err := k.initOrUpdateTick(ctx, poolId, upperTick, liquidityDelta, true)
if err != nil {
return math.Int{}, math.Int{}, false, false, err
}

pool, found := k.GetPool(ctx, poolId)
if !found {
return math.Int{}, math.Int{}, false, false, types.ErrPoolNotFound
}

position, found := k.GetPosition(ctx, positionId)
if !found {
return math.Int{}, math.Int{}, false, false, types.ErrPositionNotFound
}

// Update position liquidity
position.Liquidity = position.Liquidity.Add(liquidityDelta)
if position.Liquidity.IsNegative() {
return math.Int{}, math.Int{}, false, false, types.ErrNegativeLiquidity
}
k.SetPosition(ctx, position)

actualAmountBase, actualAmountQuote, err := pool.CalcActualAmounts(ctx, lowerTick, upperTick, liquidityDelta)
if err != nil {
return math.Int{}, math.Int{}, false, false, err
}

pool.UpdateLiquidityIfActivePosition(ctx, lowerTick, upperTick, liquidityDelta)

k.SetPool(ctx, pool)

// TODO: update feeAccumulator

return actualAmountBase.TruncateInt(), actualAmountQuote.TruncateInt(), lowerTickIsEmpty, upperTickIsEmpty, nil
}
Loading

0 comments on commit 6926e19

Please sign in to comment.