diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/order.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/order.ts index 3c714e5979..aeaaa06aa6 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/order.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/order.ts @@ -101,9 +101,8 @@ export enum Order_TimeInForce { TIME_IN_FORCE_POST_ONLY = 2, /** - * TIME_IN_FORCE_FILL_OR_KILL - TIME_IN_FORCE_FILL_OR_KILL enforces that an order will either be filled - * completely and immediately by maker orders on the book or canceled if the - * entire amount can‘t be matched. + * TIME_IN_FORCE_FILL_OR_KILL - TIME_IN_FORCE_FILL_OR_KILL has been deprecated and will be removed in + * future versions. */ TIME_IN_FORCE_FILL_OR_KILL = 3, UNRECOGNIZED = -1, @@ -138,9 +137,8 @@ export enum Order_TimeInForceSDKType { TIME_IN_FORCE_POST_ONLY = 2, /** - * TIME_IN_FORCE_FILL_OR_KILL - TIME_IN_FORCE_FILL_OR_KILL enforces that an order will either be filled - * completely and immediately by maker orders on the book or canceled if the - * entire amount can‘t be matched. + * TIME_IN_FORCE_FILL_OR_KILL - TIME_IN_FORCE_FILL_OR_KILL has been deprecated and will be removed in + * future versions. */ TIME_IN_FORCE_FILL_OR_KILL = 3, UNRECOGNIZED = -1, diff --git a/proto/dydxprotocol/clob/order.proto b/proto/dydxprotocol/clob/order.proto index 6cd482bd2b..ac80fec3c1 100644 --- a/proto/dydxprotocol/clob/order.proto +++ b/proto/dydxprotocol/clob/order.proto @@ -171,10 +171,9 @@ message Order { // any newly-placed post only orders that would cross with other maker // orders. TIME_IN_FORCE_POST_ONLY = 2; - // TIME_IN_FORCE_FILL_OR_KILL enforces that an order will either be filled - // completely and immediately by maker orders on the book or canceled if the - // entire amount can‘t be matched. - TIME_IN_FORCE_FILL_OR_KILL = 3; + // TIME_IN_FORCE_FILL_OR_KILL has been deprecated and will be removed in + // future versions. + TIME_IN_FORCE_FILL_OR_KILL = 3 [ deprecated = true ]; } // The time in force of this order. diff --git a/protocol/app/upgrades/v6.0.0/upgrade.go b/protocol/app/upgrades/v6.0.0/upgrade.go index ff8448f473..49b1937e03 100644 --- a/protocol/app/upgrades/v6.0.0/upgrade.go +++ b/protocol/app/upgrades/v6.0.0/upgrade.go @@ -2,19 +2,57 @@ package v_6_0_0 import ( "context" - - clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + "fmt" upgradetypes "cosmossdk.io/x/upgrade/types" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events" + "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" + indexershared "github.com/dydxprotocol/v4-chain/protocol/indexer/shared" + "github.com/dydxprotocol/v4-chain/protocol/lib" + clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" ) +func removeStatefulFOKOrders(ctx sdk.Context, k clobtypes.ClobKeeper) { + allStatefulOrders := k.GetAllStatefulOrders(ctx) + for _, order := range allStatefulOrders { + if order.TimeInForce == clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL { + // Remove the orders from state. + k.MustRemoveStatefulOrder(ctx, order.OrderId) + + // Send indexer event for removal of stateful order. + k.GetIndexerEventManager().AddTxnEvent( + ctx, + indexerevents.SubtypeStatefulOrder, + indexerevents.StatefulOrderEventVersion, + indexer_manager.GetBytes( + indexerevents.NewStatefulOrderRemovalEvent( + order.OrderId, + indexershared.ConvertOrderRemovalReasonToIndexerOrderRemovalReason( + clobtypes.OrderRemoval_REMOVAL_REASON_CONDITIONAL_FOK_COULD_NOT_BE_FULLY_FILLED, + ), + ), + ), + ) + } + } +} + func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, clobKeeper clobtypes.ClobKeeper, ) upgradetypes.UpgradeHandler { return func(ctx context.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + sdkCtx := lib.UnwrapSDKContext(ctx, "app/upgrades") + sdkCtx.Logger().Info(fmt.Sprintf("Running %s Upgrade...", UpgradeName)) + + // Remove all stateful FOK orders from state. + removeStatefulFOKOrders(sdkCtx, clobKeeper) + + sdkCtx.Logger().Info("Successfully removed stateful orders from state") + return mm.RunMigrations(ctx, configurator, vm) } } diff --git a/protocol/app/upgrades/v6.0.0/upgrade_container_test.go b/protocol/app/upgrades/v6.0.0/upgrade_container_test.go index 49caef9c01..a3ebac8126 100644 --- a/protocol/app/upgrades/v6.0.0/upgrade_container_test.go +++ b/protocol/app/upgrades/v6.0.0/upgrade_container_test.go @@ -4,6 +4,7 @@ package v_6_0_0_test import ( "testing" + "time" v_6_0_0 "github.com/dydxprotocol/v4-chain/protocol/app/upgrades/v6.0.0" "github.com/dydxprotocol/v4-chain/protocol/testing/containertest" @@ -11,12 +12,12 @@ import ( clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) const ( AliceBobBTCQuantums = 1_000_000 - CarlDaveBTCQuantums = 2_000_000 - CarlDaveETHQuantums = 4_000_000 ) func TestStateUpgrade(t *testing.T) { @@ -43,145 +44,151 @@ func preUpgradeSetups(node *containertest.Node, t *testing.T) { func preUpgradeChecks(node *containertest.Node, t *testing.T) { // Add test for your upgrade handler logic below + preUpgradeStatefulOrderCheck(node, t) } func postUpgradeChecks(node *containertest.Node, t *testing.T) { // Add test for your upgrade handler logic below + postUpgradeStatefulOrderCheck(node, t) } func placeOrders(node *containertest.Node, t *testing.T) { - require.NoError(t, containertest.BroadcastTx( + // FOK order setups. + require.NoError(t, containertest.BroadcastTxWithoutValidateBasic( node, &clobtypes.MsgPlaceOrder{ Order: clobtypes.Order{ OrderId: clobtypes.OrderId{ - ClientId: 0, + ClientId: 100, SubaccountId: satypes.SubaccountId{ Owner: constants.AliceAccAddress.String(), Number: 0, }, ClobPairId: 0, + OrderFlags: clobtypes.OrderIdFlags_Conditional, }, - Side: clobtypes.Order_SIDE_BUY, - Quantums: AliceBobBTCQuantums, - Subticks: 5_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{ - GoodTilBlock: 20, + Side: clobtypes.Order_SIDE_BUY, + Quantums: AliceBobBTCQuantums, + Subticks: 6_000_000, + TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, + ConditionType: clobtypes.Order_CONDITION_TYPE_TAKE_PROFIT, + ConditionalOrderTriggerSubticks: 6_000_000, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{ + GoodTilBlockTime: uint32(time.Now().Unix() + 300), }, }, }, constants.AliceAccAddress.String(), )) - require.NoError(t, containertest.BroadcastTx( + + err := node.Wait(1) + require.NoError(t, err) + + require.NoError(t, containertest.BroadcastTxWithoutValidateBasic( node, &clobtypes.MsgPlaceOrder{ Order: clobtypes.Order{ OrderId: clobtypes.OrderId{ - ClientId: 0, + ClientId: 101, SubaccountId: satypes.SubaccountId{ - Owner: constants.BobAccAddress.String(), + Owner: constants.AliceAccAddress.String(), Number: 0, }, ClobPairId: 0, + OrderFlags: clobtypes.OrderIdFlags_Conditional, }, - Side: clobtypes.Order_SIDE_SELL, - Quantums: AliceBobBTCQuantums, - Subticks: 5_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{ - GoodTilBlock: 20, + Side: clobtypes.Order_SIDE_SELL, + Quantums: AliceBobBTCQuantums, + Subticks: 6_000_000, + TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, + ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, + ConditionalOrderTriggerSubticks: 6_000_000, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{ + GoodTilBlockTime: uint32(time.Now().Unix() + 300), }, }, }, - constants.BobAccAddress.String(), + constants.AliceAccAddress.String(), )) - require.NoError(t, containertest.BroadcastTx( + + err = node.Wait(2) + require.NoError(t, err) +} + +func preUpgradeStatefulOrderCheck(node *containertest.Node, t *testing.T) { + // Check that all stateful orders are present. + _, err := containertest.Query( node, - &clobtypes.MsgPlaceOrder{ - Order: clobtypes.Order{ - OrderId: clobtypes.OrderId{ - ClientId: 0, - SubaccountId: satypes.SubaccountId{ - Owner: constants.CarlAccAddress.String(), - Number: 0, - }, - ClobPairId: 0, - }, - Side: clobtypes.Order_SIDE_BUY, - Quantums: CarlDaveBTCQuantums, - Subticks: 5_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{ - GoodTilBlock: 20, + clobtypes.NewQueryClient, + clobtypes.QueryClient.StatefulOrder, + &clobtypes.QueryStatefulOrderRequest{ + OrderId: clobtypes.OrderId{ + ClientId: 100, + SubaccountId: satypes.SubaccountId{ + Owner: constants.AliceAccAddress.String(), + Number: 0, }, + ClobPairId: 0, + OrderFlags: clobtypes.OrderIdFlags_Conditional, }, }, - constants.CarlAccAddress.String(), - )) - require.NoError(t, containertest.BroadcastTx( + ) + require.NoError(t, err) + + _, err = containertest.Query( node, - &clobtypes.MsgPlaceOrder{ - Order: clobtypes.Order{ - OrderId: clobtypes.OrderId{ - ClientId: 0, - SubaccountId: satypes.SubaccountId{ - Owner: constants.DaveAccAddress.String(), - Number: 0, - }, - ClobPairId: 0, - }, - Side: clobtypes.Order_SIDE_SELL, - Quantums: CarlDaveBTCQuantums, - Subticks: 5_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{ - GoodTilBlock: 20, + clobtypes.NewQueryClient, + clobtypes.QueryClient.StatefulOrder, + &clobtypes.QueryStatefulOrderRequest{ + OrderId: clobtypes.OrderId{ + ClientId: 101, + SubaccountId: satypes.SubaccountId{ + Owner: constants.AliceAccAddress.String(), + Number: 0, }, + ClobPairId: 0, + OrderFlags: clobtypes.OrderIdFlags_Conditional, }, }, - constants.DaveAccAddress.String(), - )) - require.NoError(t, containertest.BroadcastTx( + ) + require.NoError(t, err) +} + +func postUpgradeStatefulOrderCheck(node *containertest.Node, t *testing.T) { + // Check that all stateful orders are removed. + _, err := containertest.Query( node, - &clobtypes.MsgPlaceOrder{ - Order: clobtypes.Order{ - OrderId: clobtypes.OrderId{ - ClientId: 0, - SubaccountId: satypes.SubaccountId{ - Owner: constants.CarlAccAddress.String(), - Number: 0, - }, - ClobPairId: 1, - }, - Side: clobtypes.Order_SIDE_BUY, - Quantums: CarlDaveETHQuantums, - Subticks: 5_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{ - GoodTilBlock: 20, + clobtypes.NewQueryClient, + clobtypes.QueryClient.StatefulOrder, + &clobtypes.QueryStatefulOrderRequest{ + OrderId: clobtypes.OrderId{ + ClientId: 100, + SubaccountId: satypes.SubaccountId{ + Owner: constants.AliceAccAddress.String(), + Number: 0, }, + ClobPairId: 0, + OrderFlags: clobtypes.OrderIdFlags_Conditional, }, }, - constants.CarlAccAddress.String(), - )) - require.NoError(t, containertest.BroadcastTx( + ) + require.ErrorIs(t, err, status.Error(codes.NotFound, "not found")) + + _, err = containertest.Query( node, - &clobtypes.MsgPlaceOrder{ - Order: clobtypes.Order{ - OrderId: clobtypes.OrderId{ - ClientId: 0, - SubaccountId: satypes.SubaccountId{ - Owner: constants.DaveAccAddress.String(), - Number: 0, - }, - ClobPairId: 1, - }, - Side: clobtypes.Order_SIDE_SELL, - Quantums: CarlDaveETHQuantums, - Subticks: 5_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{ - GoodTilBlock: 20, + clobtypes.NewQueryClient, + clobtypes.QueryClient.StatefulOrder, + &clobtypes.QueryStatefulOrderRequest{ + OrderId: clobtypes.OrderId{ + ClientId: 101, + SubaccountId: satypes.SubaccountId{ + Owner: constants.AliceAccAddress.String(), + Number: 0, }, + ClobPairId: 0, + OrderFlags: clobtypes.OrderIdFlags_Conditional, }, }, - constants.DaveAccAddress.String(), - )) - err := node.Wait(2) - require.NoError(t, err) + ) + require.ErrorIs(t, err, status.Error(codes.NotFound, "not found")) } diff --git a/protocol/daemons/pricefeed/client/price_function/util.go b/protocol/daemons/pricefeed/client/price_function/util.go index 1e533f88ce..218a3a301b 100644 --- a/protocol/daemons/pricefeed/client/price_function/util.go +++ b/protocol/daemons/pricefeed/client/price_function/util.go @@ -144,26 +144,21 @@ func reverseShiftBigFloatSlice( values []*big.Float, exponent int32, ) []*big.Float { - unsignedExponent := lib.AbsInt32(exponent) - - pow10 := new(big.Float).SetInt(lib.BigPow10(uint64(unsignedExponent))) + p10, inverse := lib.BigPow10(exponent) + p10Float := new(big.Float).SetInt(p10) updatedValues := make([]*big.Float, 0, len(values)) for _, value := range values { - updatedValues = append(updatedValues, reverseShiftFloatWithPow10(value, pow10, exponent)) + newValue := new(big.Float).Set(value) + if inverse { + newValue.Mul(newValue, p10Float) + } else { + newValue.Quo(newValue, p10Float) + } + updatedValues = append(updatedValues, newValue) } return updatedValues } -func reverseShiftFloatWithPow10(value *big.Float, pow10 *big.Float, exponent int32) *big.Float { - if exponent == 0 { - return value - } else if exponent > 0 { - return new(big.Float).Quo(value, pow10) - } else { // exponent < 0 - return new(big.Float).Mul(value, pow10) - } -} - // Ticker encodes a ticker response returned by an exchange API. It contains accessors for the ticker's // ask price, bid price, and last price, which are medianized to compute an exchange price. type Ticker interface { diff --git a/protocol/daemons/pricefeed/client/price_function/util_test.go b/protocol/daemons/pricefeed/client/price_function/util_test.go index 9034241f37..fa50f0d8ce 100644 --- a/protocol/daemons/pricefeed/client/price_function/util_test.go +++ b/protocol/daemons/pricefeed/client/price_function/util_test.go @@ -261,54 +261,6 @@ func TestGetUint64MedianFromShiftedBigFloatValues(t *testing.T) { } } -func TestReverseShiftBigFloatWithPow10(t *testing.T) { - tests := map[string]struct { - // parameters - floatValue *big.Float - exponent int32 - - // expectations - expectedUpdatedFloatValue *big.Float - }{ - "Success with negative exponent": { - floatValue: new(big.Float).SetPrec(64).SetFloat64(100.123), - exponent: -3, - expectedUpdatedFloatValue: new(big.Float).SetPrec(64).SetFloat64(100_123), - }, - "Success with positive exponent": { - floatValue: new(big.Float).SetPrec(64).SetFloat64(100.1), - exponent: 1, - expectedUpdatedFloatValue: new(big.Float).SetPrec(64).SetFloat64(10.01), - }, - "Success with exponent of 0": { - floatValue: new(big.Float).SetPrec(64).SetFloat64(100), - exponent: 0, - expectedUpdatedFloatValue: new(big.Float).SetPrec(64).SetFloat64(100), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - unsignedExponent := lib.AbsInt32(tc.exponent) - - pow10 := new(big.Float).SetInt(lib.BigPow10(uint64(unsignedExponent))) - - updatedFloatValue := reverseShiftFloatWithPow10( - tc.floatValue, - pow10, - tc.exponent, - ) - - require.InDeltaSlice( - t, - bigSliceToFloatSlice([]*big.Float{tc.expectedUpdatedFloatValue}), - bigSliceToFloatSlice([]*big.Float{updatedFloatValue}), - deltaPrecision, - ) - }) - } -} - func TestReverseShiftBigFloatSlice(t *testing.T) { tests := map[string]struct { // parameters diff --git a/protocol/lib/big_math.go b/protocol/lib/big_math.go index 1d41aae736..705623172c 100644 --- a/protocol/lib/big_math.go +++ b/protocol/lib/big_math.go @@ -27,43 +27,44 @@ func BigMulPpm(val *big.Int, ppm *big.Int, roundUp bool) *big.Int { } } -// BigMulPow10 returns the result of `val * 10^exponent`, in *big.Rat. -func BigMulPow10( - val *big.Int, - exponent int32, -) ( - result *big.Rat, -) { - ratPow10 := RatPow10(exponent) - return ratPow10.Mul( - new(big.Rat).SetInt(val), - ratPow10, - ) -} - // bigPow10Memo is a cache of the most common exponent value requests. Since bigPow10Memo will be // accessed from different go-routines, the map should only ever be read from or collision // could occur. var bigPow10Memo = warmCache() -// BigPow10 returns the result of `10^exponent`. Caches all calculated values and -// re-uses cached values in any following calls to BigPow10. -func BigPow10(exponent uint64) *big.Int { - result := bigPow10Helper(exponent) - // Copy the result, such that no values can be modified by reference in the - // `bigPow10Memo` cache. - copy := new(big.Int).Set(result) - return copy +// BigPow10 returns the result of `10^abs(exponent)` and whether the exponent is non-negative. +func BigPow10[T int | int32 | int64 | uint | uint32 | uint64]( + exponent T, +) ( + result *big.Int, + inverse bool, +) { + inverse = exponent < 0 + var absExponent uint64 + if inverse { + absExponent = uint64(-exponent) + } else { + absExponent = uint64(exponent) + } + + return new(big.Int).Set(bigPow10Helper(absExponent)), inverse } -// RatPow10 returns the result of `10^exponent`. Re-uses the cached values by -// calling bigPow10Helper. -func RatPow10(exponent int32) *big.Rat { - result := new(big.Rat).SetInt(bigPow10Helper(uint64(AbsInt32(exponent)))) - if exponent < 0 { - result.Inv(result) +// BigIntMulPow10 returns the result of `input * 10^exponent`, rounding in the direction indicated. +// There is no rounding if `exponent` is non-negative. +func BigIntMulPow10[T int | int32 | int64 | uint | uint32 | uint64]( + input *big.Int, + exponent T, + roundUp bool, +) *big.Int { + p10, inverse := BigPow10(exponent) + if inverse { + if roundUp { + return BigDivCeil(input, p10) + } + return new(big.Int).Div(input, p10) } - return result + return new(big.Int).Mul(p10, input) } // BigIntMulPpm takes a `big.Int` and returns the result of `input * ppm / 1_000_000`. This method rounds towards diff --git a/protocol/lib/big_math_test.go b/protocol/lib/big_math_test.go index 89877b587f..45c3896c8f 100644 --- a/protocol/lib/big_math_test.go +++ b/protocol/lib/big_math_test.go @@ -146,146 +146,29 @@ func TestBigMulPpm(t *testing.T) { func TestBigPow10(t *testing.T) { tests := map[string]struct { - exponent uint64 - expectedResult *big.Int - }{ - "Regular exponent": { - exponent: 3, - expectedResult: new(big.Int).SetUint64(1000), - }, - "Zero exponent": { - exponent: 0, - expectedResult: new(big.Int).SetUint64(1), - }, - "One exponent": { - exponent: 1, - expectedResult: new(big.Int).SetUint64(10), - }, - "Power of 2": { - exponent: 8, - expectedResult: new(big.Int).SetUint64(100_000_000), - }, - "Non-power of 2": { - exponent: 6, - expectedResult: new(big.Int).SetUint64(1_000_000), - }, - "Greater than max uint64": { - exponent: 20, - expectedResult: big_testutil.MustFirst(new(big.Int).SetString("100000000000000000000", 10)), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - result := lib.BigPow10(tc.exponent) - require.Equal(t, tc.expectedResult, result) - }) - } -} - -func TestBigMulPow10(t *testing.T) { - tests := map[string]struct { - val *big.Int - exponent int32 - expectedResult *big.Rat + exponent int64 + expectedValue *big.Int + expectedInverse bool }{ - "exponent = 2": { - val: new(big.Int).SetUint64(12345678), - exponent: 2, - expectedResult: big.NewRat(1234567800, 1), - }, - "exponent = 10": { - val: new(big.Int).SetUint64(12345678), - exponent: 10, - expectedResult: big.NewRat(123456780000000000, 1), - }, - "exponent = 0": { - val: new(big.Int).SetUint64(12345678), - exponent: 0, - expectedResult: big.NewRat(12345678, 1), - }, - "exponent = -1": { - val: new(big.Int).SetUint64(12345678), - exponent: -1, - expectedResult: big.NewRat(12345678, 10), - }, - "exponent = -3": { - val: new(big.Int).SetUint64(12345678), - exponent: -3, - expectedResult: big.NewRat(12345678, 1000), - }, - "exponent = -8": { - val: new(big.Int).SetUint64(12345678), - exponent: -8, - expectedResult: big.NewRat(12345678, 100000000), - }, + "0": {0, big.NewInt(1), false}, + "1": {1, big.NewInt(10), false}, + "2": {2, big.NewInt(100), false}, + "3": {3, big.NewInt(1000), false}, + "4": {4, big.NewInt(10000), false}, + "5": {5, big.NewInt(100000), false}, + "20": {20, big_testutil.MustFirst(new(big.Int).SetString("100000000000000000000", 10)), false}, + "-1": {-1, big.NewInt(10), true}, + "-2": {-2, big.NewInt(100), true}, + "-3": {-3, big.NewInt(1000), true}, + "-4": {-4, big.NewInt(10000), true}, + "-5": {-5, big.NewInt(100000), true}, + "-20": {-20, big_testutil.MustFirst(new(big.Int).SetString("100000000000000000000", 10)), true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { - result := lib.BigMulPow10(tc.val, tc.exponent) - require.Equal(t, tc.expectedResult, result) - }) - } -} - -func TestRatPow10(t *testing.T) { - tests := map[string]struct { - exponent int32 - expectedResult *big.Rat - }{ - "Positive exponent": { - exponent: 3, - expectedResult: new(big.Rat).SetUint64(1000), - }, - "Negative exponent": { - exponent: -3, - expectedResult: new(big.Rat).SetFrac64(1, 1000), - }, - "Zero exponent": { - exponent: 0, - expectedResult: new(big.Rat).SetUint64(1), - }, - "One exponent": { - exponent: 1, - expectedResult: new(big.Rat).SetUint64(10), - }, - "Negative one exponent": { - exponent: -1, - expectedResult: new(big.Rat).SetFrac64(1, 10), - }, - "Power of 2": { - exponent: 8, - expectedResult: new(big.Rat).SetUint64(100_000_000), - }, - "Negative power of 2": { - exponent: -8, - expectedResult: new(big.Rat).SetFrac64(1, 100_000_000), - }, - "Non-power of 2": { - exponent: 6, - expectedResult: new(big.Rat).SetUint64(1_000_000), - }, - "Negative non-power of 2": { - exponent: -6, - expectedResult: new(big.Rat).SetFrac64(1, 1_000_000), - }, - "Greater than max uint64": { - exponent: 20, - expectedResult: big_testutil.MustFirst(new(big.Rat).SetString("100000000000000000000")), - }, - "Denom greater than max uint64": { - exponent: -20, - expectedResult: new(big.Rat).SetFrac( - new(big.Int).SetInt64(1), - big_testutil.MustFirst( - new(big.Int).SetString("100000000000000000000", 10), - ), - ), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - result := lib.RatPow10(tc.exponent) - require.Equal(t, tc.expectedResult, result) + value, inverse := lib.BigPow10(tc.exponent) + require.Equal(t, tc.expectedValue, value) + require.Equal(t, tc.expectedInverse, inverse) }) } } @@ -293,10 +176,11 @@ func TestRatPow10(t *testing.T) { func TestBigPow10AllValuesInMemo(t *testing.T) { exponentString := "1" for i := 0; i < 100; i++ { - bigValue, ok := new(big.Int).SetString(exponentString, 0) + expected, ok := new(big.Int).SetString(exponentString, 10) require.True(t, ok) - require.Equal(t, lib.BigPow10(uint64(i)), bigValue) + result, _ := lib.BigPow10(i) + require.Equal(t, expected, result) exponentString = exponentString + "0" } @@ -304,9 +188,10 @@ func TestBigPow10AllValuesInMemo(t *testing.T) { func TestBigPow10AllValueNotInMemo(t *testing.T) { exponentString := "1" + strings.Repeat("0", 110) - bigValue, ok := new(big.Int).SetString(exponentString, 0) + expected, ok := new(big.Int).SetString(exponentString, 10) require.True(t, ok) - require.Equal(t, lib.BigPow10(uint64(110)), bigValue) + result, _ := lib.BigPow10(110) + require.Equal(t, expected, result) } func TestBigIntMulPpm(t *testing.T) { diff --git a/protocol/lib/quantums.go b/protocol/lib/quantums.go index b10286e490..f381bf78ca 100644 --- a/protocol/lib/quantums.go +++ b/protocol/lib/quantums.go @@ -40,12 +40,12 @@ func BaseToQuoteQuantums( } // Otherwise multiply or divide by the 1e^exponent. - ePow := bigPow10Helper(uint64(AbsInt32(exponent))) - if exponent > 0 { - return numResult.Mul(numResult, ePow) - } else { + pow10, inverse := BigPow10(exponent) + if inverse { // Trucated division (towards zero) instead of Euclidean division. - return numResult.Quo(numResult, ePow) + return numResult.Quo(numResult, pow10) + } else { + return numResult.Mul(numResult, pow10) } } @@ -75,11 +75,11 @@ func QuoteToBaseQuantums( // Divide result (towards zero) by 10^(exponent). exponent := priceExponent + baseCurrencyAtomicResolution - QuoteCurrencyAtomicResolution - power10Exponent := BigPow10(uint64(AbsInt32(exponent))) - if exponent > 0 { - result.Quo(result, power10Exponent) + p10, inverse := BigPow10(exponent) + if inverse { + result.Mul(result, p10) } else { - result.Mul(result, power10Exponent) + result.Quo(result, p10) } // Divide result (towards zero) by priceValue. diff --git a/protocol/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index be2316088c..840229780a 100644 --- a/protocol/mocks/ClobKeeper.go +++ b/protocol/mocks/ClobKeeper.go @@ -200,6 +200,26 @@ func (_m *ClobKeeper) GetAllClobPairs(ctx types.Context) []clobtypes.ClobPair { return r0 } +// GetAllStatefulOrders provides a mock function with given fields: ctx +func (_m *ClobKeeper) GetAllStatefulOrders(ctx types.Context) []clobtypes.Order { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetAllStatefulOrders") + } + + var r0 []clobtypes.Order + if rf, ok := ret.Get(0).(func(types.Context) []clobtypes.Order); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]clobtypes.Order) + } + } + + return r0 +} + // GetBankruptcyPriceInQuoteQuantums provides a mock function with given fields: ctx, subaccountId, perpetualId, deltaQuantums func (_m *ClobKeeper) GetBankruptcyPriceInQuoteQuantums(ctx types.Context, subaccountId subaccountstypes.SubaccountId, perpetualId uint32, deltaQuantums *big.Int) (*big.Int, error) { ret := _m.Called(ctx, subaccountId, perpetualId, deltaQuantums) diff --git a/protocol/testing/containertest/containertest.sh b/protocol/testing/containertest/containertest.sh index 6f04efe3e8..4f1ec6c2b0 100755 --- a/protocol/testing/containertest/containertest.sh +++ b/protocol/testing/containertest/containertest.sh @@ -7,7 +7,7 @@ set -eo pipefail source "./genesis.sh" CHAIN_ID="localdydxprotocol" -PREUPGRADE_VERSION="v5.0.0" +PREUPGRADE_VERSION="v5.0.2" # Define mnemonics for all validators. MNEMONICS=( diff --git a/protocol/testing/containertest/node.go b/protocol/testing/containertest/node.go index cf901e07a9..e126f8e2d1 100644 --- a/protocol/testing/containertest/node.go +++ b/protocol/testing/containertest/node.go @@ -166,6 +166,27 @@ func BroadcastTx[M cosmos.Msg](n *Node, message M, signer string) (err error) { return nil } +// Broadcast a tx to the node given the message and a signer address. +func BroadcastTxWithoutValidateBasic[M cosmos.Msg](n *Node, message M, signer string) (err error) { + clientContext, flags, err := n.getContextForBroadcastTx(signer) + if err != nil { + return err + } + + txFactory, err := tx.NewFactoryCLI(*clientContext, flags) + if err != nil { + return err + } + + // Use default gas limit and gas fee. + txFactory = txFactory.WithGas(constants.TestGasLimit).WithFees(constants.TestFee) + + if err = tx.BroadcastTx(*clientContext, txFactory, message); err != nil { + return err + } + return nil +} + // Query the node's grpc endpoint given the client constructor, request method, and request func Query[Request proto.Message, Response proto.Message, Client interface{}]( n *Node, diff --git a/protocol/testing/e2e/gov/wind_down_market_test.go b/protocol/testing/e2e/gov/wind_down_market_test.go index afdfeafebd..ab65b75644 100644 --- a/protocol/testing/e2e/gov/wind_down_market_test.go +++ b/protocol/testing/e2e/gov/wind_down_market_test.go @@ -101,9 +101,6 @@ func TestWindDownMarketProposal(t *testing.T) { { Order: constants.Order_Alice_Num0_Id0_Clob0_Buy10_Price10_GTB16, }, - { - Order: constants.Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB20_FOK, - }, { Order: constants.Order_Alice_Num0_Id1_Clob0_Buy5_Price15_GTB20_IOC, }, diff --git a/protocol/testutil/big/big.go b/protocol/testutil/big/big.go index bf1e9a7a17..c1f61ac500 100644 --- a/protocol/testutil/big/big.go +++ b/protocol/testutil/big/big.go @@ -23,8 +23,5 @@ func Int64MulPow10( ) ( result *big.Int, ) { - return new(big.Int).Mul( - big.NewInt(val), - lib.BigPow10(exponent), - ) + return lib.BigIntMulPow10(big.NewInt(val), exponent, false) } diff --git a/protocol/testutil/constants/orders.go b/protocol/testutil/constants/orders.go index 136a5121fa..09a4e5df32 100644 --- a/protocol/testutil/constants/orders.go +++ b/protocol/testutil/constants/orders.go @@ -1201,168 +1201,31 @@ var ( GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21}, TimeInForce: clobtypes.Order_TIME_IN_FORCE_IOC, } - - // Fill-or-kill orders. - Order_Alice_Num0_Id0_Clob1_Sell10_Price15_GTB20_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num0, ClientId: 0, ClobPairId: 1}, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 10, - Subticks: 15, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK = clobtypes.Order{ + Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_IOC = clobtypes.Order{ OrderId: clobtypes.OrderId{SubaccountId: Alice_Num0, ClientId: 0, ClobPairId: 1}, Side: clobtypes.Order_SIDE_BUY, Quantums: 10, Subticks: 15, GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Alice_Num0_Id0_Clob1_Buy20_Price15_GTB20_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num0, ClientId: 0, ClobPairId: 1}, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 20, - Subticks: 15, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, + TimeInForce: clobtypes.Order_TIME_IN_FORCE_IOC, } - Order_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB10_FOK = clobtypes.Order{ + Order_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB10_IOC = clobtypes.Order{ OrderId: clobtypes.OrderId{SubaccountId: Alice_Num0, ClientId: 0, ClobPairId: 0}, Side: clobtypes.Order_SIDE_BUY, Quantums: 100_000_000, // 1 BTC Subticks: 50_000_000_000, GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 10}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 1, ClobPairId: 1}, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 10, - Subticks: 15, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB21_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 1, ClobPairId: 1}, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 10, - Subticks: 15, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Bob_Num0_Id1_Clob1_Buy20_Price35_GTB22_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Bob_Num0, ClientId: 1, ClobPairId: 1}, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 20, - Subticks: 35, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 22}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, + TimeInForce: clobtypes.Order_TIME_IN_FORCE_IOC, } - Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK = clobtypes.Order{ + Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC = clobtypes.Order{ OrderId: clobtypes.OrderId{SubaccountId: Carl_Num0, ClientId: 0, ClobPairId: 0}, Side: clobtypes.Order_SIDE_BUY, Quantums: 50_000_000, // 0.5 BTC Subticks: 50_000_000_000, GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 10}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB10_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Carl_Num0, ClientId: 0, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 100_000_000, // 1 BTC - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 10}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB20_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Carl_Num0, ClientId: 0, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 100_000_000, // 1 BTC - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Carl_Num0_Id0_Clob0_Buy075BTC_Price50000_GTB11_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Carl_Num0, ClientId: 0, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 75_000_000, // 0.75 BTC - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 11}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - Order_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTB10_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Dave_Num0, ClientId: 0, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 100_000_000, // 1 BTC - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 10}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - // FOK + RO orders. - Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 1, ClobPairId: 1}, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 10, - Subticks: 15, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, - } - Order_Alice_Num1_Id1_Clob1_Buy10_Price15_GTB20_FOK_RO = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 1, ClobPairId: 1}, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 10, - Subticks: 15, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, - } - Order_Alice_Num1_Id1_Clob0_Sell10_Price15_GTB20_FOK_RO = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 1, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 10, - Subticks: 15, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, - } - Order_Alice_Num1_Id1_Clob0_Buy10_Price15_GTB20_FOK_RO = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 1, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 10, - Subticks: 15, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, - } - Order_Alice_Num1_Id0_Clob0_Buy110_Price50000_GTB21_FOK_RO = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 0, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 110, - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, - } - Order_Alice_Num1_Id0_Clob0_Sell110_Price50000_GTB21_FOK_RO = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 0, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 110, - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, - } - Order_Alice_Num1_Id1_Clob0_Sell15_Price500000_GTB20_FOK_RO = clobtypes.Order{ - OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 1, ClobPairId: 0}, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 15, - Subticks: 500_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, + TimeInForce: clobtypes.Order_TIME_IN_FORCE_IOC, } + // IOC + RO orders. Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_IOC_RO = clobtypes.Order{ OrderId: clobtypes.OrderId{SubaccountId: Alice_Num1, ClientId: 1, ClobPairId: 1}, diff --git a/protocol/testutil/constants/stateful_orders.go b/protocol/testutil/constants/stateful_orders.go index f9a5657184..06fb024549 100644 --- a/protocol/testutil/constants/stateful_orders.go +++ b/protocol/testutil/constants/stateful_orders.go @@ -778,21 +778,6 @@ var ( ConditionType: clobtypes.Order_CONDITION_TYPE_TAKE_PROFIT, ConditionalOrderTriggerSubticks: 20, } - ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price50_GTBT10_StopLoss51_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{ - SubaccountId: Alice_Num0, - ClientId: 0, - OrderFlags: clobtypes.OrderIdFlags_Conditional, - ClobPairId: 0, - }, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 5, - Subticks: 50, - GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 10}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, - ConditionalOrderTriggerSubticks: 51, - } ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price50_GTBT10_StopLoss51_IOC = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Alice_Num0, @@ -957,21 +942,6 @@ var ( ConditionalOrderTriggerSubticks: 49_999_000_000, TimeInForce: clobtypes.Order_TIME_IN_FORCE_IOC, } - ConditionalOrder_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_TP_49999_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{ - SubaccountId: Alice_Num0, - ClientId: 0, - OrderFlags: clobtypes.OrderIdFlags_Conditional, - ClobPairId: 0, - }, - Side: clobtypes.Order_SIDE_BUY, - Quantums: 100_000_000, - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 10}, - ConditionType: clobtypes.Order_CONDITION_TYPE_TAKE_PROFIT, - ConditionalOrderTriggerSubticks: 49_999_000_000, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } ConditionalOrder_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_SL_50001 = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Alice_Num0, @@ -1248,7 +1218,7 @@ var ( ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, ConditionalOrderTriggerSubticks: 50_003_000_000, } - ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK = clobtypes.Order{ + ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003 = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Carl_Num0, ClientId: 0, @@ -1259,25 +1229,9 @@ var ( Quantums: 50_000_000, // 0.5 BTC Subticks: 50_000_000_000, GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 10}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, ConditionType: clobtypes.Order_CONDITION_TYPE_STOP_LOSS, ConditionalOrderTriggerSubticks: 50_003_000_000, } - ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{ - SubaccountId: Carl_Num0, - ClientId: 0, - OrderFlags: clobtypes.OrderIdFlags_Conditional, - ClobPairId: 0, - }, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 50_000_000, // 0.5 BTC - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 10}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ConditionType: clobtypes.Order_CONDITION_TYPE_TAKE_PROFIT, - ConditionalOrderTriggerSubticks: 50_003_000_000, - } ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_IOC = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Carl_Num0, @@ -1309,7 +1263,7 @@ var ( ConditionalOrderTriggerSubticks: 49_999_000_000, } - // Conditional FOK/IOC RO orders. + // Conditional IOC RO orders. ConditionalOrder_Alice_Num1_Id1_Clob0_Sell05BTC_Price500000_GTBT20_TP_50001_IOC_RO = clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: Alice_Num1, @@ -1326,22 +1280,6 @@ var ( ConditionType: clobtypes.Order_CONDITION_TYPE_TAKE_PROFIT, ConditionalOrderTriggerSubticks: 50_001_000_000, } - ConditionalOrder_Alice_Num1_Id1_Clob0_Sell05BTC_Price500000_GTBT20_TP_50001_FOK_RO = clobtypes.Order{ - OrderId: clobtypes.OrderId{ - SubaccountId: Alice_Num1, - ClientId: 1, - ClobPairId: 0, - OrderFlags: clobtypes.OrderIdFlags_Conditional, - }, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 50_000_000, // 0.5 BTC - Subticks: 500_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 20}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, - ConditionType: clobtypes.Order_CONDITION_TYPE_TAKE_PROFIT, - ConditionalOrderTriggerSubticks: 50_001_000_000, - } // Long-Term post-only orders. LongTermOrder_Alice_Num0_Id0_Clob0_Buy100_Price10_GTBT15_PO = clobtypes.Order{ @@ -1412,21 +1350,6 @@ var ( ReduceOnly: true, } - // Long-Term Fill Or Kill Orders. - LongTermOrder_Carl_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_FOK = clobtypes.Order{ - OrderId: clobtypes.OrderId{ - SubaccountId: Carl_Num0, - ClientId: 0, - OrderFlags: clobtypes.OrderIdFlags_LongTerm, - ClobPairId: 0, - }, - Side: clobtypes.Order_SIDE_SELL, - Quantums: 100_000_000, - Subticks: 50_000_000_000, - GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 10}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, - } - // Long-Term Immediate Or Cancel Orders. LongTermOrder_Carl_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_IOC = clobtypes.Order{ OrderId: clobtypes.OrderId{ diff --git a/protocol/testutil/perpetuals/perpetuals.go b/protocol/testutil/perpetuals/perpetuals.go index 2c468b7977..bbd17779b1 100644 --- a/protocol/testutil/perpetuals/perpetuals.go +++ b/protocol/testutil/perpetuals/perpetuals.go @@ -1,6 +1,7 @@ package perpetuals import ( + "fmt" "math/big" "testing" @@ -8,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" ) @@ -88,42 +90,23 @@ func GeneratePerpetual(optionalModifications ...PerpetualModifierOption) *perpty return perpetual } +// MustHumanSizeToBaseQuantums converts a human-readable size to quantums. +// It uses the inverse of the exponent to convert the human size to quantums, +// since the exponent applies to the quantums to derive the human-readable size. func MustHumanSizeToBaseQuantums( humanSize string, atomicResolution int32, ) (baseQuantums uint64) { - // Parse the humanSize string to a big rational - ratValue, ok := new(big.Rat).SetString(humanSize) + ratio, ok := new(big.Rat).SetString(humanSize) if !ok { - panic("Failed to parse humanSize to big.Rat") + panic(fmt.Sprintf("MustHumanSizeToBaseQuantums: Failed to parse humanSize: %s", humanSize)) } - - // Convert atomicResolution to int64 for calculations - resolution := int64(atomicResolution) - - // Create a multiplier which is 10 raised to the power of the absolute atomicResolution - multiplier := new(big.Int).Exp(big.NewInt(10), big.NewInt(abs(resolution)), nil) - - // Depending on the sign of atomicResolution, multiply or divide - if atomicResolution > 0 { - ratValue.Mul(ratValue, new(big.Rat).SetInt(multiplier)) - } else if atomicResolution < 0 { - divisor := new(big.Rat).SetInt(multiplier) - ratValue.Mul(ratValue, divisor) - } - - // Convert the result to an unsigned 64-bit integer - resultInt := ratValue.Num() // Get the numerator which now represents the whole value - - return resultInt.Uint64() -} - -// Helper function to get the absolute value of an int64 -func abs(n int64) int64 { - if n < 0 { - return -n + result := lib.BigIntMulPow10(ratio.Num(), -atomicResolution, false) + result.Quo(result, ratio.Denom()) + if !result.IsUint64() { + panic("MustHumanSizeToBaseQuantums: result is not a uint64") } - return n + return result.Uint64() } // Helper function to set up default open interest for input perpetuals. diff --git a/protocol/testutil/perpetuals/perpetuals_test.go b/protocol/testutil/perpetuals/perpetuals_test.go index 630797c19a..1e2b85c5b4 100644 --- a/protocol/testutil/perpetuals/perpetuals_test.go +++ b/protocol/testutil/perpetuals/perpetuals_test.go @@ -17,7 +17,8 @@ func TestMustHumanSizeToBaseQuantums(t *testing.T) { {"1.123", -8, 112_300_000, false}, {"0.55", -9, 550_000_000, false}, {"0.00000001", -8, 1, false}, - {"235", 1, 2350, false}, + {"235", -1, 2350, false}, + {"235", 1, 23, false}, {"1", -10, 10_000_000_000, false}, {"0.0000000001", -10, 1, false}, {"abc", -8, 0, true}, // Invalid humanSize diff --git a/protocol/testutil/prices/market_param_price.go b/protocol/testutil/prices/market_param_price.go index 493734e991..be1b7d798f 100644 --- a/protocol/testutil/prices/market_param_price.go +++ b/protocol/testutil/prices/market_param_price.go @@ -1,8 +1,10 @@ package prices import ( + "fmt" "math/big" + "github.com/dydxprotocol/v4-chain/protocol/lib" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" ) @@ -86,32 +88,21 @@ func GenerateMarketParamPrice(optionalModifications ...MarketParamPriceModifierO return marketParamPrice } +// MustHumanPriceToMarketPrice converts a human-readable price to a market price. +// It uses the inverse of the exponent to convert the human price to a market price, +// since the exponent applies to the market price to derive the human price. func MustHumanPriceToMarketPrice( humanPrice string, exponent int32, ) (marketPrice uint64) { - // Ensure the exponent is negative - if exponent >= 0 { - panic("Only negative exponents are supported") - } - - // Parse the humanPrice string to a big rational - ratValue, ok := new(big.Rat).SetString(humanPrice) + ratio, ok := new(big.Rat).SetString(humanPrice) if !ok { - panic("Failed to parse humanPrice to big.Rat") + panic(fmt.Sprintf("MustHumanPriceToMarketPrice: Failed to parse humanPrice: %s", humanPrice)) } - - // Convert exponent to its absolute value for calculations - absResolution := int64(-exponent) - - // Create a multiplier which is 10 raised to the power of the absolute exponent - multiplier := new(big.Int).Exp(big.NewInt(10), big.NewInt(absResolution), nil) - - // Multiply the parsed humanPrice with the multiplier - ratValue.Mul(ratValue, new(big.Rat).SetInt(multiplier)) - - // Convert the result to an unsigned 64-bit integer - resultInt := ratValue.Num() // Get the numerator which now represents the whole value - - return resultInt.Uint64() + result := lib.BigIntMulPow10(ratio.Num(), -exponent, false) + result.Quo(result, ratio.Denom()) + if !result.IsUint64() { + panic("MustHumanPriceToMarketPrice: result is not a uint64") + } + return result.Uint64() } diff --git a/protocol/testutil/prices/market_param_price_test.go b/protocol/testutil/prices/market_param_price_test.go index ebe565c3b0..a026f43ce9 100644 --- a/protocol/testutil/prices/market_param_price_test.go +++ b/protocol/testutil/prices/market_param_price_test.go @@ -20,6 +20,8 @@ func TestMustHumanPriceToMarketPrice(t *testing.T) { {"0.00000001", -8, 1, false}, {"1", -10, 10_000_000_000, false}, {"0.0000000001", -10, 1, false}, + {"500", 2, 5, false}, + {"500", 0, 500, false}, {"abc", -8, 0, true}, // Invalid humanPrice } diff --git a/protocol/x/assets/keeper/asset.go b/protocol/x/assets/keeper/asset.go index a71b6d407e..8910a34299 100644 --- a/protocol/x/assets/keeper/asset.go +++ b/protocol/x/assets/keeper/asset.go @@ -299,22 +299,19 @@ func (k Keeper) ConvertAssetToCoin( ) } - bigRatDenomAmount := lib.BigMulPow10( - quantums, - asset.AtomicResolution-asset.DenomExponent, - ) - - // round down to get denom amount that was converted. - bigConvertedDenomAmount := lib.BigRatRound(bigRatDenomAmount, false) - - bigRatConvertedQuantums := lib.BigMulPow10( - bigConvertedDenomAmount, - asset.DenomExponent-asset.AtomicResolution, - ) - - bigConvertedQuantums := bigRatConvertedQuantums.Num() + exponent := asset.AtomicResolution - asset.DenomExponent + p10, inverse := lib.BigPow10(exponent) + var resultDenom *big.Int + var resultQuantums *big.Int + if inverse { + resultDenom = new(big.Int).Div(quantums, p10) + resultQuantums = new(big.Int).Mul(resultDenom, p10) + } else { + resultDenom = new(big.Int).Mul(quantums, p10) + resultQuantums = new(big.Int).Div(resultDenom, p10) + } - return bigConvertedQuantums, sdk.NewCoin(asset.Denom, sdkmath.NewIntFromBigInt(bigConvertedDenomAmount)), nil + return resultQuantums, sdk.NewCoin(asset.Denom, sdkmath.NewIntFromBigInt(resultDenom)), nil } // IsPositionUpdatable returns whether position of an asset is updatable. diff --git a/protocol/x/clob/e2e/conditional_orders_test.go b/protocol/x/clob/e2e/conditional_orders_test.go index 56f45fe593..7ebb3dc8bb 100644 --- a/protocol/x/clob/e2e/conditional_orders_test.go +++ b/protocol/x/clob/e2e/conditional_orders_test.go @@ -573,78 +573,6 @@ func TestConditionalOrder(t *testing.T) { }, }, }, - "TakeProfit/Sell FOK conditional order can place, trigger, not match, and be removed from state": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_10000USD, - }, - orders: []clobtypes.Order{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK, - }, - priceUpdateForFirstBlock: &prices.MsgUpdateMarketPrices{ - MarketPriceUpdates: []*prices.MsgUpdateMarketPrices_MarketPrice{ - prices.NewMarketPriceUpdate(0, 5_000_400_000), - }, - }, - priceUpdateForSecondBlock: &prices.MsgUpdateMarketPrices{}, - expectedInTriggeredStateAfterBlock: map[uint32]map[clobtypes.OrderId]bool{ - 2: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: true}, - 3: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false}, - 4: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false}, - }, - expectedExistInState: map[clobtypes.OrderId]bool{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false, - }, - expectedSubaccounts: []satypes.Subaccount{ - { - Id: &constants.Carl_Num0, - AssetPositions: []*satypes.AssetPosition{ - &constants.Usdc_Asset_10_000, - }, - }, - }, - }, - "TakeProfit/Sell FOK conditional order can place, trigger, fully match, and be removed from state": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_10000USD, - constants.Dave_Num0_500000USD, - }, - orders: []clobtypes.Order{ - constants.LongTermOrder_Dave_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_PO, - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK, - }, - priceUpdateForFirstBlock: &prices.MsgUpdateMarketPrices{ - MarketPriceUpdates: []*prices.MsgUpdateMarketPrices_MarketPrice{ - prices.NewMarketPriceUpdate(0, 5_000_400_000), - }, - }, - priceUpdateForSecondBlock: &prices.MsgUpdateMarketPrices{}, - expectedInTriggeredStateAfterBlock: map[uint32]map[clobtypes.OrderId]bool{ - 2: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: true}, - 3: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false}, - 4: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false}, - }, - expectedExistInState: map[clobtypes.OrderId]bool{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false, - }, - expectedSubaccounts: []satypes.Subaccount{ - { - Id: &constants.Carl_Num0, - AssetPositions: []*satypes.AssetPosition{ - { - AssetId: 0, - Quantums: dtypes.NewInt(10_000_000_000 + 25_000_000_000), - }, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(-50_000_000), - FundingIndex: dtypes.NewInt(0), - }, - }, - }, - }, - }, "StopLoss/Buy IOC conditional order can place, trigger, fully match, and be removed from state": { subaccounts: []satypes.Subaccount{ constants.Carl_Num0_10000USD, @@ -880,35 +808,6 @@ func TestConditionalOrder(t *testing.T) { constants.Alice_Num0_1ISO_LONG_10_000USD, }, }, - `Conditional FOK order that would violate isolated subaccount constraints can be placed, trigger, - fail isolated subaccount checks and get removed from state`: { - subaccounts: []satypes.Subaccount{ - constants.Alice_Num0_1ISO_LONG_10_000USD, - constants.Dave_Num0_10000USD, - }, - orders: []clobtypes.Order{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_TP_49999_FOK, - constants.Order_Dave_Num0_Id1_Clob0_Sell025BTC_Price50000_GTB11, - }, - priceUpdateForFirstBlock: &prices.MsgUpdateMarketPrices{}, - priceUpdateForSecondBlock: &prices.MsgUpdateMarketPrices{ - MarketPriceUpdates: []*prices.MsgUpdateMarketPrices_MarketPrice{ - prices.NewMarketPriceUpdate(0, 4_999_700_000), - }, - }, - - expectedExistInState: map[clobtypes.OrderId]bool{ - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_TP_49999_FOK.OrderId: false, - }, - expectedInTriggeredStateAfterBlock: map[uint32]map[clobtypes.OrderId]bool{ - 2: {constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_TP_49999_FOK.OrderId: false}, - 3: {constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_TP_49999_FOK.OrderId: true}, - 4: {constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_TP_49999_FOK.OrderId: false}, - }, - expectedSubaccounts: []satypes.Subaccount{ - constants.Alice_Num0_1ISO_LONG_10_000USD, - }, - }, } for name, tc := range tests { @@ -1829,78 +1728,6 @@ func TestConditionalOrder_TriggeringUsingMatchedPrice(t *testing.T) { }, }, }, - "TakeProfit/Sell FOK conditional order can place, trigger, not match, and be removed from state": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_10000USD, - constants.Carl_Num1_100000USD, - constants.Dave_Num1_500000USD, - }, - ordersForFirstBlock: []clobtypes.Order{ - // Create a match with price $50,003. - constants.Order_Carl_Num1_Id0_Clob0_Buy1BTC_Price50003_GTB10, - constants.Order_Dave_Num1_Id0_Clob0_Sell1BTC_Price49997_GTB10, - // Place the conditional order. - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK, - }, - expectedInTriggeredStateAfterBlock: map[uint32]map[clobtypes.OrderId]bool{ - 2: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: true}, - 3: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false}, - 4: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false}, - }, - expectedExistInState: map[clobtypes.OrderId]bool{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false, - }, - expectedSubaccounts: []satypes.Subaccount{ - { - Id: &constants.Carl_Num0, - AssetPositions: []*satypes.AssetPosition{ - &constants.Usdc_Asset_10_000, - }, - }, - }, - }, - "TakeProfit/Sell FOK conditional order can place, trigger, fully match, and be removed from state": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_10000USD, - constants.Dave_Num0_500000USD, - constants.Carl_Num1_100000USD, - constants.Dave_Num1_500000USD, - }, - ordersForFirstBlock: []clobtypes.Order{ - // Create a match with price $50,003. - constants.Order_Carl_Num1_Id0_Clob0_Buy1BTC_Price50003_GTB10, - constants.Order_Dave_Num1_Id0_Clob0_Sell1BTC_Price49997_GTB10, - // Place the conditional order and the order that would match against it. - constants.LongTermOrder_Dave_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_PO, - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK, - }, - expectedInTriggeredStateAfterBlock: map[uint32]map[clobtypes.OrderId]bool{ - 2: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: true}, - 3: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false}, - 4: {constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false}, - }, - expectedExistInState: map[clobtypes.OrderId]bool{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Sell05BTC_Price50000_GTBT10_TP_50003_FOK.OrderId: false, - }, - expectedSubaccounts: []satypes.Subaccount{ - { - Id: &constants.Carl_Num0, - AssetPositions: []*satypes.AssetPosition{ - { - AssetId: 0, - Quantums: dtypes.NewInt(10_000_000_000 + 25_000_000_000), - }, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(-50_000_000), - FundingIndex: dtypes.NewInt(0), - }, - }, - }, - }, - }, "StopLoss/Buy IOC conditional order can place, trigger, fully match, and be removed from state": { subaccounts: []satypes.Subaccount{ constants.Carl_Num0_10000USD, diff --git a/protocol/x/clob/e2e/equity_tier_limit_test.go b/protocol/x/clob/e2e/equity_tier_limit_test.go index b83a95c008..a7e123351c 100644 --- a/protocol/x/clob/e2e/equity_tier_limit_test.go +++ b/protocol/x/clob/e2e/equity_tier_limit_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/cometbft/cometbft/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/dydxprotocol/v4-chain/protocol/dtypes" "github.com/dydxprotocol/v4-chain/protocol/lib" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" @@ -116,36 +115,6 @@ func TestPlaceOrder_EquityTierLimit(t *testing.T) { advanceBlock: true, expectError: true, }, - "Conditional FoK order would exceed max open stateful orders across blocks": { - allowedOrders: []clobtypes.Order{ - testapp.MustScaleOrder( - constants.LongTermOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTBT15, - testapp.DefaultGenesis(), - ), - }, - limitedOrder: testapp.MustScaleOrder( - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price50_GTBT10_StopLoss51_FOK, - testapp.DefaultGenesis(), - ), - equityTierLimitConfiguration: clobtypes.EquityTierLimitConfiguration{ - StatefulOrderEquityTiers: []clobtypes.EquityTierLimit{ - { - UsdTncRequired: dtypes.NewInt(0), - Limit: 0, - }, - { - UsdTncRequired: dtypes.NewInt(5_000_000_000), // $5,000 - Limit: 1, - }, - { - UsdTncRequired: dtypes.NewInt(70_000_000_000), // $70,000 - Limit: 100, - }, - }, - }, - advanceBlock: true, - expectError: true, - }, "Conditional IoC order would exceed max open stateful orders across blocks": { allowedOrders: []clobtypes.Order{ testapp.MustScaleOrder( @@ -154,7 +123,7 @@ func TestPlaceOrder_EquityTierLimit(t *testing.T) { ), }, limitedOrder: testapp.MustScaleOrder( - constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price50_GTBT10_StopLoss51_FOK, + constants.ConditionalOrder_Alice_Num0_Id0_Clob0_Buy5_Price50_GTBT10_StopLoss51_IOC, testapp.DefaultGenesis(), ), equityTierLimitConfiguration: clobtypes.EquityTierLimitConfiguration{ @@ -431,8 +400,6 @@ func TestPlaceOrder_EquityTierLimit(t *testing.T) { len(tc.allowedOrders), ), ) - - checkThatFoKOrderIsNotBlockedByEquityTierLimits(t, tApp, ctx) } else { require.Conditionf(t, resp.IsOK, "Expected CheckTx to succeed. Response: %+v", resp) } @@ -544,8 +511,6 @@ func TestPlaceOrder_EquityTierLimit_OrderExpiry(t *testing.T) { if tc.expectError { require.Conditionf(t, resp.IsErr, "Expected CheckTx to error. Response: %+v", resp) require.Contains(t, resp.Log, "Opening order would exceed equity tier limit of 1. Order count: 1,") - - checkThatFoKOrderIsNotBlockedByEquityTierLimits(t, tApp, ctx) } else { require.Conditionf(t, resp.IsOK, "Expected CheckTx to succeed. Response: %+v", resp) } @@ -747,18 +712,3 @@ func TestPlaceOrder_EquityTierLimit_OrderFill(t *testing.T) { }) } } - -func checkThatFoKOrderIsNotBlockedByEquityTierLimits(t *testing.T, tApp *testapp.TestApp, ctx sdk.Context) { - for _, fokTx := range testapp.MustMakeCheckTxsWithClobMsg( - ctx, - tApp.App, - *clobtypes.NewMsgPlaceOrder(testapp.MustScaleOrder( - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK, - testapp.DefaultGenesis(), - )), - ) { - fokResponse := tApp.CheckTx(fokTx) - require.Conditionf(t, fokResponse.IsErr, "Expected CheckTx to error. Response: %+v", fokResponse) - require.Contains(t, fokResponse.Log, "FillOrKill order could not be fully filled") - } -} diff --git a/protocol/x/clob/e2e/isolated_subaccount_orders_test.go b/protocol/x/clob/e2e/isolated_subaccount_orders_test.go index 457558af57..7c7000eb36 100644 --- a/protocol/x/clob/e2e/isolated_subaccount_orders_test.go +++ b/protocol/x/clob/e2e/isolated_subaccount_orders_test.go @@ -35,14 +35,14 @@ func TestIsolatedSubaccountOrders(t *testing.T) { Subticks: 10, GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 5}, }) - PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5_FOK := *clobtypes.NewMsgPlaceOrder( + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5_IOC := *clobtypes.NewMsgPlaceOrder( clobtypes.Order{ OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 3}, Side: clobtypes.Order_SIDE_BUY, Quantums: uint64(orderQuantums), Subticks: 10, GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 5}, - TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, + TimeInForce: clobtypes.Order_TIME_IN_FORCE_IOC, }) PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5 := *clobtypes.NewMsgPlaceOrder( clobtypes.Order{ @@ -481,7 +481,7 @@ func TestIsolatedSubaccountOrders(t *testing.T) { expectedErrCode: clobtypes.ErrWouldViolateIsolatedSubaccountConstraints.ABCICode(), expectedErrMsg: "Order would violate isolated subaccount constraints.", }, - `Subaccount with isolated perpetual position fails to place FOK order for cross perpetual`: { + `Subaccount with isolated perpetual position fails to place IOC order for cross perpetual`: { subaccounts: []satypes.Subaccount{ constants.Alice_Num0_1ISO_LONG_10_000USD, constants.Bob_Num0_10_000USD, @@ -497,9 +497,9 @@ func TestIsolatedSubaccountOrders(t *testing.T) { constants.ClobPair_3_Iso, }, orders: []clobtypes.MsgPlaceOrder{ - // Liquidity to match the FOK order + // Liquidity to match the IOC order *clobtypes.NewMsgPlaceOrder(constants.Order_Bob_Num0_Id0_Clob0_Sell1BTC_Price50000_GTB10), - *clobtypes.NewMsgPlaceOrder(constants.Order_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB10_FOK), + *clobtypes.NewMsgPlaceOrder(constants.Order_Alice_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB10_IOC), }, collateralPoolBalances: map[string]int64{ satypes.ModuleAddress.String(): 30_000_000_000, // $30,000 USDC @@ -522,7 +522,7 @@ func TestIsolatedSubaccountOrders(t *testing.T) { expectedErrCode: clobtypes.ErrWouldViolateIsolatedSubaccountConstraints.ABCICode(), expectedErrMsg: "Order would violate isolated subaccount constraints.", }, - `Subaccount with cross perpetual position fails to place FOK order for isolated perpetual`: { + `Subaccount with cross perpetual position fails to place IOC order for isolated perpetual`: { subaccounts: []satypes.Subaccount{ constants.Alice_Num0_1BTC_LONG_10_000USD, constants.Bob_Num0_10_000USD, @@ -538,9 +538,9 @@ func TestIsolatedSubaccountOrders(t *testing.T) { constants.ClobPair_3_Iso, }, orders: []clobtypes.MsgPlaceOrder{ - // Liquidity to match the FOK order + // Liquidity to match the IOC order PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5, - PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5_FOK, + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5_IOC, }, collateralPoolBalances: map[string]int64{ satypes.ModuleAddress.String(): 30_000_000_000, // $30,000 USDC diff --git a/protocol/x/clob/e2e/long_term_orders_test.go b/protocol/x/clob/e2e/long_term_orders_test.go index c326571262..0afac82a6c 100644 --- a/protocol/x/clob/e2e/long_term_orders_test.go +++ b/protocol/x/clob/e2e/long_term_orders_test.go @@ -336,12 +336,19 @@ func TestImmediateExecutionLongTermOrders(t *testing.T) { ctx, tApp.App, *clobtypes.NewMsgPlaceOrder( - constants.LongTermOrder_Carl_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_FOK, + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 1}, + Side: clobtypes.Order_SIDE_BUY, + Quantums: 20, + Subticks: 15, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 20}, + TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, + }, ), ) { resp := tApp.CheckTx(checkTx) require.Conditionf(t, resp.IsErr, "Expected CheckTx to error. Response: %+v", resp) - require.Contains(t, resp.Log, clobtypes.ErrLongTermOrdersCannotRequireImmediateExecution.Error()) + require.Contains(t, resp.Log, clobtypes.ErrDeprecatedField.Error()) } // Reject long-term IOC/FOK in DeliverTx @@ -349,12 +356,24 @@ func TestImmediateExecutionLongTermOrders(t *testing.T) { BlockAdvancement: testapp.BlockAdvancement{ StatefulOrders: []clobtypes.Order{ constants.LongTermOrder_Carl_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_IOC, - constants.LongTermOrder_Carl_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_FOK, + { + OrderId: clobtypes.OrderId{ + SubaccountId: constants.Carl_Num0, + ClientId: 0, + OrderFlags: clobtypes.OrderIdFlags_LongTerm, + ClobPairId: 0, + }, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 100_000_000, + Subticks: 50_000_000_000, + GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{GoodTilBlockTime: 10}, + TimeInForce: clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL, + }, }, }, ExpectedDeliverTxErrors: map[int]string{ 0: clobtypes.ErrLongTermOrdersCannotRequireImmediateExecution.Error(), - 1: clobtypes.ErrLongTermOrdersCannotRequireImmediateExecution.Error(), + 1: clobtypes.ErrDeprecatedField.Error(), }, } blockAdvancement.AdvanceToBlock(ctx, 2, tApp, t) diff --git a/protocol/x/clob/e2e/order_matches_test.go b/protocol/x/clob/e2e/order_matches_test.go index 8b1e5fcb4a..944e174a19 100644 --- a/protocol/x/clob/e2e/order_matches_test.go +++ b/protocol/x/clob/e2e/order_matches_test.go @@ -58,7 +58,7 @@ func TestDeliverTxMatchValidation(t *testing.T) { }, }, ExpectedDeliverTxErrors: testapp.TxIndexesToErrors{ - 0: "IOC/FOK order is already filled, remaining size is cancelled.", + 0: "IOC order is already filled, remaining size is cancelled.", }, }, }, @@ -168,52 +168,7 @@ func TestDeliverTxMatchValidation(t *testing.T) { }, }, ExpectedDeliverTxErrors: testapp.TxIndexesToErrors{ - 0: "IOC/FOK order is already filled, remaining size is cancelled.", - }, - }, - }, - }, - "Error: partially filled order cannot be replaced by FOK": { - subaccounts: []satypes.Subaccount{ - constants.Alice_Num1_10_000USD, - constants.Bob_Num0_10_000USD, - }, - blockAdvancements: []testapp.BlockAdvancementWithErrors{ - { - BlockAdvancement: testapp.BlockAdvancement{ - ShortTermOrdersAndOperations: []interface{}{ - testapp.MustScaleOrder(constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, testapp.DefaultGenesis()), - testapp.MustScaleOrder(constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, testapp.DefaultGenesis()), - clobtestutils.NewMatchOperationRaw( - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - []clobtypes.MakerFill{ - { - FillAmount: 5_000, // step base quantums is 1000 for ETH/USDC (ClobPair 1) - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId, - }, - }, - ), - }, - }, - }, - { - BlockAdvancement: testapp.BlockAdvancement{ - ShortTermOrdersAndOperations: []interface{}{ - testapp.MustScaleOrder(constants.Order_Bob_Num0_Id12_Clob1_Buy5_Price40_GTB20, testapp.DefaultGenesis()), - testapp.MustScaleOrder(constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, testapp.DefaultGenesis()), - clobtestutils.NewMatchOperationRaw( - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - []clobtypes.MakerFill{ - { - FillAmount: 5_000, - MakerOrderId: constants.Order_Bob_Num0_Id12_Clob1_Buy5_Price40_GTB20.OrderId, - }, - }, - ), - }, - }, - ExpectedDeliverTxErrors: testapp.TxIndexesToErrors{ - 0: "IOC/FOK order is already filled, remaining size is cancelled.", + 0: "IOC order is already filled, remaining size is cancelled.", }, }, }, diff --git a/protocol/x/clob/e2e/order_removal_test.go b/protocol/x/clob/e2e/order_removal_test.go index c3e9ecc804..2261b4fdf6 100644 --- a/protocol/x/clob/e2e/order_removal_test.go +++ b/protocol/x/clob/e2e/order_removal_test.go @@ -57,26 +57,6 @@ func TestConditionalOrderRemoval(t *testing.T) { true, // P0 order should be removed }, }, - "conditional fill-or-kill order does not fully match and is removed": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_10000USD, - constants.Dave_Num0_10000USD, - }, - orders: []clobtypes.Order{ - constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell025BTC_Price50000_GTBT10, - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK, - }, - - priceUpdate: &prices.MsgUpdateMarketPrices{ - MarketPriceUpdates: []*prices.MsgUpdateMarketPrices_MarketPrice{ - prices.NewMarketPriceUpdate(0, 5_000_400_000), - }, - }, - expectedOrderRemovals: []bool{ - false, - true, // non fully filled FOK order should be removed - }, - }, "conditional IOC order does not fully match and is removed": { subaccounts: []satypes.Subaccount{ constants.Carl_Num0_10000USD, @@ -441,78 +421,6 @@ func TestOrderRemoval_Invalid(t *testing.T) { // expectedErrType: clobtypes.ErrInvalidOrderRemoval, // expectedErr: "Order must be reduce only", // }, - "invalid proposal: conditional fok order cannot be removed when untriggered": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_10000USD, - }, - orders: []clobtypes.Order{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK, - }, - msgProposedOperations: &clobtypes.MsgProposedOperations{ - OperationsQueue: []clobtypes.OperationRaw{ - clobtestutils.NewOrderRemovalOperationRaw( - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK.OrderId, - clobtypes.OrderRemoval_REMOVAL_REASON_CONDITIONAL_FOK_COULD_NOT_BE_FULLY_FILLED, - ), - }, - }, - expectedErr: "does not exist in triggered conditional state", - }, - "invalid proposal: conditional fok order removal is for non fok order": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_10000USD, - }, - orders: []clobtypes.Order{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_IOC, - }, - priceUpdate: &prices.MsgUpdateMarketPrices{ - MarketPriceUpdates: []*prices.MsgUpdateMarketPrices_MarketPrice{ - prices.NewMarketPriceUpdate(0, 5_000_400_000), - }, - }, - msgProposedOperations: &clobtypes.MsgProposedOperations{ - OperationsQueue: []clobtypes.OperationRaw{ - clobtestutils.NewOrderRemovalOperationRaw( - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_IOC.OrderId, - clobtypes.OrderRemoval_REMOVAL_REASON_CONDITIONAL_FOK_COULD_NOT_BE_FULLY_FILLED, - ), - }, - }, - expectedErr: "Order is not fill-or-kill", - }, - "invalid proposal: conditional fok order cannot be removed when fully filled": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_10000USD, - constants.Dave_Num0_10000USD, - }, - orders: []clobtypes.Order{ - constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10, - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK, - }, - priceUpdate: &prices.MsgUpdateMarketPrices{ - MarketPriceUpdates: []*prices.MsgUpdateMarketPrices_MarketPrice{ - prices.NewMarketPriceUpdate(0, 5_000_400_000), - }, - }, - msgProposedOperations: &clobtypes.MsgProposedOperations{ - OperationsQueue: []clobtypes.OperationRaw{ - clobtestutils.NewMatchOperationRaw( - &constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK, - []clobtypes.MakerFill{ - { - FillAmount: 50_000_000, - MakerOrderId: constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId, - }, - }, - ), - clobtestutils.NewOrderRemovalOperationRaw( - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK.OrderId, - clobtypes.OrderRemoval_REMOVAL_REASON_CONDITIONAL_FOK_COULD_NOT_BE_FULLY_FILLED, - ), - }, - }, - expectedErr: "Fill-or-kill order is fully filled", - }, "invalid proposal: conditional ioc order cannot be removed when untriggered": { subaccounts: []satypes.Subaccount{ constants.Carl_Num0_10000USD, @@ -535,7 +443,7 @@ func TestOrderRemoval_Invalid(t *testing.T) { constants.Carl_Num0_10000USD, }, orders: []clobtypes.Order{ - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK, + constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003, }, priceUpdate: &prices.MsgUpdateMarketPrices{ MarketPriceUpdates: []*prices.MsgUpdateMarketPrices_MarketPrice{ @@ -545,7 +453,7 @@ func TestOrderRemoval_Invalid(t *testing.T) { msgProposedOperations: &clobtypes.MsgProposedOperations{ OperationsQueue: []clobtypes.OperationRaw{ clobtestutils.NewOrderRemovalOperationRaw( - constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003_FOK.OrderId, + constants.ConditionalOrder_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTBT10_SL_50003.OrderId, clobtypes.OrderRemoval_REMOVAL_REASON_CONDITIONAL_IOC_WOULD_REST_ON_BOOK, ), }, diff --git a/protocol/x/clob/e2e/reduce_only_orders_test.go b/protocol/x/clob/e2e/reduce_only_orders_test.go index d44d626558..2dec334210 100644 --- a/protocol/x/clob/e2e/reduce_only_orders_test.go +++ b/protocol/x/clob/e2e/reduce_only_orders_test.go @@ -214,202 +214,6 @@ func TestReduceOnlyOrders(t *testing.T) { }, }, }, - "FOK Reduce only order fully matches short term order second block, maker order partially filled": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_100000USD, - constants.Alice_Num1_1BTC_Long_500_000USD, - }, - ordersForFirstBlock: []clobtypes.Order{ - testapp.MustScaleOrder( - constants.Order_Carl_Num0_Id0_Clob0_Buy80_Price500000_GTB20, - testapp.DefaultGenesis(), - ), - }, - ordersForSecondBlock: []clobtypes.Order{ - testapp.MustScaleOrder( - constants.Order_Alice_Num1_Id1_Clob0_Sell15_Price500000_GTB20_FOK_RO, - testapp.DefaultGenesis(), - ), - }, - - // Crashing app checks have to be disabled because the FOK order will not match - // with an empty orderbook and fail to be placed. - crashingAppCheckTxNonDeterminismChecksDisabled: true, - - expectedOrderOnMemClob: map[clobtypes.OrderId]bool{ - constants.Order_Carl_Num0_Id0_Clob0_Buy80_Price500000_GTB20.OrderId: true, - constants.Order_Alice_Num1_Id1_Clob0_Sell15_Price500000_GTB20_FOK_RO.OrderId: false, - }, - expectedOrderFillAmount: map[clobtypes.OrderId]uint64{ - constants.Order_Carl_Num0_Id0_Clob0_Buy80_Price500000_GTB20.OrderId: 150, - }, - expectedSubaccounts: []satypes.Subaccount{ - { - Id: &constants.Carl_Num0, - AssetPositions: []*satypes.AssetPosition{ - { - AssetId: 0, - Quantums: dtypes.NewInt(9_250_0825_000), - }, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(150), - FundingIndex: dtypes.NewInt(0), - }, - }, - }, - { - Id: &constants.Alice_Num1, - AssetPositions: []*satypes.AssetPosition{ - { - AssetId: 0, - Quantums: dtypes.NewInt(507_496_250_000), - }, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(99_999_850), - FundingIndex: dtypes.NewInt(0), - }, - }, - }, - }, - }, - "FOK Reduce only order fully matches short term order same block, maker order partially filled": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_100000USD, - constants.Alice_Num1_1BTC_Long_500_000USD, - }, - ordersForFirstBlock: []clobtypes.Order{ - testapp.MustScaleOrder( - constants.Order_Carl_Num0_Id0_Clob0_Buy80_Price500000_GTB20, - testapp.DefaultGenesis(), - ), - testapp.MustScaleOrder( - constants.Order_Alice_Num1_Id1_Clob0_Sell15_Price500000_GTB20_FOK_RO, - testapp.DefaultGenesis(), - ), - }, - ordersForSecondBlock: []clobtypes.Order{}, - - // Crashing app checks don't need to be disabled since matches occur in same block. - crashingAppCheckTxNonDeterminismChecksDisabled: false, - - expectedOrderOnMemClob: map[clobtypes.OrderId]bool{ - constants.Order_Carl_Num0_Id0_Clob0_Buy80_Price500000_GTB20.OrderId: true, - constants.Order_Alice_Num1_Id1_Clob0_Sell15_Price500000_GTB20_FOK_RO.OrderId: false, - }, - expectedOrderFillAmount: map[clobtypes.OrderId]uint64{ - constants.Order_Carl_Num0_Id0_Clob0_Buy80_Price500000_GTB20.OrderId: 150, - }, - expectedSubaccounts: []satypes.Subaccount{ - { - Id: &constants.Carl_Num0, - AssetPositions: []*satypes.AssetPosition{ - { - AssetId: 0, - Quantums: dtypes.NewInt(9_250_0825_000), - }, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(150), - FundingIndex: dtypes.NewInt(0), - }, - }, - }, - { - Id: &constants.Alice_Num1, - AssetPositions: []*satypes.AssetPosition{ - { - AssetId: 0, - Quantums: dtypes.NewInt(507_496_250_000), - }, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(99_999_850), - FundingIndex: dtypes.NewInt(0), - }, - }, - }, - }, - }, - "Conditional FOK Reduce only order fully matches short term order same block, maker order partially filled": { - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_500000USD, - constants.Alice_Num1_1BTC_Long_500_000USD, - }, - ordersForFirstBlock: []clobtypes.Order{ - constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell05BTC_Price500000_GTBT20_TP_50001_FOK_RO, - constants.Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price500000_GTB10, - }, - ordersForSecondBlock: []clobtypes.Order{}, - - priceUpdateForFirstBlock: &prices.MsgUpdateMarketPrices{ - MarketPriceUpdates: []*prices.MsgUpdateMarketPrices_MarketPrice{ - prices.NewMarketPriceUpdate(0, 5_000_300_000), - }, - }, - priceUpdateForSecondBlock: &prices.MsgUpdateMarketPrices{}, - - expectedInTriggeredStateAfterBlock: map[uint32]map[clobtypes.OrderId]bool{ - 2: { - constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell05BTC_Price500000_GTBT20_TP_50001_FOK_RO.OrderId: true, - }, - 3: { - constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell05BTC_Price500000_GTBT20_TP_50001_FOK_RO.OrderId: true, - }, - }, - - expectedOrderOnMemClob: map[clobtypes.OrderId]bool{ - constants.Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price500000_GTB10.OrderId: true, - constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell05BTC_Price500000_GTBT20_TP_50001_FOK_RO.OrderId: false, - }, - expectedOrderFillAmount: map[clobtypes.OrderId]uint64{ - constants.Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price500000_GTB10.OrderId: 50_000_000, - constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell05BTC_Price500000_GTBT20_TP_50001_FOK_RO.OrderId: 50_000_000, - }, - expectedSubaccounts: []satypes.Subaccount{ - { - Id: &constants.Carl_Num0, - AssetPositions: []*satypes.AssetPosition{ - { - AssetId: 0, - Quantums: dtypes.NewInt(250_027_500_000), - }, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(50_000_000), - FundingIndex: dtypes.NewInt(0), - }, - }, - }, - { - Id: &constants.Alice_Num1, - AssetPositions: []*satypes.AssetPosition{ - { - AssetId: 0, - Quantums: dtypes.NewInt(749_875_000_000), - }, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(50_000_000), - FundingIndex: dtypes.NewInt(0), - }, - }, - }, - }, - }, "Conditional IOC Reduce only order partially matches short term order same block, maker order fully filled": { subaccounts: []satypes.Subaccount{ constants.Carl_Num0_500000USD, @@ -627,7 +431,7 @@ func TestReduceOnlyOrderFailure(t *testing.T) { "Zero perpetual position subaccount position cannot place sell RO order": { orders: []clobtypes.Order{ testapp.MustScaleOrder( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, + constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_IOC_RO, testapp.DefaultGenesis(), ), }, @@ -638,7 +442,7 @@ func TestReduceOnlyOrderFailure(t *testing.T) { "Zero perpetual position subaccount position cannot place buy RO order": { orders: []clobtypes.Order{ testapp.MustScaleOrder( - constants.Order_Alice_Num1_Id1_Clob1_Buy10_Price15_GTB20_FOK_RO, + constants.Order_Alice_Num1_Id1_Clob1_Buy10_Price15_GTB20_IOC_RO, testapp.DefaultGenesis(), ), }, @@ -646,34 +450,6 @@ func TestReduceOnlyOrderFailure(t *testing.T) { clobtypes.ErrReduceOnlyWouldIncreasePositionSize.Error(), }, }, - "FOK Reduce only order is placed but does not match immediately and is cancelled.": { - subaccounts: []satypes.Subaccount{ - constants.Alice_Num1_1BTC_Short_100_000USD, - }, - orders: []clobtypes.Order{ - testapp.MustScaleOrder( - constants.Order_Alice_Num1_Id1_Clob0_Buy10_Price15_GTB20_FOK_RO, - testapp.DefaultGenesis(), - ), - }, - errorMsg: []string{ - clobtypes.ErrFokOrderCouldNotBeFullyFilled.Error(), - }, - }, - "Conditional FOK Reduce only order is placed successfully.": { - subaccounts: []satypes.Subaccount{ - constants.Alice_Num1_1BTC_Short_100_000USD, - }, - orders: []clobtypes.Order{ - testapp.MustScaleOrder( - constants.ConditionalOrder_Alice_Num1_Id1_Clob0_Sell05BTC_Price500000_GTBT20_TP_50001_IOC_RO, - testapp.DefaultGenesis(), - ), - }, - errorMsg: []string{ - "", - }, - }, "Regular Reduce only order fails because disabled": { subaccounts: []satypes.Subaccount{ constants.Alice_Num1_1BTC_Short_100_000USD, diff --git a/protocol/x/clob/e2e/short_term_orders_test.go b/protocol/x/clob/e2e/short_term_orders_test.go index 62272dfaea..bf5abb0e0b 100644 --- a/protocol/x/clob/e2e/short_term_orders_test.go +++ b/protocol/x/clob/e2e/short_term_orders_test.go @@ -662,10 +662,8 @@ func TestPlaceOrder(t *testing.T) { func TestShortTermOrderReplacements(t *testing.T) { order := PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20 - fok_replacement := order - fok_replacement.Order.GoodTilOneof = &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21} - fok_replacement.Order.TimeInForce = clobtypes.Order_TIME_IN_FORCE_FILL_OR_KILL - ioc_replacement := fok_replacement + ioc_replacement := order + ioc_replacement.Order.GoodTilOneof = &clobtypes.Order_GoodTilBlock{GoodTilBlock: 21} ioc_replacement.Order.TimeInForce = clobtypes.Order_TIME_IN_FORCE_IOC type orderIdExpectations struct { @@ -972,23 +970,7 @@ func TestShortTermOrderReplacements(t *testing.T) { }, }, }, - "Fail: Replacing order with FOK fails": { - blocks: []blockOrdersAndExpectations{ - { - ordersToPlace: []clobtypes.MsgPlaceOrder{ - PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20, - fok_replacement, - }, - orderIdsExpectations: map[clobtypes.OrderId]orderIdExpectations{ - PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order.OrderId: { - shouldExistOnMemclob: true, - expectedOrder: PlaceOrder_Alice_Num0_Id0_Clob0_Buy6_Price10_GTB20.Order, - }, - }, - }, - }, - }, - "Fail: Replacing order with IOC fails": { + "Success: Replacing order with IOC which does not fully match results in order being removed from the book": { blocks: []blockOrdersAndExpectations{ { ordersToPlace: []clobtypes.MsgPlaceOrder{ @@ -1439,198 +1421,6 @@ func TestShortTermAdvancedOrders(t *testing.T) { constants.Order_Alice_Num0_Id1_Clob1_Sell10_Price15_GTB20_IOC.OrderId: 5000, }, }, - "FOK buy fully matches": { - blocks: []testmsgs.TestBlockWithMsgs{ - { - Block: 2, - Msgs: []testmsgs.TestSdkMsg{ - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Bob_Num0_Id8_Clob1_Sell20_Price10_GTB22, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: true, - }, - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: true, - }, - }, - }, - }, - expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ - constants.Order_Bob_Num0_Id8_Clob1_Sell20_Price10_GTB22.OrderId: true, - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK.OrderId: false, - }, - expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ - constants.Order_Bob_Num0_Id8_Clob1_Sell20_Price10_GTB22.OrderId: 10_000, - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK.OrderId: 10_000, - }, - }, - "FOK sell fully matches": { - blocks: []testmsgs.TestBlockWithMsgs{ - { - Block: 2, - Msgs: []testmsgs.TestSdkMsg{ - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: true, - }, - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Alice_Num0_Id0_Clob1_Sell10_Price15_GTB20_FOK, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: true, - }, - }, - }, - }, - expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22.OrderId: true, - constants.Order_Alice_Num0_Id0_Clob1_Sell10_Price15_GTB20_FOK.OrderId: false, - }, - expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22.OrderId: 10_000, - constants.Order_Alice_Num0_Id0_Clob1_Sell10_Price15_GTB20_FOK.OrderId: 10_000, - }, - }, - "FOK buy partially matches, fails, and is not placed on the book": { - blocks: []testmsgs.TestBlockWithMsgs{ - { - Block: 2, - Msgs: []testmsgs.TestSdkMsg{ - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Bob_Num0_Id8_Clob1_Sell5_Price10_GTB22, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: true, - }, - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: false, - ExpectedRespCode: clobtypes.ErrFokOrderCouldNotBeFullyFilled.ABCICode(), - }, - }, - }, - }, - expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ - constants.Order_Bob_Num0_Id8_Clob1_Sell5_Price10_GTB22.OrderId: true, - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK.OrderId: false, - }, - expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ - constants.Order_Bob_Num0_Id8_Clob1_Sell5_Price10_GTB22.OrderId: 0, - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK.OrderId: 0, - }, - }, - "FOK sell partially matches, fails, and is not placed on the book": { - blocks: []testmsgs.TestBlockWithMsgs{ - { - Block: 2, - Msgs: []testmsgs.TestSdkMsg{ - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: true, - }, - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Alice_Num0_Id0_Clob1_Sell10_Price15_GTB20_FOK, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: false, - ExpectedRespCode: clobtypes.ErrFokOrderCouldNotBeFullyFilled.ABCICode(), - }, - }, - }, - }, - expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: true, - constants.Order_Alice_Num0_Id0_Clob1_Sell10_Price15_GTB20_FOK.OrderId: false, - }, - expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId: 0, - constants.Order_Alice_Num0_Id0_Clob1_Sell10_Price15_GTB20_FOK.OrderId: 0, - }, - }, - "FOK fails CheckTx if previously filled": { - blocks: []testmsgs.TestBlockWithMsgs{ - { - Block: 2, - Msgs: []testmsgs.TestSdkMsg{ - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Bob_Num0_Id8_Clob1_Sell20_Price10_GTB22, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: true, - }, - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: true, - }, - }, - }, - { - Block: 3, - Msgs: []testmsgs.TestSdkMsg{ - { - Msg: clobtypes.NewMsgPlaceOrder( - testapp.MustScaleOrder( - constants.Order_Alice_Num0_Id0_Clob1_Buy20_Price15_GTB20_FOK, - testapp.DefaultGenesis(), - ), - ), - ExpectedIsOk: false, - ExpectedRespCode: clobtypes.ErrImmediateExecutionOrderAlreadyFilled.ABCICode(), - }, - }, - }, - }, - expectedOrderIdsInMemclob: map[clobtypes.OrderId]bool{ - constants.Order_Bob_Num0_Id8_Clob1_Sell5_Price10_GTB22.OrderId: true, - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK.OrderId: false, - }, - expectedOrderFillAmounts: map[clobtypes.OrderId]uint64{ - constants.Order_Bob_Num0_Id8_Clob1_Sell5_Price10_GTB22.OrderId: 10_000, - constants.Order_Alice_Num0_Id0_Clob1_Buy10_Price15_GTB20_FOK.OrderId: 10_000, - }, - }, "Post-only buy does not cross and is placed on the book": { blocks: []testmsgs.TestBlockWithMsgs{ { diff --git a/protocol/x/clob/keeper/liquidations.go b/protocol/x/clob/keeper/liquidations.go index b96284095b..7f89c5b9a4 100644 --- a/protocol/x/clob/keeper/liquidations.go +++ b/protocol/x/clob/keeper/liquidations.go @@ -1052,20 +1052,22 @@ func (k Keeper) ConvertFillablePriceToSubticks( panic("ConvertFillablePriceToSubticks: FillablePrice should not be negative") } - exponent := clobPair.QuantumConversionExponent - absExponentiatedValueBig := lib.BigPow10(uint64(lib.AbsInt32(exponent))) - quoteQuantumsPerBaseQuantumAndSubtickRat := new(big.Rat).SetInt(absExponentiatedValueBig) - // If `exponent` is negative, invert the fraction to set the result to `1 / 10^exponent`. - if exponent < 0 { - quoteQuantumsPerBaseQuantumAndSubtickRat.Inv(quoteQuantumsPerBaseQuantumAndSubtickRat) - } - // Assuming `fillablePrice` is in units of `quote quantums / base quantum`, then dividing by // `quote quantums / (base quantum * subtick)` will give the resulting units of subticks. - subticksRat := new(big.Rat).Quo( - fillablePrice, - quoteQuantumsPerBaseQuantumAndSubtickRat, - ) + exponent := clobPair.QuantumConversionExponent + p10, inverse := lib.BigPow10(exponent) + subticksRat := new(big.Rat) + if inverse { + subticksRat.SetFrac( + new(big.Int).Mul(p10, fillablePrice.Num()), + fillablePrice.Denom(), + ) + } else { + subticksRat.SetFrac( + fillablePrice.Num(), + new(big.Int).Mul(p10, fillablePrice.Denom()), + ) + } // If we are liquidating a long position with a sell order, then we round up to the nearest // subtick (and vice versa for liquidating shorts). diff --git a/protocol/x/clob/keeper/process_operations_long_term_test.go b/protocol/x/clob/keeper/process_operations_long_term_test.go index ff7baa9644..ff164275ce 100644 --- a/protocol/x/clob/keeper/process_operations_long_term_test.go +++ b/protocol/x/clob/keeper/process_operations_long_term_test.go @@ -502,10 +502,10 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, rawOperations: []types.OperationRaw{ clobtest.NewShortTermOrderPlacementOperationRaw( - constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK, + constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC, ), clobtest.NewMatchOperationRaw( - &constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK, + &constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC, []types.MakerFill{ { MakerOrderId: constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId, @@ -527,7 +527,7 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { ), }, expectedFillAmounts: map[types.OrderId]satypes.BaseQuantums{ - constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK.OrderId: 50_000_000, + constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC.OrderId: 50_000_000, constants.Order_Carl_Num0_Id2_Clob0_Buy05BTC_Price50000.OrderId: 50_000_000, // Fully filled orders are removed. constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId: 0, @@ -540,7 +540,7 @@ func TestProcessProposerMatches_LongTerm_Success(t *testing.T) { }, expectedProcessProposerMatchesEvents: types.ProcessProposerMatchesEvents{ OrderIdsFilledInLastBlock: []types.OrderId{ - constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK.OrderId, + constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC.OrderId, constants.Order_Carl_Num0_Id2_Clob0_Buy05BTC_Price50000.OrderId, constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10.OrderId, }, diff --git a/protocol/x/clob/keeper/process_operations_stateful_validation_test.go b/protocol/x/clob/keeper/process_operations_stateful_validation_test.go index 6b608b68c0..13fdd09e47 100644 --- a/protocol/x/clob/keeper/process_operations_stateful_validation_test.go +++ b/protocol/x/clob/keeper/process_operations_stateful_validation_test.go @@ -199,40 +199,6 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T constants.LongTermOrder_Dave_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10_PO.GetOrderTextString(), ), }, - `Stateful match validation: maker order cannot be FOK`: { - perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_100PercentMarginRequirement, - }, - subaccounts: []satypes.Subaccount{ - constants.Carl_Num0_1BTC_Short, - constants.Dave_Num0_1BTC_Long_50000USD, - }, - perpetualFeeParams: &constants.PerpetualFeeParams, - clobPairs: []types.ClobPair{ - constants.ClobPair_Btc, - }, - preExistingStatefulOrders: []types.Order{ - constants.LongTermOrder_Dave_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10, - constants.LongTermOrder_Carl_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_FOK, - }, - setupState: func(ctx sdk.Context, ks keepertest.ClobKeepersTestContext) { - ks.BlockTimeKeeper.SetPreviousBlockInfo(ks.Ctx, &blocktimetypes.BlockInfo{ - Timestamp: time.Unix(5, 0), - }) - }, - rawOperations: []types.OperationRaw{ - clobtest.NewMatchOperationRaw( - &constants.LongTermOrder_Dave_Num0_Id0_Clob0_Buy1BTC_Price50000_GTBT10, - []types.MakerFill{ - { - MakerOrderId: constants.LongTermOrder_Carl_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10_FOK.OrderId, - FillAmount: 100_000_000, // 1 BTC - }, - }, - ), - }, - expectedError: errors.New("IOC / FOK order cannot be matched as a maker order"), - }, `Stateful match validation: maker order cannot be IOC`: { perpetuals: []perptypes.Perpetual{ constants.BtcUsd_100PercentMarginRequirement, @@ -265,7 +231,7 @@ func TestProcessProposerMatches_LongTerm_StatefulValidation_Failure(t *testing.T }, ), }, - expectedError: errors.New("IOC / FOK order cannot be matched as a maker order"), + expectedError: errors.New("IOC order cannot be matched as a maker order"), }, `Stateful order validation: referenced long-term order is for the wrong clob pair`: { perpetuals: []perptypes.Perpetual{ diff --git a/protocol/x/clob/memclob/memclob.go b/protocol/x/clob/memclob/memclob.go index 2b838a440a..f2ae5a1799 100644 --- a/protocol/x/clob/memclob/memclob.go +++ b/protocol/x/clob/memclob/memclob.go @@ -528,17 +528,7 @@ func (m *MemClobPriceTimePriority) PlaceOrder( if order.IsStatefulOrder() { var removalReason types.OrderRemoval_RemovalReason - if errors.Is(err, types.ErrFokOrderCouldNotBeFullyFilled) { - if !order.IsConditionalOrder() { - panic( - fmt.Sprintf( - "PlaceOrder: stateful FOK order must be conditional. Order %+v", - order, - ), - ) - } - removalReason = types.OrderRemoval_REMOVAL_REASON_CONDITIONAL_FOK_COULD_NOT_BE_FULLY_FILLED - } else if errors.Is(err, types.ErrPostOnlyWouldCrossMakerOrder) { + if errors.Is(err, types.ErrPostOnlyWouldCrossMakerOrder) { removalReason = types.OrderRemoval_REMOVAL_REASON_POST_ONLY_WOULD_CROSS_MAKER_ORDER } else if errors.Is(err, types.ErrWouldViolateIsolatedSubaccountConstraints) { removalReason = types.OrderRemoval_REMOVAL_REASON_VIOLATES_ISOLATED_SUBACCOUNT_CONSTRAINTS @@ -820,23 +810,6 @@ func (m *MemClobPriceTimePriority) matchOrder( var matchingErr error - // If this is a fill-or-kill order and it wasn't fully filled, set the matching error - // so that the order is canceled. We check the taker order status since the order may - // have been unsuccessful for other reasons which would be the true root cause - // (e.g. failed a collateralization check). - if !order.IsLiquidation() && - order.MustGetOrder().TimeInForce == types.Order_TIME_IN_FORCE_FILL_OR_KILL && - takerOrderStatus.RemainingQuantums > 0 { - // FOK orders _must_ return an error here if they are not fully filled regardless - // of the reason why they were not fully filled. If an error is not returned here, then - // any partial matches that occurred during matching will be committed to state, and included - // in the operations queue. This violates the invariant of FOK orders that they must be fully - // filled or not filled at all. - // TODO(CLOB-267): Create more granular error types here that indicate why the order was not - // fully filled (i.e. undercollateralized, reduce only resized, etc). - matchingErr = types.ErrFokOrderCouldNotBeFullyFilled - } - // If the order is post only and it's not the rewind step, then it cannot be filled. // Set the matching error so that the order is canceled. // TODO(DEC-998): Determine if allowing post-only orders to match in rewind step is valid. @@ -1458,10 +1431,10 @@ func (m *MemClobPriceTimePriority) validateNewOrder( ) } - // Immediate-or-cancel and fill-or-kill orders may only be filled once. The remaining size becomes unfillable. + // Immediate-or-cancel orders may only be filled once. The remaining size becomes unfillable. // This prevents the case where an IOC order is partially filled multiple times over the course of multiple blocks. if order.RequiresImmediateExecution() && remainingAmount < order.GetBaseQuantums() { - // Prevent IOC/FOK orders from replacing partially filled orders. + // Prevent IOC orders from replacing partially filled orders. if restingOrderExists { return errorsmod.Wrapf( types.ErrInvalidReplacement, diff --git a/protocol/x/clob/memclob/memclob_place_order_test.go b/protocol/x/clob/memclob/memclob_place_order_test.go index 17eed53fbf..51c5a6a250 100644 --- a/protocol/x/clob/memclob/memclob_place_order_test.go +++ b/protocol/x/clob/memclob/memclob_place_order_test.go @@ -2127,67 +2127,7 @@ func TestPlaceOrder_MatchOrders_PreexistingMatches(t *testing.T) { expectedErr: types.ErrInvalidReplacement, }, - "Error: Taker is FOK replacement for partially filled order": { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB21_FOK, - - expectedTotalFilledSize: 5, - expectedOrderStatus: types.InternalError, - expectedExistingMatches: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - matchedQuantums: 5, - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{ - { - Order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - RemainingSize: 5, - }, - }, - expectedOperations: []types.Operation{ - clobtest.NewOrderPlacementOperation( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, - ), - clobtest.NewOrderPlacementOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - ), - clobtest.NewMatchOperation( - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - []types.MakerFill{ - { - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId, - FillAmount: 5, - }, - }, - ), - }, - expectedInternalOperations: []types.InternalOperation{ - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20, - ), - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - ), - types.NewMatchOrdersInternalOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - []types.MakerFill{ - { - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB20.OrderId, - FillAmount: 5, - }, - }, - ), - }, - - expectedErr: types.ErrInvalidReplacement, - }, - "IOC Taker cannot replace unfilled non IOC order": { + "IOC Taker replaces unfilled non IOC order": { placedMatchableOrders: []types.MatchableOrder{ &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, }, @@ -2201,22 +2141,6 @@ func TestPlaceOrder_MatchOrders_PreexistingMatches(t *testing.T) { order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB21_IOC, expectedInternalOperations: []types.InternalOperation{}, - expectedErr: types.ErrInvalidReplacement, - }, - "FOK Taker cannot replace unfilled non IOC order": { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - }, - expectedRemainingAsks: []OrderWithRemainingSize{ - { - Order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - RemainingSize: 10, - }, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB21_FOK, - expectedInternalOperations: []types.InternalOperation{}, - expectedErr: types.ErrInvalidReplacement, }, } @@ -3398,681 +3322,6 @@ func TestPlaceOrder_ImmediateOrCancel(t *testing.T) { } } -func TestPlaceOrder_FillOrKill(t *testing.T) { - ctx, _, _ := sdktest.NewSdkContextWithMultistore() - ctx = ctx.WithIsCheckTx(true) - tests := map[string]struct { - // State. - placedMatchableOrders []types.MatchableOrder - collateralizationCheckFailures map[int]map[satypes.SubaccountId]satypes.UpdateResult - // (SubaccountId, ClobPairId) tuples to state position size in quantums. - statePositionSizes map[satypes.SubaccountId]map[uint32]int64 - - // Parameters. - order types.Order - - // Expectations. - expectedErr error - expectedOrderStatus types.OrderStatus - expectedCollatCheck []expectedMatch - expectedRemainingBids []OrderWithRemainingSize - expectedRemainingAsks []OrderWithRemainingSize - expectedOperations []types.Operation - expectedInternalOperations []types.InternalOperation - }{ - `Can place a fill-or-kill order on an empty book and it's canceled`: { - placedMatchableOrders: []types.MatchableOrder{}, - collateralizationCheckFailures: map[int]map[satypes.SubaccountId]satypes.UpdateResult{}, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedRemainingBids: []OrderWithRemainingSize{}, - expectedRemainingAsks: []OrderWithRemainingSize{}, - }, - `An fill-Internalor-kill order can beInternal fully matched`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - - expectedOrderStatus: types.Success, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 15, - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - matchedQuantums: 5, - }, - { - makerOrder: &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - matchedQuantums: 5, - }, - }, - expectedOperations: []types.Operation{ - clobtest.NewOrderPlacementOperation( - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - ), - clobtest.NewOrderPlacementOperation( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - ), - clobtest.NewOrderPlacementOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - ), - clobtest.NewMatchOperation( - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - []types.MakerFill{ - { - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32.OrderId, - FillAmount: 5, - }, - { - MakerOrderId: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22.OrderId, - FillAmount: 5, - }, - }, - ), - }, - expectedInternalOperations: []types.InternalOperation{ - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - ), - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - ), - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - ), - types.NewMatchOrdersInternalOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - []types.MakerFill{ - { - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32.OrderId, - FillAmount: 5, - }, - { - MakerOrderId: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22.OrderId, - FillAmount: 5, - }, - }, - ), - }, - }, - `If an fill-or-kill order is only partially matched, the whole order is canceled`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Alice_Num0_Id4_Clob1_Buy25_Price5_GTB20, - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - RemainingSize: 5, - }, - { - Order: constants.Order_Alice_Num0_Id4_Clob1_Buy25_Price5_GTB20, - RemainingSize: 25, - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - matchedQuantums: 5, - }, - }, - }, - `A partially matched fill-or-kill buy order is canceled, and all crossing maker orders that - failed collateralization checks are removed`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15, - &constants.Order_Alice_Num0_Id3_Clob1_Sell5_Price10_GTB15, - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - }, - collateralizationCheckFailures: map[int]map[satypes.SubaccountId]satypes.UpdateResult{ - 0: { - constants.Alice_Num0: satypes.NewlyUndercollateralized, - }, - 1: { - constants.Alice_Num0: satypes.StillUndercollateralized, - }, - }, - - order: constants.Order_Bob_Num0_Id1_Clob1_Buy20_Price35_GTB22_FOK, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedRemainingBids: []OrderWithRemainingSize{}, - expectedRemainingAsks: []OrderWithRemainingSize{ - { - Order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - RemainingSize: 10, - }, - }, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15, - takerOrder: &constants.Order_Bob_Num0_Id1_Clob1_Buy20_Price35_GTB22_FOK, - matchedQuantums: 5, - }, - { - makerOrder: &constants.Order_Alice_Num0_Id3_Clob1_Sell5_Price10_GTB15, - takerOrder: &constants.Order_Bob_Num0_Id1_Clob1_Buy20_Price35_GTB22_FOK, - matchedQuantums: 5, - }, - { - makerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20, - takerOrder: &constants.Order_Bob_Num0_Id1_Clob1_Buy20_Price35_GTB22_FOK, - matchedQuantums: 10, - }, - }, - }, - `A fill-or-kill reduce only order with order size less than position size fully matched`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: 100_000, // position larger than order - }, - }, - - expectedOrderStatus: types.Success, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 15, - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 5, - }, - { - makerOrder: &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 5, - }, - }, - expectedOperations: []types.Operation{ - clobtest.NewOrderPlacementOperation( - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - ), - clobtest.NewOrderPlacementOperation( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - ), - clobtest.NewOrderPlacementOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - ), - clobtest.NewMatchOperation( - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - []types.MakerFill{ - { - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32.OrderId, - FillAmount: 5, - }, - { - MakerOrderId: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22.OrderId, - FillAmount: 5, - }, - }, - ), - }, - expectedInternalOperations: []types.InternalOperation{ - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - ), - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - ), - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - ), - types.NewMatchOrdersInternalOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - []types.MakerFill{ - { - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32.OrderId, - FillAmount: 5, - }, - { - MakerOrderId: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22.OrderId, - FillAmount: 5, - }, - }, - ), - }, - }, - `A fill-or-kill reduce only order with size equal to position size and order fully matched`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: 10, // position equal to order - }, - }, - - expectedOrderStatus: types.Success, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 15, - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 5, - }, - { - makerOrder: &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 5, - }, - }, - expectedOperations: []types.Operation{ - clobtest.NewOrderPlacementOperation( - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - ), - clobtest.NewOrderPlacementOperation( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - ), - clobtest.NewOrderPlacementOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - ), - clobtest.NewMatchOperation( - &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - []types.MakerFill{ - { - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32.OrderId, - FillAmount: 5, - }, - { - MakerOrderId: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22.OrderId, - FillAmount: 5, - }, - }, - ), - }, - expectedInternalOperations: []types.InternalOperation{ - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - ), - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - ), - types.NewShortTermOrderPlacementInternalOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - ), - types.NewMatchOrdersInternalOperation( - constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - []types.MakerFill{ - { - MakerOrderId: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32.OrderId, - FillAmount: 5, - }, - { - MakerOrderId: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22.OrderId, - FillAmount: 5, - }, - }, - ), - }, - }, - `A fill-or-kill reduce only order with order size less than position size results in the order being partially filled - and the remaining size is canceled`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Alice_Num0_Id4_Clob1_Buy25_Price5_GTB20, - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: 100_000, // position larger than order - }, - }, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - RemainingSize: 5, - }, - { - Order: constants.Order_Alice_Num0_Id4_Clob1_Buy25_Price5_GTB20, - RemainingSize: 25, - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK, - matchedQuantums: 5, - }, - }, - }, - // FOK+RO order to sell 10, but position is 5. Resulting in FOK order not filled - // and a cancellation. Should have been full match otherwise. - `A fill-or-kill reduce only order with order size greater than position size gets reduced and fails`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: 5, // position smaller than order - }, - }, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 20, // untouched size - }, - { - Order: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - RemainingSize: 5, // untouched size - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 5, // reduction min(ordersize, position) = 5 - }, - }, - }, - `A fill-or-kill reduce only order on the same side as the user's position gets cancelled`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: -85, // same side as sell - }, - }, - - expectedErr: types.ErrReduceOnlyWouldIncreasePositionSize, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 20, - }, - { - Order: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - RemainingSize: 5, - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{}, // no matches since order doesn't go through - }, - // FOK+RO order to sell 10, but failed collateralization causes it to be cancelled. - // Should have been full match otherwise. - `A fill-or-kill reduce only order with order size less than position size fails collateralization - results in the order being cancelled`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: 100_000, // position greater than order - }, - }, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedOrderStatus: types.Undercollateralized, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 20, // untouched size - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 10, - }, - }, - collateralizationCheckFailures: map[int]map[satypes.SubaccountId]satypes.UpdateResult{ - 0: { - constants.Alice_Num1: satypes.NewlyUndercollateralized, - }, - }, - }, - // FOK+RO order to sell 10 with position size of 5 fails collateralization causes it to be cancelled. - // Should have been full match otherwise. - `A fill-or-kill reduce only order with order size greater than position size fails collateralization - results in the order being cancelled`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: 5, // position smaller than order - }, - }, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedOrderStatus: types.Undercollateralized, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 20, // untouched size - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 5, // reduction min(ordersize, position) = 5 - }, - }, - collateralizationCheckFailures: map[int]map[satypes.SubaccountId]satypes.UpdateResult{ - 0: { - constants.Alice_Num1: satypes.NewlyUndercollateralized, - }, - }, - }, - `A fill-or-kill order that fails collateralization results in the order being cancelled`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: 100_000, - }, - }, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedOrderStatus: types.Undercollateralized, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 20, // untouched size - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 10, - }, - }, - collateralizationCheckFailures: map[int]map[satypes.SubaccountId]satypes.UpdateResult{ - 0: { - constants.Alice_Num1: satypes.NewlyUndercollateralized, - }, - }, - }, - `A fill-or-kill order that matches one maker order then - fails collateralization when matching the second maker order results in the order being cancelled`: { - placedMatchableOrders: []types.MatchableOrder{ - &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - }, - - order: constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - statePositionSizes: map[satypes.SubaccountId]map[uint32]int64{ - constants.Alice_Num1: { - constants.ClobPair_Eth.Id: 100_000, - }, - }, - - expectedErr: types.ErrFokOrderCouldNotBeFullyFilled, - expectedOrderStatus: types.Undercollateralized, - expectedRemainingBids: []OrderWithRemainingSize{ - { - Order: constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - RemainingSize: 20, // untouched size - }, - { - Order: constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - RemainingSize: 5, // untouched size - }, - }, - expectedRemainingAsks: []OrderWithRemainingSize{}, - expectedCollatCheck: []expectedMatch{ - { - makerOrder: &constants.Order_Bob_Num0_Id11_Clob1_Buy5_Price40_GTB32, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 5, - }, - { - makerOrder: &constants.Order_Bob_Num0_Id4_Clob1_Buy20_Price35_GTB22, - takerOrder: &constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK_RO, - matchedQuantums: 5, - }, - }, - collateralizationCheckFailures: map[int]map[satypes.SubaccountId]satypes.UpdateResult{ - 1: { - constants.Alice_Num1: satypes.NewlyUndercollateralized, - }, - }, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // Setup memclob state and test expectations. - addOrderToOrderbookSize := satypes.BaseQuantums(0) - order := tc.order - expectedFilledSize := order.GetBaseQuantums() - expectedMatches := tc.expectedCollatCheck - if !tc.expectedOrderStatus.IsSuccess() || tc.expectedErr != nil { - expectedFilledSize = 0 - expectedMatches = []expectedMatch{} - } - require.Equal(t, types.Order_TIME_IN_FORCE_FILL_OR_KILL, order.TimeInForce) - - getStatePositionFn := func( - subaccountId satypes.SubaccountId, - clobPairId types.ClobPairId, - ) (positionSizeQuantums *big.Int) { - return big.NewInt(tc.statePositionSizes[subaccountId][clobPairId.ToUint32()]) - } - if tc.statePositionSizes == nil { - getStatePositionFn = constants.GetStatePosition_ZeroPositionSize - } - - memclob, fakeMemClobKeeper, expectedNumCollateralizationChecks, numCollateralChecks := placeOrderTestSetup( - t, - ctx, - tc.placedMatchableOrders, - &order, - tc.expectedCollatCheck, - tc.expectedOrderStatus, - addOrderToOrderbookSize, - tc.expectedErr, - tc.collateralizationCheckFailures, - getStatePositionFn, - ) - - // Run the test case and verify expectations. - offchainUpdates := placeOrderAndVerifyExpectations( - t, - ctx, - memclob, - order, - numCollateralChecks, - expectedFilledSize, - expectedFilledSize, - tc.expectedOrderStatus, - tc.expectedErr, - expectedNumCollateralizationChecks, - tc.expectedRemainingBids, - tc.expectedRemainingAsks, - expectedMatches, - fakeMemClobKeeper, - ) - - // Note: This length check exists here, because testify/require considers a nil and empty slice to be unequal. - // For this reason, if there are no expected operations, we do a `Empty` check instead of an `Equal` check. - - // TODO(CLOB-656): Update test assertions to assert on operations queue. - - assertPlaceOrderOffchainMessages( - t, - ctx, - offchainUpdates, - order, - tc.placedMatchableOrders, - tc.collateralizationCheckFailures, - tc.expectedErr, - expectedFilledSize, - tc.expectedOrderStatus, - []expectedMatch{}, - expectedMatches, - []types.OrderId{}, - false, - false, - ) - }) - } -} - func TestPlaceOrder_Telemetry(t *testing.T) { m, err := telemetry.New(telemetry.Config{ Enabled: true, diff --git a/protocol/x/clob/simulation/place_order.go b/protocol/x/clob/simulation/place_order.go index b6139a5e4c..8912a0b84d 100644 --- a/protocol/x/clob/simulation/place_order.go +++ b/protocol/x/clob/simulation/place_order.go @@ -31,10 +31,9 @@ var ( orderLevels = big.NewInt(4) weightedSupportedTimeInForces = map[types.Order_TimeInForce]int{ - types.Order_TIME_IN_FORCE_UNSPECIFIED: 3, - types.Order_TIME_IN_FORCE_IOC: 1, - types.Order_TIME_IN_FORCE_POST_ONLY: 1, - types.Order_TIME_IN_FORCE_FILL_OR_KILL: 1, + types.Order_TIME_IN_FORCE_UNSPECIFIED: 3, + types.Order_TIME_IN_FORCE_IOC: 1, + types.Order_TIME_IN_FORCE_POST_ONLY: 1, } weightedReduceOnly = map[bool]int{ @@ -173,7 +172,6 @@ func SimulateMsgPlaceOrder( if err != nil { switch { case errors.Is(err, satypes.ErrIntegerOverflow), - errors.Is(err, types.ErrFokOrderCouldNotBeFullyFilled), errors.Is(err, types.ErrPostOnlyWouldCrossMakerOrder), errors.Is(err, types.ErrWouldViolateIsolatedSubaccountConstraints): // These errors are expected, and can occur during normal operation. We shouldn't panic on them. diff --git a/protocol/x/clob/types/clob_keeper.go b/protocol/x/clob/types/clob_keeper.go index 8fc8086eed..da83594e6a 100644 --- a/protocol/x/clob/types/clob_keeper.go +++ b/protocol/x/clob/types/clob_keeper.go @@ -159,4 +159,5 @@ type ClobKeeper interface { offchainUpdates *OffchainUpdates, ) MigratePruneableOrders(ctx sdk.Context) + GetAllStatefulOrders(ctx sdk.Context) []Order } diff --git a/protocol/x/clob/types/errors.go b/protocol/x/clob/types/errors.go index bf71b05325..61929674f4 100644 --- a/protocol/x/clob/types/errors.go +++ b/protocol/x/clob/types/errors.go @@ -220,6 +220,11 @@ var ( 47, "CLOB has not been initialized", ) + ErrDeprecatedField = errorsmod.Register( + ModuleName, + 48, + "This field has been deprecated", + ) // Liquidations errors. ErrInvalidLiquidationsConfig = errorsmod.Register( @@ -358,7 +363,7 @@ var ( ErrImmediateExecutionOrderAlreadyFilled = errorsmod.Register( ModuleName, 2004, - "IOC/FOK order is already filled, remaining size is cancelled.", + "IOC order is already filled, remaining size is cancelled.", ) ErrWouldViolateIsolatedSubaccountConstraints = errorsmod.Register( ModuleName, @@ -518,7 +523,7 @@ var ( ErrReduceOnlyDisabled = errorsmod.Register( ModuleName, 9003, - "Reduce-only is currently disabled for non-FOK/IOC orders", + "Reduce-only is currently disabled for non-IOC orders", ) // Equity tier limit errors. diff --git a/protocol/x/clob/types/match_with_orders.go b/protocol/x/clob/types/match_with_orders.go index 0baa8dbcb3..78ba9248eb 100644 --- a/protocol/x/clob/types/match_with_orders.go +++ b/protocol/x/clob/types/match_with_orders.go @@ -75,7 +75,7 @@ func (match MatchWithOrders) Validate() error { // Make sure the maker order is not an IOC order. unwrappedMakerOrder := makerOrder.MustGetOrder() if unwrappedMakerOrder.RequiresImmediateExecution() { - return errors.New("IOC / FOK order cannot be matched as a maker order") + return errors.New("IOC order cannot be matched as a maker order") } return nil } diff --git a/protocol/x/clob/types/match_with_orders_test.go b/protocol/x/clob/types/match_with_orders_test.go index f6a13961fd..00083ff7be 100644 --- a/protocol/x/clob/types/match_with_orders_test.go +++ b/protocol/x/clob/types/match_with_orders_test.go @@ -107,20 +107,7 @@ func TestPerformStatelessMatchOrdersValidation(t *testing.T) { }, takerOrder: &constants.Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB10, fillAmount: 100_000_000, // 1 BTC. - expectedError: errors.New("IOC / FOK order cannot be matched as a maker order"), - }, - "Stateless match validation: maker order is a FOK order": { - makerOrder: &types.Order{ - OrderId: types.OrderId{SubaccountId: constants.Dave_Num0, ClientId: 0, ClobPairId: 0}, - Side: types.Order_SIDE_SELL, - Quantums: 100_000_000, - Subticks: 50_000_000_000, - GoodTilOneof: &types.Order_GoodTilBlock{GoodTilBlock: 10}, - TimeInForce: types.Order_TIME_IN_FORCE_FILL_OR_KILL, - }, - takerOrder: &constants.Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB10, - fillAmount: 100_000_000, // 1 BTC. - expectedError: errors.New("IOC / FOK order cannot be matched as a maker order"), + expectedError: errors.New("IOC order cannot be matched as a maker order"), }, "Stateless match validation: taker order is an IOC order": { makerOrder: &constants.Order_Carl_Num0_Id0_Clob0_Buy1BTC_Price50000_GTB10, diff --git a/protocol/x/clob/types/message_place_order.go b/protocol/x/clob/types/message_place_order.go index 09b2efcc4b..b8fb88fb85 100644 --- a/protocol/x/clob/types/message_place_order.go +++ b/protocol/x/clob/types/message_place_order.go @@ -28,6 +28,11 @@ func (msg *MsgPlaceOrder) ValidateBasic() (err error) { } }() + // Check for deprecated fields. + if msg.Order.TimeInForce == Order_TIME_IN_FORCE_FILL_OR_KILL { + return errorsmod.Wrapf(ErrDeprecatedField, "Fill-or-kill has been deprecated") + } + err = msg.Order.OrderId.SubaccountId.Validate() if err != nil { return err @@ -76,7 +81,7 @@ func (msg *MsgPlaceOrder) ValidateBasic() (err error) { } if msg.Order.ReduceOnly && !msg.Order.RequiresImmediateExecution() { - return errorsmod.Wrapf(ErrReduceOnlyDisabled, "reduce only orders must be short term IOC or FOK orders") + return errorsmod.Wrapf(ErrReduceOnlyDisabled, "reduce only orders must be short term IOC orders") } if msg.Order.Subticks == uint64(0) { diff --git a/protocol/x/clob/types/message_place_order_test.go b/protocol/x/clob/types/message_place_order_test.go index ebd0db48a4..c3aab9c86e 100644 --- a/protocol/x/clob/types/message_place_order_test.go +++ b/protocol/x/clob/types/message_place_order_test.go @@ -193,26 +193,6 @@ func TestMsgPlaceOrder_ValidateBasic(t *testing.T) { }, err: ErrLongTermOrdersCannotRequireImmediateExecution, }, - "long-term: cannot be FOK": { - msg: MsgPlaceOrder{ - Order: Order{ - OrderId: OrderId{ - SubaccountId: satypes.SubaccountId{ - Owner: sample.AccAddress(), - Number: uint32(0), - }, - OrderFlags: OrderIdFlags_LongTerm, - }, - Side: Order_SIDE_BUY, - Quantums: uint64(42), - GoodTilOneof: &Order_GoodTilBlockTime{ - GoodTilBlockTime: uint32(100), - }, - TimeInForce: Order_TIME_IN_FORCE_FILL_OR_KILL, - }, - }, - err: ErrLongTermOrdersCannotRequireImmediateExecution, - }, "zero subticks": { msg: MsgPlaceOrder{ Order: Order{ @@ -245,7 +225,7 @@ func TestMsgPlaceOrder_ValidateBasic(t *testing.T) { }, }, }, - "success with fill-or-kill order": { + "fill-or-kill orders are deprecated": { msg: MsgPlaceOrder{ Order: Order{ OrderId: OrderId{ @@ -262,25 +242,7 @@ func TestMsgPlaceOrder_ValidateBasic(t *testing.T) { ReduceOnly: false, }, }, - }, - "short-term FOK reduce-only success": { - msg: MsgPlaceOrder{ - Order: Order{ - OrderId: OrderId{ - SubaccountId: satypes.SubaccountId{ - Owner: sample.AccAddress(), - Number: uint32(0), - }, - OrderFlags: OrderIdFlags_ShortTerm, - }, - Side: Order_SIDE_BUY, - Quantums: uint64(42), - GoodTilOneof: &Order_GoodTilBlock{GoodTilBlock: uint32(100)}, - Subticks: uint64(10), - TimeInForce: Order_TIME_IN_FORCE_FILL_OR_KILL, - ReduceOnly: true, - }, - }, + err: ErrDeprecatedField, }, "short-term IOC reduce-only success": { msg: MsgPlaceOrder{ diff --git a/protocol/x/clob/types/operations_to_propose_test.go b/protocol/x/clob/types/operations_to_propose_test.go index 14b4a54f49..f01ea4d092 100644 --- a/protocol/x/clob/types/operations_to_propose_test.go +++ b/protocol/x/clob/types/operations_to_propose_test.go @@ -68,7 +68,7 @@ func TestClearOperationsQueue(t *testing.T) { } func TestMustAddShortTermOrderTxBytes(t *testing.T) { - shortTermOrder1 := constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK + shortTermOrder1 := constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC shortTermOrder2 := constants.Order_Carl_Num1_Id1_Clob0_Buy1kQtBTC_Price50000 shortTermOrder3 := constants.Order_Carl_Num1_Id0_Clob0_Buy1BTC_Price50000 shortTermOrder4 := constants.Order_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB15 @@ -166,7 +166,7 @@ func TestMustAddShortTermOrderTxBytes_PanicsOnOrderInShortTermOrderHashToTxBytes } func TestMustAddShortTermOrderPlacementToOperationsQueue(t *testing.T) { - shortTermOrder1 := constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK + shortTermOrder1 := constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC shortTermOrder2 := constants.Order_Carl_Num1_Id1_Clob0_Buy1kQtBTC_Price50000 shortTermOrder3 := constants.Order_Carl_Num1_Id0_Clob0_Buy1BTC_Price50000 shortTermOrder4 := constants.Order_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB15 @@ -252,7 +252,7 @@ func TestMustAddShortTermOrderPlacementToOperationsQueue_PanicsOnOrderNotInShort } func TestRemoveShortTermOrderTxBytes(t *testing.T) { - shortTermOrder1 := constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK + shortTermOrder1 := constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC shortTermOrder2 := constants.Order_Carl_Num1_Id1_Clob0_Buy1kQtBTC_Price50000 shortTermOrder3 := constants.Order_Carl_Num1_Id0_Clob0_Buy1BTC_Price50000 shortTermOrder4 := constants.Order_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB15 @@ -415,7 +415,7 @@ func TestMustAddStatefulOrderPlacementToOperationsQueue_PanicsOnOrderInOrderHash } func TestMustAddMatchToOperationsQueue(t *testing.T) { - shortTermOrder1 := constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_FOK + shortTermOrder1 := constants.Order_Carl_Num0_Id0_Clob0_Buy05BTC_Price50000_GTB10_IOC shortTermOrder2 := constants.Order_Carl_Num1_Id1_Clob0_Buy1kQtBTC_Price50000 shortTermOrder3 := constants.Order_Carl_Num0_Id0_Clob0_Sell1BTC_Price500000_GTB10 longTermOrder1 := constants.LongTermOrder_Dave_Num0_Id0_Clob0_Sell1BTC_Price50000_GTBT10 diff --git a/protocol/x/clob/types/order.pb.go b/protocol/x/clob/types/order.pb.go index 0cd485c984..f1e239d1bd 100644 --- a/protocol/x/clob/types/order.pb.go +++ b/protocol/x/clob/types/order.pb.go @@ -78,10 +78,9 @@ const ( // any newly-placed post only orders that would cross with other maker // orders. Order_TIME_IN_FORCE_POST_ONLY Order_TimeInForce = 2 - // TIME_IN_FORCE_FILL_OR_KILL enforces that an order will either be filled - // completely and immediately by maker orders on the book or canceled if the - // entire amount can‘t be matched. - Order_TIME_IN_FORCE_FILL_OR_KILL Order_TimeInForce = 3 + // TIME_IN_FORCE_FILL_OR_KILL has been deprecated and will be removed in + // future versions. + Order_TIME_IN_FORCE_FILL_OR_KILL Order_TimeInForce = 3 // Deprecated: Do not use. ) var Order_TimeInForce_name = map[int32]string{ @@ -826,69 +825,70 @@ func init() { func init() { proto.RegisterFile("dydxprotocol/clob/order.proto", fileDescriptor_673c6f4faa93736b) } var fileDescriptor_673c6f4faa93736b = []byte{ - // 989 bytes of a gzipped FileDescriptorProto + // 996 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x56, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0x8e, 0xdb, 0x6c, 0x9b, 0x9e, 0xfc, 0xac, 0x3b, 0xdd, 0x05, 0xb7, 0xa5, 0x69, 0x88, 0x50, - 0x29, 0x42, 0x24, 0xa2, 0xac, 0x90, 0x10, 0xe2, 0x62, 0xdb, 0x26, 0xaa, 0xd5, 0xb4, 0x0e, 0xb6, - 0x8b, 0xd4, 0x15, 0x62, 0xe4, 0xd8, 0x53, 0x77, 0xb4, 0x13, 0x4f, 0xb0, 0xc7, 0xa8, 0xb9, 0xe3, - 0x11, 0x78, 0x09, 0xde, 0x82, 0x07, 0xd8, 0xcb, 0xbd, 0xe4, 0x0a, 0xa1, 0xf6, 0x19, 0x10, 0xb7, - 0x68, 0xc6, 0x6e, 0xea, 0x74, 0xb7, 0x42, 0x68, 0x6f, 0xb8, 0xf3, 0x7c, 0xe7, 0x3b, 0xdf, 0x9c, - 0xdf, 0x91, 0x61, 0x2b, 0x98, 0x06, 0x57, 0x93, 0x98, 0x0b, 0xee, 0x73, 0xd6, 0xf5, 0x19, 0x1f, - 0x75, 0x79, 0x1c, 0x90, 0xb8, 0xa3, 0x30, 0xb4, 0x5a, 0x34, 0x77, 0xa4, 0x79, 0xe3, 0x49, 0xc8, - 0x43, 0xae, 0xa0, 0xae, 0xfc, 0xca, 0x88, 0x1b, 0x9f, 0xcc, 0xe9, 0x24, 0xe9, 0xc8, 0xf3, 0x7d, - 0x9e, 0x46, 0x22, 0x29, 0x7c, 0x67, 0xd4, 0xf6, 0x6f, 0x1a, 0x2c, 0x5b, 0xf2, 0x0e, 0x33, 0x40, - 0xdf, 0x42, 0xfd, 0xce, 0x8e, 0x69, 0x60, 0x68, 0x2d, 0x6d, 0xb7, 0xba, 0xb7, 0xd3, 0x99, 0xbb, - 0xb7, 0x20, 0xd7, 0x71, 0x66, 0xdf, 0x66, 0xb0, 0x5f, 0x7e, 0xf5, 0xc7, 0x76, 0xc9, 0xae, 0x25, - 0x05, 0x0c, 0x6d, 0xc2, 0x8a, 0xcf, 0x28, 0xc9, 0xe4, 0x16, 0x5a, 0xda, 0xee, 0xb2, 0x5d, 0xc9, - 0x00, 0x33, 0x40, 0xdb, 0x50, 0x55, 0xe9, 0xe1, 0x0b, 0xe6, 0x85, 0x89, 0xb1, 0xd8, 0xd2, 0x76, - 0xeb, 0x36, 0x28, 0xa8, 0x2f, 0x11, 0xd4, 0x82, 0x9a, 0xcc, 0x12, 0x4f, 0x3c, 0x1a, 0x4b, 0x81, - 0x72, 0xc6, 0x90, 0xd8, 0xd0, 0xa3, 0xb1, 0x19, 0xb4, 0x7f, 0x80, 0x2d, 0x15, 0x7d, 0xd2, 0xa7, - 0x8c, 0x91, 0xe0, 0x30, 0x8d, 0x69, 0x14, 0x0e, 0x3c, 0x41, 0x12, 0xb1, 0xcf, 0xb8, 0xff, 0x12, - 0x7d, 0x03, 0x2b, 0xd9, 0x1d, 0x34, 0x48, 0x0c, 0xad, 0xb5, 0xb8, 0x5b, 0xdd, 0xdb, 0xe8, 0xbc, - 0x51, 0xc7, 0x4e, 0x5e, 0x82, 0x3c, 0x87, 0x0a, 0xcf, 0x8e, 0x49, 0xfb, 0x05, 0xac, 0x0f, 0xb9, - 0x20, 0x91, 0xa0, 0x1e, 0x63, 0xd3, 0x61, 0x9c, 0x46, 0xde, 0x88, 0x91, 0xec, 0xca, 0x77, 0xd5, - 0x26, 0xd0, 0x50, 0x26, 0x19, 0xba, 0x23, 0x3c, 0x41, 0x64, 0x41, 0x2e, 0x28, 0x63, 0xd8, 0x1b, - 0xcb, 0xf2, 0xa9, 0xf2, 0x97, 0x6d, 0x90, 0xd0, 0x73, 0x85, 0xa0, 0x3d, 0x78, 0x3a, 0xc9, 0x63, - 0xc0, 0x23, 0x99, 0x1f, 0xbe, 0x24, 0x34, 0xbc, 0x14, 0xaa, 0xb4, 0x75, 0x7b, 0xed, 0xd6, 0xa8, - 0x72, 0x3f, 0x52, 0xa6, 0xf6, 0xf7, 0xb0, 0xa9, 0xd4, 0x2f, 0x52, 0xa6, 0xae, 0x73, 0xe9, 0x98, - 0x38, 0x8c, 0xfa, 0xe4, 0x3b, 0x8f, 0xa5, 0xe4, 0x5d, 0x93, 0xf8, 0x55, 0x83, 0xf7, 0x06, 0x3c, - 0x0a, 0x5d, 0x12, 0x8f, 0x15, 0x67, 0xc8, 0x3c, 0x9f, 0x8c, 0x49, 0x24, 0xd0, 0x33, 0x78, 0xa4, - 0x68, 0xf9, 0x18, 0x19, 0x0f, 0xa9, 0xe6, 0x9a, 0x19, 0x19, 0x9d, 0xc1, 0xe3, 0xc9, 0xad, 0x04, - 0xa6, 0x51, 0x40, 0xae, 0x54, 0x72, 0x6f, 0x8c, 0xa1, 0xf2, 0x77, 0x63, 0x2f, 0x4a, 0x3c, 0x5f, - 0x50, 0x1e, 0x29, 0x29, 0x1a, 0x85, 0xb9, 0x5a, 0x63, 0x26, 0x62, 0x4a, 0x8d, 0xf6, 0x5f, 0x1a, - 0xac, 0x1f, 0xf0, 0x28, 0xa0, 0x92, 0xeb, 0xb1, 0xff, 0x71, 0xa8, 0xe8, 0x18, 0xea, 0x22, 0xa6, - 0x61, 0x28, 0x7b, 0xa2, 0x44, 0x17, 0xff, 0x8b, 0xa8, 0x5d, 0xcb, 0x9d, 0xb3, 0xbc, 0xff, 0x5e, - 0x82, 0x47, 0xca, 0x84, 0xbe, 0x86, 0xca, 0x6d, 0xa3, 0xf3, 0x34, 0xff, 0xbd, 0xcf, 0xcb, 0x79, - 0x9f, 0xd1, 0xe7, 0x50, 0x4e, 0x68, 0x40, 0x54, 0x7e, 0x8d, 0xbd, 0xad, 0x87, 0x1c, 0x3b, 0x0e, - 0x0d, 0x88, 0xad, 0xa8, 0x68, 0x03, 0x2a, 0x3f, 0xa6, 0x5e, 0x24, 0xd2, 0x71, 0xb6, 0xda, 0x65, - 0x7b, 0x76, 0x96, 0xb6, 0x24, 0x1d, 0x09, 0xea, 0xbf, 0x4c, 0xd4, 0x52, 0x97, 0xed, 0xd9, 0x19, - 0xed, 0x40, 0x23, 0xe4, 0x3c, 0xc0, 0x82, 0xb2, 0x6c, 0xc6, 0x8d, 0x47, 0x72, 0xb8, 0x8f, 0x4a, - 0x76, 0x4d, 0xe2, 0x2e, 0x65, 0xd9, 0x66, 0x77, 0x61, 0x6d, 0x9e, 0x87, 0x05, 0x1d, 0x13, 0x63, - 0x49, 0x3e, 0x32, 0x47, 0x25, 0x5b, 0x2f, 0x92, 0xe5, 0xcc, 0xa3, 0x23, 0xa8, 0x4b, 0x06, 0xa6, - 0x11, 0xbe, 0xe0, 0xb1, 0x4f, 0x8c, 0x65, 0x95, 0xcc, 0x47, 0x0f, 0x26, 0x23, 0xbd, 0xcc, 0xa8, - 0x2f, 0xb9, 0x76, 0x55, 0xdc, 0x1d, 0xe4, 0x9e, 0xc6, 0x24, 0x48, 0x7d, 0x82, 0x79, 0xc4, 0xa6, - 0x46, 0xa5, 0xa5, 0xed, 0x56, 0x6c, 0xc8, 0x20, 0x2b, 0x62, 0x53, 0xf4, 0x31, 0x3c, 0xce, 0x9f, - 0xbd, 0x31, 0x11, 0x5e, 0xe0, 0x09, 0xcf, 0x58, 0x51, 0x1b, 0xda, 0xc8, 0xe0, 0x93, 0x1c, 0x45, - 0x27, 0xd0, 0xf0, 0x6f, 0xa7, 0x12, 0x8b, 0xe9, 0x84, 0x18, 0xa0, 0x82, 0xda, 0x79, 0x30, 0xa8, - 0xd9, 0x10, 0xbb, 0xd3, 0x09, 0xb1, 0xeb, 0x7e, 0xf1, 0x88, 0x8e, 0xa1, 0xed, 0xdf, 0x0d, 0x39, - 0xce, 0xfa, 0x7d, 0x3b, 0x4c, 0xb3, 0x8a, 0x57, 0x55, 0xc5, 0xb7, 0xfd, 0x7b, 0xeb, 0xe0, 0x66, - 0x3c, 0x27, 0xa7, 0xb5, 0xbf, 0x82, 0xb2, 0x6c, 0x27, 0x7a, 0x02, 0xba, 0x63, 0x1e, 0xf6, 0xf0, - 0xd9, 0xa9, 0x33, 0xec, 0x1d, 0x98, 0x7d, 0xb3, 0x77, 0xa8, 0x97, 0x50, 0x0d, 0x2a, 0x0a, 0xdd, - 0x3f, 0x3b, 0xd7, 0x35, 0x54, 0x87, 0x15, 0x75, 0x72, 0x7a, 0x83, 0x81, 0xbe, 0xd0, 0xfe, 0x59, - 0x83, 0x6a, 0xa1, 0x7a, 0x68, 0x0b, 0xd6, 0x5d, 0xf3, 0xa4, 0x87, 0xcd, 0x53, 0xdc, 0xb7, 0xec, - 0x83, 0xfb, 0x5a, 0x4f, 0x61, 0x75, 0xde, 0x6c, 0x5a, 0x07, 0xba, 0x86, 0x36, 0xe1, 0xfd, 0x79, - 0x78, 0x68, 0x39, 0x2e, 0xb6, 0x4e, 0x07, 0xe7, 0xfa, 0x02, 0x6a, 0xc2, 0xc6, 0xbc, 0xb1, 0x6f, - 0x0e, 0x06, 0xd8, 0xb2, 0xf1, 0xb1, 0x39, 0x18, 0xe8, 0x8b, 0xed, 0x31, 0xd4, 0xe7, 0x4a, 0x25, - 0x1d, 0x0e, 0xac, 0xd3, 0x43, 0xd3, 0x35, 0xad, 0x53, 0xec, 0x9e, 0x0f, 0xef, 0x07, 0xf1, 0x01, - 0x18, 0xf7, 0xec, 0x8e, 0x6b, 0x0d, 0xf1, 0xc0, 0x72, 0x1c, 0x5d, 0x7b, 0x8b, 0xb7, 0xfb, 0xfc, - 0xb8, 0x87, 0x87, 0xb6, 0xd5, 0x37, 0x5d, 0x7d, 0x61, 0x5f, 0x2f, 0x4c, 0x2d, 0x8f, 0x08, 0xbf, - 0x68, 0x13, 0x58, 0x7b, 0xcb, 0x7a, 0xa2, 0x0f, 0xa1, 0x36, 0xf7, 0x72, 0x6b, 0x6a, 0x2e, 0xaa, - 0xa3, 0xbb, 0x17, 0x1b, 0x7d, 0x0a, 0xab, 0xe2, 0xce, 0xb3, 0xf0, 0xb2, 0xd4, 0x6d, 0xbd, 0x60, - 0x50, 0x0b, 0xbe, 0x3f, 0x7c, 0x75, 0xdd, 0xd4, 0x5e, 0x5f, 0x37, 0xb5, 0x3f, 0xaf, 0x9b, 0xda, - 0x2f, 0x37, 0xcd, 0xd2, 0xeb, 0x9b, 0x66, 0xe9, 0xf7, 0x9b, 0x66, 0xe9, 0xc5, 0x97, 0x21, 0x15, - 0x97, 0xe9, 0xa8, 0xe3, 0xf3, 0x71, 0x77, 0xee, 0x87, 0xe0, 0xa7, 0x67, 0x9f, 0xf9, 0x97, 0x1e, - 0x8d, 0xba, 0x33, 0xe4, 0x2a, 0xfb, 0xd9, 0x90, 0x03, 0x98, 0x8c, 0x96, 0x14, 0xfc, 0xc5, 0x3f, - 0x01, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x0b, 0xbd, 0x8a, 0x8e, 0x08, 0x00, 0x00, + 0x14, 0x8e, 0xdb, 0xec, 0x36, 0x3d, 0xf9, 0x59, 0xef, 0x74, 0x17, 0xdc, 0x94, 0xa6, 0xc1, 0x42, + 0xa5, 0x08, 0x91, 0x88, 0xb2, 0x42, 0x42, 0x88, 0x8b, 0x6d, 0x9b, 0xa8, 0x56, 0xd3, 0x3a, 0xd8, + 0x2e, 0x52, 0x57, 0x88, 0x91, 0x63, 0x4f, 0xdd, 0xd1, 0x4e, 0x3c, 0xc1, 0x1e, 0xa3, 0xe6, 0x9e, + 0x07, 0xe0, 0x25, 0x78, 0x0b, 0x1e, 0x60, 0x2f, 0xf7, 0x92, 0x2b, 0x84, 0xda, 0x67, 0xe0, 0x8a, + 0x1b, 0x34, 0x63, 0x37, 0x4d, 0xba, 0x5b, 0x21, 0xb4, 0x37, 0xdc, 0x79, 0xbe, 0xf3, 0xcd, 0x37, + 0xe7, 0x9c, 0xf9, 0xce, 0xc8, 0xb0, 0x19, 0x4e, 0xc3, 0xcb, 0x49, 0xc2, 0x05, 0x0f, 0x38, 0xeb, + 0x06, 0x8c, 0x8f, 0xba, 0x3c, 0x09, 0x49, 0xd2, 0x51, 0x18, 0x7a, 0x3c, 0x1f, 0xee, 0xc8, 0x70, + 0xf3, 0x49, 0xc4, 0x23, 0xae, 0xa0, 0xae, 0xfc, 0xca, 0x89, 0xcd, 0x4f, 0x16, 0x74, 0xd2, 0x6c, + 0xe4, 0x07, 0x01, 0xcf, 0x62, 0x91, 0xce, 0x7d, 0xe7, 0x54, 0xf3, 0x37, 0x0d, 0x56, 0x6c, 0x79, + 0x86, 0x15, 0xa2, 0x6f, 0xa1, 0x7e, 0x1b, 0xc7, 0x34, 0x34, 0xb4, 0xb6, 0xb6, 0x53, 0xdd, 0xdd, + 0xee, 0x2c, 0x9c, 0x3b, 0x27, 0xd7, 0x71, 0x67, 0xdf, 0x56, 0xb8, 0x57, 0x7e, 0xf5, 0xc7, 0x56, + 0xc9, 0xa9, 0xa5, 0x73, 0x18, 0xda, 0x80, 0xd5, 0x80, 0x51, 0x92, 0xcb, 0x2d, 0xb5, 0xb5, 0x9d, + 0x15, 0xa7, 0x92, 0x03, 0x56, 0x88, 0xb6, 0xa0, 0xaa, 0xca, 0xc3, 0xe7, 0xcc, 0x8f, 0x52, 0x63, + 0xb9, 0xad, 0xed, 0xd4, 0x1d, 0x50, 0x50, 0x5f, 0x22, 0xa8, 0x0d, 0x35, 0x59, 0x25, 0x9e, 0xf8, + 0x34, 0x91, 0x02, 0xe5, 0x9c, 0x21, 0xb1, 0xa1, 0x4f, 0x13, 0x2b, 0x34, 0x7f, 0x80, 0x4d, 0x95, + 0x7d, 0xda, 0xa7, 0x8c, 0x91, 0xf0, 0x20, 0x4b, 0x68, 0x1c, 0x0d, 0x7c, 0x41, 0x52, 0xb1, 0xc7, + 0x78, 0xf0, 0x12, 0x7d, 0x03, 0xab, 0xf9, 0x19, 0x34, 0x4c, 0x0d, 0xad, 0xbd, 0xbc, 0x53, 0xdd, + 0x6d, 0x76, 0xde, 0xe8, 0x63, 0xa7, 0x68, 0x41, 0x51, 0x43, 0x85, 0xe7, 0xcb, 0xd4, 0x7c, 0x01, + 0xeb, 0x43, 0x2e, 0x48, 0x2c, 0xa8, 0xcf, 0xd8, 0x74, 0x98, 0x64, 0xb1, 0x3f, 0x62, 0x24, 0x3f, + 0xf2, 0x5d, 0xb5, 0x09, 0x34, 0x54, 0x48, 0xa6, 0xee, 0x0a, 0x5f, 0x10, 0xd9, 0x90, 0x73, 0xca, + 0x18, 0xf6, 0xc7, 0xb2, 0x7d, 0xaa, 0xfd, 0x65, 0x07, 0x24, 0xf4, 0x5c, 0x21, 0x68, 0x17, 0x9e, + 0x4e, 0x8a, 0x1c, 0xf0, 0x48, 0xd6, 0x87, 0x2f, 0x08, 0x8d, 0x2e, 0x84, 0x6a, 0x6d, 0xdd, 0x59, + 0xbb, 0x09, 0xaa, 0xda, 0x0f, 0x55, 0xc8, 0xfc, 0x1e, 0x36, 0x94, 0xfa, 0x79, 0xc6, 0xd4, 0x71, + 0x1e, 0x1d, 0x13, 0x97, 0xd1, 0x80, 0x7c, 0xe7, 0xb3, 0x8c, 0xbc, 0x6b, 0x11, 0xbf, 0x6a, 0xf0, + 0xde, 0x80, 0xc7, 0x91, 0x47, 0x92, 0xb1, 0xe2, 0x0c, 0x99, 0x1f, 0x90, 0x31, 0x89, 0x05, 0x7a, + 0x06, 0x0f, 0x14, 0xad, 0xb0, 0x91, 0x71, 0x9f, 0x6a, 0xa1, 0x99, 0x93, 0xd1, 0x29, 0x3c, 0x9a, + 0xdc, 0x48, 0x60, 0x1a, 0x87, 0xe4, 0x52, 0x15, 0xf7, 0x86, 0x0d, 0xd5, 0x7e, 0x2f, 0xf1, 0xe3, + 0xd4, 0x0f, 0x04, 0xe5, 0xb1, 0x92, 0xa2, 0x71, 0x54, 0xa8, 0x35, 0x66, 0x22, 0x96, 0xd4, 0x30, + 0xff, 0xd2, 0x60, 0x7d, 0x9f, 0xc7, 0x21, 0x95, 0x5c, 0x9f, 0xfd, 0x8f, 0x53, 0x45, 0x47, 0x50, + 0x17, 0x09, 0x8d, 0x22, 0x79, 0x27, 0x4a, 0x74, 0xf9, 0xbf, 0x88, 0x3a, 0xb5, 0x62, 0x73, 0x5e, + 0xf7, 0xdf, 0x0f, 0xe1, 0x81, 0x0a, 0xa1, 0xaf, 0xa1, 0x72, 0x73, 0xd1, 0x45, 0x99, 0xff, 0x7e, + 0xcf, 0x2b, 0xc5, 0x3d, 0xa3, 0xcf, 0xa1, 0x9c, 0xd2, 0x90, 0xa8, 0xfa, 0x1a, 0xbb, 0x9b, 0xf7, + 0x6d, 0xec, 0xb8, 0x34, 0x24, 0x8e, 0xa2, 0xa2, 0x26, 0x54, 0x7e, 0xcc, 0xfc, 0x58, 0x64, 0xe3, + 0x7c, 0xb4, 0xcb, 0xce, 0x6c, 0x2d, 0x63, 0x69, 0x36, 0x12, 0x34, 0x78, 0x99, 0xaa, 0xa1, 0x2e, + 0x3b, 0xb3, 0x35, 0xda, 0x86, 0x46, 0xc4, 0x79, 0x88, 0x05, 0x65, 0xb9, 0xc7, 0x8d, 0x07, 0xd2, + 0xdc, 0x87, 0x25, 0xa7, 0x26, 0x71, 0x8f, 0xb2, 0x7c, 0xb2, 0xbb, 0xb0, 0xb6, 0xc8, 0xc3, 0x82, + 0x8e, 0x89, 0xf1, 0x50, 0x3e, 0x32, 0x87, 0x25, 0x47, 0x9f, 0x27, 0x4b, 0xcf, 0xa3, 0x43, 0xa8, + 0x4b, 0x06, 0xa6, 0x31, 0x3e, 0xe7, 0x49, 0x40, 0x8c, 0x15, 0x55, 0xcc, 0x47, 0xf7, 0x16, 0x23, + 0x77, 0x59, 0x71, 0x5f, 0x72, 0x9d, 0xaa, 0xb8, 0x5d, 0xc8, 0x39, 0x4d, 0x48, 0x98, 0x05, 0x04, + 0xf3, 0x98, 0x4d, 0x8d, 0x4a, 0x5b, 0xdb, 0xa9, 0x38, 0x90, 0x43, 0x76, 0xcc, 0xa6, 0xe8, 0x63, + 0x78, 0x54, 0x3c, 0x7b, 0x63, 0x22, 0xfc, 0xd0, 0x17, 0xbe, 0xb1, 0xaa, 0x26, 0xb4, 0x91, 0xc3, + 0xc7, 0x05, 0x8a, 0x8e, 0xa1, 0x11, 0xdc, 0xb8, 0x12, 0x8b, 0xe9, 0x84, 0x18, 0xa0, 0x92, 0xda, + 0xbe, 0x37, 0xa9, 0x99, 0x89, 0xbd, 0xe9, 0x84, 0x38, 0xf5, 0x60, 0x7e, 0x89, 0x8e, 0xc0, 0x0c, + 0x6e, 0x4d, 0x8e, 0xf3, 0xfb, 0xbe, 0x31, 0xd3, 0xac, 0xe3, 0x55, 0xd5, 0xf1, 0xad, 0xe0, 0xce, + 0x38, 0x78, 0x39, 0xcf, 0x2d, 0x68, 0xe6, 0x57, 0x50, 0x96, 0xd7, 0x89, 0x9e, 0x80, 0xee, 0x5a, + 0x07, 0x3d, 0x7c, 0x7a, 0xe2, 0x0e, 0x7b, 0xfb, 0x56, 0xdf, 0xea, 0x1d, 0xe8, 0x25, 0x54, 0x83, + 0x8a, 0x42, 0xf7, 0x4e, 0xcf, 0x74, 0x0d, 0xd5, 0x61, 0x55, 0xad, 0xdc, 0xde, 0x60, 0xa0, 0x2f, + 0x99, 0x3f, 0x6b, 0x50, 0x9d, 0xeb, 0x1e, 0xda, 0x84, 0x75, 0xcf, 0x3a, 0xee, 0x61, 0xeb, 0x04, + 0xf7, 0x6d, 0x67, 0xff, 0xae, 0xd6, 0x53, 0x78, 0xbc, 0x18, 0xb6, 0xec, 0x7d, 0x5d, 0x43, 0x1b, + 0xf0, 0xfe, 0x22, 0x3c, 0xb4, 0x5d, 0x0f, 0xdb, 0x27, 0x83, 0x33, 0x7d, 0x09, 0x99, 0xd0, 0x5c, + 0x0c, 0xf6, 0xad, 0xc1, 0x00, 0xdb, 0x0e, 0x3e, 0xb2, 0x06, 0x03, 0x7d, 0xb9, 0xb9, 0x54, 0xd1, + 0xcc, 0x31, 0xd4, 0x17, 0xda, 0x85, 0x5a, 0xd0, 0xdc, 0xb7, 0x4f, 0x0e, 0x2c, 0xcf, 0xb2, 0x4f, + 0xb0, 0x77, 0x36, 0xbc, 0x9b, 0xc8, 0x07, 0x60, 0xdc, 0x89, 0xbb, 0x9e, 0x3d, 0xc4, 0x03, 0xdb, + 0x75, 0x75, 0xed, 0x2d, 0xbb, 0xbd, 0xe7, 0x47, 0x3d, 0x3c, 0x74, 0xec, 0xbe, 0xe5, 0xe9, 0x4b, + 0x7b, 0xfa, 0x9c, 0x73, 0x79, 0x4c, 0xf8, 0xb9, 0x49, 0x60, 0xed, 0x2d, 0x23, 0x8a, 0x3e, 0x84, + 0xda, 0xc2, 0xeb, 0xad, 0x29, 0x6f, 0x54, 0x47, 0xb7, 0xaf, 0x36, 0xfa, 0x14, 0x1e, 0x8b, 0xdb, + 0x9d, 0x73, 0xaf, 0x4b, 0xdd, 0xd1, 0xe7, 0x02, 0x6a, 0xc8, 0xf7, 0x86, 0xaf, 0xae, 0x5a, 0xda, + 0xeb, 0xab, 0x96, 0xf6, 0xe7, 0x55, 0x4b, 0xfb, 0xe5, 0xba, 0x55, 0x7a, 0x7d, 0xdd, 0x2a, 0xfd, + 0x7e, 0xdd, 0x2a, 0xbd, 0xf8, 0x32, 0xa2, 0xe2, 0x22, 0x1b, 0x75, 0x02, 0x3e, 0xee, 0x2e, 0xfc, + 0x14, 0xfc, 0xf4, 0xec, 0xb3, 0xe0, 0xc2, 0xa7, 0x71, 0x77, 0x86, 0x5c, 0xe6, 0x3f, 0x1c, 0xd2, + 0x84, 0xe9, 0xe8, 0xa1, 0x82, 0xbf, 0xf8, 0x27, 0x00, 0x00, 0xff, 0xff, 0x85, 0x48, 0x2a, 0xbd, + 0x92, 0x08, 0x00, 0x00, } func (m *OrderId) Marshal() (dAtA []byte, err error) { diff --git a/protocol/x/clob/types/order_test.go b/protocol/x/clob/types/order_test.go index 8b33b4cf52..555423eef4 100644 --- a/protocol/x/clob/types/order_test.go +++ b/protocol/x/clob/types/order_test.go @@ -392,7 +392,6 @@ func TestOrder_IsReduceOnly(t *testing.T) { func TestOrder_RequiresImmediateExecution(t *testing.T) { require.False(t, constants.Order_Alice_Num0_Id1_Clob0_Sell10_Price15_GTB15.RequiresImmediateExecution()) - require.True(t, constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_FOK.RequiresImmediateExecution()) require.True(t, constants.Order_Alice_Num1_Id1_Clob1_Sell10_Price15_GTB20_IOC.RequiresImmediateExecution()) } diff --git a/protocol/x/clob/types/price_to_subticks.go b/protocol/x/clob/types/price_to_subticks.go index 1d7bbae40b..455e9fdd76 100644 --- a/protocol/x/clob/types/price_to_subticks.go +++ b/protocol/x/clob/types/price_to_subticks.go @@ -34,11 +34,14 @@ func PriceToSubticks( exponent := int32( marketPrice.Exponent - clobPair.QuantumConversionExponent + baseAtomicResolution - quoteAtomicResolution, ) - return lib.BigMulPow10( - // TODO(DEC-1256): Use index price from the price daemon, instead of oracle price. - new(big.Int).SetUint64(marketPrice.Price), - exponent, - ) + // TODO(DEC-1256): Use index price from the price daemon, instead of oracle price. + bigPrice := new(big.Int).SetUint64(marketPrice.Price) + p10, inverse := lib.BigPow10(exponent) + if inverse { + return new(big.Rat).SetFrac(bigPrice, p10) + } else { + return new(big.Rat).SetInt(bigPrice.Mul(bigPrice, p10)) + } } // SubticksToPrice converts subticks into price value from Prices module. @@ -71,12 +74,9 @@ func SubticksToPrice( exponent := int32( -marketPriceExponent + clobPair.QuantumConversionExponent - baseAtomicResolution + quoteAtomicResolution, ) - return lib.BigRatRound( - lib.BigMulPow10( - // TODO(DEC-1256): Use index price from the price daemon, instead of oracle price. - new(big.Int).SetUint64(uint64(subticks)), - exponent, - ), - false, - ).Uint64() + result := lib.BigIntMulPow10(new(big.Int).SetUint64(uint64(subticks)), exponent, false) + if !result.IsUint64() { + panic("SubticksToPrice: result is not a uint64") + } + return result.Uint64() } diff --git a/protocol/x/clob/types/quantums.go b/protocol/x/clob/types/quantums.go index 5e3c239cda..8d2d0b423e 100644 --- a/protocol/x/clob/types/quantums.go +++ b/protocol/x/clob/types/quantums.go @@ -33,26 +33,8 @@ func FillAmountToQuoteQuantums( bigSubticks := subticks.ToBigInt() bigBaseQuantums := baseQuantums.ToBigInt() - bigSubticksMulBaseQuantums := new(big.Int).Mul(bigSubticks, bigBaseQuantums) - - exponent := int64(quantumConversionExponent) - - // To ensure we are always doing math with integers, we take the absolute - // value of the exponent. If `exponent` is non-negative, then `10^exponent` is an - // integer and we can multiply by it. Else, `10^exponent` is less than 1 and we should - // multiply by `1 / 10^exponent` (which must be an integer if `exponent < 0`). - bigExponentValue := lib.BigPow10(lib.AbsInt64(exponent)) - - bigQuoteQuantums := new(big.Int) - if exponent < 0 { - // `1 / 10^exponent` is an integer. - bigQuoteQuantums.Div(bigSubticksMulBaseQuantums, bigExponentValue) - } else { - // `10^exponent` is an integer. - bigQuoteQuantums.Mul(bigSubticksMulBaseQuantums, bigExponentValue) - } - - return bigQuoteQuantums + result := new(big.Int).Mul(bigSubticks, bigBaseQuantums) + return lib.BigIntMulPow10(result, quantumConversionExponent, false) } // GetAveragePriceSubticks computes the average price (in subticks) of filled @@ -73,10 +55,13 @@ func GetAveragePriceSubticks( if bigBaseQuantums.Sign() == 0 { panic(errors.New("GetAveragePriceSubticks: bigBaseQuantums = 0")) } - - result := lib.BigMulPow10(bigQuoteQuantums, -quantumConversionExponent) - return result.Quo( - result, - new(big.Rat).SetInt(bigBaseQuantums), - ) + numerator := new(big.Int).Set(bigQuoteQuantums) + denominator := new(big.Int).Set(bigBaseQuantums) + p10, inverse := lib.BigPow10(-quantumConversionExponent) + if inverse { + denominator.Mul(denominator, p10) + } else { + numerator.Mul(numerator, p10) + } + return new(big.Rat).SetFrac(numerator, denominator) } diff --git a/protocol/x/prices/keeper/market_price.go b/protocol/x/prices/keeper/market_price.go index 46e0e5e120..7d168f87c6 100644 --- a/protocol/x/prices/keeper/market_price.go +++ b/protocol/x/prices/keeper/market_price.go @@ -65,13 +65,17 @@ func (k Keeper) UpdateMarketPrices( updatedMarketPrices = append(updatedMarketPrices, marketPrice) // Report the oracle price. - updatedPrice, _ := lib.BigMulPow10( - new(big.Int).SetUint64(update.Price), - marketPrice.Exponent, - ).Float32() + p10, inverse := lib.BigPow10(marketPrice.Exponent) + updatePrice := new(big.Int).SetUint64(update.Price) + var result float32 + if inverse { + result, _ = new(big.Rat).SetFrac(updatePrice, p10).Float32() + } else { + result, _ = new(big.Rat).SetInt(updatePrice.Mul(updatePrice, p10)).Float32() + } telemetry.SetGaugeWithLabels( []string{types.ModuleName, metrics.CurrentMarketPrices}, - updatedPrice, + result, []gometrics.Label{ // To track per market, include the id as a label. pricefeedmetrics.GetLabelForMarketId(marketPrice.Id), }, diff --git a/protocol/x/ratelimit/types/params.go b/protocol/x/ratelimit/types/params.go index 291f5b64b6..c61ea03dfe 100644 --- a/protocol/x/ratelimit/types/params.go +++ b/protocol/x/ratelimit/types/params.go @@ -12,15 +12,17 @@ import ( ) // BigBaselineMinimum1Hr defines the minimum baseline USDC for the 1-hour rate-limit. -var BigBaselineMinimum1Hr = new(big.Int).Mul( +var BigBaselineMinimum1Hr = lib.BigIntMulPow10( big.NewInt(1_000_000), // 1m full coins - lib.BigPow10(-assettypes.UusdcDenomExponent), + -assettypes.UusdcDenomExponent, + false, ) // BigBaselineMinimum1Day defines the minimum baseline USDC for the 1-day rate-limit. -var BigBaselineMinimum1Day = new(big.Int).Mul( +var BigBaselineMinimum1Day = lib.BigIntMulPow10( big.NewInt(10_000_000), // 10m full coins - lib.BigPow10(-assettypes.UusdcDenomExponent), + -assettypes.UusdcDenomExponent, + false, ) var DefaultUsdcHourlyLimter = Limiter{