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

Adding single perpetual trigger #1105

Merged
merged 7 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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 x/leveragelp/keeper/position_close.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ func (k Keeper) ForceCloseLong(ctx sdk.Context, position types.Position, pool ty

positionOwner := sdk.MustAccAddressFromBech32(position.Address)

// TODO This means bot failed to close position on time, need to forcefully close the position
amityadav0 marked this conversation as resolved.
Show resolved Hide resolved
if userAmount.IsNegative() {
return math.ZeroInt(), types.ErrNegUserAmountAfterRepay
}

if userAmount.IsPositive() {
err = k.bankKeeper.SendCoins(ctx, position.GetPositionAddress(), positionOwner, sdk.Coins{sdk.NewCoin(position.Collateral.Denom, userAmount)})
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions x/perpetual/client/cli/query_close_estimation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import (
"cosmossdk.io/math"
"fmt"
"errors"
"strconv"

"github.com/cosmos/cosmos-sdk/client"
Expand All @@ -29,7 +29,7 @@

reqClosingAmount, ok := math.NewIntFromString(args[2])
if !ok {
return fmt.Errorf("invalid closing amount")
return errors.New("invalid closing amount")

Check warning on line 32 in x/perpetual/client/cli/query_close_estimation.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/client/cli/query_close_estimation.go#L32

Added line #L32 was not covered by tests
}

clientCtx, err := client.GetClientQueryContext(cmd)
Expand Down
40 changes: 25 additions & 15 deletions x/perpetual/keeper/close.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
package keeper

import (
errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
assetprofiletypes "github.com/elys-network/elys/x/assetprofile/types"
ptypes "github.com/elys-network/elys/x/parameter/types"
"github.com/elys-network/elys/x/perpetual/types"
"strconv"
)

func (k Keeper) Close(ctx sdk.Context, msg *types.MsgClose) (*types.MsgCloseResponse, error) {
entry, found := k.assetProfileKeeper.GetEntry(ctx, ptypes.BaseCurrency)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
closedMtp, repayAmount, closingRatio, returnAmt, fundingFeeAmt, interestAmt, insuranceAmt, allInterestsPaid, forceClosed, err := k.ClosePosition(ctx, msg)
if err != nil {
return nil, err
}
baseCurrency := entry.Denom

closedMtp, repayAmount, closingRatio, err := k.ClosePosition(ctx, msg, baseCurrency)
tradingAssetPrice, err := k.GetAssetPrice(ctx, closedMtp.TradingAsset)
if err != nil {
return nil, err
}

// Emit close event
k.EmitCloseEvent(ctx, closedMtp, repayAmount, closingRatio)
ctx.EventManager().EmitEvent(
sdk.NewEvent(types.EventClose,
sdk.NewAttribute("mtp_id", strconv.FormatInt(int64(closedMtp.Id), 10)),
sdk.NewAttribute("owner", closedMtp.Address),
sdk.NewAttribute("amm_pool_id", strconv.FormatInt(int64(closedMtp.AmmPoolId), 10)),
sdk.NewAttribute("collateral_asset", closedMtp.CollateralAsset),
sdk.NewAttribute("position", closedMtp.Position.String()),
sdk.NewAttribute("mtp_health", closedMtp.MtpHealth.String()), // should be there if it's partial close
sdk.NewAttribute("repay_amount", repayAmount.String()),
sdk.NewAttribute("return_amount", returnAmt.String()),
sdk.NewAttribute("funding_fee_amount", fundingFeeAmt.String()),
sdk.NewAttribute("interest_amount", interestAmt.String()),
sdk.NewAttribute("insurance_amount", insuranceAmt.String()),
sdk.NewAttribute("funding_fee_paid_custody", closedMtp.FundingFeePaidCustody.String()),
sdk.NewAttribute("funding_fee_received_custody", closedMtp.FundingFeeReceivedCustody.String()),
sdk.NewAttribute("closing_ratio", closingRatio.String()),
sdk.NewAttribute("trading_asset_price", tradingAssetPrice.String()),
sdk.NewAttribute("all_interests_paid", strconv.FormatBool(allInterestsPaid)), // Funding Fee is fully paid but interest amount is only partially paid then this will be false
sdk.NewAttribute("force_closed", strconv.FormatBool(forceClosed)),
))

return &types.MsgCloseResponse{
Id: closedMtp.Id,
Amount: repayAmount,
}, nil
}

func (k Keeper) EmitCloseEvent(ctx sdk.Context, mtp *types.MTP, repayAmount sdkmath.Int, closingRatio sdkmath.LegacyDec) {
ctx.EventManager().EmitEvent(types.GenerateCloseEvent(mtp, repayAmount, closingRatio))
}
42 changes: 19 additions & 23 deletions x/perpetual/keeper/close_position.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,40 @@
package keeper

import (
"fmt"

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/elys-network/elys/x/perpetual/types"
)

func (k Keeper) ClosePosition(ctx sdk.Context, msg *types.MsgClose, baseCurrency string) (*types.MTP, math.Int, math.LegacyDec, error) {
func (k Keeper) ClosePosition(ctx sdk.Context, msg *types.MsgClose) (types.MTP, math.Int, math.LegacyDec, math.Int, math.Int, math.Int, math.Int, bool, bool, error) {
// Retrieve MTP
creator := sdk.MustAccAddressFromBech32(msg.Creator)
mtp, err := k.GetMTP(ctx, creator, msg.Id)
if err != nil {
return nil, math.ZeroInt(), math.LegacyZeroDec(), err
return types.MTP{}, math.ZeroInt(), math.LegacyZeroDec(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), false, false, err
}

// Retrieve AmmPool
ammPool, err := k.GetAmmPool(ctx, mtp.AmmPoolId)
if err != nil {
return nil, math.ZeroInt(), math.LegacyZeroDec(), err
}

// This needs to be updated here to check user doesn't send more than required amount
k.UpdateMTPBorrowInterestUnpaidLiability(ctx, &mtp)
// Retrieve Pool
pool, found := k.GetPool(ctx, mtp.AmmPoolId)
if !found {
return nil, math.ZeroInt(), math.LegacyZeroDec(), errorsmod.Wrap(types.ErrPoolDoesNotExist, fmt.Sprintf("poolId: %d", mtp.AmmPoolId))
return mtp, math.ZeroInt(), math.LegacyZeroDec(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), false, false, errorsmod.Wrap(types.ErrPoolDoesNotExist, fmt.Sprintf("poolId: %d", mtp.AmmPoolId))

Check warning on line 21 in x/perpetual/keeper/close_position.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/close_position.go#L21

Added line #L21 was not covered by tests
}

// Handle Borrow Interest if within epoch position SettleMTPBorrowInterestUnpaidLiability settles interest using mtp.Custody, mtp.Custody gets reduced
if _, err = k.SettleMTPBorrowInterestUnpaidLiability(ctx, &mtp, &pool, ammPool); err != nil {
return nil, math.ZeroInt(), math.LegacyZeroDec(), err
// Retrieve AmmPool
ammPool, err := k.GetAmmPool(ctx, mtp.AmmPoolId)
if err != nil {
return mtp, math.ZeroInt(), math.LegacyZeroDec(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), false, false, err

Check warning on line 27 in x/perpetual/keeper/close_position.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/close_position.go#L27

Added line #L27 was not covered by tests
}

err = k.SettleFunding(ctx, &mtp, &pool, ammPool)
// this also handles edge case where bot is unable to close position in time.
repayAmt, returnAmt, fundingFeeAmt, interestAmt, insuranceAmt, allInterestsPaid, forceClosed, err := k.MTPTriggerChecksAndUpdates(ctx, &mtp, &pool, &ammPool)
if err != nil {
return nil, math.ZeroInt(), math.LegacyZeroDec(), errorsmod.Wrapf(err, "error handling funding fee")
return types.MTP{}, math.ZeroInt(), math.LegacyZeroDec(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), false, false, err
}

if forceClosed {
return mtp, repayAmt, math.LegacyOneDec(), returnAmt, fundingFeeAmt, interestAmt, insuranceAmt, allInterestsPaid, forceClosed, nil
}

// Should be declared after SettleMTPBorrowInterestUnpaidLiability and settling funding
Expand All @@ -51,19 +47,19 @@
}

// Estimate swap and repay
repayAmt, err := k.EstimateAndRepay(ctx, &mtp, &pool, &ammPool, baseCurrency, closingRatio)
repayAmt, returnAmt, err = k.EstimateAndRepay(ctx, &mtp, &pool, &ammPool, closingRatio)
if err != nil {
return nil, math.ZeroInt(), math.LegacyZeroDec(), err
return mtp, math.ZeroInt(), math.LegacyZeroDec(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), math.ZeroInt(), allInterestsPaid, forceClosed, err
}

// EpochHooks after perpetual position closed
if k.hooks != nil {
params := k.GetParams(ctx)
err = k.hooks.AfterPerpetualPositionClosed(ctx, ammPool, pool, creator, params.EnableTakeProfitCustodyLiabilities)
if err != nil {
return nil, math.Int{}, math.LegacyDec{}, err
return mtp, math.Int{}, math.LegacyDec{}, math.Int{}, math.Int{}, math.Int{}, math.Int{}, allInterestsPaid, forceClosed, err

Check warning on line 60 in x/perpetual/keeper/close_position.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/close_position.go#L60

Added line #L60 was not covered by tests
}
}

return &mtp, repayAmt, closingRatio, nil
return mtp, repayAmt, closingRatio, returnAmt, fundingFeeAmt, interestAmt, insuranceAmt, allInterestsPaid, forceClosed, nil
}
10 changes: 5 additions & 5 deletions x/perpetual/keeper/close_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (suite *PerpetualKeeperTestSuite) TestClose() {
Amount: math.NewInt(500),
}
},
"asset uusdc not found",
"unable to find base currency entry",
math.NewInt(0),
},
{
Expand Down Expand Up @@ -279,7 +279,7 @@ func (suite *PerpetualKeeperTestSuite) TestClose() {
math.NewInt(4501),
},
{
"Close with too much unpaid Liability to make custody amount 0",
"Force Close with too much unpaid Liability making custody amount 0",
func() *types.MsgClose {
suite.ResetSuite()

Expand Down Expand Up @@ -313,8 +313,8 @@ func (suite *PerpetualKeeperTestSuite) TestClose() {
Amount: math.NewInt(399),
}
},
"error handling funding fee",
math.NewInt(0),
"",
math.NewInt(203),
},
{
"Close short with Not Enough liquidity",
Expand Down Expand Up @@ -364,7 +364,7 @@ func (suite *PerpetualKeeperTestSuite) TestClose() {
suite.Require().Contains(err.Error(), tc.expectedErrMsg)
} else {
suite.Require().NoError(err)
suite.Require().Equal(tc.repayAmount, res.Amount)
suite.Require().Equal(res.Amount.String(), tc.repayAmount.String())
}
})
}
Expand Down
21 changes: 15 additions & 6 deletions x/perpetual/keeper/estimate_and_repay.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,44 @@
import (
"fmt"

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
ammtypes "github.com/elys-network/elys/x/amm/types"
assetprofiletypes "github.com/elys-network/elys/x/assetprofile/types"
ptypes "github.com/elys-network/elys/x/parameter/types"
"github.com/elys-network/elys/x/perpetual/types"
)

// EstimateAndRepay ammPool has to be pointer because RemoveFromPoolBalance (in Repay) updates pool assets
// Important to send pointer mtp and pool
func (k Keeper) EstimateAndRepay(ctx sdk.Context, mtp *types.MTP, pool *types.Pool, ammPool *ammtypes.Pool, baseCurrency string, closingRatio math.LegacyDec) (math.Int, error) {
func (k Keeper) EstimateAndRepay(ctx sdk.Context, mtp *types.MTP, pool *types.Pool, ammPool *ammtypes.Pool, closingRatio math.LegacyDec) (math.Int, math.Int, error) {

if closingRatio.LTE(math.LegacyZeroDec()) || closingRatio.GT(math.LegacyOneDec()) {
return math.Int{}, fmt.Errorf("invalid closing ratio (%s)", closingRatio.String())
return math.Int{}, math.Int{}, fmt.Errorf("invalid closing ratio (%s)", closingRatio.String())

Check warning on line 20 in x/perpetual/keeper/estimate_and_repay.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/estimate_and_repay.go#L20

Added line #L20 was not covered by tests
}

repayAmount, payingLiabilities, _, _, err := k.CalcRepayAmount(ctx, mtp, ammPool, closingRatio)
if err != nil {
return math.ZeroInt(), err
return math.ZeroInt(), math.ZeroInt(), err

Check warning on line 25 in x/perpetual/keeper/estimate_and_repay.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/estimate_and_repay.go#L25

Added line #L25 was not covered by tests
}
returnAmount, err := k.CalcReturnAmount(*mtp, repayAmount, closingRatio)
if err != nil {
return math.ZeroInt(), err
return math.ZeroInt(), math.ZeroInt(), err

Check warning on line 29 in x/perpetual/keeper/estimate_and_repay.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/estimate_and_repay.go#L29

Added line #L29 was not covered by tests
}

entry, found := k.assetProfileKeeper.GetEntry(ctx, ptypes.BaseCurrency)
if !found {
return math.Int{}, math.Int{}, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
}

Check warning on line 35 in x/perpetual/keeper/estimate_and_repay.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/estimate_and_repay.go#L34-L35

Added lines #L34 - L35 were not covered by tests
baseCurrency := entry.Denom

// Note: Long settlement is done in trading asset. And short settlement in usdc in Repay function
if err = k.Repay(ctx, mtp, pool, ammPool, returnAmount, payingLiabilities, closingRatio, baseCurrency); err != nil {
return math.ZeroInt(), err
return math.ZeroInt(), math.ZeroInt(), err
}

return repayAmount, nil
return repayAmount, returnAmount, nil
}

// CalcRepayAmount repay amount is in custody asset for liabilities with closing ratio
Expand Down
43 changes: 16 additions & 27 deletions x/perpetual/keeper/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,23 @@ import (
"github.com/elys-network/elys/x/perpetual/types"
)

func (k Keeper) EmitFundPayment(ctx sdk.Context, mtp *types.MTP, takeAmount math.Int, takeAsset string, paymentType string) {
ctx.EventManager().EmitEvent(sdk.NewEvent(paymentType,
sdk.NewAttribute("id", strconv.FormatInt(int64(mtp.Id), 10)),
sdk.NewAttribute("payment_amount", takeAmount.String()),
sdk.NewAttribute("payment_asset", takeAsset),
))
}

func (k Keeper) EmitForceClose(ctx sdk.Context, eventType string, mtp *types.MTP, repayAmount math.Int, closer string) {
ctx.EventManager().EmitEvent(sdk.NewEvent(eventType,
sdk.NewAttribute("id", strconv.FormatInt(int64(mtp.Id), 10)),
func (k Keeper) EmitForceClose(ctx sdk.Context, trigger string, mtp types.MTP, repayAmount, returnAmt, fundingFeeAmt, interestAmt, insuranceAmt math.Int, closer string, allInterestsPaid bool, tradingAssetPrice math.LegacyDec) {
ctx.EventManager().EmitEvent(sdk.NewEvent(types.EventForceClosed,
sdk.NewAttribute("mtp_id", strconv.FormatInt(int64(mtp.Id), 10)),
sdk.NewAttribute("owner", mtp.Address),
sdk.NewAttribute("amm_pool_id", strconv.FormatInt(int64(mtp.AmmPoolId), 10)),
sdk.NewAttribute("position", mtp.Position.String()),
sdk.NewAttribute("address", mtp.Address),
sdk.NewAttribute("collaterals", mtp.Collateral.String()),
sdk.NewAttribute("custodies", mtp.Custody.String()),
sdk.NewAttribute("repay_amount", repayAmount.String()),
sdk.NewAttribute("liabilities", mtp.Liabilities.String()),
sdk.NewAttribute("borrow_interest_unpaid_liability", mtp.BorrowInterestUnpaidLiability.String()),
sdk.NewAttribute("borrow_interest_paid_custody", mtp.BorrowInterestPaidCustody.String()),
sdk.NewAttribute("health", mtp.MtpHealth.String()),
sdk.NewAttribute("collateral_asset", mtp.CollateralAsset),
sdk.NewAttribute("closer", closer),
))
}

func (k Keeper) EmitFundingFeePayment(ctx sdk.Context, mtp *types.MTP, takeAmount math.Int, takeAsset string, paymentType string) {
ctx.EventManager().EmitEvent(sdk.NewEvent(paymentType,
sdk.NewAttribute("id", strconv.FormatInt(int64(mtp.Id), 10)),
sdk.NewAttribute("payment_amount", takeAmount.String()),
sdk.NewAttribute("payment_asset", takeAsset),
sdk.NewAttribute("repay_amount", repayAmount.String()),
sdk.NewAttribute("return_amount", returnAmt.String()),
sdk.NewAttribute("funding_fee_amount", fundingFeeAmt.String()),
sdk.NewAttribute("interest_amount", interestAmt.String()),
sdk.NewAttribute("insurance_amount", insuranceAmt.String()),
sdk.NewAttribute("funding_fee_paid_custody", mtp.FundingFeePaidCustody.String()),
sdk.NewAttribute("funding_fee_received_custody", mtp.FundingFeeReceivedCustody.String()),
sdk.NewAttribute("trading_asset_price", tradingAssetPrice.String()),
sdk.NewAttribute("all_interests_paid", strconv.FormatBool(allInterestsPaid)), // Funding Fee is fully paid but interest amount is only partially paid then this will be false
sdk.NewAttribute("trigger", trigger),
))
}
32 changes: 32 additions & 0 deletions x/perpetual/keeper/force_close.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package keeper

import (
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
ammtypes "github.com/elys-network/elys/x/amm/types"
"github.com/elys-network/elys/x/perpetual/types"
)

func (k Keeper) ForceClose(ctx sdk.Context, mtp *types.MTP, pool *types.Pool, ammPool *ammtypes.Pool) (math.Int, math.Int, error) {
repayAmount := math.ZeroInt()

// Estimate swap and repay
repayAmt, returnAmount, err := k.EstimateAndRepay(ctx, mtp, pool, ammPool, math.LegacyOneDec())
if err != nil {
return math.ZeroInt(), math.ZeroInt(), err
}

Check warning on line 17 in x/perpetual/keeper/force_close.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/force_close.go#L16-L17

Added lines #L16 - L17 were not covered by tests

repayAmount = repayAmount.Add(repayAmt)

address := sdk.MustAccAddressFromBech32(mtp.Address)
// EpochHooks after perpetual position closed
if k.hooks != nil {
params := k.GetParams(ctx)
err = k.hooks.AfterPerpetualPositionClosed(ctx, *ammPool, *pool, address, params.EnableTakeProfitCustodyLiabilities)
if err != nil {
return math.Int{}, math.Int{}, err
}

Check warning on line 28 in x/perpetual/keeper/force_close.go

View check run for this annotation

Codecov / codecov/patch

x/perpetual/keeper/force_close.go#L27-L28

Added lines #L27 - L28 were not covered by tests
}

return repayAmt, returnAmount, nil
}
36 changes: 0 additions & 36 deletions x/perpetual/keeper/force_close_long.go

This file was deleted.

Loading
Loading