From ece92b4216ce61c6e28090124aae1c4f1144ec2f Mon Sep 17 00:00:00 2001 From: jayy04 <103467857+jayy04@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:29:05 -0500 Subject: [PATCH] [CLOB-1043] replicate IsLiquidatable logic on daemon (#873) * [CLOB-1043] replicate IsLiquidatable logic on daemon * comments * comments * update to return error * [CLOB-1044] replicate funding settlement on liquidation daemon (#882) * [CLOB-1043] deprecate unused grpc query (#889) * [CLOB-1043] deprecate unused grpc query * fix lint * [CLOB-1046] populate negative tnc subaccounts in grpc request (#890) * [CLOB-1046] populate negative tnc subaccounts in grpc request * [CLOB-1047] populate subaccounts with open positions in grpc request (#892) * [CLOB-1047] populate subaccounts with open positions in grpc request * comments --- .../dydxprotocol/clob/query.rpc.Query.ts | 16 +- .../src/codegen/dydxprotocol/clob/query.ts | 192 ----- proto/dydxprotocol/clob/query.proto | 25 - protocol/daemons/flags/flags.go | 39 +- protocol/daemons/flags/flags_test.go | 8 +- .../daemons/liquidation/client/client_test.go | 183 ----- .../daemons/liquidation/client/grpc_helper.go | 59 +- .../liquidation/client/grpc_helper_test.go | 192 ++--- .../liquidation/client/sub_task_runner.go | 306 ++++++- .../client/sub_task_runner_test.go | 735 +++++++++++++++++ protocol/daemons/server/liquidation.go | 3 + protocol/daemons/server/liquidation_test.go | 70 +- .../liquidations/daemon_liquidation_info.go | 8 +- .../daemon_liquidation_info_test.go | 119 +-- protocol/lib/collections.go | 18 + protocol/lib/collections_test.go | 52 ++ protocol/lib/metrics/constants.go | 3 + protocol/mocks/QueryClient.go | 30 - protocol/testutil/constants/perpetuals.go | 1 + protocol/testutil/constants/subaccounts.go | 36 +- ...grpc_query_are_subaccounts_liquidatable.go | 40 - ...query_are_subaccounts_liquidatable_test.go | 166 ---- protocol/x/clob/keeper/liquidations.go | 21 +- protocol/x/clob/types/query.pb.go | 772 ++---------------- protocol/x/perpetuals/keeper/perpetual.go | 69 +- protocol/x/subaccounts/keeper/subaccount.go | 47 +- .../x/subaccounts/types/expected_keepers.go | 7 + 27 files changed, 1567 insertions(+), 1650 deletions(-) create mode 100644 protocol/daemons/liquidation/client/sub_task_runner_test.go delete mode 100644 protocol/x/clob/keeper/grpc_query_are_subaccounts_liquidatable.go delete mode 100644 protocol/x/clob/keeper/grpc_query_are_subaccounts_liquidatable_test.go diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.rpc.Query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.rpc.Query.ts index bdc2213141..643117c5ea 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.rpc.Query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.rpc.Query.ts @@ -1,7 +1,7 @@ import { Rpc } from "../../helpers"; import * as _m0 from "protobufjs/minimal"; import { QueryClient, createProtobufRpcClient } from "@cosmjs/stargate"; -import { QueryGetClobPairRequest, QueryClobPairResponse, QueryAllClobPairRequest, QueryClobPairAllResponse, AreSubaccountsLiquidatableRequest, AreSubaccountsLiquidatableResponse, MevNodeToNodeCalculationRequest, MevNodeToNodeCalculationResponse, QueryEquityTierLimitConfigurationRequest, QueryEquityTierLimitConfigurationResponse, QueryBlockRateLimitConfigurationRequest, QueryBlockRateLimitConfigurationResponse, QueryLiquidationsConfigurationRequest, QueryLiquidationsConfigurationResponse } from "./query"; +import { QueryGetClobPairRequest, QueryClobPairResponse, QueryAllClobPairRequest, QueryClobPairAllResponse, MevNodeToNodeCalculationRequest, MevNodeToNodeCalculationResponse, QueryEquityTierLimitConfigurationRequest, QueryEquityTierLimitConfigurationResponse, QueryBlockRateLimitConfigurationRequest, QueryBlockRateLimitConfigurationResponse, QueryLiquidationsConfigurationRequest, QueryLiquidationsConfigurationResponse } from "./query"; /** Query defines the gRPC querier service. */ export interface Query { @@ -10,9 +10,6 @@ export interface Query { /** Queries a list of ClobPair items. */ clobPairAll(request?: QueryAllClobPairRequest): Promise<QueryClobPairAllResponse>; - /** Returns whether a subaccount is liquidatable. */ - - areSubaccountsLiquidatable(request: AreSubaccountsLiquidatableRequest): Promise<AreSubaccountsLiquidatableResponse>; /** Runs the MEV node <> node calculation with the provided parameters. */ mevNodeToNodeCalculation(request: MevNodeToNodeCalculationRequest): Promise<MevNodeToNodeCalculationResponse>; @@ -33,7 +30,6 @@ export class QueryClientImpl implements Query { this.rpc = rpc; this.clobPair = this.clobPair.bind(this); this.clobPairAll = this.clobPairAll.bind(this); - this.areSubaccountsLiquidatable = this.areSubaccountsLiquidatable.bind(this); this.mevNodeToNodeCalculation = this.mevNodeToNodeCalculation.bind(this); this.equityTierLimitConfiguration = this.equityTierLimitConfiguration.bind(this); this.blockRateLimitConfiguration = this.blockRateLimitConfiguration.bind(this); @@ -54,12 +50,6 @@ export class QueryClientImpl implements Query { return promise.then(data => QueryClobPairAllResponse.decode(new _m0.Reader(data))); } - areSubaccountsLiquidatable(request: AreSubaccountsLiquidatableRequest): Promise<AreSubaccountsLiquidatableResponse> { - const data = AreSubaccountsLiquidatableRequest.encode(request).finish(); - const promise = this.rpc.request("dydxprotocol.clob.Query", "AreSubaccountsLiquidatable", data); - return promise.then(data => AreSubaccountsLiquidatableResponse.decode(new _m0.Reader(data))); - } - mevNodeToNodeCalculation(request: MevNodeToNodeCalculationRequest): Promise<MevNodeToNodeCalculationResponse> { const data = MevNodeToNodeCalculationRequest.encode(request).finish(); const promise = this.rpc.request("dydxprotocol.clob.Query", "MevNodeToNodeCalculation", data); @@ -97,10 +87,6 @@ export const createRpcQueryExtension = (base: QueryClient) => { return queryService.clobPairAll(request); }, - areSubaccountsLiquidatable(request: AreSubaccountsLiquidatableRequest): Promise<AreSubaccountsLiquidatableResponse> { - return queryService.areSubaccountsLiquidatable(request); - }, - mevNodeToNodeCalculation(request: MevNodeToNodeCalculationRequest): Promise<MevNodeToNodeCalculationResponse> { return queryService.mevNodeToNodeCalculation(request); }, diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.ts index f2b798bc09..f0ef3987b8 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.ts @@ -1,5 +1,4 @@ import { PageRequest, PageRequestSDKType, PageResponse, PageResponseSDKType } from "../../cosmos/base/query/v1beta1/pagination"; -import { SubaccountId, SubaccountIdSDKType } from "../subaccounts/subaccount"; import { ValidatorMevMatches, ValidatorMevMatchesSDKType, MevNodeToNodeMetrics, MevNodeToNodeMetricsSDKType } from "./mev"; import { ClobPair, ClobPairSDKType } from "./clob_pair"; import { EquityTierLimitConfiguration, EquityTierLimitConfigurationSDKType } from "./equity_tier_limit_config"; @@ -51,52 +50,6 @@ export interface QueryClobPairAllResponseSDKType { clob_pair: ClobPairSDKType[]; pagination?: PageResponseSDKType; } -/** - * AreSubaccountsLiquidatableRequest is a request message used to check whether - * the given subaccounts are liquidatable. - * The subaccount ids should not contain duplicates. - */ - -export interface AreSubaccountsLiquidatableRequest { - subaccountIds: SubaccountId[]; -} -/** - * AreSubaccountsLiquidatableRequest is a request message used to check whether - * the given subaccounts are liquidatable. - * The subaccount ids should not contain duplicates. - */ - -export interface AreSubaccountsLiquidatableRequestSDKType { - subaccount_ids: SubaccountIdSDKType[]; -} -/** - * AreSubaccountsLiquidatableResponse is a response message that contains the - * liquidation status for each subaccount. - */ - -export interface AreSubaccountsLiquidatableResponse { - results: AreSubaccountsLiquidatableResponse_Result[]; -} -/** - * AreSubaccountsLiquidatableResponse is a response message that contains the - * liquidation status for each subaccount. - */ - -export interface AreSubaccountsLiquidatableResponseSDKType { - results: AreSubaccountsLiquidatableResponse_ResultSDKType[]; -} -/** Result returns whether a subaccount should be liquidated. */ - -export interface AreSubaccountsLiquidatableResponse_Result { - subaccountId?: SubaccountId; - isLiquidatable: boolean; -} -/** Result returns whether a subaccount should be liquidated. */ - -export interface AreSubaccountsLiquidatableResponse_ResultSDKType { - subaccount_id?: SubaccountIdSDKType; - is_liquidatable: boolean; -} /** * MevNodeToNodeCalculationRequest is a request message used to run the * MEV node <> node calculation. @@ -436,151 +389,6 @@ export const QueryClobPairAllResponse = { }; -function createBaseAreSubaccountsLiquidatableRequest(): AreSubaccountsLiquidatableRequest { - return { - subaccountIds: [] - }; -} - -export const AreSubaccountsLiquidatableRequest = { - encode(message: AreSubaccountsLiquidatableRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { - for (const v of message.subaccountIds) { - SubaccountId.encode(v!, writer.uint32(10).fork()).ldelim(); - } - - return writer; - }, - - decode(input: _m0.Reader | Uint8Array, length?: number): AreSubaccountsLiquidatableRequest { - const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); - let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseAreSubaccountsLiquidatableRequest(); - - while (reader.pos < end) { - const tag = reader.uint32(); - - switch (tag >>> 3) { - case 1: - message.subaccountIds.push(SubaccountId.decode(reader, reader.uint32())); - break; - - default: - reader.skipType(tag & 7); - break; - } - } - - return message; - }, - - fromPartial(object: DeepPartial<AreSubaccountsLiquidatableRequest>): AreSubaccountsLiquidatableRequest { - const message = createBaseAreSubaccountsLiquidatableRequest(); - message.subaccountIds = object.subaccountIds?.map(e => SubaccountId.fromPartial(e)) || []; - return message; - } - -}; - -function createBaseAreSubaccountsLiquidatableResponse(): AreSubaccountsLiquidatableResponse { - return { - results: [] - }; -} - -export const AreSubaccountsLiquidatableResponse = { - encode(message: AreSubaccountsLiquidatableResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { - for (const v of message.results) { - AreSubaccountsLiquidatableResponse_Result.encode(v!, writer.uint32(10).fork()).ldelim(); - } - - return writer; - }, - - decode(input: _m0.Reader | Uint8Array, length?: number): AreSubaccountsLiquidatableResponse { - const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); - let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseAreSubaccountsLiquidatableResponse(); - - while (reader.pos < end) { - const tag = reader.uint32(); - - switch (tag >>> 3) { - case 1: - message.results.push(AreSubaccountsLiquidatableResponse_Result.decode(reader, reader.uint32())); - break; - - default: - reader.skipType(tag & 7); - break; - } - } - - return message; - }, - - fromPartial(object: DeepPartial<AreSubaccountsLiquidatableResponse>): AreSubaccountsLiquidatableResponse { - const message = createBaseAreSubaccountsLiquidatableResponse(); - message.results = object.results?.map(e => AreSubaccountsLiquidatableResponse_Result.fromPartial(e)) || []; - return message; - } - -}; - -function createBaseAreSubaccountsLiquidatableResponse_Result(): AreSubaccountsLiquidatableResponse_Result { - return { - subaccountId: undefined, - isLiquidatable: false - }; -} - -export const AreSubaccountsLiquidatableResponse_Result = { - encode(message: AreSubaccountsLiquidatableResponse_Result, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { - if (message.subaccountId !== undefined) { - SubaccountId.encode(message.subaccountId, writer.uint32(10).fork()).ldelim(); - } - - if (message.isLiquidatable === true) { - writer.uint32(16).bool(message.isLiquidatable); - } - - return writer; - }, - - decode(input: _m0.Reader | Uint8Array, length?: number): AreSubaccountsLiquidatableResponse_Result { - const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); - let end = length === undefined ? reader.len : reader.pos + length; - const message = createBaseAreSubaccountsLiquidatableResponse_Result(); - - while (reader.pos < end) { - const tag = reader.uint32(); - - switch (tag >>> 3) { - case 1: - message.subaccountId = SubaccountId.decode(reader, reader.uint32()); - break; - - case 2: - message.isLiquidatable = reader.bool(); - break; - - default: - reader.skipType(tag & 7); - break; - } - } - - return message; - }, - - fromPartial(object: DeepPartial<AreSubaccountsLiquidatableResponse_Result>): AreSubaccountsLiquidatableResponse_Result { - const message = createBaseAreSubaccountsLiquidatableResponse_Result(); - message.subaccountId = object.subaccountId !== undefined && object.subaccountId !== null ? SubaccountId.fromPartial(object.subaccountId) : undefined; - message.isLiquidatable = object.isLiquidatable ?? false; - return message; - } - -}; - function createBaseMevNodeToNodeCalculationRequest(): MevNodeToNodeCalculationRequest { return { blockProposerMatches: undefined, diff --git a/proto/dydxprotocol/clob/query.proto b/proto/dydxprotocol/clob/query.proto index 9193122953..3fc37479c0 100644 --- a/proto/dydxprotocol/clob/query.proto +++ b/proto/dydxprotocol/clob/query.proto @@ -9,7 +9,6 @@ import "dydxprotocol/clob/clob_pair.proto"; import "dydxprotocol/clob/equity_tier_limit_config.proto"; import "dydxprotocol/clob/liquidations_config.proto"; import "dydxprotocol/clob/mev.proto"; -import "dydxprotocol/subaccounts/subaccount.proto"; option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/clob/types"; @@ -25,10 +24,6 @@ service Query { option (google.api.http).get = "/dydxprotocol/clob/clob_pair"; } - // Returns whether a subaccount is liquidatable. - rpc AreSubaccountsLiquidatable(AreSubaccountsLiquidatableRequest) - returns (AreSubaccountsLiquidatableResponse); - // Runs the MEV node <> node calculation with the provided parameters. rpc MevNodeToNodeCalculation(MevNodeToNodeCalculationRequest) returns (MevNodeToNodeCalculationResponse) { @@ -76,26 +71,6 @@ message QueryClobPairAllResponse { cosmos.base.query.v1beta1.PageResponse pagination = 2; } -// AreSubaccountsLiquidatableRequest is a request message used to check whether -// the given subaccounts are liquidatable. -// The subaccount ids should not contain duplicates. -message AreSubaccountsLiquidatableRequest { - repeated dydxprotocol.subaccounts.SubaccountId subaccount_ids = 1 - [ (gogoproto.nullable) = false ]; -} - -// AreSubaccountsLiquidatableResponse is a response message that contains the -// liquidation status for each subaccount. -message AreSubaccountsLiquidatableResponse { - // Result returns whether a subaccount should be liquidated. - message Result { - dydxprotocol.subaccounts.SubaccountId subaccount_id = 1 - [ (gogoproto.nullable) = false ]; - bool is_liquidatable = 2; - } - repeated Result results = 1 [ (gogoproto.nullable) = false ]; -} - // MevNodeToNodeCalculationRequest is a request message used to run the // MEV node <> node calculation. message MevNodeToNodeCalculationRequest { diff --git a/protocol/daemons/flags/flags.go b/protocol/daemons/flags/flags.go index 6fbbcd7c3c..3bae6c44e6 100644 --- a/protocol/daemons/flags/flags.go +++ b/protocol/daemons/flags/flags.go @@ -20,10 +20,9 @@ const ( FlagBridgeDaemonLoopDelayMs = "bridge-daemon-loop-delay-ms" FlagBridgeDaemonEthRpcEndpoint = "bridge-daemon-eth-rpc-endpoint" - FlagLiquidationDaemonEnabled = "liquidation-daemon-enabled" - FlagLiquidationDaemonLoopDelayMs = "liquidation-daemon-loop-delay-ms" - FlagLiquidationDaemonSubaccountPageLimit = "liquidation-daemon-subaccount-page-limit" - FlagLiquidationDaemonRequestChunkSize = "liquidation-daemon-request-chunk-size" + FlagLiquidationDaemonEnabled = "liquidation-daemon-enabled" + FlagLiquidationDaemonLoopDelayMs = "liquidation-daemon-loop-delay-ms" + FlagLiquidationDaemonQueryPageLimit = "liquidation-daemon-query-page-limit" ) // Shared flags contains configuration flags shared by all daemons. @@ -52,9 +51,8 @@ type LiquidationFlags struct { Enabled bool // LoopDelayMs configures the update frequency of the liquidation daemon. LoopDelayMs uint32 - // SubaccountPageLimit configures the pagination limit for fetching subaccounts. - SubaccountPageLimit uint64 - RequestChunkSize uint64 + // QueryPageLimit configures the pagination limit for fetching subaccounts. + QueryPageLimit uint64 } // PriceFlags contains configuration flags for the Price Daemon. @@ -90,10 +88,9 @@ func GetDefaultDaemonFlags() DaemonFlags { EthRpcEndpoint: "", }, Liquidation: LiquidationFlags{ - Enabled: true, - LoopDelayMs: 1_600, - SubaccountPageLimit: 1_000, - RequestChunkSize: 50, + Enabled: true, + LoopDelayMs: 1_600, + QueryPageLimit: 1_000, }, Price: PriceFlags{ Enabled: true, @@ -160,14 +157,9 @@ func AddDaemonFlagsToCmd( "Delay in milliseconds between running the Liquidation Daemon task loop.", ) cmd.Flags().Uint64( - FlagLiquidationDaemonSubaccountPageLimit, - df.Liquidation.SubaccountPageLimit, - "Limit on the number of subaccounts to fetch per query in the Liquidation Daemon task loop.", - ) - cmd.Flags().Uint64( - FlagLiquidationDaemonRequestChunkSize, - df.Liquidation.RequestChunkSize, - "Limit on the number of subaccounts per collateralization check in the Liquidation Daemon task loop.", + FlagLiquidationDaemonQueryPageLimit, + df.Liquidation.QueryPageLimit, + "Limit on the number of items to fetch per query in the Liquidation Daemon task loop.", ) // Price Daemon. @@ -235,14 +227,9 @@ func GetDaemonFlagValuesFromOptions( result.Liquidation.LoopDelayMs = v } } - if option := appOpts.Get(FlagLiquidationDaemonSubaccountPageLimit); option != nil { - if v, err := cast.ToUint64E(option); err == nil { - result.Liquidation.SubaccountPageLimit = v - } - } - if option := appOpts.Get(FlagLiquidationDaemonRequestChunkSize); option != nil { + if option := appOpts.Get(FlagLiquidationDaemonQueryPageLimit); option != nil { if v, err := cast.ToUint64E(option); err == nil { - result.Liquidation.RequestChunkSize = v + result.Liquidation.QueryPageLimit = v } } diff --git a/protocol/daemons/flags/flags_test.go b/protocol/daemons/flags/flags_test.go index 04191032f6..e94a055d45 100644 --- a/protocol/daemons/flags/flags_test.go +++ b/protocol/daemons/flags/flags_test.go @@ -25,7 +25,7 @@ func TestAddDaemonFlagsToCmd(t *testing.T) { flags.FlagLiquidationDaemonEnabled, flags.FlagLiquidationDaemonLoopDelayMs, - flags.FlagLiquidationDaemonSubaccountPageLimit, + flags.FlagLiquidationDaemonQueryPageLimit, flags.FlagPriceDaemonEnabled, flags.FlagPriceDaemonLoopDelayMs, @@ -52,8 +52,7 @@ func TestGetDaemonFlagValuesFromOptions_Custom(t *testing.T) { optsMap[flags.FlagLiquidationDaemonEnabled] = true optsMap[flags.FlagLiquidationDaemonLoopDelayMs] = uint32(2222) - optsMap[flags.FlagLiquidationDaemonSubaccountPageLimit] = uint64(3333) - optsMap[flags.FlagLiquidationDaemonRequestChunkSize] = uint64(4444) + optsMap[flags.FlagLiquidationDaemonQueryPageLimit] = uint64(3333) optsMap[flags.FlagPriceDaemonEnabled] = true optsMap[flags.FlagPriceDaemonLoopDelayMs] = uint32(4444) @@ -83,8 +82,7 @@ func TestGetDaemonFlagValuesFromOptions_Custom(t *testing.T) { // Liquidation Daemon. require.Equal(t, optsMap[flags.FlagLiquidationDaemonEnabled], r.Liquidation.Enabled) require.Equal(t, optsMap[flags.FlagLiquidationDaemonLoopDelayMs], r.Liquidation.LoopDelayMs) - require.Equal(t, optsMap[flags.FlagLiquidationDaemonSubaccountPageLimit], r.Liquidation.SubaccountPageLimit) - require.Equal(t, optsMap[flags.FlagLiquidationDaemonRequestChunkSize], r.Liquidation.RequestChunkSize) + require.Equal(t, optsMap[flags.FlagLiquidationDaemonQueryPageLimit], r.Liquidation.QueryPageLimit) // Price Daemon. require.Equal(t, optsMap[flags.FlagPriceDaemonEnabled], r.Price.Enabled) diff --git a/protocol/daemons/liquidation/client/client_test.go b/protocol/daemons/liquidation/client/client_test.go index 81b4943558..d925852287 100644 --- a/protocol/daemons/liquidation/client/client_test.go +++ b/protocol/daemons/liquidation/client/client_test.go @@ -7,20 +7,14 @@ import ( "testing" "github.com/cometbft/cometbft/libs/log" - "github.com/cosmos/cosmos-sdk/types/query" appflags "github.com/dydxprotocol/v4-chain/protocol/app/flags" d_constants "github.com/dydxprotocol/v4-chain/protocol/daemons/constants" "github.com/dydxprotocol/v4-chain/protocol/daemons/flags" - "github.com/dydxprotocol/v4-chain/protocol/daemons/liquidation/api" "github.com/dydxprotocol/v4-chain/protocol/daemons/liquidation/client" "github.com/dydxprotocol/v4-chain/protocol/mocks" "github.com/dydxprotocol/v4-chain/protocol/testutil/appoptions" - "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" daemontestutils "github.com/dydxprotocol/v4-chain/protocol/testutil/daemons" "github.com/dydxprotocol/v4-chain/protocol/testutil/grpc" - clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" - satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -70,183 +64,6 @@ func TestStart_UnixSocketConnectionFails(t *testing.T) { mockGrpcClient.AssertNumberOfCalls(t, "CloseConnection", 1) } -func TestRunLiquidationDaemonTaskLoop(t *testing.T) { - df := flags.GetDefaultDaemonFlags() - tests := map[string]struct { - // mocks - setupMocks func(ctx context.Context, mck *mocks.QueryClient) - - // expectations - expectedLiquidatableSubaccountIds []satypes.SubaccountId - expectedError error - }{ - "Success": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { - req := &satypes.QueryAllSubaccountRequest{ - Pagination: &query.PageRequest{ - Limit: df.Liquidation.SubaccountPageLimit, - }, - } - response := &satypes.QuerySubaccountAllResponse{ - Subaccount: []satypes.Subaccount{ - constants.Carl_Num0_1BTC_Short, - constants.Dave_Num0_1BTC_Long_50000USD, - }, - } - mck.On("SubaccountAll", ctx, req).Return(response, nil) - - req2 := &clobtypes.AreSubaccountsLiquidatableRequest{ - SubaccountIds: []satypes.SubaccountId{ - constants.Carl_Num0, - constants.Dave_Num0, - }, - } - response2 := &clobtypes.AreSubaccountsLiquidatableResponse{ - Results: []clobtypes.AreSubaccountsLiquidatableResponse_Result{ - { - SubaccountId: constants.Carl_Num0, - IsLiquidatable: true, - }, - { - SubaccountId: constants.Dave_Num0, - IsLiquidatable: false, - }, - }, - } - mck.On("AreSubaccountsLiquidatable", ctx, req2).Return(response2, nil) - - req3 := &api.LiquidateSubaccountsRequest{ - LiquidatableSubaccountIds: []satypes.SubaccountId{ - constants.Carl_Num0, - }, - } - response3 := &api.LiquidateSubaccountsResponse{} - mck.On("LiquidateSubaccounts", ctx, req3).Return(response3, nil) - }, - }, - "Success - no open position": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { - req := &satypes.QueryAllSubaccountRequest{ - Pagination: &query.PageRequest{ - Limit: df.Liquidation.SubaccountPageLimit, - }, - } - response := &satypes.QuerySubaccountAllResponse{ - Subaccount: []satypes.Subaccount{ - constants.Carl_Num0_599USD, // no open positions - constants.Dave_Num0_599USD, // no open positions - }, - } - mck.On("SubaccountAll", ctx, req).Return(response, nil) - req2 := &api.LiquidateSubaccountsRequest{ - LiquidatableSubaccountIds: []satypes.SubaccountId{}, - } - response2 := &api.LiquidateSubaccountsResponse{} - mck.On("LiquidateSubaccounts", ctx, req2).Return(response2, nil) - }, - }, - "Success - no liquidatable subaccounts": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { - req := &satypes.QueryAllSubaccountRequest{ - Pagination: &query.PageRequest{ - Limit: df.Liquidation.SubaccountPageLimit, - }, - } - response := &satypes.QuerySubaccountAllResponse{ - Subaccount: []satypes.Subaccount{ - constants.Carl_Num0_1BTC_Short, - constants.Dave_Num0_1BTC_Long_50000USD, - }, - } - mck.On("SubaccountAll", ctx, req).Return(response, nil) - - req2 := &clobtypes.AreSubaccountsLiquidatableRequest{ - SubaccountIds: []satypes.SubaccountId{ - constants.Carl_Num0, - constants.Dave_Num0, - }, - } - response2 := &clobtypes.AreSubaccountsLiquidatableResponse{ - Results: []clobtypes.AreSubaccountsLiquidatableResponse_Result{ - { - SubaccountId: constants.Carl_Num0, - IsLiquidatable: false, - }, - { - SubaccountId: constants.Dave_Num0, - IsLiquidatable: false, - }, - }, - } - mck.On("AreSubaccountsLiquidatable", ctx, req2).Return(response2, nil) - req3 := &api.LiquidateSubaccountsRequest{ - LiquidatableSubaccountIds: []satypes.SubaccountId{}, - } - response3 := &api.LiquidateSubaccountsResponse{} - mck.On("LiquidateSubaccounts", ctx, req3).Return(response3, nil) - }, - }, - "Panics on error - SubaccountAll": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { - mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(nil, errors.New("test error")) - }, - expectedError: errors.New("test error"), - }, - "Panics on error - AreSubaccountsLiquidatable": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { - mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(&satypes.QuerySubaccountAllResponse{ - Subaccount: []satypes.Subaccount{ - constants.Carl_Num0_1BTC_Short, - }, - }, nil) - mck.On("AreSubaccountsLiquidatable", mock.Anything, mock.Anything).Return(nil, errors.New("test error")) - }, - expectedError: errors.New("test error"), - }, - "Panics on error - LiquidateSubaccounts": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { - mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(&satypes.QuerySubaccountAllResponse{ - Subaccount: []satypes.Subaccount{ - constants.Carl_Num0_1BTC_Short, - }, - }, nil, - ) - mck.On("AreSubaccountsLiquidatable", mock.Anything, mock.Anything).Return( - &clobtypes.AreSubaccountsLiquidatableResponse{}, - nil, - ) - mck.On("LiquidateSubaccounts", mock.Anything, mock.Anything).Return(nil, errors.New("test error")) - }, - expectedError: errors.New("test error"), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - queryClientMock := &mocks.QueryClient{} - tc.setupMocks(grpc.Ctx, queryClientMock) - s := client.SubTaskRunnerImpl{} - - c := client.NewClient(log.NewNopLogger()) - c.SubaccountQueryClient = queryClientMock - c.ClobQueryClient = queryClientMock - c.LiquidationServiceClient = queryClientMock - - err := s.RunLiquidationDaemonTaskLoop( - grpc.Ctx, - c, - flags.GetDefaultDaemonFlags().Liquidation, - ) - if tc.expectedError != nil { - require.EqualError(t, err, tc.expectedError.Error()) - } else { - require.NoError(t, err) - queryClientMock.AssertExpectations(t) - } - }) - } -} - // FakeSubTaskRunner is a mock implementation of the SubTaskRunner interface for testing. type FakeSubTaskRunner struct { err error diff --git a/protocol/daemons/liquidation/client/grpc_helper.go b/protocol/daemons/liquidation/client/grpc_helper.go index 27a57b4c3a..92e49ba4bb 100644 --- a/protocol/daemons/liquidation/client/grpc_helper.go +++ b/protocol/daemons/liquidation/client/grpc_helper.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/cosmos/cosmos-sdk/types/query" "github.com/dydxprotocol/v4-chain/protocol/daemons/liquidation/api" + "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" @@ -45,7 +46,6 @@ func (c *Client) GetPreviousBlockInfo( // GetAllPerpetuals queries gRPC server and returns a list of perpetuals. func (c *Client) GetAllPerpetuals( ctx context.Context, - blockHeight uint32, pageLimit uint64, ) ( perpetuals []perptypes.Perpetual, @@ -85,7 +85,6 @@ func (c *Client) GetAllPerpetuals( // GetAllLiquidityTiers queries gRPC server and returns a list of liquidityTiers. func (c *Client) GetAllLiquidityTiers( ctx context.Context, - blockHeight uint32, pageLimit uint64, ) ( liquidityTiers []perptypes.LiquidityTier, @@ -125,7 +124,6 @@ func (c *Client) GetAllLiquidityTiers( // GetAllMarketPrices queries gRPC server and returns a list of market prices. func (c *Client) GetAllMarketPrices( ctx context.Context, - blockHeight uint32, pageLimit uint64, ) ( marketPrices []pricestypes.MarketPrice, @@ -205,38 +203,14 @@ func (c *Client) GetAllSubaccounts( return subaccounts, nil } -// CheckCollateralizationForSubaccounts queries a gRPC server using `AreSubaccountsLiquidatable` -// and returns a list of collateralization statuses for the given list of subaccount ids. -func (c *Client) CheckCollateralizationForSubaccounts( - ctx context.Context, - subaccountIds []satypes.SubaccountId, -) ( - results []clobtypes.AreSubaccountsLiquidatableResponse_Result, - err error, -) { - defer telemetry.ModuleMeasureSince( - metrics.LiquidationDaemon, - time.Now(), - metrics.CheckCollateralizationForSubaccounts, - metrics.Latency, - ) - - query := &clobtypes.AreSubaccountsLiquidatableRequest{ - SubaccountIds: subaccountIds, - } - response, err := c.ClobQueryClient.AreSubaccountsLiquidatable(ctx, query) - if err != nil { - return nil, err - } - - return response.Results, nil -} - // SendLiquidatableSubaccountIds sends a list of unique and potentially liquidatable // subaccount ids to a gRPC server via `LiquidateSubaccounts`. func (c *Client) SendLiquidatableSubaccountIds( ctx context.Context, - subaccountIds []satypes.SubaccountId, + blockHeight uint32, + liquidatableSubaccountIds []satypes.SubaccountId, + negativeTncSubaccountIds []satypes.SubaccountId, + openPositionInfoMap map[uint32]*clobtypes.SubaccountOpenPositionInfo, ) error { defer telemetry.ModuleMeasureSince( metrics.LiquidationDaemon, @@ -247,13 +221,31 @@ func (c *Client) SendLiquidatableSubaccountIds( telemetry.ModuleSetGauge( metrics.LiquidationDaemon, - float32(len(subaccountIds)), + float32(len(liquidatableSubaccountIds)), metrics.LiquidatableSubaccountIds, metrics.Count, ) + telemetry.ModuleSetGauge( + metrics.LiquidationDaemon, + float32(len(negativeTncSubaccountIds)), + metrics.NegativeTncSubaccountIds, + metrics.Count, + ) + + // Convert the map to a slice. + // Note that sorting here is not strictly necessary but is done for safety and to avoid making + // any assumptions on the server side. + sortedPerpetualIds := lib.GetSortedKeys[lib.Sortable[uint32]](openPositionInfoMap) + subaccountOpenPositionInfo := make([]clobtypes.SubaccountOpenPositionInfo, 0) + for _, perpetualId := range sortedPerpetualIds { + subaccountOpenPositionInfo = append(subaccountOpenPositionInfo, *openPositionInfoMap[perpetualId]) + } request := &api.LiquidateSubaccountsRequest{ - LiquidatableSubaccountIds: subaccountIds, + BlockHeight: blockHeight, + LiquidatableSubaccountIds: liquidatableSubaccountIds, + NegativeTncSubaccountIds: negativeTncSubaccountIds, + SubaccountOpenPositionInfo: subaccountOpenPositionInfo, } if _, err := c.LiquidationServiceClient.LiquidateSubaccounts(ctx, request); err != nil { @@ -262,7 +254,6 @@ func (c *Client) SendLiquidatableSubaccountIds( return nil } -// nolint:unused func newContextWithQueryBlockHeight( ctx context.Context, blockHeight uint32, diff --git a/protocol/daemons/liquidation/client/grpc_helper_test.go b/protocol/daemons/liquidation/client/grpc_helper_test.go index 3707c3f45b..2e6f0ab041 100644 --- a/protocol/daemons/liquidation/client/grpc_helper_test.go +++ b/protocol/daemons/liquidation/client/grpc_helper_test.go @@ -93,7 +93,7 @@ func TestGetAllSubaccounts(t *testing.T) { setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { req := &satypes.QueryAllSubaccountRequest{ Pagination: &query.PageRequest{ - Limit: df.Liquidation.SubaccountPageLimit, + Limit: df.Liquidation.QueryPageLimit, }, } response := &satypes.QuerySubaccountAllResponse{ @@ -113,7 +113,7 @@ func TestGetAllSubaccounts(t *testing.T) { setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { req := &satypes.QueryAllSubaccountRequest{ Pagination: &query.PageRequest{ - Limit: df.Liquidation.SubaccountPageLimit, + Limit: df.Liquidation.QueryPageLimit, }, } nextKey := []byte("next key") @@ -129,7 +129,7 @@ func TestGetAllSubaccounts(t *testing.T) { req2 := &satypes.QueryAllSubaccountRequest{ Pagination: &query.PageRequest{ Key: nextKey, - Limit: df.Liquidation.SubaccountPageLimit, + Limit: df.Liquidation.QueryPageLimit, }, } response2 := &satypes.QuerySubaccountAllResponse{ @@ -148,7 +148,7 @@ func TestGetAllSubaccounts(t *testing.T) { setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { req := &satypes.QueryAllSubaccountRequest{ Pagination: &query.PageRequest{ - Limit: df.Liquidation.SubaccountPageLimit, + Limit: df.Liquidation.QueryPageLimit, }, } mck.On("SubaccountAll", ctx, req).Return(nil, errors.New("test error")) @@ -166,7 +166,7 @@ func TestGetAllSubaccounts(t *testing.T) { daemon.SubaccountQueryClient = queryClientMock actual, err := daemon.GetAllSubaccounts( grpc.Ctx, - df.Liquidation.SubaccountPageLimit, + df.Liquidation.QueryPageLimit, ) if err != nil { require.EqualError(t, err, tc.expectedError.Error()) @@ -258,7 +258,6 @@ func TestGetAllPerpetuals(t *testing.T) { daemon.PerpetualsQueryClient = queryClientMock actual, err := daemon.GetAllPerpetuals( grpc.Ctx, - uint32(50), tc.limit, ) if err != nil { @@ -347,7 +346,6 @@ func TestGetAllLiquidityTiers(t *testing.T) { daemon.PerpetualsQueryClient = queryClientMock actual, err := daemon.GetAllLiquidityTiers( grpc.Ctx, - uint32(50), tc.limit, ) if err != nil { @@ -441,7 +439,6 @@ func TestGetAllMarketPrices(t *testing.T) { daemon.PricesQueryClient = queryClientMock actual, err := daemon.GetAllMarketPrices( grpc.Ctx, - uint32(50), tc.limit, ) if err != nil { @@ -453,160 +450,109 @@ func TestGetAllMarketPrices(t *testing.T) { } } -func TestCheckCollateralizationForSubaccounts(t *testing.T) { - tests := map[string]struct { - // mocks - setupMocks func( - ctx context.Context, - mck *mocks.QueryClient, - results []clobtypes.AreSubaccountsLiquidatableResponse_Result, - ) - subaccountIds []satypes.SubaccountId - - // expectations - expectedResults []clobtypes.AreSubaccountsLiquidatableResponse_Result - expectedError error - }{ - "Success": { - setupMocks: func( - ctx context.Context, - mck *mocks.QueryClient, - results []clobtypes.AreSubaccountsLiquidatableResponse_Result, - ) { - query := &clobtypes.AreSubaccountsLiquidatableRequest{ - SubaccountIds: []satypes.SubaccountId{ - constants.Alice_Num0, - constants.Bob_Num0, - }, - } - response := &clobtypes.AreSubaccountsLiquidatableResponse{ - Results: results, - } - mck.On("AreSubaccountsLiquidatable", ctx, query).Return(response, nil) - }, - subaccountIds: []satypes.SubaccountId{ - constants.Alice_Num0, - constants.Bob_Num0, - }, - expectedResults: []clobtypes.AreSubaccountsLiquidatableResponse_Result{ - { - SubaccountId: constants.Alice_Num0, - IsLiquidatable: true, - }, - { - SubaccountId: constants.Bob_Num0, - IsLiquidatable: false, - }, - }, - }, - "Success - Empty": { - setupMocks: func( - ctx context.Context, - mck *mocks.QueryClient, - results []clobtypes.AreSubaccountsLiquidatableResponse_Result, - ) { - query := &clobtypes.AreSubaccountsLiquidatableRequest{ - SubaccountIds: []satypes.SubaccountId{}, - } - response := &clobtypes.AreSubaccountsLiquidatableResponse{ - Results: results, - } - mck.On("AreSubaccountsLiquidatable", ctx, query).Return(response, nil) - }, - subaccountIds: []satypes.SubaccountId{}, - expectedResults: []clobtypes.AreSubaccountsLiquidatableResponse_Result{}, - }, - "Errors are propagated": { - setupMocks: func( - ctx context.Context, - mck *mocks.QueryClient, - results []clobtypes.AreSubaccountsLiquidatableResponse_Result, - ) { - query := &clobtypes.AreSubaccountsLiquidatableRequest{ - SubaccountIds: []satypes.SubaccountId{}, - } - mck.On("AreSubaccountsLiquidatable", ctx, query).Return(nil, errors.New("test error")) - }, - subaccountIds: []satypes.SubaccountId{}, - expectedResults: []clobtypes.AreSubaccountsLiquidatableResponse_Result{}, - expectedError: errors.New("test error"), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - queryClientMock := &mocks.QueryClient{} - tc.setupMocks(grpc.Ctx, queryClientMock, tc.expectedResults) - - daemon := client.NewClient(log.NewNopLogger()) - daemon.ClobQueryClient = queryClientMock - actual, err := daemon.CheckCollateralizationForSubaccounts( - grpc.Ctx, - tc.subaccountIds, - ) - - if err != nil { - require.EqualError(t, err, tc.expectedError.Error()) - } else { - require.Equal(t, tc.expectedResults, actual) - } - }) - } -} - func TestSendLiquidatableSubaccountIds(t *testing.T) { tests := map[string]struct { // mocks - setupMocks func(ctx context.Context, mck *mocks.QueryClient, ids []satypes.SubaccountId) - subaccountIds []satypes.SubaccountId + setupMocks func(context.Context, *mocks.QueryClient) + liquidatableSubaccountIds []satypes.SubaccountId + negativeTncSubaccountIds []satypes.SubaccountId + subaccountOpenPositionInfo map[uint32]*clobtypes.SubaccountOpenPositionInfo // expectations expectedError error }{ "Success": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient, ids []satypes.SubaccountId) { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { req := &api.LiquidateSubaccountsRequest{ - LiquidatableSubaccountIds: ids, + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{constants.Alice_Num0, constants.Bob_Num0}, + NegativeTncSubaccountIds: []satypes.SubaccountId{constants.Carl_Num0, constants.Dave_Num0}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Alice_Num0, + constants.Carl_Num0, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Bob_Num0, + constants.Dave_Num0, + }, + }, + }, } response := &api.LiquidateSubaccountsResponse{} mck.On("LiquidateSubaccounts", ctx, req).Return(response, nil) }, - subaccountIds: []satypes.SubaccountId{ + liquidatableSubaccountIds: []satypes.SubaccountId{ constants.Alice_Num0, constants.Bob_Num0, }, + negativeTncSubaccountIds: []satypes.SubaccountId{ + constants.Carl_Num0, + constants.Dave_Num0, + }, + subaccountOpenPositionInfo: map[uint32]*clobtypes.SubaccountOpenPositionInfo{ + 0: { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Alice_Num0, + constants.Carl_Num0, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Bob_Num0, + constants.Dave_Num0, + }, + }, + }, }, "Success Empty": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient, ids []satypes.SubaccountId) { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { req := &api.LiquidateSubaccountsRequest{ - LiquidatableSubaccountIds: ids, + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{}, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{}, } response := &api.LiquidateSubaccountsResponse{} mck.On("LiquidateSubaccounts", ctx, req).Return(response, nil) }, - subaccountIds: []satypes.SubaccountId{}, + liquidatableSubaccountIds: []satypes.SubaccountId{}, + negativeTncSubaccountIds: []satypes.SubaccountId{}, + subaccountOpenPositionInfo: map[uint32]*clobtypes.SubaccountOpenPositionInfo{}, }, "Errors are propagated": { - setupMocks: func(ctx context.Context, mck *mocks.QueryClient, ids []satypes.SubaccountId) { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { req := &api.LiquidateSubaccountsRequest{ - LiquidatableSubaccountIds: ids, + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{}, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{}, } mck.On("LiquidateSubaccounts", ctx, req).Return(nil, errors.New("test error")) }, - subaccountIds: []satypes.SubaccountId{}, - expectedError: errors.New("test error"), + liquidatableSubaccountIds: []satypes.SubaccountId{}, + negativeTncSubaccountIds: []satypes.SubaccountId{}, + subaccountOpenPositionInfo: map[uint32]*clobtypes.SubaccountOpenPositionInfo{}, + expectedError: errors.New("test error"), }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { queryClientMock := &mocks.QueryClient{} - tc.setupMocks(grpc.Ctx, queryClientMock, tc.subaccountIds) + tc.setupMocks(grpc.Ctx, queryClientMock) daemon := client.NewClient(log.NewNopLogger()) daemon.LiquidationServiceClient = queryClientMock - err := daemon.SendLiquidatableSubaccountIds(grpc.Ctx, tc.subaccountIds) + err := daemon.SendLiquidatableSubaccountIds( + grpc.Ctx, + uint32(50), + tc.liquidatableSubaccountIds, + tc.negativeTncSubaccountIds, + tc.subaccountOpenPositionInfo, + ) require.Equal(t, tc.expectedError, err) }) } diff --git a/protocol/daemons/liquidation/client/sub_task_runner.go b/protocol/daemons/liquidation/client/sub_task_runner.go index e7ae323f79..e768db2819 100644 --- a/protocol/daemons/liquidation/client/sub_task_runner.go +++ b/protocol/daemons/liquidation/client/sub_task_runner.go @@ -2,12 +2,21 @@ package client import ( "context" + "math/big" "time" + errorsmod "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/telemetry" "github.com/dydxprotocol/v4-chain/protocol/daemons/flags" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" + assetstypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + clobkeeper "github.com/dydxprotocol/v4-chain/protocol/x/clob/keeper" + clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + perpkeeper "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/keeper" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" + sakeeper "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/keeper" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) @@ -40,24 +49,49 @@ func (s *SubTaskRunnerImpl) RunLiquidationDaemonTaskLoop( metrics.Latency, ) - // 1. Fetch all subaccounts from query service. - subaccounts, err := daemonClient.GetAllSubaccounts(ctx, liqFlags.SubaccountPageLimit) + lastCommittedBlockHeight, err := daemonClient.GetPreviousBlockInfo(ctx) if err != nil { return err } - // 2. Check collateralization statuses of subaccounts with at least one open position. - liquidatableSubaccountIds, err := daemonClient.GetLiquidatableSubaccountIds( + // 1. Fetch all information needed to calculate total net collateral and margin requirements. + subaccounts, + marketPrices, + perpetuals, + liquidityTiers, + err := daemonClient.FetchApplicationStateAtBlockHeight( ctx, + lastCommittedBlockHeight, liqFlags, + ) + if err != nil { + return err + } + + // 2. Check collateralization statuses of subaccounts with at least one open position. + liquidatableSubaccountIds, + negativeTncSubaccountIds, + err := daemonClient.GetLiquidatableSubaccountIds( subaccounts, + marketPrices, + perpetuals, + liquidityTiers, ) if err != nil { return err } + // Build a map of perpetual id to subaccounts with open positions in that perpetual. + subaccountOpenPositionInfo := daemonClient.GetSubaccountOpenPositionInfo(subaccounts) + // 3. Send the list of liquidatable subaccount ids to the daemon server. - err = daemonClient.SendLiquidatableSubaccountIds(ctx, liquidatableSubaccountIds) + err = daemonClient.SendLiquidatableSubaccountIds( + ctx, + lastCommittedBlockHeight, + liquidatableSubaccountIds, + negativeTncSubaccountIds, + subaccountOpenPositionInfo, + ) if err != nil { return err } @@ -65,14 +99,79 @@ func (s *SubTaskRunnerImpl) RunLiquidationDaemonTaskLoop( return nil } +// FetchApplicationStateAtBlockHeight queries a gRPC server and fetches the following information given a block height: +// - Last committed block height. +// - Subaccounts including their open positions. +// - Market prices. +// - Perpetuals. +// - Liquidity tiers. +func (c *Client) FetchApplicationStateAtBlockHeight( + ctx context.Context, + blockHeight uint32, + liqFlags flags.LiquidationFlags, +) ( + subaccounts []satypes.Subaccount, + marketPricesMap map[uint32]pricestypes.MarketPrice, + perpetualsMap map[uint32]perptypes.Perpetual, + liquidityTiersMap map[uint32]perptypes.LiquidityTier, + err error, +) { + defer telemetry.ModuleMeasureSince( + metrics.LiquidationDaemon, + time.Now(), + metrics.FetchApplicationStateAtBlockHeight, + metrics.Latency, + ) + + // Execute all queries at the given block height. + queryCtx := newContextWithQueryBlockHeight(ctx, blockHeight) + + // Subaccounts + subaccounts, err = c.GetAllSubaccounts(queryCtx, liqFlags.QueryPageLimit) + if err != nil { + return nil, nil, nil, nil, err + } + + // Market prices + marketPrices, err := c.GetAllMarketPrices(queryCtx, liqFlags.QueryPageLimit) + if err != nil { + return nil, nil, nil, nil, err + } + marketPricesMap = lib.UniqueSliceToMap(marketPrices, func(m pricestypes.MarketPrice) uint32 { + return m.Id + }) + + // Perpetuals + perpetuals, err := c.GetAllPerpetuals(queryCtx, liqFlags.QueryPageLimit) + if err != nil { + return nil, nil, nil, nil, err + } + perpetualsMap = lib.UniqueSliceToMap(perpetuals, func(p perptypes.Perpetual) uint32 { + return p.Params.Id + }) + + // Liquidity tiers + liquidityTiers, err := c.GetAllLiquidityTiers(queryCtx, liqFlags.QueryPageLimit) + if err != nil { + return nil, nil, nil, nil, err + } + liquidityTiersMap = lib.UniqueSliceToMap(liquidityTiers, func(l perptypes.LiquidityTier) uint32 { + return l.Id + }) + + return subaccounts, marketPricesMap, perpetualsMap, liquidityTiersMap, nil +} + // GetLiquidatableSubaccountIds verifies collateralization statuses of subaccounts with // at least one open position and returns a list of unique and potentially liquidatable subaccount ids. func (c *Client) GetLiquidatableSubaccountIds( - ctx context.Context, - liqFlags flags.LiquidationFlags, subaccounts []satypes.Subaccount, + marketPrices map[uint32]pricestypes.MarketPrice, + perpetuals map[uint32]perptypes.Perpetual, + liquidityTiers map[uint32]perptypes.LiquidityTier, ) ( liquidatableSubaccountIds []satypes.SubaccountId, + negativeTncSubaccountIds []satypes.SubaccountId, err error, ) { defer telemetry.ModuleMeasureSince( @@ -82,39 +181,192 @@ func (c *Client) GetLiquidatableSubaccountIds( metrics.Latency, ) - // Filter out subaccounts with no open positions. - subaccountsToCheck := make([]satypes.SubaccountId, 0) + liquidatableSubaccountIds = make([]satypes.SubaccountId, 0) + negativeTncSubaccountIds = make([]satypes.SubaccountId, 0) for _, subaccount := range subaccounts { - if len(subaccount.PerpetualPositions) > 0 { - subaccountsToCheck = append(subaccountsToCheck, *subaccount.Id) + // Skip subaccounts with no open positions. + if len(subaccount.PerpetualPositions) == 0 { + continue + } + + // Check if the subaccount is liquidatable. + isLiquidatable, hasNegativeTnc, err := c.CheckSubaccountCollateralization( + subaccount, + marketPrices, + perpetuals, + liquidityTiers, + ) + if err != nil { + c.logger.Error("Error checking collateralization status", "error", err) + return nil, nil, err + } + + if isLiquidatable { + liquidatableSubaccountIds = append(liquidatableSubaccountIds, *subaccount.Id) + } + if hasNegativeTnc { + negativeTncSubaccountIds = append(negativeTncSubaccountIds, *subaccount.Id) } } + return liquidatableSubaccountIds, negativeTncSubaccountIds, nil +} + +// GetSubaccountOpenPositionInfo iterates over the given subaccounts and returns a map of +// perpetual id to open position info. +func (c *Client) GetSubaccountOpenPositionInfo( + subaccounts []satypes.Subaccount, +) ( + subaccountOpenPositionInfo map[uint32]*clobtypes.SubaccountOpenPositionInfo, +) { + defer telemetry.ModuleMeasureSince( + metrics.LiquidationDaemon, + time.Now(), + metrics.GetSubaccountOpenPositionInfo, + metrics.Latency, + ) + + numSubaccountsWithOpenPositions := 0 + subaccountOpenPositionInfo = make(map[uint32]*clobtypes.SubaccountOpenPositionInfo) + for _, subaccount := range subaccounts { + // Skip subaccounts with no open positions. + if len(subaccount.PerpetualPositions) == 0 { + continue + } + + for _, perpetualPosition := range subaccount.PerpetualPositions { + openPositionInfo, ok := subaccountOpenPositionInfo[perpetualPosition.PerpetualId] + if !ok { + openPositionInfo = &clobtypes.SubaccountOpenPositionInfo{ + PerpetualId: perpetualPosition.PerpetualId, + SubaccountsWithLongPosition: make([]satypes.SubaccountId, 0), + SubaccountsWithShortPosition: make([]satypes.SubaccountId, 0), + } + subaccountOpenPositionInfo[perpetualPosition.PerpetualId] = openPositionInfo + } + + if perpetualPosition.GetIsLong() { + openPositionInfo.SubaccountsWithLongPosition = append( + openPositionInfo.SubaccountsWithLongPosition, + *subaccount.Id, + ) + } else { + openPositionInfo.SubaccountsWithShortPosition = append( + openPositionInfo.SubaccountsWithShortPosition, + *subaccount.Id, + ) + } + } + + numSubaccountsWithOpenPositions++ + } + telemetry.ModuleSetGauge( metrics.LiquidationDaemon, - float32(len(subaccountsToCheck)), + float32(numSubaccountsWithOpenPositions), metrics.SubaccountsWithOpenPositions, metrics.Count, ) - // Query the gRPC server in chunks of size `liqFlags.RequestChunkSize`. - liquidatableSubaccountIds = make([]satypes.SubaccountId, 0) - for start := 0; start < len(subaccountsToCheck); start += int(liqFlags.RequestChunkSize) { - end := lib.Min(start+int(liqFlags.RequestChunkSize), len(subaccountsToCheck)) + return subaccountOpenPositionInfo +} - results, err := c.CheckCollateralizationForSubaccounts( - ctx, - subaccountsToCheck[start:end], - ) - if err != nil { - return nil, err +// CheckSubaccountCollateralization performs the same collateralization check as the application +// using the provided market prices, perpetuals, and liquidity tiers. +// +// Note that current implementation assumes that the only asset is USDC and multi-collateral support +// is not yet implemented. +func (c *Client) CheckSubaccountCollateralization( + unsettledSubaccount satypes.Subaccount, + marketPrices map[uint32]pricestypes.MarketPrice, + perpetuals map[uint32]perptypes.Perpetual, + liquidityTiers map[uint32]perptypes.LiquidityTier, +) ( + isLiquidatable bool, + hasNegativeTnc bool, + err error, +) { + defer telemetry.ModuleMeasureSince( + metrics.LiquidationDaemon, + time.Now(), + metrics.CheckCollateralizationForSubaccounts, + metrics.Latency, + ) + + // Funding payments are lazily settled, so get the settled subaccount + // to ensure that the funding payments are included in the net collateral calculation. + settledSubaccount, _, err := sakeeper.GetSettledSubaccountWithPerpetuals( + unsettledSubaccount, + perpetuals, + ) + if err != nil { + return false, false, err + } + + bigTotalNetCollateral := big.NewInt(0) + bigTotalMaintenanceMargin := big.NewInt(0) + + // Calculate the net collateral and maintenance margin for each of the asset positions. + // Note that we only expect USDC before multi-collateral support is added. + for _, assetPosition := range settledSubaccount.AssetPositions { + if assetPosition.AssetId != assetstypes.AssetUsdc.Id { + return false, false, errorsmod.Wrapf( + assetstypes.ErrNotImplementedMulticollateral, + "Asset %d is not supported", + assetPosition.AssetId, + ) } + // Net collateral for USDC is the quantums of the position. + // Margin requirements for USDC are zero. + bigTotalNetCollateral.Add(bigTotalNetCollateral, assetPosition.GetBigQuantums()) + } - for _, result := range results { - if result.IsLiquidatable { - liquidatableSubaccountIds = append(liquidatableSubaccountIds, result.SubaccountId) - } + // Calculate the net collateral and maintenance margin for each of the perpetual positions. + for _, perpetualPosition := range settledSubaccount.PerpetualPositions { + perpetual, ok := perpetuals[perpetualPosition.PerpetualId] + if !ok { + return false, false, errorsmod.Wrapf( + perptypes.ErrPerpetualDoesNotExist, + "Perpetual not found for perpetual id %d", + perpetualPosition.PerpetualId, + ) + } + + marketPrice, ok := marketPrices[perpetual.Params.MarketId] + if !ok { + return false, false, errorsmod.Wrapf( + pricestypes.ErrMarketPriceDoesNotExist, + "MarketPrice not found for perpetual %+v", + perpetual, + ) + } + + bigQuantums := perpetualPosition.GetBigQuantums() + + // Get the net collateral for the position. + bigNetCollateralQuoteQuantums := perpkeeper.GetNetNotionalInQuoteQuantums(perpetual, marketPrice, bigQuantums) + bigTotalNetCollateral.Add(bigTotalNetCollateral, bigNetCollateralQuoteQuantums) + + liquidityTier, ok := liquidityTiers[perpetual.Params.LiquidityTier] + if !ok { + return false, false, errorsmod.Wrapf( + perptypes.ErrLiquidityTierDoesNotExist, + "LiquidityTier not found for perpetual %+v", + perpetual, + ) } + + // Get the maintenance margin requirement for the position. + _, bigMaintenanceMarginQuoteQuantums := perpkeeper.GetMarginRequirementsInQuoteQuantums( + perpetual, + marketPrice, + liquidityTier, + bigQuantums, + ) + bigTotalMaintenanceMargin.Add(bigTotalMaintenanceMargin, bigMaintenanceMarginQuoteQuantums) } - return liquidatableSubaccountIds, nil + + return clobkeeper.CanLiquidateSubaccount(bigTotalNetCollateral, bigTotalMaintenanceMargin), + bigTotalNetCollateral.Sign() == -1, + nil } diff --git a/protocol/daemons/liquidation/client/sub_task_runner_test.go b/protocol/daemons/liquidation/client/sub_task_runner_test.go new file mode 100644 index 0000000000..2914e3389d --- /dev/null +++ b/protocol/daemons/liquidation/client/sub_task_runner_test.go @@ -0,0 +1,735 @@ +package client_test + +import ( + "context" + "testing" + + "github.com/cometbft/cometbft/libs/log" + "github.com/dydxprotocol/v4-chain/protocol/daemons/flags" + "github.com/dydxprotocol/v4-chain/protocol/daemons/liquidation/api" + "github.com/dydxprotocol/v4-chain/protocol/daemons/liquidation/client" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/mocks" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + "github.com/dydxprotocol/v4-chain/protocol/testutil/grpc" + blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" + clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestRunLiquidationDaemonTaskLoop(t *testing.T) { + tests := map[string]struct { + // mocks + setupMocks func(ctx context.Context, mck *mocks.QueryClient) + + // expectations + expectedLiquidatableSubaccountIds []satypes.SubaccountId + expectedError error + }{ + "Can get liquidatable subaccount with short position": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + constants.Carl_Num0_1BTC_Short_54999USD, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{}, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Can get liquidatable subaccount with long position": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + constants.Dave_Num0_1BTC_Long_45001USD_Short, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{}, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Skip well collateralized subaccounts": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + constants.Carl_Num0_1BTC_Short_55000USD, + constants.Dave_Num0_1BTC_Long_45000USD_Short, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{}, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Skip subaccounts with no open positions": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + constants.Alice_Num0_100_000USD, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{}, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{}, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Can get subaccount that become undercollateralized with funding payments (short)": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + // Without funding, Carl has a TNC of $5,000, MMR of $5,000, and is + // well-collateralized. + // However, funding index for Carl's position is 10,000 and perpetual's funding index + // is 0. Index delta is -10,000, so Carl has to make a funding payment of $1 and + // become under-collateralized. + { + Id: &constants.Carl_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(55_000_000_000), // $55,000 + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 0, + Quantums: dtypes.NewInt(-100_000_000), // -1 BTC + FundingIndex: dtypes.NewInt(10_000), + }, + }, + }, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{}, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Can get subaccount that become liquidatable with funding payments (long)": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + // Without funding, Dave has a TNC of $5,000, MMR of $5,000, and is + // well-collateralized. + // However, funding index for Dave's position is -10,000 and perpetual's funding index + // is 0. Index delta is 10,000, so Dave has to make a funding payment of $1 and + // become under-collateralized. + { + Id: &constants.Dave_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(-45_000_000_000), // -$45,000 + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 0, + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + FundingIndex: dtypes.NewInt(-10_000), + }, + }, + }, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{}, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Skips subaccount that become well-collateralized with funding payments (short)": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + // Without funding, Carl has a TNC of $4,999, MMR of $5,000, and is + // under-collateralized. + // However, funding index for Carl's position is -10,000 and perpetual's funding index + // is 0. Index delta is 10,000, so Carl would receive a funding payment of $1 and + // become well-collateralized. + { + Id: &constants.Carl_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(54_999_000_000), // $54,999 + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 0, + Quantums: dtypes.NewInt(-100_000_000), // -1 BTC + FundingIndex: dtypes.NewInt(-10_000), + }, + }, + }, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{}, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{}, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Skips subaccount that become well-collateralized with funding payments (long)": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + // Without funding, Dave has a TNC of $4,999, MMR of $5,000, and is + // under-collateralized. + // However, funding index for Dave's position is 10,000 and perpetual's funding index + // is 0. Index delta is -10,000, so Dave would receive a funding payment of $1 and + // become well-collateralized. + { + Id: &constants.Dave_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(-44_999_000_000), // -$44,999 + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 0, + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + FundingIndex: dtypes.NewInt(10_000), + }, + }, + }, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{}, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{}, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Can get negative tnc subaccount with short position": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + // Carl has TNC of -$1. + constants.Carl_Num0_1BTC_Short_49999USD, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + NegativeTncSubaccountIds: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{}, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + "Can get negative tnc subaccount with long position": { + setupMocks: func(ctx context.Context, mck *mocks.QueryClient) { + // Block height. + res := &blocktimetypes.QueryPreviousBlockInfoResponse{ + Info: &blocktimetypes.BlockInfo{ + Height: uint32(50), + Timestamp: constants.TimeTen, + }, + } + mck.On("PreviousBlockInfo", mock.Anything, mock.Anything).Return(res, nil) + + // Subaccount. + res2 := &satypes.QuerySubaccountAllResponse{ + Subaccount: []satypes.Subaccount{ + // Dave has TNC of -$1. + constants.Dave_Num0_1BTC_Long_50001USD_Short, + }, + } + mck.On("SubaccountAll", mock.Anything, mock.Anything).Return(res2, nil) + + // Market prices. + res3 := &pricestypes.QueryAllMarketPricesResponse{ + MarketPrices: constants.TestMarketPrices, + } + mck.On("AllMarketPrices", mock.Anything, mock.Anything).Return(res3, nil) + + // Perpetuals. + res4 := &perptypes.QueryAllPerpetualsResponse{ + Perpetual: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + }, + } + mck.On("AllPerpetuals", mock.Anything, mock.Anything).Return(res4, nil) + + // Liquidity tiers. + res5 := &perptypes.QueryAllLiquidityTiersResponse{ + LiquidityTiers: constants.LiquidityTiers, + } + mck.On("AllLiquidityTiers", mock.Anything, mock.Anything).Return(res5, nil) + + // Sends liquidatable subaccount ids to the server. + req := &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(50), + LiquidatableSubaccountIds: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + NegativeTncSubaccountIds: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + SubaccountOpenPositionInfo: []clobtypes.SubaccountOpenPositionInfo{ + { + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Dave_Num0, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{}, + }, + }, + } + response3 := &api.LiquidateSubaccountsResponse{} + mck.On("LiquidateSubaccounts", ctx, req).Return(response3, nil) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + queryClientMock := &mocks.QueryClient{} + tc.setupMocks(grpc.Ctx, queryClientMock) + s := client.SubTaskRunnerImpl{} + + c := client.NewClient(log.NewNopLogger()) + c.SubaccountQueryClient = queryClientMock + c.ClobQueryClient = queryClientMock + c.LiquidationServiceClient = queryClientMock + c.PerpetualsQueryClient = queryClientMock + c.PricesQueryClient = queryClientMock + c.BlocktimeQueryClient = queryClientMock + + err := s.RunLiquidationDaemonTaskLoop( + grpc.Ctx, + c, + flags.GetDefaultDaemonFlags().Liquidation, + ) + if tc.expectedError != nil { + require.EqualError(t, err, tc.expectedError.Error()) + } else { + require.NoError(t, err) + queryClientMock.AssertExpectations(t) + } + }) + } +} diff --git a/protocol/daemons/server/liquidation.go b/protocol/daemons/server/liquidation.go index 753609ccb8..573ede7eb7 100644 --- a/protocol/daemons/server/liquidation.go +++ b/protocol/daemons/server/liquidation.go @@ -42,7 +42,10 @@ func (s *Server) LiquidateSubaccounts( metrics.Count, ) + s.daemonLiquidationInfo.UpdateBlockHeight(req.BlockHeight) s.daemonLiquidationInfo.UpdateLiquidatableSubaccountIds(req.LiquidatableSubaccountIds) + s.daemonLiquidationInfo.UpdateNegativeTncSubaccountIds(req.NegativeTncSubaccountIds) + s.daemonLiquidationInfo.UpdateSubaccountsWithPositions(req.SubaccountOpenPositionInfo) // Capture valid responses in metrics. s.reportValidResponse(types.LiquidationsDaemonServiceName) diff --git a/protocol/daemons/server/liquidation_test.go b/protocol/daemons/server/liquidation_test.go index f6b3186294..7fca88b369 100644 --- a/protocol/daemons/server/liquidation_test.go +++ b/protocol/daemons/server/liquidation_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestLiquidateSubaccounts_Empty_Update(t *testing.T) { +func TestLiquidateSubaccounts_Empty_Update_Liquidatable_SubaccountIds(t *testing.T) { mockGrpcServer := &mocks.GrpcServer{} mockFileHandler := &mocks.FileHandler{} daemonLiquidationInfo := liquidationtypes.NewDaemonLiquidationInfo() @@ -31,7 +31,7 @@ func TestLiquidateSubaccounts_Empty_Update(t *testing.T) { require.Empty(t, daemonLiquidationInfo.GetLiquidatableSubaccountIds()) } -func TestLiquidateSubaccounts_Multiple_Subaccount_Ids(t *testing.T) { +func TestLiquidateSubaccounts_Multiple_Liquidatable_Subaccount_Ids(t *testing.T) { mockGrpcServer := &mocks.GrpcServer{} mockFileHandler := &mocks.FileHandler{} daemonLiquidationInfo := liquidationtypes.NewDaemonLiquidationInfo() @@ -57,3 +57,69 @@ func TestLiquidateSubaccounts_Multiple_Subaccount_Ids(t *testing.T) { actualSubaccountIds := daemonLiquidationInfo.GetLiquidatableSubaccountIds() require.Equal(t, expectedSubaccountIds, actualSubaccountIds) } + +func TestLiquidateSubaccounts_GetSetBlockHeight(t *testing.T) { + mockGrpcServer := &mocks.GrpcServer{} + mockFileHandler := &mocks.FileHandler{} + daemonLiquidationInfo := liquidationtypes.NewDaemonLiquidationInfo() + + s := createServerWithMocks( + t, + mockGrpcServer, + mockFileHandler, + ).WithDaemonLiquidationInfo( + daemonLiquidationInfo, + ) + _, err := s.LiquidateSubaccounts(grpc.Ctx, &api.LiquidateSubaccountsRequest{ + BlockHeight: uint32(123), + LiquidatableSubaccountIds: []satypes.SubaccountId{}, + }) + require.NoError(t, err) + require.Equal(t, uint32(123), daemonLiquidationInfo.GetBlockHeight()) +} + +func TestLiquidateSubaccounts_Empty_Update_Negative_TNC_SubaccountIds(t *testing.T) { + mockGrpcServer := &mocks.GrpcServer{} + mockFileHandler := &mocks.FileHandler{} + daemonLiquidationInfo := liquidationtypes.NewDaemonLiquidationInfo() + + s := createServerWithMocks( + t, + mockGrpcServer, + mockFileHandler, + ).WithDaemonLiquidationInfo( + daemonLiquidationInfo, + ) + _, err := s.LiquidateSubaccounts(grpc.Ctx, &api.LiquidateSubaccountsRequest{ + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + }) + require.NoError(t, err) + require.Empty(t, daemonLiquidationInfo.GetNegativeTncSubaccountIds()) +} + +func TestLiquidateSubaccounts_Multiple_Negative_TNC_Subaccount_Ids(t *testing.T) { + mockGrpcServer := &mocks.GrpcServer{} + mockFileHandler := &mocks.FileHandler{} + daemonLiquidationInfo := liquidationtypes.NewDaemonLiquidationInfo() + + s := createServerWithMocks( + t, + mockGrpcServer, + mockFileHandler, + ).WithDaemonLiquidationInfo( + daemonLiquidationInfo, + ) + + expectedSubaccountIds := []satypes.SubaccountId{ + constants.Alice_Num1, + constants.Bob_Num0, + constants.Carl_Num0, + } + _, err := s.LiquidateSubaccounts(grpc.Ctx, &api.LiquidateSubaccountsRequest{ + NegativeTncSubaccountIds: expectedSubaccountIds, + }) + require.NoError(t, err) + + actualSubaccountIds := daemonLiquidationInfo.GetNegativeTncSubaccountIds() + require.Equal(t, expectedSubaccountIds, actualSubaccountIds) +} diff --git a/protocol/daemons/server/types/liquidations/daemon_liquidation_info.go b/protocol/daemons/server/types/liquidations/daemon_liquidation_info.go index a416bfb499..68f887abf7 100644 --- a/protocol/daemons/server/types/liquidations/daemon_liquidation_info.go +++ b/protocol/daemons/server/types/liquidations/daemon_liquidation_info.go @@ -80,20 +80,20 @@ func (ls *DaemonLiquidationInfo) GetNegativeTncSubaccountIds() []satypes.Subacco // UpdateSubaccountsWithPositions updates the struct with the given a list of subaccount ids with open positions. func (ls *DaemonLiquidationInfo) UpdateSubaccountsWithPositions( - subaccountsWithPositions map[uint32]*clobtypes.SubaccountOpenPositionInfo, + subaccountsWithPositions []clobtypes.SubaccountOpenPositionInfo, ) { ls.Lock() defer ls.Unlock() ls.subaccountsWithPositions = make(map[uint32]*clobtypes.SubaccountOpenPositionInfo) - for perpetualId, info := range subaccountsWithPositions { + for _, info := range subaccountsWithPositions { clone := &clobtypes.SubaccountOpenPositionInfo{ - PerpetualId: perpetualId, + PerpetualId: info.PerpetualId, SubaccountsWithLongPosition: make([]satypes.SubaccountId, len(info.SubaccountsWithLongPosition)), SubaccountsWithShortPosition: make([]satypes.SubaccountId, len(info.SubaccountsWithShortPosition)), } copy(clone.SubaccountsWithLongPosition, info.SubaccountsWithLongPosition) copy(clone.SubaccountsWithShortPosition, info.SubaccountsWithShortPosition) - ls.subaccountsWithPositions[perpetualId] = clone + ls.subaccountsWithPositions[info.PerpetualId] = clone } } diff --git a/protocol/daemons/server/types/liquidations/daemon_liquidation_info_test.go b/protocol/daemons/server/types/liquidations/daemon_liquidation_info_test.go index 87b6b275b3..e699d28473 100644 --- a/protocol/daemons/server/types/liquidations/daemon_liquidation_info_test.go +++ b/protocol/daemons/server/types/liquidations/daemon_liquidation_info_test.go @@ -49,18 +49,22 @@ func TestSubaccountsWithOpenPositions_Multiple_Reads(t *testing.T) { ls := liquidationstypes.NewDaemonLiquidationInfo() require.Empty(t, ls.GetNegativeTncSubaccountIds()) - expected := map[uint32]*clobtypes.SubaccountOpenPositionInfo{ - 0: { - PerpetualId: 0, - SubaccountsWithLongPosition: []satypes.SubaccountId{ - constants.Alice_Num1, - }, - SubaccountsWithShortPosition: []satypes.SubaccountId{ - constants.Bob_Num0, - }, + info := clobtypes.SubaccountOpenPositionInfo{ + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Alice_Num1, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Bob_Num0, }, } - ls.UpdateSubaccountsWithPositions(expected) + + input := []clobtypes.SubaccountOpenPositionInfo{info} + ls.UpdateSubaccountsWithPositions(input) + + expected := map[uint32]*clobtypes.SubaccountOpenPositionInfo{ + 0: &info, + } require.Equal(t, expected, ls.GetSubaccountsWithPositions()) require.Equal(t, expected, ls.GetSubaccountsWithPositions()) require.Equal(t, expected, ls.GetSubaccountsWithPositions()) @@ -116,46 +120,55 @@ func TestSubaccountsWithOpenPositions_Multiple_Writes(t *testing.T) { ls := liquidationstypes.NewDaemonLiquidationInfo() require.Empty(t, ls.GetSubaccountsWithPositions()) - expected := map[uint32]*clobtypes.SubaccountOpenPositionInfo{ - 0: { - PerpetualId: 0, - SubaccountsWithLongPosition: []satypes.SubaccountId{ - constants.Alice_Num1, - }, - SubaccountsWithShortPosition: []satypes.SubaccountId{ - constants.Bob_Num0, - }, + info := clobtypes.SubaccountOpenPositionInfo{ + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Alice_Num1, }, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Bob_Num0, + }, + } + + input := []clobtypes.SubaccountOpenPositionInfo{info} + ls.UpdateSubaccountsWithPositions(input) + expected := map[uint32]*clobtypes.SubaccountOpenPositionInfo{ + 0: &info, } - ls.UpdateSubaccountsWithPositions(expected) require.Equal(t, expected, ls.GetSubaccountsWithPositions()) - expected = map[uint32]*clobtypes.SubaccountOpenPositionInfo{ - 0: { - PerpetualId: 0, - SubaccountsWithLongPosition: []satypes.SubaccountId{ - constants.Carl_Num0, - }, - SubaccountsWithShortPosition: []satypes.SubaccountId{ - constants.Dave_Num0, - }, + info2 := clobtypes.SubaccountOpenPositionInfo{ + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Carl_Num0, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Dave_Num0, }, } - ls.UpdateSubaccountsWithPositions(expected) - require.Equal(t, expected, ls.GetSubaccountsWithPositions()) + input2 := []clobtypes.SubaccountOpenPositionInfo{info2} + ls.UpdateSubaccountsWithPositions(input2) expected = map[uint32]*clobtypes.SubaccountOpenPositionInfo{ - 0: { - PerpetualId: 0, - SubaccountsWithLongPosition: []satypes.SubaccountId{ - constants.Dave_Num1, - }, - SubaccountsWithShortPosition: []satypes.SubaccountId{ - constants.Alice_Num1, - }, + 0: &info2, + } + require.Equal(t, expected, ls.GetSubaccountsWithPositions()) + + info3 := clobtypes.SubaccountOpenPositionInfo{ + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Dave_Num1, + }, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Alice_Num1, }, } - ls.UpdateSubaccountsWithPositions(expected) + + input3 := []clobtypes.SubaccountOpenPositionInfo{info3} + ls.UpdateSubaccountsWithPositions(input3) + expected = map[uint32]*clobtypes.SubaccountOpenPositionInfo{ + 0: &info3, + } require.Equal(t, expected, ls.GetSubaccountsWithPositions()) } @@ -193,21 +206,23 @@ func TestSubaccountsWithOpenPosition_Empty_Update(t *testing.T) { ls := liquidationstypes.NewDaemonLiquidationInfo() require.Empty(t, ls.GetSubaccountsWithPositions()) - expected := map[uint32]*clobtypes.SubaccountOpenPositionInfo{ - 0: { - PerpetualId: 0, - SubaccountsWithLongPosition: []satypes.SubaccountId{ - constants.Alice_Num1, - }, - SubaccountsWithShortPosition: []satypes.SubaccountId{ - constants.Bob_Num0, - }, + info := clobtypes.SubaccountOpenPositionInfo{ + PerpetualId: 0, + SubaccountsWithLongPosition: []satypes.SubaccountId{ + constants.Alice_Num1, }, + SubaccountsWithShortPosition: []satypes.SubaccountId{ + constants.Bob_Num0, + }, + } + input := []clobtypes.SubaccountOpenPositionInfo{info} + ls.UpdateSubaccountsWithPositions(input) + expected := map[uint32]*clobtypes.SubaccountOpenPositionInfo{ + 0: &info, } - ls.UpdateSubaccountsWithPositions(expected) require.Equal(t, expected, ls.GetSubaccountsWithPositions()) - expected = map[uint32]*clobtypes.SubaccountOpenPositionInfo{} - ls.UpdateSubaccountsWithPositions(expected) + input2 := []clobtypes.SubaccountOpenPositionInfo{} + ls.UpdateSubaccountsWithPositions(input2) require.Empty(t, ls.GetSubaccountsWithPositions()) } diff --git a/protocol/lib/collections.go b/protocol/lib/collections.go index a00a0347df..5f57e7ff42 100644 --- a/protocol/lib/collections.go +++ b/protocol/lib/collections.go @@ -50,6 +50,24 @@ func UniqueSliceToSet[K comparable](values []K) map[K]struct{} { return set } +// UniqueSliceToMap converts a slice to a map using the provided keyFunc to generate the key. +func UniqueSliceToMap[K comparable, V any](slice []V, keyFunc func(V) K) map[K]V { + m := make(map[K]V) + for _, v := range slice { + k := keyFunc(v) + if _, exists := m[k]; exists { + panic( + fmt.Sprintf( + "UniqueSliceToMap: duplicate value: %+v", + v, + ), + ) + } + m[k] = v + } + return m +} + // MapSlice takes a function and executes that function on each element of a slice, returning the result. // Note the function must return one result for each element of the slice. func MapSlice[V any, E any](values []V, mapFunc func(V) E) []E { diff --git a/protocol/lib/collections_test.go b/protocol/lib/collections_test.go index 14878e8365..6b0f75443a 100644 --- a/protocol/lib/collections_test.go +++ b/protocol/lib/collections_test.go @@ -104,6 +104,58 @@ func TestUniqueSliceToSet(t *testing.T) { } } +func TestUniqueSliceToMap(t *testing.T) { + type testStruct struct { + Id uint32 + } + + tests := map[string]struct { + input []testStruct + expected map[uint32]testStruct + panicWith string + }{ + "Empty": { + input: []testStruct{}, + expected: map[uint32]testStruct{}, + }, + "Basic": { + input: []testStruct{ + {Id: 0}, {Id: 1}, {Id: 2}, + }, + expected: map[uint32]testStruct{ + 0: {Id: 0}, + 1: {Id: 1}, + 2: {Id: 2}, + }, + }, + "Duplicate": { + input: []testStruct{ + {Id: 0}, {Id: 0}, + }, + panicWith: "UniqueSliceToMap: duplicate value: {Id:0}", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + if tc.panicWith != "" { + require.PanicsWithValue( + t, + tc.panicWith, + func() { + lib.UniqueSliceToMap(tc.input, func(t testStruct) uint32 { return t.Id }) + }, + ) + } else { + require.Equal( + t, + tc.expected, + lib.UniqueSliceToMap(tc.input, func(t testStruct) uint32 { return t.Id }), + ) + } + }) + } +} + func TestMapSlice(t *testing.T) { // Can increment all numbers in a slice by 1, and change type to `uint64`. require.Equal( diff --git a/protocol/lib/metrics/constants.go b/protocol/lib/metrics/constants.go index 2b7c511198..ee3f61a02e 100644 --- a/protocol/lib/metrics/constants.go +++ b/protocol/lib/metrics/constants.go @@ -293,11 +293,14 @@ const ( // Liquidation Daemon. CheckCollateralizationForSubaccounts = "check_collateralization_for_subaccounts" + FetchApplicationStateAtBlockHeight = "fetch_application_state_at_block_height" GetAllSubaccounts = "get_all_subaccounts" GetLiquidatableSubaccountIds = "get_liquidatable_subaccount_ids" + GetSubaccountOpenPositionInfo = "get_subaccount_open_position_info" GetSubaccountsFromKey = "get_subaccounts_from_key" LiquidatableSubaccountIds = "liquidatable_subaccount_ids" LiquidationDaemon = "liquidation_daemon" + NegativeTncSubaccountIds = "negative_tnc_subaccount_ids" PageLimit = "page_limit" SendLiquidatableSubaccountIds = "send_liquidatable_subaccount_ids" SubaccountsWithOpenPositions = "subaccounts_with_open_positions" diff --git a/protocol/mocks/QueryClient.go b/protocol/mocks/QueryClient.go index 45a231ab3d..995b2ea62c 100644 --- a/protocol/mocks/QueryClient.go +++ b/protocol/mocks/QueryClient.go @@ -210,36 +210,6 @@ func (_m *QueryClient) AllPerpetuals(ctx context.Context, in *perpetualstypes.Qu return r0, r1 } -// AreSubaccountsLiquidatable provides a mock function with given fields: ctx, in, opts -func (_m *QueryClient) AreSubaccountsLiquidatable(ctx context.Context, in *clobtypes.AreSubaccountsLiquidatableRequest, opts ...grpc.CallOption) (*clobtypes.AreSubaccountsLiquidatableResponse, error) { - _va := make([]interface{}, len(opts)) - for _i := range opts { - _va[_i] = opts[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, in) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - var r0 *clobtypes.AreSubaccountsLiquidatableResponse - if rf, ok := ret.Get(0).(func(context.Context, *clobtypes.AreSubaccountsLiquidatableRequest, ...grpc.CallOption) *clobtypes.AreSubaccountsLiquidatableResponse); ok { - r0 = rf(ctx, in, opts...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*clobtypes.AreSubaccountsLiquidatableResponse) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(context.Context, *clobtypes.AreSubaccountsLiquidatableRequest, ...grpc.CallOption) error); ok { - r1 = rf(ctx, in, opts...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // BlockRateLimitConfiguration provides a mock function with given fields: ctx, in, opts func (_m *QueryClient) BlockRateLimitConfiguration(ctx context.Context, in *clobtypes.QueryBlockRateLimitConfigurationRequest, opts ...grpc.CallOption) (*clobtypes.QueryBlockRateLimitConfigurationResponse, error) { _va := make([]interface{}, len(opts)) diff --git a/protocol/testutil/constants/perpetuals.go b/protocol/testutil/constants/perpetuals.go index ccf11f1189..9f9739d64c 100644 --- a/protocol/testutil/constants/perpetuals.go +++ b/protocol/testutil/constants/perpetuals.go @@ -215,6 +215,7 @@ var ( DefaultFundingPpm: int32(0), LiquidityTier: uint32(3), }, + FundingIndex: dtypes.ZeroInt(), } BtcUsd_NoMarginRequirement = perptypes.Perpetual{ Params: perptypes.PerpetualParams{ diff --git a/protocol/testutil/constants/subaccounts.go b/protocol/testutil/constants/subaccounts.go index 28575e8c0d..832db4057d 100644 --- a/protocol/testutil/constants/subaccounts.go +++ b/protocol/testutil/constants/subaccounts.go @@ -141,8 +141,9 @@ var ( }, PerpetualPositions: []*satypes.PerpetualPosition{ { - PerpetualId: 0, - Quantums: dtypes.NewInt(-100_000_000), // -1 BTC + PerpetualId: 0, + Quantums: dtypes.NewInt(-100_000_000), // -1 BTC + FundingIndex: dtypes.NewInt(0), }, }, } @@ -171,8 +172,9 @@ var ( }, PerpetualPositions: []*satypes.PerpetualPosition{ { - PerpetualId: 0, - Quantums: dtypes.NewInt(-100_000_000), // -1 BTC + PerpetualId: 0, + Quantums: dtypes.NewInt(-100_000_000), // -1 BTC + FundingIndex: dtypes.NewInt(0), }, }, } @@ -328,6 +330,22 @@ var ( }, }, } + Dave_Num0_1BTC_Long_45000USD_Short = satypes.Subaccount{ + Id: &Dave_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(-45_000_000_000), // -$45,000 + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 0, + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + FundingIndex: dtypes.NewInt(0), + }, + }, + } Dave_Num0_1BTC_Long_45001USD_Short = satypes.Subaccount{ Id: &Dave_Num0, AssetPositions: []*satypes.AssetPosition{ @@ -338,8 +356,9 @@ var ( }, PerpetualPositions: []*satypes.PerpetualPosition{ { - PerpetualId: 0, - Quantums: dtypes.NewInt(100_000_000), // 1 BTC + PerpetualId: 0, + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + FundingIndex: dtypes.NewInt(0), }, }, } @@ -384,8 +403,9 @@ var ( }, PerpetualPositions: []*satypes.PerpetualPosition{ { - PerpetualId: 0, - Quantums: dtypes.NewInt(100_000_000), // 1 BTC + PerpetualId: 0, + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + FundingIndex: dtypes.NewInt(0), }, }, } diff --git a/protocol/x/clob/keeper/grpc_query_are_subaccounts_liquidatable.go b/protocol/x/clob/keeper/grpc_query_are_subaccounts_liquidatable.go deleted file mode 100644 index 7713c4dbe5..0000000000 --- a/protocol/x/clob/keeper/grpc_query_are_subaccounts_liquidatable.go +++ /dev/null @@ -1,40 +0,0 @@ -package keeper - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func (k Keeper) AreSubaccountsLiquidatable( - c context.Context, - req *types.AreSubaccountsLiquidatableRequest, -) ( - *types.AreSubaccountsLiquidatableResponse, - error, -) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "invalid request") - } - - ctx := sdk.UnwrapSDKContext(c) - - results := make([]types.AreSubaccountsLiquidatableResponse_Result, len(req.SubaccountIds)) - for i, subaccountId := range req.SubaccountIds { - isLiquidatable, err := k.IsLiquidatable(ctx, subaccountId) - - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - results[i] = types.AreSubaccountsLiquidatableResponse_Result{ - SubaccountId: subaccountId, - IsLiquidatable: isLiquidatable, - } - } - return &types.AreSubaccountsLiquidatableResponse{ - Results: results, - }, nil -} diff --git a/protocol/x/clob/keeper/grpc_query_are_subaccounts_liquidatable_test.go b/protocol/x/clob/keeper/grpc_query_are_subaccounts_liquidatable_test.go deleted file mode 100644 index 1194fd7b30..0000000000 --- a/protocol/x/clob/keeper/grpc_query_are_subaccounts_liquidatable_test.go +++ /dev/null @@ -1,166 +0,0 @@ -package keeper_test - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" - - "github.com/dydxprotocol/v4-chain/protocol/dtypes" - "github.com/dydxprotocol/v4-chain/protocol/mocks" - "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" - keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" - "github.com/dydxprotocol/v4-chain/protocol/testutil/nullify" - "github.com/dydxprotocol/v4-chain/protocol/x/clob/memclob" - "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" - perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" - satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" -) - -func TestAreSubaccountsLiquidatable(t *testing.T) { - for _, tc := range []struct { - desc string - subaccounts []satypes.Subaccount - perpetuals []perptypes.Perpetual - request *types.AreSubaccountsLiquidatableRequest - response *types.AreSubaccountsLiquidatableResponse - err error - }{ - { - desc: "No errors", - perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_100PercentMarginRequirement, - }, - subaccounts: []satypes.Subaccount{ - { - Id: &constants.Alice_Num0, - AssetPositions: []*satypes.AssetPosition{ - &constants.Usdc_Asset_10_000, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(-1_000_000_000), // 1 BTC - }, - }, - }, - { - Id: &constants.Bob_Num0, - AssetPositions: []*satypes.AssetPosition{ - &constants.Usdc_Asset_10_000, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(100_000_000), // 1 BTC - }, - }, - }, - }, - request: &types.AreSubaccountsLiquidatableRequest{ - SubaccountIds: []satypes.SubaccountId{ - constants.Alice_Num0, - constants.Bob_Num0, - }, - }, - response: &types.AreSubaccountsLiquidatableResponse{ - Results: []types.AreSubaccountsLiquidatableResponse_Result{ - { - SubaccountId: constants.Alice_Num0, - IsLiquidatable: true, - }, - { - SubaccountId: constants.Bob_Num0, - IsLiquidatable: false, - }, - }, - }, - }, - { - desc: "Non-existent subaccount", - perpetuals: []perptypes.Perpetual{ - constants.BtcUsd_100PercentMarginRequirement, - }, - subaccounts: []satypes.Subaccount{}, - request: &types.AreSubaccountsLiquidatableRequest{ - SubaccountIds: []satypes.SubaccountId{ - constants.Alice_Num0, - }, - }, - response: &types.AreSubaccountsLiquidatableResponse{ - Results: []types.AreSubaccountsLiquidatableResponse_Result{ - { - SubaccountId: constants.Alice_Num0, - IsLiquidatable: false, - }, - }, - }, - }, - { - desc: "Errors are propagated", - subaccounts: []satypes.Subaccount{ - { - Id: &constants.Alice_Num0, - AssetPositions: []*satypes.AssetPosition{ - &constants.Usdc_Asset_10_000, - }, - PerpetualPositions: []*satypes.PerpetualPosition{ - { - PerpetualId: 0, - Quantums: dtypes.NewInt(-1_000_000_000), // 1 BTC - }, - }, - }, - }, - perpetuals: []perptypes.Perpetual{}, - request: &types.AreSubaccountsLiquidatableRequest{ - SubaccountIds: []satypes.SubaccountId{ - constants.Alice_Num0, - }, - }, - err: perptypes.ErrPerpetualDoesNotExist, - }, - } { - t.Run(tc.desc, func(t *testing.T) { - memClob := memclob.NewMemClobPriceTimePriority(false) - ks := keepertest.NewClobKeepersTestContext(t, memClob, &mocks.BankKeeper{}, &mocks.IndexerEventManager{}) - - // Create the default markets. - keepertest.CreateTestMarkets(t, ks.Ctx, ks.PricesKeeper) - - // Create liquidity tiers. - keepertest.CreateTestLiquidityTiers(t, ks.Ctx, ks.PerpetualsKeeper) - - // Create all perpetuals. - for _, p := range tc.perpetuals { - _, err := ks.PerpetualsKeeper.CreatePerpetual( - ks.Ctx, - p.Params.Id, - p.Params.Ticker, - p.Params.MarketId, - p.Params.AtomicResolution, - p.Params.DefaultFundingPpm, - p.Params.LiquidityTier, - ) - require.NoError(t, err) - } - - for _, subaccount := range tc.subaccounts { - ks.SubaccountsKeeper.SetSubaccount(ks.Ctx, subaccount) - } - - wctx := sdk.WrapSDKContext(ks.Ctx) - response, err := ks.ClobKeeper.AreSubaccountsLiquidatable(wctx, tc.request) - - if tc.err != nil { - require.ErrorContains(t, err, tc.err.Error()) - } else { - require.NoError(t, err) - require.Equal(t, - nullify.Fill(tc.response), //nolint:staticcheck - nullify.Fill(response), //nolint:staticcheck - ) - } - }) - } -} diff --git a/protocol/x/clob/keeper/liquidations.go b/protocol/x/clob/keeper/liquidations.go index 8017370cfa..6c2136c0fb 100644 --- a/protocol/x/clob/keeper/liquidations.go +++ b/protocol/x/clob/keeper/liquidations.go @@ -360,11 +360,22 @@ func (k Keeper) IsLiquidatable( return false, err } - // The subaccount is liquidatable if both of the following are true: - // - The maintenance margin requirements are greater than zero (note that they can never be negative). - // - The maintenance margin requirements are greater than the subaccount's net collateral. - isLiquidatable := bigMaintenanceMargin.Sign() > 0 && bigMaintenanceMargin.Cmp(bigNetCollateral) == 1 - return isLiquidatable, nil + return CanLiquidateSubaccount(bigNetCollateral, bigMaintenanceMargin), nil +} + +// CanLiquidateSubaccount returns true if a subaccount is liquidatable given its total net collateral and +// maintenance margin requirement. +// +// The subaccount is liquidatable if both of the following are true: +// - The maintenance margin requirements are greater than zero (note that they can never be negative). +// - The maintenance margin requirements are greater than the subaccount's net collateral. +// +// Note that this is a stateless function. +func CanLiquidateSubaccount( + bigNetCollateral *big.Int, + bigMaintenanceMargin *big.Int, +) bool { + return bigMaintenanceMargin.Sign() > 0 && bigMaintenanceMargin.Cmp(bigNetCollateral) == 1 } // EnsureIsLiquidatable returns an error if the subaccount is not liquidatable. diff --git a/protocol/x/clob/types/query.pb.go b/protocol/x/clob/types/query.pb.go index 40751a9ac1..78f490f800 100644 --- a/protocol/x/clob/types/query.pb.go +++ b/protocol/x/clob/types/query.pb.go @@ -11,7 +11,6 @@ import ( _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" - types "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" @@ -220,156 +219,6 @@ func (m *QueryClobPairAllResponse) GetPagination() *query.PageResponse { return nil } -// AreSubaccountsLiquidatableRequest is a request message used to check whether -// the given subaccounts are liquidatable. -// The subaccount ids should not contain duplicates. -type AreSubaccountsLiquidatableRequest struct { - SubaccountIds []types.SubaccountId `protobuf:"bytes,1,rep,name=subaccount_ids,json=subaccountIds,proto3" json:"subaccount_ids"` -} - -func (m *AreSubaccountsLiquidatableRequest) Reset() { *m = AreSubaccountsLiquidatableRequest{} } -func (m *AreSubaccountsLiquidatableRequest) String() string { return proto.CompactTextString(m) } -func (*AreSubaccountsLiquidatableRequest) ProtoMessage() {} -func (*AreSubaccountsLiquidatableRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{4} -} -func (m *AreSubaccountsLiquidatableRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *AreSubaccountsLiquidatableRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_AreSubaccountsLiquidatableRequest.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *AreSubaccountsLiquidatableRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_AreSubaccountsLiquidatableRequest.Merge(m, src) -} -func (m *AreSubaccountsLiquidatableRequest) XXX_Size() int { - return m.Size() -} -func (m *AreSubaccountsLiquidatableRequest) XXX_DiscardUnknown() { - xxx_messageInfo_AreSubaccountsLiquidatableRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_AreSubaccountsLiquidatableRequest proto.InternalMessageInfo - -func (m *AreSubaccountsLiquidatableRequest) GetSubaccountIds() []types.SubaccountId { - if m != nil { - return m.SubaccountIds - } - return nil -} - -// AreSubaccountsLiquidatableResponse is a response message that contains the -// liquidation status for each subaccount. -type AreSubaccountsLiquidatableResponse struct { - Results []AreSubaccountsLiquidatableResponse_Result `protobuf:"bytes,1,rep,name=results,proto3" json:"results"` -} - -func (m *AreSubaccountsLiquidatableResponse) Reset() { *m = AreSubaccountsLiquidatableResponse{} } -func (m *AreSubaccountsLiquidatableResponse) String() string { return proto.CompactTextString(m) } -func (*AreSubaccountsLiquidatableResponse) ProtoMessage() {} -func (*AreSubaccountsLiquidatableResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{5} -} -func (m *AreSubaccountsLiquidatableResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *AreSubaccountsLiquidatableResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_AreSubaccountsLiquidatableResponse.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *AreSubaccountsLiquidatableResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_AreSubaccountsLiquidatableResponse.Merge(m, src) -} -func (m *AreSubaccountsLiquidatableResponse) XXX_Size() int { - return m.Size() -} -func (m *AreSubaccountsLiquidatableResponse) XXX_DiscardUnknown() { - xxx_messageInfo_AreSubaccountsLiquidatableResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_AreSubaccountsLiquidatableResponse proto.InternalMessageInfo - -func (m *AreSubaccountsLiquidatableResponse) GetResults() []AreSubaccountsLiquidatableResponse_Result { - if m != nil { - return m.Results - } - return nil -} - -// Result returns whether a subaccount should be liquidated. -type AreSubaccountsLiquidatableResponse_Result struct { - SubaccountId types.SubaccountId `protobuf:"bytes,1,opt,name=subaccount_id,json=subaccountId,proto3" json:"subaccount_id"` - IsLiquidatable bool `protobuf:"varint,2,opt,name=is_liquidatable,json=isLiquidatable,proto3" json:"is_liquidatable,omitempty"` -} - -func (m *AreSubaccountsLiquidatableResponse_Result) Reset() { - *m = AreSubaccountsLiquidatableResponse_Result{} -} -func (m *AreSubaccountsLiquidatableResponse_Result) String() string { - return proto.CompactTextString(m) -} -func (*AreSubaccountsLiquidatableResponse_Result) ProtoMessage() {} -func (*AreSubaccountsLiquidatableResponse_Result) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{5, 0} -} -func (m *AreSubaccountsLiquidatableResponse_Result) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *AreSubaccountsLiquidatableResponse_Result) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_AreSubaccountsLiquidatableResponse_Result.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *AreSubaccountsLiquidatableResponse_Result) XXX_Merge(src proto.Message) { - xxx_messageInfo_AreSubaccountsLiquidatableResponse_Result.Merge(m, src) -} -func (m *AreSubaccountsLiquidatableResponse_Result) XXX_Size() int { - return m.Size() -} -func (m *AreSubaccountsLiquidatableResponse_Result) XXX_DiscardUnknown() { - xxx_messageInfo_AreSubaccountsLiquidatableResponse_Result.DiscardUnknown(m) -} - -var xxx_messageInfo_AreSubaccountsLiquidatableResponse_Result proto.InternalMessageInfo - -func (m *AreSubaccountsLiquidatableResponse_Result) GetSubaccountId() types.SubaccountId { - if m != nil { - return m.SubaccountId - } - return types.SubaccountId{} -} - -func (m *AreSubaccountsLiquidatableResponse_Result) GetIsLiquidatable() bool { - if m != nil { - return m.IsLiquidatable - } - return false -} - // MevNodeToNodeCalculationRequest is a request message used to run the // MEV node <> node calculation. type MevNodeToNodeCalculationRequest struct { @@ -386,7 +235,7 @@ func (m *MevNodeToNodeCalculationRequest) Reset() { *m = MevNodeToNodeCa func (m *MevNodeToNodeCalculationRequest) String() string { return proto.CompactTextString(m) } func (*MevNodeToNodeCalculationRequest) ProtoMessage() {} func (*MevNodeToNodeCalculationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{6} + return fileDescriptor_3365c195b25c5bc0, []int{4} } func (m *MevNodeToNodeCalculationRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -439,7 +288,7 @@ func (m *MevNodeToNodeCalculationResponse) Reset() { *m = MevNodeToNodeC func (m *MevNodeToNodeCalculationResponse) String() string { return proto.CompactTextString(m) } func (*MevNodeToNodeCalculationResponse) ProtoMessage() {} func (*MevNodeToNodeCalculationResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{7} + return fileDescriptor_3365c195b25c5bc0, []int{5} } func (m *MevNodeToNodeCalculationResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -490,7 +339,7 @@ func (m *MevNodeToNodeCalculationResponse_MevAndVolumePerClob) String() string { } func (*MevNodeToNodeCalculationResponse_MevAndVolumePerClob) ProtoMessage() {} func (*MevNodeToNodeCalculationResponse_MevAndVolumePerClob) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{7, 0} + return fileDescriptor_3365c195b25c5bc0, []int{5, 0} } func (m *MevNodeToNodeCalculationResponse_MevAndVolumePerClob) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -551,7 +400,7 @@ func (m *QueryEquityTierLimitConfigurationRequest) Reset() { func (m *QueryEquityTierLimitConfigurationRequest) String() string { return proto.CompactTextString(m) } func (*QueryEquityTierLimitConfigurationRequest) ProtoMessage() {} func (*QueryEquityTierLimitConfigurationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{8} + return fileDescriptor_3365c195b25c5bc0, []int{6} } func (m *QueryEquityTierLimitConfigurationRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -594,7 +443,7 @@ func (m *QueryEquityTierLimitConfigurationResponse) String() string { } func (*QueryEquityTierLimitConfigurationResponse) ProtoMessage() {} func (*QueryEquityTierLimitConfigurationResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{9} + return fileDescriptor_3365c195b25c5bc0, []int{7} } func (m *QueryEquityTierLimitConfigurationResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -641,7 +490,7 @@ func (m *QueryBlockRateLimitConfigurationRequest) Reset() { func (m *QueryBlockRateLimitConfigurationRequest) String() string { return proto.CompactTextString(m) } func (*QueryBlockRateLimitConfigurationRequest) ProtoMessage() {} func (*QueryBlockRateLimitConfigurationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{10} + return fileDescriptor_3365c195b25c5bc0, []int{8} } func (m *QueryBlockRateLimitConfigurationRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -682,7 +531,7 @@ func (m *QueryBlockRateLimitConfigurationResponse) Reset() { func (m *QueryBlockRateLimitConfigurationResponse) String() string { return proto.CompactTextString(m) } func (*QueryBlockRateLimitConfigurationResponse) ProtoMessage() {} func (*QueryBlockRateLimitConfigurationResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{11} + return fileDescriptor_3365c195b25c5bc0, []int{9} } func (m *QueryBlockRateLimitConfigurationResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -727,7 +576,7 @@ func (m *QueryLiquidationsConfigurationRequest) Reset() { *m = QueryLiqu func (m *QueryLiquidationsConfigurationRequest) String() string { return proto.CompactTextString(m) } func (*QueryLiquidationsConfigurationRequest) ProtoMessage() {} func (*QueryLiquidationsConfigurationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{12} + return fileDescriptor_3365c195b25c5bc0, []int{10} } func (m *QueryLiquidationsConfigurationRequest) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -768,7 +617,7 @@ func (m *QueryLiquidationsConfigurationResponse) Reset() { func (m *QueryLiquidationsConfigurationResponse) String() string { return proto.CompactTextString(m) } func (*QueryLiquidationsConfigurationResponse) ProtoMessage() {} func (*QueryLiquidationsConfigurationResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_3365c195b25c5bc0, []int{13} + return fileDescriptor_3365c195b25c5bc0, []int{11} } func (m *QueryLiquidationsConfigurationResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -809,9 +658,6 @@ func init() { proto.RegisterType((*QueryClobPairResponse)(nil), "dydxprotocol.clob.QueryClobPairResponse") proto.RegisterType((*QueryAllClobPairRequest)(nil), "dydxprotocol.clob.QueryAllClobPairRequest") proto.RegisterType((*QueryClobPairAllResponse)(nil), "dydxprotocol.clob.QueryClobPairAllResponse") - proto.RegisterType((*AreSubaccountsLiquidatableRequest)(nil), "dydxprotocol.clob.AreSubaccountsLiquidatableRequest") - proto.RegisterType((*AreSubaccountsLiquidatableResponse)(nil), "dydxprotocol.clob.AreSubaccountsLiquidatableResponse") - proto.RegisterType((*AreSubaccountsLiquidatableResponse_Result)(nil), "dydxprotocol.clob.AreSubaccountsLiquidatableResponse.Result") proto.RegisterType((*MevNodeToNodeCalculationRequest)(nil), "dydxprotocol.clob.MevNodeToNodeCalculationRequest") proto.RegisterType((*MevNodeToNodeCalculationResponse)(nil), "dydxprotocol.clob.MevNodeToNodeCalculationResponse") proto.RegisterType((*MevNodeToNodeCalculationResponse_MevAndVolumePerClob)(nil), "dydxprotocol.clob.MevNodeToNodeCalculationResponse.MevAndVolumePerClob") @@ -826,75 +672,67 @@ func init() { func init() { proto.RegisterFile("dydxprotocol/clob/query.proto", fileDescriptor_3365c195b25c5bc0) } var fileDescriptor_3365c195b25c5bc0 = []byte{ - // 1084 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xcf, 0x6f, 0x1b, 0xc5, - 0x1f, 0xcd, 0x38, 0xfd, 0xe6, 0x9b, 0x7e, 0xda, 0x04, 0x98, 0x34, 0x6d, 0x70, 0x52, 0xc7, 0x59, - 0x88, 0x63, 0xa7, 0x62, 0x97, 0xa6, 0x01, 0x41, 0x5a, 0x21, 0x25, 0x11, 0x44, 0x95, 0x12, 0xe4, - 0x6e, 0xab, 0x22, 0x41, 0xa5, 0xd5, 0x7a, 0x77, 0x70, 0x46, 0x5d, 0xef, 0x38, 0xfb, 0x4b, 0x89, - 0x10, 0x17, 0x84, 0x90, 0x2a, 0x38, 0x20, 0x71, 0xe0, 0xc0, 0x91, 0xbf, 0x82, 0x03, 0x02, 0x6e, - 0x3d, 0x56, 0xe2, 0xc2, 0x01, 0x21, 0x94, 0x70, 0xe6, 0x6f, 0x40, 0x3b, 0x3b, 0xb6, 0x77, 0xbd, - 0x3f, 0x9c, 0xf8, 0xe2, 0xd8, 0x33, 0x6f, 0xde, 0xbc, 0x37, 0x6f, 0xf6, 0xf3, 0xd9, 0xc0, 0x4d, - 0xf3, 0xc4, 0x3c, 0xee, 0x3a, 0xcc, 0x63, 0x06, 0xb3, 0x14, 0xc3, 0x62, 0x2d, 0xe5, 0xc8, 0x27, - 0xce, 0x89, 0xcc, 0xc7, 0xf0, 0x2b, 0xf1, 0x69, 0x39, 0x9c, 0x2e, 0x5f, 0x6b, 0xb3, 0x36, 0xe3, - 0x43, 0x4a, 0xf8, 0x2d, 0x02, 0x96, 0x97, 0xda, 0x8c, 0xb5, 0x2d, 0xa2, 0xe8, 0x5d, 0xaa, 0xe8, - 0xb6, 0xcd, 0x3c, 0xdd, 0xa3, 0xcc, 0x76, 0xc5, 0xec, 0xba, 0xc1, 0xdc, 0x0e, 0x73, 0x95, 0x96, - 0xee, 0x92, 0x88, 0x5f, 0x09, 0x6e, 0xb7, 0x88, 0xa7, 0xdf, 0x56, 0xba, 0x7a, 0x9b, 0xda, 0x1c, - 0x2c, 0xb0, 0x4a, 0x5a, 0x51, 0xcb, 0x62, 0xc6, 0x53, 0xcd, 0xd1, 0x3d, 0xa2, 0x59, 0xb4, 0x43, - 0x3d, 0xcd, 0x60, 0xf6, 0xa7, 0xb4, 0x2d, 0x16, 0xac, 0xa4, 0x17, 0x84, 0x1f, 0x5a, 0x57, 0xa7, - 0x8e, 0x80, 0xbc, 0x99, 0x86, 0x90, 0x23, 0x9f, 0x7a, 0x27, 0x9a, 0x47, 0x89, 0x93, 0x45, 0x7a, - 0x2b, 0xbd, 0xc2, 0xa2, 0x47, 0x3e, 0x35, 0x23, 0x5f, 0x49, 0xf0, 0x62, 0x1a, 0xdc, 0x21, 0x81, - 0x98, 0x6c, 0x24, 0x26, 0x5d, 0xbf, 0xa5, 0x1b, 0x06, 0xf3, 0x6d, 0xcf, 0x8d, 0x7d, 0x8f, 0xa0, - 0x52, 0x03, 0x6e, 0x3c, 0x08, 0x0f, 0x67, 0x8f, 0x78, 0xbb, 0x16, 0x6b, 0x35, 0x75, 0xea, 0xa8, - 0xe4, 0xc8, 0x27, 0xae, 0x87, 0x67, 0xa1, 0x44, 0xcd, 0x05, 0x54, 0x45, 0xf5, 0x19, 0xb5, 0x44, - 0x4d, 0xe9, 0x23, 0x98, 0xe7, 0xd0, 0x01, 0xce, 0xed, 0x32, 0xdb, 0x25, 0xf8, 0x3d, 0xb8, 0xdc, - 0x77, 0xcf, 0xf1, 0x57, 0x36, 0x16, 0xe5, 0x54, 0x8a, 0x72, 0x6f, 0xdd, 0xce, 0xa5, 0xe7, 0x7f, - 0x2d, 0x4f, 0xa8, 0xd3, 0x86, 0xf8, 0x2d, 0xe9, 0x42, 0xc3, 0xb6, 0x65, 0x0d, 0x6b, 0xf8, 0x00, - 0x60, 0x90, 0x96, 0xe0, 0xae, 0xc9, 0x51, 0xb4, 0x72, 0x18, 0xad, 0x1c, 0x5d, 0x1d, 0x11, 0xad, - 0xdc, 0xd4, 0xdb, 0x44, 0xac, 0x55, 0x63, 0x2b, 0xa5, 0x1f, 0x11, 0x2c, 0x24, 0xc4, 0x6f, 0x5b, - 0x56, 0x9e, 0xfe, 0xc9, 0x0b, 0xea, 0xc7, 0x7b, 0x09, 0x91, 0x25, 0x2e, 0x72, 0x6d, 0xa4, 0xc8, - 0x68, 0xf3, 0x84, 0xca, 0x63, 0x58, 0xd9, 0x76, 0xc8, 0xc3, 0x41, 0x5e, 0xfb, 0x22, 0x7f, 0xbd, - 0x65, 0xf5, 0x6c, 0xe1, 0x87, 0x30, 0x3b, 0x48, 0x51, 0xa3, 0xa6, 0x2b, 0x24, 0xd7, 0x92, 0x92, - 0x63, 0xa9, 0xcb, 0x03, 0xc6, 0xfb, 0xa6, 0x50, 0x3f, 0xe3, 0xc6, 0xc6, 0x5c, 0xe9, 0x59, 0x09, - 0xa4, 0xa2, 0xad, 0xc5, 0x49, 0x3d, 0x81, 0xff, 0x3b, 0xc4, 0xf5, 0x2d, 0xaf, 0xb7, 0xe9, 0xbd, - 0x8c, 0x73, 0x1a, 0xcd, 0x23, 0xab, 0x9c, 0x44, 0x48, 0xe9, 0x51, 0x96, 0xbf, 0x44, 0x30, 0x15, - 0xcd, 0xe0, 0x07, 0x30, 0x93, 0x30, 0xd9, 0x8f, 0xfe, 0x22, 0x1e, 0xaf, 0xc6, 0x3d, 0xe2, 0x35, - 0x78, 0x89, 0xba, 0x9a, 0x15, 0x93, 0xc3, 0xa3, 0x9a, 0x56, 0x67, 0x69, 0x42, 0xa4, 0xf4, 0x27, - 0x82, 0xe5, 0x03, 0x12, 0x7c, 0xc8, 0x4c, 0xf2, 0x88, 0x85, 0x9f, 0xbb, 0xba, 0x65, 0xf8, 0x16, - 0x8f, 0xa8, 0x17, 0xc2, 0x13, 0xb8, 0x1e, 0x55, 0x88, 0xae, 0xc3, 0xba, 0xcc, 0x25, 0x8e, 0xd6, - 0xd1, 0x3d, 0xe3, 0x90, 0xb8, 0xd9, 0x42, 0xf9, 0xb9, 0x3c, 0xd6, 0xad, 0x70, 0x0f, 0xe6, 0x1c, - 0x90, 0xe0, 0x20, 0x42, 0xab, 0xd7, 0x38, 0x4b, 0x53, 0x90, 0x88, 0x51, 0xfc, 0x09, 0xcc, 0x07, - 0x3d, 0xb0, 0xd6, 0x21, 0x81, 0xd6, 0x21, 0x9e, 0x43, 0x0d, 0xb7, 0x7f, 0xb7, 0xd2, 0xe4, 0x09, - 0xc1, 0x07, 0x11, 0x5c, 0x9d, 0x0b, 0xe2, 0x5b, 0x46, 0x83, 0xd2, 0xbf, 0x08, 0xaa, 0xf9, 0xf6, - 0x44, 0xd0, 0xed, 0xe1, 0xa0, 0xf7, 0x46, 0xed, 0x99, 0xc1, 0x12, 0x02, 0xb6, 0x6d, 0xf3, 0x31, - 0xb3, 0xfc, 0x0e, 0x69, 0x12, 0x27, 0x7c, 0x80, 0x86, 0x33, 0xd7, 0x61, 0x2e, 0x03, 0x85, 0xab, - 0x70, 0xb5, 0xff, 0x48, 0x6a, 0xfd, 0x2a, 0x04, 0xbd, 0x47, 0xee, 0xbe, 0x89, 0x5f, 0x86, 0xc9, - 0x0e, 0x09, 0xf8, 0x89, 0x94, 0xd4, 0xf0, 0x2b, 0xbe, 0x0e, 0x53, 0x01, 0x27, 0x59, 0x98, 0xac, - 0xa2, 0xfa, 0x25, 0x55, 0xfc, 0x92, 0xd6, 0xa1, 0xce, 0x1f, 0xfd, 0xf7, 0x79, 0xf9, 0x7d, 0x44, - 0x89, 0xb3, 0x1f, 0x16, 0xdf, 0x5d, 0x5e, 0x4e, 0x7d, 0x27, 0x9e, 0xab, 0xf4, 0x03, 0x82, 0xc6, - 0x39, 0xc0, 0xe2, 0x94, 0x6c, 0x58, 0xc8, 0xab, 0xe9, 0xe2, 0x1e, 0x28, 0x19, 0xc7, 0x56, 0x44, - 0x2d, 0x8e, 0x67, 0x9e, 0x64, 0x61, 0xa4, 0x06, 0xac, 0x71, 0x71, 0x3b, 0xe1, 0xa5, 0x51, 0x75, - 0x8f, 0xe4, 0x1b, 0xf9, 0x1e, 0x09, 0xd7, 0x85, 0x58, 0xe1, 0xe3, 0x29, 0xdc, 0xc8, 0xe9, 0x77, - 0xc2, 0x86, 0x9c, 0x61, 0xa3, 0x80, 0x58, 0xb8, 0x88, 0x2e, 0xf7, 0x10, 0x44, 0x5a, 0x83, 0x55, - 0x2e, 0x6c, 0x3f, 0xd6, 0xdb, 0x32, 0x2d, 0x7c, 0x85, 0xa0, 0x36, 0x0a, 0xd9, 0xaf, 0x4b, 0x73, - 0x19, 0xad, 0x52, 0x88, 0x5f, 0xcd, 0x10, 0x9f, 0xa6, 0x14, 0x9a, 0xb1, 0x95, 0x9a, 0xd9, 0xf8, - 0xe9, 0x32, 0xfc, 0x8f, 0x0b, 0xc1, 0x5f, 0x23, 0x98, 0xee, 0xb5, 0x01, 0xbc, 0x9e, 0xc1, 0x9b, - 0xd3, 0x4b, 0xcb, 0xf5, 0x3c, 0xec, 0x70, 0x33, 0x95, 0x1a, 0x5f, 0xfc, 0xfe, 0xcf, 0x77, 0xa5, - 0xd7, 0xf0, 0x8a, 0x52, 0xf0, 0x8e, 0xa1, 0x7c, 0x46, 0xcd, 0xcf, 0xf1, 0x37, 0x08, 0xae, 0xc4, - 0xfa, 0x59, 0xbe, 0xa0, 0x74, 0x63, 0x2d, 0xdf, 0x1a, 0x25, 0x28, 0xd6, 0x20, 0xa5, 0xd7, 0xb9, - 0xa6, 0x0a, 0x5e, 0x2a, 0xd2, 0x84, 0x9f, 0x21, 0x28, 0xe7, 0xd7, 0x7e, 0xbc, 0x79, 0xc1, 0x56, - 0x11, 0xe9, 0x7c, 0x6b, 0xac, 0x06, 0x83, 0x7f, 0x41, 0xb0, 0x90, 0x57, 0x9e, 0xf0, 0xc6, 0x85, - 0x6a, 0x59, 0xa4, 0xe3, 0xce, 0x18, 0xf5, 0x4f, 0xda, 0xe2, 0xe7, 0xb6, 0xb9, 0x85, 0xd6, 0x25, - 0x45, 0xc9, 0x7c, 0x61, 0xd3, 0x6c, 0x66, 0x12, 0xcd, 0x63, 0xd1, 0x5f, 0x23, 0x26, 0xf2, 0x37, - 0x04, 0x4b, 0x45, 0x95, 0x02, 0xdf, 0xcd, 0x4b, 0xf0, 0x1c, 0x75, 0xae, 0x7c, 0x6f, 0xbc, 0xc5, - 0xc2, 0x57, 0x8d, 0xfb, 0xaa, 0xe2, 0x8a, 0x52, 0xf8, 0x92, 0x8b, 0x7f, 0x46, 0xb0, 0x58, 0x50, - 0x26, 0xf0, 0x56, 0x9e, 0x8a, 0xd1, 0x05, 0xae, 0x7c, 0x77, 0xac, 0xb5, 0xc2, 0xc0, 0x2a, 0x37, - 0xb0, 0x8c, 0x6f, 0x16, 0xbe, 0xf9, 0xe3, 0x5f, 0x11, 0xbc, 0x9a, 0x5b, 0x7c, 0xf0, 0x3b, 0x79, - 0x0a, 0x46, 0x55, 0xb6, 0xf2, 0xbb, 0x63, 0xac, 0x14, 0xca, 0x65, 0xae, 0xbc, 0x8e, 0x6b, 0xca, - 0xb9, 0xfe, 0x5b, 0xd8, 0x69, 0x3e, 0x3f, 0xad, 0xa0, 0x17, 0xa7, 0x15, 0xf4, 0xf7, 0x69, 0x05, - 0x7d, 0x7b, 0x56, 0x99, 0x78, 0x71, 0x56, 0x99, 0xf8, 0xe3, 0xac, 0x32, 0xf1, 0xf1, 0xdb, 0x6d, - 0xea, 0x1d, 0xfa, 0x2d, 0xd9, 0x60, 0x9d, 0x24, 0x57, 0xb0, 0xf9, 0x86, 0x71, 0xa8, 0x53, 0x5b, - 0xe9, 0x8f, 0x1c, 0x47, 0xfc, 0xde, 0x49, 0x97, 0xb8, 0xad, 0x29, 0x3e, 0x7c, 0xe7, 0xbf, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xd2, 0x5a, 0xb5, 0xd4, 0xc7, 0x0d, 0x00, 0x00, + // 946 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xcf, 0x6f, 0xdc, 0x44, + 0x14, 0xce, 0x6c, 0x4a, 0x09, 0x53, 0x40, 0x30, 0x69, 0xda, 0x65, 0x93, 0x6e, 0xb6, 0x86, 0x24, + 0x9b, 0x54, 0x78, 0x68, 0x5a, 0x21, 0x48, 0x11, 0x52, 0x12, 0x41, 0x84, 0xd4, 0xa0, 0xc5, 0xaa, + 0x8a, 0x04, 0x95, 0xac, 0x59, 0x7b, 0x70, 0x46, 0x1d, 0x7b, 0x36, 0xf6, 0xd8, 0x6a, 0x84, 0xb8, + 0x70, 0xe0, 0x02, 0x07, 0x24, 0x0e, 0x1c, 0x38, 0x72, 0xe6, 0x4f, 0x40, 0xc0, 0xad, 0xc7, 0x4a, + 0x5c, 0x38, 0x20, 0x84, 0x12, 0xce, 0xfc, 0x0d, 0x95, 0xc7, 0xb3, 0x5b, 0x6f, 0xfc, 0x63, 0x93, + 0xbd, 0xec, 0xda, 0x33, 0xdf, 0x7b, 0xfe, 0xbe, 0xf7, 0xde, 0x7c, 0x36, 0xbc, 0xe6, 0x1e, 0xb9, + 0x8f, 0x06, 0xa1, 0x90, 0xc2, 0x11, 0x1c, 0x3b, 0x5c, 0xf4, 0xf1, 0x61, 0x4c, 0xc3, 0x23, 0x53, + 0xad, 0xa1, 0x57, 0xf3, 0xdb, 0x66, 0xba, 0xdd, 0xba, 0xec, 0x09, 0x4f, 0xa8, 0x25, 0x9c, 0x5e, + 0x65, 0xc0, 0xd6, 0x92, 0x27, 0x84, 0xc7, 0x29, 0x26, 0x03, 0x86, 0x49, 0x10, 0x08, 0x49, 0x24, + 0x13, 0x41, 0xa4, 0x77, 0x37, 0x1c, 0x11, 0xf9, 0x22, 0xc2, 0x7d, 0x12, 0xd1, 0x2c, 0x3f, 0x4e, + 0x6e, 0xf6, 0xa9, 0x24, 0x37, 0xf1, 0x80, 0x78, 0x2c, 0x50, 0x60, 0x8d, 0xc5, 0x45, 0x46, 0x7d, + 0x2e, 0x9c, 0x87, 0x76, 0x48, 0x24, 0xb5, 0x39, 0xf3, 0x99, 0xb4, 0x1d, 0x11, 0x7c, 0xc1, 0x3c, + 0x1d, 0x70, 0xbd, 0x18, 0x90, 0xfe, 0xd8, 0x03, 0xc2, 0x42, 0x0d, 0x79, 0xab, 0x08, 0xa1, 0x87, + 0x31, 0x93, 0x47, 0xb6, 0x64, 0x34, 0x2c, 0x4b, 0x7a, 0xa3, 0x18, 0xc1, 0xd9, 0x61, 0xcc, 0xdc, + 0x4c, 0xd7, 0x38, 0x78, 0xb1, 0x08, 0xf6, 0x69, 0x92, 0x6d, 0x1a, 0xeb, 0xf0, 0xea, 0x27, 0xa9, + 0xe2, 0x3d, 0x2a, 0x77, 0xb9, 0xe8, 0xf7, 0x08, 0x0b, 0x2d, 0x7a, 0x18, 0xd3, 0x48, 0xa2, 0x97, + 0x61, 0x83, 0xb9, 0x4d, 0xd0, 0x01, 0xdd, 0x97, 0xac, 0x06, 0x73, 0x8d, 0x4f, 0xe1, 0x82, 0x82, + 0x3e, 0xc3, 0x45, 0x03, 0x11, 0x44, 0x14, 0xbd, 0x0f, 0x5f, 0x18, 0x49, 0x52, 0xf8, 0x4b, 0x9b, + 0x8b, 0x66, 0xa1, 0x35, 0xe6, 0x30, 0x6e, 0xe7, 0xc2, 0xe3, 0x7f, 0x96, 0x67, 0xac, 0x39, 0x47, + 0xdf, 0x1b, 0x44, 0x73, 0xd8, 0xe6, 0xfc, 0x34, 0x87, 0x0f, 0x21, 0x7c, 0xd6, 0x02, 0x9d, 0x7b, + 0xd5, 0xcc, 0xfa, 0x65, 0xa6, 0xfd, 0x32, 0xb3, 0x79, 0xd0, 0xfd, 0x32, 0x7b, 0xc4, 0xa3, 0x3a, + 0xd6, 0xca, 0x45, 0x1a, 0x3f, 0x03, 0xd8, 0x1c, 0x23, 0xbf, 0xcd, 0x79, 0x15, 0xff, 0xd9, 0x73, + 0xf2, 0x47, 0x7b, 0x63, 0x24, 0x1b, 0x8a, 0xe4, 0xda, 0x44, 0x92, 0xd9, 0xc3, 0xc7, 0x58, 0xfe, + 0x0d, 0xe0, 0xf2, 0x3e, 0x4d, 0x3e, 0x16, 0x2e, 0xbd, 0x27, 0xd2, 0xdf, 0x5d, 0xc2, 0x9d, 0x98, + 0xab, 0xcd, 0x61, 0x45, 0x1e, 0xc0, 0x2b, 0xd9, 0xc0, 0x0d, 0x42, 0x31, 0x10, 0x11, 0x0d, 0x6d, + 0x9f, 0x48, 0xe7, 0x80, 0x46, 0xa3, 0xea, 0x14, 0x99, 0xdf, 0x27, 0x3c, 0x1d, 0x0d, 0x11, 0xee, + 0xd3, 0x64, 0x3f, 0x43, 0x5b, 0x97, 0x55, 0x96, 0x9e, 0x4e, 0xa2, 0x57, 0xd1, 0xe7, 0x70, 0x21, + 0x19, 0x82, 0x6d, 0x9f, 0x26, 0xb6, 0x4f, 0x65, 0xc8, 0x9c, 0x68, 0xa4, 0xaa, 0x98, 0x7c, 0x8c, + 0xf0, 0x7e, 0x06, 0xb7, 0xe6, 0x93, 0xfc, 0x23, 0xb3, 0x45, 0xe3, 0x7f, 0x00, 0x3b, 0xd5, 0xf2, + 0x74, 0x33, 0x3c, 0xf8, 0x7c, 0x48, 0xa3, 0x98, 0xcb, 0x48, 0xb7, 0x62, 0x6f, 0xd2, 0x33, 0x4b, + 0xb2, 0xa4, 0x80, 0xed, 0xc0, 0xbd, 0x2f, 0x78, 0xec, 0xd3, 0x1e, 0x0d, 0xd3, 0xd6, 0xe9, 0xb6, + 0x0d, 0xb3, 0xb7, 0x08, 0x9c, 0x2f, 0x41, 0xa1, 0x0e, 0x7c, 0x71, 0x34, 0x0c, 0xf6, 0x68, 0xfe, + 0xe1, 0xb0, 0xd9, 0x1f, 0xb9, 0xe8, 0x15, 0x38, 0xeb, 0xd3, 0x44, 0x55, 0xa4, 0x61, 0xa5, 0x97, + 0xe8, 0x0a, 0xbc, 0x98, 0xa8, 0x24, 0xcd, 0xd9, 0x0e, 0xe8, 0x5e, 0xb0, 0xf4, 0x9d, 0xb1, 0x01, + 0xbb, 0x6a, 0xe8, 0x3e, 0x50, 0xa7, 0xf9, 0x1e, 0xa3, 0xe1, 0xdd, 0xf4, 0x2c, 0xef, 0xaa, 0xd3, + 0x19, 0x87, 0xf9, 0xbe, 0x1a, 0x3f, 0x01, 0xb8, 0x7e, 0x06, 0xb0, 0xae, 0x52, 0x00, 0x9b, 0x55, + 0x16, 0xa1, 0xe7, 0x00, 0x97, 0x94, 0xad, 0x2e, 0xb5, 0x2e, 0xcf, 0x02, 0x2d, 0xc3, 0x18, 0xeb, + 0x70, 0x4d, 0x91, 0xdb, 0x49, 0x87, 0xc6, 0x22, 0x92, 0x56, 0x0b, 0xf9, 0x11, 0x68, 0xd5, 0xb5, + 0x58, 0xad, 0xe3, 0x21, 0xbc, 0x5a, 0x61, 0x9f, 0x5a, 0x86, 0x59, 0x22, 0xa3, 0x26, 0xb1, 0x56, + 0x91, 0x0d, 0xf7, 0x29, 0x88, 0xb1, 0x06, 0x57, 0x14, 0xb1, 0xbb, 0x39, 0xab, 0x2c, 0x95, 0xf0, + 0x0d, 0x80, 0xab, 0x93, 0x90, 0x5a, 0xc0, 0x03, 0x38, 0x5f, 0xe2, 0xbc, 0x9a, 0xfc, 0x4a, 0x09, + 0xf9, 0x62, 0x4a, 0xcd, 0x19, 0xf1, 0xc2, 0xce, 0xe6, 0x2f, 0x73, 0xf0, 0x39, 0x45, 0x04, 0x7d, + 0x0b, 0xe0, 0xdc, 0xd0, 0x80, 0xd0, 0x46, 0x49, 0xde, 0x0a, 0x17, 0x6f, 0x75, 0xab, 0xb0, 0xa7, + 0x6d, 0xdc, 0x58, 0xff, 0xfa, 0xcf, 0xff, 0x7e, 0x68, 0xbc, 0x8e, 0xae, 0xe3, 0x9a, 0x57, 0x16, + 0xfe, 0x92, 0xb9, 0x5f, 0xa1, 0xef, 0x00, 0xbc, 0x94, 0x73, 0xd2, 0x6a, 0x42, 0x45, 0x4b, 0x6f, + 0xdd, 0x98, 0x44, 0x28, 0x67, 0xcd, 0xc6, 0x1b, 0x8a, 0x53, 0x1b, 0x2d, 0xd5, 0x71, 0x42, 0xbf, + 0x01, 0xd8, 0xac, 0xb2, 0x04, 0xb4, 0x79, 0x2e, 0xff, 0xc8, 0x38, 0xde, 0x9a, 0xc2, 0x73, 0x8c, + 0x2d, 0xc5, 0xf5, 0xf6, 0x16, 0xd8, 0x30, 0x30, 0x2e, 0x7d, 0xe7, 0xda, 0x81, 0x70, 0xa9, 0x2d, + 0x45, 0xf6, 0xef, 0xe4, 0x48, 0xfe, 0x01, 0xe0, 0x52, 0xdd, 0xe9, 0x44, 0x77, 0xaa, 0xaa, 0x76, + 0x06, 0x6f, 0x69, 0xbd, 0x37, 0x5d, 0xb0, 0xd6, 0xb5, 0xaa, 0x74, 0x75, 0x50, 0x1b, 0xd7, 0x7e, + 0xa7, 0xa0, 0x5f, 0x01, 0x5c, 0xac, 0x39, 0x9a, 0x68, 0xab, 0x8a, 0xc5, 0x64, 0x53, 0x69, 0xdd, + 0x99, 0x2a, 0x56, 0x0b, 0x58, 0x51, 0x02, 0x96, 0xd1, 0xb5, 0xda, 0x8f, 0x37, 0xf4, 0x3b, 0x80, + 0xaf, 0x55, 0x1e, 0x78, 0xf4, 0x4e, 0x15, 0x83, 0x49, 0x6e, 0xd2, 0x7a, 0x77, 0x8a, 0x48, 0xcd, + 0xdc, 0x54, 0xcc, 0xbb, 0x68, 0x15, 0x9f, 0xe9, 0x83, 0x6f, 0xa7, 0xf7, 0xf8, 0xb8, 0x0d, 0x9e, + 0x1c, 0xb7, 0xc1, 0xbf, 0xc7, 0x6d, 0xf0, 0xfd, 0x49, 0x7b, 0xe6, 0xc9, 0x49, 0x7b, 0xe6, 0xaf, + 0x93, 0xf6, 0xcc, 0x67, 0x6f, 0x7b, 0x4c, 0x1e, 0xc4, 0x7d, 0xd3, 0x11, 0xfe, 0x78, 0xae, 0xe4, + 0xf6, 0x9b, 0xce, 0x01, 0x61, 0x01, 0x1e, 0xad, 0x3c, 0xca, 0xf2, 0xcb, 0xa3, 0x01, 0x8d, 0xfa, + 0x17, 0xd5, 0xf2, 0xad, 0xa7, 0x01, 0x00, 0x00, 0xff, 0xff, 0x75, 0xf9, 0x20, 0xb1, 0x8a, 0x0b, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -913,8 +751,6 @@ type QueryClient interface { ClobPair(ctx context.Context, in *QueryGetClobPairRequest, opts ...grpc.CallOption) (*QueryClobPairResponse, error) // Queries a list of ClobPair items. ClobPairAll(ctx context.Context, in *QueryAllClobPairRequest, opts ...grpc.CallOption) (*QueryClobPairAllResponse, error) - // Returns whether a subaccount is liquidatable. - AreSubaccountsLiquidatable(ctx context.Context, in *AreSubaccountsLiquidatableRequest, opts ...grpc.CallOption) (*AreSubaccountsLiquidatableResponse, error) // Runs the MEV node <> node calculation with the provided parameters. MevNodeToNodeCalculation(ctx context.Context, in *MevNodeToNodeCalculationRequest, opts ...grpc.CallOption) (*MevNodeToNodeCalculationResponse, error) // Queries EquityTierLimitConfiguration. @@ -951,15 +787,6 @@ func (c *queryClient) ClobPairAll(ctx context.Context, in *QueryAllClobPairReque return out, nil } -func (c *queryClient) AreSubaccountsLiquidatable(ctx context.Context, in *AreSubaccountsLiquidatableRequest, opts ...grpc.CallOption) (*AreSubaccountsLiquidatableResponse, error) { - out := new(AreSubaccountsLiquidatableResponse) - err := c.cc.Invoke(ctx, "/dydxprotocol.clob.Query/AreSubaccountsLiquidatable", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *queryClient) MevNodeToNodeCalculation(ctx context.Context, in *MevNodeToNodeCalculationRequest, opts ...grpc.CallOption) (*MevNodeToNodeCalculationResponse, error) { out := new(MevNodeToNodeCalculationResponse) err := c.cc.Invoke(ctx, "/dydxprotocol.clob.Query/MevNodeToNodeCalculation", in, out, opts...) @@ -1002,8 +829,6 @@ type QueryServer interface { ClobPair(context.Context, *QueryGetClobPairRequest) (*QueryClobPairResponse, error) // Queries a list of ClobPair items. ClobPairAll(context.Context, *QueryAllClobPairRequest) (*QueryClobPairAllResponse, error) - // Returns whether a subaccount is liquidatable. - AreSubaccountsLiquidatable(context.Context, *AreSubaccountsLiquidatableRequest) (*AreSubaccountsLiquidatableResponse, error) // Runs the MEV node <> node calculation with the provided parameters. MevNodeToNodeCalculation(context.Context, *MevNodeToNodeCalculationRequest) (*MevNodeToNodeCalculationResponse, error) // Queries EquityTierLimitConfiguration. @@ -1024,9 +849,6 @@ func (*UnimplementedQueryServer) ClobPair(ctx context.Context, req *QueryGetClob func (*UnimplementedQueryServer) ClobPairAll(ctx context.Context, req *QueryAllClobPairRequest) (*QueryClobPairAllResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ClobPairAll not implemented") } -func (*UnimplementedQueryServer) AreSubaccountsLiquidatable(ctx context.Context, req *AreSubaccountsLiquidatableRequest) (*AreSubaccountsLiquidatableResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method AreSubaccountsLiquidatable not implemented") -} func (*UnimplementedQueryServer) MevNodeToNodeCalculation(ctx context.Context, req *MevNodeToNodeCalculationRequest) (*MevNodeToNodeCalculationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method MevNodeToNodeCalculation not implemented") } @@ -1080,24 +902,6 @@ func _Query_ClobPairAll_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } -func _Query_AreSubaccountsLiquidatable_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(AreSubaccountsLiquidatableRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).AreSubaccountsLiquidatable(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/dydxprotocol.clob.Query/AreSubaccountsLiquidatable", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).AreSubaccountsLiquidatable(ctx, req.(*AreSubaccountsLiquidatableRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _Query_MevNodeToNodeCalculation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MevNodeToNodeCalculationRequest) if err := dec(in); err != nil { @@ -1182,10 +986,6 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "ClobPairAll", Handler: _Query_ClobPairAll_Handler, }, - { - MethodName: "AreSubaccountsLiquidatable", - Handler: _Query_AreSubaccountsLiquidatable_Handler, - }, { MethodName: "MevNodeToNodeCalculation", Handler: _Query_MevNodeToNodeCalculation_Handler, @@ -1352,123 +1152,6 @@ func (m *QueryClobPairAllResponse) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } -func (m *AreSubaccountsLiquidatableRequest) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AreSubaccountsLiquidatableRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *AreSubaccountsLiquidatableRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.SubaccountIds) > 0 { - for iNdEx := len(m.SubaccountIds) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.SubaccountIds[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *AreSubaccountsLiquidatableResponse) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AreSubaccountsLiquidatableResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *AreSubaccountsLiquidatableResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Results) > 0 { - for iNdEx := len(m.Results) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.Results[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - } - } - return len(dAtA) - i, nil -} - -func (m *AreSubaccountsLiquidatableResponse_Result) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *AreSubaccountsLiquidatableResponse_Result) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *AreSubaccountsLiquidatableResponse_Result) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.IsLiquidatable { - i-- - if m.IsLiquidatable { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x10 - } - { - size, err := m.SubaccountId.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintQuery(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} - func (m *MevNodeToNodeCalculationRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1826,50 +1509,6 @@ func (m *QueryClobPairAllResponse) Size() (n int) { return n } -func (m *AreSubaccountsLiquidatableRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.SubaccountIds) > 0 { - for _, e := range m.SubaccountIds { - l = e.Size() - n += 1 + l + sovQuery(uint64(l)) - } - } - return n -} - -func (m *AreSubaccountsLiquidatableResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.Results) > 0 { - for _, e := range m.Results { - l = e.Size() - n += 1 + l + sovQuery(uint64(l)) - } - } - return n -} - -func (m *AreSubaccountsLiquidatableResponse_Result) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = m.SubaccountId.Size() - n += 1 + l + sovQuery(uint64(l)) - if m.IsLiquidatable { - n += 2 - } - return n -} - func (m *MevNodeToNodeCalculationRequest) Size() (n int) { if m == nil { return 0 @@ -2344,277 +1983,6 @@ func (m *QueryClobPairAllResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *AreSubaccountsLiquidatableRequest) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AreSubaccountsLiquidatableRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AreSubaccountsLiquidatableRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SubaccountIds", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.SubaccountIds = append(m.SubaccountIds, types.SubaccountId{}) - if err := m.SubaccountIds[len(m.SubaccountIds)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AreSubaccountsLiquidatableResponse) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AreSubaccountsLiquidatableResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AreSubaccountsLiquidatableResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Results", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Results = append(m.Results, AreSubaccountsLiquidatableResponse_Result{}) - if err := m.Results[len(m.Results)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AreSubaccountsLiquidatableResponse_Result) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Result: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Result: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SubaccountId", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthQuery - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthQuery - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.SubaccountId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field IsLiquidatable", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowQuery - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - v |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - m.IsLiquidatable = bool(v != 0) - default: - iNdEx = preIndex - skippy, err := skipQuery(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthQuery - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *MevNodeToNodeCalculationRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index 9d03408c48..c170c6c31b 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -768,6 +768,23 @@ func (k Keeper) GetNetNotional( return new(big.Int), err } + return GetNetNotionalInQuoteQuantums(perpetual, marketPrice, bigQuantums), nil +} + +// GetNetNotionalInQuoteQuantums returns the net notional in quote quantums, which can be +// represented by the following equation: +// +// `quantums / 10^baseAtomicResolution * marketPrice * 10^marketExponent * 10^quoteAtomicResolution`. +// Note that longs are positive, and shorts are negative. +// +// Also note that this is a stateless function. +func GetNetNotionalInQuoteQuantums( + perpetual types.Perpetual, + marketPrice pricestypes.MarketPrice, + bigQuantums *big.Int, +) ( + bigNetNotionalQuoteQuantums *big.Int, +) { bigQuoteQuantums := lib.BaseToQuoteQuantums( bigQuantums, perpetual.Params.AtomicResolution, @@ -775,7 +792,7 @@ func (k Keeper) GetNetNotional( marketPrice.Exponent, ) - return bigQuoteQuantums, nil + return bigQuoteQuantums } // GetNotionalInBaseQuantums returns the net notional in base quantums, which can be represented @@ -879,6 +896,29 @@ func (k Keeper) GetMarginRequirements( return nil, nil, err } + bigInitialMarginQuoteQuantums, + bigMaintenanceMarginQuoteQuantums = GetMarginRequirementsInQuoteQuantums( + perpetual, + marketPrice, + liquidityTier, + bigQuantums, + ) + return bigInitialMarginQuoteQuantums, bigMaintenanceMarginQuoteQuantums, nil +} + +// GetMarginRequirementsInQuoteQuantums returns initial and maintenance margin requirements +// in quote quantums, given the position size in base quantums. +// +// Note that this is a stateless function. +func GetMarginRequirementsInQuoteQuantums( + perpetual types.Perpetual, + marketPrice pricestypes.MarketPrice, + liquidityTier types.LiquidityTier, + bigQuantums *big.Int, +) ( + bigInitialMarginQuoteQuantums *big.Int, + bigMaintenanceMarginQuoteQuantums *big.Int, +) { // Always consider the magnitude of the position regardless of whether it is long/short. bigAbsQuantums := new(big.Int).Set(bigQuantums).Abs(bigQuantums) @@ -900,8 +940,7 @@ func (k Keeper) GetMarginRequirements( ), true, ) - - return bigInitialMarginQuoteQuantums, bigMaintenanceMarginQuoteQuantums, nil + return bigInitialMarginQuoteQuantums, bigMaintenanceMarginQuoteQuantums } // GetSettlementPpm returns the net settlement amount ppm (in quote quantums) given @@ -930,11 +969,31 @@ func (k Keeper) GetSettlementPpm( return big.NewInt(0), big.NewInt(0), err } + bigNetSettlementPpm, newFundingIndex = GetSettlementPpmWithPerpetual( + perpetual, + quantums, + index, + ) + return bigNetSettlementPpm, newFundingIndex, nil +} + +// GetSettlementPpm returns the net settlement amount ppm (in quote quantums) given +// the perpetual and position size (in base quantums). +// +// Note that this function is a stateless utility function. +func GetSettlementPpmWithPerpetual( + perpetual types.Perpetual, + quantums *big.Int, + index *big.Int, +) ( + bigNetSettlementPpm *big.Int, + newFundingIndex *big.Int, +) { indexDelta := new(big.Int).Sub(perpetual.FundingIndex.BigInt(), index) // if indexDelta is zero, then net settlement is zero. if indexDelta.Sign() == 0 { - return big.NewInt(0), perpetual.FundingIndex.BigInt(), nil + return big.NewInt(0), perpetual.FundingIndex.BigInt() } bigNetSettlementPpm = new(big.Int).Mul(indexDelta, quantums) @@ -944,7 +1003,7 @@ func (k Keeper) GetSettlementPpm( // Thus, always negate `bigNetSettlementPpm` here. bigNetSettlementPpm = bigNetSettlementPpm.Neg(bigNetSettlementPpm) - return bigNetSettlementPpm, perpetual.FundingIndex.BigInt(), nil + return bigNetSettlementPpm, perpetual.FundingIndex.BigInt() } // GetPremiumSamples reads premium samples from the current `funding-tick` epoch, diff --git a/protocol/x/subaccounts/keeper/subaccount.go b/protocol/x/subaccounts/keeper/subaccount.go index f387649059..4716ad3072 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -17,6 +17,8 @@ import ( indexer_manager "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" + perpkeeper "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/keeper" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) @@ -376,6 +378,33 @@ func (k Keeper) getSettledSubaccount( settledSubaccount types.Subaccount, fundingPayments map[uint32]dtypes.SerializableInt, err error, +) { + // Fetch all relevant perpetuals. + perpetuals := make(map[uint32]perptypes.Perpetual) + for _, p := range subaccount.PerpetualPositions { + perpetual, err := k.perpetualsKeeper.GetPerpetual(ctx, p.PerpetualId) + if err != nil { + return types.Subaccount{}, nil, err + } + perpetuals[p.PerpetualId] = perpetual + } + + return GetSettledSubaccountWithPerpetuals(subaccount, perpetuals) +} + +// GetSettledSubaccountWithPerpetuals returns 1. a new settled subaccount given an unsettled subaccount, +// updating the USDC AssetPosition, FundingIndex, and LastFundingPayment fields accordingly +// (does not persist any changes) and 2. a map with perpetual ID as key and last funding +// payment as value (for emitting funding payments to indexer). +// +// Note that this is a stateless utility function. +func GetSettledSubaccountWithPerpetuals( + subaccount types.Subaccount, + perpetuals map[uint32]perptypes.Perpetual, +) ( + settledSubaccount types.Subaccount, + fundingPayments map[uint32]dtypes.SerializableInt, + err error, ) { totalNetSettlementPpm := big.NewInt(0) @@ -384,15 +413,21 @@ func (k Keeper) getSettledSubaccount( // Iterate through and settle all perpetual positions. for _, p := range subaccount.PerpetualPositions { - bigNetSettlementPpm, newFundingIndex, err := k.perpetualsKeeper.GetSettlementPpm( - ctx, - p.PerpetualId, + perpetual, found := perpetuals[p.PerpetualId] + if !found { + return types.Subaccount{}, + nil, + errorsmod.Wrap( + perptypes.ErrPerpetualDoesNotExist, lib.UintToString(p.PerpetualId), + ) + } + + // Call the stateless utility function to get the net settlement and new funding index. + bigNetSettlementPpm, newFundingIndex := perpkeeper.GetSettlementPpmWithPerpetual( + perpetual, p.GetBigQuantums(), p.FundingIndex.BigInt(), ) - if err != nil { - return types.Subaccount{}, nil, err - } // Record non-zero funding payment (to be later emitted in SubaccountUpdateEvent to indexer). // Note: Funding payment is the negative of settlement, i.e. positive settlement is equivalent // to a negative funding payment (position received funding payment) and vice versa. diff --git a/protocol/x/subaccounts/types/expected_keepers.go b/protocol/x/subaccounts/types/expected_keepers.go index a8890d649e..56d2c3e25e 100644 --- a/protocol/x/subaccounts/types/expected_keepers.go +++ b/protocol/x/subaccounts/types/expected_keepers.go @@ -62,6 +62,13 @@ type PerpetualsKeeper interface { newFundingIndex *big.Int, err error, ) + GetPerpetual( + ctx sdk.Context, + perpetualId uint32, + ) ( + perpetual perptypes.Perpetual, + err error, + ) GetAllPerpetuals(ctx sdk.Context) []perptypes.Perpetual }