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

Add order limit for price level and pair limit for contracts #781

Merged
merged 3 commits into from
May 19, 2023
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
12 changes: 12 additions & 0 deletions aclmapping/dex/mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ func DexPlaceOrdersDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context,
ResourceType: sdkacltypes.ResourceType_KV_DEX_CONTRACT,
IdentifierTemplate: hex.EncodeToString([]byte(dexkeeper.ContractPrefixKey)),
},

{
AccessType: sdkacltypes.AccessType_READ,
ResourceType: sdkacltypes.ResourceType_KV_DEX_LONG_ORDER_COUNT,
IdentifierTemplate: hex.EncodeToString([]byte(dextypes.LongOrderCountKey)),
},

{
AccessType: sdkacltypes.AccessType_READ,
ResourceType: sdkacltypes.ResourceType_KV_DEX_SHORT_ORDER_COUNT,
IdentifierTemplate: hex.EncodeToString([]byte(dextypes.ShortOrderCountKey)),
},
}

// Last Operation should always be a commit
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ require (
github.com/firefart/nonamedreturns v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fzipp/gocyclo v0.5.1 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-critic/go-critic v0.6.3 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-kit/log v0.2.0 // indirect
Expand Down Expand Up @@ -140,6 +141,7 @@ require (
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3n
github.com/fzipp/gocyclo v0.5.1 h1:L66amyuYogbxl0j2U+vGqJXusPF2IkduvXLnYD5TFgw=
github.com/fzipp/gocyclo v0.5.1/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
Expand Down Expand Up @@ -596,6 +597,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
Expand Down
8 changes: 8 additions & 0 deletions proto/dex/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,12 @@ message Params {
(gogoproto.jsontag) = "contract_unsuspend_cost",
(gogoproto.moretags) = "yaml:\"contract_unsuspend_cost\""
];
uint64 max_order_per_price = 12 [
(gogoproto.jsontag) = "max_order_per_price",
(gogoproto.moretags) = "yaml:\"max_order_per_price\""
];
uint64 max_pairs_per_contract = 13 [
(gogoproto.jsontag) = "max_pairs_per_contract",
(gogoproto.moretags) = "yaml:\"max_pairs_per_contract\""
];
}
28 changes: 28 additions & 0 deletions proto/dex/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import "dex/asset_list.proto";
import "dex/pair.proto";
import "dex/order.proto";
import "dex/match_result.proto";
import "dex/enums.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 @@ -100,6 +101,8 @@ service Query {

rpc GetMatchResult(QueryGetMatchResultRequest) returns (QueryGetMatchResultResponse) {}

rpc GetOrderCount(QueryGetOrderCountRequest) returns (QueryGetOrderCountResponse) {}

// this line is used by starport scaffolding # 2
}

Expand Down Expand Up @@ -370,4 +373,29 @@ message QueryGetMatchResultResponse {
(gogoproto.jsontag) = "result"
];
}

message QueryGetOrderCountRequest {
string contractAddr = 1 [
(gogoproto.jsontag) = "contract_address"
];
string priceDenom = 2 [
(gogoproto.jsontag) = "price_denom"
];
string assetDenom = 3 [
(gogoproto.jsontag) = "asset_denom"
];
string price = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.jsontag) = "price"
];
PositionDirection positionDirection = 5 [
(gogoproto.jsontag) = "position_direction"
];
}

