Skip to content

Commit

Permalink
Feat/perp close estimation (#828)
Browse files Browse the repository at this point in the history
* feat: close estimation query init files

* feat: add additional logic + tests

* test(perpetual): fix tests
  • Loading branch information
cosmic-vagabond authored Oct 3, 2024
1 parent 6e5c3a9 commit e018c0a
Show file tree
Hide file tree
Showing 25 changed files with 2,717 additions and 850 deletions.
1,431 changes: 855 additions & 576 deletions docs/static/openapi.yml

Large diffs are not rendered by default.

32 changes: 25 additions & 7 deletions proto/elys/perpetual/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,15 @@ service Query {
option (google.api.http).get = "/elys-network/elys/perpetual/get_all_to_pay";

}

// Queries a list of CloseEstimation items.
rpc CloseEstimation (QueryCloseEstimationRequest) returns (QueryCloseEstimationResponse) {
option (google.api.http).get = "/elys-network/elys/perpetual/close-estimation/{position_id}";

}
}

message MtpAndPrice {
MTP mtp =1;
MTP mtp = 1;
string trading_asset_price = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
Expand All @@ -99,6 +104,7 @@ message MtpAndPrice {
(gogoproto.nullable) = false
];
}

// ParamsRequest is request type for the Query/Params RPC method.
message ParamsRequest {}

Expand All @@ -114,7 +120,7 @@ message PositionsRequest {
}

