Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion proto/dex/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ option go_package = "github.com/sei-protocol/sei-chain/x/dex/types";

// Params defines the parameters for the module.
message Params {
option (gogoproto.equal) = true;
option (gogoproto.goproto_stringer) = false;


uint64 price_snapshot_retention = 1 [(gogoproto.moretags) = "yaml:\"price_snapshot_retention\""];
}
18 changes: 18 additions & 0 deletions proto/dex/price.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
syntax = "proto3";
package seiprotocol.seichain.dex;

import "gogoproto/gogo.proto";
import "dex/pair.proto";

option go_package = "github.com/sei-protocol/sei-chain/x/dex/types";

message Price {

uint64 snapshotTimestampInSeconds = 1;
string price = 2 [
(gogoproto.moretags) = "yaml:\"price\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
Pair pair = 3;
}
31 changes: 22 additions & 9 deletions proto/dex/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import "dex/params.proto";
import "dex/long_book.proto";
import "dex/short_book.proto";
import "dex/settlement.proto";
import "dex/twap.proto";
import "dex/enums.proto";
import "dex/price.proto";
import "dex/twap.proto";
// this line is used by starport scaffolding # 1

option go_package = "github.com/sei-protocol/sei-chain/x/dex/types";
Expand Down Expand Up @@ -44,9 +45,12 @@ service Query {
option (google.api.http).get = "/sei-protocol/seichain/dex/settlement";
}

// Queries a list of GetTwap items.
rpc GetTwap(QueryGetTwapRequest) returns (QueryGetTwapResponse) {
option (google.api.http).get = "/sei-protocol/seichain/dex/get_twap/{priceDenom}/{assetDenom}";
rpc GetPrices(QueryGetPricesRequest) returns (QueryGetPricesResponse) {
option (google.api.http).get = "/sei-protocol/seichain/dex/get_prices/{contractAddr}/{priceDenom}/{assetDenom}";
}

rpc GetTwaps(QueryGetTwapsRequest) returns (QueryGetTwapsResponse) {
option (google.api.http).get = "/sei-protocol/seichain/dex/get_twaps/{contractAddr}/{lookbackSeconds}";
}

// this line is used by starport scaffolding # 2
Expand Down Expand Up @@ -127,14 +131,23 @@ message QueryAllSettlementsResponse {
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

message QueryGetTwapRequest {
Denom priceDenom = 1;
Denom assetDenom = 2;
message QueryGetPricesRequest {
Denom priceDenom = 1;
Denom assetDenom = 2;
string contractAddr = 3;
}

message QueryGetTwapResponse {
Twap twaps = 1;
message QueryGetPricesResponse {
repeated Price prices = 1;
}

message QueryGetTwapsRequest {
string contractAddr = 1;
uint64 lookbackSeconds = 2;
}

message QueryGetTwapsResponse {
repeated Twap twaps = 1;
}

// this line is used by starport scaffolding # 3
18 changes: 12 additions & 6 deletions proto/dex/twap.proto
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
syntax = "proto3";
package seiprotocol.seichain.dex;

import "gogoproto/gogo.proto";
import "dex/pair.proto";

option go_package = "github.com/sei-protocol/sei-chain/x/dex/types";


message Twap {

uint64 lastEpoch = 1;
repeated uint64 prices = 2;
uint64 twapPrice = 3;
string priceDenom = 4;
string assetDenom = 5;

Pair pair = 1;
string twap = 2 [
(gogoproto.moretags) = "yaml:\"twap\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
uint64 lookbackSeconds = 3;
}
3 changes: 2 additions & 1 deletion x/dex/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ func GetQueryCmd(queryRoute string) *cobra.Command {
cmd.AddCommand(CmdShowLongBook())
cmd.AddCommand(CmdListShortBook())
cmd.AddCommand(CmdShowShortBook())
cmd.AddCommand(CmdGetTwap())
cmd.AddCommand(CmdGetPrice())
cmd.AddCommand(CmdGetTwaps())

// this line is used by starport scaffolding # 1

Expand Down
55 changes: 55 additions & 0 deletions x/dex/client/cli/query_get_prices.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cli

import (
"strconv"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/sei-protocol/sei-chain/x/dex/types"
"github.com/spf13/cobra"
)

var _ = strconv.Itoa(0)

func CmdGetPrice() *cobra.Command {
cmd := &cobra.Command{
Use: "get-prices [contract-address] [price-denom] [asset-denom]",
Short: "Query getPrices",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) (err error) {
reqContractAddr := args[0]
reqPriceDenom, _, err := types.GetDenomFromStr(args[1])
if err != nil {
return err
}
reqAssetDenom, _, err := types.GetDenomFromStr(args[2])
if err != nil {
return err
}

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

queryClient := types.NewQueryClient(clientCtx)

params := &types.QueryGetPricesRequest{
ContractAddr: reqContractAddr,
PriceDenom: reqPriceDenom,
AssetDenom: reqAssetDenom,
}

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

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
61 changes: 0 additions & 61 deletions x/dex/client/cli/query_get_twap.go

This file was deleted.

49 changes: 49 additions & 0 deletions x/dex/client/cli/query_get_twaps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cli

import (
"strconv"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/sei-protocol/sei-chain/x/dex/types"
"github.com/spf13/cobra"
)

var _ = strconv.Itoa(0)

func CmdGetTwaps() *cobra.Command {
cmd := &cobra.Command{
Use: "get-twaps [contract-address] [lookback]",
Short: "Query getPrice",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) (err error) {
reqContractAddr := args[0]
reqLookback, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
return err
}

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

params := &types.QueryGetTwapsRequest{
ContractAddr: reqContractAddr,
LookbackSeconds: reqLookback,
}

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

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
6 changes: 0 additions & 6 deletions x/dex/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState)
// this line is used by starport scaffolding # genesis/module/init
k.SetParams(ctx, genState.Params)

for _, twap := range genState.TwapList {
twap.LastEpoch = 0
twap.TwapPrice = twap.Prices[0]
k.SetTwap(ctx, *twap, "genesis")
}

k.SetEpoch(ctx, genState.LastEpoch)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import (
"google.golang.org/grpc/status"
)

func (k Keeper) GetTwap(goCtx context.Context, req *types.QueryGetTwapRequest) (*types.QueryGetTwapResponse, error) {
func (k Keeper) GetPrices(goCtx context.Context, req *types.QueryGetPricesRequest) (*types.QueryGetPricesResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

ctx := sdk.UnwrapSDKContext(goCtx)

twap := k.GetTwapState(ctx, req.ContractAddr, req.PriceDenom, req.AssetDenom)
prices := k.GetAllPrices(ctx, req.ContractAddr, types.Pair{PriceDenom: req.PriceDenom, AssetDenom: req.AssetDenom})

return &types.QueryGetTwapResponse{
Twaps: &twap,
return &types.QueryGetPricesResponse{
Prices: prices,
}, nil
}
61 changes: 61 additions & 0 deletions x/dex/keeper/grpc_query_get_twaps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package keeper

import (
"context"
"sort"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sei-protocol/sei-chain/x/dex/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

func (k Keeper) GetTwaps(goCtx context.Context, req *types.QueryGetTwapsRequest) (*types.QueryGetTwapsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

ctx := sdk.UnwrapSDKContext(goCtx)
allRegisteredPairs := k.GetAllRegisteredPairs(ctx, req.ContractAddr)
twaps := []*types.Twap{}
for _, pair := range allRegisteredPairs {
prices := k.GetAllPrices(ctx, req.ContractAddr, pair)
twaps = append(twaps, &types.Twap{
Pair: &pair,
Twap: calculateTwap(ctx, prices, req.LookbackSeconds),
LookbackSeconds: req.LookbackSeconds,
})
}

return &types.QueryGetTwapsResponse{
Twaps: twaps,
}, nil
}

func calculateTwap(ctx sdk.Context, prices []*types.Price, lookback uint64) sdk.Dec {
// sort prices in descending order to start iteration from the latest
sort.Slice(prices, func(p1, p2 int) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the ordering won't change, we could consider using a priority queue to reduce number of sorts . If number of prices is pretty small then probably doesn't matter

return prices[p1].SnapshotTimestampInSeconds > prices[p2].SnapshotTimestampInSeconds
})
var timeTraversed uint64 = 0
var weightedPriceSum sdk.Dec = sdk.ZeroDec()
for _, price := range prices {
newTimeTraversed := uint64(ctx.BlockTime().Unix()) - price.SnapshotTimestampInSeconds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just double check ctx.BlockTime() is static (i.e. block start time) right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it's set during BeginBlock based on block proposal (so consistent across all nodes)

if newTimeTraversed > lookback {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in this case, wouldn't we exclude any data from the first time interval?

Lets say for example we had data at T-61 and then T-59 ... T-0, when calculating the TWAP, it would technically only represent the weighted price sum for T-59 - T-0, right? because we would break on T-61 and never include data for the first interval?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's true. We should align on the behavior in such case between dex twap and oracle twap. Still factoring in T-61 but only assign a time weight of 1 sounds reasonable to me, if that's also how oracle twap is calculated

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that makes sense to me, currently my implementation also has the missing first interval issue, but I can modify it to keep the first out of bounds data point for calculating to the start of the TWAP window

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will approve once this change is in

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pushed the fix as well as a unit test for this case

weightedPriceSum = weightedPriceSum.Add(
price.Price.MulInt64(int64(lookback - timeTraversed)),
)
timeTraversed = lookback
break
}
weightedPriceSum = weightedPriceSum.Add(
price.Price.MulInt64(int64(newTimeTraversed - timeTraversed)),
)
timeTraversed = newTimeTraversed
}
if timeTraversed == 0 {
return sdk.ZeroDec()
} else {
return weightedPriceSum.QuoInt64(int64(timeTraversed))
}
}
Loading