message QueryGetOrderCountResponse {
uint64 count = 1 [
(gogoproto.jsontag) = "count"
];
}
// this line is used by starport scaffolding # 3
1 change: 1 addition & 0 deletions x/dex/client/cli/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func GetQueryCmd() *cobra.Command {
cmd.AddCommand(CmdGetOrders())
cmd.AddCommand(CmdGetOrdersByID())
cmd.AddCommand(CmdGetMatchResult())
cmd.AddCommand(CmdGetOrderCount())

// this line is used by starport scaffolding # 1

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

import (
"context"
"fmt"

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

func CmdGetOrderCount() *cobra.Command {
cmd := &cobra.Command{
Use: "get-order-count [contract] [price denom] [asset denom] [LONG|SHORT] [price]",
Short: "get number of orders at a price leve",
Args: cobra.ExactArgs(5),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)

queryClient := types.NewQueryClient(clientCtx)

var direction types.PositionDirection
switch args[3] {
case "LONG":
direction = types.PositionDirection_LONG
case "SHORT":
direction = types.PositionDirection_SHORT
default:
return fmt.Errorf("unknown direction %s", args[3])
}
price := sdk.MustNewDecFromStr(args[4])
res, err := queryClient.GetOrderCount(context.Background(), &types.QueryGetOrderCountRequest{
ContractAddr: args[0],
PriceDenom: args[1],
AssetDenom: args[2],
PositionDirection: direction,
Price: &price,
})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
2 changes: 2 additions & 0 deletions x/dex/contract/whitelist.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ var DexWhitelistedKeys = []string{
types.SettlementEntryKey,
types.NextOrderIDKey,
types.MatchResultKey,
types.LongOrderCountKey,
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need OrderCount keys for both long and short if we only define one max_order_per_price in param?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

since long orders and short orders are stored separately it's easier to just store their counts separately as well. We can add params to specify max orders for long and for short separately but i don't know if they would ever be set differently

types.ShortOrderCountKey,
keeper.ContractPrefixKey,
}

Expand Down
9 changes: 9 additions & 0 deletions x/dex/exchange/cancel_order.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package exchange

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sei-protocol/sei-chain/x/dex/keeper"
"github.com/sei-protocol/sei-chain/x/dex/types"
Expand Down Expand Up @@ -33,6 +35,13 @@ func cancelOrder(ctx sdk.Context, keeper *keeper.Keeper, cancellation *types.Can
newQuantity = newQuantity.Add(allocation.Quantity)
}
}
numAllocationsRemoved := len(newEntry.Allocations) - len(newAllocations)
if numAllocationsRemoved > 0 {
err := keeper.DecreaseOrderCount(ctx, string(contract), pair.PriceDenom, pair.AssetDenom, cancellation.PositionDirection, entry.GetPrice(), uint64(numAllocationsRemoved))
if err != nil {
ctx.Logger().Error(fmt.Sprintf("error decreasing order count: %s", err))
}
}
if newQuantity.IsZero() {
deleter(ctx, string(contract), entry.GetPrice(), pair.PriceDenom, pair.AssetDenom)
return
Expand Down
6 changes: 6 additions & 0 deletions x/dex/exchange/limit_order.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package exchange

import (
"fmt"
"math"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -80,6 +81,11 @@ func addOrderToOrderBookEntry(
entry.SetPrice(order.Price)
entry.SetEntry(orderEntry)
setter(ctx, order.ContractAddr, entry)

err := keeper.IncreaseOrderCount(ctx, order.ContractAddr, order.PriceDenom, order.AssetDenom, order.PositionDirection, order.Price, 1)
if err != nil {
ctx.Logger().Error(fmt.Sprintf("error increasing order count: %s", err))
}
}

func AddOutstandingLimitOrdersToOrderbook(
Expand Down
4 changes: 4 additions & 0 deletions x/dex/keeper/msgserver/msg_server_place_orders.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ func (k msgServer) PlaceOrders(goCtx context.Context, msg *types.MsgPlaceOrders)
events := []sdk.Event{}
nextID := k.GetNextOrderID(ctx, msg.ContractAddr)
idsInResp := []uint64{}
maxOrderPerPrice := k.GetMaxOrderPerPrice(ctx)
for _, order := range msg.GetOrders() {
if k.GetOrderCountState(ctx, msg.GetContractAddr(), order.PriceDenom, order.AssetDenom, order.PositionDirection, order.Price) >= maxOrderPerPrice {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "order book already has more than %d orders for %s-%s-%s %s at %s", maxOrderPerPrice, msg.GetContractAddr(), order.PriceDenom, order.AssetDenom, order.PositionDirection, order.Price)
}
priceTicksize, found := k.Keeper.GetPriceTickSizeForPair(ctx, msg.GetContractAddr(), types.Pair{PriceDenom: order.PriceDenom, AssetDenom: order.AssetDenom})
if !found {
return nil, sdkerrors.Wrapf(sdkerrors.ErrKeyNotFound, "the pair {price:%s,asset:%s} has no price ticksize configured", order.PriceDenom, order.AssetDenom)
Expand Down
29 changes: 29 additions & 0 deletions x/dex/keeper/msgserver/msg_server_place_orders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,32 @@ func TestPlaceNoOrder(t *testing.T) {
_, err := server.PlaceOrders(wctx, msg)
require.NotNil(t, err)
}

func TestPlaceOrderExceedingLimit(t *testing.T) {
msg := &types.MsgPlaceOrders{
Creator: TestCreator,
ContractAddr: TestContract,
Orders: []*types.Order{
{
Price: sdk.MustNewDecFromStr("10"),
Quantity: sdk.MustNewDecFromStr("10"),
Data: "",
PositionDirection: types.PositionDirection_LONG,
OrderType: types.OrderType_LIMIT,
PriceDenom: keepertest.TestPriceDenom,
AssetDenom: keepertest.TestAssetDenom,
},
},
}
keeper, ctx := keepertest.DexKeeper(t)
params := keeper.GetParams(ctx)
params.MaxOrderPerPrice = 0
keeper.SetParams(ctx, params)
keeper.AddRegisteredPair(ctx, TestContract, keepertest.TestPair)
keeper.SetPriceTickSizeForPair(ctx, TestContract, keepertest.TestPair, *keepertest.TestPair.PriceTicksize)
keeper.SetQuantityTickSizeForPair(ctx, TestContract, keepertest.TestPair, *keepertest.TestPair.QuantityTicksize)
wctx := sdk.WrapSDKContext(ctx)
server := msgserver.NewMsgServerImpl(*keeper)
_, err := server.PlaceOrders(wctx, msg)
require.NotNil(t, err)
}
5 changes: 5 additions & 0 deletions x/dex/keeper/msgserver/msg_server_register_pairs.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ func (k msgServer) RegisterPairs(goCtx context.Context, msg *types.MsgRegisterPa

ctx := sdk.UnwrapSDKContext(goCtx)

maxPairsPerContract := k.GetMaxPairsPerContract(ctx)
events := []sdk.Event{}
// Validation such that only the user who stored the code can register pairs
for _, batchPair := range msg.Batchcontractpair {
contractAddr := batchPair.ContractAddr
existingPairCount := uint64(len(k.GetAllRegisteredPairs(ctx, contractAddr)))
if existingPairCount >= maxPairsPerContract {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "contract %s already has %d pairs registered", contractAddr, existingPairCount)
}
contractInfo, err := k.GetContract(ctx, contractAddr)
if err != nil {
return nil, err
Expand Down
49 changes: 49 additions & 0 deletions x/dex/keeper/msgserver/msg_server_register_pairs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,52 @@ func TestInvalidRegisterPairCreator(t *testing.T) {
})
require.NoError(t, err)
}

func TestRegisterPairsExceedingLimit(t *testing.T) {
testApp := keepertest.TestApp()
ctx := testApp.BaseApp.NewContext(false, tmproto.Header{Time: time.Now()})
ctx = ctx.WithContext(context.WithValue(ctx.Context(), dexutils.DexMemStateContextKey, dexcache.NewMemState(testApp.GetMemKey(types.MemStoreKey))))
wctx := sdk.WrapSDKContext(ctx)
keeper := testApp.DexKeeper

testAccount, _ := sdk.AccAddressFromBech32("sei1yezq49upxhunjjhudql2fnj5dgvcwjj87pn2wx")
amounts := sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(100000000)), sdk.NewCoin("uusdc", sdk.NewInt(100000000)))
bankkeeper := testApp.BankKeeper
bankkeeper.MintCoins(ctx, minttypes.ModuleName, amounts)
bankkeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, testAccount, amounts)
wasm, err := ioutil.ReadFile("../../testdata/mars.wasm")
if err != nil {
panic(err)
}
wasmKeeper := testApp.WasmKeeper
contractKeeper := wasmkeeper.NewDefaultPermissionKeeper(&wasmKeeper)
var perm *wasmtypes.AccessConfig
codeId, err := contractKeeper.Create(ctx, testAccount, wasm, perm)
if err != nil {
panic(err)
}
contractAddrA, _, err := contractKeeper.Instantiate(ctx, codeId, testAccount, testAccount, []byte(GOOD_CONTRACT_INSTANTIATE), "test",
sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(100000))))
if err != nil {
panic(err)
}

params := keeper.GetParams(ctx)
params.MaxPairsPerContract = 0
keeper.SetParams(ctx, params)
server := msgserver.NewMsgServerImpl(keeper)
err = RegisterContractUtil(server, wctx, contractAddrA.String(), nil)
require.NoError(t, err)

batchContractPairs := []types.BatchContractPair{}
batchContractPairs = append(batchContractPairs, types.BatchContractPair{
ContractAddr: contractAddrA.String(),
Pairs: []*types.Pair{&keepertest.TestPair},
})
_, err = server.RegisterPairs(wctx, &types.MsgRegisterPairs{
Creator: keepertest.TestAccount,
Batchcontractpair: batchContractPairs,
})

require.NotNil(t, err)
}
56 changes: 56 additions & 0 deletions x/dex/keeper/order_count.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package keeper

import (
"encoding/binary"
"fmt"

"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sei-protocol/sei-chain/x/dex/types"
)

func (k Keeper) SetOrderCount(ctx sdk.Context, contractAddr string, priceDenom string, assetDenom string, direction types.PositionDirection, price sdk.Dec, count uint64) error {
store := prefix.NewStore(
ctx.KVStore(k.storeKey),
types.OrderCountPrefix(contractAddr, priceDenom, assetDenom, direction == types.PositionDirection_LONG),
)
key, err := price.Marshal()
if err != nil {
return err
}
value := make([]byte, 8)
binary.BigEndian.PutUint64(value, count)
store.Set(key, value)
return nil
}

func (k Keeper) GetOrderCountState(ctx sdk.Context, contractAddr string, priceDenom string, assetDenom string, direction types.PositionDirection, price sdk.Dec) uint64 {
store := prefix.NewStore(
ctx.KVStore(k.storeKey),
types.OrderCountPrefix(contractAddr, priceDenom, assetDenom, direction == types.PositionDirection_LONG),
)
key, err := price.Marshal()
if err != nil {
ctx.Logger().Error(fmt.Sprintf("error marshal provided price %s due to %s", price.String(), err))
return 0
}
value := store.Get(key)
if value == nil {
return 0
}
return binary.BigEndian.Uint64(value)
}

func (k Keeper) DecreaseOrderCount(ctx sdk.Context, contractAddr string, priceDenom string, assetDenom string, direction types.PositionDirection, price sdk.Dec, count uint64) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: why don't we use a generic UpdateOrderCount here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

want to keep integer type consistent instead of having to use a signed int64

oldCount := k.GetOrderCountState(ctx, contractAddr, priceDenom, assetDenom, direction, price)
newCount := uint64(0)
if oldCount > count {
newCount = oldCount - count
}
return k.SetOrderCount(ctx, contractAddr, priceDenom, assetDenom, direction, price, newCount)
}

func (k Keeper) IncreaseOrderCount(ctx sdk.Context, contractAddr string, priceDenom string, assetDenom string, direction types.PositionDirection, price sdk.Dec, count uint64) error {
oldCount := k.GetOrderCountState(ctx, contractAddr, priceDenom, assetDenom, direction, price)
return k.SetOrderCount(ctx, contractAddr, priceDenom, assetDenom, direction, price, oldCount+count)
}
Loading