message PositionsResponse {
repeated MtpAndPrice mtps = 1;
repeated MtpAndPrice mtps = 1;
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

Expand All @@ -124,7 +130,7 @@ message PositionsByPoolRequest {
}

message PositionsByPoolResponse {
repeated MtpAndPrice mtps = 1;
repeated MtpAndPrice mtps = 1;
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

Expand All @@ -141,7 +147,7 @@ message PositionsForAddressRequest {
}

message PositionsForAddressResponse {
repeated MtpAndPrice mtps = 1;
repeated MtpAndPrice mtps = 1;
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

Expand Down Expand Up @@ -176,8 +182,8 @@ message QueryAllPoolRequest {
}

message QueryAllPoolResponse {
repeated PoolResponse pool = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
repeated PoolResponse pool = 1 [(gogoproto.nullable) = false];
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

message MTPRequest {
Expand Down Expand Up @@ -252,6 +258,18 @@ message PoolResponse {
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}

message QueryCloseEstimationRequest {
string address = 1;
uint64 position_id = 2;
}

message QueryCloseEstimationResponse {
Position position = 1;
cosmos.base.v1beta1.Coin position_size = 2 [(gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin liabilities = 3 [(gogoproto.nullable) = false];
string price_impact = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
string swap_fee = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
cosmos.base.v1beta1.Coin return_amount = 6 [(gogoproto.nullable) = false];
}
4 changes: 2 additions & 2 deletions testutil/keeper/perpetual.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
"github.com/stretchr/testify/require"
)

func PerpetualKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) {
func PerpetualKeeper(t testing.TB) (*keeper.Keeper, sdk.Context, *mocks.AssetProfileKeeper) {
storeKey := sdk.NewKVStoreKey(types.StoreKey)
memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey)

Expand Down Expand Up @@ -52,5 +52,5 @@ func PerpetualKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) {
params := types.DefaultParams()
k.SetParams(ctx, &params)

return k, ctx
return k, ctx, assetProfileKeeper
}
4 changes: 2 additions & 2 deletions x/amm/types/calc_in_amt_given_out.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ func (p Pool) CalcInAmtGivenOut(
}

amountInWithoutSlippage := sdk.NewDecFromInt(tokenOut.Amount).Quo(rate)
if tokenAmountIn.IsZero(){
if tokenAmountIn.IsZero() {
return sdk.Coin{}, sdk.ZeroDec(), ErrAmountTooLow
}
}
slippage = sdk.OneDec().Sub(amountInWithoutSlippage.Quo(tokenAmountIn))

// Ensure (1 - swapfee) is not zero to avoid division by zero
Expand Down
6 changes: 3 additions & 3 deletions x/amm/types/calc_out_amt_given_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ func (p Pool) CalcOutAmtGivenIn(
if err != nil {
return sdk.Coin{}, sdk.ZeroDec(), err
}
if tokenAmountOut.IsZero(){
if tokenAmountOut.IsZero() {
return sdk.Coin{}, sdk.ZeroDec(), ErrAmountTooLow
}
}

rate, err := p.GetTokenARate(ctx, oracle, snapshot, tokenIn.Denom, tokenOutDenom, accountedPool)
if err != nil {
Expand All @@ -80,7 +80,7 @@ func (p Pool) CalcOutAmtGivenIn(
if amountOutWithoutSlippage.IsZero() {
return sdk.Coin{}, sdk.ZeroDec(), errorsmod.Wrapf(ErrInvalidMathApprox, "amount out without slippage must be positive")
}

slippage := sdk.OneDec().Sub(tokenAmountOut.Quo(amountOutWithoutSlippage))

// We ignore the decimal component, as we round down the token amount out.
Expand Down
2 changes: 2 additions & 0 deletions x/perpetual/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func GetQueryCmd(queryRoute string) *cobra.Command {

cmd.AddCommand(CmdGetAllToPay())

cmd.AddCommand(CmdCloseEstimation())

// this line is used by starport scaffolding # 1

return cmd
Expand Down
52 changes: 52 additions & 0 deletions x/perpetual/client/cli/query_close_estimation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cli

import (
"strconv"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/elys-network/elys/x/perpetual/types"
"github.com/spf13/cast"
"github.com/spf13/cobra"
)

var _ = strconv.Itoa(0)

func CmdCloseEstimation() *cobra.Command {
cmd := &cobra.Command{
Use: "close-estimation [address] [position-id]",
Short: "Query close-estimation",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) (err error) {
reqAddress := args[0]

reqPositionId, err := cast.ToUint64E(args[1])
if err != nil {
return err
}

clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

params := &types.QueryCloseEstimationRequest{
Address: reqAddress,
PositionId: reqPositionId,
}

res, err := queryClient.CloseEstimation(cmd.Context(), params)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
2 changes: 1 addition & 1 deletion x/perpetual/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestGenesis(t *testing.T) {
// this line is used by starport scaffolding # genesis/test/state
}

k, ctx := keepertest.PerpetualKeeper(t)
k, ctx, _ := keepertest.PerpetualKeeper(t)
perpetual.InitGenesis(ctx, *k, genesisState)
got := perpetual.ExportGenesis(ctx, *k)
require.NotNil(t, got)
Expand Down
2 changes: 1 addition & 1 deletion x/perpetual/keeper/borrow_rate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func createNBorrowRate(keeper *keeper.Keeper, ctx sdk.Context, n int) ([]types.I
}

func TestBorrowRateGet(t *testing.T) {
keeper, ctx := keepertest.PerpetualKeeper(t)
keeper, ctx, _ := keepertest.PerpetualKeeper(t)
_, lastBlock := createNBorrowRate(keeper, ctx, 10)
ctx = ctx.WithBlockHeight(lastBlock)

Expand Down
75 changes: 75 additions & 0 deletions x/perpetual/keeper/calc_return_amount.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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) CalcReturnAmount(ctx sdk.Context, mtp types.MTP, pool types.Pool, ammPool ammtypes.Pool, repayAmount math.Int, amount math.Int, baseCurrency string) (returnAmount math.Int, err error) {
Liabilities := mtp.Liabilities
BorrowInterestUnpaid := mtp.BorrowInterestUnpaidCollateral

if mtp.BorrowInterestUnpaidCollateral.IsPositive() {
if mtp.Position == types.Position_SHORT {
// swap to trading asset
unpaidCollateralIn := sdk.NewCoin(mtp.CollateralAsset, mtp.BorrowInterestUnpaidCollateral)
C, err := k.CloseEstimationChecker.EstimateSwapGivenOut(ctx, unpaidCollateralIn, mtp.TradingAsset, ammPool)
if err != nil {
return sdk.ZeroInt(), err
}

BorrowInterestUnpaid = C
} else if mtp.CollateralAsset != baseCurrency {
// swap to base currency
unpaidCollateralIn := sdk.NewCoin(mtp.CollateralAsset, mtp.BorrowInterestUnpaidCollateral)
C, err := k.CloseEstimationChecker.EstimateSwapGivenOut(ctx, unpaidCollateralIn, baseCurrency, ammPool)
if err != nil {
return sdk.ZeroInt(), err
}

BorrowInterestUnpaid = C
}
}

// Reminder:
// if long both repay amount and liablities are collateral asset
// if short both repay amount and liablities are trading asset

have := repayAmount
owe := Liabilities.Add(BorrowInterestUnpaid).Mul(amount.Quo(mtp.Custody))

if have.LT(owe) {
// v principle liability; x excess liability
returnAmount = sdk.ZeroInt()
} else {
// can afford both
returnAmount = have.Sub(owe)
}

// returnAmount is so far in base currency if long or trading asset if short, now should convert it to collateralAsset in order to return
if returnAmount.IsPositive() {
if mtp.Position == types.Position_SHORT {
// swap to collateral asset
amtTokenIn := sdk.NewCoin(mtp.TradingAsset, returnAmount)
C, err := k.CloseEstimationChecker.EstimateSwapGivenOut(ctx, amtTokenIn, mtp.CollateralAsset, ammPool)
if err != nil {
return sdk.ZeroInt(), err
}

returnAmount = C
} else if mtp.CollateralAsset != baseCurrency {
// swap to collateral asset
amtTokenIn := sdk.NewCoin(baseCurrency, returnAmount)
C, err := k.CloseEstimationChecker.EstimateSwapGivenOut(ctx, amtTokenIn, mtp.CollateralAsset, ammPool)
if err != nil {
return sdk.ZeroInt(), err
}

returnAmount = C
}
}

return returnAmount, nil
}
18 changes: 17 additions & 1 deletion x/perpetual/keeper/estimate_and_repay.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,24 @@ func (k Keeper) EstimateAndRepay(ctx sdk.Context, mtp types.MTP, pool types.Pool
return sdk.ZeroInt(), types.ErrInvalidPosition
}

returnAmount, err := k.CalcReturnAmount(ctx, mtp, pool, ammPool, repayAmount, amount, baseCurrency)
if err != nil {
return sdk.ZeroInt(), err
}

// update mtp health
mtp.MtpHealth, err = k.GetMTPHealth(ctx, mtp, ammPool, baseCurrency)
if err != nil {
return sdk.ZeroInt(), err
}

// if return amount positive then update liabilities
if returnAmount.IsPositive() {
mtp.Liabilities = mtp.Liabilities.Sub(mtp.Liabilities.Mul(amount).Quo(mtp.Custody))
}

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

Expand Down
10 changes: 10 additions & 0 deletions x/perpetual/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type (
types.OpenShortChecker
types.CloseLongChecker
types.CloseShortChecker
types.CloseEstimationChecker

cdc codec.BinaryCodec
storeKey storetypes.StoreKey
Expand Down Expand Up @@ -76,6 +77,7 @@ func NewKeeper(
keeper.OpenShortChecker = keeper
keeper.CloseLongChecker = keeper
keeper.CloseShortChecker = keeper
keeper.CloseEstimationChecker = keeper

return keeper
}
Expand Down Expand Up @@ -457,6 +459,14 @@ func (k Keeper) TakeFundPayment(ctx sdk.Context, returnAmount math.Int, returnAs
return takeAmount, nil
}

// CalcTakeFundPayment calculates the take fund payment
func (k Keeper) CalcTakeFundPayment(ctx sdk.Context, returnAmount math.Int, returnAsset string, takePercentage sdk.Dec) math.Int {
returnAmountDec := sdk.NewDecFromBigInt(returnAmount.BigInt())
takeAmount := sdk.NewIntFromBigInt(takePercentage.Mul(returnAmountDec).TruncateInt().BigInt())

return takeAmount
}

// Set the perpetual hooks.
func (k *Keeper) SetHooks(gh types.PerpetualHooks) *Keeper {
if k.hooks != nil {
Expand Down
2 changes: 1 addition & 1 deletion x/perpetual/keeper/msg_server_update_take_profit_price.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/elys-network/elys/x/perpetual/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"
)

func (k msgServer) UpdateTakeProfitPrice(goCtx context.Context, msg *types.MsgUpdateTakeProfitPrice) (*types.MsgUpdateTakeProfitPriceResponse, error) {
Expand Down
2 changes: 1 addition & 1 deletion x/perpetual/keeper/msg_servers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ import (
)

func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) {
k, ctx := keepertest.PerpetualKeeper(t)
k, ctx, _ := keepertest.PerpetualKeeper(t)
return keeper.NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx)
}
2 changes: 1 addition & 1 deletion x/perpetual/keeper/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func TestGetParams(t *testing.T) {
k, ctx := testkeeper.PerpetualKeeper(t)
k, ctx, _ := testkeeper.PerpetualKeeper(t)
params := types.DefaultParams()

k.SetParams(ctx, &params)
Expand Down
Loading

0 comments on commit e018c0a

Please sign in to comment.