diff --git a/.gitignore b/.gitignore index a88d2ec1a4..bbbe817346 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ v4-proto-js/node_modules v4-proto-js/src .idea +.vscode **/.DS_Store diff --git a/indexer/packages/kafka/src/websocket-helper.ts b/indexer/packages/kafka/src/websocket-helper.ts index cdd296e1c1..ae96c8d069 100644 --- a/indexer/packages/kafka/src/websocket-helper.ts +++ b/indexer/packages/kafka/src/websocket-helper.ts @@ -41,12 +41,12 @@ export function getTriggerPrice( return undefined; } -export function createSubaccountWebsocketMessage( +export function generateSubaccountMessageContents( redisOrder: RedisOrder, order: OrderFromDatabase | undefined, perpetualMarket: PerpetualMarketFromDatabase, placementStatus: OrderPlaceV1_OrderPlacementStatus, -): Buffer { +): SubaccountMessageContents { const orderTIF: TimeInForce = protocolTranslations.protocolOrderTIFToTIF( redisOrder.order!.timeInForce, ); @@ -90,6 +90,21 @@ export function createSubaccountWebsocketMessage( }, ], }; + return contents; +} + +export function createSubaccountWebsocketMessage( + redisOrder: RedisOrder, + order: OrderFromDatabase | undefined, + perpetualMarket: PerpetualMarketFromDatabase, + placementStatus: OrderPlaceV1_OrderPlacementStatus, +): Buffer { + const contents: SubaccountMessageContents = generateSubaccountMessageContents( + redisOrder, + order, + perpetualMarket, + placementStatus, + ); const subaccountMessage: SubaccountMessage = SubaccountMessage.fromPartial({ contents: JSON.stringify(contents), diff --git a/indexer/packages/postgres/__tests__/helpers/constants.ts b/indexer/packages/postgres/__tests__/helpers/constants.ts index 84791623a0..26f714041b 100644 --- a/indexer/packages/postgres/__tests__/helpers/constants.ts +++ b/indexer/packages/postgres/__tests__/helpers/constants.ts @@ -179,6 +179,16 @@ export const defaultAssetPositionId2: string = AssetPositionTable.uuid( defaultAssetPosition2.subaccountId, defaultAssetPosition2.assetId, ); +export const isolatedSubaccountAssetPosition: AssetPositionCreateObject = { + subaccountId: isolatedSubaccountId, + assetId: '0', + size: '5000', + isLong: true, +}; +export const isolatedSubaccountAssetPositionId: string = AssetPositionTable.uuid( + isolatedSubaccountAssetPosition.subaccountId, + isolatedSubaccountAssetPosition.assetId, +); // ============== PerpetualMarkets ============== @@ -445,6 +455,28 @@ export const defaultPerpetualPositionId: string = PerpetualPositionTable.uuid( defaultPerpetualPosition.openEventId, ); +export const isolatedPerpetualPosition: PerpetualPositionCreateObject = { + subaccountId: isolatedSubaccountId, + perpetualId: isolatedPerpetualMarket.id, + side: PositionSide.LONG, + status: PerpetualPositionStatus.OPEN, + size: '10', + maxSize: '25', + entryPrice: '20000', + sumOpen: '10', + sumClose: '0', + createdAt: createdDateTime.toISO(), + createdAtHeight: createdHeight, + openEventId: defaultTendermintEventId, + lastEventId: defaultTendermintEventId2, + settledFunding: '200000', +}; + +export const isolatedPerpetualPositionId: string = PerpetualPositionTable.uuid( + isolatedPerpetualPosition.subaccountId, + isolatedPerpetualPosition.openEventId, +); + // ============== Fills ============== export const defaultFill: FillCreateObject = { @@ -710,6 +742,22 @@ export const defaultFundingIndexUpdateId: string = FundingIndexUpdatesTable.uuid defaultFundingIndexUpdate.perpetualId, ); +export const isolatedMarketFundingIndexUpdate: FundingIndexUpdatesCreateObject = { + perpetualId: isolatedPerpetualMarket.id, + eventId: defaultTendermintEventId, + rate: '0.0004', + oraclePrice: '10000', + fundingIndex: '10200', + effectiveAt: createdDateTime.toISO(), + effectiveAtHeight: createdHeight, +}; + +export const isolatedMarketFundingIndexUpdateId: string = FundingIndexUpdatesTable.uuid( + isolatedMarketFundingIndexUpdate.effectiveAtHeight, + isolatedMarketFundingIndexUpdate.eventId, + isolatedMarketFundingIndexUpdate.perpetualId, +); + // ========= Compliance Data ========== export const blockedComplianceData: ComplianceDataCreateObject = { diff --git a/indexer/packages/postgres/__tests__/lib/api-translations.test.ts b/indexer/packages/postgres/__tests__/lib/api-translations.test.ts index 2ba80bc4a3..ec54f29eae 100644 --- a/indexer/packages/postgres/__tests__/lib/api-translations.test.ts +++ b/indexer/packages/postgres/__tests__/lib/api-translations.test.ts @@ -1,7 +1,5 @@ import { APITimeInForce, TimeInForce } from '../../src/types'; import { - getChildSubaccountNums, - getParentSubaccountNum, isOrderTIFPostOnly, orderTIFToAPITIF, } from '../../src/lib/api-translations'; @@ -36,41 +34,4 @@ describe('apiTranslations', () => { expect(isOrderTIFPostOnly(orderTimeInForce)).toEqual(expectedPostOnly); }); }); - - describe('getChildSubaccountNums', () => { - it('Gets a list of all possible child subaccount numbers for a parent subaccount 0', () => { - const childSubaccounts = getChildSubaccountNums(0); - expect(childSubaccounts.length).toEqual(1000); - expect(childSubaccounts[0]).toEqual(0); - expect(childSubaccounts[1]).toEqual(128); - expect(childSubaccounts[999]).toEqual(128 * 999); - }); - it('Gets a list of all possible child subaccount numbers for a parent subaccount 127', () => { - const childSubaccounts = getChildSubaccountNums(127); - expect(childSubaccounts.length).toEqual(1000); - expect(childSubaccounts[0]).toEqual(127); - expect(childSubaccounts[1]).toEqual(128 + 127); - expect(childSubaccounts[999]).toEqual(128 * 999 + 127); - }); - }); - - describe('getChildSubaccountNums', () => { - it('Throws an error if the parent subaccount number is greater than or equal to the maximum parent subaccount number', () => { - expect(() => getChildSubaccountNums(128)).toThrowError('Parent subaccount number must be less than 128'); - }); - }); - - describe('getParentSubaccountNum', () => { - it('Gets the parent subaccount number from a child subaccount number', () => { - expect(getParentSubaccountNum(0)).toEqual(0); - expect(getParentSubaccountNum(128)).toEqual(0); - expect(getParentSubaccountNum(128 * 999 - 1)).toEqual(127); - }); - }); - - describe('getParentSubaccountNum', () => { - it('Throws an error if the child subaccount number is greater than the max child subaccount number', () => { - expect(() => getParentSubaccountNum(128001)).toThrowError('Child subaccount number must be less than 128000'); - }); - }); }); diff --git a/indexer/packages/postgres/src/lib/api-translations.ts b/indexer/packages/postgres/src/lib/api-translations.ts index c6e69e248f..27c776b91e 100644 --- a/indexer/packages/postgres/src/lib/api-translations.ts +++ b/indexer/packages/postgres/src/lib/api-translations.ts @@ -1,4 +1,4 @@ -import { TIME_IN_FORCE_TO_API_TIME_IN_FORCE, CHILD_SUBACCOUNT_MULTIPLIER, MAX_PARENT_SUBACCOUNTS } from '../constants'; +import { TIME_IN_FORCE_TO_API_TIME_IN_FORCE } from '../constants'; import { APITimeInForce, TimeInForce } from '../types'; /** @@ -20,30 +20,3 @@ export function isOrderTIFPostOnly(timeInForce: TimeInForce): boolean { export function orderTIFToAPITIF(timeInForce: TimeInForce): APITimeInForce { return TIME_IN_FORCE_TO_API_TIME_IN_FORCE[timeInForce]; } - -/** - * Gets a list of all possible child subaccount numbers for a parent subaccount number - * Child subaccounts = [128*0+parentSubaccount, 128*1+parentSubaccount ... 128*999+parentSubaccount] - * @param parentSubaccount - * @returns - */ -export function getChildSubaccountNums(parentSubaccountNum: number): number[] { - if (parentSubaccountNum >= MAX_PARENT_SUBACCOUNTS) { - throw new Error(`Parent subaccount number must be less than ${MAX_PARENT_SUBACCOUNTS}`); - } - return Array.from({ length: CHILD_SUBACCOUNT_MULTIPLIER }, - (_, i) => MAX_PARENT_SUBACCOUNTS * i + parentSubaccountNum); -} - -/** - * Gets the parent subaccount number from a child subaccount number - * Parent subaccount = childSubaccount % 128 - * @param childSubaccountNum - * @returns - */ -export function getParentSubaccountNum(childSubaccountNum: number): number { - if (childSubaccountNum > MAX_PARENT_SUBACCOUNTS * CHILD_SUBACCOUNT_MULTIPLIER) { - throw new Error(`Child subaccount number must be less than ${MAX_PARENT_SUBACCOUNTS * CHILD_SUBACCOUNT_MULTIPLIER}`); - } - return childSubaccountNum % MAX_PARENT_SUBACCOUNTS; -} diff --git a/indexer/packages/redis/src/helpers/order-helper.ts b/indexer/packages/redis/src/helpers/order-helper.ts new file mode 100644 index 0000000000..d2a910a282 --- /dev/null +++ b/indexer/packages/redis/src/helpers/order-helper.ts @@ -0,0 +1,28 @@ +import { OrderTable, PerpetualMarketFromDatabase, protocolTranslations } from '@dydxprotocol-indexer/postgres'; +import { IndexerOrder, RedisOrder, RedisOrder_TickerType } from '@dydxprotocol-indexer/v4-protos'; + +/** + * Creates a `RedisOrder` given an `Order` and the corresponding `PerpetualMarket` for the `Order`. + * @param order + * @param perpetualMarket + * @returns + */ +export function convertToRedisOrder( + order: IndexerOrder, + perpetualMarket: PerpetualMarketFromDatabase, +): RedisOrder { + return { + order, + id: OrderTable.orderIdToUuid(order.orderId!), + ticker: perpetualMarket.ticker, + tickerType: RedisOrder_TickerType.TICKER_TYPE_PERPETUAL, + price: protocolTranslations.subticksToPrice( + order.subticks.toString(), + perpetualMarket, + ), + size: protocolTranslations.quantumsToHumanFixedString( + order.quantums.toString(), + perpetualMarket.atomicResolution, + ), + }; +} diff --git a/indexer/packages/redis/src/index.ts b/indexer/packages/redis/src/index.ts index 08708b42e6..27e08da583 100644 --- a/indexer/packages/redis/src/index.ts +++ b/indexer/packages/redis/src/index.ts @@ -15,6 +15,7 @@ export * as StateFilledQuantumsCache from './caches/state-filled-quantums-cache' export { placeOrder } from './caches/place-order'; export { removeOrder } from './caches/remove-order'; export { updateOrder } from './caches/update-order'; +export * from './helpers/order-helper'; export * from './types'; export { redisConfigSchema } from './config'; diff --git a/indexer/packages/v4-proto-parser/__tests__/order-helpers.test.ts b/indexer/packages/v4-proto-parser/__tests__/order-helpers.test.ts index bd8d3f811a..90468e4cbd 100644 --- a/indexer/packages/v4-proto-parser/__tests__/order-helpers.test.ts +++ b/indexer/packages/v4-proto-parser/__tests__/order-helpers.test.ts @@ -1,5 +1,5 @@ import { IndexerOrderId } from '@dydxprotocol-indexer/v4-protos'; -import { getOrderIdHash, isStatefulOrder } from '../src/order-helpers'; +import { getOrderIdHash, isLongTermOrder, isStatefulOrder } from '../src/order-helpers'; import { ORDER_FLAG_CONDITIONAL, ORDER_FLAG_LONG_TERM, ORDER_FLAG_SHORT_TERM } from '../src'; describe('getOrderIdHash', () => { @@ -65,3 +65,23 @@ describe('isStatefulOrder', () => { expect(isStatefulOrder(flag)).toEqual(isStateful); }); }); + +describe('isLongTermOrder', () => { + it.each([ + [ORDER_FLAG_SHORT_TERM.toString(), 'string', false], + ['4', 'string', false], + [ORDER_FLAG_CONDITIONAL.toString(), 'string', false], + [ORDER_FLAG_LONG_TERM.toString(), 'string', true], + [ORDER_FLAG_SHORT_TERM, 'number', false], + [3, 'number', false], + [ORDER_FLAG_CONDITIONAL, 'number', false], + [ORDER_FLAG_LONG_TERM, 'number', true], + ['abc', 'string', false], + ])('Checks if flag %s with type %s is a long term order', ( + flag: number | string, + _type: string, + isLongTerm: boolean, + ) => { + expect(isLongTermOrder(flag)).toEqual(isLongTerm); + }); +}); diff --git a/indexer/packages/v4-proto-parser/src/order-helpers.ts b/indexer/packages/v4-proto-parser/src/order-helpers.ts index beeb567ac1..6724942941 100644 --- a/indexer/packages/v4-proto-parser/src/order-helpers.ts +++ b/indexer/packages/v4-proto-parser/src/order-helpers.ts @@ -24,6 +24,15 @@ export function isStatefulOrder(orderFlag: number | String): boolean { return numberOrderFlag === ORDER_FLAG_CONDITIONAL || numberOrderFlag === ORDER_FLAG_LONG_TERM; } +export function isLongTermOrder(orderFlag: number | String): boolean { + const numberOrderFlag: number = Number(orderFlag); + // A string that is not a number will be converted to NaN, and should return false. + if (Number.isNaN(numberOrderFlag)) { + return false; + } + return numberOrderFlag === ORDER_FLAG_LONG_TERM; +} + export function requiresImmediateExecution(tif: IndexerOrder_TimeInForce): boolean { return ( tif === IndexerOrder_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL || diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts index 4cf72f481d..866360b7b5 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/bundle.ts @@ -90,77 +90,79 @@ import * as _93 from "./subaccounts/perpetual_position"; import * as _94 from "./subaccounts/query"; import * as _95 from "./subaccounts/subaccount"; import * as _96 from "./vault/genesis"; -import * as _97 from "./vault/query"; -import * as _98 from "./vault/tx"; -import * as _99 from "./vault/vault"; -import * as _100 from "./vest/genesis"; -import * as _101 from "./vest/query"; -import * as _102 from "./vest/tx"; -import * as _103 from "./vest/vest_entry"; -import * as _111 from "./assets/query.lcd"; -import * as _112 from "./blocktime/query.lcd"; -import * as _113 from "./bridge/query.lcd"; -import * as _114 from "./clob/query.lcd"; -import * as _115 from "./delaymsg/query.lcd"; -import * as _116 from "./epochs/query.lcd"; -import * as _117 from "./feetiers/query.lcd"; -import * as _118 from "./perpetuals/query.lcd"; -import * as _119 from "./prices/query.lcd"; -import * as _120 from "./ratelimit/query.lcd"; -import * as _121 from "./rewards/query.lcd"; -import * as _122 from "./stats/query.lcd"; -import * as _123 from "./subaccounts/query.lcd"; -import * as _124 from "./vest/query.lcd"; -import * as _125 from "./assets/query.rpc.Query"; -import * as _126 from "./blocktime/query.rpc.Query"; -import * as _127 from "./bridge/query.rpc.Query"; -import * as _128 from "./clob/query.rpc.Query"; -import * as _129 from "./delaymsg/query.rpc.Query"; -import * as _130 from "./epochs/query.rpc.Query"; -import * as _131 from "./feetiers/query.rpc.Query"; -import * as _132 from "./govplus/query.rpc.Query"; -import * as _133 from "./perpetuals/query.rpc.Query"; -import * as _134 from "./prices/query.rpc.Query"; -import * as _135 from "./ratelimit/query.rpc.Query"; -import * as _136 from "./rewards/query.rpc.Query"; -import * as _137 from "./sending/query.rpc.Query"; -import * as _138 from "./stats/query.rpc.Query"; -import * as _139 from "./subaccounts/query.rpc.Query"; -import * as _140 from "./vault/query.rpc.Query"; -import * as _141 from "./vest/query.rpc.Query"; -import * as _142 from "./blocktime/tx.rpc.msg"; -import * as _143 from "./bridge/tx.rpc.msg"; -import * as _144 from "./clob/tx.rpc.msg"; -import * as _145 from "./delaymsg/tx.rpc.msg"; -import * as _146 from "./feetiers/tx.rpc.msg"; -import * as _147 from "./govplus/tx.rpc.msg"; -import * as _148 from "./perpetuals/tx.rpc.msg"; -import * as _149 from "./prices/tx.rpc.msg"; -import * as _150 from "./ratelimit/tx.rpc.msg"; -import * as _151 from "./rewards/tx.rpc.msg"; -import * as _152 from "./sending/tx.rpc.msg"; -import * as _153 from "./stats/tx.rpc.msg"; -import * as _154 from "./vault/tx.rpc.msg"; -import * as _155 from "./vest/tx.rpc.msg"; -import * as _156 from "./lcd"; -import * as _157 from "./rpc.query"; -import * as _158 from "./rpc.tx"; +import * as _97 from "./vault/params"; +import * as _98 from "./vault/query"; +import * as _99 from "./vault/tx"; +import * as _100 from "./vault/vault"; +import * as _101 from "./vest/genesis"; +import * as _102 from "./vest/query"; +import * as _103 from "./vest/tx"; +import * as _104 from "./vest/vest_entry"; +import * as _112 from "./assets/query.lcd"; +import * as _113 from "./blocktime/query.lcd"; +import * as _114 from "./bridge/query.lcd"; +import * as _115 from "./clob/query.lcd"; +import * as _116 from "./delaymsg/query.lcd"; +import * as _117 from "./epochs/query.lcd"; +import * as _118 from "./feetiers/query.lcd"; +import * as _119 from "./perpetuals/query.lcd"; +import * as _120 from "./prices/query.lcd"; +import * as _121 from "./ratelimit/query.lcd"; +import * as _122 from "./rewards/query.lcd"; +import * as _123 from "./stats/query.lcd"; +import * as _124 from "./subaccounts/query.lcd"; +import * as _125 from "./vault/query.lcd"; +import * as _126 from "./vest/query.lcd"; +import * as _127 from "./assets/query.rpc.Query"; +import * as _128 from "./blocktime/query.rpc.Query"; +import * as _129 from "./bridge/query.rpc.Query"; +import * as _130 from "./clob/query.rpc.Query"; +import * as _131 from "./delaymsg/query.rpc.Query"; +import * as _132 from "./epochs/query.rpc.Query"; +import * as _133 from "./feetiers/query.rpc.Query"; +import * as _134 from "./govplus/query.rpc.Query"; +import * as _135 from "./perpetuals/query.rpc.Query"; +import * as _136 from "./prices/query.rpc.Query"; +import * as _137 from "./ratelimit/query.rpc.Query"; +import * as _138 from "./rewards/query.rpc.Query"; +import * as _139 from "./sending/query.rpc.Query"; +import * as _140 from "./stats/query.rpc.Query"; +import * as _141 from "./subaccounts/query.rpc.Query"; +import * as _142 from "./vault/query.rpc.Query"; +import * as _143 from "./vest/query.rpc.Query"; +import * as _144 from "./blocktime/tx.rpc.msg"; +import * as _145 from "./bridge/tx.rpc.msg"; +import * as _146 from "./clob/tx.rpc.msg"; +import * as _147 from "./delaymsg/tx.rpc.msg"; +import * as _148 from "./feetiers/tx.rpc.msg"; +import * as _149 from "./govplus/tx.rpc.msg"; +import * as _150 from "./perpetuals/tx.rpc.msg"; +import * as _151 from "./prices/tx.rpc.msg"; +import * as _152 from "./ratelimit/tx.rpc.msg"; +import * as _153 from "./rewards/tx.rpc.msg"; +import * as _154 from "./sending/tx.rpc.msg"; +import * as _155 from "./stats/tx.rpc.msg"; +import * as _156 from "./vault/tx.rpc.msg"; +import * as _157 from "./vest/tx.rpc.msg"; +import * as _158 from "./lcd"; +import * as _159 from "./rpc.query"; +import * as _160 from "./rpc.tx"; export namespace dydxprotocol { export const assets = { ..._5, ..._6, ..._7, ..._8, - ..._111, - ..._125 + ..._112, + ..._127 }; export const blocktime = { ..._9, ..._10, ..._11, ..._12, ..._13, - ..._112, - ..._126, - ..._142 + ..._113, + ..._128, + ..._144 }; export const bridge = { ..._14, ..._15, @@ -168,9 +170,9 @@ export namespace dydxprotocol { ..._17, ..._18, ..._19, - ..._113, - ..._127, - ..._143 + ..._114, + ..._129, + ..._145 }; export const clob = { ..._20, ..._21, @@ -186,9 +188,9 @@ export namespace dydxprotocol { ..._31, ..._32, ..._33, - ..._114, - ..._128, - ..._144 + ..._115, + ..._130, + ..._146 }; export namespace daemons { export const bridge = { ..._34 @@ -203,29 +205,29 @@ export namespace dydxprotocol { ..._39, ..._40, ..._41, - ..._115, - ..._129, - ..._145 + ..._116, + ..._131, + ..._147 }; export const epochs = { ..._42, ..._43, ..._44, - ..._116, - ..._130 + ..._117, + ..._132 }; export const feetiers = { ..._45, ..._46, ..._47, ..._48, - ..._117, - ..._131, - ..._146 + ..._118, + ..._133, + ..._148 }; export const govplus = { ..._49, ..._50, ..._51, - ..._132, - ..._147 + ..._134, + ..._149 }; export namespace indexer { export const events = { ..._52 @@ -252,18 +254,18 @@ export namespace dydxprotocol { ..._63, ..._64, ..._65, - ..._118, - ..._133, - ..._148 + ..._119, + ..._135, + ..._150 }; export const prices = { ..._66, ..._67, ..._68, ..._69, ..._70, - ..._119, - ..._134, - ..._149 + ..._120, + ..._136, + ..._151 }; export const ratelimit = { ..._71, ..._72, @@ -271,60 +273,62 @@ export namespace dydxprotocol { ..._74, ..._75, ..._76, - ..._120, - ..._135, - ..._150 + ..._121, + ..._137, + ..._152 }; export const rewards = { ..._77, ..._78, ..._79, ..._80, ..._81, - ..._121, - ..._136, - ..._151 + ..._122, + ..._138, + ..._153 }; export const sending = { ..._82, ..._83, ..._84, ..._85, - ..._137, - ..._152 + ..._139, + ..._154 }; export const stats = { ..._86, ..._87, ..._88, ..._89, ..._90, - ..._122, - ..._138, - ..._153 + ..._123, + ..._140, + ..._155 }; export const subaccounts = { ..._91, ..._92, ..._93, ..._94, ..._95, - ..._123, - ..._139 + ..._124, + ..._141 }; export const vault = { ..._96, ..._97, ..._98, ..._99, - ..._140, - ..._154 + ..._100, + ..._125, + ..._142, + ..._156 }; - export const vest = { ..._100, - ..._101, + export const vest = { ..._101, ..._102, ..._103, - ..._124, - ..._141, - ..._155 + ..._104, + ..._126, + ..._143, + ..._157 }; - export const ClientFactory = { ..._156, - ..._157, - ..._158 + export const ClientFactory = { ..._158, + ..._159, + ..._160 }; } \ No newline at end of file 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 ddf8e9bd2d..aa31787820 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/clob/query.ts @@ -233,6 +233,15 @@ export interface StreamOrderbookUpdatesResponse { */ snapshot: boolean; + /** + * ---Additional fields used to debug issues--- + * Block height of the updates. + */ + + blockHeight: number; + /** Exec mode of the updates. */ + + execMode: number; } /** * StreamOrderbookUpdatesResponse is a response message for the @@ -250,6 +259,15 @@ export interface StreamOrderbookUpdatesResponseSDKType { */ snapshot: boolean; + /** + * ---Additional fields used to debug issues--- + * Block height of the updates. + */ + + block_height: number; + /** Exec mode of the updates. */ + + exec_mode: number; } function createBaseQueryGetClobPairRequest(): QueryGetClobPairRequest { @@ -904,7 +922,9 @@ export const StreamOrderbookUpdatesRequest = { function createBaseStreamOrderbookUpdatesResponse(): StreamOrderbookUpdatesResponse { return { updates: [], - snapshot: false + snapshot: false, + blockHeight: 0, + execMode: 0 }; } @@ -918,6 +938,14 @@ export const StreamOrderbookUpdatesResponse = { writer.uint32(16).bool(message.snapshot); } + if (message.blockHeight !== 0) { + writer.uint32(24).uint32(message.blockHeight); + } + + if (message.execMode !== 0) { + writer.uint32(32).uint32(message.execMode); + } + return writer; }, @@ -938,6 +966,14 @@ export const StreamOrderbookUpdatesResponse = { message.snapshot = reader.bool(); break; + case 3: + message.blockHeight = reader.uint32(); + break; + + case 4: + message.execMode = reader.uint32(); + break; + default: reader.skipType(tag & 7); break; @@ -951,6 +987,8 @@ export const StreamOrderbookUpdatesResponse = { const message = createBaseStreamOrderbookUpdatesResponse(); message.updates = object.updates?.map(e => OffChainUpdateV1.fromPartial(e)) || []; message.snapshot = object.snapshot ?? false; + message.blockHeight = object.blockHeight ?? 0; + message.execMode = object.execMode ?? 0; return message; } diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/indexer/events/events.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/indexer/events/events.ts index 1f8d38d1fd..bf9891706c 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/indexer/events/events.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/indexer/events/events.ts @@ -957,6 +957,12 @@ export interface LiquidityTierUpsertEventV1 { /** @deprecated */ basePositionNotional: Long; + /** Lower cap of open interest in quote quantums. */ + + openInterestLowerCap: Long; + /** Upper cap of open interest in quote quantums. */ + + openInterestUpperCap: Long; } /** * LiquidityTierUpsertEventV1 message contains all the information to @@ -992,6 +998,12 @@ export interface LiquidityTierUpsertEventV1SDKType { /** @deprecated */ base_position_notional: Long; + /** Lower cap of open interest in quote quantums. */ + + open_interest_lower_cap: Long; + /** Upper cap of open interest in quote quantums. */ + + open_interest_upper_cap: Long; } /** * UpdateClobPairEventV1 message contains all the information about an update to @@ -2675,7 +2687,9 @@ function createBaseLiquidityTierUpsertEventV1(): LiquidityTierUpsertEventV1 { name: "", initialMarginPpm: 0, maintenanceFractionPpm: 0, - basePositionNotional: Long.UZERO + basePositionNotional: Long.UZERO, + openInterestLowerCap: Long.UZERO, + openInterestUpperCap: Long.UZERO }; } @@ -2701,6 +2715,14 @@ export const LiquidityTierUpsertEventV1 = { writer.uint32(40).uint64(message.basePositionNotional); } + if (!message.openInterestLowerCap.isZero()) { + writer.uint32(48).uint64(message.openInterestLowerCap); + } + + if (!message.openInterestUpperCap.isZero()) { + writer.uint32(56).uint64(message.openInterestUpperCap); + } + return writer; }, @@ -2733,6 +2755,14 @@ export const LiquidityTierUpsertEventV1 = { message.basePositionNotional = (reader.uint64() as Long); break; + case 6: + message.openInterestLowerCap = (reader.uint64() as Long); + break; + + case 7: + message.openInterestUpperCap = (reader.uint64() as Long); + break; + default: reader.skipType(tag & 7); break; @@ -2749,6 +2779,8 @@ export const LiquidityTierUpsertEventV1 = { message.initialMarginPpm = object.initialMarginPpm ?? 0; message.maintenanceFractionPpm = object.maintenanceFractionPpm ?? 0; message.basePositionNotional = object.basePositionNotional !== undefined && object.basePositionNotional !== null ? Long.fromValue(object.basePositionNotional) : Long.UZERO; + message.openInterestLowerCap = object.openInterestLowerCap !== undefined && object.openInterestLowerCap !== null ? Long.fromValue(object.openInterestLowerCap) : Long.UZERO; + message.openInterestUpperCap = object.openInterestUpperCap !== undefined && object.openInterestUpperCap !== null ? Long.fromValue(object.openInterestUpperCap) : Long.UZERO; return message; } diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/lcd.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/lcd.ts index 318178b12f..8858e6006c 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/lcd.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/lcd.ts @@ -48,6 +48,9 @@ export const createLCDClient = async ({ subaccounts: new (await import("./subaccounts/query.lcd")).LCDQueryClient({ requestClient }), + vault: new (await import("./vault/query.lcd")).LCDQueryClient({ + requestClient + }), vest: new (await import("./vest/query.lcd")).LCDQueryClient({ requestClient }) diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.lcd.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.lcd.ts index 045342f671..8c761428e7 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.lcd.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.lcd.ts @@ -1,6 +1,6 @@ import { setPaginationParams } from "../../helpers"; import { LCDClient } from "@osmonauts/lcd"; -import { QueryGetSubaccountRequest, QuerySubaccountResponseSDKType, QueryAllSubaccountRequest, QuerySubaccountAllResponseSDKType, QueryGetWithdrawalAndTransfersBlockedInfoRequest, QueryGetWithdrawalAndTransfersBlockedInfoResponseSDKType } from "./query"; +import { QueryGetSubaccountRequest, QuerySubaccountResponseSDKType, QueryAllSubaccountRequest, QuerySubaccountAllResponseSDKType, QueryGetWithdrawalAndTransfersBlockedInfoRequest, QueryGetWithdrawalAndTransfersBlockedInfoResponseSDKType, QueryCollateralPoolAddressRequest, QueryCollateralPoolAddressResponseSDKType } from "./query"; export class LCDQueryClient { req: LCDClient; @@ -13,6 +13,7 @@ export class LCDQueryClient { this.subaccount = this.subaccount.bind(this); this.subaccountAll = this.subaccountAll.bind(this); this.getWithdrawalAndTransfersBlockedInfo = this.getWithdrawalAndTransfersBlockedInfo.bind(this); + this.collateralPoolAddress = this.collateralPoolAddress.bind(this); } /* Queries a Subaccount by id */ @@ -43,16 +44,15 @@ export class LCDQueryClient { async getWithdrawalAndTransfersBlockedInfo(params: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise { - const options: any = { - params: {} - }; + const endpoint = `dydxprotocol/subaccounts/withdrawals_and_transfers_blocked_info/${params.perpetualId}`; + return await this.req.get(endpoint); + } + /* Queries the collateral pool account address for a perpetual id. */ - if (typeof params?.perpetualId !== "undefined") { - options.params.perpetual_id = params.perpetualId; - } - const endpoint = `dydxprotocol/subaccounts/withdrawals_and_transfers_blocked_info`; - return await this.req.get(endpoint, options); + async collateralPoolAddress(params: QueryCollateralPoolAddressRequest): Promise { + const endpoint = `dydxprotocol/subaccounts/collateral_pool_address/${params.perpetualId}`; + return await this.req.get(endpoint); } } \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.rpc.Query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.rpc.Query.ts index 1e76701fcf..69ea5b399d 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.rpc.Query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/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 { QueryGetSubaccountRequest, QuerySubaccountResponse, QueryAllSubaccountRequest, QuerySubaccountAllResponse, QueryGetWithdrawalAndTransfersBlockedInfoRequest, QueryGetWithdrawalAndTransfersBlockedInfoResponse } from "./query"; +import { QueryGetSubaccountRequest, QuerySubaccountResponse, QueryAllSubaccountRequest, QuerySubaccountAllResponse, QueryGetWithdrawalAndTransfersBlockedInfoRequest, QueryGetWithdrawalAndTransfersBlockedInfoResponse, QueryCollateralPoolAddressRequest, QueryCollateralPoolAddressResponse } from "./query"; /** Query defines the gRPC querier service. */ export interface Query { @@ -16,6 +16,9 @@ export interface Query { */ getWithdrawalAndTransfersBlockedInfo(request: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise; + /** Queries the collateral pool account address for a perpetual id. */ + + collateralPoolAddress(request: QueryCollateralPoolAddressRequest): Promise; } export class QueryClientImpl implements Query { private readonly rpc: Rpc; @@ -25,6 +28,7 @@ export class QueryClientImpl implements Query { this.subaccount = this.subaccount.bind(this); this.subaccountAll = this.subaccountAll.bind(this); this.getWithdrawalAndTransfersBlockedInfo = this.getWithdrawalAndTransfersBlockedInfo.bind(this); + this.collateralPoolAddress = this.collateralPoolAddress.bind(this); } subaccount(request: QueryGetSubaccountRequest): Promise { @@ -47,6 +51,12 @@ export class QueryClientImpl implements Query { return promise.then(data => QueryGetWithdrawalAndTransfersBlockedInfoResponse.decode(new _m0.Reader(data))); } + collateralPoolAddress(request: QueryCollateralPoolAddressRequest): Promise { + const data = QueryCollateralPoolAddressRequest.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.subaccounts.Query", "CollateralPoolAddress", data); + return promise.then(data => QueryCollateralPoolAddressResponse.decode(new _m0.Reader(data))); + } + } export const createRpcQueryExtension = (base: QueryClient) => { const rpc = createProtobufRpcClient(base); @@ -62,6 +72,10 @@ export const createRpcQueryExtension = (base: QueryClient) => { getWithdrawalAndTransfersBlockedInfo(request: QueryGetWithdrawalAndTransfersBlockedInfoRequest): Promise { return queryService.getWithdrawalAndTransfersBlockedInfo(request); + }, + + collateralPoolAddress(request: QueryCollateralPoolAddressRequest): Promise { + return queryService.collateralPoolAddress(request); } }; diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.ts index 0464bd1359..d5512b12fe 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/subaccounts/query.ts @@ -84,6 +84,52 @@ export interface QueryGetWithdrawalAndTransfersBlockedInfoResponseSDKType { chain_outage_seen_at_block: number; withdrawals_and_transfers_unblocked_at_block: number; } +/** + * QueryCollateralPoolAddressRequest is the request type for fetching the + * account address of the collateral pool associated with the passed in + * perpetual id. + */ + +export interface QueryCollateralPoolAddressRequest { + /** + * QueryCollateralPoolAddressRequest is the request type for fetching the + * account address of the collateral pool associated with the passed in + * perpetual id. + */ + perpetualId: number; +} +/** + * QueryCollateralPoolAddressRequest is the request type for fetching the + * account address of the collateral pool associated with the passed in + * perpetual id. + */ + +export interface QueryCollateralPoolAddressRequestSDKType { + /** + * QueryCollateralPoolAddressRequest is the request type for fetching the + * account address of the collateral pool associated with the passed in + * perpetual id. + */ + perpetual_id: number; +} +/** + * QueryCollateralPoolAddressResponse is a response type for fetching the + * account address of the collateral pool associated with the passed in + * perpetual id. + */ + +export interface QueryCollateralPoolAddressResponse { + collateralPoolAddress: string; +} +/** + * QueryCollateralPoolAddressResponse is a response type for fetching the + * account address of the collateral pool associated with the passed in + * perpetual id. + */ + +export interface QueryCollateralPoolAddressResponseSDKType { + collateral_pool_address: string; +} function createBaseQueryGetSubaccountRequest(): QueryGetSubaccountRequest { return { @@ -393,4 +439,94 @@ export const QueryGetWithdrawalAndTransfersBlockedInfoResponse = { return message; } +}; + +function createBaseQueryCollateralPoolAddressRequest(): QueryCollateralPoolAddressRequest { + return { + perpetualId: 0 + }; +} + +export const QueryCollateralPoolAddressRequest = { + encode(message: QueryCollateralPoolAddressRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.perpetualId !== 0) { + writer.uint32(8).uint32(message.perpetualId); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryCollateralPoolAddressRequest { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryCollateralPoolAddressRequest(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.perpetualId = reader.uint32(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryCollateralPoolAddressRequest { + const message = createBaseQueryCollateralPoolAddressRequest(); + message.perpetualId = object.perpetualId ?? 0; + return message; + } + +}; + +function createBaseQueryCollateralPoolAddressResponse(): QueryCollateralPoolAddressResponse { + return { + collateralPoolAddress: "" + }; +} + +export const QueryCollateralPoolAddressResponse = { + encode(message: QueryCollateralPoolAddressResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.collateralPoolAddress !== "") { + writer.uint32(10).string(message.collateralPoolAddress); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryCollateralPoolAddressResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryCollateralPoolAddressResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.collateralPoolAddress = reader.string(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryCollateralPoolAddressResponse { + const message = createBaseQueryCollateralPoolAddressResponse(); + message.collateralPoolAddress = object.collateralPoolAddress ?? ""; + return message; + } + }; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/genesis.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/genesis.ts index 9cf3de2b6d..122b12ec03 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/genesis.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/genesis.ts @@ -1,18 +1,31 @@ +import { Params, ParamsSDKType } from "./params"; import * as _m0 from "protobufjs/minimal"; import { DeepPartial } from "../../helpers"; /** GenesisState defines `x/vault`'s genesis state. */ -export interface GenesisState {} +export interface GenesisState { + /** The parameters of the module. */ + params?: Params; +} /** GenesisState defines `x/vault`'s genesis state. */ -export interface GenesisStateSDKType {} +export interface GenesisStateSDKType { + /** The parameters of the module. */ + params?: ParamsSDKType; +} function createBaseGenesisState(): GenesisState { - return {}; + return { + params: undefined + }; } export const GenesisState = { - encode(_: GenesisState, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + encode(message: GenesisState, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.params !== undefined) { + Params.encode(message.params, writer.uint32(10).fork()).ldelim(); + } + return writer; }, @@ -25,6 +38,10 @@ export const GenesisState = { const tag = reader.uint32(); switch (tag >>> 3) { + case 1: + message.params = Params.decode(reader, reader.uint32()); + break; + default: reader.skipType(tag & 7); break; @@ -34,8 +51,9 @@ export const GenesisState = { return message; }, - fromPartial(_: DeepPartial): GenesisState { + fromPartial(object: DeepPartial): GenesisState { const message = createBaseGenesisState(); + message.params = object.params !== undefined && object.params !== null ? Params.fromPartial(object.params) : undefined; return message; } diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/params.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/params.ts new file mode 100644 index 0000000000..29ecf2563d --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/params.ts @@ -0,0 +1,153 @@ +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial } from "../../helpers"; +/** Params stores `x/vault` parameters. */ + +export interface Params { + /** + * The number of layers of orders a vault places. For example if + * `layers=2`, a vault places 2 asks and 2 bids. + */ + layers: number; + /** The minimum base spread when a vault quotes around reservation price. */ + + spreadMinPpm: number; + /** + * The buffer amount to add to min_price_change_ppm to arrive at `spread` + * according to formula: + * `spread = max(spread_min_ppm, min_price_change_ppm + spread_buffer_ppm)`. + */ + + spreadBufferPpm: number; + /** The factor that determines how aggressive a vault skews its orders. */ + + skewFactorPpm: number; + /** The percentage of vault equity that each order is sized at. */ + + orderSizePctPpm: number; + /** The duration that a vault's orders are valid for. */ + + orderExpirationSeconds: number; +} +/** Params stores `x/vault` parameters. */ + +export interface ParamsSDKType { + /** + * The number of layers of orders a vault places. For example if + * `layers=2`, a vault places 2 asks and 2 bids. + */ + layers: number; + /** The minimum base spread when a vault quotes around reservation price. */ + + spread_min_ppm: number; + /** + * The buffer amount to add to min_price_change_ppm to arrive at `spread` + * according to formula: + * `spread = max(spread_min_ppm, min_price_change_ppm + spread_buffer_ppm)`. + */ + + spread_buffer_ppm: number; + /** The factor that determines how aggressive a vault skews its orders. */ + + skew_factor_ppm: number; + /** The percentage of vault equity that each order is sized at. */ + + order_size_pct_ppm: number; + /** The duration that a vault's orders are valid for. */ + + order_expiration_seconds: number; +} + +function createBaseParams(): Params { + return { + layers: 0, + spreadMinPpm: 0, + spreadBufferPpm: 0, + skewFactorPpm: 0, + orderSizePctPpm: 0, + orderExpirationSeconds: 0 + }; +} + +export const Params = { + encode(message: Params, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.layers !== 0) { + writer.uint32(8).uint32(message.layers); + } + + if (message.spreadMinPpm !== 0) { + writer.uint32(16).uint32(message.spreadMinPpm); + } + + if (message.spreadBufferPpm !== 0) { + writer.uint32(24).uint32(message.spreadBufferPpm); + } + + if (message.skewFactorPpm !== 0) { + writer.uint32(32).uint32(message.skewFactorPpm); + } + + if (message.orderSizePctPpm !== 0) { + writer.uint32(40).uint32(message.orderSizePctPpm); + } + + if (message.orderExpirationSeconds !== 0) { + writer.uint32(48).uint32(message.orderExpirationSeconds); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Params { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseParams(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.layers = reader.uint32(); + break; + + case 2: + message.spreadMinPpm = reader.uint32(); + break; + + case 3: + message.spreadBufferPpm = reader.uint32(); + break; + + case 4: + message.skewFactorPpm = reader.uint32(); + break; + + case 5: + message.orderSizePctPpm = reader.uint32(); + break; + + case 6: + message.orderExpirationSeconds = reader.uint32(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): Params { + const message = createBaseParams(); + message.layers = object.layers ?? 0; + message.spreadMinPpm = object.spreadMinPpm ?? 0; + message.spreadBufferPpm = object.spreadBufferPpm ?? 0; + message.skewFactorPpm = object.skewFactorPpm ?? 0; + message.orderSizePctPpm = object.orderSizePctPpm ?? 0; + message.orderExpirationSeconds = object.orderExpirationSeconds ?? 0; + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.lcd.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.lcd.ts new file mode 100644 index 0000000000..3d7b91613c --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.lcd.ts @@ -0,0 +1,30 @@ +import { LCDClient } from "@osmonauts/lcd"; +import { QueryParamsRequest, QueryParamsResponseSDKType, QueryVaultRequest, QueryVaultResponseSDKType } from "./query"; +export class LCDQueryClient { + req: LCDClient; + + constructor({ + requestClient + }: { + requestClient: LCDClient; + }) { + this.req = requestClient; + this.params = this.params.bind(this); + this.vault = this.vault.bind(this); + } + /* Queries the Params. */ + + + async params(_params: QueryParamsRequest = {}): Promise { + const endpoint = `dydxprotocol/v4/vault/params`; + return await this.req.get(endpoint); + } + /* Queries a Vault by type and number. */ + + + async vault(params: QueryVaultRequest): Promise { + const endpoint = `dydxprotocol/v4/vault/vaults/${params.type}/${params.number}`; + return await this.req.get(endpoint); + } + +} \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.rpc.Query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.rpc.Query.ts index ab81adee85..2aee4acbe5 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.rpc.Query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.rpc.Query.ts @@ -1,18 +1,49 @@ import { Rpc } from "../../helpers"; +import * as _m0 from "protobufjs/minimal"; import { QueryClient, createProtobufRpcClient } from "@cosmjs/stargate"; +import { QueryParamsRequest, QueryParamsResponse, QueryVaultRequest, QueryVaultResponse } from "./query"; /** Query defines the gRPC querier service. */ -export interface Query {} +export interface Query { + /** Queries the Params. */ + params(request?: QueryParamsRequest): Promise; + /** Queries a Vault by type and number. */ + + vault(request: QueryVaultRequest): Promise; +} export class QueryClientImpl implements Query { private readonly rpc: Rpc; constructor(rpc: Rpc) { this.rpc = rpc; + this.params = this.params.bind(this); + this.vault = this.vault.bind(this); + } + + params(request: QueryParamsRequest = {}): Promise { + const data = QueryParamsRequest.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.vault.Query", "Params", data); + return promise.then(data => QueryParamsResponse.decode(new _m0.Reader(data))); + } + + vault(request: QueryVaultRequest): Promise { + const data = QueryVaultRequest.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.vault.Query", "Vault", data); + return promise.then(data => QueryVaultResponse.decode(new _m0.Reader(data))); } } export const createRpcQueryExtension = (base: QueryClient) => { const rpc = createProtobufRpcClient(base); const queryService = new QueryClientImpl(rpc); - return {}; + return { + params(request?: QueryParamsRequest): Promise { + return queryService.params(request); + }, + + vault(request: QueryVaultRequest): Promise { + return queryService.vault(request); + } + + }; }; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.ts index 693da49fc4..5c1bae70b8 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.ts @@ -1 +1,270 @@ -export {} \ No newline at end of file +import { VaultType, VaultTypeSDKType, VaultId, VaultIdSDKType } from "./vault"; +import { Params, ParamsSDKType } from "./params"; +import { SubaccountId, SubaccountIdSDKType } from "../subaccounts/subaccount"; +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial, Long } from "../../helpers"; +/** QueryParamsRequest is a request type for the Params RPC method. */ + +export interface QueryParamsRequest {} +/** QueryParamsRequest is a request type for the Params RPC method. */ + +export interface QueryParamsRequestSDKType {} +/** QueryParamsResponse is a response type for the Params RPC method. */ + +export interface QueryParamsResponse { + params?: Params; +} +/** QueryParamsResponse is a response type for the Params RPC method. */ + +export interface QueryParamsResponseSDKType { + params?: ParamsSDKType; +} +/** QueryVaultRequest is a request type for the Vault RPC method. */ + +export interface QueryVaultRequest { + type: VaultType; + number: number; +} +/** QueryVaultRequest is a request type for the Vault RPC method. */ + +export interface QueryVaultRequestSDKType { + type: VaultTypeSDKType; + number: number; +} +/** QueryVaultResponse is a response type for the Vault RPC method. */ + +export interface QueryVaultResponse { + vaultId?: VaultId; + subaccountId?: SubaccountId; + equity: Long; + inventory: Long; + totalShares: Long; +} +/** QueryVaultResponse is a response type for the Vault RPC method. */ + +export interface QueryVaultResponseSDKType { + vault_id?: VaultIdSDKType; + subaccount_id?: SubaccountIdSDKType; + equity: Long; + inventory: Long; + total_shares: Long; +} + +function createBaseQueryParamsRequest(): QueryParamsRequest { + return {}; +} + +export const QueryParamsRequest = { + encode(_: QueryParamsRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryParamsRequest { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryParamsRequest(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(_: DeepPartial): QueryParamsRequest { + const message = createBaseQueryParamsRequest(); + return message; + } + +}; + +function createBaseQueryParamsResponse(): QueryParamsResponse { + return { + params: undefined + }; +} + +export const QueryParamsResponse = { + encode(message: QueryParamsResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.params !== undefined) { + Params.encode(message.params, writer.uint32(10).fork()).ldelim(); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryParamsResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryParamsResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.params = Params.decode(reader, reader.uint32()); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryParamsResponse { + const message = createBaseQueryParamsResponse(); + message.params = object.params !== undefined && object.params !== null ? Params.fromPartial(object.params) : undefined; + return message; + } + +}; + +function createBaseQueryVaultRequest(): QueryVaultRequest { + return { + type: 0, + number: 0 + }; +} + +export const QueryVaultRequest = { + encode(message: QueryVaultRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.type !== 0) { + writer.uint32(8).int32(message.type); + } + + if (message.number !== 0) { + writer.uint32(16).uint32(message.number); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryVaultRequest { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryVaultRequest(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.type = (reader.int32() as any); + break; + + case 2: + message.number = reader.uint32(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryVaultRequest { + const message = createBaseQueryVaultRequest(); + message.type = object.type ?? 0; + message.number = object.number ?? 0; + return message; + } + +}; + +function createBaseQueryVaultResponse(): QueryVaultResponse { + return { + vaultId: undefined, + subaccountId: undefined, + equity: Long.UZERO, + inventory: Long.UZERO, + totalShares: Long.UZERO + }; +} + +export const QueryVaultResponse = { + encode(message: QueryVaultResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.vaultId !== undefined) { + VaultId.encode(message.vaultId, writer.uint32(10).fork()).ldelim(); + } + + if (message.subaccountId !== undefined) { + SubaccountId.encode(message.subaccountId, writer.uint32(18).fork()).ldelim(); + } + + if (!message.equity.isZero()) { + writer.uint32(24).uint64(message.equity); + } + + if (!message.inventory.isZero()) { + writer.uint32(32).uint64(message.inventory); + } + + if (!message.totalShares.isZero()) { + writer.uint32(40).uint64(message.totalShares); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): QueryVaultResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryVaultResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.vaultId = VaultId.decode(reader, reader.uint32()); + break; + + case 2: + message.subaccountId = SubaccountId.decode(reader, reader.uint32()); + break; + + case 3: + message.equity = (reader.uint64() as Long); + break; + + case 4: + message.inventory = (reader.uint64() as Long); + break; + + case 5: + message.totalShares = (reader.uint64() as Long); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): QueryVaultResponse { + const message = createBaseQueryVaultResponse(); + message.vaultId = object.vaultId !== undefined && object.vaultId !== null ? VaultId.fromPartial(object.vaultId) : undefined; + message.subaccountId = object.subaccountId !== undefined && object.subaccountId !== null ? SubaccountId.fromPartial(object.subaccountId) : undefined; + message.equity = object.equity !== undefined && object.equity !== null ? Long.fromValue(object.equity) : Long.UZERO; + message.inventory = object.inventory !== undefined && object.inventory !== null ? Long.fromValue(object.inventory) : Long.UZERO; + message.totalShares = object.totalShares !== undefined && object.totalShares !== null ? Long.fromValue(object.totalShares) : Long.UZERO; + return message; + } + +}; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/tx.rpc.msg.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/tx.rpc.msg.ts index e4028957d6..8a9bcbbbac 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/tx.rpc.msg.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/tx.rpc.msg.ts @@ -1,11 +1,14 @@ import { Rpc } from "../../helpers"; import * as _m0 from "protobufjs/minimal"; -import { MsgDepositToVault, MsgDepositToVaultResponse } from "./tx"; +import { MsgDepositToVault, MsgDepositToVaultResponse, MsgUpdateParams, MsgUpdateParamsResponse } from "./tx"; /** Msg defines the Msg service. */ export interface Msg { /** DepositToVault deposits funds into a vault. */ depositToVault(request: MsgDepositToVault): Promise; + /** UpdateParams updates the Params in state. */ + + updateParams(request: MsgUpdateParams): Promise; } export class MsgClientImpl implements Msg { private readonly rpc: Rpc; @@ -13,6 +16,7 @@ export class MsgClientImpl implements Msg { constructor(rpc: Rpc) { this.rpc = rpc; this.depositToVault = this.depositToVault.bind(this); + this.updateParams = this.updateParams.bind(this); } depositToVault(request: MsgDepositToVault): Promise { @@ -21,4 +25,10 @@ export class MsgClientImpl implements Msg { return promise.then(data => MsgDepositToVaultResponse.decode(new _m0.Reader(data))); } + updateParams(request: MsgUpdateParams): Promise { + const data = MsgUpdateParams.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.vault.Msg", "UpdateParams", data); + return promise.then(data => MsgUpdateParamsResponse.decode(new _m0.Reader(data))); + } + } \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/tx.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/tx.ts index b3587f335b..1aed45a95f 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/tx.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/tx.ts @@ -1,5 +1,6 @@ import { VaultId, VaultIdSDKType } from "./vault"; import { SubaccountId, SubaccountIdSDKType } from "../subaccounts/subaccount"; +import { Params, ParamsSDKType } from "./params"; import * as _m0 from "protobufjs/minimal"; import { DeepPartial } from "../../helpers"; /** MsgDepositToVault is the Msg/DepositToVault request type. */ @@ -32,6 +33,28 @@ export interface MsgDepositToVaultResponse {} /** MsgDepositToVaultResponse is the Msg/DepositToVault response type. */ export interface MsgDepositToVaultResponseSDKType {} +/** MsgUpdateParams is the Msg/UpdateParams request type. */ + +export interface MsgUpdateParams { + authority: string; + /** The parameters to update. Each field must be set. */ + + params?: Params; +} +/** MsgUpdateParams is the Msg/UpdateParams request type. */ + +export interface MsgUpdateParamsSDKType { + authority: string; + /** The parameters to update. Each field must be set. */ + + params?: ParamsSDKType; +} +/** MsgUpdateParamsResponse is the Msg/UpdateParams response type. */ + +export interface MsgUpdateParamsResponse {} +/** MsgUpdateParamsResponse is the Msg/UpdateParams response type. */ + +export interface MsgUpdateParamsResponseSDKType {} function createBaseMsgDepositToVault(): MsgDepositToVault { return { @@ -130,4 +153,93 @@ export const MsgDepositToVaultResponse = { return message; } +}; + +function createBaseMsgUpdateParams(): MsgUpdateParams { + return { + authority: "", + params: undefined + }; +} + +export const MsgUpdateParams = { + encode(message: MsgUpdateParams, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.authority !== "") { + writer.uint32(10).string(message.authority); + } + + if (message.params !== undefined) { + Params.encode(message.params, writer.uint32(18).fork()).ldelim(); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgUpdateParams { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgUpdateParams(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.authority = reader.string(); + break; + + case 2: + message.params = Params.decode(reader, reader.uint32()); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): MsgUpdateParams { + const message = createBaseMsgUpdateParams(); + message.authority = object.authority ?? ""; + message.params = object.params !== undefined && object.params !== null ? Params.fromPartial(object.params) : undefined; + return message; + } + +}; + +function createBaseMsgUpdateParamsResponse(): MsgUpdateParamsResponse { + return {}; +} + +export const MsgUpdateParamsResponse = { + encode(_: MsgUpdateParamsResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgUpdateParamsResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgUpdateParamsResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(_: DeepPartial): MsgUpdateParamsResponse { + const message = createBaseMsgUpdateParamsResponse(); + return message; + } + }; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/vault.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/vault.ts index cc93d62a58..a9a7696cef 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/vault.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/vault.ts @@ -144,7 +144,7 @@ function createBaseNumShares(): NumShares { export const NumShares = { encode(message: NumShares, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { if (message.numShares.length !== 0) { - writer.uint32(10).bytes(message.numShares); + writer.uint32(18).bytes(message.numShares); } return writer; @@ -159,7 +159,7 @@ export const NumShares = { const tag = reader.uint32(); switch (tag >>> 3) { - case 1: + case 2: message.numShares = reader.bytes(); break; diff --git a/indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts b/indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts index 1a8dc184de..f86995801b 100644 --- a/indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts +++ b/indexer/packages/v4-protos/src/codegen/gogoproto/bundle.ts @@ -1,3 +1,3 @@ -import * as _104 from "./gogo"; -export const gogoproto = { ..._104 +import * as _105 from "./gogo"; +export const gogoproto = { ..._105 }; \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/google/bundle.ts b/indexer/packages/v4-protos/src/codegen/google/bundle.ts index 3ae0e3f38d..bd8180db33 100644 --- a/indexer/packages/v4-protos/src/codegen/google/bundle.ts +++ b/indexer/packages/v4-protos/src/codegen/google/bundle.ts @@ -1,16 +1,16 @@ -import * as _105 from "./api/annotations"; -import * as _106 from "./api/http"; -import * as _107 from "./protobuf/descriptor"; -import * as _108 from "./protobuf/duration"; -import * as _109 from "./protobuf/timestamp"; -import * as _110 from "./protobuf/any"; +import * as _106 from "./api/annotations"; +import * as _107 from "./api/http"; +import * as _108 from "./protobuf/descriptor"; +import * as _109 from "./protobuf/duration"; +import * as _110 from "./protobuf/timestamp"; +import * as _111 from "./protobuf/any"; export namespace google { - export const api = { ..._105, - ..._106 + export const api = { ..._106, + ..._107 }; - export const protobuf = { ..._107, - ..._108, + export const protobuf = { ..._108, ..._109, - ..._110 + ..._110, + ..._111 }; } \ No newline at end of file diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/addresses-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/addresses-controller.test.ts index e28feb7e21..119d085d40 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/addresses-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/addresses-controller.test.ts @@ -15,6 +15,7 @@ import { RequestMethod } from '../../../../src/types'; import request from 'supertest'; import { getFixedRepresentation, sendRequest } from '../../../helpers/helpers'; import { stats } from '@dydxprotocol-indexer/base'; +import { defaultAddress } from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants'; describe('addresses-controller#V4', () => { const latestHeight: string = '3'; @@ -379,4 +380,176 @@ describe('addresses-controller#V4', () => { }); }); }); + + describe('/addresses/:address/parentSubaccountNumber/:parentSubaccountNumber', () => { + afterEach(async () => { + await dbHelpers.clearData(); + }); + + it('Get /:address/parentSubaccountNumber/ gets all subaccounts for the provided parent', async () => { + await PerpetualPositionTable.create( + testConstants.defaultPerpetualPosition, + ); + + await Promise.all([ + AssetPositionTable.upsert(testConstants.defaultAssetPosition), + AssetPositionTable.upsert({ + ...testConstants.defaultAssetPosition2, + subaccountId: testConstants.defaultSubaccountId, + }), + AssetPositionTable.upsert(testConstants.isolatedSubaccountAssetPosition), + FundingIndexUpdatesTable.create({ + ...testConstants.defaultFundingIndexUpdate, + fundingIndex: initialFundingIndex, + effectiveAtHeight: testConstants.createdHeight, + }), + FundingIndexUpdatesTable.create({ + ...testConstants.defaultFundingIndexUpdate, + eventId: testConstants.defaultTendermintEventId2, + effectiveAtHeight: latestHeight, + }), + ]); + + const parentSubaccountNumber: number = 0; + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/addresses/${testConstants.defaultAddress}/parentSubaccountNumber/${parentSubaccountNumber}`, + }); + + expect(response.body).toEqual({ + subaccount: { + address: testConstants.defaultAddress, + parentSubaccountNumber, + equity: getFixedRepresentation(164500), + freeCollateral: getFixedRepresentation(157000), + childSubaccounts: [ + { + address: testConstants.defaultAddress, + subaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + equity: getFixedRepresentation(159500), + freeCollateral: getFixedRepresentation(152000), + marginEnabled: true, + openPerpetualPositions: { + [testConstants.defaultPerpetualMarket.ticker]: { + market: testConstants.defaultPerpetualMarket.ticker, + size: testConstants.defaultPerpetualPosition.size, + side: testConstants.defaultPerpetualPosition.side, + entryPrice: getFixedRepresentation( + testConstants.defaultPerpetualPosition.entryPrice!, + ), + maxSize: testConstants.defaultPerpetualPosition.maxSize, + // 200000 + 10*(10000-10050)=199500 + netFunding: getFixedRepresentation('199500'), + // sumClose=0, so realized Pnl is the same as the net funding of the position. + // Unsettled funding is funding payments that already "happened" but not reflected + // in the subaccount's balance yet, so it's considered a part of realizedPnl. + realizedPnl: getFixedRepresentation('199500'), + // size * (index-entry) = 10*(15000-20000) = -50000 + unrealizedPnl: getFixedRepresentation(-50000), + status: testConstants.defaultPerpetualPosition.status, + sumOpen: testConstants.defaultPerpetualPosition.sumOpen, + sumClose: testConstants.defaultPerpetualPosition.sumClose, + createdAt: testConstants.defaultPerpetualPosition.createdAt, + createdAtHeight: testConstants.defaultPerpetualPosition.createdAtHeight, + exitPrice: null, + closedAt: null, + }, + }, + assetPositions: { + [testConstants.defaultAsset.symbol]: { + symbol: testConstants.defaultAsset.symbol, + size: '9500', + side: PositionSide.LONG, + assetId: testConstants.defaultAssetPosition.assetId, + subaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }, + [testConstants.defaultAsset2.symbol]: { + symbol: testConstants.defaultAsset2.symbol, + size: testConstants.defaultAssetPosition2.size, + side: PositionSide.SHORT, + assetId: testConstants.defaultAssetPosition2.assetId, + subaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }, + }, + }, + { + address: testConstants.defaultAddress, + subaccountNumber: testConstants.isolatedSubaccount.subaccountNumber, + equity: getFixedRepresentation(5000), + freeCollateral: getFixedRepresentation(5000), + marginEnabled: true, + openPerpetualPositions: {}, + assetPositions: { + [testConstants.defaultAsset.symbol]: { + symbol: testConstants.defaultAsset.symbol, + size: testConstants.isolatedSubaccountAssetPosition.size, + side: PositionSide.LONG, + assetId: testConstants.isolatedSubaccountAssetPosition.assetId, + subaccountNumber: testConstants.isolatedSubaccount.subaccountNumber, + }, + }, + }, + { + address: testConstants.defaultAddress, + subaccountNumber: testConstants.isolatedSubaccount2.subaccountNumber, + equity: getFixedRepresentation(0), + freeCollateral: getFixedRepresentation(0), + marginEnabled: true, + openPerpetualPositions: {}, + assetPositions: {}, + }, + ], + }, + }); + expect(stats.increment).toHaveBeenCalledWith('comlink.addresses-controller.response_status_code.200', 1, + { + path: '/:address/parentSubaccountNumber/:parentSubaccountNumber', + method: 'GET', + }); + }); + }); + + it('Get /:address/parentSubaccountNumber/ with non-existent address returns 404', async () => { + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/addresses/${invalidAddress}/parentSubaccountNumber/` + + `${testConstants.defaultSubaccount.subaccountNumber}`, + expectedStatus: 404, + }); + + expect(response.body).toEqual({ + errors: [ + { + msg: `No subaccounts found for address ${invalidAddress} and ` + + `parentSubaccountNumber ${testConstants.defaultSubaccount.subaccountNumber}`, + }, + ], + }); + expect(stats.increment).toHaveBeenCalledWith('comlink.addresses-controller.response_status_code.404', 1, + { + path: '/:address/parentSubaccountNumber/:parentSubaccountNumber', + method: 'GET', + }); + }); + + it('Get /:address/parentSubaccountNumber/ with invalid parentSubaccount number returns 400', async () => { + const parentSubaccountNumber: number = 128; + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/addresses/${defaultAddress}/parentSubaccountNumber/${parentSubaccountNumber}`, + expectedStatus: 400, + }); + + expect(response.body).toEqual({ + errors: [ + { + location: 'params', + msg: 'parentSubaccountNumber must be a non-negative integer less than 128', + param: 'parentSubaccountNumber', + value: '128', + }, + ], + }); + }); + }); diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/asset-positions-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/asset-positions-controller.test.ts index 3e9dd4e60e..2385cd52aa 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/asset-positions-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/asset-positions-controller.test.ts @@ -38,7 +38,7 @@ describe('asset-positions-controller#V4', () => { const response: request.Response = await sendRequest({ type: RequestMethod.GET, path: `/v4/assetPositions?address=${testConstants.defaultAddress}` + - `&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, + `&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, }); const expectedAssetPosition: AssetPositionResponseObject = { @@ -58,7 +58,7 @@ describe('asset-positions-controller#V4', () => { ); }); - it('Get /assetPositions gets short asset and perpetual positions', async () => { + it('Get /assetPositions gets short asset positions', async () => { await testMocks.seedData(); await AssetPositionTable.upsert({ ...testConstants.defaultAssetPosition, @@ -68,7 +68,7 @@ describe('asset-positions-controller#V4', () => { const response: request.Response = await sendRequest({ type: RequestMethod.GET, path: `/v4/assetPositions?address=${testConstants.defaultAddress}` + - `&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, + `&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, }); const expectedAssetPosition: AssetPositionResponseObject = { @@ -103,7 +103,7 @@ describe('asset-positions-controller#V4', () => { const response: request.Response = await sendRequest({ type: RequestMethod.GET, path: `/v4/assetPositions?address=${testConstants.defaultAddress}` + - `&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, + `&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, }); expect(response.body.positions).toEqual( @@ -133,11 +133,13 @@ describe('asset-positions-controller#V4', () => { subaccountId: testConstants.defaultSubaccountId, size: '0', }), + // Funding index at height 0 is 10000 FundingIndexUpdatesTable.create({ ...testConstants.defaultFundingIndexUpdate, fundingIndex: '10000', effectiveAtHeight: testConstants.createdHeight, }), + // Funding index at height 3 is 10050 FundingIndexUpdatesTable.create({ ...testConstants.defaultFundingIndexUpdate, eventId: testConstants.defaultTendermintEventId2, @@ -148,12 +150,14 @@ describe('asset-positions-controller#V4', () => { const response: request.Response = await sendRequest({ type: RequestMethod.GET, path: `/v4/assetPositions?address=${testConstants.defaultAddress}` + - `&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, + `&subaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, }); expect(response.body.positions).toEqual( [{ symbol: testConstants.defaultAsset.symbol, + // funding index difference = 10050 (height 3) - 10000 (height 0) = 50 + // size = 10000 (initial size) - 50 (funding index diff) * 10(position size) size: '9500', side: PositionSide.LONG, assetId: testConstants.defaultAssetPosition.assetId, @@ -161,5 +165,149 @@ describe('asset-positions-controller#V4', () => { }], ); }); + + it('Get /assetPositions/parentSubaccountNumber gets long and short asset positions across subaccounts', async () => { + await testMocks.seedData(); + await Promise.all([ + AssetPositionTable.upsert(testConstants.defaultAssetPosition), + AssetPositionTable.upsert({ + ...testConstants.isolatedSubaccountAssetPosition, + isLong: false, + }), + ]); + + const parentSubaccountNumber: number = 0; + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/assetPositions/parentSubaccountNumber?address=${testConstants.defaultAddress}` + + `&parentSubaccountNumber=${parentSubaccountNumber}`, + }); + + const expectedAssetPosition: AssetPositionResponseObject = { + symbol: testConstants.defaultAsset.symbol, + side: PositionSide.LONG, + size: testConstants.defaultAssetPosition.size, + assetId: testConstants.defaultAssetPosition.assetId, + subaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }; + const expectedIsolatedAssetPosition: AssetPositionResponseObject = { + symbol: testConstants.defaultAsset.symbol, + side: PositionSide.SHORT, + size: testConstants.isolatedSubaccountAssetPosition.size, + assetId: testConstants.isolatedSubaccountAssetPosition.assetId, + subaccountNumber: testConstants.isolatedSubaccount.subaccountNumber, + }; + + expect(response.body.positions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ...expectedAssetPosition, + }), + expect.objectContaining({ + ...expectedIsolatedAssetPosition, + }), + ]), + ); + }); + + it('Get /assetPositions/parentSubaccountNumber does not get asset positions with 0 size', async () => { + await testMocks.seedData(); + + await Promise.all([ + await AssetPositionTable.upsert(testConstants.defaultAssetPosition), + await AssetPositionTable.upsert({ + ...testConstants.isolatedSubaccountAssetPosition, + size: '0', + }), + ]); + + const parentSubaccountNumber: number = 0; + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/assetPositions/parentSubaccountNumber?address=${testConstants.defaultAddress}` + + `&parentSubaccountNumber=${parentSubaccountNumber}`, + }); + + expect(response.body.positions).toEqual( + [{ + symbol: testConstants.defaultAsset.symbol, + size: testConstants.defaultAssetPosition.size, + side: PositionSide.LONG, + assetId: testConstants.defaultAssetPosition.assetId, + subaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }], + ); + }); + + it('Get /assetPositions/parentSubaccountNumber gets USDC asset positions adjusted by unsettled funding', async () => { + await testMocks.seedData(); + await BlockTable.create({ + ...testConstants.defaultBlock, + blockHeight: '3', + }); + await Promise.all([ + PerpetualPositionTable.create(testConstants.defaultPerpetualPosition), + PerpetualPositionTable.create(testConstants.isolatedPerpetualPosition), + AssetPositionTable.upsert(testConstants.defaultAssetPosition), + AssetPositionTable.upsert(testConstants.isolatedSubaccountAssetPosition), + FundingIndexUpdatesTable.create({ + ...testConstants.defaultFundingIndexUpdate, + fundingIndex: '10000', + effectiveAtHeight: testConstants.createdHeight, + }), + FundingIndexUpdatesTable.create({ + ...testConstants.defaultFundingIndexUpdate, + eventId: testConstants.defaultTendermintEventId2, + effectiveAtHeight: '3', + }), + FundingIndexUpdatesTable.create({ + ...testConstants.isolatedMarketFundingIndexUpdate, + fundingIndex: '10000', + effectiveAtHeight: testConstants.createdHeight, + }), + FundingIndexUpdatesTable.create({ + ...testConstants.isolatedMarketFundingIndexUpdate, + eventId: testConstants.defaultTendermintEventId2, + effectiveAtHeight: '3', + }), + ]); + + const parentSubaccountNumber: number = 0; + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/assetPositions/parentSubaccountNumber?address=${testConstants.defaultAddress}` + + `&parentSubaccountNumber=${parentSubaccountNumber}`, + }); + + const expectedAssetPosition: AssetPositionResponseObject = { + symbol: testConstants.defaultAsset.symbol, + side: PositionSide.LONG, + // funding index difference = 10050 (height 3) - 10000 (height 0) = 50 + // size = 10000 (initial size) - 50 (funding index diff) * 10(position size) + size: '9500', + assetId: testConstants.defaultAssetPosition.assetId, + subaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }; + const expectedIsolatedAssetPosition: AssetPositionResponseObject = { + symbol: testConstants.defaultAsset.symbol, + side: PositionSide.LONG, + // funding index difference = 10200 (height 3) - 10000 (height 0) = 200 + // size = 5000 (initial size) - 200 (funding index diff) * 10(position size) + size: '3000', + assetId: testConstants.isolatedSubaccountAssetPosition.assetId, + subaccountNumber: testConstants.isolatedSubaccount.subaccountNumber, + }; + + expect(response.body.positions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ...expectedAssetPosition, + }), + expect.objectContaining({ + ...expectedIsolatedAssetPosition, + }), + ]), + ); + }); }); }); diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/transfers-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/transfers-controller.test.ts index a89c3c027e..d2fef6368c 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/transfers-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/transfers-controller.test.ts @@ -7,9 +7,16 @@ import { TransferType, WalletTable, } from '@dydxprotocol-indexer/postgres'; -import { RequestMethod, TransferResponseObject } from '../../../../src/types'; +import { ParentSubaccountTransferResponseObject, RequestMethod, TransferResponseObject } from '../../../../src/types'; import request from 'supertest'; import { sendRequest } from '../../../helpers/helpers'; +import { + createdDateTime, createdHeight, + defaultAsset, + defaultTendermintEventId4, + defaultWalletAddress, + isolatedSubaccountId, +} from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants'; describe('transfers-controller#V4', () => { beforeAll(async () => { @@ -287,5 +294,417 @@ describe('transfers-controller#V4', () => { ], }); }); + + it('Get /transfers/parentSubaccountNumber returns transfers/deposits/withdrawals', async () => { + await testMocks.seedData(); + const transfer2: TransferCreateObject = { + senderSubaccountId: testConstants.defaultSubaccountId2, + recipientSubaccountId: testConstants.defaultSubaccountId, + assetId: testConstants.defaultAsset2.id, + size: '5', + eventId: testConstants.defaultTendermintEventId2, + transactionHash: '', // TODO: Add a real transaction Hash + createdAt: testConstants.createdDateTime.toISO(), + createdAtHeight: testConstants.createdHeight, + }; + await WalletTable.create({ + address: testConstants.defaultWalletAddress, + totalTradingRewards: '0', + }); + await Promise.all([ + TransferTable.create(testConstants.defaultTransfer), + TransferTable.create(transfer2), + TransferTable.create(testConstants.defaultWithdrawal), + TransferTable.create(testConstants.defaultDeposit), + ]); + + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/transfers/parentSubaccountNumber?address=${testConstants.defaultAddress}` + + `&parentSubaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, + }); + + const expectedTransferResponse: ParentSubaccountTransferResponseObject = { + id: testConstants.defaultTransferId, + sender: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }, + recipient: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount2.subaccountNumber, + }, + size: testConstants.defaultTransfer.size, + createdAt: testConstants.defaultTransfer.createdAt, + createdAtHeight: testConstants.defaultTransfer.createdAtHeight, + symbol: testConstants.defaultAsset.symbol, + type: TransferType.TRANSFER_OUT, + transactionHash: testConstants.defaultTransfer.transactionHash, + }; + + const expectedTransfer2Response: ParentSubaccountTransferResponseObject = { + id: TransferTable.uuid( + transfer2.eventId, + transfer2.assetId, + transfer2.senderSubaccountId, + transfer2.recipientSubaccountId, + transfer2.senderWalletAddress, + transfer2.recipientWalletAddress, + ), + sender: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount2.subaccountNumber, + }, + recipient: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }, + size: transfer2.size, + createdAt: transfer2.createdAt, + createdAtHeight: transfer2.createdAtHeight, + symbol: testConstants.defaultAsset2.symbol, + type: TransferType.TRANSFER_IN, + transactionHash: transfer2.transactionHash, + }; + + const expectedDepositResponse: ParentSubaccountTransferResponseObject = { + id: testConstants.defaultDepositId, + sender: { + address: testConstants.defaultWalletAddress, + }, + recipient: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }, + size: testConstants.defaultDeposit.size, + createdAt: testConstants.defaultDeposit.createdAt, + createdAtHeight: testConstants.defaultDeposit.createdAtHeight, + symbol: testConstants.defaultAsset.symbol, + type: TransferType.DEPOSIT, + transactionHash: testConstants.defaultDeposit.transactionHash, + }; + + const expectedWithdrawalResponse: ParentSubaccountTransferResponseObject = { + id: testConstants.defaultWithdrawalId, + sender: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }, + recipient: { + address: testConstants.defaultWalletAddress, + }, + size: testConstants.defaultWithdrawal.size, + createdAt: testConstants.defaultWithdrawal.createdAt, + createdAtHeight: testConstants.defaultWithdrawal.createdAtHeight, + symbol: testConstants.defaultAsset.symbol, + type: TransferType.WITHDRAWAL, + transactionHash: testConstants.defaultWithdrawal.transactionHash, + }; + + expect(response.body.transfers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ...expectedTransferResponse, + }), + expect.objectContaining({ + ...expectedTransfer2Response, + }), + expect.objectContaining({ + ...expectedWithdrawalResponse, + }), + expect.objectContaining({ + ...expectedDepositResponse, + }), + ]), + ); + }); + + it('Get /transfers/parentSubaccountNumber excludes transfers for parent <> child subaccounts', async () => { + await testMocks.seedData(); + const transfer2: TransferCreateObject = { + senderSubaccountId: testConstants.defaultSubaccountId, + recipientSubaccountId: testConstants.isolatedSubaccountId, + assetId: testConstants.defaultAsset.id, + size: '5', + eventId: testConstants.defaultTendermintEventId2, + transactionHash: '', // TODO: Add a real transaction Hash + createdAt: testConstants.createdDateTime.toISO(), + createdAtHeight: testConstants.createdHeight, + }; + const transfer3: TransferCreateObject = { + senderSubaccountId: testConstants.isolatedSubaccountId2, + recipientSubaccountId: testConstants.defaultSubaccountId, + assetId: testConstants.defaultAsset.id, + size: '5', + eventId: testConstants.defaultTendermintEventId3, + transactionHash: '', // TODO: Add a real transaction Hash + createdAt: testConstants.createdDateTime.toISO(), + createdAtHeight: testConstants.createdHeight, + }; + await WalletTable.create({ + address: testConstants.defaultWalletAddress, + totalTradingRewards: '0', + }); + await Promise.all([ + TransferTable.create(testConstants.defaultTransfer), + TransferTable.create(transfer2), + TransferTable.create(transfer3), + ]); + + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/transfers/parentSubaccountNumber?address=${testConstants.defaultAddress}` + + `&parentSubaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, + }); + + const expectedTransferResponse: ParentSubaccountTransferResponseObject = { + id: testConstants.defaultTransferId, + sender: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount.subaccountNumber, + }, + recipient: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount2.subaccountNumber, + }, + size: testConstants.defaultTransfer.size, + createdAt: testConstants.defaultTransfer.createdAt, + createdAtHeight: testConstants.defaultTransfer.createdAtHeight, + symbol: testConstants.defaultAsset.symbol, + type: TransferType.TRANSFER_OUT, + transactionHash: testConstants.defaultTransfer.transactionHash, + }; + + expect(response.body.transfers.length).toEqual(1); + expect(response.body.transfers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ...expectedTransferResponse, + }), + ]), + ); + }); + + it('Get /transfers/parentSubaccountNumber includes transfers for wallets/subaccounts(non parent) <> child subaccounts', async () => { + await testMocks.seedData(); + const transferFromNonParent: TransferCreateObject = { + senderSubaccountId: testConstants.defaultSubaccountId2, + recipientSubaccountId: testConstants.isolatedSubaccountId, + assetId: testConstants.defaultAsset.id, + size: '5', + eventId: testConstants.defaultTendermintEventId2, + transactionHash: '', // TODO: Add a real transaction Hash + createdAt: testConstants.createdDateTime.toISO(), + createdAtHeight: testConstants.createdHeight, + }; + const transferToNonParent: TransferCreateObject = { + senderSubaccountId: testConstants.isolatedSubaccountId2, + recipientSubaccountId: testConstants.defaultSubaccountId2, + assetId: testConstants.defaultAsset.id, + size: '5', + eventId: testConstants.defaultTendermintEventId3, + transactionHash: '', // TODO: Add a real transaction Hash + createdAt: testConstants.createdDateTime.toISO(), + createdAtHeight: testConstants.createdHeight, + }; + const depositToChildSA: TransferCreateObject = { + senderWalletAddress: defaultWalletAddress, + recipientSubaccountId: isolatedSubaccountId, + assetId: defaultAsset.id, + size: '10', + eventId: defaultTendermintEventId4, + transactionHash: '', // TODO: Add a real transaction Hash + createdAt: createdDateTime.toISO(), + createdAtHeight: createdHeight, + }; + const withdrawFromChildSA: TransferCreateObject = { + senderSubaccountId: isolatedSubaccountId, + recipientWalletAddress: defaultWalletAddress, + assetId: defaultAsset.id, + size: '10', + eventId: defaultTendermintEventId4, + transactionHash: '', // TODO: Add a real transaction Hash + createdAt: createdDateTime.toISO(), + createdAtHeight: createdHeight, + }; + await WalletTable.create({ + address: testConstants.defaultWalletAddress, + totalTradingRewards: '0', + }); + await Promise.all([ + TransferTable.create(transferFromNonParent), + TransferTable.create(transferToNonParent), + TransferTable.create(depositToChildSA), + TransferTable.create(withdrawFromChildSA), + ]); + + const parentSubaccountNumber: number = 0; + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/transfers/parentSubaccountNumber?address=${testConstants.defaultAddress}` + + `&parentSubaccountNumber=${parentSubaccountNumber}`, + }); + + const expectedTransferResponse1: ParentSubaccountTransferResponseObject = { + id: TransferTable.uuid( + transferFromNonParent.eventId, + transferFromNonParent.assetId, + transferFromNonParent.senderSubaccountId, + transferFromNonParent.recipientSubaccountId, + transferFromNonParent.senderWalletAddress, + transferFromNonParent.recipientWalletAddress, + ), + sender: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount2.subaccountNumber, + }, + recipient: { + address: testConstants.defaultAddress, + parentSubaccountNumber: 0, + }, + size: transferFromNonParent.size, + createdAt: transferFromNonParent.createdAt, + createdAtHeight: transferFromNonParent.createdAtHeight, + symbol: testConstants.defaultAsset.symbol, + type: TransferType.TRANSFER_IN, + transactionHash: transferFromNonParent.transactionHash, + }; + const expectedTransferResponse2: ParentSubaccountTransferResponseObject = { + id: TransferTable.uuid( + transferToNonParent.eventId, + transferToNonParent.assetId, + transferToNonParent.senderSubaccountId, + transferToNonParent.recipientSubaccountId, + transferToNonParent.senderWalletAddress, + transferToNonParent.recipientWalletAddress, + ), + sender: { + address: testConstants.defaultAddress, + parentSubaccountNumber: 0, + }, + recipient: { + address: testConstants.defaultAddress, + parentSubaccountNumber: testConstants.defaultSubaccount2.subaccountNumber, + }, + size: transferToNonParent.size, + createdAt: transferToNonParent.createdAt, + createdAtHeight: transferToNonParent.createdAtHeight, + symbol: testConstants.defaultAsset.symbol, + type: TransferType.TRANSFER_OUT, + transactionHash: transferToNonParent.transactionHash, + }; + const expectedDepositResponse: ParentSubaccountTransferResponseObject = { + id: TransferTable.uuid( + depositToChildSA.eventId, + depositToChildSA.assetId, + depositToChildSA.senderSubaccountId, + depositToChildSA.recipientSubaccountId, + depositToChildSA.senderWalletAddress, + depositToChildSA.recipientWalletAddress, + ), + sender: { + address: testConstants.defaultWalletAddress, + }, + recipient: { + address: testConstants.defaultAddress, + parentSubaccountNumber: 0, + }, + size: depositToChildSA.size, + createdAt: depositToChildSA.createdAt, + createdAtHeight: depositToChildSA.createdAtHeight, + symbol: testConstants.defaultAsset.symbol, + type: TransferType.DEPOSIT, + transactionHash: depositToChildSA.transactionHash, + }; + const expectedWithdrawalResponse: ParentSubaccountTransferResponseObject = { + id: TransferTable.uuid( + withdrawFromChildSA.eventId, + withdrawFromChildSA.assetId, + withdrawFromChildSA.senderSubaccountId, + withdrawFromChildSA.recipientSubaccountId, + withdrawFromChildSA.senderWalletAddress, + withdrawFromChildSA.recipientWalletAddress, + ), + sender: { + address: testConstants.defaultAddress, + parentSubaccountNumber: 0, + }, + recipient: { + address: testConstants.defaultWalletAddress, + }, + size: withdrawFromChildSA.size, + createdAt: withdrawFromChildSA.createdAt, + createdAtHeight: withdrawFromChildSA.createdAtHeight, + symbol: testConstants.defaultAsset.symbol, + type: TransferType.WITHDRAWAL, + transactionHash: withdrawFromChildSA.transactionHash, + }; + + expect(response.body.transfers.length).toEqual(4); + expect(response.body.transfers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ...expectedTransferResponse1, + }), + expect.objectContaining({ + ...expectedTransferResponse2, + }), + expect.objectContaining({ + ...expectedDepositResponse, + }), + expect.objectContaining({ + ...expectedWithdrawalResponse, + }), + ]), + ); + }); + + it('Get /transfers/parentSubaccountNumber returns empty when there are no transfers', async () => { + await testMocks.seedData(); + + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/transfers/parentSubaccountNumber?address=${testConstants.defaultAddress}` + + `&parentSubaccountNumber=${testConstants.defaultSubaccount.subaccountNumber}`, + }); + + expect(response.body.transfers).toHaveLength(0); + }); + + it('Get /transfers/parentSubaccountNumber with non-existent address and subaccount number returns 404', async () => { + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: '/v4/transfers/parentSubaccountNumber?address=invalid_address&parentSubaccountNumber=100', + expectedStatus: 404, + }); + + expect(response.body).toEqual({ + errors: [ + { + msg: 'No subaccount found with address invalid_address and parentSubaccountNumber 100', + }, + ], + }); + }); + + it('Get /transfers/parentSubaccountNumber with invalid parentSubaccountNumber', async () => { + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/transfers/parentSubaccountNumber?address=${testConstants.defaultAddress}` + + '&parentSubaccountNumber=128', + expectedStatus: 400, + }); + + expect(response.body).toEqual({ + errors: [ + { + location: 'query', + msg: 'parentSubaccountNumber must be a non-negative integer less than 128', + param: 'parentSubaccountNumber', + value: '128', + }, + ], + }); + }); }); }); diff --git a/indexer/services/comlink/__tests__/lib/helpers.test.ts b/indexer/services/comlink/__tests__/lib/helpers.test.ts index 815d1382cc..9f6728b522 100644 --- a/indexer/services/comlink/__tests__/lib/helpers.test.ts +++ b/indexer/services/comlink/__tests__/lib/helpers.test.ts @@ -39,7 +39,7 @@ import { getSignedNotionalAndRisk, getTotalUnsettledFunding, getPerpetualPositionsWithUpdatedFunding, - initializePerpetualPositionsWithFunding, + initializePerpetualPositionsWithFunding, getChildSubaccountNums, getParentSubaccountNum, } from '../../src/lib/helpers'; import _ from 'lodash'; import Big from 'big.js'; @@ -697,4 +697,41 @@ describe('helpers', () => { .toEqual('0'); }); }); + + describe('getChildSubaccountNums', () => { + it('Gets a list of all possible child subaccount numbers for a parent subaccount 0', () => { + const childSubaccounts = getChildSubaccountNums(0); + expect(childSubaccounts.length).toEqual(1000); + expect(childSubaccounts[0]).toEqual(0); + expect(childSubaccounts[1]).toEqual(128); + expect(childSubaccounts[999]).toEqual(128 * 999); + }); + it('Gets a list of all possible child subaccount numbers for a parent subaccount 127', () => { + const childSubaccounts = getChildSubaccountNums(127); + expect(childSubaccounts.length).toEqual(1000); + expect(childSubaccounts[0]).toEqual(127); + expect(childSubaccounts[1]).toEqual(128 + 127); + expect(childSubaccounts[999]).toEqual(128 * 999 + 127); + }); + }); + + describe('getChildSubaccountNums', () => { + it('Throws an error if the parent subaccount number is greater than or equal to the maximum parent subaccount number', () => { + expect(() => getChildSubaccountNums(128)).toThrowError('Parent subaccount number must be less than 128'); + }); + }); + + describe('getParentSubaccountNum', () => { + it('Gets the parent subaccount number from a child subaccount number', () => { + expect(getParentSubaccountNum(0)).toEqual(0); + expect(getParentSubaccountNum(128)).toEqual(0); + expect(getParentSubaccountNum(128 * 999 - 1)).toEqual(127); + }); + }); + + describe('getParentSubaccountNum', () => { + it('Throws an error if the child subaccount number is greater than the max child subaccount number', () => { + expect(() => getParentSubaccountNum(128001)).toThrowError('Child subaccount number must be less than 128000'); + }); + }); }); diff --git a/indexer/services/comlink/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index 50a9a43c87..f3dd667640 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -261,6 +261,137 @@ fetch('https://dydx-testnet.imperator.co/v4/addresses/{address}/subaccountNumber This operation does not require authentication +## GetParentSubaccount + + + +> Code samples + +```python +import requests +headers = { + 'Accept': 'application/json' +} + +r = requests.get('https://dydx-testnet.imperator.co/v4/addresses/{address}/parentSubaccountNumber/{parentSubaccountNumber}', headers = headers) + +print(r.json()) + +``` + +```javascript + +const headers = { + 'Accept':'application/json' +}; + +fetch('https://dydx-testnet.imperator.co/v4/addresses/{address}/parentSubaccountNumber/{parentSubaccountNumber}', +{ + method: 'GET', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +`GET /addresses/{address}/parentSubaccountNumber/{parentSubaccountNumber}` + +### Parameters + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|address|path|string|true|none| +|parentSubaccountNumber|path|number(double)|true|none| + +> Example responses + +> 200 Response + +```json +{ + "address": "string", + "parentSubaccountNumber": 0, + "equity": "string", + "freeCollateral": "string", + "childSubaccounts": [ + { + "address": "string", + "subaccountNumber": 0, + "equity": "string", + "freeCollateral": "string", + "openPerpetualPositions": { + "property1": { + "market": "string", + "status": "OPEN", + "side": "LONG", + "size": "string", + "maxSize": "string", + "entryPrice": "string", + "realizedPnl": "string", + "createdAt": "string", + "createdAtHeight": "string", + "sumOpen": "string", + "sumClose": "string", + "netFunding": "string", + "unrealizedPnl": "string", + "closedAt": null, + "exitPrice": "string" + }, + "property2": { + "market": "string", + "status": "OPEN", + "side": "LONG", + "size": "string", + "maxSize": "string", + "entryPrice": "string", + "realizedPnl": "string", + "createdAt": "string", + "createdAtHeight": "string", + "sumOpen": "string", + "sumClose": "string", + "netFunding": "string", + "unrealizedPnl": "string", + "closedAt": null, + "exitPrice": "string" + } + }, + "assetPositions": { + "property1": { + "symbol": "string", + "side": "LONG", + "size": "string", + "assetId": "string", + "subaccountNumber": 0 + }, + "property2": { + "symbol": "string", + "side": "LONG", + "size": "string", + "assetId": "string", + "subaccountNumber": 0 + } + }, + "marginEnabled": true + } + ] +} +``` + +### Responses + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[ParentSubaccountResponse](#schemaparentsubaccountresponse)| + + + ## GetAssetPositions @@ -338,6 +469,83 @@ fetch('https://dydx-testnet.imperator.co/v4/assetPositions?address=string&subacc This operation does not require authentication +## GetAssetPositionsForParentSubaccount + + + +> Code samples + +```python +import requests +headers = { + 'Accept': 'application/json' +} + +r = requests.get('https://dydx-testnet.imperator.co/v4/assetPositions/parentSubaccountNumber', params={ + 'address': 'string', 'parentSubaccountNumber': '0' +}, headers = headers) + +print(r.json()) + +``` + +```javascript + +const headers = { + 'Accept':'application/json' +}; + +fetch('https://dydx-testnet.imperator.co/v4/assetPositions/parentSubaccountNumber?address=string&parentSubaccountNumber=0', +{ + method: 'GET', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +`GET /assetPositions/parentSubaccountNumber` + +### Parameters + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|address|query|string|true|none| +|parentSubaccountNumber|query|number(double)|true|none| + +> Example responses + +> 200 Response + +```json +{ + "positions": [ + { + "symbol": "string", + "side": "LONG", + "size": "string", + "assetId": "string", + "subaccountNumber": 0 + } + ] +} +``` + +### Responses + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[AssetPositionResponse](#schemaassetpositionresponse)| + + + ## GetCandles @@ -1956,6 +2164,96 @@ fetch('https://dydx-testnet.imperator.co/v4/transfers?address=string&subaccountN This operation does not require authentication +## GetTransfersForParentSubaccount + + + +> Code samples + +```python +import requests +headers = { + 'Accept': 'application/json' +} + +r = requests.get('https://dydx-testnet.imperator.co/v4/transfers/parentSubaccountNumber', params={ + 'address': 'string', 'parentSubaccountNumber': '0' +}, headers = headers) + +print(r.json()) + +``` + +```javascript + +const headers = { + 'Accept':'application/json' +}; + +fetch('https://dydx-testnet.imperator.co/v4/transfers/parentSubaccountNumber?address=string&parentSubaccountNumber=0', +{ + method: 'GET', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +`GET /transfers/parentSubaccountNumber` + +### Parameters + +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|address|query|string|true|none| +|parentSubaccountNumber|query|number(double)|true|none| +|limit|query|number(double)|false|none| +|createdBeforeOrAtHeight|query|number(double)|false|none| +|createdBeforeOrAt|query|[IsoString](#schemaisostring)|false|none| + +> Example responses + +> 200 Response + +```json +{ + "transfers": [ + { + "id": "string", + "sender": { + "subaccountNumber": 0, + "address": "string" + }, + "recipient": { + "subaccountNumber": 0, + "address": "string" + }, + "size": "string", + "createdAt": "string", + "createdAtHeight": "string", + "symbol": "string", + "type": "TRANSFER_IN", + "transactionHash": "string" + } + ] +} +``` + +### Responses + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[ParentSubaccountTransferResponse](#schemaparentsubaccounttransferresponse)| + + + # Schemas ## PerpetualPositionStatus @@ -2353,6 +2651,94 @@ This operation does not require authentication |subaccounts|[[SubaccountResponseObject](#schemasubaccountresponseobject)]|true|none|none| |totalTradingRewards|string|true|none|none| +## ParentSubaccountResponse + + + + + + +```json +{ + "address": "string", + "parentSubaccountNumber": 0, + "equity": "string", + "freeCollateral": "string", + "childSubaccounts": [ + { + "address": "string", + "subaccountNumber": 0, + "equity": "string", + "freeCollateral": "string", + "openPerpetualPositions": { + "property1": { + "market": "string", + "status": "OPEN", + "side": "LONG", + "size": "string", + "maxSize": "string", + "entryPrice": "string", + "realizedPnl": "string", + "createdAt": "string", + "createdAtHeight": "string", + "sumOpen": "string", + "sumClose": "string", + "netFunding": "string", + "unrealizedPnl": "string", + "closedAt": null, + "exitPrice": "string" + }, + "property2": { + "market": "string", + "status": "OPEN", + "side": "LONG", + "size": "string", + "maxSize": "string", + "entryPrice": "string", + "realizedPnl": "string", + "createdAt": "string", + "createdAtHeight": "string", + "sumOpen": "string", + "sumClose": "string", + "netFunding": "string", + "unrealizedPnl": "string", + "closedAt": null, + "exitPrice": "string" + } + }, + "assetPositions": { + "property1": { + "symbol": "string", + "side": "LONG", + "size": "string", + "assetId": "string", + "subaccountNumber": 0 + }, + "property2": { + "symbol": "string", + "side": "LONG", + "size": "string", + "assetId": "string", + "subaccountNumber": 0 + } + }, + "marginEnabled": true + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|address|string|true|none|none| +|parentSubaccountNumber|number(double)|true|none|none| +|equity|string|true|none|none| +|freeCollateral|string|true|none|none| +|childSubaccounts|[[SubaccountResponseObject](#schemasubaccountresponseobject)]|true|none|none| + ## AssetPositionResponse @@ -3754,3 +4140,41 @@ or |---|---|---|---|---| |transfers|[[TransferResponseObject](#schematransferresponseobject)]|true|none|none| +## ParentSubaccountTransferResponse + + + + + + +```json +{ + "transfers": [ + { + "id": "string", + "sender": { + "subaccountNumber": 0, + "address": "string" + }, + "recipient": { + "subaccountNumber": 0, + "address": "string" + }, + "size": "string", + "createdAt": "string", + "createdAtHeight": "string", + "symbol": "string", + "type": "TRANSFER_IN", + "transactionHash": "string" + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|transfers|[[TransferResponseObject](#schematransferresponseobject)]|true|none|none| + diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index ad36977d51..cf3507be2c 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -195,6 +195,38 @@ "type": "object", "additionalProperties": false }, + "ParentSubaccountResponse": { + "properties": { + "address": { + "type": "string" + }, + "parentSubaccountNumber": { + "type": "number", + "format": "double" + }, + "equity": { + "type": "string" + }, + "freeCollateral": { + "type": "string" + }, + "childSubaccounts": { + "items": { + "$ref": "#/components/schemas/SubaccountResponseObject" + }, + "type": "array" + } + }, + "required": [ + "address", + "parentSubaccountNumber", + "equity", + "freeCollateral", + "childSubaccounts" + ], + "type": "object", + "additionalProperties": false + }, "AssetPositionResponse": { "properties": { "positions": { @@ -1138,6 +1170,21 @@ ], "type": "object", "additionalProperties": false + }, + "ParentSubaccountTransferResponse": { + "properties": { + "transfers": { + "items": { + "$ref": "#/components/schemas/TransferResponseObject" + }, + "type": "array" + } + }, + "required": [ + "transfers" + ], + "type": "object", + "additionalProperties": false } }, "securitySchemes": {} @@ -1213,6 +1260,43 @@ ] } }, + "/addresses/{address}/parentSubaccountNumber/{parentSubaccountNumber}": { + "get": { + "operationId": "GetParentSubaccount", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParentSubaccountResponse" + } + } + } + } + }, + "security": [], + "parameters": [ + { + "in": "path", + "name": "address", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "parentSubaccountNumber", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, "/assetPositions": { "get": { "operationId": "GetAssetPositions", @@ -1250,6 +1334,43 @@ ] } }, + "/assetPositions/parentSubaccountNumber": { + "get": { + "operationId": "GetAssetPositionsForParentSubaccount", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetPositionResponse" + } + } + } + } + }, + "security": [], + "parameters": [ + { + "in": "query", + "name": "address", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "parentSubaccountNumber", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, "/candles/perpetualMarkets/{ticker}": { "get": { "operationId": "GetCandles", @@ -2231,6 +2352,69 @@ } ] } + }, + "/transfers/parentSubaccountNumber": { + "get": { + "operationId": "GetTransfersForParentSubaccount", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ParentSubaccountTransferResponse" + } + } + } + } + }, + "security": [], + "parameters": [ + { + "in": "query", + "name": "address", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "parentSubaccountNumber", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + }, + { + "in": "query", + "name": "limit", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + }, + { + "in": "query", + "name": "createdBeforeOrAtHeight", + "required": false, + "schema": { + "format": "double", + "type": "number" + } + }, + { + "in": "query", + "name": "createdBeforeOrAt", + "required": false, + "schema": { + "$ref": "#/components/schemas/IsoString" + } + } + ] + } } }, "servers": [ diff --git a/indexer/services/comlink/src/controllers/api/v4/addresses-controller.ts b/indexer/services/comlink/src/controllers/api/v4/addresses-controller.ts index 1539fea52c..cd5b1f77e9 100644 --- a/indexer/services/comlink/src/controllers/api/v4/addresses-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/addresses-controller.ts @@ -49,9 +49,10 @@ import { handleControllerError, getPerpetualPositionsWithUpdatedFunding, initializePerpetualPositionsWithFunding, + getChildSubaccountIds, } from '../../../lib/helpers'; import { rateLimiterMiddleware } from '../../../lib/rate-limit'; -import { CheckAddressSchema, CheckSubaccountSchema } from '../../../lib/validation/schemas'; +import { CheckAddressSchema, CheckParentSubaccountSchema, CheckSubaccountSchema } from '../../../lib/validation/schemas'; import { handleValidationErrors } from '../../../request-helpers/error-handler'; import ExportResponseCodeStats from '../../../request-helpers/export-response-code-stats'; import { @@ -69,7 +70,7 @@ import { AssetPositionResponseObject, AssetPositionsMap, PerpetualPositionWithFunding, - AddressResponse, + AddressResponse, ParentSubaccountResponse, ParentSubaccountRequest, } from '../../../types'; const router: express.Router = express.Router(); @@ -241,6 +242,105 @@ class AddressesController extends Controller { ); return subaccountResponse; } + + @Get('/:address/parentSubaccountNumber/:parentSubaccountNumber') + public async getParentSubaccount( + @Path() address: string, + @Path() parentSubaccountNumber: number, + ): Promise { + + const childSubaccountIds: string[] = getChildSubaccountIds(address, parentSubaccountNumber); + + // TODO(IND-189): Use a transaction across all the DB queries + const [subaccounts, latestBlock]: [ + SubaccountFromDatabase[], + BlockFromDatabase, + ] = await Promise.all([ + SubaccountTable.findAll( + { + id: childSubaccountIds, + address, + }, + [], + ), + BlockTable.getLatest(), + ]); + + if (subaccounts.length === 0) { + throw new NotFoundError(`No subaccounts found for address ${address} and parentSubaccountNumber ${parentSubaccountNumber}`); + } + + const latestFundingIndexMap: FundingIndexMap = await FundingIndexUpdatesTable + .findFundingIndexMap( + latestBlock.blockHeight, + ); + + const [assets, markets]: [AssetFromDatabase[], MarketFromDatabase[]] = await Promise.all([ + AssetTable.findAll( + {}, + [], + ), + MarketTable.findAll( + {}, + [], + ), + ]); + const subaccountResponses: SubaccountResponseObject[] = await Promise.all(subaccounts.map( + async (subaccount: SubaccountFromDatabase): Promise => { + const [ + perpetualPositions, + assetPositions, + lastUpdatedFundingIndexMap, + ] = await Promise.all([ + getOpenPerpetualPositionsForSubaccount( + subaccount.id, + ), + getAssetPositionsForSubaccount( + subaccount.id, + ), + FundingIndexUpdatesTable.findFundingIndexMap( + subaccount.updatedAtHeight, + ), + ]); + const unsettledFunding: Big = getTotalUnsettledFunding( + perpetualPositions, + latestFundingIndexMap, + lastUpdatedFundingIndexMap, + ); + + const updatedPerpetualPositions: + PerpetualPositionWithFunding[] = getPerpetualPositionsWithUpdatedFunding( + initializePerpetualPositionsWithFunding(perpetualPositions), + latestFundingIndexMap, + lastUpdatedFundingIndexMap, + ); + + return getSubaccountResponse( + subaccount, + updatedPerpetualPositions, + assetPositions, + assets, + markets, + unsettledFunding, + ); + }, + )); + + return { + address, + parentSubaccountNumber, + equity: subaccountResponses.reduce( + (acc: Big, subaccount: SubaccountResponseObject): Big => acc.plus(subaccount.equity), + Big(0), + ).toString(), + freeCollateral: subaccountResponses.reduce( + // eslint-disable-next-line max-len + (acc: Big, subaccount: SubaccountResponseObject): Big => acc.plus(subaccount.freeCollateral), + Big(0), + ).toString(), + childSubaccounts: subaccountResponses, + }; + } } router.get( @@ -251,6 +351,7 @@ router.get( complianceAndGeoCheck, ExportResponseCodeStats({ controllerName }), async (req: express.Request, res: express.Response) => { + const start: number = Date.now(); const { address, }: { @@ -272,6 +373,11 @@ router.get( req, res, ); + } finally { + stats.timing( + `${config.SERVICE_NAME}.${controllerName}.get_addresses.timing`, + Date.now() - start, + ); } }, ); @@ -313,7 +419,54 @@ router.get( ); } finally { stats.timing( - `${config.SERVICE_NAME}.${controllerName}.get_addresses.timing`, + `${config.SERVICE_NAME}.${controllerName}.get_subaccount.timing`, + Date.now() - start, + ); + } + }, +); + +router.get( + '/:address/parentSubaccountNumber/:parentSubaccountNumber', + rateLimiterMiddleware(getReqRateLimiter), + ...CheckParentSubaccountSchema, + handleValidationErrors, + complianceAndGeoCheck, + ExportResponseCodeStats({ controllerName }), + async (req: express.Request, res: express.Response) => { + const start: number = Date.now(); + const { + address, + parentSubaccountNumber, + }: { + address: string, + parentSubaccountNumber: number, + } = matchedData(req) as ParentSubaccountRequest; + + // The schema checks allow subaccountNumber to be a string, but we know it's a number here. + const parentSubaccountNum = +parentSubaccountNumber; + + try { + const controller: AddressesController = new AddressesController(); + const subaccountResponse: ParentSubaccountResponse = await controller.getParentSubaccount( + address, + parentSubaccountNum, + ); + + return res.send({ + subaccount: subaccountResponse, + }); + } catch (error) { + return handleControllerError( + 'AddressesController GET /:address/parentSubaccountNumber/:parentSubaccountNumber', + 'Addresses subaccount error', + error, + req, + res, + ); + } finally { + stats.timing( + `${config.SERVICE_NAME}.${controllerName}.get_parentSubaccount.timing`, Date.now() - start, ); } @@ -387,6 +540,7 @@ async function getSubaccountResponse( assetPositionResponses, 'symbol', ); + const { assetPositionsMap: adjustedAssetPositionsMap, adjustedUSDCAssetPositionSize, diff --git a/indexer/services/comlink/src/controllers/api/v4/asset-positions-controller.ts b/indexer/services/comlink/src/controllers/api/v4/asset-positions-controller.ts index 7bf0ae96c2..2bc97fa3b8 100644 --- a/indexer/services/comlink/src/controllers/api/v4/asset-positions-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/asset-positions-controller.ts @@ -26,26 +26,29 @@ import { import { getReqRateLimiter } from '../../../caches/rate-limiters'; import config from '../../../config'; import { complianceAndGeoCheck } from '../../../lib/compliance-and-geo-check'; +import { NotFoundError } from '../../../lib/errors'; import { adjustUSDCAssetPosition, filterAssetPositions, + getChildSubaccountIds, getFundingIndexMaps, getTotalUnsettledFunding, handleControllerError, } from '../../../lib/helpers'; import { rateLimiterMiddleware } from '../../../lib/rate-limit'; -import { - CheckSubaccountSchema, -} from '../../../lib/validation/schemas'; +import { CheckParentSubaccountSchema, CheckSubaccountSchema } from '../../../lib/validation/schemas'; import { handleValidationErrors } from '../../../request-helpers/error-handler'; import ExportResponseCodeStats from '../../../request-helpers/export-response-code-stats'; -import { assetPositionToResponseObject } from '../../../request-helpers/request-transformer'; +import { + assetPositionToResponseObject, +} from '../../../request-helpers/request-transformer'; import { AssetById, AssetPositionRequest, AssetPositionResponse, AssetPositionResponseObject, AssetPositionsMap, + ParentSubaccountAssetPositionRequest, } from '../../../types'; const router: express.Router = express.Router(); @@ -98,6 +101,12 @@ class AssetPositionsController extends Controller { BlockTable.getLatest(), ]); + if (subaccount === undefined) { + throw new NotFoundError( + `No subaccount found with address ${address} and subaccountNumber ${subaccountNumber}`, + ); + } + const sortedAssetPositions: AssetPositionFromDatabase[] = filterAssetPositions(assetPositions); @@ -106,47 +115,167 @@ class AssetPositionsController extends Controller { AssetColumns.id, ); - let assetPositionsMap: AssetPositionsMap = _.chain(sortedAssetPositions) - .map( - (position: AssetPositionFromDatabase) => assetPositionToResponseObject(position, - idToAsset, - subaccountNumber), - ).keyBy( - (positionResponse: AssetPositionResponseObject) => positionResponse.symbol, - ).value(); - - // If a subaccount, latest block, and perpetual positions exist, calculate the unsettled funding - // for positions and adjust the returned USDC position - if (subaccount !== undefined && perpetualPositions.length > 0) { - const { - lastUpdatedFundingIndexMap, - latestFundingIndexMap, - }: { - lastUpdatedFundingIndexMap: FundingIndexMap, - latestFundingIndexMap: FundingIndexMap, - } = await getFundingIndexMaps(subaccount, latestBlock); - const unsettledFunding: Big = getTotalUnsettledFunding( - perpetualPositions, - latestFundingIndexMap, - lastUpdatedFundingIndexMap, + const assetPositionsMap: AssetPositionsMap = await adjustAssetPositionsWithFunding( + sortedAssetPositions, + perpetualPositions, + idToAsset, + subaccount, + latestBlock, + ); + + return { + positions: Object.values(assetPositionsMap), + }; + } + + @Get('/parentSubaccountNumber') + async getAssetPositionsForParentSubaccount( + @Query() address: string, + @Query() parentSubaccountNumber: number, + ): Promise { + + const childSubaccountUuids: string[] = getChildSubaccountIds(address, parentSubaccountNumber); + + // TODO(IND-189): Use a transaction across all the DB queries + const [ + subaccounts, + assetPositions, + perpetualPositions, + // TODO(DEC-656): Change to a cache in Redis or local instead of querying DB. + assets, + latestBlock, + ]: [ + SubaccountFromDatabase[], + AssetPositionFromDatabase[], + PerpetualPositionFromDatabase[], + AssetFromDatabase[], + BlockFromDatabase, + ] = await Promise.all([ + SubaccountTable.findAll( + { + id: childSubaccountUuids, + }, + [QueryableField.ID], + ), + AssetPositionTable.findAll( + { + subaccountId: childSubaccountUuids, + }, + [QueryableField.SUBACCOUNT_ID], + ), + PerpetualPositionTable.findAll( + { + subaccountId: childSubaccountUuids, + status: [PerpetualPositionStatus.OPEN], + }, + [QueryableField.SUBACCOUNT_ID], + ), + AssetTable.findAll( + {}, + [], + ), + BlockTable.getLatest(), + ]); + + const sortedAssetPositions: + AssetPositionFromDatabase[] = filterAssetPositions(assetPositions); + + const idToAsset: AssetById = _.keyBy( + assets, + AssetColumns.id, + ); + + const assetPositionsBySubaccount: + { [subaccountId: string]: AssetPositionFromDatabase[] } = _.groupBy( + sortedAssetPositions, + 'subaccountId', + ); + + const perpetualPositionsBySubaccount: + { [subaccountId: string]: PerpetualPositionFromDatabase[] } = _.groupBy( + perpetualPositions, + 'subaccountId', + ); + + // For each subaccount, adjust the asset positions with the unsettled funding and return the + // asset positions per subaccount + const assetPositionsPromises = subaccounts.map(async (subaccount) => { + const adjustedAssetPositionsMap: AssetPositionsMap = await adjustAssetPositionsWithFunding( + assetPositionsBySubaccount[subaccount.id] || [], + perpetualPositionsBySubaccount[subaccount.id] || [], + idToAsset, + subaccount, + latestBlock, ); + return Object.values(adjustedAssetPositionsMap); + }); - // Adjust the USDC asset position - const { - assetPositionsMap: adjustedAssetPositionsMap, - }: { - assetPositionsMap: AssetPositionsMap, - adjustedUSDCAssetPositionSize: string, - } = adjustUSDCAssetPosition(assetPositionsMap, unsettledFunding); - assetPositionsMap = adjustedAssetPositionsMap; - } + const assetPositionsResponse: AssetPositionResponseObject[] = ( + await Promise.all(assetPositionsPromises) + ).flat(); return { - positions: Object.values(assetPositionsMap), + positions: assetPositionsResponse, }; } } +/** + * Helper function to adjust the asset positions with the unsettled funding + * per subaccount + * @param assetPositions pulled from DB + * @param perpetualPositions pulled from DB + * @param idToAsset mapping of assetId to asset + * @param subaccount subaccount for which the asset positions are being adjusted + * @param latestBlock + * @returns AssetPositionsMap + */ +async function adjustAssetPositionsWithFunding( + assetPositions: AssetPositionFromDatabase[], + perpetualPositions: PerpetualPositionFromDatabase[], + idToAsset: AssetById, + subaccount: SubaccountFromDatabase, + latestBlock: BlockFromDatabase, +): Promise { + let assetPositionsMap: AssetPositionsMap = _.chain(assetPositions) + .map( + (position: AssetPositionFromDatabase) => assetPositionToResponseObject( + position, + idToAsset, + subaccount.subaccountNumber), + ).keyBy( + (positionResponse: AssetPositionResponseObject) => positionResponse.symbol, + ).value(); + + // If the latest block, and perpetual positions exist, calculate the unsettled funding + // for positions and adjust the returned USDC position + if (perpetualPositions.length > 0) { + const { + lastUpdatedFundingIndexMap, + latestFundingIndexMap, + }: { + lastUpdatedFundingIndexMap: FundingIndexMap, + latestFundingIndexMap: FundingIndexMap, + } = await getFundingIndexMaps(subaccount, latestBlock); + const unsettledFunding: Big = getTotalUnsettledFunding( + perpetualPositions, + latestFundingIndexMap, + lastUpdatedFundingIndexMap, + ); + + // Adjust the USDC asset position + const { + assetPositionsMap: adjustedAssetPositionsMap, + }: { + assetPositionsMap: AssetPositionsMap, + adjustedUSDCAssetPositionSize: string, + } = adjustUSDCAssetPosition(assetPositionsMap, unsettledFunding); + assetPositionsMap = adjustedAssetPositionsMap; + } + + return assetPositionsMap; +} + router.get( '/', rateLimiterMiddleware(getReqRateLimiter), @@ -189,4 +318,48 @@ router.get( }, ); +router.get( + '/parentSubaccountNumber', + rateLimiterMiddleware(getReqRateLimiter), + ...CheckParentSubaccountSchema, + handleValidationErrors, + complianceAndGeoCheck, + ExportResponseCodeStats({ controllerName }), + async (req: express.Request, res: express.Response) => { + const start: number = Date.now(); + const { + address, + parentSubaccountNumber, + }: ParentSubaccountAssetPositionRequest = matchedData( + req, + ) as ParentSubaccountAssetPositionRequest; + + // The schema checks allow subaccountNumber to be a string, but we know it's a number here. + const parentSubaccountNum: number = +parentSubaccountNumber; + + try { + const controller: AssetPositionsController = new AssetPositionsController(); + const response: AssetPositionResponse = await controller.getAssetPositionsForParentSubaccount( + address, + parentSubaccountNum, + ); + + return res.send(response); + } catch (error) { + return handleControllerError( + 'AssetPositionsController GET /parentSubaccountNumber', + 'Asset positions error', + error, + req, + res, + ); + } finally { + stats.timing( + `${config.SERVICE_NAME}.${controllerName}.get_asset_positions_parent_subaccount.timing`, + Date.now() - start, + ); + } + }, +); + export default router; diff --git a/indexer/services/comlink/src/controllers/api/v4/fills-controller.ts b/indexer/services/comlink/src/controllers/api/v4/fills-controller.ts index aff2ed3f51..f9ae639b92 100644 --- a/indexer/services/comlink/src/controllers/api/v4/fills-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/fills-controller.ts @@ -8,7 +8,6 @@ import { FillFromDatabase, QueryableField, } from '@dydxprotocol-indexer/postgres'; -import { getChildSubaccountNums } from '@dydxprotocol-indexer/postgres/build/src/lib/api-translations'; import express from 'express'; import { checkSchema, @@ -25,6 +24,7 @@ import config from '../../../config'; import { complianceAndGeoCheck } from '../../../lib/compliance-and-geo-check'; import { NotFoundError } from '../../../lib/errors'; import { + getChildSubaccountNums, getClobPairId, handleControllerError, isDefined, } from '../../../lib/helpers'; import { rateLimiterMiddleware } from '../../../lib/rate-limit'; diff --git a/indexer/services/comlink/src/controllers/api/v4/transfers-controller.ts b/indexer/services/comlink/src/controllers/api/v4/transfers-controller.ts index ecb412b16c..1ba50af669 100644 --- a/indexer/services/comlink/src/controllers/api/v4/transfers-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/transfers-controller.ts @@ -25,17 +25,27 @@ import { getReqRateLimiter } from '../../../caches/rate-limiters'; import config from '../../../config'; import { complianceAndGeoCheck } from '../../../lib/compliance-and-geo-check'; import { NotFoundError } from '../../../lib/errors'; -import { handleControllerError } from '../../../lib/helpers'; +import { getChildSubaccountNums, handleControllerError } from '../../../lib/helpers'; import { rateLimiterMiddleware } from '../../../lib/rate-limit'; -import { CheckLimitAndCreatedBeforeOrAtSchema, CheckSubaccountSchema } from '../../../lib/validation/schemas'; +import { + CheckLimitAndCreatedBeforeOrAtSchema, + CheckParentSubaccountSchema, + CheckSubaccountSchema, +} from '../../../lib/validation/schemas'; import { handleValidationErrors } from '../../../request-helpers/error-handler'; import ExportResponseCodeStats from '../../../request-helpers/export-response-code-stats'; -import { transferToResponseObject } from '../../../request-helpers/request-transformer'; +import { + transferToParentSubaccountResponseObject, + transferToResponseObject, +} from '../../../request-helpers/request-transformer'; import { AssetById, SubaccountById, TransferRequest, TransferResponse, + ParentSubaccountTransferRequest, + ParentSubaccountTransferResponse, + ParentSubaccountTransferResponseObject, } from '../../../types'; const router: express.Router = express.Router(); @@ -54,7 +64,7 @@ class TransfersController extends Controller { const subaccountId: string = SubaccountTable.uuid(address, subaccountNumber); // TODO(DEC-656): Change to a cache in Redis similar to Librarian instead of querying DB. - const [subaccount, transfers, assets] : [ + const [subaccount, transfers, assets]: [ SubaccountFromDatabase | undefined, TransferFromDatabase[], AssetFromDatabase[] @@ -125,6 +135,106 @@ class TransfersController extends Controller { }), }; } + + @Get('/parentSubaccountNumber') + async getTransfersForParentSubaccount( + @Query() address: string, + @Query() parentSubaccountNumber: number, + @Query() limit?: number, + @Query() createdBeforeOrAtHeight?: number, + @Query() createdBeforeOrAt?: IsoString, + ): Promise { + + // get all child subaccountIds for the parent subaccount number + const subaccountIds: string[] = getChildSubaccountNums(parentSubaccountNumber).map( + (childSubaccountNumber: number) => SubaccountTable.uuid(address, childSubaccountNumber), + ); + + // TODO(DEC-656): Change to a cache in Redis similar to Librarian instead of querying DB. + const [subaccounts, transfers, assets]: [ + SubaccountFromDatabase[] | undefined, + TransferFromDatabase[], + AssetFromDatabase[] + ] = await + Promise.all([ + SubaccountTable.findAll( + { id: subaccountIds }, + [], + ), + TransferTable.findAllToOrFromSubaccountId( + { + subaccountId: subaccountIds, + limit, + createdBeforeOrAtHeight: createdBeforeOrAtHeight + ? createdBeforeOrAtHeight.toString() + : undefined, + createdBeforeOrAt, + }, + [QueryableField.LIMIT], + { + ...DEFAULT_POSTGRES_OPTIONS, + orderBy: [[TransferColumns.createdAtHeight, Ordering.DESC]], + }, + ), + AssetTable.findAll( + {}, + [], + ), + ]); + if (subaccounts === undefined || subaccounts.length === 0) { + throw new NotFoundError( + `No subaccount found with address ${address} and parentSubaccountNumber ${parentSubaccountNumber}`, + ); + } + const recipientSubaccountIds: string[] = _ + .map(transfers, TransferColumns.recipientSubaccountId) + .filter( + (recipientSubaccountId: string | undefined) => recipientSubaccountId !== undefined, + ) as string[]; + const senderSubaccountIds: string[] = _ + .map(transfers, TransferColumns.senderSubaccountId) + .filter( + (senderSubaccountId: string | undefined) => senderSubaccountId !== undefined, + ) as string[]; + + const allSubaccountIds: string[] = _.uniq([ + ...recipientSubaccountIds, + ...senderSubaccountIds, + ]); + const allSubaccounts: SubaccountFromDatabase[] = await SubaccountTable.findAll( + { + id: allSubaccountIds, + }, + [], + ); + const idToSubaccount: SubaccountById = _.keyBy( + allSubaccounts, + SubaccountColumns.id, + ); + + const idToAsset: AssetById = _.keyBy( + assets, + AssetColumns.id, + ); + + const transfersWithParentSubaccount: ParentSubaccountTransferResponseObject[] = transfers.map( + (transfer: TransferFromDatabase) => { + return transferToParentSubaccountResponseObject( + transfer, + idToAsset, + idToSubaccount, + parentSubaccountNumber); + }); + + // Filter out transfers where the sender and recipient parent subaccount numbers are the same + const transfersFiltered: + ParentSubaccountTransferResponseObject[] = transfersWithParentSubaccount.filter( + (transfer) => { + return transfer.sender.parentSubaccountNumber !== transfer.recipient.parentSubaccountNumber; + }); + + return { transfers: transfersFiltered }; + } } router.get( @@ -173,4 +283,53 @@ router.get( }, ); +router.get( + '/parentSubaccountNumber', + rateLimiterMiddleware(getReqRateLimiter), + ...CheckParentSubaccountSchema, + ...CheckLimitAndCreatedBeforeOrAtSchema, + handleValidationErrors, + complianceAndGeoCheck, + ExportResponseCodeStats({ controllerName }), + async (req: express.Request, res: express.Response) => { + const start: number = Date.now(); + const { + address, + parentSubaccountNumber, + limit, + createdBeforeOrAtHeight, + createdBeforeOrAt, + }: ParentSubaccountTransferRequest = matchedData(req) as ParentSubaccountTransferRequest; + + // The schema checks allow subaccountNumber to be a string, but we know it's a number here. + const parentSubaccountNum: number = +parentSubaccountNumber; + + try { + const controllers: TransfersController = new TransfersController(); + const response: TransferResponse = await controllers.getTransfersForParentSubaccount( + address, + parentSubaccountNum, + limit, + createdBeforeOrAtHeight, + createdBeforeOrAt, + ); + + return res.send(response); + } catch (error) { + return handleControllerError( + 'TransfersController GET /', + 'Transfers error', + error, + req, + res, + ); + } finally { + stats.timing( + `${config.SERVICE_NAME}.${controllerName}.get_transfers.timing`, + Date.now() - start, + ); + } + }, +); + export default router; diff --git a/indexer/services/comlink/src/lib/helpers.ts b/indexer/services/comlink/src/lib/helpers.ts index cf260aaf97..eab74bf228 100644 --- a/indexer/services/comlink/src/lib/helpers.ts +++ b/indexer/services/comlink/src/lib/helpers.ts @@ -2,6 +2,7 @@ import { logger } from '@dydxprotocol-indexer/base'; import { AssetPositionFromDatabase, BlockFromDatabase, + CHILD_SUBACCOUNT_MULTIPLIER, FundingIndexMap, FundingIndexUpdatesTable, helpers, @@ -9,6 +10,7 @@ import { LiquidityTiersFromDatabase, MarketFromDatabase, MarketsMap, + MAX_PARENT_SUBACCOUNTS, PerpetualMarketFromDatabase, PerpetualMarketsMap, PerpetualMarketTable, @@ -16,6 +18,7 @@ import { PerpetualPositionStatus, PositionSide, SubaccountFromDatabase, + SubaccountTable, TendermintEventFromDatabase, TendermintEventTable, USDC_SYMBOL, @@ -434,7 +437,8 @@ export function adjustUSDCAssetPosition( _.set( adjustedAssetPositionsMap, USDC_SYMBOL, - getUSDCAssetPosition(adjustedSize), + getUSDCAssetPosition(adjustedSize, + adjustedAssetPositionsMap[USDC_SYMBOL]?.subaccountNumber ?? 0), ); // Remove the USDC position in the map if the adjusted size is zero } else { @@ -447,12 +451,14 @@ export function adjustUSDCAssetPosition( }; } -function getUSDCAssetPosition(signedSize: Big): AssetPositionResponseObject { +function getUSDCAssetPosition(signedSize: Big, subaccountNumber: number): + AssetPositionResponseObject { const side: PositionSide = signedSize.gt(ZERO) ? PositionSide.LONG : PositionSide.SHORT; return { ...ZERO_USDC_POSITION, side, size: signedSize.abs().toFixed(), + subaccountNumber, }; } @@ -492,3 +498,43 @@ export function initializePerpetualPositionsWithFunding( }; }); } + +/** + * Gets a list of all possible child subaccount numbers for a parent subaccount number + * Child subaccounts = [128*0+parentSubaccount, 128*1+parentSubaccount ... 128*999+parentSubaccount] + * @param parentSubaccount + * @returns + */ +export function getChildSubaccountNums(parentSubaccountNum: number): number[] { + if (parentSubaccountNum >= MAX_PARENT_SUBACCOUNTS) { + throw new NotFoundError(`Parent subaccount number must be less than ${MAX_PARENT_SUBACCOUNTS}`); + } + return Array.from({ length: CHILD_SUBACCOUNT_MULTIPLIER }, + // eslint-disable-next-line @typescript-eslint/no-shadow + (_, i) => MAX_PARENT_SUBACCOUNTS * i + parentSubaccountNum); +} + +/** + * Gets the subaccount uuids of all the child subaccounts given a parent subaccount number + * @param address + * @param parentSubaccountNum + * @returns + */ +export function getChildSubaccountIds(address: string, parentSubaccountNum: number): string[] { + return getChildSubaccountNums(parentSubaccountNum).map( + (subaccountNumber: number): string => SubaccountTable.uuid(address, subaccountNumber), + ); +} + +/** + * Gets the parent subaccount number from a child subaccount number + * Parent subaccount = childSubaccount % 128 + * @param childSubaccountNum + * @returns + */ +export function getParentSubaccountNum(childSubaccountNum: number): number { + if (childSubaccountNum > MAX_PARENT_SUBACCOUNTS * CHILD_SUBACCOUNT_MULTIPLIER) { + throw new Error(`Child subaccount number must be less than ${MAX_PARENT_SUBACCOUNTS * CHILD_SUBACCOUNT_MULTIPLIER}`); + } + return childSubaccountNum % MAX_PARENT_SUBACCOUNTS; +} diff --git a/indexer/services/comlink/src/request-helpers/request-transformer.ts b/indexer/services/comlink/src/request-helpers/request-transformer.ts index b516e900f9..3eea8dc703 100644 --- a/indexer/services/comlink/src/request-helpers/request-transformer.ts +++ b/indexer/services/comlink/src/request-helpers/request-transformer.ts @@ -25,12 +25,14 @@ import { TradingRewardAggregationFromDatabase, TradingRewardFromDatabase, TransferFromDatabase, + TransferType, } from '@dydxprotocol-indexer/postgres'; import { OrderbookLevels, PriceLevel } from '@dydxprotocol-indexer/redis'; import { RedisOrder } from '@dydxprotocol-indexer/v4-protos'; import Big from 'big.js'; import _ from 'lodash'; +import { getParentSubaccountNum } from '../lib/helpers'; import { AssetById, AssetPositionResponseObject, @@ -43,7 +45,7 @@ import { MarketAndTypeByClobPairId, OrderbookResponseObject, OrderbookResponsePriceLevel, - OrderResponseObject, + OrderResponseObject, ParentSubaccountTransferResponseObject, PerpetualMarketResponseObject, PerpetualPositionResponseObject, PerpetualPositionsMap, @@ -231,6 +233,59 @@ export function transferToResponseObject( }; } +export function transferToParentSubaccountResponseObject( + transfer: TransferFromDatabase, + assetMap: AssetById, + subaccountMap: SubaccountById, + parentSubaccountNumber: number, +): ParentSubaccountTransferResponseObject { + + const senderParentSubaccountNum = transfer.senderWalletAddress + ? undefined + : getParentSubaccountNum(subaccountMap[transfer.senderSubaccountId!].subaccountNumber, + ); + + const recipientParentSubaccountNum = transfer.recipientWalletAddress + ? undefined + : getParentSubaccountNum(subaccountMap[transfer.recipientSubaccountId!].subaccountNumber); + + // Determine transfer type based on parent subaccount number. + let transferType: TransferType = TransferType.TRANSFER_IN; + if (senderParentSubaccountNum === parentSubaccountNumber) { + if (transfer.recipientSubaccountId) { + transferType = TransferType.TRANSFER_OUT; + } else { + transferType = TransferType.WITHDRAWAL; + } + } else if (recipientParentSubaccountNum === parentSubaccountNumber) { + if (transfer.senderSubaccountId) { + transferType = TransferType.TRANSFER_IN; + } else { + transferType = TransferType.DEPOSIT; + } + } + + return { + id: transfer.id, + sender: { + address: transfer.senderWalletAddress ?? subaccountMap[transfer.senderSubaccountId!].address, + parentSubaccountNumber: senderParentSubaccountNum, + }, + recipient: { + address: transfer.recipientWalletAddress ?? subaccountMap[ + transfer.recipientSubaccountId! + ].address, + parentSubaccountNumber: recipientParentSubaccountNum, + }, + size: transfer.size, + createdAt: transfer.createdAt, + createdAtHeight: transfer.createdAtHeight, + symbol: assetMap[transfer.assetId].symbol, + type: transferType, + transactionHash: transfer.transactionHash, + }; +} + export function pnlTicksToResponseObject( pnlTicks: PnlTicksFromDatabase, ): PnlTicksResponseObject { diff --git a/indexer/services/comlink/src/types.ts b/indexer/services/comlink/src/types.ts index 3a1ba4402e..83afcd18a7 100644 --- a/indexer/services/comlink/src/types.ts +++ b/indexer/services/comlink/src/types.ts @@ -57,6 +57,14 @@ export interface SubaccountResponseObject { marginEnabled: boolean, } +export interface ParentSubaccountResponse { + address: string; + parentSubaccountNumber: number; + equity: string; // aggregated over all child subaccounts + freeCollateral: string; // aggregated over all child subaccounts + childSubaccounts: SubaccountResponseObject[]; +} + export type SubaccountById = {[id: string]: SubaccountFromDatabase}; /* ------- TIME TYPES ------- */ @@ -157,6 +165,28 @@ export interface TransferResponseObject { transactionHash: string, } +export interface ParentSubaccountTransferResponse { + transfers: TransferResponseObject[], +} + +export interface ParentSubaccountTransferResponseObject { + id: string, + sender: { + address: string, + parentSubaccountNumber?: number, + }, + recipient: { + address: string, + parentSubaccountNumber?: number, + }, + size: string, + createdAt: string, + createdAtHeight: string, + symbol: string, + type: TransferType, + transactionHash: string, +} + /* ------- PNL TICKS TYPES ------- */ export interface HistoricalPnlResponse { @@ -343,8 +373,15 @@ export interface PerpetualPositionRequest extends SubaccountRequest, LimitAndCre export interface AssetPositionRequest extends SubaccountRequest {} +export interface ParentSubaccountAssetPositionRequest extends ParentSubaccountRequest { +} + export interface TransferRequest extends SubaccountRequest, LimitAndCreatedBeforeRequest {} +export interface ParentSubaccountTransferRequest + extends ParentSubaccountRequest, LimitAndCreatedBeforeRequest { +} + export interface FillRequest extends SubaccountRequest, LimitAndCreatedBeforeRequest { market: string, marketType: MarketType, diff --git a/indexer/services/ender/__tests__/handlers/stateful-order/stateful-order-placement-handler.test.ts b/indexer/services/ender/__tests__/handlers/stateful-order/stateful-order-placement-handler.test.ts index 52e0056dc4..c9cbf01a97 100644 --- a/indexer/services/ender/__tests__/handlers/stateful-order/stateful-order-placement-handler.test.ts +++ b/indexer/services/ender/__tests__/handlers/stateful-order/stateful-order-placement-handler.test.ts @@ -36,6 +36,7 @@ import { updateBlockCache } from '../../../src/caches/block-cache'; import { createIndexerTendermintBlock, createIndexerTendermintEvent, + expectOrderSubaccountKafkaMessage, expectVulcanKafkaMessage, } from '../../helpers/indexer-proto-helpers'; import { StatefulOrderPlacementHandler } from '../../../src/handlers/stateful-order/stateful-order-placement-handler'; @@ -44,6 +45,7 @@ import { STATEFUL_ORDER_ORDER_FILL_EVENT_TYPE } from '../../../src/constants'; import { producer } from '@dydxprotocol-indexer/kafka'; import { ORDER_FLAG_LONG_TERM } from '@dydxprotocol-indexer/v4-proto-parser'; import { createPostgresFunctions } from '../../../src/helpers/postgres/postgres-functions'; +import config from '../../../src/config'; describe('statefulOrderPlacementHandler', () => { beforeAll(async () => { @@ -61,6 +63,7 @@ describe('statefulOrderPlacementHandler', () => { afterEach(async () => { await dbHelpers.clearData(); jest.clearAllMocks(); + config.SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS = false; }); afterAll(async () => { @@ -135,12 +138,16 @@ describe('statefulOrderPlacementHandler', () => { it.each([ // TODO(IND-334): Remove after deprecating StatefulOrderPlacementEvent - ['stateful order placement', defaultStatefulOrderEvent], - ['stateful long term order placement', defaultStatefulOrderLongTermEvent], - ])('successfully places order with %s', async ( + ['stateful order placement', defaultStatefulOrderEvent, false], + ['stateful long term order placement', defaultStatefulOrderLongTermEvent, false], + ['stateful order placement', defaultStatefulOrderEvent, true], + ['stateful long term order placement', defaultStatefulOrderLongTermEvent, true], + ])('successfully places order with %s (emit subaccount websocket msg: %s)', async ( _name: string, statefulOrderEvent: StatefulOrderEventV1, + emitSubaccountMessage: boolean, ) => { + config.SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS = emitSubaccountMessage; const kafkaMessage: KafkaMessage = createKafkaMessageFromStatefulOrderEvent( statefulOrderEvent, ); @@ -182,6 +189,13 @@ describe('statefulOrderPlacementHandler', () => { offchainUpdate: expectedOffchainUpdate, headers: { message_received_timestamp: kafkaMessage.timestamp, event_type: 'StatefulOrderPlacement' }, }); + if (emitSubaccountMessage) { + expectOrderSubaccountKafkaMessage( + producerSendMock, + defaultOrder.orderId!.subaccountId!, + order!, + ); + } }); it.each([ diff --git a/indexer/services/ender/__tests__/helpers/constants.ts b/indexer/services/ender/__tests__/helpers/constants.ts index e058d9dcec..54b77f54c9 100644 --- a/indexer/services/ender/__tests__/helpers/constants.ts +++ b/indexer/services/ender/__tests__/helpers/constants.ts @@ -148,6 +148,8 @@ export const defaultLiquidityTierUpsertEvent: LiquidityTierUpsertEventV1 = { initialMarginPpm: 50000, // 5% maintenanceFractionPpm: 600000, // 60% of IM = 3% basePositionNotional: Long.fromValue(1_000_000_000_000, true), // 1_000_000 USDC + openInterestLowerCap: Long.fromValue(0, true), + openInterestUpperCap: Long.fromValue(1_000_000_000, true), }; export const defaultUpdatePerpetualEvent: UpdatePerpetualEventV1 = { diff --git a/indexer/services/ender/__tests__/helpers/indexer-proto-helpers.ts b/indexer/services/ender/__tests__/helpers/indexer-proto-helpers.ts index a210b936a5..43e34282f9 100644 --- a/indexer/services/ender/__tests__/helpers/indexer-proto-helpers.ts +++ b/indexer/services/ender/__tests__/helpers/indexer-proto-helpers.ts @@ -29,9 +29,9 @@ import { PerpetualMarketFromDatabase, PerpetualMarketTable, IsoString, - fillTypeToTradeType, + fillTypeToTradeType, OrderSubaccountMessageContents, } from '@dydxprotocol-indexer/postgres'; -import { getOrderIdHash } from '@dydxprotocol-indexer/v4-proto-parser'; +import { getOrderIdHash, ORDER_FLAG_CONDITIONAL } from '@dydxprotocol-indexer/v4-proto-parser'; import { LiquidationOrderV1, MarketMessage, @@ -693,6 +693,10 @@ export async function expectFillSubaccountKafkaMessageFromLiquidationEvent( }); } +function isConditionalOrder(order: OrderFromDatabase): boolean { + return Number(order.orderFlags) === ORDER_FLAG_CONDITIONAL; +} + export function expectOrderSubaccountKafkaMessage( producerSendMock: jest.SpyInstance, subaccountIdProto: IndexerSubaccountId, @@ -702,16 +706,34 @@ export function expectOrderSubaccountKafkaMessage( eventIndex: number = 0, ticker: string = defaultPerpetualMarketTicker, ): void { + const { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + triggerPrice, totalFilled, goodTilBlock, ...orderWithoutUnwantedFields + } = order!; + let orderObject: OrderSubaccountMessageContents; + + if (isConditionalOrder(order)) { + orderObject = { + ...order!, + timeInForce: apiTranslations.orderTIFToAPITIF(order!.timeInForce), + postOnly: apiTranslations.isOrderTIFPostOnly(order!.timeInForce), + goodTilBlock: order!.goodTilBlock, + goodTilBlockTime: order!.goodTilBlockTime, + ticker, + }; + } else { + orderObject = { + ...orderWithoutUnwantedFields!, + timeInForce: apiTranslations.orderTIFToAPITIF(order!.timeInForce), + postOnly: apiTranslations.isOrderTIFPostOnly(order!.timeInForce), + goodTilBlockTime: order!.goodTilBlockTime, + ticker, + }; + } + const contents: SubaccountMessageContents = { orders: [ - { - ...order!, - timeInForce: apiTranslations.orderTIFToAPITIF(order!.timeInForce), - postOnly: apiTranslations.isOrderTIFPostOnly(order!.timeInForce), - goodTilBlock: order!.goodTilBlock, - goodTilBlockTime: order!.goodTilBlockTime, - ticker, - }, + orderObject, ], }; diff --git a/indexer/services/ender/src/config.ts b/indexer/services/ender/src/config.ts index c22122b318..6f79e75fb4 100644 --- a/indexer/services/ender/src/config.ts +++ b/indexer/services/ender/src/config.ts @@ -23,6 +23,9 @@ export const configSchema = { SEND_WEBSOCKET_MESSAGES: parseBoolean({ default: true, }), + SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS: parseBoolean({ + default: false, + }), }; export default parseSchema(configSchema); diff --git a/indexer/services/ender/src/handlers/stateful-order/stateful-order-placement-handler.ts b/indexer/services/ender/src/handlers/stateful-order/stateful-order-placement-handler.ts index 31a5104123..e8ccb4e7ef 100644 --- a/indexer/services/ender/src/handlers/stateful-order/stateful-order-placement-handler.ts +++ b/indexer/services/ender/src/handlers/stateful-order/stateful-order-placement-handler.ts @@ -1,25 +1,34 @@ +import { generateSubaccountMessageContents } from '@dydxprotocol-indexer/kafka'; import { + OrderFromDatabase, OrderModel, OrderTable, + PerpetualMarketFromDatabase, + perpetualMarketRefresher, + SubaccountMessageContents, } from '@dydxprotocol-indexer/postgres'; +import { convertToRedisOrder } from '@dydxprotocol-indexer/redis'; import { getOrderIdHash } from '@dydxprotocol-indexer/v4-proto-parser'; import { - OrderPlaceV1_OrderPlacementStatus, - OffChainUpdateV1, IndexerOrder, + IndexerSubaccountId, + OffChainUpdateV1, + OrderPlaceV1_OrderPlacementStatus, + RedisOrder, StatefulOrderEventV1, + SubaccountId, } from '@dydxprotocol-indexer/v4-protos'; import * as pg from 'pg'; +import config from '../../config'; import { ConsolidatedKafkaEvent } from '../../lib/types'; import { AbstractStatefulOrderHandler } from '../abstract-stateful-order-handler'; // TODO(IND-334): Rename to LongTermOrderPlacementHandler after deprecating StatefulOrderPlacement -export class StatefulOrderPlacementHandler extends - AbstractStatefulOrderHandler { +export class StatefulOrderPlacementHandler + extends AbstractStatefulOrderHandler { eventType: string = 'StatefulOrderEvent'; - public getParallelizationIds(): string[] { - // Stateful Order Events with the same orderId + public getOrderId(): string { let orderId: string; // TODO(IND-334): Remove after deprecating StatefulOrderPlacementEvent if (this.event.orderPlace !== undefined) { @@ -27,11 +36,27 @@ export class StatefulOrderPlacementHandler extends } else { orderId = OrderTable.orderIdToUuid(this.event.longTermOrderPlacement!.order!.orderId!); } - return this.getParallelizationIdsFromOrderId(orderId); + return orderId; + } + + public getSubaccountId(): IndexerSubaccountId { + let subaccountId: IndexerSubaccountId; + // TODO(IND-334): Remove after deprecating StatefulOrderPlacementEvent + if (this.event.orderPlace !== undefined) { + subaccountId = this.event.orderPlace!.order!.orderId!.subaccountId!; + } else { + subaccountId = this.event.longTermOrderPlacement!.order!.orderId!.subaccountId!; + } + return subaccountId; + } + + public getParallelizationIds(): string[] { + // Stateful Order Events with the same orderId + return this.getParallelizationIdsFromOrderId(this.getOrderId()); } // eslint-disable-next-line @typescript-eslint/require-await - public async internalHandle(_: pg.QueryResultRow): Promise { + public async internalHandle(resultRow: pg.QueryResultRow): Promise { let order: IndexerOrder; // TODO(IND-334): Remove after deprecating StatefulOrderPlacementEvent if (this.event.orderPlace !== undefined) { @@ -39,11 +64,14 @@ export class StatefulOrderPlacementHandler extends } else { order = this.event.longTermOrderPlacement!.order!; } - return this.createKafkaEvents(order); + return this.createKafkaEvents(order, resultRow); } - private createKafkaEvents(order: IndexerOrder): ConsolidatedKafkaEvent[] { - const kafakEvents: ConsolidatedKafkaEvent[] = []; + private createKafkaEvents( + order: IndexerOrder, + resultRow: pg.QueryResultRow, + ): ConsolidatedKafkaEvent[] { + const kafkaEvents: ConsolidatedKafkaEvent[] = []; const offChainUpdate: OffChainUpdateV1 = OffChainUpdateV1.fromPartial({ orderPlace: { @@ -51,7 +79,7 @@ export class StatefulOrderPlacementHandler extends placementStatus: OrderPlaceV1_OrderPlacementStatus.ORDER_PLACEMENT_STATUS_OPENED, }, }); - kafakEvents.push(this.generateConsolidatedVulcanKafkaEvent( + kafkaEvents.push(this.generateConsolidatedVulcanKafkaEvent( getOrderIdHash(order.orderId!), offChainUpdate, { @@ -60,6 +88,30 @@ export class StatefulOrderPlacementHandler extends }, )); - return kafakEvents; + if (config.SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS) { + const perpetualMarket: PerpetualMarketFromDatabase = perpetualMarketRefresher + .getPerpetualMarketFromClobPairId(order.orderId!.clobPairId.toString())!; + const dbOrder: OrderFromDatabase = OrderModel.fromJson(resultRow.order) as OrderFromDatabase; + const redisOrder: RedisOrder = convertToRedisOrder(order, perpetualMarket); + const subaccountContent: SubaccountMessageContents = generateSubaccountMessageContents( + redisOrder, + dbOrder, + perpetualMarket, + OrderPlaceV1_OrderPlacementStatus.ORDER_PLACEMENT_STATUS_OPENED, + ); + + const subaccountIdProto: SubaccountId = { + owner: this.getSubaccountId().owner, + number: this.getSubaccountId().number, + }; + kafkaEvents.push(this.generateConsolidatedSubaccountKafkaEvent( + JSON.stringify(subaccountContent), + subaccountIdProto, + this.getOrderId(), + false, + subaccountContent, + )); + } + return kafkaEvents; } } diff --git a/indexer/services/socks/src/config.ts b/indexer/services/socks/src/config.ts index 964c12df21..f0bafab341 100644 --- a/indexer/services/socks/src/config.ts +++ b/indexer/services/socks/src/config.ts @@ -33,7 +33,7 @@ export const configSchema = { WS_HEARTBEAT_INTERVAL_MS: parseInteger(), WS_HEARTBEAT_TIMEOUT_MS: parseInteger(), - RATE_LIMIT_ENABLED: parseBoolean({ default: false }), + RATE_LIMIT_ENABLED: parseBoolean({ default: true }), RATE_LIMIT_SUBSCRIBE_POINTS: parseNumber({ default: 2 }), RATE_LIMIT_SUBSCRIBE_DURATION_MS: parseInteger({ default: 1000 }), RATE_LIMIT_PING_POINTS: parseNumber({ default: 5 }), diff --git a/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts b/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts index 20636667c8..ceab04794c 100644 --- a/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts +++ b/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts @@ -56,14 +56,14 @@ import { } from '@dydxprotocol-indexer/v4-protos'; import { KafkaMessage } from 'kafkajs'; import Long from 'long'; -import { convertToRedisOrder } from '../../src/handlers/helpers'; import { redisClient, redisClient as client } from '../../src/helpers/redis/redis-controller'; import { onMessage } from '../../src/lib/on-message'; import { expectCanceledOrderStatus, expectOpenOrderIds, handleInitialOrderPlace } from '../helpers/helpers'; import { expectOffchainUpdateMessage, expectWebsocketOrderbookMessage, expectWebsocketSubaccountMessage } from '../helpers/websocket-helpers'; import { OrderbookSide } from '../../src/lib/types'; -import { getOrderIdHash, isStatefulOrder } from '@dydxprotocol-indexer/v4-proto-parser'; +import { getOrderIdHash, isLongTermOrder, isStatefulOrder } from '@dydxprotocol-indexer/v4-proto-parser'; import { defaultKafkaHeaders } from '../helpers/constants'; +import config from '../../src/config'; jest.mock('@dydxprotocol-indexer/base', () => ({ ...jest.requireActual('@dydxprotocol-indexer/base'), @@ -84,6 +84,10 @@ describe('order-place-handler', () => { jest.useRealTimers(); }); + afterEach(() => { + config.SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS = true; + }); + describe('handle', () => { const replacementOrder: IndexerOrder = { ...redisTestConstants.defaultOrder, @@ -119,23 +123,23 @@ describe('order-place-handler', () => { quantums: Long.fromValue(500_000, true), subticks: Long.fromValue(1_000_000, true), }; - const replacedOrder: RedisOrder = convertToRedisOrder( + const replacedOrder: RedisOrder = redisPackage.convertToRedisOrder( replacementOrder, testConstants.defaultPerpetualMarket, ); - const replacedOrderGoodTilBlockTime: RedisOrder = convertToRedisOrder( + const replacedOrderGoodTilBlockTime: RedisOrder = redisPackage.convertToRedisOrder( replacementOrderGoodTilBlockTime, testConstants.defaultPerpetualMarket, ); - const replacedOrderConditional: RedisOrder = convertToRedisOrder( + const replacedOrderConditional: RedisOrder = redisPackage.convertToRedisOrder( replacementOrderConditional, testConstants.defaultPerpetualMarket, ); - const replacedOrderFok: RedisOrder = convertToRedisOrder( + const replacedOrderFok: RedisOrder = redisPackage.convertToRedisOrder( replacementOrderFok, testConstants.defaultPerpetualMarket, ); - const replacedOrderIoc: RedisOrder = convertToRedisOrder( + const replacedOrderIoc: RedisOrder = redisPackage.convertToRedisOrder( replacementOrderIoc, testConstants.defaultPerpetualMarket, ); @@ -286,7 +290,7 @@ describe('order-place-handler', () => { expectedOrderUuid: string, expectSubaccountMessageSent: boolean, ) => { - const expectedOrder: RedisOrder = convertToRedisOrder( + const expectedOrder: RedisOrder = redisPackage.convertToRedisOrder( orderToPlace, testConstants.defaultPerpetualMarket, ); @@ -838,19 +842,38 @@ describe('order-place-handler', () => { redisTestConstants.defaultOrderGoodTilBlockTime, redisTestConstants.defaultRedisOrderGoodTilBlockTime, dbOrderGoodTilBlockTime, + false, ], [ 'conditional', redisTestConstants.defaultConditionalOrder, redisTestConstants.defaultRedisOrderConditional, dbConditionalOrder, + false, + ], + [ + 'good-til-block-time', + redisTestConstants.defaultOrderGoodTilBlockTime, + redisTestConstants.defaultRedisOrderGoodTilBlockTime, + dbOrderGoodTilBlockTime, + true, + ], + [ + 'conditional', + redisTestConstants.defaultConditionalOrder, + redisTestConstants.defaultRedisOrderConditional, + dbConditionalOrder, + true, ], ])('handles order place with OPEN placement status, exists initially (with %s)', async ( _name: string, orderToPlace: IndexerOrder, expectedRedisOrder: RedisOrder, placedOrder: OrderFromDatabase, + sendSubaccountWebsocketMessage: boolean, ) => { + // eslint-disable-next-line max-len + config.SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS = sendSubaccountWebsocketMessage; synchronizeWrapBackgroundTask(wrapBackgroundTask); const producerSendSpy: jest.SpyInstance = jest.spyOn(producer, 'send').mockReturnThis(); // Handle the order place event for the initial order with BEST_EFFORT_OPENED @@ -891,7 +914,9 @@ describe('order-place-handler', () => { testConstants.defaultPerpetualMarket, APIOrderStatusEnum.OPEN, // Subaccount messages should be sent for stateful order with OPEN status - true, + !( + isLongTermOrder(expectedRedisOrder.order!.orderId!.orderFlags) && + !sendSubaccountWebsocketMessage), ); expect(logger.error).not.toHaveBeenCalled(); diff --git a/indexer/services/vulcan/src/config.ts b/indexer/services/vulcan/src/config.ts index 651071e759..e71f830f76 100644 --- a/indexer/services/vulcan/src/config.ts +++ b/indexer/services/vulcan/src/config.ts @@ -34,6 +34,9 @@ export const configSchema = { SEND_WEBSOCKET_MESSAGES: parseBoolean({ default: true, }), + SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS: parseBoolean({ + default: true, + }), }; export default parseSchema(configSchema); diff --git a/indexer/services/vulcan/src/handlers/helpers.ts b/indexer/services/vulcan/src/handlers/helpers.ts index ba49631b94..e2c04eea38 100644 --- a/indexer/services/vulcan/src/handlers/helpers.ts +++ b/indexer/services/vulcan/src/handlers/helpers.ts @@ -1,39 +1,10 @@ -import { OrderTable, PerpetualMarketFromDatabase, protocolTranslations } from '@dydxprotocol-indexer/postgres'; import { StateFilledQuantumsCache } from '@dydxprotocol-indexer/redis'; -import { - IndexerOrder, IndexerOrder_Side, RedisOrder, RedisOrder_TickerType, -} from '@dydxprotocol-indexer/v4-protos'; +import { IndexerOrder_Side, RedisOrder } from '@dydxprotocol-indexer/v4-protos'; import Big from 'big.js'; import { redisClient } from '../helpers/redis/redis-controller'; import { OrderbookSide } from '../lib/types'; -/** - * Creates a `RedisOrder` given an `Order` and the corresponding `PerpetualMarket` for the `Order`. - * @param order - * @param perpetualMarket - * @returns - */ -export function convertToRedisOrder( - order: IndexerOrder, - perpetualMarket: PerpetualMarketFromDatabase, -): RedisOrder { - return { - order, - id: OrderTable.orderIdToUuid(order.orderId!), - ticker: perpetualMarket.ticker, - tickerType: RedisOrder_TickerType.TICKER_TYPE_PERPETUAL, - price: protocolTranslations.subticksToPrice( - order.subticks.toString(), - perpetualMarket, - ), - size: protocolTranslations.quantumsToHumanFixedString( - order.quantums.toString(), - perpetualMarket.atomicResolution, - ), - }; -} - export function orderSideToOrderbookSide( orderSide: IndexerOrder_Side, ): OrderbookSide { diff --git a/indexer/services/vulcan/src/handlers/order-place-handler.ts b/indexer/services/vulcan/src/handlers/order-place-handler.ts index 65fcf24df7..ee86d95cba 100644 --- a/indexer/services/vulcan/src/handlers/order-place-handler.ts +++ b/indexer/services/vulcan/src/handlers/order-place-handler.ts @@ -14,9 +14,11 @@ import { placeOrder, PlaceOrderResult, StatefulOrderUpdatesCache, + convertToRedisOrder, } from '@dydxprotocol-indexer/redis'; import { getOrderIdHash, + isLongTermOrder, isStatefulOrder, ORDER_FLAG_SHORT_TERM, requiresImmediateExecution, @@ -36,7 +38,6 @@ import config from '../config'; import { redisClient } from '../helpers/redis/redis-controller'; import { sendMessageWrapper } from '../lib/send-message-helper'; import { Handler } from './handler'; -import { convertToRedisOrder } from './helpers'; /** * Handler for OrderPlace messages. @@ -120,7 +121,12 @@ export class OrderPlaceHandler extends Handler { // TODO(CLOB-597): Remove this logic and log erorrs once best-effort-open is not sent for // stateful orders in the protocol - if (this.shouldSendSubaccountMessage(orderPlace, placeOrderResult, placementStatus)) { + if (this.shouldSendSubaccountMessage( + orderPlace, + placeOrderResult, + placementStatus, + redisOrder, + )) { // TODO(IND-171): Determine whether we should always be sending a message, even when the cache // isn't updated. // For stateful and conditional orders, look the order up in the db for the createdAtHeight @@ -279,7 +285,15 @@ export class OrderPlaceHandler extends Handler { orderPlace: OrderPlaceV1, placeOrderResult: PlaceOrderResult, placementStatus: OrderPlaceV1_OrderPlacementStatus, + redisOrder: RedisOrder, ): boolean { + if ( + isLongTermOrder(redisOrder.order!.orderId!.orderFlags) && + !config.SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS + ) { + return false; + } + const orderFlags: number = orderPlace.order!.orderId!.orderFlags; const status: OrderPlaceV1_OrderPlacementStatus = orderPlace.placementStatus; // Best-effort-opened status should only be sent for short-term orders diff --git a/proto/dydxprotocol/clob/query.proto b/proto/dydxprotocol/clob/query.proto index b4a61e2062..3745756894 100644 --- a/proto/dydxprotocol/clob/query.proto +++ b/proto/dydxprotocol/clob/query.proto @@ -153,4 +153,11 @@ message StreamOrderbookUpdatesResponse { // Note that if the snapshot is true, then all previous entries should be // discarded and the orderbook should be resynced. bool snapshot = 2; + + // ---Additional fields used to debug issues--- + // Block height of the updates. + uint32 block_height = 3; + + // Exec mode of the updates. + uint32 exec_mode = 4; } diff --git a/proto/dydxprotocol/indexer/events/events.proto b/proto/dydxprotocol/indexer/events/events.proto index affeaefea8..68346e5b6c 100644 --- a/proto/dydxprotocol/indexer/events/events.proto +++ b/proto/dydxprotocol/indexer/events/events.proto @@ -381,6 +381,12 @@ message LiquidityTierUpsertEventV1 { // // Deprecated since v3.x. uint64 base_position_notional = 5 [ deprecated = true ]; + + // Lower cap of open interest in quote quantums. + uint64 open_interest_lower_cap = 6; + + // Upper cap of open interest in quote quantums. + uint64 open_interest_upper_cap = 7; } // UpdateClobPairEventV1 message contains all the information about an update to diff --git a/proto/dydxprotocol/subaccounts/query.proto b/proto/dydxprotocol/subaccounts/query.proto index 7533366d0f..63a1578dec 100644 --- a/proto/dydxprotocol/subaccounts/query.proto +++ b/proto/dydxprotocol/subaccounts/query.proto @@ -29,7 +29,15 @@ service Query { QueryGetWithdrawalAndTransfersBlockedInfoRequest) returns (QueryGetWithdrawalAndTransfersBlockedInfoResponse) { option (google.api.http).get = - "/dydxprotocol/subaccounts/withdrawals_and_transfers_blocked_info"; + "/dydxprotocol/subaccounts/withdrawals_and_transfers_blocked_info/" + "{perpetual_id}"; + } + + // Queries the collateral pool account address for a perpetual id. + rpc CollateralPoolAddress(QueryCollateralPoolAddressRequest) + returns (QueryCollateralPoolAddressResponse) { + option (google.api.http).get = + "/dydxprotocol/subaccounts/collateral_pool_address/{perpetual_id}"; } } @@ -69,3 +77,16 @@ message QueryGetWithdrawalAndTransfersBlockedInfoResponse { uint32 chain_outage_seen_at_block = 2; uint32 withdrawals_and_transfers_unblocked_at_block = 3; } + +// QueryCollateralPoolAddressRequest is the request type for fetching the +// account address of the collateral pool associated with the passed in +// perpetual id. +message QueryCollateralPoolAddressRequest { uint32 perpetual_id = 1; } + +// QueryCollateralPoolAddressResponse is a response type for fetching the +// account address of the collateral pool associated with the passed in +// perpetual id. +message QueryCollateralPoolAddressResponse { + string collateral_pool_address = 1 + [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; +} diff --git a/proto/dydxprotocol/vault/genesis.proto b/proto/dydxprotocol/vault/genesis.proto index f0341f243c..04b1976e84 100644 --- a/proto/dydxprotocol/vault/genesis.proto +++ b/proto/dydxprotocol/vault/genesis.proto @@ -1,7 +1,13 @@ syntax = "proto3"; package dydxprotocol.vault; +import "gogoproto/gogo.proto"; +import "dydxprotocol/vault/params.proto"; + option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/vault/types"; // GenesisState defines `x/vault`'s genesis state. -message GenesisState {} +message GenesisState { + // The parameters of the module. + Params params = 1 [ (gogoproto.nullable) = false ]; +} diff --git a/proto/dydxprotocol/vault/params.proto b/proto/dydxprotocol/vault/params.proto new file mode 100644 index 0000000000..ec80d2e971 --- /dev/null +++ b/proto/dydxprotocol/vault/params.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; +package dydxprotocol.vault; + +option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/vault/types"; + +// Params stores `x/vault` parameters. +message Params { + // The number of layers of orders a vault places. For example if + // `layers=2`, a vault places 2 asks and 2 bids. + uint32 layers = 1; + + // The minimum base spread when a vault quotes around reservation price. + uint32 spread_min_ppm = 2; + + // The buffer amount to add to min_price_change_ppm to arrive at `spread` + // according to formula: + // `spread = max(spread_min_ppm, min_price_change_ppm + spread_buffer_ppm)`. + uint32 spread_buffer_ppm = 3; + + // The factor that determines how aggressive a vault skews its orders. + uint32 skew_factor_ppm = 4; + + // The percentage of vault equity that each order is sized at. + uint32 order_size_pct_ppm = 5; + + // The duration that a vault's orders are valid for. + uint32 order_expiration_seconds = 6; +} diff --git a/proto/dydxprotocol/vault/query.proto b/proto/dydxprotocol/vault/query.proto index 4c47f5531f..019a07acb5 100644 --- a/proto/dydxprotocol/vault/query.proto +++ b/proto/dydxprotocol/vault/query.proto @@ -1,7 +1,47 @@ syntax = "proto3"; package dydxprotocol.vault; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "dydxprotocol/subaccounts/subaccount.proto"; +import "dydxprotocol/vault/params.proto"; +import "dydxprotocol/vault/vault.proto"; + option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/vault/types"; // Query defines the gRPC querier service. -service Query {} +service Query { + // Queries the Params. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/dydxprotocol/v4/vault/params"; + } + // Queries a Vault by type and number. + rpc Vault(QueryVaultRequest) returns (QueryVaultResponse) { + option (google.api.http).get = + "/dydxprotocol/v4/vault/vaults/{type}/{number}"; + } +} + +// QueryParamsRequest is a request type for the Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is a response type for the Params RPC method. +message QueryParamsResponse { + Params params = 1 [ (gogoproto.nullable) = false ]; +} + +// QueryVaultRequest is a request type for the Vault RPC method. +message QueryVaultRequest { + VaultType type = 1; + uint32 number = 2; +} + +// QueryVaultResponse is a response type for the Vault RPC method. +message QueryVaultResponse { + VaultId vault_id = 1 [ (gogoproto.nullable) = false ]; + dydxprotocol.subaccounts.SubaccountId subaccount_id = 2 + [ (gogoproto.nullable) = false ]; + uint64 equity = 3; + uint64 inventory = 4; + uint64 total_shares = 5; +} diff --git a/proto/dydxprotocol/vault/tx.proto b/proto/dydxprotocol/vault/tx.proto index 90da68e40f..9f5b77b146 100644 --- a/proto/dydxprotocol/vault/tx.proto +++ b/proto/dydxprotocol/vault/tx.proto @@ -1,7 +1,10 @@ syntax = "proto3"; package dydxprotocol.vault; +import "cosmos_proto/cosmos.proto"; +import "cosmos/msg/v1/msg.proto"; import "dydxprotocol/subaccounts/subaccount.proto"; +import "dydxprotocol/vault/params.proto"; import "dydxprotocol/vault/vault.proto"; import "gogoproto/gogo.proto"; @@ -11,10 +14,14 @@ option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/vault/types"; service Msg { // DepositToVault deposits funds into a vault. rpc DepositToVault(MsgDepositToVault) returns (MsgDepositToVaultResponse); + // UpdateParams updates the Params in state. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } // MsgDepositToVault is the Msg/DepositToVault request type. message MsgDepositToVault { + option (cosmos.msg.v1.signer) = "subaccount_id"; + // The vault to deposit into. VaultId vault_id = 1; @@ -31,3 +38,16 @@ message MsgDepositToVault { // MsgDepositToVaultResponse is the Msg/DepositToVault response type. message MsgDepositToVaultResponse {} + +// MsgUpdateParams is the Msg/UpdateParams request type. +message MsgUpdateParams { + // Authority is the address that controls the module. + option (cosmos.msg.v1.signer) = "authority"; + string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; + + // The parameters to update. Each field must be set. + Params params = 2 [ (gogoproto.nullable) = false ]; +} + +// MsgUpdateParamsResponse is the Msg/UpdateParams response type. +message MsgUpdateParamsResponse {} diff --git a/proto/dydxprotocol/vault/vault.proto b/proto/dydxprotocol/vault/vault.proto index 87b8c9dd5a..75b7766ab8 100644 --- a/proto/dydxprotocol/vault/vault.proto +++ b/proto/dydxprotocol/vault/vault.proto @@ -26,7 +26,7 @@ message VaultId { // NumShares represents the number of shares in a vault. message NumShares { // Number of shares. - bytes num_shares = 1 [ + bytes num_shares = 2 [ (gogoproto.customtype) = "github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt", (gogoproto.nullable) = false diff --git a/protocol/Dockerfile b/protocol/Dockerfile index 17a0bb9458..09c5ef0d66 100644 --- a/protocol/Dockerfile +++ b/protocol/Dockerfile @@ -41,11 +41,6 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ -o /dydxprotocol/build/ \ ./... -# Build the oracle binary -WORKDIR / -RUN git clone https://github.com/skip-mev/slinky.git -WORKDIR /slinky -RUN make build # -------------------------------------------------------- # Runner @@ -56,9 +51,6 @@ FROM golang@sha256:${GOLANG_1_22_ALPINE_DIGEST} RUN apk add --no-cache bash COPY --from=builder /dydxprotocol/build/dydxprotocold /bin/dydxprotocold -COPY --from=builder /dydxprotocol/daemons/slinky/config/oracle.json /etc/oracle.json -COPY --from=builder /dydxprotocol/daemons/slinky/config/market.json /etc/market.json -COPY --from=builder /slinky/build/oracle /bin/slinky ENV HOME /dydxprotocol WORKDIR $HOME diff --git a/protocol/Makefile b/protocol/Makefile index 39b66779cd..206a530946 100644 --- a/protocol/Makefile +++ b/protocol/Makefile @@ -234,10 +234,10 @@ benchmark: @VERSION=$(VERSION) go test -mod=readonly -tags='$(build_tags)' -bench=. ./... test-container: - @SKIP_DISABLED=true VERSION=$(VERSION) go test -mod=readonly -tags='container_test $(build_tags)' ./testing/containertest + @SKIP_DISABLED=true VERSION=$(VERSION) go test -mod=readonly -tags='container_test $(build_tags)' ./... test-container-accept: - @SKIP_DISABLED=true VERSION=$(VERSION) go test -mod=readonly -tags='container_test $(build_tags)' ./testing/containertest -args -accept + @SKIP_DISABLED=true VERSION=$(VERSION) go test -mod=readonly -tags='container_test $(build_tags)' ./... -args -accept test-container-build: $(MAKE) localnet-build diff --git a/protocol/app/app.go b/protocol/app/app.go index dcac547d0c..3138b7845c 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -1683,6 +1683,8 @@ func (app *App) PreBlocker(ctx sdk.Context, _ *abci.RequestFinalizeBlock) (*sdk. // BeginBlocker application updates every begin block func (app *App) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { + ctx = ctx.WithExecMode(lib.ExecModeBeginBlock) + // Update the proposer address in the logger for the panic logging middleware. proposerAddr := sdk.ConsAddress(ctx.BlockHeader().ProposerAddress) middleware.Logger = ctx.Logger().With("proposer_cons_addr", proposerAddr.String()) @@ -1693,6 +1695,8 @@ func (app *App) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { // EndBlocker application updates every end block func (app *App) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { + ctx = ctx.WithExecMode(lib.ExecModeEndBlock) + // Reset the logger for middleware. // Note that the middleware is only used by `CheckTx` and `DeliverTx`, and not `EndBlocker`. // Panics from `EndBlocker` will not be logged by the middleware and will lead to consensus failures. @@ -1716,6 +1720,8 @@ func (app *App) Precommitter(ctx sdk.Context) { // PrepareCheckStater application updates after commit and before any check state is invoked. func (app *App) PrepareCheckStater(ctx sdk.Context) { + ctx = ctx.WithExecMode(lib.ExecModePrepareCheckState) + if err := app.ModuleManager.PrepareCheckState(ctx); err != nil { panic(err) } diff --git a/protocol/app/module/interface_registry.go b/protocol/app/module/interface_registry.go index 88cd60655b..3246180a27 100644 --- a/protocol/app/module/interface_registry.go +++ b/protocol/app/module/interface_registry.go @@ -92,6 +92,9 @@ func NewInterfaceRegistry(addrPrefix string, valAddrPrefix string) (types.Interf "dydxprotocol.sending.MsgWithdrawFromSubaccount": getLegacyMsgSignerFn( []string{"sender", "owner"}, ), + "dydxprotocol.vault.MsgDepositToVault": getLegacyMsgSignerFn( + []string{"subaccount_id", "owner"}, + ), // App injected messages have no signers. "dydxprotocol.bridge.MsgAcknowledgeBridges": noSigners, diff --git a/protocol/app/msgs/all_msgs.go b/protocol/app/msgs/all_msgs.go index dc4715319f..a790f8858b 100644 --- a/protocol/app/msgs/all_msgs.go +++ b/protocol/app/msgs/all_msgs.go @@ -237,6 +237,8 @@ var ( // vault "/dydxprotocol.vault.MsgDepositToVault": {}, "/dydxprotocol.vault.MsgDepositToVaultResponse": {}, + "/dydxprotocol.vault.MsgUpdateParams": {}, + "/dydxprotocol.vault.MsgUpdateParamsResponse": {}, // vest "/dydxprotocol.vest.MsgSetVestEntry": {}, diff --git a/protocol/app/msgs/internal_msgs.go b/protocol/app/msgs/internal_msgs.go index f728967eef..1e6fc67d5c 100644 --- a/protocol/app/msgs/internal_msgs.go +++ b/protocol/app/msgs/internal_msgs.go @@ -28,6 +28,7 @@ import ( rewards "github.com/dydxprotocol/v4-chain/protocol/x/rewards/types" sending "github.com/dydxprotocol/v4-chain/protocol/x/sending/types" stats "github.com/dydxprotocol/v4-chain/protocol/x/stats/types" + vault "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" vest "github.com/dydxprotocol/v4-chain/protocol/x/vest/types" ) @@ -172,6 +173,10 @@ var ( "/dydxprotocol.stats.MsgUpdateParams": &stats.MsgUpdateParams{}, "/dydxprotocol.stats.MsgUpdateParamsResponse": nil, + // vault + "/dydxprotocol.vault.MsgUpdateParams": &vault.MsgUpdateParams{}, + "/dydxprotocol.vault.MsgUpdateParamsResponse": nil, + // vest "/dydxprotocol.vest.MsgSetVestEntry": &vest.MsgSetVestEntry{}, "/dydxprotocol.vest.MsgSetVestEntryResponse": nil, diff --git a/protocol/app/msgs/internal_msgs_test.go b/protocol/app/msgs/internal_msgs_test.go index cd4b7f5aad..763706d0a7 100644 --- a/protocol/app/msgs/internal_msgs_test.go +++ b/protocol/app/msgs/internal_msgs_test.go @@ -133,6 +133,10 @@ func TestInternalMsgSamples_Gov_Key(t *testing.T) { "/dydxprotocol.stats.MsgUpdateParams", "/dydxprotocol.stats.MsgUpdateParamsResponse", + // vault + "/dydxprotocol.vault.MsgUpdateParams", + "/dydxprotocol.vault.MsgUpdateParamsResponse", + // vest "/dydxprotocol.vest.MsgDeleteVestEntry", "/dydxprotocol.vest.MsgDeleteVestEntryResponse", diff --git a/protocol/app/testdata/default_genesis_state.json b/protocol/app/testdata/default_genesis_state.json index 92e61f4219..ead2cf2d7e 100644 --- a/protocol/app/testdata/default_genesis_state.json +++ b/protocol/app/testdata/default_genesis_state.json @@ -455,7 +455,16 @@ "total_escrowed": [] }, "upgrade": {}, - "vault": {}, + "vault": { + "params": { + "layers": 2, + "spread_min_ppm": 3000, + "spread_buffer_ppm": 1500, + "skew_factor_ppm": 500000, + "order_size_pct_ppm": 100000, + "order_expiration_seconds": 2 + } + }, "vest": { "vest_entries": [ { diff --git a/protocol/app/upgrades.go b/protocol/app/upgrades.go index 99c6c7b0c1..8497ee427c 100644 --- a/protocol/app/upgrades.go +++ b/protocol/app/upgrades.go @@ -32,6 +32,7 @@ func (app *App) setupUpgradeHandlers() { app.configurator, app.PerpetualsKeeper, app.ClobKeeper, + app.SubaccountsKeeper, ), ) } diff --git a/protocol/app/upgrades/v5.0.0/upgrade.go b/protocol/app/upgrades/v5.0.0/upgrade.go index 2e8176c179..9729389ad1 100644 --- a/protocol/app/upgrades/v5.0.0/upgrade.go +++ b/protocol/app/upgrades/v5.0.0/upgrade.go @@ -8,6 +8,7 @@ import ( clobtypes "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" upgradetypes "cosmossdk.io/x/upgrade/types" "github.com/cosmos/cosmos-sdk/types/module" @@ -98,22 +99,138 @@ func blockRateLimitConfigUpdate( ) } +func negativeTncSubaccountSeenAtBlockUpgrade( + ctx sdk.Context, + perpetualsKeeper perptypes.PerpetualsKeeper, + subaccountsKeeper satypes.SubaccountsKeeper, +) { + // Get block height stored by v4.x.x. + blockHeight, exists := subaccountsKeeper.LegacyGetNegativeTncSubaccountSeenAtBlock(ctx) + ctx.Logger().Info( + fmt.Sprintf( + "Retrieved block height from store for negative tnc subaccount seen at block: %d, exists: %t\n", + blockHeight, + exists, + ), + ) + // If no block height was stored in the legacy store, no migration needed. + if !exists { + return + } + + // If there are no perpetuals, then no new state needs to be stored, as there can be no + // negative tnc subaccounts w/o perpetuals. + perpetuals := perpetualsKeeper.GetAllPerpetuals(ctx) + ctx.Logger().Info( + fmt.Sprintf( + "Retrieved all perpetuals for negative tnc subaccount migration, # of perpetuals is %d\n", + len(perpetuals), + ), + ) + if len(perpetuals) == 0 { + return + } + + ctx.Logger().Info( + fmt.Sprintf( + "Migrating negative tnc subaccount seen store, storing block height %d for perpetual %d\n", + perpetuals[0].Params.Id, + blockHeight, + ), + ) + // Migrate the value from the legacy store to the new store. + if err := subaccountsKeeper.SetNegativeTncSubaccountSeenAtBlock( + ctx, + perpetuals[0].Params.Id, // must be a cross-margined perpetual due to `perpetualsUpgrade`. + blockHeight, + ); err != nil { + panic(fmt.Sprintf("failed to set negative tnc subaccount seen at block with value %d: %s", blockHeight, err)) + } + ctx.Logger().Info( + fmt.Sprintf( + "Successfully migrated negative tnc subaccount seen at block with block height %d\n", + blockHeight, + ), + ) +} + +// Initialize soft and upper caps for OIMF +func initializeOIMFCaps( + ctx sdk.Context, + perpetualsKeeper perptypes.PerpetualsKeeper, +) { + allLiquidityTiers := perpetualsKeeper.GetAllLiquidityTiers(ctx) + for _, tier := range allLiquidityTiers { + if tier.Id == 0 { + // For large cap, no OIMF caps + tier.OpenInterestLowerCap = 0 + tier.OpenInterestUpperCap = 0 + } else if tier.Id == 1 { + // Mid cap + tier.OpenInterestLowerCap = 25_000_000_000_000 // 25 million USDC + tier.OpenInterestUpperCap = 50_000_000_000_000 // 50 million USDC + } else if tier.Id == 2 { + // Long tail + tier.OpenInterestLowerCap = 10_000_000_000_000 // 10 million USDC + tier.OpenInterestUpperCap = 20_000_000_000_000 // 20 million USDC + } else { + // Safety + tier.OpenInterestLowerCap = 500_000_000_000 // 0.5 million USDC + tier.OpenInterestUpperCap = 1_000_000_000_000 // 1 million USDC + } + + lt, err := perpetualsKeeper.SetLiquidityTier( + ctx, + tier.Id, + tier.Name, + tier.InitialMarginPpm, + tier.MaintenanceFractionPpm, + tier.ImpactNotional, + tier.OpenInterestLowerCap, + tier.OpenInterestUpperCap, + ) + if err != nil { + panic(fmt.Sprintf("failed to set liquidity tier: %+v,\n err: %s", tier.Id, err)) + } + // TODO(OTE-248): Optional - emit indexer events that for updated liquidity tier + ctx.Logger().Info( + fmt.Sprintf( + "Successfully set liqiquidity tier with `OpenInterestLower/UpperCap`: %+v\n", + lt, + ), + ) + // TODO(OTE-249): Add upgrade test that checks if the OIMF caps are set correctly + } +} + func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, perpetualsKeeper perptypes.PerpetualsKeeper, clobKeeper clobtypes.ClobKeeper, + subaccountsKeeper satypes.SubaccountsKeeper, ) upgradetypes.UpgradeHandler { return func(ctx context.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { sdkCtx := lib.UnwrapSDKContext(ctx, "app/upgrades") sdkCtx.Logger().Info(fmt.Sprintf("Running %s Upgrade...", UpgradeName)) + // Migrate pruneable orders to new format + clobKeeper.MigratePruneableOrders(sdkCtx) + sdkCtx.Logger().Info("Successfully migrated pruneable orders") + // Set all perpetuals to cross market type perpetualsUpgrade(sdkCtx, perpetualsKeeper) // Set block rate limit configuration blockRateLimitConfigUpdate(sdkCtx, clobKeeper) + // Migrate state from legacy store for negative tnc subaccount seen to new store for + // negative tnc subaccount seen. + // Note, must be done after the upgrade to perpetuals to cross market type. + negativeTncSubaccountSeenAtBlockUpgrade(sdkCtx, perpetualsKeeper, subaccountsKeeper) + // Initialize liquidity tier with lower and upper OI caps. + initializeOIMFCaps(sdkCtx, perpetualsKeeper) + // TODO(TRA-93): Initialize `x/vault` module. return mm.RunMigrations(ctx, configurator, vm) diff --git a/protocol/app/upgrades/v5.0.0/upgrade_container_test.go b/protocol/app/upgrades/v5.0.0/upgrade_container_test.go new file mode 100644 index 0000000000..57c3328ae9 --- /dev/null +++ b/protocol/app/upgrades/v5.0.0/upgrade_container_test.go @@ -0,0 +1,130 @@ +//go:build all || container_test + +package v_5_0_0_test + +import ( + "testing" + + "github.com/cosmos/gogoproto/proto" + v_5_0_0 "github.com/dydxprotocol/v4-chain/protocol/app/upgrades/v5.0.0" + containertest "github.com/dydxprotocol/v4-chain/protocol/testing/containertest" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + perpetuals "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStateUpgrade(t *testing.T) { + testnet, err := containertest.NewTestnetWithPreupgradeGenesis() + require.NoError(t, err, "failed to create testnet - is docker daemon running?") + err = testnet.Start() + require.NoError(t, err) + defer testnet.MustCleanUp() + node := testnet.Nodes["alice"] + nodeAddress := constants.AliceAccAddress.String() + + preUpgradeChecks(node, t) + + err = containertest.UpgradeTestnet(nodeAddress, t, node, v_5_0_0.UpgradeName) + require.NoError(t, err) + + postUpgradeChecks(node, t) +} + +func preUpgradeChecks(node *containertest.Node, t *testing.T) { + preUpgradeCheckPerpetualMarketType(node, t) + // Add test for your upgrade handler logic below +} + +func postUpgradeChecks(node *containertest.Node, t *testing.T) { + postUpgradecheckPerpetualMarketType(node, t) + postUpgradeCheckLiquidityTiers(node, t) + // Add test for your upgrade handler logic below +} + +func preUpgradeCheckPerpetualMarketType(node *containertest.Node, t *testing.T) { + perpetualsList := &perpetuals.QueryAllPerpetualsResponse{} + resp, err := containertest.Query( + node, + perpetuals.NewQueryClient, + perpetuals.QueryClient.AllPerpetuals, + &perpetuals.QueryAllPerpetualsRequest{}, + ) + require.NoError(t, err) + err = proto.UnmarshalText(resp.String(), perpetualsList) + require.NoError(t, err) + for _, perpetual := range perpetualsList.Perpetual { + assert.Equal(t, perpetuals.PerpetualMarketType_PERPETUAL_MARKET_TYPE_UNSPECIFIED, perpetual.Params.MarketType) + } +} + +func postUpgradecheckPerpetualMarketType(node *containertest.Node, t *testing.T) { + perpetualsList := &perpetuals.QueryAllPerpetualsResponse{} + resp, err := containertest.Query( + node, + perpetuals.NewQueryClient, + perpetuals.QueryClient.AllPerpetuals, + &perpetuals.QueryAllPerpetualsRequest{}, + ) + require.NoError(t, err) + err = proto.UnmarshalText(resp.String(), perpetualsList) + require.NoError(t, err) + for _, perpetual := range perpetualsList.Perpetual { + assert.Equal(t, perpetuals.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, perpetual.Params.MarketType) + } +} + +func postUpgradeCheckLiquidityTiers(node *containertest.Node, t *testing.T) { + resp, err := containertest.Query( + node, + perpetuals.NewQueryClient, + perpetuals.QueryClient.AllLiquidityTiers, + &perpetuals.QueryAllLiquidityTiersRequest{}, + ) + require.NoError(t, err) + + liquidityTiersResponse := &perpetuals.QueryAllLiquidityTiersResponse{} + err = proto.UnmarshalText(resp.String(), liquidityTiersResponse) + require.NoError(t, err) + + assert.Equal(t, 4, len(liquidityTiersResponse.LiquidityTiers)) + assert.Equal(t, perpetuals.LiquidityTier{ + Id: 0, + Name: "Large-Cap", + InitialMarginPpm: 50_000, + MaintenanceFractionPpm: 600_000, + ImpactNotional: 10_000_000_000, + OpenInterestLowerCap: uint64(0), + OpenInterestUpperCap: uint64(0), + }, liquidityTiersResponse.LiquidityTiers[0]) + + assert.Equal(t, perpetuals.LiquidityTier{ + Id: 1, + Name: "Mid-Cap", + InitialMarginPpm: 100_000, + MaintenanceFractionPpm: 500_000, + ImpactNotional: 5_000_000_000, + OpenInterestLowerCap: uint64(25_000_000_000_000), + OpenInterestUpperCap: uint64(50_000_000_000_000), + }, liquidityTiersResponse.LiquidityTiers[1]) + + assert.Equal(t, perpetuals.LiquidityTier{ + Id: 2, + Name: "Long-Tail", + InitialMarginPpm: 200_000, + MaintenanceFractionPpm: 500_000, + ImpactNotional: 2_500_000_000, + OpenInterestLowerCap: uint64(10_000_000_000_000), + OpenInterestUpperCap: uint64(20_000_000_000_000), + }, liquidityTiersResponse.LiquidityTiers[2]) + + assert.Equal(t, perpetuals.LiquidityTier{ + Id: 3, + Name: "Safety", + InitialMarginPpm: 1_000_000, + MaintenanceFractionPpm: 200_000, + ImpactNotional: 2_500_000_000, + OpenInterestLowerCap: uint64(500_000_000_000), + OpenInterestUpperCap: uint64(1_000_000_000_000), + }, liquidityTiersResponse.LiquidityTiers[3]) +} diff --git a/protocol/docker-compose.yml b/protocol/docker-compose.yml index d5a2b384ff..8531d1041e 100644 --- a/protocol/docker-compose.yml +++ b/protocol/docker-compose.yml @@ -113,17 +113,10 @@ services: volumes: - ./localnet/dydxprotocol3:/dydxprotocol/chain/.dave/data slinky0: - image: local:dydxprotocol - entrypoint: - - slinky - - -oracle-config-path - - /etc/oracle.json - - -market-config-path - - /etc/market.json - - -host - - "0.0.0.0" - - -port - - "8080" + image: ghcr.io/skip-mev/slinky-sidecar:v0.3.1 + entrypoint: > + sh -c "slinky-config --chain dydx --node-http-url http://dydxprotocold0:1317 --oracle-config-path /etc/oracle.json && + slinky --oracle-config-path /etc/oracle.json --update-market-config-path /etc/market.json" ports: - "8080:8080" diff --git a/protocol/go.mod b/protocol/go.mod index 4303d3034d..f8ee1d5547 100644 --- a/protocol/go.mod +++ b/protocol/go.mod @@ -18,7 +18,7 @@ require ( github.com/gofrs/flock v0.8.1 github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.6.0 - github.com/golang/protobuf v1.5.3 + github.com/golang/protobuf v1.5.4 github.com/golangci/golangci-lint v1.56.2 github.com/google/go-cmp v0.6.0 github.com/gorilla/mux v1.8.1 @@ -30,11 +30,11 @@ require ( github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/vektra/mockery/v2 v2.42.0 github.com/zyedidia/generic v1.0.0 - golang.org/x/exp v0.0.0-20240213143201-ec583247a57a - google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect google.golang.org/grpc v1.62.0 gopkg.in/DataDog/dd-trace-go.v1 v1.48.0 gopkg.in/typ.v4 v4.1.0 @@ -50,7 +50,7 @@ require ( cosmossdk.io/x/circuit v0.1.0 cosmossdk.io/x/evidence v0.1.0 cosmossdk.io/x/feegrant v0.1.0 - cosmossdk.io/x/tx v0.13.0 + cosmossdk.io/x/tx v0.13.1 cosmossdk.io/x/upgrade v0.1.1 github.com/burdiyan/kafkautil v0.0.0-20190131162249-eaf83ed22d5b github.com/cosmos/cosmos-db v1.0.2 @@ -70,8 +70,8 @@ require ( github.com/spf13/viper v1.18.2 github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d go.uber.org/zap v1.27.0 - google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 - google.golang.org/protobuf v1.32.0 + google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 + google.golang.org/protobuf v1.33.0 gotest.tools/v3 v3.5.1 ) @@ -79,9 +79,9 @@ require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect cloud.google.com/go v0.112.0 // indirect - cloud.google.com/go/compute v1.23.3 // indirect + cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.5 // indirect + cloud.google.com/go/iam v1.1.6 // indirect cloud.google.com/go/storage v1.36.0 // indirect cosmossdk.io/collections v0.4.0 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect @@ -186,7 +186,7 @@ require ( github.com/go-critic/go-critic v0.11.1 // indirect github.com/go-kit/kit v0.13.0 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.3.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -358,7 +358,7 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect - github.com/stretchr/objx v0.5.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect @@ -389,11 +389,11 @@ require ( go-simpler.org/sloglint v0.4.0 // indirect go.etcd.io/bbolt v1.3.8 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.21.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect + go.opentelemetry.io/otel v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.22.0 // indirect + go.opentelemetry.io/otel/trace v1.22.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.19.0 // indirect golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect @@ -406,9 +406,9 @@ require ( golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect - google.golang.org/api v0.155.0 // indirect + google.golang.org/api v0.162.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect @@ -432,11 +432,11 @@ replace ( // should use v0.11.0. The Cosmos build fails with types/context.go:65:29: undefined: comet.BlockInfo otherwise. cosmossdk.io/core => cosmossdk.io/core v0.11.0 // Use dYdX fork of Cosmos SDK/store - cosmossdk.io/store => github.com/dydxprotocol/cosmos-sdk/store v1.0.3-0.20240227194839-f4e166bc1057 + cosmossdk.io/store => github.com/dydxprotocol/cosmos-sdk/store v1.0.3-0.20240326192503-dd116391188d // Use dYdX fork of CometBFT github.com/cometbft/cometbft => github.com/dydxprotocol/cometbft v0.38.6-0.20240229050000-3b085f30b462 // Use dYdX fork of Cosmos SDK - github.com/cosmos/cosmos-sdk => github.com/dydxprotocol/cosmos-sdk v0.50.5-0.20240320193634-44a06cd9428e + github.com/cosmos/cosmos-sdk => github.com/dydxprotocol/cosmos-sdk v0.50.6-0.20240326192503-dd116391188d ) replace ( diff --git a/protocol/go.sum b/protocol/go.sum index 26879ce2f1..02209a2a5e 100644 --- a/protocol/go.sum +++ b/protocol/go.sum @@ -72,8 +72,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= -cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= @@ -113,8 +113,8 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= @@ -210,8 +210,8 @@ cosmossdk.io/x/evidence v0.1.0 h1:J6OEyDl1rbykksdGynzPKG5R/zm6TacwW2fbLTW4nCk= cosmossdk.io/x/evidence v0.1.0/go.mod h1:hTaiiXsoiJ3InMz1uptgF0BnGqROllAN8mwisOMMsfw= cosmossdk.io/x/feegrant v0.1.0 h1:c7s3oAq/8/UO0EiN1H5BIjwVntujVTkYs35YPvvrdQk= cosmossdk.io/x/feegrant v0.1.0/go.mod h1:4r+FsViJRpcZif/yhTn+E0E6OFfg4n0Lx+6cCtnZElU= -cosmossdk.io/x/tx v0.13.0 h1:8lzyOh3zONPpZv2uTcUmsv0WTXy6T1/aCVDCqShmpzU= -cosmossdk.io/x/tx v0.13.0/go.mod h1:CpNQtmoqbXa33/DVxWQNx5Dcnbkv2xGUhL7tYQ5wUsY= +cosmossdk.io/x/tx v0.13.1 h1:Mg+EMp67Pz+NukbJqYxuo8uRp7N/a9uR+oVS9pONtj8= +cosmossdk.io/x/tx v0.13.1/go.mod h1:CBCU6fsRVz23QGFIQBb1DNX2DztJCf3jWyEkHY2nJQ0= cosmossdk.io/x/upgrade v0.1.1 h1:aoPe2gNvH+Gwt/Pgq3dOxxQVU3j5P6Xf+DaUJTDZATc= cosmossdk.io/x/upgrade v0.1.1/go.mod h1:MNLptLPcIFK9CWt7Ra//8WUZAxweyRDNcbs5nkOcQy0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -527,10 +527,10 @@ github.com/dvsekhvalnov/jose2go v1.6.0 h1:Y9gnSnP4qEI0+/uQkHvFXeD2PLPJeXEL+ySMEA github.com/dvsekhvalnov/jose2go v1.6.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/dydxprotocol/cometbft v0.38.6-0.20240229050000-3b085f30b462 h1:ufl8wDrax5K/33NMX/2P54iJAb5LlHJA8CYIcdfeR+o= github.com/dydxprotocol/cometbft v0.38.6-0.20240229050000-3b085f30b462/go.mod h1:REQN+ObgfYxi39TcYR/Hv95C9bPxY3sYJCvghryj7vY= -github.com/dydxprotocol/cosmos-sdk v0.50.5-0.20240320193634-44a06cd9428e h1:3HCMOMbqlBAK3dboZSKJjvDZ4eg3N1h7EdaMCpmRt9U= -github.com/dydxprotocol/cosmos-sdk v0.50.5-0.20240320193634-44a06cd9428e/go.mod h1:yQgIqbm+W0FODUwPIihPJOp3igupvNTbPcmyaRYZGTw= -github.com/dydxprotocol/cosmos-sdk/store v1.0.3-0.20240227194839-f4e166bc1057 h1:rY2cckHMrpk3+9ggVqp3Zsvfn7AwEjjazvyp4HK3lgw= -github.com/dydxprotocol/cosmos-sdk/store v1.0.3-0.20240227194839-f4e166bc1057/go.mod h1:M4hH9rTCui49tnujKeM+BWCiXGQwZ//3QCvl0HbR6Cs= +github.com/dydxprotocol/cosmos-sdk v0.50.6-0.20240326192503-dd116391188d h1:kHT+xXpjwdKO2drqMEFSN1J6VjEoOweYPIH4RrQ3IWs= +github.com/dydxprotocol/cosmos-sdk v0.50.6-0.20240326192503-dd116391188d/go.mod h1:NruCXox2SOkVq2ZC5Bs8s2sT+jjVqBEU59wXyZOkCkM= +github.com/dydxprotocol/cosmos-sdk/store v1.0.3-0.20240326192503-dd116391188d h1:HgLu1FD2oDFzlKW6/+SFXlH5Os8cwNTbplQIrQOWx8w= +github.com/dydxprotocol/cosmos-sdk/store v1.0.3-0.20240326192503-dd116391188d/go.mod h1:zMcD3hfNwd0WMTpdRUhS3QxoCoEtBXWeoKsu3iaLBbQ= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0= github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= @@ -617,8 +617,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= @@ -724,8 +724,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -1405,8 +1405,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= -github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -1419,8 +1419,9 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -1530,18 +1531,18 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= -go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= +go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= +go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= +go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= +go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -1595,8 +1596,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= -golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 h1:UhRVJ0i7bF9n/Hd8YjW3eKjlPVBHzbQdxrBgjbSKl64= @@ -2034,8 +2035,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= -google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/api v0.162.0 h1:Vhs54HkaEpkMBdgGdOT2P6F0csGG/vxDS0hWHJzmmps= +google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2152,12 +2153,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= -google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= -google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 h1:x9PwdEgd11LgK+orcck69WVRo7DezSO4VUMPI4xpc8A= +google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -2218,8 +2219,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/DataDog/dd-trace-go.v1 v1.48.0 h1:AZhmo9zstDWWD7qG7g+2W7x4X7FYuGJcwRIIEjsLiEY= gopkg.in/DataDog/dd-trace-go.v1 v1.48.0/go.mod h1:z+Zm99hL8zep83JLkTbknOxSMJnNvAggmL6mnUtDhWE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/protocol/indexer/events/events.pb.go b/protocol/indexer/events/events.pb.go index 40208d144e..b39168347e 100644 --- a/protocol/indexer/events/events.pb.go +++ b/protocol/indexer/events/events.pb.go @@ -1763,6 +1763,10 @@ type LiquidityTierUpsertEventV1 struct { // // Deprecated since v3.x. BasePositionNotional uint64 `protobuf:"varint,5,opt,name=base_position_notional,json=basePositionNotional,proto3" json:"base_position_notional,omitempty"` // Deprecated: Do not use. + // Lower cap of open interest in quote quantums. + OpenInterestLowerCap uint64 `protobuf:"varint,6,opt,name=open_interest_lower_cap,json=openInterestLowerCap,proto3" json:"open_interest_lower_cap,omitempty"` + // Upper cap of open interest in quote quantums. + OpenInterestUpperCap uint64 `protobuf:"varint,7,opt,name=open_interest_upper_cap,json=openInterestUpperCap,proto3" json:"open_interest_upper_cap,omitempty"` } func (m *LiquidityTierUpsertEventV1) Reset() { *m = LiquidityTierUpsertEventV1{} } @@ -1834,6 +1838,20 @@ func (m *LiquidityTierUpsertEventV1) GetBasePositionNotional() uint64 { return 0 } +func (m *LiquidityTierUpsertEventV1) GetOpenInterestLowerCap() uint64 { + if m != nil { + return m.OpenInterestLowerCap + } + return 0 +} + +func (m *LiquidityTierUpsertEventV1) GetOpenInterestUpperCap() uint64 { + if m != nil { + return m.OpenInterestUpperCap + } + return 0 +} + // UpdateClobPairEventV1 message contains all the information about an update to // a clob pair on the dYdX chain. type UpdateClobPairEventV1 struct { @@ -2148,140 +2166,143 @@ func init() { } var fileDescriptor_6331dfb59c6fd2bb = []byte{ - // 2122 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x59, 0xcb, 0x6f, 0x1b, 0xc9, - 0xd1, 0x17, 0xc9, 0x11, 0x25, 0x15, 0x45, 0x99, 0x6c, 0x4b, 0x32, 0x25, 0x7d, 0x9f, 0xec, 0x0c, - 0x10, 0xc0, 0xf0, 0xee, 0x52, 0x96, 0xb3, 0x09, 0x16, 0x39, 0x04, 0x11, 0xf5, 0x58, 0xd1, 0xb0, - 0x64, 0xee, 0x88, 0xf2, 0xee, 0x3a, 0xc1, 0x4e, 0x5a, 0x33, 0x4d, 0xaa, 0xa1, 0x79, 0x79, 0xba, - 0x29, 0x5b, 0x06, 0x72, 0x4e, 0x0e, 0x01, 0x12, 0x20, 0xc8, 0x21, 0x87, 0x1c, 0x72, 0xc8, 0x25, - 0x40, 0x0e, 0x01, 0x72, 0xcd, 0x29, 0x97, 0xbd, 0x65, 0x91, 0x4b, 0x82, 0x1c, 0x16, 0x81, 0x7d, - 0xc8, 0xbf, 0x11, 0xf4, 0x63, 0x86, 0xa4, 0xf8, 0x30, 0x6d, 0x69, 0x4f, 0x9c, 0xae, 0xea, 0xfa, - 0x55, 0x75, 0x55, 0x75, 0x57, 0x75, 0x13, 0xee, 0xba, 0x17, 0xee, 0x8b, 0x28, 0x0e, 0x79, 0xe8, - 0x84, 0xde, 0x06, 0x0d, 0x5c, 0xf2, 0x82, 0xc4, 0x1b, 0xe4, 0x9c, 0x04, 0x9c, 0xe9, 0x9f, 0xaa, - 0x64, 0xa3, 0xb5, 0xde, 0x99, 0x55, 0x3d, 0xb3, 0xaa, 0xa6, 0xac, 0xae, 0x38, 0x21, 0xf3, 0x43, - 0x66, 0x4b, 0xfe, 0x86, 0x1a, 0x28, 0xb9, 0xd5, 0xc5, 0x76, 0xd8, 0x0e, 0x15, 0x5d, 0x7c, 0x69, - 0xea, 0xfd, 0xa1, 0x7a, 0xd9, 0x29, 0x8e, 0x89, 0xbb, 0x11, 0x13, 0x3f, 0x3c, 0xc7, 0x9e, 0x1d, - 0x13, 0xcc, 0xc2, 0x40, 0x4b, 0xbc, 0x37, 0x54, 0x22, 0x25, 0x9c, 0x6f, 0x6e, 0x38, 0x5e, 0x78, - 0x32, 0x16, 0xbe, 0x77, 0x72, 0x44, 0xe2, 0x88, 0xf0, 0x0e, 0xf6, 0xb4, 0xc4, 0xe6, 0x1b, 0x25, - 0x58, 0xe7, 0x04, 0x3b, 0x4e, 0xd8, 0x09, 0xb8, 0x12, 0x31, 0xff, 0x9e, 0x81, 0x1b, 0x7b, 0x9d, - 0xc0, 0xa5, 0x41, 0xfb, 0x38, 0x72, 0x31, 0x27, 0x4f, 0x36, 0xd1, 0xb7, 0x60, 0x3e, 0x45, 0xb6, - 0xa9, 0x5b, 0xc9, 0xdc, 0xc9, 0xdc, 0x2d, 0x5a, 0x85, 0x94, 0x56, 0x77, 0xd1, 0x3d, 0x28, 0xb7, - 0x94, 0x94, 0x7d, 0x8e, 0xbd, 0x0e, 0xb1, 0xa3, 0xc8, 0xaf, 0x64, 0xef, 0x64, 0xee, 0x4e, 0x5b, - 0x37, 0x34, 0xe3, 0x89, 0xa0, 0x37, 0x22, 0x1f, 0xf9, 0x50, 0x4c, 0xe6, 0x4a, 0x93, 0x2a, 0xb9, - 0x3b, 0x99, 0xbb, 0xf3, 0xb5, 0xfd, 0x2f, 0xbf, 0xbe, 0x3d, 0xf5, 0xef, 0xaf, 0x6f, 0xff, 0xb0, - 0x4d, 0xf9, 0x69, 0xe7, 0xa4, 0xea, 0x84, 0xfe, 0x46, 0x9f, 0xfd, 0xe7, 0x1f, 0x7e, 0xe0, 0x9c, - 0x62, 0x1a, 0x74, 0x17, 0xe0, 0xf2, 0x8b, 0x88, 0xb0, 0xea, 0x11, 0x89, 0x29, 0xf6, 0xe8, 0x4b, - 0x7c, 0xe2, 0x91, 0x7a, 0xc0, 0xad, 0x79, 0x0d, 0x5f, 0x17, 0xe8, 0xe6, 0xaf, 0xb3, 0xb0, 0xa0, - 0x57, 0xb4, 0x2b, 0x02, 0xfb, 0x64, 0x13, 0x3d, 0x82, 0x99, 0x8e, 0x5c, 0x1c, 0xab, 0x64, 0xee, - 0xe4, 0xee, 0x16, 0x1e, 0xbc, 0x5f, 0x1d, 0x93, 0x08, 0xd5, 0x4b, 0xfe, 0xa8, 0x19, 0xc2, 0x52, - 0x2b, 0x81, 0x40, 0x3b, 0x60, 0x08, 0x3b, 0xe4, 0x72, 0x17, 0x1e, 0xdc, 0x9f, 0x04, 0x4a, 0x1b, - 0x52, 0x6d, 0x5e, 0x44, 0xc4, 0x92, 0xd2, 0xa6, 0x0f, 0x86, 0x18, 0xa1, 0x45, 0x28, 0x35, 0x3f, - 0x6f, 0xec, 0xda, 0xc7, 0x87, 0x47, 0x8d, 0xdd, 0xed, 0xfa, 0x5e, 0x7d, 0x77, 0xa7, 0x34, 0x85, - 0x6e, 0xc1, 0x4d, 0x49, 0x6d, 0x58, 0xbb, 0x07, 0xf5, 0xe3, 0x03, 0xfb, 0x68, 0xeb, 0xa0, 0xf1, - 0x68, 0xb7, 0x94, 0x41, 0xb7, 0x61, 0x4d, 0x32, 0xf6, 0x8e, 0x0f, 0x77, 0xea, 0x87, 0x1f, 0xdb, - 0xd6, 0x56, 0x73, 0xd7, 0xde, 0x3a, 0xdc, 0xb1, 0xeb, 0x87, 0x3b, 0xbb, 0x9f, 0x95, 0xb2, 0x68, - 0x09, 0xca, 0x7d, 0x92, 0x4f, 0x1e, 0x37, 0x77, 0x4b, 0x39, 0xf3, 0x6f, 0x59, 0x28, 0x1e, 0xe0, - 0xf8, 0x8c, 0xf0, 0xc4, 0x29, 0x6b, 0x30, 0xe7, 0x4b, 0x42, 0x37, 0xc4, 0xb3, 0x8a, 0x50, 0x77, - 0xd1, 0x53, 0x98, 0x8f, 0x62, 0xea, 0x10, 0x5b, 0x2d, 0x5a, 0xae, 0xb5, 0xf0, 0xe0, 0xbb, 0x63, - 0xd7, 0xaa, 0xe0, 0x1b, 0x42, 0x4c, 0xb9, 0x4e, 0x6b, 0xda, 0x9f, 0xb2, 0x0a, 0x51, 0x97, 0x8a, - 0x3e, 0x85, 0xa2, 0x56, 0xec, 0xc4, 0x44, 0x80, 0xe7, 0x24, 0xf8, 0xfd, 0x09, 0xc0, 0xb7, 0xa5, - 0x40, 0x17, 0x77, 0xde, 0xef, 0x21, 0xf7, 0x00, 0xfb, 0xa1, 0x4b, 0x5b, 0x17, 0x15, 0x63, 0x62, - 0xe0, 0x03, 0x29, 0x30, 0x00, 0xac, 0xc8, 0xb5, 0x19, 0x98, 0x96, 0xb3, 0xcd, 0x87, 0x50, 0x19, - 0xb5, 0x4a, 0x54, 0x85, 0x9b, 0xca, 0x65, 0xcf, 0x29, 0x3f, 0xb5, 0xc9, 0x8b, 0x28, 0x0c, 0x48, - 0xc0, 0xa5, 0x67, 0x0d, 0xab, 0x2c, 0x59, 0x9f, 0x52, 0x7e, 0xba, 0xab, 0x19, 0xe6, 0x67, 0x50, - 0x56, 0x58, 0x35, 0xcc, 0x52, 0x10, 0x04, 0x46, 0x84, 0x69, 0x2c, 0xa5, 0xe6, 0x2c, 0xf9, 0x8d, - 0x36, 0x60, 0xd1, 0xa7, 0x81, 0xad, 0xc0, 0x9d, 0x53, 0x1c, 0xb4, 0xbb, 0xdb, 0xad, 0x68, 0x95, - 0x7d, 0x1a, 0x48, 0x6b, 0xb6, 0x25, 0xa7, 0x11, 0xf9, 0x66, 0x07, 0x6e, 0x0e, 0x71, 0x17, 0xaa, - 0x81, 0x71, 0x82, 0x19, 0x91, 0xd8, 0x85, 0x07, 0xd5, 0x09, 0xbc, 0xd2, 0x63, 0x99, 0x25, 0x65, - 0xd1, 0x2a, 0xcc, 0xa6, 0x2b, 0x13, 0xfa, 0xcb, 0x56, 0x3a, 0x36, 0x3f, 0x4f, 0xd4, 0xf6, 0x39, - 0xf3, 0x3a, 0xd4, 0x9a, 0x7f, 0xca, 0x40, 0xf1, 0x28, 0xec, 0xc4, 0x0e, 0x79, 0xdc, 0x12, 0x5b, - 0x8a, 0xa1, 0x1f, 0x43, 0xb1, 0x7b, 0x96, 0x25, 0x19, 0x3c, 0x32, 0x43, 0x53, 0xc2, 0xf9, 0x66, - 0xb5, 0xae, 0x68, 0x47, 0xa9, 0x74, 0xdd, 0x15, 0x01, 0x67, 0x3d, 0x63, 0xf4, 0x21, 0xcc, 0x60, - 0xd7, 0x8d, 0x09, 0x63, 0x72, 0x95, 0x73, 0xb5, 0xca, 0x3f, 0xfe, 0xf2, 0xc1, 0xa2, 0x2e, 0x09, - 0x5b, 0x8a, 0x73, 0xc4, 0x63, 0x1a, 0xb4, 0xf7, 0xa7, 0xac, 0x64, 0x6a, 0x6d, 0x16, 0xf2, 0x4c, - 0x1a, 0x69, 0xfe, 0x31, 0x07, 0x37, 0x9a, 0x31, 0x0e, 0x58, 0x8b, 0xc4, 0x89, 0x1f, 0xda, 0xb0, - 0xc8, 0x48, 0xe0, 0x92, 0xd8, 0xbe, 0x3e, 0xc3, 0x2d, 0xa4, 0x20, 0x7b, 0x69, 0xc8, 0x87, 0x5b, - 0x31, 0x71, 0x68, 0x44, 0x49, 0xc0, 0x2f, 0xe9, 0xca, 0x5e, 0x45, 0xd7, 0x52, 0x8a, 0xda, 0xa7, - 0x6e, 0x05, 0x66, 0x31, 0x63, 0xea, 0x18, 0xc9, 0xc9, 0x94, 0x9c, 0x91, 0xe3, 0xba, 0x8b, 0x96, - 0x21, 0x8f, 0x7d, 0x31, 0x4d, 0xee, 0x44, 0xc3, 0xd2, 0x23, 0x54, 0x83, 0xbc, 0xb2, 0xbb, 0x32, - 0x2d, 0x0d, 0xba, 0x37, 0x36, 0x29, 0xfa, 0x02, 0x6f, 0x69, 0x49, 0xb4, 0x0f, 0x73, 0xa9, 0x3d, - 0x95, 0xfc, 0x5b, 0xc3, 0x74, 0x85, 0xcd, 0x7f, 0xe6, 0xa0, 0xf4, 0x38, 0x76, 0x49, 0xbc, 0x47, - 0x3d, 0x2f, 0x89, 0xd6, 0x31, 0x14, 0x7c, 0x7c, 0x46, 0x62, 0x3b, 0x14, 0x9c, 0xf1, 0xc9, 0x3b, - 0xc4, 0x71, 0x12, 0x4f, 0x17, 0x0e, 0x90, 0x40, 0x92, 0x82, 0xf6, 0x60, 0x5a, 0x01, 0x66, 0xdf, - 0x05, 0x70, 0x7f, 0xca, 0x52, 0xe2, 0xe8, 0x0b, 0x28, 0x7b, 0xf4, 0x59, 0x87, 0xba, 0x98, 0xd3, - 0x30, 0xd0, 0x46, 0xaa, 0xe3, 0x6e, 0x63, 0xac, 0x17, 0x1e, 0x75, 0xa5, 0x24, 0xa4, 0x3c, 0xed, - 0x4a, 0xde, 0x25, 0x2a, 0xba, 0x0d, 0x85, 0x16, 0xf5, 0x3c, 0x5b, 0x87, 0x2f, 0x27, 0xc3, 0x07, - 0x82, 0xb4, 0xa5, 0x42, 0x28, 0xab, 0x87, 0xf0, 0x4f, 0x8b, 0x10, 0x19, 0x45, 0x24, 0xaa, 0xc7, - 0x19, 0x89, 0xf7, 0x08, 0x11, 0x4c, 0x9e, 0x32, 0xf3, 0x8a, 0xc9, 0x13, 0xe6, 0xfb, 0x80, 0x78, - 0xc8, 0xb1, 0x67, 0x0b, 0x34, 0xe2, 0xda, 0x52, 0xaa, 0x32, 0x23, 0x35, 0x94, 0x24, 0x67, 0x4f, - 0x32, 0x0e, 0x04, 0x7d, 0x60, 0xb6, 0x84, 0xa9, 0xcc, 0x0e, 0xcc, 0x6e, 0x0a, 0x7a, 0xad, 0x08, - 0x05, 0xde, 0x8d, 0x9a, 0xf9, 0x8b, 0x1c, 0xdc, 0xdc, 0x21, 0x1e, 0x39, 0x27, 0x31, 0x6e, 0xf7, - 0xf4, 0x03, 0x3f, 0x02, 0x48, 0x56, 0x4c, 0xae, 0xb6, 0x01, 0x93, 0x10, 0x77, 0xe1, 0x04, 0x78, - 0xd8, 0x6a, 0x31, 0xc2, 0x39, 0x0d, 0xda, 0x57, 0xda, 0x71, 0x09, 0x78, 0x17, 0x6e, 0xa0, 0x35, - 0xcb, 0x0d, 0xb6, 0x66, 0x97, 0x42, 0x67, 0x0c, 0x84, 0xee, 0x3e, 0x2c, 0x2a, 0x97, 0x3e, 0xeb, - 0x84, 0x9c, 0xd8, 0xcf, 0x3a, 0x38, 0xe0, 0x1d, 0x9f, 0xc9, 0x28, 0x1a, 0x96, 0x72, 0xf7, 0x27, - 0x82, 0xf5, 0x89, 0xe6, 0xa0, 0x25, 0xc8, 0x53, 0x66, 0x9f, 0x74, 0x2e, 0x64, 0x30, 0x67, 0xad, - 0x69, 0xca, 0x6a, 0x9d, 0x0b, 0x51, 0xf1, 0x28, 0xb3, 0x5b, 0x34, 0xc0, 0x9e, 0x2d, 0x0c, 0xf4, - 0x88, 0x2f, 0x36, 0xe3, 0x8c, 0x9c, 0x53, 0xa6, 0x6c, 0x4f, 0x70, 0x8e, 0x52, 0x86, 0xf9, 0xf3, - 0x2c, 0xa0, 0xc1, 0xfc, 0xfb, 0x66, 0xa3, 0x71, 0x07, 0xe6, 0x45, 0x4b, 0x6d, 0x8b, 0x4a, 0x9a, - 0x9c, 0x80, 0x45, 0x0b, 0x04, 0xad, 0x81, 0x69, 0x5c, 0x77, 0x27, 0x71, 0xe9, 0xff, 0x03, 0x28, - 0x8f, 0x31, 0xfa, 0x92, 0x68, 0x8f, 0xce, 0x49, 0xca, 0x11, 0x7d, 0x49, 0x7a, 0xdc, 0x33, 0xdd, - 0xeb, 0x9e, 0x55, 0x98, 0x65, 0x9d, 0x13, 0x4e, 0x9d, 0x33, 0x26, 0xfd, 0x66, 0x58, 0xe9, 0xd8, - 0xfc, 0x6f, 0x16, 0x6e, 0x75, 0x2d, 0xef, 0x6f, 0x24, 0x9e, 0x5e, 0x67, 0x69, 0xbb, 0x54, 0xd8, - 0x5e, 0xc2, 0x9a, 0xea, 0xe8, 0x5c, 0xbb, 0xbb, 0xe8, 0x28, 0x64, 0x54, 0x04, 0x84, 0x55, 0x72, - 0xb2, 0x3b, 0xfe, 0xfe, 0xc4, 0x9a, 0x1a, 0x09, 0x46, 0x43, 0x43, 0x58, 0x2b, 0x1a, 0x7e, 0x80, - 0xc3, 0x50, 0x00, 0xb7, 0x12, 0xdd, 0xaa, 0x60, 0x74, 0xf5, 0x1a, 0x52, 0xef, 0xf7, 0x26, 0xd6, - 0xbb, 0x25, 0xe4, 0x53, 0x9d, 0x4b, 0x1a, 0xb6, 0x8f, 0xca, 0x1e, 0x1a, 0xb3, 0xd9, 0x52, 0xce, - 0xfc, 0x3d, 0xc0, 0xe2, 0x11, 0xc7, 0x9c, 0xb4, 0x3a, 0x9e, 0xcc, 0xb8, 0xc4, 0xcd, 0x3e, 0x14, - 0xe4, 0x29, 0x61, 0x47, 0x1e, 0x76, 0x92, 0xf6, 0xe4, 0xe1, 0xf8, 0x12, 0x32, 0x04, 0xa7, 0x9f, - 0xd8, 0x10, 0x58, 0x7e, 0xd2, 0x45, 0x42, 0x98, 0xd2, 0x50, 0x08, 0x45, 0xa5, 0x4e, 0x5f, 0x0c, - 0xf5, 0x69, 0xbd, 0x7f, 0x45, 0x85, 0x96, 0x42, 0x53, 0x4d, 0x6b, 0xd8, 0x43, 0x41, 0xbf, 0xcc, - 0xc0, 0x9a, 0x13, 0x06, 0xae, 0xf4, 0x06, 0xf6, 0xec, 0x9e, 0xc5, 0xca, 0x6d, 0xaa, 0x4a, 0xef, - 0xc1, 0xdb, 0xeb, 0xdf, 0xee, 0x82, 0x0e, 0x59, 0xf3, 0x8a, 0x33, 0x8a, 0x3d, 0xc2, 0x22, 0x1e, - 0xd3, 0x76, 0x9b, 0xc4, 0xc4, 0xd5, 0x55, 0xfc, 0x1a, 0x2c, 0x6a, 0x26, 0x90, 0xc3, 0x2d, 0x4a, - 0xd9, 0xe8, 0x67, 0x19, 0x58, 0xf1, 0xc2, 0xa0, 0x6d, 0x73, 0x12, 0xfb, 0x03, 0x1e, 0x9a, 0x79, - 0xd7, 0x94, 0x78, 0x14, 0x06, 0xed, 0x26, 0x89, 0xfd, 0x21, 0xee, 0x59, 0xf6, 0x86, 0xf2, 0x56, - 0x7f, 0x02, 0x95, 0x51, 0x89, 0x84, 0x76, 0x92, 0xa6, 0xe1, 0x9d, 0xba, 0x10, 0xdd, 0x32, 0xac, - 0xfe, 0x35, 0x03, 0xcb, 0xc3, 0x53, 0x07, 0x3d, 0x85, 0x92, 0xcc, 0x4a, 0xe2, 0x6a, 0x1f, 0xa4, - 0x87, 0xce, 0xfd, 0xb7, 0xd3, 0x55, 0x77, 0xad, 0x05, 0x8d, 0xa4, 0xc7, 0xe8, 0x63, 0xc8, 0xab, - 0x27, 0x10, 0x7d, 0x5f, 0x1e, 0xd1, 0x9e, 0xa8, 0x57, 0x93, 0x6a, 0xaf, 0x61, 0x96, 0x14, 0xb3, - 0xb4, 0xf8, 0xaa, 0x03, 0x6b, 0x63, 0x32, 0xef, 0x9a, 0x9c, 0xf4, 0xd3, 0x41, 0x25, 0x3d, 0xc9, - 0x84, 0xbe, 0x00, 0x94, 0xa6, 0xeb, 0xd5, 0x5d, 0x55, 0x4a, 0xb1, 0x34, 0x45, 0x64, 0xc1, 0xa8, - 0xdc, 0xb9, 0x9e, 0x05, 0xa6, 0x57, 0x59, 0x75, 0x3a, 0x3e, 0x34, 0x66, 0x73, 0x25, 0xc3, 0xfc, - 0x43, 0x06, 0x90, 0x3c, 0x3c, 0xfb, 0x2f, 0x8c, 0x0b, 0x90, 0x4d, 0x9f, 0x06, 0xb2, 0x54, 0xb6, - 0xf3, 0xec, 0xc2, 0x3f, 0x09, 0x3d, 0x75, 0x29, 0xb2, 0xf4, 0x48, 0x94, 0xc7, 0x53, 0xcc, 0x6c, - 0x75, 0x65, 0x96, 0xf5, 0x73, 0xd6, 0x9a, 0x3b, 0xc5, 0x4c, 0xdd, 0xe6, 0xfa, 0x1f, 0x1a, 0x8c, - 0x4b, 0x0f, 0x0d, 0xef, 0x41, 0x19, 0xf3, 0xd0, 0xa7, 0x8e, 0x1d, 0x13, 0x16, 0x7a, 0x1d, 0xe1, - 0x78, 0x79, 0x34, 0x95, 0xad, 0x92, 0x62, 0x58, 0x29, 0xdd, 0xfc, 0x8d, 0x01, 0xff, 0x97, 0x16, - 0x96, 0x61, 0x57, 0xdc, 0xcb, 0x16, 0xbf, 0xb9, 0xfa, 0x2f, 0x43, 0x5e, 0x54, 0x64, 0x12, 0x4b, - 0xbb, 0xe7, 0x2c, 0x3d, 0x1a, 0x6f, 0xf4, 0x3e, 0xe4, 0x19, 0xc7, 0xbc, 0xa3, 0x7a, 0xa6, 0x85, - 0x49, 0x42, 0xbf, 0xad, 0x55, 0x1e, 0x49, 0x39, 0x4b, 0xcb, 0xa3, 0x1f, 0xc0, 0x9a, 0xee, 0xbf, - 0x6c, 0x27, 0x0c, 0xce, 0x49, 0xcc, 0x44, 0x3b, 0x9f, 0x5e, 0xb1, 0xf3, 0xd2, 0x11, 0x2b, 0x7a, - 0xca, 0x76, 0x3a, 0x23, 0x79, 0x44, 0x18, 0xee, 0xbe, 0x99, 0xe1, 0xee, 0x43, 0xf7, 0xa0, 0x9c, - 0x34, 0x20, 0xa2, 0xfa, 0xdb, 0xe2, 0x4b, 0xb6, 0xd2, 0x45, 0xeb, 0x46, 0xc2, 0x68, 0x90, 0xb8, - 0x49, 0x9d, 0x33, 0xd1, 0x77, 0x33, 0x4e, 0x22, 0x5b, 0x5c, 0xbf, 0xbb, 0x2d, 0xe2, 0x9c, 0xea, - 0xbb, 0x05, 0x47, 0x5c, 0xd2, 0xd3, 0x06, 0xf1, 0xdb, 0xb0, 0xa0, 0x7a, 0x2e, 0xca, 0x2f, 0x6c, - 0x4e, 0x49, 0x5c, 0x01, 0x09, 0x5b, 0x4c, 0xa9, 0x4d, 0x4a, 0x62, 0xf4, 0x44, 0x5c, 0xaa, 0xa4, - 0x53, 0xe5, 0x03, 0x5a, 0x41, 0x3a, 0x6f, 0x82, 0xbe, 0xe6, 0x52, 0xcc, 0xe5, 0x2b, 0x1a, 0xf8, - 0xe9, 0xb7, 0xf9, 0x2a, 0x03, 0xab, 0x8f, 0x7a, 0x35, 0x1d, 0x47, 0x8c, 0xc4, 0x7c, 0x54, 0x56, - 0x20, 0x30, 0x02, 0xec, 0x13, 0x9d, 0xc5, 0xf2, 0x5b, 0xac, 0x97, 0x06, 0x94, 0x53, 0xec, 0x89, - 0x3c, 0x6e, 0xd3, 0x40, 0x3e, 0xb1, 0xa8, 0x5e, 0xb0, 0xa4, 0x39, 0x07, 0x92, 0xd1, 0x88, 0x7c, - 0xf4, 0x11, 0x54, 0x7c, 0x4c, 0x03, 0x4e, 0x02, 0x1c, 0x38, 0xc4, 0x6e, 0xc5, 0xd8, 0x91, 0xf7, - 0x30, 0x21, 0xa3, 0x92, 0x65, 0xb9, 0x87, 0xbf, 0xa7, 0xd9, 0x4a, 0x72, 0x59, 0xba, 0x34, 0xe9, - 0x7d, 0xec, 0x20, 0x54, 0x67, 0x8d, 0x6a, 0xbf, 0x6b, 0xd9, 0x4a, 0xc6, 0x5a, 0x14, 0x33, 0x92, - 0x3e, 0xe6, 0x50, 0xf3, 0xcd, 0xdf, 0x65, 0x61, 0x49, 0x35, 0x8a, 0x49, 0x2e, 0x25, 0xeb, 0xbb, - 0x9c, 0xe5, 0x99, 0x81, 0x2c, 0xef, 0x26, 0x6c, 0xf6, 0x9b, 0x4d, 0xd8, 0xdc, 0x9b, 0x12, 0x76, - 0x68, 0x0e, 0x1a, 0x6f, 0x93, 0x83, 0xd3, 0xc3, 0x73, 0xd0, 0xfc, 0x73, 0x06, 0x96, 0x95, 0x7f, - 0xd2, 0x74, 0x19, 0x73, 0x90, 0xe9, 0x4d, 0x9f, 0x1d, 0xbd, 0xe9, 0x73, 0x93, 0x9c, 0x54, 0xc6, - 0x88, 0xad, 0x36, 0xb8, 0x21, 0xa6, 0x87, 0x6c, 0x08, 0x93, 0xc1, 0x52, 0x33, 0xc6, 0x2e, 0x0d, - 0xda, 0x16, 0x79, 0x8e, 0x63, 0x97, 0x75, 0xef, 0x00, 0x37, 0xb8, 0x62, 0xd8, 0xb1, 0xe2, 0xe8, - 0x97, 0xeb, 0xcd, 0xb1, 0xdd, 0x88, 0x7e, 0x9a, 0xea, 0xc3, 0xb4, 0x16, 0x78, 0x9f, 0x0a, 0xf3, - 0xb7, 0x19, 0x58, 0x1c, 0x36, 0x11, 0x2d, 0xc2, 0x74, 0xf8, 0x3c, 0x20, 0xc9, 0xeb, 0xa3, 0x1a, - 0xa0, 0x33, 0x98, 0x77, 0x49, 0x10, 0xfa, 0xc9, 0x85, 0x32, 0x7b, 0xcd, 0xaf, 0xf7, 0x05, 0x89, - 0xae, 0xee, 0xa6, 0x35, 0xeb, 0xcb, 0x57, 0xeb, 0x99, 0xaf, 0x5e, 0xad, 0x67, 0xfe, 0xf3, 0x6a, - 0x3d, 0xf3, 0xab, 0xd7, 0xeb, 0x53, 0x5f, 0xbd, 0x5e, 0x9f, 0xfa, 0xd7, 0xeb, 0xf5, 0xa9, 0xa7, - 0x1f, 0x4d, 0xae, 0xa8, 0xff, 0x1f, 0xa0, 0x93, 0xbc, 0x64, 0x7c, 0xe7, 0x7f, 0x01, 0x00, 0x00, - 0xff, 0xff, 0xd1, 0x69, 0xc7, 0x1d, 0x27, 0x1a, 0x00, 0x00, + // 2166 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x59, 0xbd, 0x6f, 0x1b, 0xc9, + 0x15, 0x17, 0x3f, 0x44, 0x49, 0x8f, 0xa2, 0x4c, 0x8e, 0x29, 0x99, 0x92, 0x12, 0xd9, 0x59, 0x20, + 0x80, 0xe1, 0xbb, 0xa3, 0x2c, 0xe7, 0x2e, 0x38, 0xa4, 0x08, 0x22, 0xea, 0xe3, 0x44, 0x43, 0x92, + 0x79, 0x2b, 0xca, 0x77, 0xe7, 0x04, 0xb7, 0x19, 0xed, 0x0e, 0xa9, 0x81, 0xf6, 0xcb, 0x3b, 0x43, + 0xd9, 0x32, 0x90, 0x3a, 0x29, 0x02, 0x24, 0x40, 0x90, 0x22, 0x45, 0x8a, 0x14, 0x69, 0x02, 0xa4, + 0x08, 0x90, 0x36, 0x55, 0x9a, 0xeb, 0x62, 0xa4, 0x49, 0x90, 0xe2, 0x10, 0xd8, 0x45, 0xfe, 0x8d, + 0x60, 0x3e, 0x76, 0x49, 0x8a, 0x1f, 0xa6, 0x2d, 0x5d, 0x25, 0xee, 0xfb, 0xcd, 0xfb, 0xbd, 0x37, + 0xef, 0xbd, 0x99, 0x79, 0x33, 0x82, 0xbb, 0xce, 0x85, 0xf3, 0x3c, 0x8c, 0x02, 0x1e, 0xd8, 0x81, + 0xbb, 0x4e, 0x7d, 0x87, 0x3c, 0x27, 0xd1, 0x3a, 0x39, 0x27, 0x3e, 0x67, 0xfa, 0x4f, 0x55, 0xc2, + 0x68, 0xb5, 0x77, 0x64, 0x55, 0x8f, 0xac, 0xaa, 0x21, 0x2b, 0xcb, 0x76, 0xc0, 0xbc, 0x80, 0x59, + 0x12, 0x5f, 0x57, 0x1f, 0x4a, 0x6f, 0xa5, 0xdc, 0x0e, 0xda, 0x81, 0x92, 0x8b, 0x5f, 0x5a, 0x7a, + 0x7f, 0xa8, 0x5d, 0x76, 0x8a, 0x23, 0xe2, 0xac, 0x47, 0xc4, 0x0b, 0xce, 0xb1, 0x6b, 0x45, 0x04, + 0xb3, 0xc0, 0xd7, 0x1a, 0xef, 0x0d, 0xd5, 0x48, 0x04, 0xe7, 0x1b, 0xeb, 0xb6, 0x1b, 0x9c, 0x8c, + 0xa5, 0xef, 0x1d, 0x1c, 0x92, 0x28, 0x24, 0xbc, 0x83, 0x5d, 0xad, 0xb1, 0xf1, 0x46, 0x0d, 0xd6, + 0x39, 0xc1, 0xb6, 0x1d, 0x74, 0x7c, 0xae, 0x54, 0x8c, 0x7f, 0xa4, 0xe0, 0xc6, 0x6e, 0xc7, 0x77, + 0xa8, 0xdf, 0x3e, 0x0e, 0x1d, 0xcc, 0xc9, 0xe3, 0x0d, 0xf4, 0x1d, 0x98, 0x4f, 0x98, 0x2d, 0xea, + 0x54, 0x52, 0x77, 0x52, 0x77, 0x0b, 0x66, 0x3e, 0x91, 0xd5, 0x1d, 0x74, 0x0f, 0x4a, 0x2d, 0xa5, + 0x65, 0x9d, 0x63, 0xb7, 0x43, 0xac, 0x30, 0xf4, 0x2a, 0xe9, 0x3b, 0xa9, 0xbb, 0xd3, 0xe6, 0x0d, + 0x0d, 0x3c, 0x16, 0xf2, 0x46, 0xe8, 0x21, 0x0f, 0x0a, 0xf1, 0x58, 0xe9, 0x52, 0x25, 0x73, 0x27, + 0x75, 0x77, 0xbe, 0xb6, 0xf7, 0xd5, 0xd7, 0xb7, 0xa7, 0xfe, 0xf3, 0xf5, 0xed, 0x1f, 0xb5, 0x29, + 0x3f, 0xed, 0x9c, 0x54, 0xed, 0xc0, 0x5b, 0xef, 0xf3, 0xff, 0xfc, 0xc3, 0x0f, 0xec, 0x53, 0x4c, + 0xfd, 0xee, 0x04, 0x1c, 0x7e, 0x11, 0x12, 0x56, 0x3d, 0x22, 0x11, 0xc5, 0x2e, 0x7d, 0x81, 0x4f, + 0x5c, 0x52, 0xf7, 0xb9, 0x39, 0xaf, 0xe9, 0xeb, 0x82, 0xdd, 0xf8, 0x4d, 0x1a, 0x16, 0xf4, 0x8c, + 0x76, 0x44, 0x62, 0x1f, 0x6f, 0xa0, 0x7d, 0x98, 0xe9, 0xc8, 0xc9, 0xb1, 0x4a, 0xea, 0x4e, 0xe6, + 0x6e, 0xfe, 0xc1, 0xfb, 0xd5, 0x31, 0x85, 0x50, 0xbd, 0x14, 0x8f, 0x5a, 0x56, 0x78, 0x6a, 0xc6, + 0x14, 0x68, 0x1b, 0xb2, 0xc2, 0x0f, 0x39, 0xdd, 0x85, 0x07, 0xf7, 0x27, 0xa1, 0xd2, 0x8e, 0x54, + 0x9b, 0x17, 0x21, 0x31, 0xa5, 0xb6, 0xe1, 0x41, 0x56, 0x7c, 0xa1, 0x32, 0x14, 0x9b, 0x5f, 0x34, + 0x76, 0xac, 0xe3, 0xc3, 0xa3, 0xc6, 0xce, 0x56, 0x7d, 0xb7, 0xbe, 0xb3, 0x5d, 0x9c, 0x42, 0xb7, + 0xe0, 0xa6, 0x94, 0x36, 0xcc, 0x9d, 0x83, 0xfa, 0xf1, 0x81, 0x75, 0xb4, 0x79, 0xd0, 0xd8, 0xdf, + 0x29, 0xa6, 0xd0, 0x6d, 0x58, 0x95, 0xc0, 0xee, 0xf1, 0xe1, 0x76, 0xfd, 0xf0, 0x13, 0xcb, 0xdc, + 0x6c, 0xee, 0x58, 0x9b, 0x87, 0xdb, 0x56, 0xfd, 0x70, 0x7b, 0xe7, 0xf3, 0x62, 0x1a, 0x2d, 0x42, + 0xa9, 0x4f, 0xf3, 0xf1, 0xa3, 0xe6, 0x4e, 0x31, 0x63, 0xfc, 0x3d, 0x0d, 0x85, 0x03, 0x1c, 0x9d, + 0x11, 0x1e, 0x07, 0x65, 0x15, 0xe6, 0x3c, 0x29, 0xe8, 0xa6, 0x78, 0x56, 0x09, 0xea, 0x0e, 0x7a, + 0x02, 0xf3, 0x61, 0x44, 0x6d, 0x62, 0xa9, 0x49, 0xcb, 0xb9, 0xe6, 0x1f, 0x7c, 0x34, 0x76, 0xae, + 0x8a, 0xbe, 0x21, 0xd4, 0x54, 0xe8, 0xb4, 0xa5, 0xbd, 0x29, 0x33, 0x1f, 0x76, 0xa5, 0xe8, 0x33, + 0x28, 0x68, 0xc3, 0x76, 0x44, 0x04, 0x79, 0x46, 0x92, 0xdf, 0x9f, 0x80, 0x7c, 0x4b, 0x2a, 0x74, + 0x79, 0xe7, 0xbd, 0x1e, 0x71, 0x0f, 0xb1, 0x17, 0x38, 0xb4, 0x75, 0x51, 0xc9, 0x4e, 0x4c, 0x7c, + 0x20, 0x15, 0x06, 0x88, 0x95, 0xb8, 0x36, 0x03, 0xd3, 0x72, 0xb4, 0xf1, 0x10, 0x2a, 0xa3, 0x66, + 0x89, 0xaa, 0x70, 0x53, 0x85, 0xec, 0x19, 0xe5, 0xa7, 0x16, 0x79, 0x1e, 0x06, 0x3e, 0xf1, 0xb9, + 0x8c, 0x6c, 0xd6, 0x2c, 0x49, 0xe8, 0x33, 0xca, 0x4f, 0x77, 0x34, 0x60, 0x7c, 0x0e, 0x25, 0xc5, + 0x55, 0xc3, 0x2c, 0x21, 0x41, 0x90, 0x0d, 0x31, 0x8d, 0xa4, 0xd6, 0x9c, 0x29, 0x7f, 0xa3, 0x75, + 0x28, 0x7b, 0xd4, 0xb7, 0x14, 0xb9, 0x7d, 0x8a, 0xfd, 0x76, 0x77, 0xb9, 0x15, 0xcc, 0x92, 0x47, + 0x7d, 0xe9, 0xcd, 0x96, 0x44, 0x1a, 0xa1, 0x67, 0x74, 0xe0, 0xe6, 0x90, 0x70, 0xa1, 0x1a, 0x64, + 0x4f, 0x30, 0x23, 0x92, 0x3b, 0xff, 0xa0, 0x3a, 0x41, 0x54, 0x7a, 0x3c, 0x33, 0xa5, 0x2e, 0x5a, + 0x81, 0xd9, 0x64, 0x66, 0xc2, 0x7e, 0xc9, 0x4c, 0xbe, 0x8d, 0x2f, 0x62, 0xb3, 0x7d, 0xc1, 0xbc, + 0x0e, 0xb3, 0xc6, 0x9f, 0x53, 0x50, 0x38, 0x0a, 0x3a, 0x91, 0x4d, 0x1e, 0xb5, 0xc4, 0x92, 0x62, + 0xe8, 0x27, 0x50, 0xe8, 0xee, 0x65, 0x71, 0x05, 0x8f, 0xac, 0xd0, 0x44, 0x70, 0xbe, 0x51, 0xad, + 0x2b, 0xd9, 0x51, 0xa2, 0x5d, 0x77, 0x44, 0xc2, 0x59, 0xcf, 0x37, 0xfa, 0x10, 0x66, 0xb0, 0xe3, + 0x44, 0x84, 0x31, 0x39, 0xcb, 0xb9, 0x5a, 0xe5, 0x9f, 0x7f, 0xfd, 0xa0, 0xac, 0x8f, 0x84, 0x4d, + 0x85, 0x1c, 0xf1, 0x88, 0xfa, 0xed, 0xbd, 0x29, 0x33, 0x1e, 0x5a, 0x9b, 0x85, 0x1c, 0x93, 0x4e, + 0x1a, 0x7f, 0xca, 0xc0, 0x8d, 0x66, 0x84, 0x7d, 0xd6, 0x22, 0x51, 0x1c, 0x87, 0x36, 0x94, 0x19, + 0xf1, 0x1d, 0x12, 0x59, 0xd7, 0xe7, 0xb8, 0x89, 0x14, 0x65, 0xaf, 0x0c, 0x79, 0x70, 0x2b, 0x22, + 0x36, 0x0d, 0x29, 0xf1, 0xf9, 0x25, 0x5b, 0xe9, 0xab, 0xd8, 0x5a, 0x4c, 0x58, 0xfb, 0xcc, 0x2d, + 0xc3, 0x2c, 0x66, 0x4c, 0x6d, 0x23, 0x19, 0x59, 0x92, 0x33, 0xf2, 0xbb, 0xee, 0xa0, 0x25, 0xc8, + 0x61, 0x4f, 0x0c, 0x93, 0x2b, 0x31, 0x6b, 0xea, 0x2f, 0x54, 0x83, 0x9c, 0xf2, 0xbb, 0x32, 0x2d, + 0x1d, 0xba, 0x37, 0xb6, 0x28, 0xfa, 0x12, 0x6f, 0x6a, 0x4d, 0xb4, 0x07, 0x73, 0x89, 0x3f, 0x95, + 0xdc, 0x5b, 0xd3, 0x74, 0x95, 0x8d, 0x7f, 0x65, 0xa0, 0xf8, 0x28, 0x72, 0x48, 0xb4, 0x4b, 0x5d, + 0x37, 0xce, 0xd6, 0x31, 0xe4, 0x3d, 0x7c, 0x46, 0x22, 0x2b, 0x10, 0xc8, 0xf8, 0xe2, 0x1d, 0x12, + 0x38, 0xc9, 0xa7, 0x0f, 0x0e, 0x90, 0x44, 0x52, 0x82, 0x76, 0x61, 0x5a, 0x11, 0xa6, 0xdf, 0x85, + 0x70, 0x6f, 0xca, 0x54, 0xea, 0xe8, 0x4b, 0x28, 0xb9, 0xf4, 0x69, 0x87, 0x3a, 0x98, 0xd3, 0xc0, + 0xd7, 0x4e, 0xaa, 0xed, 0x6e, 0x7d, 0x6c, 0x14, 0xf6, 0xbb, 0x5a, 0x92, 0x52, 0xee, 0x76, 0x45, + 0xf7, 0x92, 0x14, 0xdd, 0x86, 0x7c, 0x8b, 0xba, 0xae, 0xa5, 0xd3, 0x97, 0x91, 0xe9, 0x03, 0x21, + 0xda, 0x54, 0x29, 0x94, 0xa7, 0x87, 0x88, 0x4f, 0x8b, 0x10, 0x99, 0x45, 0x24, 0x4e, 0x8f, 0x33, + 0x12, 0xed, 0x12, 0x22, 0x40, 0x9e, 0x80, 0x39, 0x05, 0xf2, 0x18, 0x7c, 0x1f, 0x10, 0x0f, 0x38, + 0x76, 0x2d, 0xc1, 0x46, 0x1c, 0x4b, 0x6a, 0x55, 0x66, 0xa4, 0x85, 0xa2, 0x44, 0x76, 0x25, 0x70, + 0x20, 0xe4, 0x03, 0xa3, 0x25, 0x4d, 0x65, 0x76, 0x60, 0x74, 0x53, 0xc8, 0x6b, 0x05, 0xc8, 0xf3, + 0x6e, 0xd6, 0x8c, 0x5f, 0x66, 0xe0, 0xe6, 0x36, 0x71, 0xc9, 0x39, 0x89, 0x70, 0xbb, 0xa7, 0x1f, + 0xf8, 0x31, 0x40, 0x3c, 0x63, 0x72, 0xb5, 0x05, 0x18, 0xa7, 0xb8, 0x4b, 0x27, 0xc8, 0x83, 0x56, + 0x8b, 0x11, 0xce, 0xa9, 0xdf, 0xbe, 0xd2, 0x8a, 0x8b, 0xc9, 0xbb, 0x74, 0x03, 0xad, 0x59, 0x66, + 0xb0, 0x35, 0xbb, 0x94, 0xba, 0xec, 0x40, 0xea, 0xee, 0x43, 0x59, 0x85, 0xf4, 0x69, 0x27, 0xe0, + 0xc4, 0x7a, 0xda, 0xc1, 0x3e, 0xef, 0x78, 0x4c, 0x66, 0x31, 0x6b, 0xaa, 0x70, 0x7f, 0x2a, 0xa0, + 0x4f, 0x35, 0x82, 0x16, 0x21, 0x47, 0x99, 0x75, 0xd2, 0xb9, 0x90, 0xc9, 0x9c, 0x35, 0xa7, 0x29, + 0xab, 0x75, 0x2e, 0xc4, 0x89, 0x47, 0x99, 0xd5, 0xa2, 0x3e, 0x76, 0x2d, 0xe1, 0xa0, 0x4b, 0x3c, + 0xb1, 0x18, 0x67, 0xe4, 0x98, 0x12, 0x65, 0xbb, 0x02, 0x39, 0x4a, 0x00, 0xe3, 0x17, 0x69, 0x40, + 0x83, 0xf5, 0xf7, 0xcd, 0x66, 0xe3, 0x0e, 0xcc, 0x8b, 0x96, 0xda, 0x12, 0x27, 0x69, 0xbc, 0x03, + 0x16, 0x4c, 0x10, 0xb2, 0x06, 0xa6, 0x51, 0xdd, 0x99, 0x24, 0xa4, 0xdf, 0x06, 0x50, 0x11, 0x63, + 0xf4, 0x05, 0xd1, 0x11, 0x9d, 0x93, 0x92, 0x23, 0xfa, 0x82, 0xf4, 0x84, 0x67, 0xba, 0x37, 0x3c, + 0x2b, 0x30, 0xcb, 0x3a, 0x27, 0x9c, 0xda, 0x67, 0x4c, 0xc6, 0x2d, 0x6b, 0x26, 0xdf, 0xc6, 0xff, + 0xd2, 0x70, 0xab, 0xeb, 0x79, 0x7f, 0x23, 0xf1, 0xe4, 0x3a, 0x8f, 0xb6, 0x4b, 0x07, 0xdb, 0x0b, + 0x58, 0x55, 0x1d, 0x9d, 0x63, 0x75, 0x27, 0x1d, 0x06, 0x8c, 0x8a, 0x84, 0xb0, 0x4a, 0x46, 0x76, + 0xc7, 0x3f, 0x98, 0xd8, 0x52, 0x23, 0xe6, 0x68, 0x68, 0x0a, 0x73, 0x59, 0xd3, 0x0f, 0x20, 0x0c, + 0xf9, 0x70, 0x2b, 0xb6, 0xad, 0x0e, 0x8c, 0xae, 0xdd, 0xac, 0xb4, 0xfb, 0xfd, 0x89, 0xed, 0x6e, + 0x0a, 0xfd, 0xc4, 0xe6, 0xa2, 0xa6, 0xed, 0x93, 0xb2, 0x87, 0xd9, 0xd9, 0x74, 0x31, 0x63, 0xfc, + 0x01, 0xa0, 0x7c, 0xc4, 0x31, 0x27, 0xad, 0x8e, 0x2b, 0x2b, 0x2e, 0x0e, 0xb3, 0x07, 0x79, 0xb9, + 0x4b, 0x58, 0xa1, 0x8b, 0xed, 0xb8, 0x3d, 0x79, 0x38, 0xfe, 0x08, 0x19, 0xc2, 0xd3, 0x2f, 0x6c, + 0x08, 0x2e, 0x2f, 0xee, 0x22, 0x21, 0x48, 0x64, 0x28, 0x80, 0x82, 0x32, 0xa7, 0x2f, 0x86, 0x7a, + 0xb7, 0xde, 0xbb, 0xa2, 0x41, 0x53, 0xb1, 0xa9, 0xa6, 0x35, 0xe8, 0x91, 0xa0, 0x5f, 0xa5, 0x60, + 0xd5, 0x0e, 0x7c, 0x47, 0x46, 0x03, 0xbb, 0x56, 0xcf, 0x64, 0xe5, 0x32, 0x55, 0x47, 0xef, 0xc1, + 0xdb, 0xdb, 0xdf, 0xea, 0x92, 0x0e, 0x99, 0xf3, 0xb2, 0x3d, 0x0a, 0x1e, 0xe1, 0x11, 0x8f, 0x68, + 0xbb, 0x4d, 0x22, 0xe2, 0xe8, 0x53, 0xfc, 0x1a, 0x3c, 0x6a, 0xc6, 0x94, 0xc3, 0x3d, 0x4a, 0x60, + 0xf4, 0xf3, 0x14, 0x2c, 0xbb, 0x81, 0xdf, 0xb6, 0x38, 0x89, 0xbc, 0x81, 0x08, 0xcd, 0xbc, 0x6b, + 0x49, 0xec, 0x07, 0x7e, 0xbb, 0x49, 0x22, 0x6f, 0x48, 0x78, 0x96, 0xdc, 0xa1, 0xd8, 0xca, 0x4f, + 0xa1, 0x32, 0xaa, 0x90, 0xd0, 0x76, 0xdc, 0x34, 0xbc, 0x53, 0x17, 0xa2, 0x5b, 0x86, 0x95, 0xbf, + 0xa5, 0x60, 0x69, 0x78, 0xe9, 0xa0, 0x27, 0x50, 0x94, 0x55, 0x49, 0x1c, 0x1d, 0x83, 0x64, 0xd3, + 0xb9, 0xff, 0x76, 0xb6, 0xea, 0x8e, 0xb9, 0xa0, 0x99, 0xf4, 0x37, 0xfa, 0x04, 0x72, 0xea, 0x09, + 0x44, 0xdf, 0x97, 0x47, 0xb4, 0x27, 0xea, 0xd5, 0xa4, 0xda, 0xeb, 0x98, 0x29, 0xd5, 0x4c, 0xad, + 0xbe, 0x62, 0xc3, 0xea, 0x98, 0xca, 0xbb, 0xa6, 0x20, 0xfd, 0x6c, 0xd0, 0x48, 0x4f, 0x31, 0xa1, + 0x2f, 0x01, 0x25, 0xe5, 0x7a, 0xf5, 0x50, 0x15, 0x13, 0x2e, 0x2d, 0x11, 0x55, 0x30, 0xaa, 0x76, + 0xae, 0x67, 0x82, 0xc9, 0x55, 0x56, 0xed, 0x8e, 0x0f, 0xb3, 0xb3, 0x99, 0x62, 0xd6, 0xf8, 0x63, + 0x0a, 0x90, 0xdc, 0x3c, 0xfb, 0x2f, 0x8c, 0x0b, 0x90, 0x4e, 0x9e, 0x06, 0xd2, 0x54, 0xb6, 0xf3, + 0xec, 0xc2, 0x3b, 0x09, 0x5c, 0x75, 0x29, 0x32, 0xf5, 0x97, 0x38, 0x1e, 0x4f, 0x31, 0xb3, 0xd4, + 0x95, 0x59, 0x9e, 0x9f, 0xb3, 0xe6, 0xdc, 0x29, 0x66, 0xea, 0x36, 0xd7, 0xff, 0xd0, 0x90, 0xbd, + 0xf4, 0xd0, 0xf0, 0x1e, 0x94, 0x30, 0x0f, 0x3c, 0x6a, 0x5b, 0x11, 0x61, 0x81, 0xdb, 0x11, 0x81, + 0x97, 0x5b, 0x53, 0xc9, 0x2c, 0x2a, 0xc0, 0x4c, 0xe4, 0xc6, 0x6f, 0xb3, 0xf0, 0xad, 0xe4, 0x60, + 0x19, 0x76, 0xc5, 0xbd, 0xec, 0xf1, 0x9b, 0x4f, 0xff, 0x25, 0xc8, 0x89, 0x13, 0x99, 0x44, 0xd2, + 0xef, 0x39, 0x53, 0x7f, 0x8d, 0x77, 0x7a, 0x0f, 0x72, 0x8c, 0x63, 0xde, 0x51, 0x3d, 0xd3, 0xc2, + 0x24, 0xa9, 0xdf, 0xd2, 0x26, 0x8f, 0xa4, 0x9e, 0xa9, 0xf5, 0xd1, 0x0f, 0x61, 0x55, 0xf7, 0x5f, + 0x96, 0x1d, 0xf8, 0xe7, 0x24, 0x62, 0xa2, 0x9d, 0x4f, 0xae, 0xd8, 0x39, 0x19, 0x88, 0x65, 0x3d, + 0x64, 0x2b, 0x19, 0x11, 0x3f, 0x22, 0x0c, 0x0f, 0xdf, 0xcc, 0xf0, 0xf0, 0xa1, 0x7b, 0x50, 0x8a, + 0x1b, 0x10, 0x71, 0xfa, 0x5b, 0xe2, 0x97, 0x6c, 0xa5, 0x0b, 0xe6, 0x8d, 0x18, 0x68, 0x90, 0xa8, + 0x49, 0xed, 0x33, 0xd1, 0x77, 0x33, 0x4e, 0x42, 0x4b, 0x5c, 0xbf, 0xbb, 0x2d, 0xe2, 0x9c, 0xea, + 0xbb, 0x05, 0x22, 0x2e, 0xe9, 0x49, 0x83, 0xf8, 0x5d, 0x58, 0x50, 0x3d, 0x17, 0xe5, 0x17, 0x16, + 0xa7, 0x24, 0xaa, 0x80, 0xa4, 0x2d, 0x24, 0xd2, 0x26, 0x25, 0x11, 0x7a, 0x2c, 0x2e, 0x55, 0x32, + 0xa8, 0xf2, 0x01, 0x2d, 0x2f, 0x83, 0x37, 0x41, 0x5f, 0x73, 0x29, 0xe7, 0xf2, 0x15, 0x0d, 0xbc, + 0xe4, 0xb7, 0xf1, 0x32, 0x0d, 0x2b, 0xfb, 0xbd, 0x96, 0x8e, 0x43, 0x46, 0x22, 0x3e, 0xaa, 0x2a, + 0x10, 0x64, 0x7d, 0xec, 0x11, 0x5d, 0xc5, 0xf2, 0xb7, 0x98, 0x2f, 0xf5, 0x29, 0xa7, 0xd8, 0x15, + 0x75, 0xdc, 0xa6, 0xbe, 0x7c, 0x62, 0x51, 0xbd, 0x60, 0x51, 0x23, 0x07, 0x12, 0x68, 0x84, 0x1e, + 0xfa, 0x18, 0x2a, 0x1e, 0xa6, 0x3e, 0x27, 0x3e, 0xf6, 0x6d, 0x62, 0xb5, 0x22, 0x6c, 0xcb, 0x7b, + 0x98, 0xd0, 0x51, 0xc5, 0xb2, 0xd4, 0x83, 0xef, 0x6a, 0x58, 0x69, 0x2e, 0xc9, 0x90, 0xc6, 0xbd, + 0x8f, 0xe5, 0x07, 0x6a, 0xaf, 0x51, 0xed, 0x77, 0x2d, 0x5d, 0x49, 0x99, 0x65, 0x31, 0x22, 0xee, + 0x63, 0x0e, 0x35, 0x8e, 0x3e, 0x82, 0x5b, 0x41, 0x48, 0x7c, 0x4b, 0xd0, 0x46, 0x84, 0x71, 0xcb, + 0x0d, 0x9e, 0x91, 0xc8, 0xb2, 0x71, 0xa8, 0xbb, 0xcb, 0xb2, 0x80, 0xeb, 0x1a, 0xdd, 0x17, 0xe0, + 0x16, 0x0e, 0x07, 0xd5, 0x3a, 0x61, 0xa8, 0xd5, 0x66, 0x06, 0xd5, 0x8e, 0x05, 0xb8, 0x85, 0x43, + 0xe3, 0xf7, 0x69, 0x58, 0x54, 0x6d, 0x69, 0x5c, 0xb9, 0x71, 0x34, 0x2f, 0xaf, 0xa9, 0xd4, 0xc0, + 0x9a, 0xea, 0x2e, 0x8f, 0xf4, 0x37, 0xbb, 0x3c, 0x32, 0x6f, 0x5a, 0x1e, 0x43, 0x2b, 0x3e, 0xfb, + 0x36, 0x15, 0x3f, 0x3d, 0xbc, 0xe2, 0x8d, 0xbf, 0xa4, 0x60, 0x49, 0xc5, 0x27, 0x29, 0xce, 0x31, + 0xdb, 0xa6, 0xde, 0x62, 0xd2, 0xa3, 0xb7, 0x98, 0xcc, 0x24, 0xfb, 0x62, 0x76, 0xc4, 0xc2, 0x1e, + 0x5c, 0x7e, 0xd3, 0x43, 0x96, 0x9f, 0xc1, 0x60, 0xb1, 0x19, 0x61, 0x87, 0xfa, 0x6d, 0x93, 0x3c, + 0xc3, 0x91, 0xc3, 0xba, 0x37, 0x8e, 0x1b, 0x5c, 0x01, 0x56, 0xa4, 0x10, 0xfd, 0x4e, 0xbe, 0x31, + 0xb6, 0xf7, 0xd1, 0x0f, 0x61, 0x7d, 0x9c, 0xe6, 0x02, 0xef, 0x33, 0x61, 0xfc, 0x2e, 0x05, 0xe5, + 0x61, 0x03, 0x51, 0x19, 0xa6, 0x83, 0x67, 0x3e, 0x89, 0xdf, 0x3a, 0xd5, 0x07, 0x3a, 0x83, 0x79, + 0x87, 0xf8, 0x81, 0x17, 0x5f, 0x5f, 0xd3, 0xd7, 0xfc, 0xbf, 0x82, 0xbc, 0x64, 0x57, 0x37, 0xe1, + 0x9a, 0xf9, 0xd5, 0xab, 0xb5, 0xd4, 0xcb, 0x57, 0x6b, 0xa9, 0xff, 0xbe, 0x5a, 0x4b, 0xfd, 0xfa, + 0xf5, 0xda, 0xd4, 0xcb, 0xd7, 0x6b, 0x53, 0xff, 0x7e, 0xbd, 0x36, 0xf5, 0xe4, 0xe3, 0xc9, 0x0d, + 0xf5, 0xff, 0xbf, 0xe9, 0x24, 0x27, 0x81, 0xef, 0xfd, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x09, 0x45, + 0x07, 0x31, 0x95, 0x1a, 0x00, 0x00, } func (m *FundingUpdateV1) Marshal() (dAtA []byte, err error) { @@ -3540,6 +3561,16 @@ func (m *LiquidityTierUpsertEventV1) MarshalToSizedBuffer(dAtA []byte) (int, err _ = i var l int _ = l + if m.OpenInterestUpperCap != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.OpenInterestUpperCap)) + i-- + dAtA[i] = 0x38 + } + if m.OpenInterestLowerCap != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.OpenInterestLowerCap)) + i-- + dAtA[i] = 0x30 + } if m.BasePositionNotional != 0 { i = encodeVarintEvents(dAtA, i, uint64(m.BasePositionNotional)) i-- @@ -4326,6 +4357,12 @@ func (m *LiquidityTierUpsertEventV1) Size() (n int) { if m.BasePositionNotional != 0 { n += 1 + sovEvents(uint64(m.BasePositionNotional)) } + if m.OpenInterestLowerCap != 0 { + n += 1 + sovEvents(uint64(m.OpenInterestLowerCap)) + } + if m.OpenInterestUpperCap != 0 { + n += 1 + sovEvents(uint64(m.OpenInterestUpperCap)) + } return n } @@ -7570,6 +7607,44 @@ func (m *LiquidityTierUpsertEventV1) Unmarshal(dAtA []byte) error { break } } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OpenInterestLowerCap", wireType) + } + m.OpenInterestLowerCap = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OpenInterestLowerCap |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OpenInterestUpperCap", wireType) + } + m.OpenInterestUpperCap = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OpenInterestUpperCap |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/protocol/indexer/events/liquidity_tier.go b/protocol/indexer/events/liquidity_tier.go index 14b77c754f..cc3d438105 100644 --- a/protocol/indexer/events/liquidity_tier.go +++ b/protocol/indexer/events/liquidity_tier.go @@ -7,11 +7,15 @@ func NewLiquidityTierUpsertEvent( name string, initialMarginPpm uint32, maintenanceFractionPpm uint32, + openIntersetLowerCap uint64, + openInterestUpperCap uint64, ) *LiquidityTierUpsertEventV1 { return &LiquidityTierUpsertEventV1{ Id: id, Name: name, InitialMarginPpm: initialMarginPpm, MaintenanceFractionPpm: maintenanceFractionPpm, + OpenInterestLowerCap: openIntersetLowerCap, + OpenInterestUpperCap: openInterestUpperCap, } } diff --git a/protocol/indexer/events/liquidity_tier_test.go b/protocol/indexer/events/liquidity_tier_test.go index 64e1718392..fd7552c57a 100644 --- a/protocol/indexer/events/liquidity_tier_test.go +++ b/protocol/indexer/events/liquidity_tier_test.go @@ -12,12 +12,16 @@ func TestNewLiquidityTierUpsertEvent_Success(t *testing.T) { "Large-Cap", 50000, 600000, + 0, + 1000000, ) expectedLiquidityTierUpsertEventProto := &LiquidityTierUpsertEventV1{ Id: 0, Name: "Large-Cap", InitialMarginPpm: 50000, MaintenanceFractionPpm: 600000, + OpenInterestLowerCap: 0, + OpenInterestUpperCap: 1000000, } require.Equal(t, expectedLiquidityTierUpsertEventProto, liquidityTierUpsertEvent) } diff --git a/protocol/lib/ante/internal_msg.go b/protocol/lib/ante/internal_msg.go index 652985b16d..3ca7b1fc77 100644 --- a/protocol/lib/ante/internal_msg.go +++ b/protocol/lib/ante/internal_msg.go @@ -27,6 +27,7 @@ import ( rewards "github.com/dydxprotocol/v4-chain/protocol/x/rewards/types" sending "github.com/dydxprotocol/v4-chain/protocol/x/sending/types" stats "github.com/dydxprotocol/v4-chain/protocol/x/stats/types" + vault "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" vest "github.com/dydxprotocol/v4-chain/protocol/x/vest/types" ) @@ -115,6 +116,9 @@ func IsInternalMsg(msg sdk.Msg) bool { // stats *stats.MsgUpdateParams, + // vault + *vault.MsgUpdateParams, + // vest *vest.MsgDeleteVestEntry, *vest.MsgSetVestEntry, diff --git a/protocol/lib/context.go b/protocol/lib/context.go index f5503d7ea4..608229cd83 100644 --- a/protocol/lib/context.go +++ b/protocol/lib/context.go @@ -11,6 +11,13 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/lib/log" ) +// Custom exec modes +const ( + ExecModeBeginBlock = 100 + ExecModeEndBlock = 101 + ExecModePrepareCheckState = 102 +) + type TxHash string func GetTxHash(tx []byte) TxHash { diff --git a/protocol/lib/metrics/constants.go b/protocol/lib/metrics/constants.go index de87905cdd..48b163bad3 100644 --- a/protocol/lib/metrics/constants.go +++ b/protocol/lib/metrics/constants.go @@ -248,6 +248,8 @@ const ( VaultPlaceOrder = "vault_place_order" VaultType = "vault_type" VaultId = "vault_id" + VaultEquity = "vault_equity" + TotalShares = "total_shares" // Vest. GetVestEntry = "get_vest_entry" diff --git a/protocol/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index 1b2fd17fa4..14000608f4 100644 --- a/protocol/mocks/ClobKeeper.go +++ b/protocol/mocks/ClobKeeper.go @@ -828,6 +828,11 @@ func (_m *ClobKeeper) MaybeGetLiquidationOrder(ctx types.Context, subaccountId s return r0, r1 } +// MigratePruneableOrders provides a mock function with given fields: ctx +func (_m *ClobKeeper) MigratePruneableOrders(ctx types.Context) { + _m.Called(ctx) +} + // MustAddOrderToStatefulOrdersTimeSlice provides a mock function with given fields: ctx, goodTilBlockTime, orderId func (_m *ClobKeeper) MustAddOrderToStatefulOrdersTimeSlice(ctx types.Context, goodTilBlockTime time.Time, orderId clobtypes.OrderId) { _m.Called(ctx, goodTilBlockTime, orderId) @@ -1130,9 +1135,9 @@ func (_m *ClobKeeper) RemoveOrderFillAmount(ctx types.Context, orderId clobtypes _m.Called(ctx, orderId) } -// SendOrderbookUpdates provides a mock function with given fields: offchainUpdates, snapshot -func (_m *ClobKeeper) SendOrderbookUpdates(offchainUpdates *clobtypes.OffchainUpdates, snapshot bool) { - _m.Called(offchainUpdates, snapshot) +// SendOrderbookUpdates provides a mock function with given fields: ctx, offchainUpdates, snapshot +func (_m *ClobKeeper) SendOrderbookUpdates(ctx types.Context, offchainUpdates *clobtypes.OffchainUpdates, snapshot bool) { + _m.Called(ctx, offchainUpdates, snapshot) } // SetLongTermOrderPlacement provides a mock function with given fields: ctx, order, blockHeight diff --git a/protocol/mocks/Makefile b/protocol/mocks/Makefile index 12cd02acb2..5300d51f91 100644 --- a/protocol/mocks/Makefile +++ b/protocol/mocks/Makefile @@ -39,6 +39,7 @@ mock-gen: @go run github.com/vektra/mockery/v2 --name=PerpetualsKeeper --dir=./x/perpetuals/types --recursive --output=./mocks @go run github.com/vektra/mockery/v2 --name=SendingKeeper --dir=./x/sending/types --recursive --output=./mocks @go run github.com/vektra/mockery/v2 --name=SubaccountsKeeper --dir=./x/subaccounts/types --recursive --output=./mocks + @go run github.com/vektra/mockery/v2 --name=VaultKeeper --dir=./x/vault/types --recursive --output=./mocks @go run github.com/vektra/mockery/v2 --name=FileHandler --dir=./daemons/types --recursive --output=./mocks @go run github.com/vektra/mockery/v2 --name=GrpcServer --dir=./daemons/types --recursive --output=./mocks @go run github.com/vektra/mockery/v2 --name=GrpcClient --dir=./daemons/types --recursive --output=./mocks diff --git a/protocol/mocks/MemClobKeeper.go b/protocol/mocks/MemClobKeeper.go index e9b6d3456a..49561ea752 100644 --- a/protocol/mocks/MemClobKeeper.go +++ b/protocol/mocks/MemClobKeeper.go @@ -434,9 +434,9 @@ func (_m *MemClobKeeper) ReplayPlaceOrder(ctx types.Context, msg *clobtypes.MsgP return r0, r1, r2, r3 } -// SendOrderbookUpdates provides a mock function with given fields: offchainUpdates, snapshot -func (_m *MemClobKeeper) SendOrderbookUpdates(offchainUpdates *clobtypes.OffchainUpdates, snapshot bool) { - _m.Called(offchainUpdates, snapshot) +// SendOrderbookUpdates provides a mock function with given fields: ctx, offchainUpdates, snapshot +func (_m *MemClobKeeper) SendOrderbookUpdates(ctx types.Context, offchainUpdates *clobtypes.OffchainUpdates, snapshot bool) { + _m.Called(ctx, offchainUpdates, snapshot) } // SetLongTermOrderPlacement provides a mock function with given fields: ctx, order, blockHeight diff --git a/protocol/mocks/PerpetualsKeeper.go b/protocol/mocks/PerpetualsKeeper.go index 85da4a3b2b..36354b18a4 100644 --- a/protocol/mocks/PerpetualsKeeper.go +++ b/protocol/mocks/PerpetualsKeeper.go @@ -82,6 +82,26 @@ func (_m *PerpetualsKeeper) GetAddPremiumVotes(ctx types.Context) *perpetualstyp return r0 } +// GetAllLiquidityTiers provides a mock function with given fields: ctx +func (_m *PerpetualsKeeper) GetAllLiquidityTiers(ctx types.Context) []perpetualstypes.LiquidityTier { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetAllLiquidityTiers") + } + + var r0 []perpetualstypes.LiquidityTier + if rf, ok := ret.Get(0).(func(types.Context) []perpetualstypes.LiquidityTier); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]perpetualstypes.LiquidityTier) + } + } + + return r0 +} + // GetAllPerpetuals provides a mock function with given fields: ctx func (_m *PerpetualsKeeper) GetAllPerpetuals(ctx types.Context) []perpetualstypes.Perpetual { ret := _m.Called(ctx) @@ -231,6 +251,34 @@ func (_m *PerpetualsKeeper) GetNotionalInBaseQuantums(ctx types.Context, id uint return r0, r1 } +// GetPerpetual provides a mock function with given fields: ctx, id +func (_m *PerpetualsKeeper) GetPerpetual(ctx types.Context, id uint32) (perpetualstypes.Perpetual, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetPerpetual") + } + + var r0 perpetualstypes.Perpetual + var r1 error + if rf, ok := ret.Get(0).(func(types.Context, uint32) (perpetualstypes.Perpetual, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(types.Context, uint32) perpetualstypes.Perpetual); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Get(0).(perpetualstypes.Perpetual) + } + + if rf, ok := ret.Get(1).(func(types.Context, uint32) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // HasAuthority provides a mock function with given fields: authority func (_m *PerpetualsKeeper) HasAuthority(authority string) bool { ret := _m.Called(authority) diff --git a/protocol/mocks/QueryClient.go b/protocol/mocks/QueryClient.go index e04a613c33..81b7f9a05f 100644 --- a/protocol/mocks/QueryClient.go +++ b/protocol/mocks/QueryClient.go @@ -363,6 +363,43 @@ func (_m *QueryClient) ClobPairAll(ctx context.Context, in *clobtypes.QueryAllCl return r0, r1 } +// CollateralPoolAddress provides a mock function with given fields: ctx, in, opts +func (_m *QueryClient) CollateralPoolAddress(ctx context.Context, in *subaccountstypes.QueryCollateralPoolAddressRequest, opts ...grpc.CallOption) (*subaccountstypes.QueryCollateralPoolAddressResponse, 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...) + + if len(ret) == 0 { + panic("no return value specified for CollateralPoolAddress") + } + + var r0 *subaccountstypes.QueryCollateralPoolAddressResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *subaccountstypes.QueryCollateralPoolAddressRequest, ...grpc.CallOption) (*subaccountstypes.QueryCollateralPoolAddressResponse, error)); ok { + return rf(ctx, in, opts...) + } + if rf, ok := ret.Get(0).(func(context.Context, *subaccountstypes.QueryCollateralPoolAddressRequest, ...grpc.CallOption) *subaccountstypes.QueryCollateralPoolAddressResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*subaccountstypes.QueryCollateralPoolAddressResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *subaccountstypes.QueryCollateralPoolAddressRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // DowntimeParams provides a mock function with given fields: ctx, in, opts func (_m *QueryClient) DowntimeParams(ctx context.Context, in *types.QueryDowntimeParamsRequest, opts ...grpc.CallOption) (*types.QueryDowntimeParamsResponse, error) { _va := make([]interface{}, len(opts)) diff --git a/protocol/mocks/VaultKeeper.go b/protocol/mocks/VaultKeeper.go new file mode 100644 index 0000000000..adc3d970c7 --- /dev/null +++ b/protocol/mocks/VaultKeeper.go @@ -0,0 +1,198 @@ +// Code generated by mockery v2.42.0. DO NOT EDIT. + +package mocks + +import ( + big "math/big" + + clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" + + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" +) + +// VaultKeeper is an autogenerated mock type for the VaultKeeper type +type VaultKeeper struct { + mock.Mock +} + +// GetTotalShares provides a mock function with given fields: ctx, vaultId +func (_m *VaultKeeper) GetTotalShares(ctx types.Context, vaultId vaulttypes.VaultId) (vaulttypes.NumShares, bool) { + ret := _m.Called(ctx, vaultId) + + if len(ret) == 0 { + panic("no return value specified for GetTotalShares") + } + + var r0 vaulttypes.NumShares + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId) (vaulttypes.NumShares, bool)); ok { + return rf(ctx, vaultId) + } + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId) vaulttypes.NumShares); ok { + r0 = rf(ctx, vaultId) + } else { + r0 = ret.Get(0).(vaulttypes.NumShares) + } + + if rf, ok := ret.Get(1).(func(types.Context, vaulttypes.VaultId) bool); ok { + r1 = rf(ctx, vaultId) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GetVaultClobOrderClientId provides a mock function with given fields: ctx, side, layer +func (_m *VaultKeeper) GetVaultClobOrderClientId(ctx types.Context, side clobtypes.Order_Side, layer uint8) uint32 { + ret := _m.Called(ctx, side, layer) + + if len(ret) == 0 { + panic("no return value specified for GetVaultClobOrderClientId") + } + + var r0 uint32 + if rf, ok := ret.Get(0).(func(types.Context, clobtypes.Order_Side, uint8) uint32); ok { + r0 = rf(ctx, side, layer) + } else { + r0 = ret.Get(0).(uint32) + } + + return r0 +} + +// GetVaultClobOrders provides a mock function with given fields: ctx, vaultId +func (_m *VaultKeeper) GetVaultClobOrders(ctx types.Context, vaultId vaulttypes.VaultId) ([]*clobtypes.Order, error) { + ret := _m.Called(ctx, vaultId) + + if len(ret) == 0 { + panic("no return value specified for GetVaultClobOrders") + } + + var r0 []*clobtypes.Order + var r1 error + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId) ([]*clobtypes.Order, error)); ok { + return rf(ctx, vaultId) + } + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId) []*clobtypes.Order); ok { + r0 = rf(ctx, vaultId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*clobtypes.Order) + } + } + + if rf, ok := ret.Get(1).(func(types.Context, vaulttypes.VaultId) error); ok { + r1 = rf(ctx, vaultId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetVaultEquity provides a mock function with given fields: ctx, vaultId +func (_m *VaultKeeper) GetVaultEquity(ctx types.Context, vaultId vaulttypes.VaultId) (*big.Int, error) { + ret := _m.Called(ctx, vaultId) + + if len(ret) == 0 { + panic("no return value specified for GetVaultEquity") + } + + var r0 *big.Int + var r1 error + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId) (*big.Int, error)); ok { + return rf(ctx, vaultId) + } + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId) *big.Int); ok { + r0 = rf(ctx, vaultId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + if rf, ok := ret.Get(1).(func(types.Context, vaulttypes.VaultId) error); ok { + r1 = rf(ctx, vaultId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MintShares provides a mock function with given fields: ctx, vaultId, owner, quantumsToDeposit +func (_m *VaultKeeper) MintShares(ctx types.Context, vaultId vaulttypes.VaultId, owner string, quantumsToDeposit *big.Int) error { + ret := _m.Called(ctx, vaultId, owner, quantumsToDeposit) + + if len(ret) == 0 { + panic("no return value specified for MintShares") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId, string, *big.Int) error); ok { + r0 = rf(ctx, vaultId, owner, quantumsToDeposit) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RefreshAllVaultOrders provides a mock function with given fields: ctx +func (_m *VaultKeeper) RefreshAllVaultOrders(ctx types.Context) { + _m.Called(ctx) +} + +// RefreshVaultClobOrders provides a mock function with given fields: ctx, vaultId +func (_m *VaultKeeper) RefreshVaultClobOrders(ctx types.Context, vaultId vaulttypes.VaultId) error { + ret := _m.Called(ctx, vaultId) + + if len(ret) == 0 { + panic("no return value specified for RefreshVaultClobOrders") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId) error); ok { + r0 = rf(ctx, vaultId) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetTotalShares provides a mock function with given fields: ctx, vaultId, totalShares +func (_m *VaultKeeper) SetTotalShares(ctx types.Context, vaultId vaulttypes.VaultId, totalShares vaulttypes.NumShares) error { + ret := _m.Called(ctx, vaultId, totalShares) + + if len(ret) == 0 { + panic("no return value specified for SetTotalShares") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, vaulttypes.VaultId, vaulttypes.NumShares) error); ok { + r0 = rf(ctx, vaultId, totalShares) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewVaultKeeper creates a new instance of VaultKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewVaultKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *VaultKeeper { + mock := &VaultKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/protocol/scripts/genesis/sample_pregenesis.json b/protocol/scripts/genesis/sample_pregenesis.json index 046f53e67f..40b3eb7b9d 100644 --- a/protocol/scripts/genesis/sample_pregenesis.json +++ b/protocol/scripts/genesis/sample_pregenesis.json @@ -895,8 +895,8 @@ "initial_margin_ppm": 1000000, "maintenance_fraction_ppm": 200000, "name": "Safety", - "open_interest_lower_cap": 0, - "open_interest_upper_cap": 0 + "open_interest_lower_cap": 500000000000, + "open_interest_upper_cap": 1000000000000 } ], "params": { @@ -1806,7 +1806,16 @@ "total_escrowed": [] }, "upgrade": {}, - "vault": {}, + "vault": { + "params": { + "layers": 2, + "order_expiration_seconds": 2, + "order_size_pct_ppm": 100000, + "skew_factor_ppm": 500000, + "spread_buffer_ppm": 1500, + "spread_min_ppm": 3000 + } + }, "vest": { "vest_entries": [ { diff --git a/protocol/streaming/grpc/grpc_streaming_manager.go b/protocol/streaming/grpc/grpc_streaming_manager.go index b4cea80b57..7cc9beea23 100644 --- a/protocol/streaming/grpc/grpc_streaming_manager.go +++ b/protocol/streaming/grpc/grpc_streaming_manager.go @@ -3,6 +3,7 @@ package grpc import ( "sync" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/gogoproto/proto" ocutypes "github.com/dydxprotocol/v4-chain/protocol/indexer/off_chain_updates/types" "github.com/dydxprotocol/v4-chain/protocol/lib" @@ -76,6 +77,8 @@ func (sm *GrpcStreamingManagerImpl) Subscribe( func (sm *GrpcStreamingManagerImpl) SendOrderbookUpdates( offchainUpdates *clobtypes.OffchainUpdates, snapshot bool, + blockHeight uint32, + execMode sdk.ExecMode, ) { // Group updates by clob pair id. updates := make(map[uint32]*clobtypes.OffchainUpdates) @@ -113,8 +116,10 @@ func (sm *GrpcStreamingManagerImpl) SendOrderbookUpdates( if len(updatesToSend) > 0 { if err := subscription.srv.Send( &clobtypes.StreamOrderbookUpdatesResponse{ - Updates: updatesToSend, - Snapshot: snapshot, + Updates: updatesToSend, + Snapshot: snapshot, + BlockHeight: blockHeight, + ExecMode: uint32(execMode), }, ); err != nil { idsToRemove = append(idsToRemove, id) diff --git a/protocol/streaming/grpc/noop_streaming_manager.go b/protocol/streaming/grpc/noop_streaming_manager.go index f8cc229772..3de85deaec 100644 --- a/protocol/streaming/grpc/noop_streaming_manager.go +++ b/protocol/streaming/grpc/noop_streaming_manager.go @@ -1,6 +1,7 @@ package grpc import ( + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/dydxprotocol/v4-chain/protocol/streaming/grpc/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" ) @@ -29,6 +30,8 @@ func (sm *NoopGrpcStreamingManager) Subscribe( func (sm *NoopGrpcStreamingManager) SendOrderbookUpdates( updates *clobtypes.OffchainUpdates, snapshot bool, + blockHeight uint32, + execMode sdk.ExecMode, ) { } diff --git a/protocol/streaming/grpc/types/manager.go b/protocol/streaming/grpc/types/manager.go index 0027358e79..5d0d9f1ab4 100644 --- a/protocol/streaming/grpc/types/manager.go +++ b/protocol/streaming/grpc/types/manager.go @@ -1,6 +1,7 @@ package types import ( + sdk "github.com/cosmos/cosmos-sdk/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" ) @@ -18,5 +19,7 @@ type GrpcStreamingManager interface { SendOrderbookUpdates( offchainUpdates *clobtypes.OffchainUpdates, snapshot bool, + blockHeight uint32, + execMode sdk.ExecMode, ) } diff --git a/protocol/testing/containertest/testnet_test.go b/protocol/testing/containertest/testnet_test.go index 9e3cdf5c14..4f78736829 100644 --- a/protocol/testing/containertest/testnet_test.go +++ b/protocol/testing/containertest/testnet_test.go @@ -12,14 +12,11 @@ import ( "time" sdkmath "cosmossdk.io/math" - upgrade "cosmossdk.io/x/upgrade/types" sdk "github.com/cosmos/cosmos-sdk/types" bank "github.com/cosmos/cosmos-sdk/x/bank/types" - gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/cosmos/gogoproto/jsonpb" "github.com/cosmos/gogoproto/proto" "github.com/dydxprotocol/v4-chain/protocol/daemons/pricefeed/client/types" - testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" "github.com/dydxprotocol/v4-chain/protocol/testutil/daemons/pricefeed/exchange_config" assets "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" @@ -31,15 +28,8 @@ import ( ) const expectDirName = "expect" -const govModuleAddress = "dydx10d07y265gmmuvt4z0w9aw880jnsr700jnmapky" var acceptFlag = flag.Bool("accept", false, "Accept new values for expect files") -var nodeAddresses = []string{ - constants.AliceAccAddress.String(), - constants.BobAccAddress.String(), - constants.CarlAccAddress.String(), - constants.DaveAccAddress.String(), -} // Compare a message against an expected output. Use flag `-accept` to write or modify expected output. // Expected output will read/written from `expect/{testName}_{tag}.expect`. @@ -292,45 +282,6 @@ func TestUpgrade(t *testing.T) { defer testnet.MustCleanUp() node := testnet.Nodes["alice"] - proposal, err := gov.NewMsgSubmitProposal( - []sdk.Msg{ - &upgrade.MsgSoftwareUpgrade{ - Authority: govModuleAddress, - Plan: upgrade.Plan{ - Name: UpgradeToVersion, - Height: 10, - }, - }, - }, - testapp.TestDeposit, - constants.AliceAccAddress.String(), - testapp.TestMetadata, - testapp.TestTitle, - testapp.TestSummary, - false, - ) - require.NoError(t, err) - - require.NoError(t, BroadcastTx( - node, - proposal, - constants.AliceAccAddress.String(), - )) - err = node.Wait(2) - require.NoError(t, err) - - for _, address := range nodeAddresses { - require.NoError(t, BroadcastTx( - node, - &gov.MsgVote{ - ProposalId: 1, - Voter: address, - Option: gov.VoteOption_VOTE_OPTION_YES, - }, - address, - )) - } - - err = node.WaitUntilBlockHeight(12) + err = UpgradeTestnet(constants.AliceAccAddress.String(), t, node, UpgradeToVersion) require.NoError(t, err) } diff --git a/protocol/testing/containertest/testnet_utils.go b/protocol/testing/containertest/testnet_utils.go new file mode 100644 index 0000000000..c615f17dde --- /dev/null +++ b/protocol/testing/containertest/testnet_utils.go @@ -0,0 +1,66 @@ +package containertest + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + "github.com/stretchr/testify/require" + + upgrade "cosmossdk.io/x/upgrade/types" + sdk "github.com/cosmos/cosmos-sdk/types" + gov "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" +) + +const GovModuleAddress = "dydx10d07y265gmmuvt4z0w9aw880jnsr700jnmapky" + +var NodeAddresses = []string{ + constants.AliceAccAddress.String(), + constants.BobAccAddress.String(), + constants.CarlAccAddress.String(), + constants.DaveAccAddress.String(), +} + +func UpgradeTestnet(nodeAddress string, t *testing.T, node *Node, upgradeToVersion string) error { + proposal, err := gov.NewMsgSubmitProposal( + []sdk.Msg{ + &upgrade.MsgSoftwareUpgrade{ + Authority: GovModuleAddress, + Plan: upgrade.Plan{ + Name: upgradeToVersion, + Height: 10, + }, + }, + }, + testapp.TestDeposit, + nodeAddress, + testapp.TestMetadata, + testapp.TestTitle, + testapp.TestSummary, + false, + ) + require.NoError(t, err) + + require.NoError(t, BroadcastTx( + node, + proposal, + nodeAddress, + )) + err = node.Wait(2) + require.NoError(t, err) + + for _, address := range NodeAddresses { + require.NoError(t, BroadcastTx( + node, + &gov.MsgVote{ + ProposalId: 1, + Voter: address, + Option: gov.VoteOption_VOTE_OPTION_YES, + }, + address, + )) + } + + err = node.WaitUntilBlockHeight(12) + return err +} diff --git a/protocol/testing/genesis.sh b/protocol/testing/genesis.sh index 747b02bbbe..33252badf5 100755 --- a/protocol/testing/genesis.sh +++ b/protocol/testing/genesis.sh @@ -169,10 +169,9 @@ function edit_genesis() { dasel put -t int -f "$GENESIS" '.app_state.perpetuals.liquidity_tiers.[3].maintenance_fraction_ppm' -v '200000' # 20% of IM dasel put -t int -f "$GENESIS" '.app_state.perpetuals.liquidity_tiers.[3].base_position_notional' -v '1000000000' # 1_000 USDC dasel put -t int -f "$GENESIS" '.app_state.perpetuals.liquidity_tiers.[3].impact_notional' -v '2500000000' # 2_500 USDC (2_500 USDC / 100%) - # OIMF doesn't apply to `Safety`, since IMF is already at 100%. - dasel put -t int -f "$GENESIS" '.app_state.perpetuals.liquidity_tiers.[3].open_interest_lower_cap' -v '0' - dasel put -t int -f "$GENESIS" '.app_state.perpetuals.liquidity_tiers.[3].open_interest_upper_cap' -v '0' - + # For `Safety` IMF is already at 100%; still we set OIMF for completeness. + dasel put -t int -f "$GENESIS" '.app_state.perpetuals.liquidity_tiers.[3].open_interest_lower_cap' -v '500000000000' # 0.5 million USDC + dasel put -t int -f "$GENESIS" '.app_state.perpetuals.liquidity_tiers.[3].open_interest_upper_cap' -v '1000000000000' # 1 million USDC # Params. dasel put -t int -f "$GENESIS" '.app_state.perpetuals.params.funding_rate_clamp_factor_ppm' -v '6000000' # 600 % (same as 75% on hourly rate) diff --git a/protocol/testing/testnet-local/local.sh b/protocol/testing/testnet-local/local.sh index c8b599d70d..856e8eb540 100755 --- a/protocol/testing/testnet-local/local.sh +++ b/protocol/testing/testnet-local/local.sh @@ -101,7 +101,7 @@ create_validators() { # Using "@" as a subscript results in separate args: "dydx1..." "dydx1..." "dydx1..." # Note: `edit_genesis` must be called before `add-genesis-account`. edit_genesis "$VAL_CONFIG_DIR" "${TEST_ACCOUNTS[*]}" "${FAUCET_ACCOUNTS[*]}" "" "" "" "" - update_genesis_use_test_volatile_market "$VAL_CONFIG_DIR" + # update_genesis_use_test_volatile_market "$VAL_CONFIG_DIR" update_genesis_complete_bridge_delay "$VAL_CONFIG_DIR" "30" echo "${MNEMONICS[$i]}" | dydxprotocold keys add "${MONIKERS[$i]}" --recover --keyring-backend=test --home "$VAL_HOME_DIR" diff --git a/protocol/testutil/constants/genesis.go b/protocol/testutil/constants/genesis.go index dc56ee3590..6ee1737fba 100644 --- a/protocol/testutil/constants/genesis.go +++ b/protocol/testutil/constants/genesis.go @@ -1492,7 +1492,16 @@ const GenesisState = `{ "port_id": "transfer" }, "upgrade": {}, - "vault": {}, + "vault": { + "params": { + "layers": 2, + "spread_min_ppm": 3000, + "spread_buffer_ppm": 1500, + "skew_factor_ppm": 500000, + "order_size_pct_ppm": 100000, + "order_expiration_seconds": 2 + } + }, "vest": { "vest_entries": [ { diff --git a/protocol/testutil/constants/orders.go b/protocol/testutil/constants/orders.go index bebaff3975..d1d5c33498 100644 --- a/protocol/testutil/constants/orders.go +++ b/protocol/testutil/constants/orders.go @@ -1010,6 +1010,13 @@ var ( Subticks: 50_000_000_000, GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 12}, } + Order_Dave_Num0_Id1_Clob3_Sell025ISO_Price50_GTB11 = clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: Dave_Num0, ClientId: 1, ClobPairId: 3}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: 250_000_000, + Subticks: 5_000_000, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 11}, + } Order_Dave_Num0_Id2_Clob0_Sell025BTC_Price50000_GTB12 = clobtypes.Order{ OrderId: clobtypes.OrderId{SubaccountId: Dave_Num0, ClientId: 2, ClobPairId: 0}, Side: clobtypes.Order_SIDE_SELL, diff --git a/protocol/testutil/constants/perpetuals.go b/protocol/testutil/constants/perpetuals.go index 3c6db1e6fa..a95623be3f 100644 --- a/protocol/testutil/constants/perpetuals.go +++ b/protocol/testutil/constants/perpetuals.go @@ -104,12 +104,12 @@ var LiquidityTiers = []perptypes.LiquidityTier{ // Perpetual OI setup in tests var ( BtcUsd_OpenInterest1_AtomicRes8 = perptypes.OpenInterestDelta{ - PerpetualId: 0, - BaseQuantumsDelta: big.NewInt(100_000_000), + PerpetualId: 0, + BaseQuantums: big.NewInt(100_000_000), } EthUsd_OpenInterest1_AtomicRes9 = perptypes.OpenInterestDelta{ - PerpetualId: 1, - BaseQuantumsDelta: big.NewInt(1_000_000_000), + PerpetualId: 1, + BaseQuantums: big.NewInt(1_000_000_000), } DefaultTestPerpOIs = []perptypes.OpenInterestDelta{ BtcUsd_OpenInterest1_AtomicRes8, @@ -277,6 +277,19 @@ var ( FundingIndex: dtypes.ZeroInt(), OpenInterest: dtypes.NewInt(100_000_000), } + BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest2 = perptypes.Perpetual{ + Params: perptypes.PerpetualParams{ + Id: 0, + Ticker: "BTC-USD 20/10 margin requirements", + MarketId: uint32(0), + AtomicResolution: int32(-8), + DefaultFundingPpm: int32(0), + LiquidityTier: uint32(3), + MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + }, + FundingIndex: dtypes.ZeroInt(), + OpenInterest: dtypes.NewInt(200_000_000), + } BtcUsd_20PercentInitial_10PercentMaintenance_25mmLowerCap_50mmUpperCap = perptypes.Perpetual{ Params: perptypes.PerpetualParams{ Id: 0, @@ -379,6 +392,7 @@ var ( MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, }, FundingIndex: dtypes.ZeroInt(), + OpenInterest: dtypes.ZeroInt(), } Iso2Usd_IsolatedMarket = perptypes.Perpetual{ Params: perptypes.PerpetualParams{ @@ -391,6 +405,7 @@ var ( MarketType: perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, }, FundingIndex: dtypes.ZeroInt(), + OpenInterest: dtypes.ZeroInt(), } ) diff --git a/protocol/testutil/constants/subaccounts.go b/protocol/testutil/constants/subaccounts.go index 46532aeede..f5ed8edfdf 100644 --- a/protocol/testutil/constants/subaccounts.go +++ b/protocol/testutil/constants/subaccounts.go @@ -21,6 +21,19 @@ var ( }, PerpetualPositions: nil, } + Alice_Num0_1BTC_LONG_10_000USD = satypes.Subaccount{ + Id: &Alice_Num0, + AssetPositions: []*satypes.AssetPosition{ + &Usdc_Asset_10_000, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 1, + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + FundingIndex: dtypes.NewInt(0), + }, + }, + } Alice_Num0_1ISO_LONG_10_000USD = satypes.Subaccount{ Id: &Alice_Num0, AssetPositions: []*satypes.AssetPosition{ @@ -96,7 +109,7 @@ var ( PerpetualPositions: []*satypes.PerpetualPosition{ { PerpetualId: 3, - Quantums: dtypes.NewInt(10_000_000), // 1 ISO2 + Quantums: dtypes.NewInt(1_000_000_000), // 1 ISO FundingIndex: dtypes.NewInt(0), }, }, @@ -534,6 +547,22 @@ var ( }, }, } + Dave_Num0_1ISO_Long_50USD_Short = satypes.Subaccount{ + Id: &Dave_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewInt(-50_000_000), // -$50 + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: 3, + Quantums: dtypes.NewInt(1_000_000_000), // 1 ISO + FundingIndex: dtypes.NewInt(0), + }, + }, + } Dave_Num0_1ISO2_Short_499USD = satypes.Subaccount{ Id: &Dave_Num0, AssetPositions: []*satypes.AssetPosition{ diff --git a/protocol/testutil/constants/vault.go b/protocol/testutil/constants/vault.go index eba5e6120d..dce55d1f63 100644 --- a/protocol/testutil/constants/vault.go +++ b/protocol/testutil/constants/vault.go @@ -1,16 +1,23 @@ package constants import ( - vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" ) var ( - Vault_Clob_0 = vaulttypes.VaultId{ - Type: vaulttypes.VaultType_VAULT_TYPE_CLOB, + Vault_Clob_0 = types.VaultId{ + Type: types.VaultType_VAULT_TYPE_CLOB, Number: 0, } - Vault_Clob_1 = vaulttypes.VaultId{ - Type: vaulttypes.VaultType_VAULT_TYPE_CLOB, + Vault_Clob_1 = types.VaultId{ + Type: types.VaultType_VAULT_TYPE_CLOB, Number: 1, } + + MsgDepositToVault_Clob0_Alice0_100 = &types.MsgDepositToVault{ + VaultId: &Vault_Clob_0, + SubaccountId: &Alice_Num0, + QuoteQuantums: dtypes.NewInt(100), + } ) diff --git a/protocol/testutil/encoding/utils.go b/protocol/testutil/encoding/utils.go index 8b27df37da..a25ebe1c22 100644 --- a/protocol/testutil/encoding/utils.go +++ b/protocol/testutil/encoding/utils.go @@ -36,6 +36,8 @@ import ( pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" sendingtypes "github.com/dydxprotocol/v4-chain/protocol/x/sending/types" subaccountsmodule "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts" + vaultmodule "github.com/dydxprotocol/v4-chain/protocol/x/vault" + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" "github.com/stretchr/testify/require" ) @@ -67,7 +69,9 @@ func GetTestEncodingCfg() testutil.TestEncodingConfig { // Custom modules bridgemodule.AppModuleBasic{}, - subaccountsmodule.AppModuleBasic{}) + subaccountsmodule.AppModuleBasic{}, + vaultmodule.AppModuleBasic{}, + ) msgInterfacesToRegister := []sdk.Msg{ // Clob. @@ -86,6 +90,9 @@ func GetTestEncodingCfg() testutil.TestEncodingConfig { &sendingtypes.MsgCreateTransfer{}, &sendingtypes.MsgDepositToSubaccount{}, &sendingtypes.MsgWithdrawFromSubaccount{}, + + // Vault. + &vaulttypes.MsgDepositToVault{}, } for _, msg := range msgInterfacesToRegister { diff --git a/protocol/testutil/keeper/vault.go b/protocol/testutil/keeper/vault.go new file mode 100644 index 0000000000..73b18c8940 --- /dev/null +++ b/protocol/testutil/keeper/vault.go @@ -0,0 +1,67 @@ +package keeper + +import ( + "testing" + + dbm "github.com/cosmos/cosmos-db" + "github.com/dydxprotocol/v4-chain/protocol/lib" + delaymsgtypes "github.com/dydxprotocol/v4-chain/protocol/x/delaymsg/types" + + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/mocks" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" +) + +func VaultKeepers( + t testing.TB, +) ( + ctx sdk.Context, + keeper *keeper.Keeper, + storeKey storetypes.StoreKey, +) { + ctx = initKeepers(t, func( + db *dbm.MemDB, + registry codectypes.InterfaceRegistry, + cdc *codec.ProtoCodec, + stateStore storetypes.CommitMultiStore, + transientStoreKey storetypes.StoreKey, + ) []GenesisInitializer { + // Define necessary keepers here for unit tests + keeper, storeKey = createVaultKeeper(stateStore, db, cdc, transientStoreKey) + return []GenesisInitializer{keeper} + }) + + return ctx, keeper, storeKey +} + +func createVaultKeeper( + stateStore storetypes.CommitMultiStore, + db *dbm.MemDB, + cdc *codec.ProtoCodec, + transientStoreKey storetypes.StoreKey, +) ( + *keeper.Keeper, + storetypes.StoreKey, +) { + storeKey := storetypes.NewKVStoreKey(types.StoreKey) + stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) + + k := keeper.NewKeeper( + cdc, + storeKey, + &mocks.ClobKeeper{}, + &mocks.PerpetualsKeeper{}, + &mocks.PricesKeeper{}, + &mocks.SubaccountsKeeper{}, + []string{ + lib.GovModuleAddress.String(), + delaymsgtypes.ModuleAddress.String(), + }, + ) + + return k, storeKey +} diff --git a/protocol/testutil/memclob/keeper.go b/protocol/testutil/memclob/keeper.go index 6ba8856285..1a1fa07fec 100644 --- a/protocol/testutil/memclob/keeper.go +++ b/protocol/testutil/memclob/keeper.go @@ -513,6 +513,7 @@ func (f *FakeMemClobKeeper) Logger(ctx sdk.Context) log.Logger { } func (f *FakeMemClobKeeper) SendOrderbookUpdates( + ctx sdk.Context, offchainUpdates *types.OffchainUpdates, snapshot bool, ) { diff --git a/protocol/testutil/perpetuals/perpetuals.go b/protocol/testutil/perpetuals/perpetuals.go index bde3204aff..deb06ae378 100644 --- a/protocol/testutil/perpetuals/perpetuals.go +++ b/protocol/testutil/perpetuals/perpetuals.go @@ -137,7 +137,7 @@ func SetUpDefaultPerpOIsForTest( k.ModifyOpenInterest( ctx, perp.Params.Id, - perpOI.BaseQuantumsDelta, + perpOI.BaseQuantums, ), ) } diff --git a/protocol/x/clob/abci.go b/protocol/x/clob/abci.go index 27f4492589..764e23828d 100644 --- a/protocol/x/clob/abci.go +++ b/protocol/x/clob/abci.go @@ -169,13 +169,32 @@ func PrepareCheckState( // For orders that are filled in the last block, send an orderbook update to the grpc streams. if keeper.GetGrpcStreamingManager().Enabled() { allUpdates := types.NewOffchainUpdates() + orderIdsToSend := make(map[types.OrderId]bool) + + // Send an update for reverted local operations. + for _, operation := range localValidatorOperationsQueue { + if match := operation.GetMatch(); match != nil { + orderIdsToSend[match.GetMatchOrders().TakerOrderId] = true + + for _, fill := range match.GetMatchOrders().Fills { + orderIdsToSend[fill.MakerOrderId] = true + } + } + } + + // Send an update for orders that were proposed. for _, orderId := range processProposerMatchesEvents.OrderIdsFilledInLastBlock { + orderIdsToSend[orderId] = true + } + + // Send update. + for orderId := range orderIdsToSend { if _, exists := keeper.MemClob.GetOrder(ctx, orderId); exists { orderbookUpdate := keeper.MemClob.GetOrderbookUpdatesForOrderUpdate(ctx, orderId) allUpdates.Append(orderbookUpdate) } } - keeper.SendOrderbookUpdates(allUpdates, false) + keeper.SendOrderbookUpdates(ctx, allUpdates, false) } // 3. Place all stateful order placements included in the last block on the memclob. diff --git a/protocol/x/clob/abci_test.go b/protocol/x/clob/abci_test.go index 4a10197b3f..345bb94c09 100644 --- a/protocol/x/clob/abci_test.go +++ b/protocol/x/clob/abci_test.go @@ -22,8 +22,6 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" - "cosmossdk.io/store/prefix" - storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/dydxprotocol/v4-chain/protocol/mocks" keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" @@ -51,9 +49,7 @@ func assertFillAmountAndPruneState( t *testing.T, k *keeper.Keeper, ctx sdk.Context, - storeKey storetypes.StoreKey, expectedFillAmounts map[types.OrderId]satypes.BaseQuantums, - expectedPruneableBlockHeights map[uint32][]types.OrderId, expectedPrunedOrders map[types.OrderId]bool, ) { for orderId, expectedFillAmount := range expectedFillAmounts { @@ -66,28 +62,6 @@ func assertFillAmountAndPruneState( exists, _, _ := k.GetOrderFillAmount(ctx, orderId) require.False(t, exists) } - - for blockHeight, orderIds := range expectedPruneableBlockHeights { - // Verify that expected `blockHeightToPotentiallyPrunableOrders` were deleted. - blockHeightToPotentiallyPrunableOrdersStore := prefix.NewStore( - ctx.KVStore(storeKey), - []byte(types.BlockHeightToPotentiallyPrunableOrdersPrefix), - ) - - potentiallyPrunableOrdersBytes := blockHeightToPotentiallyPrunableOrdersStore.Get( - lib.Uint32ToKey(blockHeight), - ) - - var potentiallyPrunableOrders = &types.PotentiallyPrunableOrders{} - err := potentiallyPrunableOrders.Unmarshal(potentiallyPrunableOrdersBytes) - require.NoError(t, err) - - require.ElementsMatch( - t, - potentiallyPrunableOrders.OrderIds, - orderIds, - ) - } } func TestEndBlocker_Failure(t *testing.T) { @@ -184,7 +158,6 @@ func TestEndBlocker_Success(t *testing.T) { // Expectations. expectedFillAmounts map[types.OrderId]satypes.BaseQuantums - expectedPruneableBlockHeights map[uint32][]types.OrderId expectedPrunedOrders map[types.OrderId]bool expectedStatefulPlacementInState map[types.OrderId]bool expectedStatefulOrderTimeSlice map[time.Time][]types.OrderId @@ -802,9 +775,7 @@ func TestEndBlocker_Success(t *testing.T) { t, ks.ClobKeeper, ctx, - ks.StoreKey, tc.expectedFillAmounts, - tc.expectedPruneableBlockHeights, tc.expectedPrunedOrders, ) diff --git a/protocol/x/clob/e2e/isolated_subaccount_orders_test.go b/protocol/x/clob/e2e/isolated_subaccount_orders_test.go new file mode 100644 index 0000000000..aba434b93f --- /dev/null +++ b/protocol/x/clob/e2e/isolated_subaccount_orders_test.go @@ -0,0 +1,493 @@ +package clob_test + +import ( + "math/big" + "testing" + + "github.com/cometbft/cometbft/types" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" + + sdkmath "cosmossdk.io/math" + sdktypes "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + clobtypes "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 TestIsolatedSubaccountOrders(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + + orderQuantums := 1_000_000_000 + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5 := *clobtypes.NewMsgPlaceOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Alice_Num0, ClientId: 0, ClobPairId: 3}, + Side: clobtypes.Order_SIDE_BUY, + Quantums: uint64(orderQuantums), + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 5}, + }) + PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5 := *clobtypes.NewMsgPlaceOrder( + clobtypes.Order{ + OrderId: clobtypes.OrderId{SubaccountId: constants.Bob_Num0, ClientId: 0, ClobPairId: 3}, + Side: clobtypes.Order_SIDE_SELL, + Quantums: uint64(orderQuantums), + Subticks: 10, + GoodTilOneof: &clobtypes.Order_GoodTilBlock{GoodTilBlock: 5}, + }) + + // Alice holds a long position after the match. + Alice_Num0_IsolatedAfterMatch := satypes.Subaccount{ + Id: &constants.Alice_Num0, + AssetPositions: []*satypes.AssetPosition{ + // USDC asset position. + { + AssetId: uint32(0), + // Match = 10e9 * 10e-8 * 10 = 100 quantums. Fees = 0. + // Alice is buying, subtract match quantums from asset position. + Quantums: dtypes.NewInt(10_000_000_000 - 100), + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + // Isolated perpetual position. + { + PerpetualId: uint32(3), + Quantums: dtypes.NewInt(int64(orderQuantums)), + FundingIndex: dtypes.NewInt(0), + }, + }, + } + + // Bob holds a short position after the match. + Bob_Num0_IsolatedAfterMatch := satypes.Subaccount{ + Id: &constants.Bob_Num0, + AssetPositions: []*satypes.AssetPosition{ + // USDC asset position. + { + AssetId: uint32(0), + // Match = 10e9 * 10e-8 * 10 = 100 quantums. Fees = 1 quantum. + // Bob is selling, add match quantums from asset position. + Quantums: dtypes.NewInt(10_000_000_000 + 99), + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + // Isolated perpetual position. + { + PerpetualId: uint32(3), + Quantums: dtypes.NewInt(-1 * int64(orderQuantums)), + FundingIndex: dtypes.NewInt(0), + }, + }, + } + + // Alice holds a larger long position after the match. + Alice_Num0_MoreIsolatedAfterMatch := satypes.Subaccount{ + Id: &constants.Alice_Num0, + AssetPositions: []*satypes.AssetPosition{ + // USDC asset position. + { + AssetId: uint32(0), + // Match = 10e9 * 10e-8 * 10 = 100 quantums. Fees = 0. + // Alice is buying, subtract match quantums from asset position. + Quantums: dtypes.NewInt(10_000_000_000 - 100), + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + // Isolated perpetual position. + { + PerpetualId: uint32(3), + // Alice buys 1 more ISO, + Quantums: dtypes.NewInt(2 * int64(orderQuantums)), + FundingIndex: dtypes.NewInt(0), + }, + }, + } + + // Bob closes an isolated long position. + Bob_Num0_CrossAfterMatch := satypes.Subaccount{ + Id: &constants.Bob_Num0, + AssetPositions: []*satypes.AssetPosition{ + // USDC asset position. + { + AssetId: uint32(0), + // Match = 10e9 * 10e-8 * 10 = 100 quantums. Fees = 1. + // Bob is selling, add match quantums from asset position. + Quantums: dtypes.NewInt(10_000_000_000 + 99), + }, + }, + } + + tests := map[string]struct { + // Initial state + subaccounts []satypes.Subaccount + perpetuals []perptypes.Perpetual + clobPairs []clobtypes.ClobPair + collateralPoolBalances map[string]int64 + + // Test params + orders []clobtypes.MsgPlaceOrder + + // Expectation + expectedOrdersFilled []clobtypes.OrderId + expectedSubaccounts []satypes.Subaccount + expectedCollateralPoolBalances map[string]int64 + expectedErr string + }{ + "Isolated subaccount will not have matches for cross-market orders": { + subaccounts: []satypes.Subaccount{ + // Alice subaccount is isolated to ISO perpetual market with id 3. + constants.Alice_Num0_1ISO_LONG_10_000USD, + constants.Bob_Num0_10_000USD, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.EthUsd_20PercentInitial_10PercentMaintenance, + constants.IsoUsd_IsolatedMarket, + }, + clobPairs: []clobtypes.ClobPair{ + constants.ClobPair_Btc, + constants.ClobPair_Eth, + constants.ClobPair_3_Iso, + }, + orders: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB20, // this order is invalid, so a match won't happen + PlaceOrder_Bob_Num0_Id0_Clob0_Sell5_Price10_GTB20, + }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 10_000_000_000, // $10,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 10_000_000_000, // $10,000 USDC + }, + // No orders filled. + expectedOrdersFilled: []clobtypes.OrderId{}, + expectedSubaccounts: []satypes.Subaccount{ + constants.Alice_Num0_1ISO_LONG_10_000USD, + constants.Bob_Num0_10_000USD, + }, + // No changes as no match should have happened. + expectedCollateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 10_000_000_000, + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 10_000_000_000, + }, + }, + "Cross subaccount (with cross position) will not have matches for isolated-market orders": { + subaccounts: []satypes.Subaccount{ + constants.Alice_Num0_1BTC_LONG_10_000USD, + constants.Bob_Num0_10_000USD, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.EthUsd_20PercentInitial_10PercentMaintenance, + constants.IsoUsd_IsolatedMarket, + }, + clobPairs: []clobtypes.ClobPair{ + constants.ClobPair_Btc, + constants.ClobPair_Eth, + constants.ClobPair_3_Iso, + }, + orders: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5, // this order is invalid, so a match won't happen + PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5, + }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 10_000_000_000, // $10,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 10_000_000_000, // $10,000 USDC + }, + // No orders filled. + expectedOrdersFilled: []clobtypes.OrderId{}, + expectedSubaccounts: []satypes.Subaccount{ + constants.Alice_Num0_1BTC_LONG_10_000USD, + constants.Bob_Num0_10_000USD, + }, + // No changes as no match should have happened. + expectedCollateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 10_000_000_000, + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 10_000_000_000, + }, + }, + `Empty subaccount becomes isolated if an order matches for an isolated market, collateral balances + move to isolated collateral pools`: { + subaccounts: []satypes.Subaccount{ + constants.Alice_Num0_10_000USD, + constants.Bob_Num0_10_000USD, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.EthUsd_20PercentInitial_10PercentMaintenance, + constants.IsoUsd_IsolatedMarket, + }, + clobPairs: []clobtypes.ClobPair{ + constants.ClobPair_Btc, + constants.ClobPair_Eth, + constants.ClobPair_3_Iso, + }, + // Orders should match. + orders: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5, + PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5, + }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 30_000_000_000, // $30,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 5_000_000_000, // $5,000 USDC + }, + expectedOrdersFilled: []clobtypes.OrderId{ + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5.Order.OrderId, + PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5.Order.OrderId, + }, + expectedSubaccounts: []satypes.Subaccount{ + Alice_Num0_IsolatedAfterMatch, + Bob_Num0_IsolatedAfterMatch, + }, + expectedCollateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 10_000_000_000, // $30,000 USDC - $10,000 USDC - $10,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 24_999_999_999, // $5,000 USDC + $10,000 USDC + $10,000 USDC - fee (1 quote quantum) + }, + }, + "Isolated subaccount closing position moves collateral back to cross collateral pool": { + subaccounts: []satypes.Subaccount{ + constants.Alice_Num0_1ISO_LONG_10_000USD, + constants.Bob_Num0_1ISO_LONG_10_000USD, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.EthUsd_20PercentInitial_10PercentMaintenance, + constants.IsoUsd_IsolatedMarket, + }, + clobPairs: []clobtypes.ClobPair{ + constants.ClobPair_Btc, + constants.ClobPair_Eth, + constants.ClobPair_3_Iso, + }, + // Orders should match. + orders: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5, + PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5, + }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 10_000_000_000, // $10,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 30_000_000_000, // $30,000 USDC + }, + expectedOrdersFilled: []clobtypes.OrderId{}, + expectedSubaccounts: []satypes.Subaccount{ + Alice_Num0_MoreIsolatedAfterMatch, + Bob_Num0_CrossAfterMatch, + }, + expectedCollateralPoolBalances: map[string]int64{ + // $10,000 USDC + $10,000 USDC + (match) 100 quote quantums - fee (1 quote quantum) + satypes.ModuleAddress.String(): 20_000_000_099, + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + // $30,000 USDC - $10,000 USDC - (match) 100 quote quantums + ).String(): 19_999_999_900, + }, + }, + `Empty subaccount will not have matches for isolated market orders if cross collateral pool does not + have enough balance`: { + subaccounts: []satypes.Subaccount{ + constants.Alice_Num0_10_000USD, + constants.Bob_Num0_10_000USD, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.EthUsd_20PercentInitial_10PercentMaintenance, + constants.IsoUsd_IsolatedMarket, + }, + clobPairs: []clobtypes.ClobPair{ + constants.ClobPair_Btc, + constants.ClobPair_Eth, + constants.ClobPair_3_Iso, + }, + orders: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5, + PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5, + }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 1_000_000_000, // $1,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 30_000_000_000, // $30,000 USDC + }, + expectedOrdersFilled: []clobtypes.OrderId{}, + expectedSubaccounts: []satypes.Subaccount{ + constants.Alice_Num0_10_000USD, + constants.Bob_Num0_10_000USD, + }, + // No changes + expectedCollateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 1_000_000_000, // $1,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 30_000_000_000, // $30,000 USDC + }, + expectedErr: "insufficient funds", + }, + `Isolated subaccount will not have matches to close isolated perpetual position if isolated collateral pool does not + have enough balance`: { + subaccounts: []satypes.Subaccount{ + constants.Alice_Num0_1ISO_LONG_10_000USD, + constants.Bob_Num0_1ISO_LONG_10_000USD, + }, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance, + constants.EthUsd_20PercentInitial_10PercentMaintenance, + constants.IsoUsd_IsolatedMarket, + }, + clobPairs: []clobtypes.ClobPair{ + constants.ClobPair_Btc, + constants.ClobPair_Eth, + constants.ClobPair_3_Iso, + }, + // Orders should match. + orders: []clobtypes.MsgPlaceOrder{ + PlaceOrder_Alice_Num0_Id0_Clob3_Buy_1ISO_Price10_GTB5, + PlaceOrder_Bob_Num0_Id0_Clob3_Sell_1ISO_Price10_GTB5, + }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 30_000_000_000, // $30,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 1_000_000_000, // $1,000 USDC + }, + expectedOrdersFilled: []clobtypes.OrderId{}, + expectedSubaccounts: []satypes.Subaccount{ + constants.Alice_Num0_1ISO_LONG_10_000USD, + constants.Bob_Num0_1ISO_LONG_10_000USD, + }, + // No changes + expectedCollateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 30_000_000_000, // $30,000 USDC + authtypes.NewModuleAddress( + satypes.ModuleName + ":" + lib.UintToString(constants.IsoUsd_IsolatedMarket.Params.Id), + ).String(): 1_000_000_000, // $1,000 USDC + }, + expectedErr: "insufficient funds", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis types.GenesisDoc) { + genesis = testapp.DefaultGenesis() + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + genesisState.Subaccounts = tc.subaccounts + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *perptypes.GenesisState) { + genesisState.Params = constants.PerpetualsGenesisParams + genesisState.LiquidityTiers = constants.LiquidityTiers + genesisState.Perpetuals = tc.perpetuals + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *banktypes.GenesisState) { + // If the collateral pool address is already in bank genesis state, update it. + foundPools := make(map[string]struct{}) + for i, bal := range genesisState.Balances { + usdcBal, exists := tc.collateralPoolBalances[bal.Address] + if exists { + foundPools[bal.Address] = struct{}{} + genesisState.Balances[i] = banktypes.Balance{ + Address: bal.Address, + Coins: sdktypes.Coins{ + sdktypes.NewCoin(constants.Usdc.Denom, sdkmath.NewInt(usdcBal)), + }, + } + } + } + // If the collateral pool address is not in the bank genesis state, add it. + for addr, bal := range tc.collateralPoolBalances { + _, exists := foundPools[addr] + if exists { + continue + } + genesisState.Balances = append(genesisState.Balances, banktypes.Balance{ + Address: addr, + Coins: sdktypes.Coins{ + sdktypes.NewCoin(constants.Usdc.Denom, sdkmath.NewInt(bal)), + }, + }) + } + }, + ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *clobtypes.GenesisState) { + genesisState.ClobPairs = tc.clobPairs + genesisState.LiquidationsConfig = constants.LiquidationsConfig_FillablePrice_Max_Smmr + genesisState.EquityTierLimitConfig = clobtypes.EquityTierLimitConfiguration{} + }, + ) + return genesis + }).Build() + ctx = tApp.InitChain() + + for i, order := range tc.orders { + for _, checkTx := range testapp.MustMakeCheckTxsWithClobMsg(ctx, tApp.App, order) { + resp := tApp.CheckTx(checkTx) + // Error should only be returned for the second order, as it results in a match. + if tc.expectedErr == "" || (i != len(tc.orders)-1) { + require.Conditionf(t, resp.IsOK, "Expected CheckTx to succeed. Response: %+v", resp) + } else { + require.False(t, resp.IsOK()) + require.Contains(t, resp.Log, tc.expectedErr) + } + } + } + + ctx = tApp.AdvanceToBlock(2, testapp.AdvanceToBlockOptions{}) + + for _, order := range tc.orders { + if slices.Contains(tc.expectedOrdersFilled, order.Order.OrderId) { + exists, fillAmount, _ := tApp.App.ClobKeeper.GetOrderFillAmount( + ctx, + order.Order.OrderId, + ) + + require.True(t, exists) + require.Equal(t, order.Order.GetBaseQuantums(), fillAmount) + } + } + + for _, expectedSubaccount := range tc.expectedSubaccounts { + require.Equal( + t, + expectedSubaccount, + tApp.App.SubaccountsKeeper.GetSubaccount(ctx, *expectedSubaccount.Id), + ) + } + + for addr, expectedBalance := range tc.expectedCollateralPoolBalances { + require.Equal( + t, + sdkmath.NewIntFromBigInt(big.NewInt(expectedBalance)), + tApp.App.BankKeeper.GetBalance(ctx, sdktypes.MustAccAddressFromBech32(addr), constants.Usdc.Denom).Amount, + ) + } + }) + } +} diff --git a/protocol/x/clob/e2e/withdrawal_gating_test.go b/protocol/x/clob/e2e/withdrawal_gating_test.go index 762624807d..f66608d3b4 100644 --- a/protocol/x/clob/e2e/withdrawal_gating_test.go +++ b/protocol/x/clob/e2e/withdrawal_gating_test.go @@ -3,15 +3,17 @@ package clob_test import ( "testing" + sdkmath "cosmossdk.io/math" abcitypes "github.com/cometbft/cometbft/abci/types" sdktypes "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/gogoproto/proto" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" "github.com/dydxprotocol/v4-chain/protocol/daemons/liquidation/api" - "github.com/dydxprotocol/v4-chain/protocol/dtypes" "github.com/cometbft/cometbft/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" clobtest "github.com/dydxprotocol/v4-chain/protocol/testutil/clob" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" @@ -26,10 +28,21 @@ import ( ) func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) { + Alice_Num0_AfterWithdrawal := satypes.Subaccount{ + Id: &constants.Alice_Num0, + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(9_999_999_999), + }, + }, + PerpetualPositions: nil, + } tests := map[string]struct { // State. subaccounts []satypes.Subaccount marketIdToOraclePriceOverride map[uint32]uint64 + collateralPoolBalances map[string]int64 // Parameters. placedMatchableOrders []clobtypes.MatchableOrder @@ -43,11 +56,13 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) clobPairs []clobtypes.ClobPair transferOrWithdrawSubaccount satypes.SubaccountId isWithdrawal bool + gatedPerpetualId uint32 // Expectations. expectedSubaccounts []satypes.Subaccount - expectedWithdrawalsGated bool - expectedNegativeTncSubaccountSeenAtBlock uint32 + expectedSubaccountsAfterWithdrawal []satypes.Subaccount + expectedWithdrawalsGated map[uint32]bool + expectedNegativeTncSubaccountSeenAtBlock map[uint32]uint32 expectedErr string }{ `Can place a liquidation order that is unfilled and cannot be deleveraged due to @@ -60,6 +75,9 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) marketIdToOraclePriceOverride: map[uint32]uint64{ constants.BtcUsd.MarketId: 5_050_000_000, // $50,500 / BTC }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 1_000_000_000_000, // $1,000,000 USDC + }, placedMatchableOrders: []clobtypes.MatchableOrder{ // Carl's bankruptcy price to close 1 BTC short is $49,999, and closing at $50,000 @@ -78,6 +96,7 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, transferOrWithdrawSubaccount: constants.Dave_Num1, isWithdrawal: true, + gatedPerpetualId: constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1.Params.Id, expectedSubaccounts: []satypes.Subaccount{ // Deleveraging fails. @@ -86,9 +105,122 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) constants.Carl_Num0_1BTC_Short_49999USD, constants.Dave_Num0_1BTC_Long_50000USD_Short, }, - expectedWithdrawalsGated: true, - expectedNegativeTncSubaccountSeenAtBlock: 4, - expectedErr: "WithdrawalsAndTransfersBlocked: failed to apply subaccount updates", + expectedWithdrawalsGated: map[uint32]bool{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1.Params.Id: true, + }, + expectedNegativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1.Params.Id: 4, + }, + expectedErr: "WithdrawalsAndTransfersBlocked: failed to apply subaccount updates", + }, + `Can place a liquidation order that is unfilled and cannot be deleveraged due to + non-overlapping bankruptcy prices for isolated market, withdrawals are gated for isolated subaccount`: { + subaccounts: []satypes.Subaccount{ + constants.Carl_Num0_1ISO_Short_49USD, + constants.Dave_Num0_1ISO_Long_50USD_Short, + constants.Alice_Num0_1ISO_LONG_10_000USD, + }, + marketIdToOraclePriceOverride: map[uint32]uint64{ + constants.IsoUsd_IsolatedMarket.Params.MarketId: 5_050_000_000, // $50.5 / ISO + }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 1_000_000_000_000, // $1,000,000 USDC + constants.IsoCollateralPoolAddress.String(): 1_000_000_000_000, // $1,000,000 USDC + }, + + placedMatchableOrders: []clobtypes.MatchableOrder{ + // Carl's bankruptcy price to close 1 ISO short is $49, and closing at $50 + // would require $1 from the insurance fund. Since the insurance fund is empty, + // deleveraging is required to close this position. + &constants.Order_Dave_Num0_Id1_Clob3_Sell025ISO_Price50_GTB11, + }, + liquidationConfig: constants.LiquidationsConfig_FillablePrice_Max_Smmr, + liquidatableSubaccountIds: []satypes.SubaccountId{constants.Carl_Num0}, + negativeTncSubaccountIds: []satypes.SubaccountId{constants.Carl_Num0}, + + liquidityTiers: constants.LiquidityTiers, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, + constants.IsoUsd_IsolatedMarket, + }, + clobPairs: []clobtypes.ClobPair{constants.ClobPair_3_Iso}, + transferOrWithdrawSubaccount: constants.Alice_Num0, + isWithdrawal: true, + gatedPerpetualId: constants.IsoUsd_IsolatedMarket.Params.Id, + + expectedSubaccounts: []satypes.Subaccount{ + // Deleveraging fails. + // Dave's bankruptcy price to close 1 BTC long is $50, and deleveraging can not be + // performed due to non overlapping bankruptcy prices. + constants.Carl_Num0_1ISO_Short_49USD, + constants.Dave_Num0_1ISO_Long_50USD_Short, + constants.Alice_Num0_1ISO_LONG_10_000USD, + }, + expectedWithdrawalsGated: map[uint32]bool{ + constants.IsoUsd_IsolatedMarket.Params.Id: true, + }, + expectedNegativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 4, + }, + expectedErr: "WithdrawalsAndTransfersBlocked: failed to apply subaccount updates", + }, + `Can place a liquidation order that is unfilled and cannot be deleveraged due to + non-overlapping bankruptcy prices for isolated market, withdrawals are not gated for non-isolated subaccount`: { + subaccounts: []satypes.Subaccount{ + constants.Carl_Num0_1ISO_Short_49USD, + constants.Dave_Num0_1ISO_Long_50USD_Short, + constants.Alice_Num0_10_000USD, + }, + marketIdToOraclePriceOverride: map[uint32]uint64{ + constants.IsoUsd_IsolatedMarket.Params.MarketId: 5_050_000_000, // $50.5 / ISO + }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 1_000_000_000_000, // $1,000,000 USDC + constants.IsoCollateralPoolAddress.String(): 1_000_000_000_000, // $1,000,000 USDC + }, + + placedMatchableOrders: []clobtypes.MatchableOrder{ + // Carl's bankruptcy price to close 1 ISO short is $49, and closing at $50 + // would require $1 from the insurance fund. Since the insurance fund is empty, + // deleveraging is required to close this position. + &constants.Order_Dave_Num0_Id1_Clob3_Sell025ISO_Price50_GTB11, + }, + liquidationConfig: constants.LiquidationsConfig_FillablePrice_Max_Smmr, + liquidatableSubaccountIds: []satypes.SubaccountId{constants.Carl_Num0}, + negativeTncSubaccountIds: []satypes.SubaccountId{constants.Carl_Num0}, + + liquidityTiers: constants.LiquidityTiers, + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, + constants.IsoUsd_IsolatedMarket, + }, + clobPairs: []clobtypes.ClobPair{constants.ClobPair_3_Iso}, + transferOrWithdrawSubaccount: constants.Alice_Num0, + isWithdrawal: true, + gatedPerpetualId: constants.IsoUsd_IsolatedMarket.Params.Id, + + expectedSubaccounts: []satypes.Subaccount{ + // Deleveraging fails. + // Dave's bankruptcy price to close 1 BTC long is $50, and deleveraging can not be + // performed due to non overlapping bankruptcy prices. + constants.Carl_Num0_1ISO_Short_49USD, + constants.Dave_Num0_1ISO_Long_50USD_Short, + constants.Alice_Num0_10_000USD, + }, + expectedSubaccountsAfterWithdrawal: []satypes.Subaccount{ + constants.Carl_Num0_1ISO_Short_49USD, + constants.Dave_Num0_1ISO_Long_50USD_Short, + // Alice is not an isolated subaccount, and so can still withdraw. + Alice_Num0_AfterWithdrawal, + }, + expectedWithdrawalsGated: map[uint32]bool{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance.Params.Id: false, + constants.IsoUsd_IsolatedMarket.Params.Id: true, + }, + expectedNegativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.IsoUsd_IsolatedMarket.Params.Id: 4, + }, + expectedErr: "", }, `Can place a liquidation order that is partially-filled filled, deleveraging is skipped but its still negative TNC, withdrawals are gated`: { @@ -97,6 +229,9 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) constants.Dave_Num0_1BTC_Long_50000USD_Short, constants.Dave_Num1_025BTC_Long_50000USD, }, + collateralPoolBalances: map[string]int64{ + satypes.ModuleAddress.String(): 1_000_000_000_000, // $1,000,000 USDC + }, placedMatchableOrders: []clobtypes.MatchableOrder{ &constants.Order_Dave_Num1_Id0_Clob0_Sell025BTC_Price49999_GTB10, @@ -117,6 +252,7 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) clobPairs: []clobtypes.ClobPair{constants.ClobPair_Btc}, transferOrWithdrawSubaccount: constants.Dave_Num1, isWithdrawal: false, + gatedPerpetualId: constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1.Params.Id, expectedSubaccounts: []satypes.Subaccount{ // Deleveraging fails for remaining amount. @@ -150,9 +286,13 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) }, }, }, - expectedWithdrawalsGated: true, - expectedNegativeTncSubaccountSeenAtBlock: 4, - expectedErr: "WithdrawalsAndTransfersBlocked: failed to apply subaccount updates", + expectedWithdrawalsGated: map[uint32]bool{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1.Params.Id: true, + }, + expectedNegativeTncSubaccountSeenAtBlock: map[uint32]uint32{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1.Params.Id: 4, + }, + expectedErr: "WithdrawalsAndTransfersBlocked: failed to apply subaccount updates", }, } @@ -193,6 +333,38 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) *genesisState = pricesGenesis }, ) + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *banktypes.GenesisState) { + // If the collateral pool address is already in bank genesis state, update it. + foundPools := make(map[string]struct{}) + for i, bal := range genesisState.Balances { + usdcBal, exists := tc.collateralPoolBalances[bal.Address] + if exists { + foundPools[bal.Address] = struct{}{} + genesisState.Balances[i] = banktypes.Balance{ + Address: bal.Address, + Coins: sdktypes.Coins{ + sdktypes.NewCoin(constants.Usdc.Denom, sdkmath.NewInt(usdcBal)), + }, + } + } + } + // If the collateral pool address is not in the bank genesis state, add it. + for addr, bal := range tc.collateralPoolBalances { + _, exists := foundPools[addr] + if exists { + continue + } + genesisState.Balances = append(genesisState.Balances, banktypes.Balance{ + Address: addr, + Coins: sdktypes.Coins{ + sdktypes.NewCoin(constants.Usdc.Denom, sdkmath.NewInt(bal)), + }, + }) + } + }, + ) testapp.UpdateGenesisDocWithAppStateForModule( &genesis, func(genesisState *perptypes.GenesisState) { @@ -252,13 +424,15 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) tApp.App.SubaccountsKeeper.GetSubaccount(ctx, *expectedSubaccount.Id), ) } - negativeTncSubaccountSeenAtBlock, exists, err := tApp.App.SubaccountsKeeper.GetNegativeTncSubaccountSeenAtBlock( - ctx, - constants.BtcUsd_NoMarginRequirement.Params.Id, - ) - require.NoError(t, err) - require.Equal(t, tc.expectedWithdrawalsGated, exists) - require.Equal(t, tc.expectedNegativeTncSubaccountSeenAtBlock, negativeTncSubaccountSeenAtBlock) + for perpetualId, expectedWithdrawalsGated := range tc.expectedWithdrawalsGated { + negativeTncSubaccountSeenAtBlock, exists, err := tApp.App.SubaccountsKeeper.GetNegativeTncSubaccountSeenAtBlock( + ctx, + perpetualId, + ) + require.NoError(t, err) + require.Equal(t, expectedWithdrawalsGated, exists) + require.Equal(t, tc.expectedNegativeTncSubaccountSeenAtBlock[perpetualId], negativeTncSubaccountSeenAtBlock) + } // Verify withdrawals are blocked by trying to create a transfer message that withdraws funds. var msg proto.Message @@ -304,14 +478,27 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) ) (haltchain bool) { // Note the first TX is MsgProposedOperations, the second is all other TXs. execResult := response.TxResults[1] - require.True(t, execResult.IsErr()) - require.Equal(t, satypes.ErrFailedToUpdateSubaccounts.ABCICode(), execResult.Code) - require.Contains(t, execResult.Log, tc.expectedErr) + if tc.expectedErr != "" { + require.True(t, execResult.IsErr()) + require.Equal(t, satypes.ErrFailedToUpdateSubaccounts.ABCICode(), execResult.Code) + require.Contains(t, execResult.Log, tc.expectedErr) + } else { + require.False(t, execResult.IsErr()) + } return false }, }, ) - for _, expectedSubaccount := range tc.expectedSubaccounts { + var expectedSubaccountsAfterWithdrawal []satypes.Subaccount + // If an error was expected for withdrawal / transfer, subaccounts should be the same as + // the ones expected after chain initialization, otherwise the test-case should have a set + // of expected subaccounts after a successful withdrawal. + if tc.expectedErr != "" { + expectedSubaccountsAfterWithdrawal = tc.expectedSubaccounts + } else { + expectedSubaccountsAfterWithdrawal = tc.expectedSubaccountsAfterWithdrawal + } + for _, expectedSubaccount := range expectedSubaccountsAfterWithdrawal { require.Equal( t, expectedSubaccount, @@ -319,38 +506,41 @@ func TestWithdrawalGating_NegativeTncSubaccount_BlocksThenUnblocks(t *testing.T) ) } - // Verify that transfers and withdrawals are unblocked after the withdrawal gating period passes. - _, err = tApp.App.Server.LiquidateSubaccounts(ctx, &api.LiquidateSubaccountsRequest{ - LiquidatableSubaccountIds: tc.liquidatableSubaccountIds, - NegativeTncSubaccountIds: []satypes.SubaccountId{}, - SubaccountOpenPositionInfo: clobtest.GetOpenPositionsFromSubaccounts(tc.subaccounts), - }) - require.NoError(t, err) - tApp.AdvanceToBlock( - tc.expectedNegativeTncSubaccountSeenAtBlock+ - satypes.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS+ - 1, - testapp.AdvanceToBlockOptions{}, - ) - for _, checkTx := range testapp.MustMakeCheckTxsWithSdkMsg( - ctx, - tApp.App, - testapp.MustMakeCheckTxOptions{ - AccAddressForSigning: tc.transferOrWithdrawSubaccount.Owner, - Gas: 1000000, - FeeAmt: constants.TestFeeCoins_5Cents, - }, - msg, - ) { - resp := tApp.CheckTx(checkTx) - require.Conditionf(t, resp.IsOK, "Expected CheckTx to succeed. Response: %+v", resp) + // If an error was expected for the withdrawal / transfer, verify that transfers and withdrawals are + // unblocked after the withdrawal gating period passes. + if tc.expectedErr != "" { + _, err = tApp.App.Server.LiquidateSubaccounts(ctx, &api.LiquidateSubaccountsRequest{ + LiquidatableSubaccountIds: tc.liquidatableSubaccountIds, + NegativeTncSubaccountIds: []satypes.SubaccountId{}, + SubaccountOpenPositionInfo: clobtest.GetOpenPositionsFromSubaccounts(tc.subaccounts), + }) + require.NoError(t, err) + tApp.AdvanceToBlock( + tc.expectedNegativeTncSubaccountSeenAtBlock[tc.gatedPerpetualId]+ + satypes.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS+ + 1, + testapp.AdvanceToBlockOptions{}, + ) + for _, checkTx := range testapp.MustMakeCheckTxsWithSdkMsg( + ctx, + tApp.App, + testapp.MustMakeCheckTxOptions{ + AccAddressForSigning: tc.transferOrWithdrawSubaccount.Owner, + Gas: 1000000, + FeeAmt: constants.TestFeeCoins_5Cents, + }, + msg, + ) { + resp := tApp.CheckTx(checkTx) + require.Conditionf(t, resp.IsOK, "Expected CheckTx to succeed. Response: %+v", resp) + } + tApp.AdvanceToBlock( + tc.expectedNegativeTncSubaccountSeenAtBlock[tc.gatedPerpetualId]+ + satypes.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS+ + 2, + testapp.AdvanceToBlockOptions{}, + ) } - tApp.AdvanceToBlock( - tc.expectedNegativeTncSubaccountSeenAtBlock+ - satypes.WITHDRAWAL_AND_TRANSFERS_BLOCKED_AFTER_NEGATIVE_TNC_SUBACCOUNT_SEEN_BLOCKS+ - 2, - testapp.AdvanceToBlockOptions{}, - ) }) } } diff --git a/protocol/x/clob/keeper/deleveraging_test.go b/protocol/x/clob/keeper/deleveraging_test.go index 1b28708043..3f54a3cd19 100644 --- a/protocol/x/clob/keeper/deleveraging_test.go +++ b/protocol/x/clob/keeper/deleveraging_test.go @@ -467,6 +467,9 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { expectedSubaccounts []satypes.Subaccount expectedFills []types.MatchPerpetualDeleveraging_Fill expectedQuantumsRemaining *big.Int + // Expected remaining OI after test. + // The test initializes each perp with default open interest of 1 full coin. + expectedOpenInterest *big.Int }{ "Can get one offsetting subaccount for deleveraged short": { subaccounts: []satypes.Subaccount{ @@ -496,6 +499,7 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { }, }, expectedQuantumsRemaining: new(big.Int), + expectedOpenInterest: new(big.Int), // fully deleveraged }, "Can get one offsetting subaccount for deleveraged long": { subaccounts: []satypes.Subaccount{ @@ -523,6 +527,7 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { }, }, expectedQuantumsRemaining: new(big.Int), + expectedOpenInterest: new(big.Int), // fully deleveraged }, "Can get multiple offsetting subaccounts": { subaccounts: []satypes.Subaccount{ @@ -587,6 +592,7 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { }, }, expectedQuantumsRemaining: new(big.Int), + expectedOpenInterest: new(big.Int), // fully deleveraged }, "Skips subaccounts with positions on the same side": { subaccounts: []satypes.Subaccount{ @@ -618,6 +624,7 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { }, }, expectedQuantumsRemaining: new(big.Int), + expectedOpenInterest: new(big.Int), // fully deleveraged }, "Skips subaccounts with no open position for the given perpetual": { subaccounts: []satypes.Subaccount{ @@ -649,6 +656,7 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { }, }, expectedQuantumsRemaining: new(big.Int), + expectedOpenInterest: new(big.Int), // fully deleveraged }, "Skips subaccounts with non-overlapping bankruptcy prices": { subaccounts: []satypes.Subaccount{ @@ -680,6 +688,7 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { }, }, expectedQuantumsRemaining: new(big.Int), + expectedOpenInterest: new(big.Int), // fully deleveraged }, "Returns an error if not enough subaccounts to fully deleverage liquidated subaccount's position": { subaccounts: []satypes.Subaccount{ @@ -692,6 +701,7 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { expectedSubaccounts: nil, expectedFills: []types.MatchPerpetualDeleveraging_Fill{}, expectedQuantumsRemaining: big.NewInt(100_000_000), + expectedOpenInterest: big.NewInt(100_000_000), }, "Can offset subaccount with multiple positions, first position is offset leaving TNC constant": { subaccounts: []satypes.Subaccount{ @@ -731,6 +741,7 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { }, }, expectedQuantumsRemaining: big.NewInt(0), + expectedOpenInterest: new(big.Int), // fully deleveraged }, } @@ -865,6 +876,17 @@ func TestOffsetSubaccountPerpetualPosition(t *testing.T) { for _, subaccount := range tc.expectedSubaccounts { require.Equal(t, subaccount, ks.SubaccountsKeeper.GetSubaccount(ks.Ctx, *subaccount.Id)) } + + if tc.expectedOpenInterest != nil { + gotPerp, err := ks.PerpetualsKeeper.GetPerpetual(ks.Ctx, tc.perpetualId) + require.NoError(t, err) + require.Zero(t, + tc.expectedOpenInterest.Cmp(gotPerp.OpenInterest.BigInt()), + "expected open interest %s, got %s", + tc.expectedOpenInterest.String(), + gotPerp.OpenInterest.String(), + ) + } }) } } diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index e6e34619ac..49c405176c 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -233,11 +233,12 @@ func (k Keeper) InitializeNewGrpcStreams(ctx sdk.Context) { allUpdates.Append(update) } - k.SendOrderbookUpdates(allUpdates, true) + k.SendOrderbookUpdates(ctx, allUpdates, true) } // SendOrderbookUpdates sends the offchain updates to the gRPC streaming manager. func (k Keeper) SendOrderbookUpdates( + ctx sdk.Context, offchainUpdates *types.OffchainUpdates, snapshot bool, ) { @@ -245,5 +246,10 @@ func (k Keeper) SendOrderbookUpdates( return } - k.GetGrpcStreamingManager().SendOrderbookUpdates(offchainUpdates, snapshot) + k.GetGrpcStreamingManager().SendOrderbookUpdates( + offchainUpdates, + snapshot, + lib.MustConvertIntegerToUint32(ctx.BlockHeight()), + ctx.ExecMode(), + ) } diff --git a/protocol/x/clob/keeper/liquidations_test.go b/protocol/x/clob/keeper/liquidations_test.go index 8d89d59975..c0cc7b00bf 100644 --- a/protocol/x/clob/keeper/liquidations_test.go +++ b/protocol/x/clob/keeper/liquidations_test.go @@ -52,6 +52,9 @@ func TestPlacePerpetualLiquidation(t *testing.T) { // Expectations. expectedPlacedOrders []*types.MsgPlaceOrder expectedMatchedOrders []*types.ClobMatch + // Expected remaining OI after test. + // The test initializes each perp with default open interest of 1 full coin. + expectedOpenInterests map[uint32]*big.Int }{ `Can place a liquidation that doesn't match any maker orders`: { perpetuals: []perptypes.Perpetual{ @@ -67,6 +70,9 @@ func TestPlacePerpetualLiquidation(t *testing.T) { expectedPlacedOrders: []*types.MsgPlaceOrder{}, expectedMatchedOrders: []*types.ClobMatch{}, + expectedOpenInterests: map[uint32]*big.Int{ + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), // unchanged + }, }, `Can place a liquidation that matches maker orders`: { perpetuals: []perptypes.Perpetual{ @@ -107,6 +113,9 @@ func TestPlacePerpetualLiquidation(t *testing.T) { }, ), }, + expectedOpenInterests: map[uint32]*big.Int{ + constants.BtcUsd_SmallMarginRequirement.Params.Id: new(big.Int), // fully liquidated + }, }, `Can place a liquidation that matches maker orders and removes undercollateralized ones`: { perpetuals: []perptypes.Perpetual{ @@ -149,6 +158,9 @@ func TestPlacePerpetualLiquidation(t *testing.T) { }, ), }, + expectedOpenInterests: map[uint32]*big.Int{ + constants.BtcUsd_SmallMarginRequirement.Params.Id: new(big.Int), // fully liquidated + }, }, `Can place a liquidation that matches maker orders with maker rebates and empty fee collector`: { perpetuals: []perptypes.Perpetual{ @@ -189,6 +201,9 @@ func TestPlacePerpetualLiquidation(t *testing.T) { }, ), }, + expectedOpenInterests: map[uint32]*big.Int{ + constants.BtcUsd_SmallMarginRequirement.Params.Id: new(big.Int), // fully liquidated + }, }, `Can place a liquidation that matches maker orders with maker rebates`: { perpetuals: []perptypes.Perpetual{ @@ -228,6 +243,9 @@ func TestPlacePerpetualLiquidation(t *testing.T) { }, ), }, + expectedOpenInterests: map[uint32]*big.Int{ + constants.BtcUsd_SmallMarginRequirement.Params.Id: new(big.Int), // fully liquidated + }, }, } for name, tc := range tests { @@ -342,6 +360,19 @@ func TestPlacePerpetualLiquidation(t *testing.T) { _, _, err = ks.ClobKeeper.PlacePerpetualLiquidation(ctx, tc.order) require.NoError(t, err) + for _, perp := range tc.perpetuals { + if expectedOI, exists := tc.expectedOpenInterests[perp.Params.Id]; exists { + gotPerp, err := ks.PerpetualsKeeper.GetPerpetual(ks.Ctx, perp.Params.Id) + require.NoError(t, err) + require.Zero(t, + expectedOI.Cmp(gotPerp.OpenInterest.BigInt()), + "expected open interest %s, got %s", + expectedOI.String(), + gotPerp.OpenInterest.String(), + ) + } + } + // Verify test expectations. // TODO(DEC-1979): Refactor these tests to support the operations queue refactor. // placedOrders, matchedOrders := memClob.GetPendingFills(ctx) diff --git a/protocol/x/clob/keeper/order_state.go b/protocol/x/clob/keeper/order_state.go index 814684e141..a4bd805689 100644 --- a/protocol/x/clob/keeper/order_state.go +++ b/protocol/x/clob/keeper/order_state.go @@ -1,8 +1,12 @@ package keeper import ( + "bytes" + "encoding/binary" + "cosmossdk.io/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/indexer/off_chain_updates" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/log" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" @@ -125,14 +129,34 @@ func (k Keeper) GetOrderFillAmount( return true, satypes.BaseQuantums(orderFillState.FillAmount), orderFillState.PrunableBlockHeight } -// AddOrdersForPruning creates or updates a slice of `orderIds` to state for potential future pruning from state. -// These orders will be checked for pruning from state at `prunableBlockHeight`. If the `orderIds` slice provided -// contains duplicates, the duplicates will be ignored. +// GetPruneableOrdersStore gets a prefix store for pruneable orders at a given height. +// The full format for these keys is :. +func (k Keeper) GetPruneableOrdersStore(ctx sdk.Context, height uint32) prefix.Store { + var buf bytes.Buffer + buf.Write([]byte(types.PrunableOrdersKeyPrefix)) + buf.Write(lib.Uint32ToKey(height)) + buf.Write([]byte(":")) + return prefix.NewStore(ctx.KVStore(k.storeKey), buf.Bytes()) +} + +// AddOrdersForPruning creates or updates `orderIds` to state for potential future pruning from state. func (k Keeper) AddOrdersForPruning(ctx sdk.Context, orderIds []types.OrderId, prunableBlockHeight uint32) { + store := k.GetPruneableOrdersStore(ctx, prunableBlockHeight) + for _, orderId := range orderIds { + store.Set( + orderId.ToStateKey(), + k.cdc.MustMarshal(&orderId), + ) + } +} + +// Deprecated: Do not use. Retained for testing purposes. +// LegacyAddOrdersForPruning is the old key-per-height format of storing orders to prune. +func (k Keeper) LegacyAddOrdersForPruning(ctx sdk.Context, orderIds []types.OrderId, prunableBlockHeight uint32) { // Retrieve an instance of the store. store := prefix.NewStore( ctx.KVStore(k.storeKey), - []byte(types.BlockHeightToPotentiallyPrunableOrdersPrefix), + []byte(types.LegacyBlockHeightToPotentiallyPrunableOrdersPrefix), ) // Retrieve the `PotentiallyPrunableOrders` bytes from the store. @@ -187,27 +211,13 @@ func (k Keeper) AddOrdersForPruning(ctx sdk.Context, orderIds []types.OrderId, p // Note: An order is only deemed prunable if the `prunableBlockHeight` on the `OrderFillState` is less than or equal // to the provided `blockHeight` passed this method. Returns a slice of unique `OrderIds` which were pruned from state. func (k Keeper) PruneOrdersForBlockHeight(ctx sdk.Context, blockHeight uint32) (prunedOrderIds []types.OrderId) { - // Retrieve an instance of the stores. - blockHeightToPotentiallyPrunableOrdersStore := prefix.NewStore( - ctx.KVStore(k.storeKey), - []byte(types.BlockHeightToPotentiallyPrunableOrdersPrefix), - ) - - // Retrieve the raw bytes of the `prunableOrders`. - potentiallyPrunableOrderBytes := blockHeightToPotentiallyPrunableOrdersStore.Get( - lib.Uint32ToKey(blockHeight), - ) - - // If there are no prunable orders for this block, then there is nothing to do. Early return. - if potentiallyPrunableOrderBytes == nil { - return - } - - var potentiallyPrunableOrders types.PotentiallyPrunableOrders - k.cdc.MustUnmarshal(potentiallyPrunableOrderBytes, &potentiallyPrunableOrders) + potentiallyPrunableOrdersStore := k.GetPruneableOrdersStore(ctx, blockHeight) + it := potentiallyPrunableOrdersStore.Iterator(nil, nil) + defer it.Close() - for _, orderId := range potentiallyPrunableOrders.OrderIds { - // Check if the order can be pruned, and prune if so. + for ; it.Valid(); it.Next() { + var orderId types.OrderId + k.cdc.MustUnmarshal(it.Value(), &orderId) exists, _, prunableBlockHeight := k.GetOrderFillAmount(ctx, orderId) if exists && prunableBlockHeight <= blockHeight { k.RemoveOrderFillAmount(ctx, orderId) @@ -221,14 +231,32 @@ func (k Keeper) PruneOrdersForBlockHeight(ctx sdk.Context, blockHeight uint32) ( ) } } + potentiallyPrunableOrdersStore.Delete(it.Key()) } - // Delete the key for prunable orders at this block height. - blockHeightToPotentiallyPrunableOrdersStore.Delete( - lib.Uint32ToKey(blockHeight), + return prunedOrderIds +} + +// MigratePruneableOrders is used to migrate prunable orders from key-per-height to key-per-order format. +func (k Keeper) MigratePruneableOrders(ctx sdk.Context) { + store := prefix.NewStore( + ctx.KVStore(k.storeKey), + []byte(types.LegacyBlockHeightToPotentiallyPrunableOrdersPrefix), // nolint:staticcheck ) + it := store.Iterator(nil, nil) + defer it.Close() - return prunedOrderIds + for ; it.Valid(); it.Next() { + if it.Value() == nil { + continue + } + + height := binary.BigEndian.Uint32(it.Key()) + var potentiallyPrunableOrders types.PotentiallyPrunableOrders + k.cdc.MustUnmarshal(it.Value(), &potentiallyPrunableOrders) + k.AddOrdersForPruning(ctx, potentiallyPrunableOrders.OrderIds, height) + store.Delete(it.Key()) + } } // RemoveOrderFillAmount removes the fill amount of an Order from state and the memstore. @@ -258,5 +286,27 @@ func (k Keeper) PruneStateFillAmountsForShortTermOrders( blockHeight := lib.MustConvertIntegerToUint32(ctx.BlockHeight()) // Prune all fill amounts from state which have a pruneable block height of the current `blockHeight`. - k.PruneOrdersForBlockHeight(ctx, blockHeight) + prunedOrderIds := k.PruneOrdersForBlockHeight(ctx, blockHeight) + + // Send an orderbook update for each pruned order for grpc streams. + // This is needed because short term orders are pruned in PrepareCheckState using + // keeper.MemClob.openOrders.blockExpirationsForOrders, which can fall out of sync with state fill amount + // pruning when there's replacement. + // Long-term fix would be to add logic to keep them in sync. + // TODO(CT-722): add logic to keep state fill amount pruning and order pruning in sync. + if k.GetGrpcStreamingManager().Enabled() { + allUpdates := types.NewOffchainUpdates() + for _, orderId := range prunedOrderIds { + if _, exists := k.MemClob.GetOrder(ctx, orderId); exists { + if message, success := off_chain_updates.CreateOrderUpdateMessage( + ctx, + orderId, + 0, // Total filled quantums is zero because it's been pruned from state. + ); success { + allUpdates.AddUpdateMessage(orderId, message) + } + } + } + k.SendOrderbookUpdates(ctx, allUpdates, false) + } } diff --git a/protocol/x/clob/keeper/order_state_test.go b/protocol/x/clob/keeper/order_state_test.go index 1ddf8e6026..5b3a60557b 100644 --- a/protocol/x/clob/keeper/order_state_test.go +++ b/protocol/x/clob/keeper/order_state_test.go @@ -1,12 +1,10 @@ package keeper_test import ( - "sort" "testing" "cosmossdk.io/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/mocks" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" @@ -262,99 +260,6 @@ func TestOrderFillAmountInitMemStore_Success(t *testing.T) { require.False(t, exists) } -func TestAddOrdersForPruning_Determinism(t *testing.T) { - memClob := &mocks.MemClob{} - memClob.On("SetClobKeeper", mock.Anything).Return() - - ks := keepertest.NewClobKeepersTestContext( - t, - memClob, - &mocks.BankKeeper{}, - &mocks.IndexerEventManager{}, - ) - - blockHeight := uint32(10) - - store := prefix.NewStore( - ks.Ctx.KVStore(ks.StoreKey), - []byte(types.BlockHeightToPotentiallyPrunableOrdersPrefix), - ) - - orders := []types.OrderId{ - constants.Order_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB15.OrderId, - constants.Order_Bob_Num0_Id0_Clob1_Sell10_Price15_GTB20.OrderId, - constants.Order_Alice_Num1_Id0_Clob0_Sell10_Price15_GTB20.OrderId, - constants.Order_Alice_Num0_Id1_Clob0_Sell10_Price15_GTB15.OrderId, - } - - expectedOrders := []types.OrderId{ - constants.Order_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB15.OrderId, - constants.Order_Alice_Num0_Id1_Clob0_Sell10_Price15_GTB15.OrderId, - constants.Order_Alice_Num1_Id0_Clob0_Sell10_Price15_GTB20.OrderId, - constants.Order_Bob_Num0_Id0_Clob1_Sell10_Price15_GTB20.OrderId, - } - - for i := 0; i < 100; i++ { - ks.ClobKeeper.AddOrdersForPruning( - ks.Ctx, - orders, - blockHeight, - ) - - potentiallyPrunableOrdersBytes := store.Get( - lib.Uint32ToKey(blockHeight), - ) - - var potentiallyPrunableOrders = &types.PotentiallyPrunableOrders{} - err := potentiallyPrunableOrders.Unmarshal(potentiallyPrunableOrdersBytes) - require.NoError(t, err) - - sort.Sort(types.SortedOrders(expectedOrders)) - for i, o := range potentiallyPrunableOrders.OrderIds { - require.Equal(t, o, expectedOrders[i]) - } - } -} - -func TestAddOrdersForPruning_DuplicateOrderIds(t *testing.T) { - memClob := &mocks.MemClob{} - memClob.On("SetClobKeeper", mock.Anything).Return() - ks := keepertest.NewClobKeepersTestContext( - t, - memClob, - &mocks.BankKeeper{}, - &mocks.IndexerEventManager{}, - ) - - blockHeight := uint32(10) - - ks.ClobKeeper.AddOrdersForPruning( - ks.Ctx, - []types.OrderId{ - constants.Order_Alice_Num0_Id1_Clob0_Sell10_Price15_GTB15.OrderId, - constants.Order_Alice_Num0_Id1_Clob0_Sell10_Price15_GTB15.OrderId, - constants.Order_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15.OrderId, - constants.Order_Alice_Num0_Id2_Clob1_Sell5_Price10_GTB15.OrderId, - }, - blockHeight, - ) - - store := prefix.NewStore( - ks.Ctx.KVStore(ks.StoreKey), - []byte(types.BlockHeightToPotentiallyPrunableOrdersPrefix), - ) - - potentiallyPrunableOrdersBytes := store.Get( - lib.Uint32ToKey(blockHeight), - ) - - var potentiallyPrunableOrders = &types.PotentiallyPrunableOrders{} - err := potentiallyPrunableOrders.Unmarshal(potentiallyPrunableOrdersBytes) - require.NoError(t, err) - - require.Len(t, potentiallyPrunableOrders.OrderIds, 2) -} - func TestPruning(t *testing.T) { tests := map[string]struct { // Setup. @@ -571,22 +476,78 @@ func TestPruning(t *testing.T) { require.Equal(t, prunableBlockHeight, tc.expectedPrunableBlockHeight) } - // Verify that expected `blockHeightToPotentiallyPrunableOrdersStore` were deleted. - blockHeightToPotentiallyPrunableOrdersStore := prefix.NewStore( - ks.Ctx.KVStore(ks.StoreKey), - []byte(types.BlockHeightToPotentiallyPrunableOrdersPrefix), - ) - + // Verify all prune order keys were deleted for specified heights for _, blockHeight := range tc.expectedEmptyPotentiallyPrunableOrderBlockHeights { - has := blockHeightToPotentiallyPrunableOrdersStore.Has( - lib.Uint32ToKey(blockHeight), - ) - require.False(t, has) + potentiallyPrunableOrdersStore := ks.ClobKeeper.GetPruneableOrdersStore(ks.Ctx, blockHeight) + it := potentiallyPrunableOrdersStore.Iterator(nil, nil) + defer it.Close() + require.False(t, it.Valid()) } }) } } +func TestMigratePruneableOrders(t *testing.T) { + memClob := &mocks.MemClob{} + memClob.On("SetClobKeeper", mock.Anything).Return() + + ks := keepertest.NewClobKeepersTestContext( + t, + memClob, + &mocks.BankKeeper{}, + &mocks.IndexerEventManager{}, + ) + + ordersA := []types.OrderId{ + constants.Order_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB15.OrderId, + constants.Order_Alice_Num1_Id0_Clob0_Sell10_Price15_GTB20.OrderId, + constants.Order_Alice_Num0_Id1_Clob0_Sell10_Price15_GTB15.OrderId, + } + ordersB := []types.OrderId{ + constants.Order_Alice_Num0_Id0_Clob0_Buy5_Price10_GTB15.OrderId, + constants.Order_Bob_Num0_Id0_Clob1_Sell10_Price15_GTB20.OrderId, + constants.Order_Alice_Num1_Id0_Clob0_Sell10_Price15_GTB20.OrderId, + } + + ks.ClobKeeper.LegacyAddOrdersForPruning( + ks.Ctx, + ordersA, + 10, + ) + ks.ClobKeeper.LegacyAddOrdersForPruning( + ks.Ctx, + ordersB, + 100, + ) + + ks.ClobKeeper.MigratePruneableOrders(ks.Ctx) + + getPostMigrationOrdersAtHeight := func(height uint32) []types.OrderId { + postMigrationOrders := []types.OrderId{} + store := ks.ClobKeeper.GetPruneableOrdersStore(ks.Ctx, height) + it := store.Iterator(nil, nil) + defer it.Close() + for ; it.Valid(); it.Next() { + var orderId types.OrderId + err := orderId.Unmarshal(it.Value()) + require.NoError(t, err) + postMigrationOrders = append(postMigrationOrders, orderId) + } + return postMigrationOrders + } + + require.ElementsMatch(t, ordersA, getPostMigrationOrdersAtHeight(10)) + require.ElementsMatch(t, ordersB, getPostMigrationOrdersAtHeight(100)) + + oldStore := prefix.NewStore( + ks.Ctx.KVStore(ks.StoreKey), + []byte(types.LegacyBlockHeightToPotentiallyPrunableOrdersPrefix), // nolint:staticcheck + ) + it := oldStore.Iterator(nil, nil) + defer it.Close() + require.False(t, it.Valid()) +} + func TestRemoveOrderFillAmount(t *testing.T) { tests := map[string]struct { // Setup. diff --git a/protocol/x/clob/keeper/orders_test.go b/protocol/x/clob/keeper/orders_test.go index 0912d2be24..da10a4b948 100644 --- a/protocol/x/clob/keeper/orders_test.go +++ b/protocol/x/clob/keeper/orders_test.go @@ -57,7 +57,10 @@ func TestPlaceShortTermOrder(t *testing.T) { expectedMultiStoreWrites []string expectedOrderStatus types.OrderStatus expectedFilledSize satypes.BaseQuantums - expectedErr error + // Expected remaining OI after test. + // The test initializes each perp with default open interest of 1 full coin. + expectedOpenInterests map[uint32]*big.Int + expectedErr error }{ "Can place an order on the orderbook closing a position": { perpetuals: []perptypes.Perpetual{ @@ -73,6 +76,10 @@ func TestPlaceShortTermOrder(t *testing.T) { expectedOrderStatus: types.Success, expectedFilledSize: 0, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Can place an order on the orderbook in a different market than their current perpetual position": { perpetuals: []perptypes.Perpetual{ @@ -92,6 +99,10 @@ func TestPlaceShortTermOrder(t *testing.T) { expectedOrderStatus: types.Success, expectedFilledSize: 0, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Can place an order and the order is fully matched": { perpetuals: []perptypes.Perpetual{ @@ -130,18 +141,22 @@ func TestPlaceShortTermOrder(t *testing.T) { // Update block stats statstypes.BlockStatsKey, // Update prunable block height for taker fill amount - types.BlockHeightToPotentiallyPrunableOrdersPrefix, + types.PrunableOrdersKeyPrefix, // Update taker order fill amount types.OrderAmountFilledKeyPrefix, // Update taker order fill amount in memStore types.OrderAmountFilledKeyPrefix, // Update prunable block height for maker fill amount - types.BlockHeightToPotentiallyPrunableOrdersPrefix, + types.PrunableOrdersKeyPrefix, // Update maker order fill amount types.OrderAmountFilledKeyPrefix, // Update maker order fill amount in memStore types.OrderAmountFilledKeyPrefix, }, + expectedOpenInterests: map[uint32]*big.Int{ + // positions fully closed + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(0), + }, }, "Cannot place an order on the orderbook if the account would be undercollateralized": { perpetuals: []perptypes.Perpetual{ @@ -161,6 +176,10 @@ func TestPlaceShortTermOrder(t *testing.T) { expectedOrderStatus: types.Undercollateralized, expectedFilledSize: 0, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Can place an order on the orderbook if the subaccount is right at the initial margin ratio": { perpetuals: []perptypes.Perpetual{ @@ -178,6 +197,10 @@ func TestPlaceShortTermOrder(t *testing.T) { expectedOrderStatus: types.Success, expectedFilledSize: 0, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Cannot place an order on the orderbook if the account would be undercollateralized due to fees paid": { perpetuals: []perptypes.Perpetual{ @@ -196,6 +219,10 @@ func TestPlaceShortTermOrder(t *testing.T) { expectedOrderStatus: types.Undercollateralized, expectedFilledSize: 0, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Can place an order on the orderbook if the account would be collateralized due to rebate": { perpetuals: []perptypes.Perpetual{ @@ -215,6 +242,10 @@ func TestPlaceShortTermOrder(t *testing.T) { expectedOrderStatus: types.Success, expectedFilledSize: 0, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Cannot open an order if it doesn't reference a valid CLOB": { perpetuals: []perptypes.Perpetual{ @@ -232,6 +263,10 @@ func TestPlaceShortTermOrder(t *testing.T) { order: constants.Order_Carl_Num0_Id3_Clob1_Buy1ETH_Price3000, expectedErr: types.ErrInvalidClob, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Cannot open an order if the subticks are invalid": { perpetuals: []perptypes.Perpetual{ @@ -254,6 +289,10 @@ func TestPlaceShortTermOrder(t *testing.T) { }, expectedErr: types.ErrInvalidPlaceOrder, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Cannot open an order that is smaller than the minimum base quantums": { perpetuals: []perptypes.Perpetual{ @@ -276,6 +315,10 @@ func TestPlaceShortTermOrder(t *testing.T) { }, expectedErr: types.ErrInvalidPlaceOrder, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Cannot open an order that is not divisible by the step size base quantums": { perpetuals: []perptypes.Perpetual{ @@ -296,6 +339,10 @@ func TestPlaceShortTermOrder(t *testing.T) { }, expectedErr: types.ErrInvalidPlaceOrder, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Cannot open an order with a GoodTilBlock in the past": { perpetuals: []perptypes.Perpetual{ @@ -317,6 +364,10 @@ func TestPlaceShortTermOrder(t *testing.T) { }, expectedErr: types.ErrHeightExceedsGoodTilBlock, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, "Cannot open an order with a GoodTilBlock greater than ShortBlockWindow blocks in the future": { perpetuals: []perptypes.Perpetual{ @@ -338,6 +389,10 @@ func TestPlaceShortTermOrder(t *testing.T) { }, expectedErr: types.ErrGoodTilBlockExceedsShortBlockWindow, + expectedOpenInterests: map[uint32]*big.Int{ + // unchanged, no match happened + constants.BtcUsd_SmallMarginRequirement.Params.Id: big.NewInt(100_000_000), + }, }, // This is a regression test for an issue whereby orders that had been previously matched were being checked for // collateralization as if the subticks of the order were `0`. This resulted in always using `0` @@ -401,6 +456,10 @@ func TestPlaceShortTermOrder(t *testing.T) { // and a perpetual of size 0.01 BTC ($500), and the perpetual has a 100% margin requirement. order: constants.Order_Carl_Num1_Id1_Clob0_Buy1kQtBTC_Price50000, expectedOrderStatus: types.Undercollateralized, + expectedOpenInterests: map[uint32]*big.Int{ + // 1 BTC + 0.01 BTC filled + constants.BtcUsd_100PercentMarginRequirement.Params.Id: big.NewInt(101_000_000), + }, }, `New order should be undercollateralized when matching when previous fills make it undercollateralized when using maker orders subticks, but would be collateralized if using taker order subticks`: { @@ -446,18 +505,22 @@ func TestPlaceShortTermOrder(t *testing.T) { // Update block stats statstypes.BlockStatsKey, // Update prunable block height for taker fill amount - types.BlockHeightToPotentiallyPrunableOrdersPrefix, + types.PrunableOrdersKeyPrefix, // Update taker order fill amount types.OrderAmountFilledKeyPrefix, // Update taker order fill amount in memStore types.OrderAmountFilledKeyPrefix, // Update prunable block height for maker fill amount - types.BlockHeightToPotentiallyPrunableOrdersPrefix, + types.PrunableOrdersKeyPrefix, // Update maker order fill amount types.OrderAmountFilledKeyPrefix, // Update maker order fill amount in memStore types.OrderAmountFilledKeyPrefix, }, + expectedOpenInterests: map[uint32]*big.Int{ + // 1 BTC + 0.01 BTC + 0.01 BTC filled + constants.BtcUsd_50PercentInitial_40PercentMaintenance.Params.Id: big.NewInt(102_000_000), + }, }, // This is a regression test for an issue whereby orders that had been previously matched were being checked for // collateralization as if the CLOB pair ID of the order was `0`. This resulted in always using `0` @@ -508,6 +571,12 @@ func TestPlaceShortTermOrder(t *testing.T) { // quantums required to open the previous buy order and fail. order: constants.Order_Carl_Num0_Id4_Clob1_Buy01ETH_Price3000, expectedOrderStatus: types.Success, + expectedOpenInterests: map[uint32]*big.Int{ + // Unchanged, no BTC match happened + constants.BtcUsd_NoMarginRequirement.Params.Id: big.NewInt(100_000_000), + // 1 ETH + 1 ETH filled + constants.EthUsd_20PercentInitial_10PercentMaintenance.Params.Id: big.NewInt(2_000_000_000), + }, }, `Subaccount cannot place maker buy order for 1 BTC at 5 subticks with 0 collateral`: { perpetuals: []perptypes.Perpetual{ @@ -753,6 +822,19 @@ func TestPlaceShortTermOrder(t *testing.T) { } traceDecoder.RequireKeyPrefixesWritten(t, tc.expectedMultiStoreWrites) + + for _, perp := range tc.perpetuals { + if expectedOI, exists := tc.expectedOpenInterests[perp.Params.Id]; exists { + gotPerp, err := ks.PerpetualsKeeper.GetPerpetual(ks.Ctx, perp.Params.Id) + require.NoError(t, err) + require.Zero(t, + expectedOI.Cmp(gotPerp.OpenInterest.BigInt()), + "expected open interest %s, got %s", + expectedOI.String(), + gotPerp.OpenInterest.String(), + ) + } + } }) } } diff --git a/protocol/x/clob/memclob/memclob.go b/protocol/x/clob/memclob/memclob.go index 11579c6488..87c1d3b652 100644 --- a/protocol/x/clob/memclob/memclob.go +++ b/protocol/x/clob/memclob/memclob.go @@ -1523,7 +1523,7 @@ func (m *MemClobPriceTimePriority) mustAddOrderToOrderbook( if m.generateOrderbookUpdates { // Send an orderbook update to grpc streams. orderbookUpdate := m.GetOrderbookUpdatesForOrderPlacement(ctx, newOrder) - m.clobKeeper.SendOrderbookUpdates(orderbookUpdate, false) + m.clobKeeper.SendOrderbookUpdates(ctx, orderbookUpdate, false) } } @@ -1963,7 +1963,7 @@ func (m *MemClobPriceTimePriority) mustRemoveOrder( if m.generateOrderbookUpdates { // Send an orderbook update to grpc streams. orderbookUpdate := m.GetOrderbookUpdatesForOrderRemoval(ctx, order.OrderId) - m.clobKeeper.SendOrderbookUpdates(orderbookUpdate, false) + m.clobKeeper.SendOrderbookUpdates(ctx, orderbookUpdate, false) } } @@ -1985,7 +1985,7 @@ func (m *MemClobPriceTimePriority) mustUpdateOrderbookStateWithMatchedMakerOrder // Send an orderbook update for the order's new total filled amount. if m.generateOrderbookUpdates { orderbookUpdate := m.GetOrderbookUpdatesForOrderUpdate(ctx, makerOrder.OrderId) - m.clobKeeper.SendOrderbookUpdates(orderbookUpdate, false) + m.clobKeeper.SendOrderbookUpdates(ctx, orderbookUpdate, false) } // If the order is fully filled, remove it from the orderbook. diff --git a/protocol/x/clob/types/clob_keeper.go b/protocol/x/clob/types/clob_keeper.go index d96b046ad5..6cd6a8bd5f 100644 --- a/protocol/x/clob/types/clob_keeper.go +++ b/protocol/x/clob/types/clob_keeper.go @@ -147,7 +147,9 @@ type ClobKeeper interface { // Gprc streaming InitializeNewGrpcStreams(ctx sdk.Context) SendOrderbookUpdates( + ctx sdk.Context, offchainUpdates *OffchainUpdates, snapshot bool, ) + MigratePruneableOrders(ctx sdk.Context) } diff --git a/protocol/x/clob/types/keys.go b/protocol/x/clob/types/keys.go index 4231f02576..71a681832f 100644 --- a/protocol/x/clob/types/keys.go +++ b/protocol/x/clob/types/keys.go @@ -26,6 +26,9 @@ const ( // conditional orders. It represents all stateful orders that should be placed upon the memclob // during app start up. PlacedStatefulOrderKeyPrefix = StatefulOrderKeyPrefix + "P/" + + // PrunableOrdersKeyPrefix is the prefix key for orders prunable at a certain height. + PrunableOrdersKeyPrefix = "PO/" ) // State @@ -45,9 +48,10 @@ const ( // OrderAmountFilledKeyPrefix is the prefix to retrieve the fill amount for an order. OrderAmountFilledKeyPrefix = "Fill:" - // BlockHeightToPotentiallyPrunableOrdersPrefix is the prefix to retrieve a list of potentially prunable - // short term orders by block height. - BlockHeightToPotentiallyPrunableOrdersPrefix = "ExpHt:" + // Deprecated: LegacyBlockHeightToPotentiallyPrunableOrdersPrefix is the prefix to retrieve a list of + // potentially prunable short term orders by block height. Should not be used after migrating to + // key-per-order format. + LegacyBlockHeightToPotentiallyPrunableOrdersPrefix = "ExpHt:" // StatefulOrdersTimeSlicePrefix is the key to retrieve a unique list of the stateful orders that // expire at a given timestamp, sorted by order ID. diff --git a/protocol/x/clob/types/keys_test.go b/protocol/x/clob/types/keys_test.go index 225d930775..dc082a3b57 100644 --- a/protocol/x/clob/types/keys_test.go +++ b/protocol/x/clob/types/keys_test.go @@ -24,7 +24,7 @@ func TestStateKeys(t *testing.T) { require.Equal(t, "Clob:", types.ClobPairKeyPrefix) require.Equal(t, "Fill:", types.OrderAmountFilledKeyPrefix) - require.Equal(t, "ExpHt:", types.BlockHeightToPotentiallyPrunableOrdersPrefix) + require.Equal(t, "ExpHt:", types.LegacyBlockHeightToPotentiallyPrunableOrdersPrefix) require.Equal(t, "ExpTm:", types.StatefulOrdersTimeSlicePrefix) } diff --git a/protocol/x/clob/types/mem_clob_keeper.go b/protocol/x/clob/types/mem_clob_keeper.go index 7ca02f2569..ecb63403e9 100644 --- a/protocol/x/clob/types/mem_clob_keeper.go +++ b/protocol/x/clob/types/mem_clob_keeper.go @@ -116,6 +116,7 @@ type MemClobKeeper interface { ctx sdk.Context, ) log.Logger SendOrderbookUpdates( + ctx sdk.Context, offchainUpdates *OffchainUpdates, snapshot bool, ) diff --git a/protocol/x/clob/types/query.pb.go b/protocol/x/clob/types/query.pb.go index 7f48a50310..ee500746b5 100644 --- a/protocol/x/clob/types/query.pb.go +++ b/protocol/x/clob/types/query.pb.go @@ -711,6 +711,11 @@ type StreamOrderbookUpdatesResponse struct { // Note that if the snapshot is true, then all previous entries should be // discarded and the orderbook should be resynced. Snapshot bool `protobuf:"varint,2,opt,name=snapshot,proto3" json:"snapshot,omitempty"` + // ---Additional fields used to debug issues--- + // Block height of the updates. + BlockHeight uint32 `protobuf:"varint,3,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + // Exec mode of the updates. + ExecMode uint32 `protobuf:"varint,4,opt,name=exec_mode,json=execMode,proto3" json:"exec_mode,omitempty"` } func (m *StreamOrderbookUpdatesResponse) Reset() { *m = StreamOrderbookUpdatesResponse{} } @@ -760,6 +765,20 @@ func (m *StreamOrderbookUpdatesResponse) GetSnapshot() bool { return false } +func (m *StreamOrderbookUpdatesResponse) GetBlockHeight() uint32 { + if m != nil { + return m.BlockHeight + } + return 0 +} + +func (m *StreamOrderbookUpdatesResponse) GetExecMode() uint32 { + if m != nil { + return m.ExecMode + } + return 0 +} + func init() { proto.RegisterType((*QueryGetClobPairRequest)(nil), "dydxprotocol.clob.QueryGetClobPairRequest") proto.RegisterType((*QueryClobPairResponse)(nil), "dydxprotocol.clob.QueryClobPairResponse") @@ -781,75 +800,77 @@ func init() { func init() { proto.RegisterFile("dydxprotocol/clob/query.proto", fileDescriptor_3365c195b25c5bc0) } var fileDescriptor_3365c195b25c5bc0 = []byte{ - // 1073 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xcf, 0x6f, 0xdc, 0x44, - 0x14, 0xce, 0x24, 0xa5, 0x4d, 0xa7, 0x80, 0x60, 0xd2, 0xa4, 0x8b, 0x93, 0x6c, 0xb6, 0x86, 0x24, - 0x9b, 0x54, 0xd8, 0x49, 0x5a, 0xa1, 0x92, 0xa2, 0x4a, 0x49, 0x04, 0x11, 0x52, 0x43, 0x17, 0x53, - 0x02, 0x82, 0x4a, 0xd6, 0xac, 0x3d, 0xbb, 0x19, 0xc5, 0xf6, 0x38, 0xf6, 0xd8, 0x4a, 0x84, 0x10, - 0x12, 0x07, 0x2e, 0x70, 0x40, 0x42, 0x82, 0x03, 0x47, 0xee, 0xfc, 0x07, 0x08, 0xb8, 0xf5, 0x58, - 0x89, 0x0b, 0x07, 0x84, 0x50, 0xc2, 0x99, 0xbf, 0x01, 0x79, 0x3c, 0xbb, 0x5d, 0xc7, 0x3f, 0x36, - 0xc9, 0x65, 0xd7, 0x9e, 0xf9, 0xde, 0xf3, 0xf7, 0xbd, 0xf7, 0xfc, 0x8d, 0xe1, 0xac, 0x7d, 0x64, - 0x1f, 0xfa, 0x01, 0xe3, 0xcc, 0x62, 0x8e, 0x6e, 0x39, 0xac, 0xad, 0x1f, 0x44, 0x24, 0x38, 0xd2, - 0xc4, 0x1a, 0x7a, 0x79, 0x70, 0x5b, 0x4b, 0xb6, 0x95, 0xeb, 0x5d, 0xd6, 0x65, 0x62, 0x49, 0x4f, - 0xae, 0x52, 0xa0, 0x32, 0xd3, 0x65, 0xac, 0xeb, 0x10, 0x1d, 0xfb, 0x54, 0xc7, 0x9e, 0xc7, 0x38, - 0xe6, 0x94, 0x79, 0xa1, 0xdc, 0x5d, 0xb6, 0x58, 0xe8, 0xb2, 0x50, 0x6f, 0xe3, 0x90, 0xa4, 0xf9, - 0xf5, 0x78, 0xb5, 0x4d, 0x38, 0x5e, 0xd5, 0x7d, 0xdc, 0xa5, 0x9e, 0x00, 0x4b, 0xac, 0x9e, 0x67, - 0xd4, 0x76, 0x98, 0xb5, 0x6f, 0x06, 0x98, 0x13, 0xd3, 0xa1, 0x2e, 0xe5, 0xa6, 0xc5, 0xbc, 0x0e, - 0xed, 0xca, 0x80, 0x9b, 0xf9, 0x80, 0xe4, 0xc7, 0xf4, 0x31, 0x0d, 0x24, 0x64, 0x25, 0x0f, 0x21, - 0x07, 0x11, 0xe5, 0x47, 0x26, 0xa7, 0x24, 0x28, 0x4a, 0x7a, 0x2b, 0x1f, 0xe1, 0xd0, 0x83, 0x88, - 0xda, 0xa9, 0xae, 0x2c, 0x78, 0x3a, 0x0f, 0x76, 0x49, 0x2c, 0x37, 0xef, 0x67, 0x36, 0xa9, 0x67, - 0x93, 0x43, 0x12, 0xe8, 0xac, 0xd3, 0x31, 0xad, 0x3d, 0x4c, 0x3d, 0x33, 0xf2, 0x6d, 0xcc, 0x49, - 0x98, 0x5f, 0x49, 0xe3, 0xd5, 0x25, 0x78, 0xe3, 0xfd, 0xa4, 0x62, 0xdb, 0x84, 0x6f, 0x39, 0xac, - 0xdd, 0xc2, 0x34, 0x30, 0xc8, 0x41, 0x44, 0x42, 0x8e, 0x5e, 0x84, 0xa3, 0xd4, 0xae, 0x81, 0x06, - 0x68, 0xbe, 0x60, 0x8c, 0x52, 0x5b, 0xfd, 0x08, 0x4e, 0x0a, 0xe8, 0x33, 0x5c, 0xe8, 0x33, 0x2f, - 0x24, 0xe8, 0x3e, 0xbc, 0xda, 0x2f, 0x89, 0xc0, 0x5f, 0x5b, 0x9b, 0xd6, 0x72, 0xad, 0xd5, 0x7a, - 0x71, 0x9b, 0x97, 0x9e, 0xfc, 0x3d, 0x37, 0x62, 0x8c, 0x5b, 0xf2, 0x5e, 0xc5, 0x92, 0xc3, 0x86, - 0xe3, 0x9c, 0xe6, 0xf0, 0x0e, 0x84, 0xcf, 0x5a, 0x28, 0x73, 0x2f, 0x68, 0x69, 0xbf, 0xb5, 0xa4, - 0xdf, 0x5a, 0x3a, 0x4f, 0xb2, 0xdf, 0x5a, 0x0b, 0x77, 0x89, 0x8c, 0x35, 0x06, 0x22, 0xd5, 0x9f, - 0x00, 0xac, 0x65, 0xc8, 0x6f, 0x38, 0x4e, 0x19, 0xff, 0xb1, 0x73, 0xf2, 0x47, 0xdb, 0x19, 0x92, - 0xa3, 0x82, 0xe4, 0xe2, 0x50, 0x92, 0xe9, 0xc3, 0x33, 0x2c, 0xff, 0x02, 0x70, 0x6e, 0x87, 0xc4, - 0xef, 0x31, 0x9b, 0x3c, 0x62, 0xc9, 0xef, 0x16, 0x76, 0xac, 0xc8, 0x11, 0x9b, 0xbd, 0x8a, 0x3c, - 0x86, 0x53, 0xe9, 0xc0, 0xfa, 0x01, 0xf3, 0x59, 0x48, 0x02, 0xd3, 0xc5, 0xdc, 0xda, 0x23, 0x61, - 0xbf, 0x3a, 0x79, 0xe6, 0xbb, 0xd8, 0x49, 0x46, 0x8b, 0x05, 0x3b, 0x24, 0xde, 0x49, 0xd1, 0xc6, - 0x75, 0x91, 0xa5, 0x25, 0x93, 0xc8, 0x55, 0xf4, 0x29, 0x9c, 0x8c, 0x7b, 0x60, 0xd3, 0x25, 0xb1, - 0xe9, 0x12, 0x1e, 0x50, 0x2b, 0xec, 0xab, 0xca, 0x27, 0xcf, 0x10, 0xde, 0x49, 0xe1, 0xc6, 0x44, - 0x3c, 0xf8, 0xc8, 0x74, 0x51, 0xfd, 0x0f, 0xc0, 0x46, 0xb9, 0x3c, 0xd9, 0x8c, 0x2e, 0xbc, 0x12, - 0x90, 0x30, 0x72, 0x78, 0x28, 0x5b, 0xb1, 0x3d, 0xec, 0x99, 0x05, 0x59, 0x12, 0xc0, 0x86, 0x67, - 0xef, 0x32, 0x27, 0x72, 0x49, 0x8b, 0x04, 0x49, 0xeb, 0x64, 0xdb, 0x7a, 0xd9, 0x15, 0x0c, 0x27, - 0x0a, 0x50, 0xa8, 0x01, 0x9f, 0xef, 0x0f, 0x83, 0xd9, 0x9f, 0x7f, 0xd8, 0x6b, 0xf6, 0xbb, 0x36, - 0x7a, 0x09, 0x8e, 0xb9, 0x24, 0x16, 0x15, 0x19, 0x35, 0x92, 0x4b, 0x34, 0x05, 0x2f, 0xc7, 0x22, - 0x49, 0x6d, 0xac, 0x01, 0x9a, 0x97, 0x0c, 0x79, 0xa7, 0x2e, 0xc3, 0xa6, 0x18, 0xba, 0xb7, 0x85, - 0x1b, 0x3c, 0xa2, 0x24, 0x78, 0x90, 0x78, 0xc1, 0x96, 0x78, 0xbb, 0xa3, 0x60, 0xb0, 0xaf, 0xea, - 0x8f, 0x00, 0x2e, 0x9d, 0x01, 0x2c, 0xab, 0xe4, 0xc1, 0x5a, 0x99, 0xc5, 0xc8, 0x39, 0xd0, 0x0b, - 0xca, 0x56, 0x95, 0x5a, 0x96, 0x67, 0x92, 0x14, 0x61, 0xd4, 0x25, 0xb8, 0x28, 0xc8, 0x6d, 0x26, - 0x43, 0x63, 0x60, 0x4e, 0xca, 0x85, 0xfc, 0x00, 0xa4, 0xea, 0x4a, 0xac, 0xd4, 0xb1, 0x0f, 0x6f, - 0x94, 0xd8, 0xaf, 0x94, 0xa1, 0x15, 0xc8, 0xa8, 0x48, 0x2c, 0x55, 0xa4, 0xc3, 0x7d, 0x0a, 0xa2, - 0x2e, 0xc2, 0x79, 0x41, 0xec, 0xc1, 0x80, 0xd5, 0x16, 0x4a, 0xf8, 0x0a, 0xc0, 0x85, 0x61, 0x48, - 0x29, 0xe0, 0x31, 0x9c, 0x28, 0x70, 0x6e, 0x49, 0x7e, 0xbe, 0x80, 0x7c, 0x3e, 0xa5, 0xe4, 0x8c, - 0x9c, 0xdc, 0x8e, 0xba, 0x01, 0x67, 0x3f, 0xe0, 0x01, 0xc1, 0xee, 0xc3, 0xc0, 0x26, 0x41, 0x9b, - 0xb1, 0xfd, 0x0f, 0x53, 0xf7, 0xee, 0xb9, 0x41, 0x7e, 0x5a, 0xc7, 0xb2, 0xd3, 0xaa, 0x7e, 0x0f, - 0x60, 0xbd, 0x2c, 0x87, 0xd4, 0xf0, 0x31, 0xbc, 0x22, 0x0f, 0x05, 0xf9, 0xca, 0xdd, 0xcd, 0xf2, - 0x96, 0xa7, 0x8a, 0x96, 0x3f, 0x43, 0x1e, 0x76, 0x3a, 0x5b, 0xc9, 0x42, 0x9a, 0x71, 0x77, 0xb5, - 0xf7, 0x8e, 0xc9, 0x7d, 0xa4, 0xc0, 0xf1, 0xd0, 0xc3, 0x7e, 0xb8, 0xc7, 0xb8, 0x78, 0x5f, 0xc6, - 0x8d, 0xfe, 0xfd, 0xda, 0xcf, 0x57, 0xe1, 0x73, 0xa2, 0xc8, 0xe8, 0x6b, 0x00, 0xc7, 0x7b, 0xe6, - 0x8a, 0x96, 0x0b, 0x6a, 0x56, 0x72, 0x42, 0x29, 0xcd, 0x32, 0xec, 0xe9, 0x23, 0x4a, 0x5d, 0xfa, - 0xf2, 0x8f, 0x7f, 0xbf, 0x1b, 0x7d, 0x15, 0xdd, 0xd4, 0x2b, 0x8e, 0x73, 0xfd, 0x33, 0x6a, 0x7f, - 0x8e, 0xbe, 0x01, 0xf0, 0xda, 0xc0, 0x29, 0x51, 0x4e, 0x28, 0x7f, 0x5c, 0x29, 0xb7, 0x86, 0x11, - 0x1a, 0x38, 0x76, 0xd4, 0xd7, 0x04, 0xa7, 0x3a, 0x9a, 0xa9, 0xe2, 0x84, 0x7e, 0x05, 0xb0, 0x56, - 0x66, 0x77, 0x68, 0xed, 0x5c, 0xde, 0x98, 0x72, 0xbc, 0x7d, 0x01, 0x3f, 0x55, 0xd7, 0x05, 0xd7, - 0x3b, 0xeb, 0x60, 0x59, 0xd5, 0xf5, 0xc2, 0xef, 0x11, 0xd3, 0x63, 0x36, 0x31, 0x39, 0x4b, 0xff, - 0xad, 0x01, 0x92, 0xbf, 0x03, 0x38, 0x53, 0xe5, 0x3c, 0xe8, 0x5e, 0x59, 0xd5, 0xce, 0xe0, 0x9b, - 0xca, 0x5b, 0x17, 0x0b, 0x96, 0xba, 0x16, 0x84, 0xae, 0x06, 0xaa, 0xeb, 0x95, 0xdf, 0x70, 0xe8, - 0x17, 0x00, 0xa7, 0x2b, 0x6c, 0x07, 0xad, 0x97, 0xb1, 0x18, 0x6e, 0x98, 0xca, 0xbd, 0x0b, 0xc5, - 0x4a, 0x01, 0xf3, 0x42, 0xc0, 0x1c, 0x9a, 0xad, 0xfc, 0xb0, 0x45, 0xbf, 0x01, 0xf8, 0x4a, 0xa9, - 0x99, 0xa1, 0xbb, 0x65, 0x0c, 0x86, 0x39, 0xa5, 0xf2, 0xe6, 0x05, 0x22, 0x25, 0x73, 0x4d, 0x30, - 0x6f, 0xa2, 0x05, 0xfd, 0x4c, 0x1f, 0xc3, 0xe8, 0x0b, 0x38, 0x55, 0xec, 0x63, 0x68, 0xa5, 0x80, - 0x44, 0xa5, 0x6d, 0x2a, 0xab, 0xe7, 0x88, 0x48, 0xe9, 0xae, 0x80, 0xcd, 0xd6, 0x93, 0xe3, 0x3a, - 0x78, 0x7a, 0x5c, 0x07, 0xff, 0x1c, 0xd7, 0xc1, 0xb7, 0x27, 0xf5, 0x91, 0xa7, 0x27, 0xf5, 0x91, - 0x3f, 0x4f, 0xea, 0x23, 0x9f, 0xbc, 0xd1, 0xa5, 0x7c, 0x2f, 0x6a, 0x6b, 0x16, 0x73, 0xb3, 0x62, - 0xe2, 0x3b, 0xaf, 0x0b, 0xc3, 0xd4, 0xfb, 0x2b, 0x87, 0xa9, 0x40, 0x7e, 0xe4, 0x93, 0xb0, 0x7d, - 0x59, 0x2c, 0xdf, 0xfe, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x3b, 0xb1, 0xe8, 0xed, 0x27, 0x0d, 0x00, - 0x00, + // 1112 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xcd, 0x4f, 0x24, 0x45, + 0x14, 0xa7, 0x00, 0x77, 0xe1, 0xb1, 0x6b, 0xb4, 0x58, 0xd8, 0x71, 0x80, 0x01, 0x5a, 0xf9, 0xdc, + 0xd8, 0x0d, 0xec, 0xc6, 0xac, 0xac, 0xd9, 0x04, 0x88, 0xa2, 0xc9, 0xe2, 0x62, 0xbb, 0xa2, 0xd1, + 0x4d, 0x3a, 0x35, 0xdd, 0xc5, 0x4c, 0x85, 0xee, 0xae, 0xa1, 0xbb, 0xa6, 0x03, 0x31, 0xc6, 0xc4, + 0x83, 0x17, 0x3d, 0x98, 0x78, 0xf0, 0xe0, 0xd1, 0xbb, 0xff, 0x81, 0x51, 0x6f, 0x7b, 0xdc, 0xc4, + 0xc4, 0x78, 0x30, 0xc6, 0x80, 0x67, 0xff, 0x86, 0x4d, 0x57, 0xd7, 0xcc, 0xce, 0xd0, 0x1f, 0x03, + 0x5c, 0x66, 0xba, 0x5f, 0xfd, 0xde, 0xeb, 0xdf, 0xfb, 0xa8, 0x5f, 0x15, 0x4c, 0x39, 0xc7, 0xce, + 0x51, 0x23, 0xe0, 0x82, 0xdb, 0xdc, 0x35, 0x6c, 0x97, 0x57, 0x8d, 0xc3, 0x26, 0x0d, 0x8e, 0x75, + 0x69, 0xc3, 0x2f, 0x77, 0x2e, 0xeb, 0xf1, 0x72, 0xf9, 0x46, 0x8d, 0xd7, 0xb8, 0x34, 0x19, 0xf1, + 0x53, 0x02, 0x2c, 0x4f, 0xd6, 0x38, 0xaf, 0xb9, 0xd4, 0x20, 0x0d, 0x66, 0x10, 0xdf, 0xe7, 0x82, + 0x08, 0xc6, 0xfd, 0x50, 0xad, 0x2e, 0xdb, 0x3c, 0xf4, 0x78, 0x68, 0x54, 0x49, 0x48, 0x93, 0xf8, + 0x46, 0xb4, 0x5a, 0xa5, 0x82, 0xac, 0x1a, 0x0d, 0x52, 0x63, 0xbe, 0x04, 0x2b, 0xac, 0x91, 0x66, + 0x54, 0x75, 0xb9, 0x7d, 0x60, 0x05, 0x44, 0x50, 0xcb, 0x65, 0x1e, 0x13, 0x96, 0xcd, 0xfd, 0x7d, + 0x56, 0x53, 0x0e, 0xb3, 0x69, 0x87, 0xf8, 0xc7, 0x6a, 0x10, 0x16, 0x28, 0xc8, 0x4a, 0x1a, 0x42, + 0x0f, 0x9b, 0x4c, 0x1c, 0x5b, 0x82, 0xd1, 0x20, 0x2b, 0xe8, 0xad, 0xb4, 0x87, 0xcb, 0x0e, 0x9b, + 0xcc, 0x49, 0xf2, 0xea, 0x06, 0x4f, 0xa4, 0xc1, 0x1e, 0x8d, 0xd4, 0xe2, 0xfd, 0xae, 0x45, 0xe6, + 0x3b, 0xf4, 0x88, 0x06, 0x06, 0xdf, 0xdf, 0xb7, 0xec, 0x3a, 0x61, 0xbe, 0xd5, 0x6c, 0x38, 0x44, + 0xd0, 0x30, 0x6d, 0x49, 0xfc, 0xb5, 0x25, 0xb8, 0xf9, 0x41, 0x5c, 0xb1, 0x6d, 0x2a, 0xb6, 0x5c, + 0x5e, 0xdd, 0x25, 0x2c, 0x30, 0xe9, 0x61, 0x93, 0x86, 0x02, 0xbf, 0x08, 0xfd, 0xcc, 0x29, 0xa1, + 0x19, 0xb4, 0x78, 0xdd, 0xec, 0x67, 0x8e, 0xf6, 0x31, 0x8c, 0x49, 0xe8, 0x73, 0x5c, 0xd8, 0xe0, + 0x7e, 0x48, 0xf1, 0x7d, 0x18, 0x6e, 0x97, 0x44, 0xe2, 0x47, 0xd6, 0x26, 0xf4, 0x54, 0x6b, 0xf5, + 0x96, 0xdf, 0xe6, 0xe0, 0x93, 0x7f, 0xa6, 0xfb, 0xcc, 0x21, 0x5b, 0xbd, 0x6b, 0x44, 0x71, 0xd8, + 0x70, 0xdd, 0xb3, 0x1c, 0xde, 0x01, 0x78, 0xde, 0x42, 0x15, 0x7b, 0x5e, 0x4f, 0xfa, 0xad, 0xc7, + 0xfd, 0xd6, 0x93, 0x79, 0x52, 0xfd, 0xd6, 0x77, 0x49, 0x8d, 0x2a, 0x5f, 0xb3, 0xc3, 0x53, 0xfb, + 0x09, 0x41, 0xa9, 0x8b, 0xfc, 0x86, 0xeb, 0xe6, 0xf1, 0x1f, 0xb8, 0x20, 0x7f, 0xbc, 0xdd, 0x45, + 0xb2, 0x5f, 0x92, 0x5c, 0xe8, 0x49, 0x32, 0xf9, 0x78, 0x17, 0xcb, 0xbf, 0x11, 0x4c, 0xef, 0xd0, + 0xe8, 0x7d, 0xee, 0xd0, 0x47, 0x3c, 0xfe, 0xdd, 0x22, 0xae, 0xdd, 0x74, 0xe5, 0x62, 0xab, 0x22, + 0x8f, 0x61, 0x3c, 0x19, 0xd8, 0x46, 0xc0, 0x1b, 0x3c, 0xa4, 0x81, 0xe5, 0x11, 0x61, 0xd7, 0x69, + 0xd8, 0xae, 0x4e, 0x9a, 0xf9, 0x1e, 0x71, 0xe3, 0xd1, 0xe2, 0xc1, 0x0e, 0x8d, 0x76, 0x12, 0xb4, + 0x79, 0x43, 0x46, 0xd9, 0x55, 0x41, 0x94, 0x15, 0x7f, 0x06, 0x63, 0x51, 0x0b, 0x6c, 0x79, 0x34, + 0xb2, 0x3c, 0x2a, 0x02, 0x66, 0x87, 0xed, 0xac, 0xd2, 0xc1, 0xbb, 0x08, 0xef, 0x24, 0x70, 0x73, + 0x34, 0xea, 0xfc, 0x64, 0x62, 0xd4, 0xfe, 0x47, 0x30, 0x93, 0x9f, 0x9e, 0x6a, 0x46, 0x0d, 0xae, + 0x06, 0x34, 0x6c, 0xba, 0x22, 0x54, 0xad, 0xd8, 0xee, 0xf5, 0xcd, 0x8c, 0x28, 0x31, 0x60, 0xc3, + 0x77, 0xf6, 0xb8, 0xdb, 0xf4, 0xe8, 0x2e, 0x0d, 0xe2, 0xd6, 0xa9, 0xb6, 0xb5, 0xa2, 0x97, 0x09, + 0x8c, 0x66, 0xa0, 0xf0, 0x0c, 0x5c, 0x6b, 0x0f, 0x83, 0xd5, 0x9e, 0x7f, 0x68, 0x35, 0xfb, 0x3d, + 0x07, 0xbf, 0x04, 0x03, 0x1e, 0x8d, 0x64, 0x45, 0xfa, 0xcd, 0xf8, 0x11, 0x8f, 0xc3, 0x95, 0x48, + 0x06, 0x29, 0x0d, 0xcc, 0xa0, 0xc5, 0x41, 0x53, 0xbd, 0x69, 0xcb, 0xb0, 0x28, 0x87, 0xee, 0x6d, + 0xa9, 0x06, 0x8f, 0x18, 0x0d, 0x1e, 0xc4, 0x5a, 0xb0, 0x25, 0x77, 0x77, 0x33, 0xe8, 0xec, 0xab, + 0xf6, 0x23, 0x82, 0xa5, 0x73, 0x80, 0x55, 0x95, 0x7c, 0x28, 0xe5, 0x49, 0x8c, 0x9a, 0x03, 0x23, + 0xa3, 0x6c, 0x45, 0xa1, 0x55, 0x79, 0xc6, 0x68, 0x16, 0x46, 0x5b, 0x82, 0x05, 0x49, 0x6e, 0x33, + 0x1e, 0x1a, 0x93, 0x08, 0x9a, 0x9f, 0xc8, 0x0f, 0x48, 0x65, 0x5d, 0x88, 0x55, 0x79, 0x1c, 0xc0, + 0xcd, 0x1c, 0xf9, 0x55, 0x69, 0xe8, 0x19, 0x69, 0x14, 0x04, 0x56, 0x59, 0x24, 0xc3, 0x7d, 0x06, + 0xa2, 0x2d, 0xc0, 0x9c, 0x24, 0xf6, 0xa0, 0x43, 0x6a, 0x33, 0x53, 0xf8, 0x1a, 0xc1, 0x7c, 0x2f, + 0xa4, 0x4a, 0xe0, 0x31, 0x8c, 0x66, 0x28, 0xb7, 0x22, 0x3f, 0x97, 0x41, 0x3e, 0x1d, 0x52, 0x71, + 0xc6, 0x6e, 0x6a, 0x45, 0xdb, 0x80, 0xa9, 0x0f, 0x45, 0x40, 0x89, 0xf7, 0x30, 0x70, 0x68, 0x50, + 0xe5, 0xfc, 0xe0, 0xa3, 0x44, 0xbd, 0x5b, 0x6a, 0x90, 0x9e, 0xd6, 0x81, 0xee, 0x69, 0xd5, 0xfe, + 0x44, 0x50, 0xc9, 0x8b, 0xa1, 0x72, 0xf8, 0x04, 0xae, 0xaa, 0x43, 0x41, 0x6d, 0xb9, 0xbb, 0xdd, + 0xbc, 0xd5, 0xa9, 0xa2, 0xa7, 0xcf, 0x90, 0x87, 0xfb, 0xfb, 0x5b, 0xb1, 0x21, 0x89, 0xb8, 0xb7, + 0xda, 0xda, 0x63, 0x6a, 0x1d, 0x97, 0x61, 0x28, 0xf4, 0x49, 0x23, 0xac, 0x73, 0x21, 0xf7, 0xcb, + 0x90, 0xd9, 0x7e, 0xc7, 0xb3, 0x70, 0x2d, 0x69, 0x7d, 0x9d, 0xb2, 0x5a, 0x5d, 0xc8, 0xad, 0x73, + 0xdd, 0x1c, 0x91, 0xb6, 0x77, 0xa5, 0x09, 0x4f, 0xc0, 0x30, 0x3d, 0xa2, 0xb6, 0xe5, 0x71, 0x87, + 0x96, 0x06, 0xe5, 0xfa, 0x50, 0x6c, 0xd8, 0xe1, 0x0e, 0x5d, 0xfb, 0x79, 0x18, 0x5e, 0x90, 0x4d, + 0xc2, 0xdf, 0x20, 0x18, 0x6a, 0x89, 0x33, 0x5e, 0xce, 0xa8, 0x79, 0xce, 0x09, 0x57, 0x5e, 0xcc, + 0xc3, 0x9e, 0x3d, 0xe2, 0xb4, 0xa5, 0xaf, 0xfe, 0xf8, 0xef, 0xfb, 0xfe, 0x57, 0xf1, 0xac, 0x51, + 0x70, 0x1d, 0x30, 0x3e, 0x67, 0xce, 0x17, 0xf8, 0x5b, 0x04, 0x23, 0x1d, 0xa7, 0x4c, 0x3e, 0xa1, + 0xf4, 0x71, 0x57, 0xbe, 0xd5, 0x8b, 0x50, 0xc7, 0xb1, 0xa5, 0xbd, 0x26, 0x39, 0x55, 0xf0, 0x64, + 0x11, 0x27, 0xfc, 0x2b, 0x82, 0x52, 0x9e, 0x5c, 0xe2, 0xb5, 0x0b, 0x69, 0x6b, 0xc2, 0xf1, 0xf6, + 0x25, 0xf4, 0x58, 0x5b, 0x97, 0x5c, 0xef, 0xac, 0xa3, 0x65, 0xcd, 0x30, 0x32, 0xef, 0x33, 0x96, + 0xcf, 0x1d, 0x6a, 0x09, 0x9e, 0xfc, 0xdb, 0x1d, 0x24, 0x7f, 0x47, 0x30, 0x59, 0xa4, 0x5c, 0xf8, + 0x5e, 0x5e, 0xd5, 0xce, 0xa1, 0xbb, 0xe5, 0xb7, 0x2e, 0xe7, 0xac, 0xf2, 0x9a, 0x97, 0x79, 0xcd, + 0xe0, 0x8a, 0x51, 0x78, 0x07, 0xc4, 0xbf, 0x20, 0x98, 0x28, 0x90, 0x2d, 0xbc, 0x9e, 0xc7, 0xa2, + 0xb7, 0xe0, 0x96, 0xef, 0x5d, 0xca, 0x57, 0x25, 0x30, 0x27, 0x13, 0x98, 0xc6, 0x53, 0x85, 0x17, + 0x63, 0xfc, 0x1b, 0x82, 0x57, 0x72, 0xc5, 0x10, 0xdf, 0xcd, 0x63, 0xd0, 0x4b, 0x69, 0xcb, 0x6f, + 0x5e, 0xc2, 0x53, 0x31, 0xd7, 0x25, 0xf3, 0x45, 0x3c, 0x6f, 0x9c, 0xeb, 0x32, 0x8d, 0xbf, 0x84, + 0xf1, 0x6c, 0x1d, 0xc4, 0x2b, 0x19, 0x24, 0x0a, 0x65, 0xb7, 0xbc, 0x7a, 0x01, 0x8f, 0x84, 0xee, + 0x0a, 0xda, 0xdc, 0x7d, 0x72, 0x52, 0x41, 0x4f, 0x4f, 0x2a, 0xe8, 0xdf, 0x93, 0x0a, 0xfa, 0xee, + 0xb4, 0xd2, 0xf7, 0xf4, 0xb4, 0xd2, 0xf7, 0xd7, 0x69, 0xa5, 0xef, 0xd3, 0x37, 0x6a, 0x4c, 0xd4, + 0x9b, 0x55, 0xdd, 0xe6, 0x5e, 0x77, 0x32, 0xd1, 0x9d, 0xd7, 0xa5, 0xe0, 0x1a, 0x6d, 0xcb, 0x51, + 0x92, 0xa0, 0x38, 0x6e, 0xd0, 0xb0, 0x7a, 0x45, 0x9a, 0x6f, 0x3f, 0x0b, 0x00, 0x00, 0xff, 0xff, + 0xf1, 0xed, 0x1e, 0x02, 0x67, 0x0d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1687,6 +1708,16 @@ func (m *StreamOrderbookUpdatesResponse) MarshalToSizedBuffer(dAtA []byte) (int, _ = i var l int _ = l + if m.ExecMode != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.ExecMode)) + i-- + dAtA[i] = 0x20 + } + if m.BlockHeight != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.BlockHeight)) + i-- + dAtA[i] = 0x18 + } if m.Snapshot { i-- if m.Snapshot { @@ -1921,6 +1952,12 @@ func (m *StreamOrderbookUpdatesResponse) Size() (n int) { if m.Snapshot { n += 2 } + if m.BlockHeight != 0 { + n += 1 + sovQuery(uint64(m.BlockHeight)) + } + if m.ExecMode != 0 { + n += 1 + sovQuery(uint64(m.ExecMode)) + } return n } @@ -3201,6 +3238,44 @@ func (m *StreamOrderbookUpdatesResponse) Unmarshal(dAtA []byte) error { } } m.Snapshot = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockHeight", wireType) + } + m.BlockHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockHeight |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExecMode", wireType) + } + m.ExecMode = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExecMode |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipQuery(dAtA[iNdEx:]) diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index 651111ea5c..c95913b3b6 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -1247,6 +1247,11 @@ func (k Keeper) ModifyOpenInterest( ) ( err error, ) { + // No-op if delta is zero. + if openInterestDeltaBaseQuantums.Sign() == 0 { + return nil + } + // Get perpetual. perpetual, err := k.GetPerpetual(ctx, perpetualId) if err != nil { @@ -1271,6 +1276,8 @@ func (k Keeper) ModifyOpenInterest( perpetual.OpenInterest = dtypes.NewIntFromBigInt(bigOpenInterest) k.SetPerpetual(ctx, perpetual) + + // TODO(OTE-247): add indexer update logic for open interest change. return nil } @@ -1532,6 +1539,8 @@ func (k Keeper) SetLiquidityTier( name, initialMarginPpm, maintenanceFractionPpm, + openInterestLowerCap, + openInterestUpperCap, ), ), ) diff --git a/protocol/x/perpetuals/keeper/perpetual_test.go b/protocol/x/perpetuals/keeper/perpetual_test.go index fcc005db5e..a88ee0039b 100644 --- a/protocol/x/perpetuals/keeper/perpetual_test.go +++ b/protocol/x/perpetuals/keeper/perpetual_test.go @@ -568,7 +568,7 @@ func TestModifyOpenInterest_Failure(t *testing.T) { "Non-existent perp Id": { id: 1111, initOpenInterest: big.NewInt(1_000), - openInterestDelta: big.NewInt(0), + openInterestDelta: big.NewInt(100), err: types.ErrPerpetualDoesNotExist, }, } diff --git a/protocol/x/perpetuals/simulation/genesis.go b/protocol/x/perpetuals/simulation/genesis.go index b68e5cbe6f..0fef123be8 100644 --- a/protocol/x/perpetuals/simulation/genesis.go +++ b/protocol/x/perpetuals/simulation/genesis.go @@ -189,8 +189,14 @@ func RandomizedGenState(simState *module.SimulationState) { for i := 0; i < numPerpetuals; i++ { marketId := marketsForPerp[i] - marketType := types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS - // TODO: add isolated markets when order placements for isolated markets are supported + var marketType types.PerpetualMarketType + // RandIntBetween generates integers in the range [min, max), so need [0, 2) to generate integers that are + // 0 or 1. + if simtypes.RandIntBetween(r, 0, 2) == 0 { + marketType = types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS + } else { + marketType = types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED + } perpetuals[i] = types.Perpetual{ Params: types.PerpetualParams{ diff --git a/protocol/x/perpetuals/types/types.go b/protocol/x/perpetuals/types/types.go index 81ba665c2b..0fec3426c9 100644 --- a/protocol/x/perpetuals/types/types.go +++ b/protocol/x/perpetuals/types/types.go @@ -115,14 +115,20 @@ type PerpetualsKeeper interface { id uint32, marketType PerpetualMarketType, ) (Perpetual, error) + GetPerpetual( + ctx sdk.Context, + id uint32, + ) (Perpetual, error) GetAllPerpetuals( ctx sdk.Context, ) []Perpetual + GetAllLiquidityTiers(ctx sdk.Context) (list []LiquidityTier) } +// OpenInterestDelta represents a (perpId, openInterestDelta) tuple. type OpenInterestDelta struct { // The `Id` of the `Perpetual`. PerpetualId uint32 // Delta of open interest (in base quantums). - BaseQuantumsDelta *big.Int + BaseQuantums *big.Int } diff --git a/protocol/x/subaccounts/keeper/grpc_query_collateral_pool.go b/protocol/x/subaccounts/keeper/grpc_query_collateral_pool.go new file mode 100644 index 0000000000..339194b241 --- /dev/null +++ b/protocol/x/subaccounts/keeper/grpc_query_collateral_pool.go @@ -0,0 +1,46 @@ +package keeper + +import ( + "context" + "errors" + "fmt" + + "github.com/dydxprotocol/v4-chain/protocol/lib" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (k Keeper) CollateralPoolAddress( + c context.Context, + req *types.QueryCollateralPoolAddressRequest, +) (*types.QueryCollateralPoolAddressResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := lib.UnwrapSDKContext(c, types.ModuleName) + + collateralPool, err := k.GetCollateralPoolFromPerpetualId( + ctx, + req.PerpetualId, + ) + if err != nil { + if errors.Is(err, perptypes.ErrPerpetualDoesNotExist) { + return nil, + status.Error( + codes.NotFound, + fmt.Sprintf( + "Perpetual id %+v not found.", + req.PerpetualId, + ), + ) + } + + return nil, status.Error(codes.Internal, "internal error") + } + + return &types.QueryCollateralPoolAddressResponse{ + CollateralPoolAddress: collateralPool.String(), + }, nil +} diff --git a/protocol/x/subaccounts/keeper/grpc_query_collateral_pool_test.go b/protocol/x/subaccounts/keeper/grpc_query_collateral_pool_test.go new file mode 100644 index 0000000000..ec476552da --- /dev/null +++ b/protocol/x/subaccounts/keeper/grpc_query_collateral_pool_test.go @@ -0,0 +1,68 @@ +package keeper_test + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" +) + +func TestQueryCollateralPoolAddress(t *testing.T) { + for testName, tc := range map[string]struct { + // Parameters + request *types.QueryCollateralPoolAddressRequest + + // Expectations + response *types.QueryCollateralPoolAddressResponse + err error + }{ + "Nil request results in error": { + err: status.Error(codes.InvalidArgument, "invalid request"), + }, + "Cross perpetual": { + request: &types.QueryCollateralPoolAddressRequest{ + PerpetualId: constants.BtcUsd_NoMarginRequirement.Params.Id, + }, + response: &types.QueryCollateralPoolAddressResponse{ + CollateralPoolAddress: types.ModuleAddress.String(), + }, + }, + "Isolated perpetual": { + request: &types.QueryCollateralPoolAddressRequest{ + PerpetualId: constants.IsoUsd_IsolatedMarket.Params.Id, + }, + response: &types.QueryCollateralPoolAddressResponse{ + CollateralPoolAddress: constants.IsoCollateralPoolAddress.String(), + }, + }, + "Perpetual not found": { + request: &types.QueryCollateralPoolAddressRequest{ + PerpetualId: uint32(1000), + }, + err: status.Error(codes.NotFound, fmt.Sprintf( + "Perpetual id %+v not found.", + uint32(1000), + )), + }, + } { + t.Run(testName, func(t *testing.T) { + ctx, keeper, pricesKeeper, perpetualsKeeper, _, _, _, _, _ := keepertest.SubaccountsKeepers(t, true) + keepertest.CreateTestMarkets(t, ctx, pricesKeeper) + keepertest.CreateTestLiquidityTiers(t, ctx, perpetualsKeeper) + keepertest.CreateTestPerpetuals(t, ctx, perpetualsKeeper) + response, err := keeper.CollateralPoolAddress(ctx, tc.request) + if tc.err != nil { + require.ErrorIs(t, err, tc.err) + } else { + require.NoError(t, err) + require.Equal(t, tc.response, response) + } + }) + } +} diff --git a/protocol/x/subaccounts/keeper/negative_tnc_subaccount.go b/protocol/x/subaccounts/keeper/negative_tnc_subaccount.go index 04bc0867b3..ebef13a73e 100644 --- a/protocol/x/subaccounts/keeper/negative_tnc_subaccount.go +++ b/protocol/x/subaccounts/keeper/negative_tnc_subaccount.go @@ -176,3 +176,25 @@ func (k Keeper) getLastBlockNegativeSubaccountSeen( } return lastBlockNegativeSubaccountSeen, negativeSubaccountExists, nil } + +// LegacyGetNegativeTncSubaccountSeenAtBlock gets the last block height a negative TNC subaccount was +// seen in state and a boolean for whether it exists in state. +// Deprecated: This is the legacy implementation and meant to be used for the v5.0.0 state migration. +// Use `GetNegativeTncSubaccountSeenAtBlock` instead. +func (k Keeper) LegacyGetNegativeTncSubaccountSeenAtBlock( + ctx sdk.Context, +) (uint32, bool) { + store := ctx.KVStore(k.storeKey) + b := store.Get( + // Key used in v4.0.0. + []byte("NegSA:"), + ) + blockHeight := gogotypes.UInt32Value{Value: 0} + exists := false + if b != nil { + k.cdc.MustUnmarshal(b, &blockHeight) + exists = true + } + + return blockHeight.Value, exists +} diff --git a/protocol/x/subaccounts/keeper/oimf.go b/protocol/x/subaccounts/keeper/oimf.go index 2f08cece71..691dd941a0 100644 --- a/protocol/x/subaccounts/keeper/oimf.go +++ b/protocol/x/subaccounts/keeper/oimf.go @@ -45,7 +45,7 @@ func getDeltaLongFromSettledUpdate( } // For `Match` updates: -// - returns a struct `OpenInterestDelta` if input updates results in OI delta. +// - returns a struct `OpenInterest` if input updates results in OI delta. // - returns nil if OI delta is zero. // - panics if update format is invalid. // @@ -114,7 +114,7 @@ func GetDeltaOpenInterestFromUpdates( } return &perptypes.OpenInterestDelta{ - PerpetualId: updatedPerpId, - BaseQuantumsDelta: baseQuantumsDelta, + PerpetualId: updatedPerpId, + BaseQuantums: baseQuantumsDelta, } } diff --git a/protocol/x/subaccounts/keeper/oimf_test.go b/protocol/x/subaccounts/keeper/oimf_test.go index e91e986d73..46cb310769 100644 --- a/protocol/x/subaccounts/keeper/oimf_test.go +++ b/protocol/x/subaccounts/keeper/oimf_test.go @@ -176,8 +176,8 @@ func TestGetDeltaOpenInterestFromUpdates(t *testing.T) { }, }, expectedVal: &perptypes.OpenInterestDelta{ - PerpetualId: 1, - BaseQuantumsDelta: big.NewInt(500), + PerpetualId: 1, + BaseQuantums: big.NewInt(500), }, }, "Valid: 500 -> 0, 0 -> 500, delta = 0": { @@ -307,8 +307,8 @@ func TestGetDeltaOpenInterestFromUpdates(t *testing.T) { }, }, expectedVal: &perptypes.OpenInterestDelta{ - PerpetualId: 1000, - BaseQuantumsDelta: big.NewInt(-50), + PerpetualId: 1000, + BaseQuantums: big.NewInt(-50), }, }, "Valid: -3100 -> -5000, 1000 -> 2900, delta = 1900": { @@ -350,8 +350,8 @@ func TestGetDeltaOpenInterestFromUpdates(t *testing.T) { }, }, expectedVal: &perptypes.OpenInterestDelta{ - PerpetualId: 1000, - BaseQuantumsDelta: big.NewInt(1900), + PerpetualId: 1000, + BaseQuantums: big.NewInt(1900), }, }, } diff --git a/protocol/x/subaccounts/keeper/subaccount.go b/protocol/x/subaccounts/keeper/subaccount.go index defce49e3f..3a213710b4 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -308,6 +308,25 @@ func (k Keeper) UpdateSubaccounts( perpIdToFundingIndex[perp.Params.Id] = perp.FundingIndex } + // Get OpenInterestDelta from the updates, and persist the OI change if any. + perpOpenInterestDelta := GetDeltaOpenInterestFromUpdates(settledUpdates, updateType) + if perpOpenInterestDelta != nil { + if err := k.perpetualsKeeper.ModifyOpenInterest( + ctx, + perpOpenInterestDelta.PerpetualId, + perpOpenInterestDelta.BaseQuantums, + ); err != nil { + return false, nil, errorsmod.Wrapf( + types.ErrCannotModifyPerpOpenInterestForOIMF, + "perpId = %v, delta = %v, settledUpdates = %+v, err = %v", + perpOpenInterestDelta.PerpetualId, + perpOpenInterestDelta.BaseQuantums, + settledUpdates, + err, + ) + } + } + // Apply the updates to perpetual positions. UpdatePerpetualPositions( settledUpdates, @@ -679,13 +698,13 @@ func (k Keeper) internalCanUpdateSubaccounts( if err := k.perpetualsKeeper.ModifyOpenInterest( branchedContext, perpOpenInterestDelta.PerpetualId, - perpOpenInterestDelta.BaseQuantumsDelta, + perpOpenInterestDelta.BaseQuantums, ); err != nil { return false, nil, errorsmod.Wrapf( types.ErrCannotModifyPerpOpenInterestForOIMF, "perpId = %v, delta = %v, settledUpdates = %+v, err = %v", perpOpenInterestDelta.PerpetualId, - perpOpenInterestDelta.BaseQuantumsDelta, + perpOpenInterestDelta.BaseQuantums, settledUpdates, err, ) diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index 1999d6b6f0..90501a68c7 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -95,13 +95,22 @@ func assertSubaccountUpdateEventsInIndexerBlock( // For each update, verify that the expected SubaccountUpdateEvent is emitted. for _, update := range updates { - expecetedSubaccountUpdateEvent := indexerevents.NewSubaccountUpdateEvent( + expectedSubaccountUpdateEvent := indexerevents.NewSubaccountUpdateEvent( &update.SubaccountId, expectedUpdatedPerpetualPositions[update.SubaccountId], expectedUpdatedAssetPositions[update.SubaccountId], expectedSubaccoundIdToFundingPayments[update.SubaccountId], ) - require.Contains(t, subaccountUpdates, expecetedSubaccountUpdateEvent) + + for _, gotUpdate := range subaccountUpdates { + if gotUpdate.SubaccountId.Owner == expectedSubaccountUpdateEvent.SubaccountId.Owner && + gotUpdate.SubaccountId.Number == expectedSubaccountUpdateEvent.SubaccountId.Number { + require.Equal(t, + expectedSubaccountUpdateEvent, + gotUpdate, + ) + } + } } } @@ -297,6 +306,9 @@ func TestUpdateSubaccounts(t *testing.T) { newFundingIndices []*big.Int // 1:1 mapped to perpetuals list assets []*asstypes.Asset marketParamPrices []pricestypes.MarketParamPrice + // If not specified, default to `CollatCheck` + updateType types.UpdateType + additionalTestSubaccounts []types.Subaccount // subaccount state perpetualPositions []*types.PerpetualPosition @@ -316,6 +328,9 @@ func TestUpdateSubaccounts(t *testing.T) { expectedSuccess bool expectedSuccessPerUpdate []types.UpdateResult expectedErr error + // List of expected open interest. + // If not specified, this means OI is default value. + expectedOpenInterest map[uint32]*big.Int // Only contains the updated perpetual positions, to assert against the events included. expectedUpdatedPerpetualPositions map[types.SubaccountId][]*types.PerpetualPosition @@ -2461,6 +2476,313 @@ func TestUpdateSubaccounts(t *testing.T) { expectedErr: sdkerrors.ErrInsufficientFunds, msgSenderEnabled: true, }, + "Match updates increase OI: 0 -> 0.9, 0 -> -0.9": { + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, + }, + updates: []types.Update{ + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(9_000_000_000), // 90 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(-4_500_000_000_000), // -4,500,000 USDC + }, + }, + SubaccountId: constants.Bob_Num0, + }, + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-9_000_000_000), // 9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(4_500_000_000_000), // 4,500,000 USDC + }, + }, + }, + }, + assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(900_000_000_000)), // 900_000 USDC + additionalTestSubaccounts: []types.Subaccount{ + { + Id: &constants.Bob_Num0, + AssetPositions: testutil.CreateUsdcAssetPosition(big.NewInt( + 900_000_000_000, + )), // 900_000 USDC + }, + }, + updateType: types.Match, + expectedAssetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(5_400_000_000_000), + }, + }, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + defaultSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(5_400_000_000_000), + }, + }, + constants.Bob_Num0: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(-3_600_000_000_000), + }, + }, + }, + expectedPerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(-9_000_000_000), + FundingIndex: dtypes.NewInt(0), + }, + }, + expectedUpdatedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + defaultSubaccountId: { + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(-9_000_000_000), + FundingIndex: dtypes.NewInt(0), + }, + }, + constants.Bob_Num0: { + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(9_000_000_000), + FundingIndex: dtypes.NewInt(0), + }, + }, + }, + expectedSuccess: true, + expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, + expectedOpenInterest: map[uint32]*big.Int{ + 0: big.NewInt(9_100_000_000), // 1 + 90 = 91 BTC + }, + msgSenderEnabled: true, + }, + "Match updates decreases OI: 1 -> 0.1, -2 -> -1.1": { + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest2, + }, + perpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + }, + }, + assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(-40_000_000_000)), // -40_000 USDC + updates: []types.Update{ + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(90_000_000), // 0.9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(-45_000_000_000), // -45,000 USDC + }, + }, + SubaccountId: constants.Bob_Num0, + }, + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-90_000_000), // -0.9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(45_000_000_000), // 45,000 USDC + }, + }, + }, + }, + additionalTestSubaccounts: []types.Subaccount{ + { + Id: &constants.Bob_Num0, + AssetPositions: testutil.CreateUsdcAssetPosition(big.NewInt( + 120_000_000_000, + )), // 120_000 USDC + PerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(-200_000_000), // -2 BTC + }, + }, + }, + }, + updateType: types.Match, + expectedAssetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(5_000_000_000), // 5_000 USDC + }, + }, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + defaultSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(5_000_000_000), // 5_000 USDC + }, + }, + constants.Bob_Num0: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(75_000_000_000), // 75_000 USDC + }, + }, + }, + expectedPerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(10_000_000), // 0.1 BTC + FundingIndex: dtypes.NewInt(0), + }, + }, + expectedUpdatedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + defaultSubaccountId: { + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(10_000_000), // 0.1 BTC + FundingIndex: dtypes.NewInt(0), + }, + }, + constants.Bob_Num0: { + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(-110_000_000), // -1.1 BTC + FundingIndex: dtypes.NewInt(0), + }, + }, + }, + expectedSuccess: true, + expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, + expectedOpenInterest: map[uint32]*big.Int{ + 0: big.NewInt(110_000_000), // 2 - 0.9 = 1.1 BTC + }, + msgSenderEnabled: true, + }, + "Match updates does not change OI: 1 -> 0.1, 0.1 -> 1": { + perpetuals: []perptypes.Perpetual{ + constants.BtcUsd_20PercentInitial_10PercentMaintenance_OpenInterest1, + }, + perpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + }, + }, + assetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(-40_000_000_000)), // -40_000 USDC + updates: []types.Update{ + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(90_000_000), // 0.9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(-45_000_000_000), // -45,000 USDC + }, + }, + SubaccountId: constants.Bob_Num0, + }, + { + PerpetualUpdates: []types.PerpetualUpdate{ + { + PerpetualId: uint32(0), + BigQuantumsDelta: big.NewInt(-90_000_000), // -0.9 BTC + }, + }, + AssetUpdates: []types.AssetUpdate{ + { + AssetId: uint32(0), + BigQuantumsDelta: big.NewInt(45_000_000_000), // 45,000 USDC + }, + }, + }, + }, + additionalTestSubaccounts: []types.Subaccount{ + { + Id: &constants.Bob_Num0, + AssetPositions: testutil.CreateUsdcAssetPosition(big.NewInt(5_000_000_000)), // 5000 USDC + PerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(10_000_000), // 0.1 BTC + }, + }, + }, + }, + updateType: types.Match, + expectedAssetPositions: []*types.AssetPosition{ + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(5_000_000_000), // 5_000 USDC + }, + }, + expectedUpdatedAssetPositions: map[types.SubaccountId][]*types.AssetPosition{ + defaultSubaccountId: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(5_000_000_000), // 5_000 USDC + }, + }, + constants.Bob_Num0: { + { + AssetId: uint32(0), + Quantums: dtypes.NewInt(-40_000_000_000), // -40_000 USDC + }, + }, + }, + expectedPerpetualPositions: []*types.PerpetualPosition{ + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(10_000_000), // 0.1 BTC + FundingIndex: dtypes.NewInt(0), + }, + }, + expectedUpdatedPerpetualPositions: map[types.SubaccountId][]*types.PerpetualPosition{ + defaultSubaccountId: { + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(10_000_000), // 0.1 BTC + FundingIndex: dtypes.NewInt(0), + }, + }, + constants.Bob_Num0: { + { + PerpetualId: uint32(0), + Quantums: dtypes.NewInt(100_000_000), // 1 BTC + FundingIndex: dtypes.NewInt(0), + }, + }, + }, + expectedSuccess: true, + expectedSuccessPerUpdate: []types.UpdateResult{types.Success, types.Success}, + expectedOpenInterest: map[uint32]*big.Int{ + 0: big.NewInt(100_000_000), // 1 BTC + }, + msgSenderEnabled: true, + }, } for name, tc := range tests { @@ -2499,23 +2821,16 @@ func TestUpdateSubaccounts(t *testing.T) { } for i, p := range tc.perpetuals { - perp, err := perpetualsKeeper.CreatePerpetual( + perpetualsKeeper.SetPerpetual( ctx, - p.Params.Id, - p.Params.Ticker, - p.Params.MarketId, - p.Params.AtomicResolution, - p.Params.DefaultFundingPpm, - p.Params.LiquidityTier, - p.Params.MarketType, + p, ) - require.NoError(t, err) // Update FundingIndex for testing settlements. if i < len(tc.newFundingIndices) { - err = perpetualsKeeper.ModifyFundingIndex( + err := perpetualsKeeper.ModifyFundingIndex( ctx, - perp.Params.Id, + p.Params.Id, tc.newFundingIndices[i], ) require.NoError(t, err) @@ -2540,6 +2855,10 @@ func TestUpdateSubaccounts(t *testing.T) { keeper.SetSubaccount(ctx, subaccount) subaccountId := *subaccount.Id + for _, sa := range tc.additionalTestSubaccounts { + keeper.SetSubaccount(ctx, sa) + } + for i, u := range tc.updates { if u.SubaccountId == (types.SubaccountId{}) { u.SubaccountId = subaccountId @@ -2547,7 +2866,11 @@ func TestUpdateSubaccounts(t *testing.T) { tc.updates[i] = u } - success, successPerUpdate, err := keeper.UpdateSubaccounts(ctx, tc.updates, types.CollatCheck) + updateType := types.CollatCheck + if tc.updateType != types.UpdateTypeUnspecified { + updateType = tc.updateType + } + success, successPerUpdate, err := keeper.UpdateSubaccounts(ctx, tc.updates, updateType) if tc.expectedErr != nil { require.ErrorIs(t, tc.expectedErr, err) } else { @@ -2598,6 +2921,20 @@ func TestUpdateSubaccounts(t *testing.T) { usdcBal, ) } + + for _, perp := range tc.perpetuals { + gotPerp, err := perpetualsKeeper.GetPerpetual(ctx, perp.GetId()) + require.NoError(t, err) + + if expectedOI, exists := tc.expectedOpenInterest[perp.GetId()]; exists { + require.Equal(t, expectedOI, gotPerp.OpenInterest.BigInt()) + } else { + // If no specified expected OI, then check OI is unchanged. + require.Zero(t, perp.OpenInterest.BigInt().Cmp( + gotPerp.OpenInterest.BigInt(), + )) + } + } }) } } @@ -4150,7 +4487,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { { PerpetualId: uint32(0), // 500 BTC. At $50,000, this is $25,000,000 of OI. - BaseQuantumsDelta: big.NewInt(50_000_000_000), + BaseQuantums: big.NewInt(50_000_000_000), }, }, updates: []types.Update{ @@ -4220,7 +4557,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { // (Only difference from prevoius test case) // 410 BTC. At $50,000, this is $20,500,000 of OI. // OI would be $25,000,000 after the Match updates, so OIMF is still at base IMF. - BaseQuantumsDelta: big.NewInt(41_000_000_000), + BaseQuantums: big.NewInt(41_000_000_000), }, }, updates: []types.Update{ @@ -4290,7 +4627,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { // (Only difference from prevoius test case) // 410 BTC + 1 base quantum. At $50,000, this is > $20,500,000 of OI. // OI would be just past $25,000,000 after the Match updates, so OIMF > IMF = 20% - BaseQuantumsDelta: big.NewInt(41_000_000_001), + BaseQuantums: big.NewInt(41_000_000_001), }, }, updates: []types.Update{ @@ -4357,7 +4694,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { { PerpetualId: uint32(0), // 10_000 BTC. At $50,000, this is $500mm of OI which way past upper cap - BaseQuantumsDelta: big.NewInt(1_000_000_000_000), + BaseQuantums: big.NewInt(1_000_000_000_000), }, }, updates: []types.Update{ @@ -5052,7 +5389,7 @@ func TestCanUpdateSubaccounts(t *testing.T) { require.NoError(t, perpetualsKeeper.ModifyOpenInterest( ctx, openInterest.PerpetualId, - openInterest.BaseQuantumsDelta, + openInterest.BaseQuantums, )) } @@ -5095,9 +5432,9 @@ func TestCanUpdateSubaccounts(t *testing.T) { perp, err := perpetualsKeeper.GetPerpetual(ctx, openInterest.PerpetualId) require.NoError(t, err) require.Zerof(t, - openInterest.BaseQuantumsDelta.Cmp(perp.OpenInterest.BigInt()), + openInterest.BaseQuantums.Cmp(perp.OpenInterest.BigInt()), "expected: %s, got: %s", - openInterest.BaseQuantumsDelta.String(), + openInterest.BaseQuantums.String(), perp.OpenInterest.String(), ) } diff --git a/protocol/x/subaccounts/types/errors.go b/protocol/x/subaccounts/types/errors.go index d27fb280fb..2113575cfe 100644 --- a/protocol/x/subaccounts/types/errors.go +++ b/protocol/x/subaccounts/types/errors.go @@ -58,7 +58,7 @@ var ( ErrCannotModifyPerpOpenInterestForOIMF = errorsmod.Register( ModuleName, 402, - "cannot temporarily modify perpetual open interest for OIMF calculation", + "cannot modify perpetual open interest for OIMF calculation", ) ErrCannotRevertPerpOpenInterestForOIMF = errorsmod.Register( ModuleName, diff --git a/protocol/x/subaccounts/types/query.pb.go b/protocol/x/subaccounts/types/query.pb.go index 9c0bc41dc2..25bbbc13ac 100644 --- a/protocol/x/subaccounts/types/query.pb.go +++ b/protocol/x/subaccounts/types/query.pb.go @@ -344,6 +344,100 @@ func (m *QueryGetWithdrawalAndTransfersBlockedInfoResponse) GetWithdrawalsAndTra return 0 } +// QueryCollateralPoolAddressRequest is the request type for fetching the +// account address of the collateral pool associated with the passed in +// perpetual id. +type QueryCollateralPoolAddressRequest struct { + PerpetualId uint32 `protobuf:"varint,1,opt,name=perpetual_id,json=perpetualId,proto3" json:"perpetual_id,omitempty"` +} + +func (m *QueryCollateralPoolAddressRequest) Reset() { *m = QueryCollateralPoolAddressRequest{} } +func (m *QueryCollateralPoolAddressRequest) String() string { return proto.CompactTextString(m) } +func (*QueryCollateralPoolAddressRequest) ProtoMessage() {} +func (*QueryCollateralPoolAddressRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_adc19ff1d5b72954, []int{6} +} +func (m *QueryCollateralPoolAddressRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCollateralPoolAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCollateralPoolAddressRequest.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 *QueryCollateralPoolAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCollateralPoolAddressRequest.Merge(m, src) +} +func (m *QueryCollateralPoolAddressRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryCollateralPoolAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCollateralPoolAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCollateralPoolAddressRequest proto.InternalMessageInfo + +func (m *QueryCollateralPoolAddressRequest) GetPerpetualId() uint32 { + if m != nil { + return m.PerpetualId + } + return 0 +} + +// QueryCollateralPoolAddressResponse is a response type for fetching the +// account address of the collateral pool associated with the passed in +// perpetual id. +type QueryCollateralPoolAddressResponse struct { + CollateralPoolAddress string `protobuf:"bytes,1,opt,name=collateral_pool_address,json=collateralPoolAddress,proto3" json:"collateral_pool_address,omitempty"` +} + +func (m *QueryCollateralPoolAddressResponse) Reset() { *m = QueryCollateralPoolAddressResponse{} } +func (m *QueryCollateralPoolAddressResponse) String() string { return proto.CompactTextString(m) } +func (*QueryCollateralPoolAddressResponse) ProtoMessage() {} +func (*QueryCollateralPoolAddressResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_adc19ff1d5b72954, []int{7} +} +func (m *QueryCollateralPoolAddressResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryCollateralPoolAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryCollateralPoolAddressResponse.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 *QueryCollateralPoolAddressResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryCollateralPoolAddressResponse.Merge(m, src) +} +func (m *QueryCollateralPoolAddressResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryCollateralPoolAddressResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryCollateralPoolAddressResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryCollateralPoolAddressResponse proto.InternalMessageInfo + +func (m *QueryCollateralPoolAddressResponse) GetCollateralPoolAddress() string { + if m != nil { + return m.CollateralPoolAddress + } + return "" +} + func init() { proto.RegisterType((*QueryGetSubaccountRequest)(nil), "dydxprotocol.subaccounts.QueryGetSubaccountRequest") proto.RegisterType((*QuerySubaccountResponse)(nil), "dydxprotocol.subaccounts.QuerySubaccountResponse") @@ -351,6 +445,8 @@ func init() { proto.RegisterType((*QuerySubaccountAllResponse)(nil), "dydxprotocol.subaccounts.QuerySubaccountAllResponse") proto.RegisterType((*QueryGetWithdrawalAndTransfersBlockedInfoRequest)(nil), "dydxprotocol.subaccounts.QueryGetWithdrawalAndTransfersBlockedInfoRequest") proto.RegisterType((*QueryGetWithdrawalAndTransfersBlockedInfoResponse)(nil), "dydxprotocol.subaccounts.QueryGetWithdrawalAndTransfersBlockedInfoResponse") + proto.RegisterType((*QueryCollateralPoolAddressRequest)(nil), "dydxprotocol.subaccounts.QueryCollateralPoolAddressRequest") + proto.RegisterType((*QueryCollateralPoolAddressResponse)(nil), "dydxprotocol.subaccounts.QueryCollateralPoolAddressResponse") } func init() { @@ -358,50 +454,56 @@ func init() { } var fileDescriptor_adc19ff1d5b72954 = []byte{ - // 687 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x4f, 0x4f, 0xd4, 0x4e, - 0x18, 0xde, 0x2e, 0x7f, 0x92, 0xdf, 0xf0, 0xe3, 0x32, 0x21, 0xb8, 0x34, 0x66, 0xc5, 0xcd, 0x0a, - 0x68, 0xa0, 0x75, 0x01, 0x63, 0x62, 0x42, 0xe2, 0xee, 0x41, 0x04, 0x0f, 0x68, 0x81, 0x90, 0x98, - 0x98, 0x66, 0xda, 0xbe, 0x94, 0xc6, 0x32, 0x53, 0x3a, 0x53, 0xfe, 0x84, 0x70, 0xf1, 0xe6, 0xcd, - 0xc4, 0x0f, 0xe0, 0xd5, 0xab, 0xd1, 0x0f, 0xc1, 0x91, 0xe8, 0xc5, 0x93, 0x31, 0xe0, 0x47, 0xf0, - 0x03, 0x98, 0x9d, 0xb6, 0xdb, 0x2e, 0xd8, 0xec, 0x6a, 0xbc, 0xb5, 0x33, 0xef, 0xf3, 0xbc, 0xcf, - 0xf3, 0xf4, 0x7d, 0x8b, 0xea, 0xce, 0x91, 0x73, 0x18, 0x84, 0x4c, 0x30, 0x9b, 0xf9, 0x3a, 0x8f, - 0x2c, 0x62, 0xdb, 0x2c, 0xa2, 0x82, 0xeb, 0x7b, 0x11, 0x84, 0x47, 0x9a, 0xbc, 0xc2, 0x95, 0x7c, - 0x95, 0x96, 0xab, 0x52, 0x27, 0x6c, 0xc6, 0x77, 0x19, 0x37, 0xe5, 0xa5, 0x1e, 0xbf, 0xc4, 0x20, - 0x75, 0xcc, 0x65, 0x2e, 0x8b, 0xcf, 0xdb, 0x4f, 0xc9, 0xe9, 0x75, 0x97, 0x31, 0xd7, 0x07, 0x9d, - 0x04, 0x9e, 0x4e, 0x28, 0x65, 0x82, 0x08, 0x8f, 0xd1, 0x14, 0x73, 0x27, 0x66, 0xd0, 0x2d, 0xc2, - 0x21, 0x56, 0xa0, 0xef, 0x37, 0x2c, 0x10, 0xa4, 0xa1, 0x07, 0xc4, 0xf5, 0xa8, 0x2c, 0x4e, 0x6a, - 0x6f, 0x17, 0x4a, 0xcf, 0x9e, 0xe3, 0xd2, 0x9a, 0x8d, 0x26, 0x9e, 0xb5, 0xc9, 0x96, 0x41, 0xac, - 0x77, 0xee, 0x0c, 0xd8, 0x8b, 0x80, 0x0b, 0xac, 0xa1, 0x21, 0x76, 0x40, 0x21, 0xac, 0x28, 0x93, - 0xca, 0xcc, 0x7f, 0xad, 0xca, 0xe7, 0x4f, 0x73, 0x63, 0x89, 0x91, 0xa6, 0xe3, 0x84, 0xc0, 0xf9, - 0xba, 0x08, 0x3d, 0xea, 0x1a, 0x71, 0x19, 0x1e, 0x47, 0xc3, 0x34, 0xda, 0xb5, 0x20, 0xac, 0x94, - 0x27, 0x95, 0x99, 0x51, 0x23, 0x79, 0xab, 0x01, 0xba, 0x26, 0x9b, 0xe4, 0x3b, 0xf0, 0x80, 0x51, - 0x0e, 0x78, 0x15, 0xa1, 0x4c, 0x93, 0xec, 0x33, 0x32, 0x5f, 0xd7, 0x8a, 0x42, 0xd5, 0x32, 0x86, - 0xd6, 0xe0, 0xe9, 0xb7, 0x1b, 0x25, 0x23, 0x87, 0xee, 0x78, 0x69, 0xfa, 0xfe, 0x55, 0x2f, 0x8f, - 0x10, 0xca, 0x72, 0x4a, 0x1a, 0x4d, 0x69, 0x89, 0x9b, 0x76, 0xa8, 0x5a, 0xfc, 0x59, 0x93, 0x50, - 0xb5, 0xa7, 0xc4, 0x85, 0x04, 0x6b, 0xe4, 0x90, 0xb5, 0x0f, 0x0a, 0x52, 0x2f, 0x99, 0x69, 0xfa, - 0x7e, 0xa1, 0x9f, 0x81, 0xbf, 0xf7, 0x83, 0x97, 0xbb, 0x24, 0x97, 0xa5, 0xe4, 0xe9, 0x9e, 0x92, - 0x63, 0x21, 0x5d, 0x9a, 0x37, 0xd1, 0xdd, 0xf4, 0x23, 0x6f, 0x79, 0x62, 0xc7, 0x09, 0xc9, 0x01, - 0xf1, 0x9b, 0xd4, 0xd9, 0x08, 0x09, 0xe5, 0xdb, 0x10, 0xf2, 0x96, 0xcf, 0xec, 0x97, 0xe0, 0xac, - 0xd0, 0x6d, 0x96, 0xe6, 0x75, 0x13, 0xfd, 0x1f, 0x40, 0x18, 0x80, 0x88, 0x88, 0x6f, 0x7a, 0x8e, - 0x4c, 0x6c, 0xd4, 0x18, 0xe9, 0x9c, 0xad, 0x38, 0xb5, 0x77, 0x65, 0xd4, 0xf8, 0x03, 0xde, 0x24, - 0xa1, 0x35, 0x74, 0x8b, 0x82, 0x4b, 0x84, 0xb7, 0x0f, 0xa6, 0xa0, 0xb6, 0x99, 0x19, 0x36, 0x39, - 0x00, 0x35, 0x89, 0x30, 0xad, 0x36, 0x2c, 0xe9, 0x38, 0x99, 0x16, 0x6f, 0x50, 0x3b, 0x4b, 0x6b, - 0x1d, 0x80, 0x36, 0x85, 0xa4, 0xc7, 0x0f, 0x90, 0x6a, 0xef, 0x10, 0x8f, 0x9a, 0x2c, 0x12, 0xc4, - 0x85, 0x4b, 0x2c, 0xf1, 0x24, 0x8e, 0xcb, 0x8a, 0x35, 0x59, 0x90, 0xc7, 0xbe, 0x40, 0xb3, 0x07, - 0x1d, 0xe5, 0xdc, 0x24, 0xd4, 0x31, 0x45, 0x2a, 0xde, 0x8c, 0xa8, 0x15, 0xeb, 0xcf, 0xd8, 0x06, - 0x24, 0xdb, 0x74, 0x0e, 0x93, 0xb7, 0xbb, 0x99, 0x02, 0x12, 0xfa, 0xf9, 0x9f, 0x83, 0x68, 0x48, - 0x26, 0x84, 0x3f, 0x2a, 0x08, 0x65, 0xf2, 0xf1, 0x42, 0xf1, 0x48, 0x14, 0xae, 0xa3, 0xda, 0xe8, - 0x01, 0xba, 0xba, 0x5e, 0xb5, 0xa5, 0x57, 0x5f, 0x7e, 0xbc, 0x2d, 0xdf, 0xc7, 0xf7, 0xf4, 0x3e, - 0x7e, 0x09, 0xfa, 0xb1, 0x5c, 0xe3, 0x13, 0xfd, 0x38, 0xde, 0xdb, 0x13, 0xfc, 0x5e, 0x41, 0xa3, - 0x5d, 0x73, 0xde, 0x53, 0xf8, 0xef, 0x76, 0x4f, 0x5d, 0xec, 0x5b, 0x78, 0x6e, 0x95, 0x6a, 0xb3, - 0x52, 0xfb, 0x14, 0xae, 0xf7, 0xa3, 0x1d, 0xbf, 0x2e, 0xa3, 0x7a, 0x3f, 0x73, 0x88, 0x57, 0x7b, - 0x47, 0xdf, 0xef, 0x92, 0xa8, 0x4f, 0xfe, 0x09, 0x57, 0xe2, 0xf7, 0xb1, 0xf4, 0xdb, 0xc2, 0x0f, - 0x8b, 0xfd, 0x16, 0xcf, 0x6a, 0x3a, 0xa9, 0x1e, 0xdd, 0x66, 0xad, 0xad, 0xd3, 0xf3, 0xaa, 0x72, - 0x76, 0x5e, 0x55, 0xbe, 0x9f, 0x57, 0x95, 0x37, 0x17, 0xd5, 0xd2, 0xd9, 0x45, 0xb5, 0xf4, 0xf5, - 0xa2, 0x5a, 0x7a, 0xbe, 0xe4, 0x7a, 0x62, 0x27, 0xb2, 0x34, 0x9b, 0xed, 0x76, 0x77, 0xd9, 0x5f, - 0x9c, 0x93, 0x2b, 0xa2, 0x77, 0x4e, 0x0e, 0xbb, 0x3a, 0x8b, 0xa3, 0x00, 0xb8, 0x35, 0x2c, 0x6f, - 0x17, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0xed, 0x0b, 0x2a, 0xec, 0x1c, 0x07, 0x00, 0x00, + // 774 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcf, 0x4f, 0xdb, 0x48, + 0x18, 0x8d, 0xc3, 0x82, 0xb4, 0xc3, 0x72, 0x19, 0xf1, 0x23, 0x58, 0xab, 0x2c, 0x58, 0x59, 0x60, + 0x57, 0x60, 0x6f, 0x80, 0xd5, 0x4a, 0xbb, 0x8b, 0xd4, 0xa4, 0x12, 0x14, 0x7a, 0x00, 0x02, 0x08, + 0xa9, 0x52, 0x65, 0x8d, 0xed, 0xc1, 0x58, 0x35, 0x33, 0xc6, 0x33, 0x0e, 0x20, 0xc4, 0xa5, 0x7f, + 0x41, 0xa5, 0x5e, 0x7a, 0xeb, 0xb5, 0xd7, 0xaa, 0xfd, 0x23, 0x38, 0xa2, 0xf6, 0xd2, 0x43, 0x55, + 0x55, 0xa1, 0x7f, 0x48, 0x95, 0xf1, 0x24, 0x76, 0x00, 0x93, 0x80, 0x7a, 0xb3, 0x67, 0xbe, 0xf7, + 0xbe, 0xf7, 0x9e, 0xfd, 0x7d, 0xa0, 0xe4, 0x9c, 0x38, 0xc7, 0x41, 0x48, 0x39, 0xb5, 0xa9, 0x6f, + 0xb0, 0xc8, 0x42, 0xb6, 0x4d, 0x23, 0xc2, 0x99, 0x71, 0x18, 0xe1, 0xf0, 0x44, 0x17, 0x57, 0xb0, + 0x90, 0xae, 0xd2, 0x53, 0x55, 0xea, 0xb8, 0x4d, 0xd9, 0x01, 0x65, 0xa6, 0xb8, 0x34, 0xe2, 0x97, + 0x18, 0xa4, 0x0e, 0xbb, 0xd4, 0xa5, 0xf1, 0x79, 0xf3, 0x49, 0x9e, 0xfe, 0xea, 0x52, 0xea, 0xfa, + 0xd8, 0x40, 0x81, 0x67, 0x20, 0x42, 0x28, 0x47, 0xdc, 0xa3, 0xa4, 0x85, 0xf9, 0x33, 0x66, 0x30, + 0x2c, 0xc4, 0x70, 0xac, 0xc0, 0xa8, 0x97, 0x2d, 0xcc, 0x51, 0xd9, 0x08, 0x90, 0xeb, 0x11, 0x51, + 0x2c, 0x6b, 0xff, 0xc8, 0x94, 0x9e, 0x3c, 0xc7, 0xa5, 0x9a, 0x0d, 0xc6, 0x37, 0x9b, 0x64, 0x2b, + 0x98, 0x6f, 0xb5, 0xef, 0x6a, 0xf8, 0x30, 0xc2, 0x8c, 0x43, 0x1d, 0xf4, 0xd3, 0x23, 0x82, 0xc3, + 0x82, 0x32, 0xa1, 0xcc, 0xfc, 0x5c, 0x2d, 0x7c, 0x78, 0x3f, 0x37, 0x2c, 0x8d, 0x54, 0x1c, 0x27, + 0xc4, 0x8c, 0x6d, 0xf1, 0xd0, 0x23, 0x6e, 0x2d, 0x2e, 0x83, 0xa3, 0x60, 0x80, 0x44, 0x07, 0x16, + 0x0e, 0x0b, 0xf9, 0x09, 0x65, 0x66, 0xa8, 0x26, 0xdf, 0x34, 0x0c, 0xc6, 0x44, 0x93, 0x74, 0x07, + 0x16, 0x50, 0xc2, 0x30, 0x5c, 0x03, 0x20, 0xd1, 0x24, 0xfa, 0x0c, 0xce, 0x97, 0xf4, 0xac, 0x50, + 0xf5, 0x84, 0xa1, 0xfa, 0xd3, 0xf9, 0x97, 0xdf, 0x72, 0xb5, 0x14, 0xba, 0xed, 0xa5, 0xe2, 0xfb, + 0xd7, 0xbd, 0x2c, 0x03, 0x90, 0xe4, 0x24, 0x1b, 0x4d, 0xe9, 0xd2, 0x4d, 0x33, 0x54, 0x3d, 0xfe, + 0xac, 0x32, 0x54, 0x7d, 0x03, 0xb9, 0x58, 0x62, 0x6b, 0x29, 0xa4, 0xf6, 0x56, 0x01, 0xea, 0x15, + 0x33, 0x15, 0xdf, 0xcf, 0xf4, 0xd3, 0x77, 0x7f, 0x3f, 0x70, 0xa5, 0x43, 0x72, 0x5e, 0x48, 0x9e, + 0xee, 0x2a, 0x39, 0x16, 0xd2, 0xa1, 0x79, 0x07, 0xfc, 0xd5, 0xfa, 0xc8, 0xbb, 0x1e, 0xdf, 0x77, + 0x42, 0x74, 0x84, 0xfc, 0x0a, 0x71, 0xb6, 0x43, 0x44, 0xd8, 0x1e, 0x0e, 0x59, 0xd5, 0xa7, 0xf6, + 0x33, 0xec, 0xac, 0x92, 0x3d, 0xda, 0xca, 0x6b, 0x12, 0xfc, 0x12, 0xe0, 0x30, 0xc0, 0x3c, 0x42, + 0xbe, 0xe9, 0x39, 0x22, 0xb1, 0xa1, 0xda, 0x60, 0xfb, 0x6c, 0xd5, 0xd1, 0x5e, 0xe7, 0x41, 0xf9, + 0x0e, 0xbc, 0x32, 0xa1, 0x75, 0xf0, 0x3b, 0xc1, 0x2e, 0xe2, 0x5e, 0x1d, 0x9b, 0x9c, 0xd8, 0x66, + 0x62, 0xd8, 0x64, 0x18, 0x13, 0x13, 0x71, 0xd3, 0x6a, 0xc2, 0x64, 0xc7, 0x89, 0x56, 0xf1, 0x36, + 0xb1, 0x93, 0xb4, 0xb6, 0x30, 0x26, 0x15, 0x2e, 0xe8, 0xe1, 0xbf, 0x40, 0xb5, 0xf7, 0x91, 0x47, + 0x4c, 0x1a, 0x71, 0xe4, 0xe2, 0x2b, 0x2c, 0xf1, 0x9f, 0x38, 0x2a, 0x2a, 0xd6, 0x45, 0x41, 0x1a, + 0xfb, 0x14, 0xcc, 0x1e, 0xb5, 0x95, 0x33, 0x13, 0x11, 0xc7, 0xe4, 0x2d, 0xf1, 0x66, 0x44, 0xac, + 0x58, 0x7f, 0xc2, 0xd6, 0x27, 0xd8, 0xa6, 0x53, 0x98, 0xb4, 0xdd, 0x9d, 0x16, 0x40, 0xd2, 0x6b, + 0xcb, 0x60, 0x52, 0x04, 0xf4, 0x90, 0xfa, 0x3e, 0xe2, 0x38, 0x44, 0xfe, 0x06, 0xa5, 0xbe, 0x9c, + 0x9d, 0x3b, 0x24, 0x5d, 0x07, 0xda, 0x6d, 0x3c, 0x32, 0xd9, 0x0d, 0x30, 0x66, 0xb7, 0x0b, 0xcc, + 0x80, 0x52, 0xdf, 0x44, 0x71, 0x49, 0xd7, 0x01, 0x1e, 0xb1, 0x6f, 0x62, 0x9e, 0x6f, 0x0c, 0x80, + 0x7e, 0xd1, 0x18, 0xbe, 0x53, 0x00, 0x48, 0xe2, 0x87, 0x0b, 0xd9, 0xbf, 0x74, 0xe6, 0x3a, 0x51, + 0xcb, 0x5d, 0x40, 0xd7, 0xd7, 0x83, 0xb6, 0xf4, 0xfc, 0xe3, 0xb7, 0x97, 0xf9, 0x7f, 0xe0, 0xdf, + 0x46, 0x0f, 0x2b, 0xcd, 0x38, 0x15, 0x6b, 0xe8, 0xcc, 0x38, 0x8d, 0xf7, 0xce, 0x19, 0x7c, 0xa3, + 0x80, 0xa1, 0x8e, 0x39, 0xed, 0x2a, 0xfc, 0xa6, 0xdd, 0xa1, 0x2e, 0xf6, 0x2c, 0x3c, 0xb5, 0x0a, + 0xb4, 0x59, 0xa1, 0x7d, 0x0a, 0x96, 0x7a, 0xd1, 0x0e, 0x5f, 0xe5, 0x41, 0xa9, 0x97, 0x39, 0x82, + 0x6b, 0xdd, 0xa3, 0xef, 0x75, 0xc8, 0xd5, 0xc7, 0x3f, 0x84, 0x4b, 0xfa, 0xdd, 0x15, 0x7e, 0x37, + 0xe1, 0x7a, 0xb6, 0xdf, 0xec, 0x59, 0x6b, 0x4d, 0x9a, 0x47, 0xf6, 0xa8, 0x71, 0x9a, 0x9e, 0x87, + 0x33, 0xf8, 0x59, 0x01, 0x23, 0x37, 0xfe, 0xf9, 0xf0, 0xbf, 0x2e, 0xfa, 0x6f, 0x9b, 0x3b, 0xf5, + 0xff, 0xfb, 0x81, 0xa5, 0xdb, 0x47, 0xc2, 0x6d, 0x15, 0x3e, 0xc8, 0x76, 0x9b, 0x31, 0x8c, 0x57, + 0xec, 0x55, 0x77, 0xcf, 0x1b, 0x45, 0xe5, 0xa2, 0x51, 0x54, 0xbe, 0x36, 0x8a, 0xca, 0x8b, 0xcb, + 0x62, 0xee, 0xe2, 0xb2, 0x98, 0xfb, 0x74, 0x59, 0xcc, 0x3d, 0x59, 0x72, 0x3d, 0xbe, 0x1f, 0x59, + 0xba, 0x4d, 0x0f, 0x3a, 0xbb, 0xd4, 0x17, 0xe7, 0xc4, 0x42, 0x33, 0xda, 0x27, 0xc7, 0x1d, 0x9d, + 0xf9, 0x49, 0x80, 0x99, 0x35, 0x20, 0x6e, 0x17, 0xbe, 0x07, 0x00, 0x00, 0xff, 0xff, 0x6d, 0x51, + 0x19, 0x95, 0xca, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -423,6 +525,8 @@ type QueryClient interface { // Queries information about whether withdrawal and transfers are blocked, and // if so which block they are re-enabled on. GetWithdrawalAndTransfersBlockedInfo(ctx context.Context, in *QueryGetWithdrawalAndTransfersBlockedInfoRequest, opts ...grpc.CallOption) (*QueryGetWithdrawalAndTransfersBlockedInfoResponse, error) + // Queries the collateral pool account address for a perpetual id. + CollateralPoolAddress(ctx context.Context, in *QueryCollateralPoolAddressRequest, opts ...grpc.CallOption) (*QueryCollateralPoolAddressResponse, error) } type queryClient struct { @@ -460,6 +564,15 @@ func (c *queryClient) GetWithdrawalAndTransfersBlockedInfo(ctx context.Context, return out, nil } +func (c *queryClient) CollateralPoolAddress(ctx context.Context, in *QueryCollateralPoolAddressRequest, opts ...grpc.CallOption) (*QueryCollateralPoolAddressResponse, error) { + out := new(QueryCollateralPoolAddressResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.subaccounts.Query/CollateralPoolAddress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // Queries a Subaccount by id @@ -469,6 +582,8 @@ type QueryServer interface { // Queries information about whether withdrawal and transfers are blocked, and // if so which block they are re-enabled on. GetWithdrawalAndTransfersBlockedInfo(context.Context, *QueryGetWithdrawalAndTransfersBlockedInfoRequest) (*QueryGetWithdrawalAndTransfersBlockedInfoResponse, error) + // Queries the collateral pool account address for a perpetual id. + CollateralPoolAddress(context.Context, *QueryCollateralPoolAddressRequest) (*QueryCollateralPoolAddressResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -484,6 +599,9 @@ func (*UnimplementedQueryServer) SubaccountAll(ctx context.Context, req *QueryAl func (*UnimplementedQueryServer) GetWithdrawalAndTransfersBlockedInfo(ctx context.Context, req *QueryGetWithdrawalAndTransfersBlockedInfoRequest) (*QueryGetWithdrawalAndTransfersBlockedInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetWithdrawalAndTransfersBlockedInfo not implemented") } +func (*UnimplementedQueryServer) CollateralPoolAddress(ctx context.Context, req *QueryCollateralPoolAddressRequest) (*QueryCollateralPoolAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CollateralPoolAddress not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -543,6 +661,24 @@ func _Query_GetWithdrawalAndTransfersBlockedInfo_Handler(srv interface{}, ctx co return interceptor(ctx, in, info, handler) } +func _Query_CollateralPoolAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryCollateralPoolAddressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).CollateralPoolAddress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.subaccounts.Query/CollateralPoolAddress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).CollateralPoolAddress(ctx, req.(*QueryCollateralPoolAddressRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "dydxprotocol.subaccounts.Query", HandlerType: (*QueryServer)(nil), @@ -559,6 +695,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "GetWithdrawalAndTransfersBlockedInfo", Handler: _Query_GetWithdrawalAndTransfersBlockedInfo_Handler, }, + { + MethodName: "CollateralPoolAddress", + Handler: _Query_CollateralPoolAddress_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "dydxprotocol/subaccounts/query.proto", @@ -782,6 +922,64 @@ func (m *QueryGetWithdrawalAndTransfersBlockedInfoResponse) MarshalToSizedBuffer return len(dAtA) - i, nil } +func (m *QueryCollateralPoolAddressRequest) 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 *QueryCollateralPoolAddressRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCollateralPoolAddressRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.PerpetualId != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.PerpetualId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *QueryCollateralPoolAddressResponse) 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 *QueryCollateralPoolAddressResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryCollateralPoolAddressResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CollateralPoolAddress) > 0 { + i -= len(m.CollateralPoolAddress) + copy(dAtA[i:], m.CollateralPoolAddress) + i = encodeVarintQuery(dAtA, i, uint64(len(m.CollateralPoolAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -882,6 +1080,31 @@ func (m *QueryGetWithdrawalAndTransfersBlockedInfoResponse) Size() (n int) { return n } +func (m *QueryCollateralPoolAddressRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PerpetualId != 0 { + n += 1 + sovQuery(uint64(m.PerpetualId)) + } + return n +} + +func (m *QueryCollateralPoolAddressResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CollateralPoolAddress) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1454,6 +1677,157 @@ func (m *QueryGetWithdrawalAndTransfersBlockedInfoResponse) Unmarshal(dAtA []byt } return nil } +func (m *QueryCollateralPoolAddressRequest) 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: QueryCollateralPoolAddressRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCollateralPoolAddressRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PerpetualId", wireType) + } + m.PerpetualId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PerpetualId |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + 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 *QueryCollateralPoolAddressResponse) 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: QueryCollateralPoolAddressResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryCollateralPoolAddressResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CollateralPoolAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CollateralPoolAddress = string(dAtA[iNdEx:postIndex]) + 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 skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/protocol/x/subaccounts/types/query.pb.gw.go b/protocol/x/subaccounts/types/query.pb.gw.go index 9aebee906d..cf3f87be63 100644 --- a/protocol/x/subaccounts/types/query.pb.gw.go +++ b/protocol/x/subaccounts/types/query.pb.gw.go @@ -145,19 +145,26 @@ func local_request_Query_SubaccountAll_0(ctx context.Context, marshaler runtime. } -var ( - filter_Query_GetWithdrawalAndTransfersBlockedInfo_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} -) - func request_Query_GetWithdrawalAndTransfersBlockedInfo_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QueryGetWithdrawalAndTransfersBlockedInfoRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["perpetual_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "perpetual_id") } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_GetWithdrawalAndTransfersBlockedInfo_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + + protoReq.PerpetualId, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "perpetual_id", err) } msg, err := client.GetWithdrawalAndTransfersBlockedInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) @@ -169,11 +176,22 @@ func local_request_Query_GetWithdrawalAndTransfersBlockedInfo_0(ctx context.Cont var protoReq QueryGetWithdrawalAndTransfersBlockedInfoRequest var metadata runtime.ServerMetadata - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["perpetual_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "perpetual_id") } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_GetWithdrawalAndTransfersBlockedInfo_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + + protoReq.PerpetualId, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "perpetual_id", err) } msg, err := server.GetWithdrawalAndTransfersBlockedInfo(ctx, &protoReq) @@ -181,6 +199,60 @@ func local_request_Query_GetWithdrawalAndTransfersBlockedInfo_0(ctx context.Cont } +func request_Query_CollateralPoolAddress_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCollateralPoolAddressRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["perpetual_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "perpetual_id") + } + + protoReq.PerpetualId, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "perpetual_id", err) + } + + msg, err := client.CollateralPoolAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_CollateralPoolAddress_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCollateralPoolAddressRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["perpetual_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "perpetual_id") + } + + protoReq.PerpetualId, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "perpetual_id", err) + } + + msg, err := server.CollateralPoolAddress(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -256,6 +328,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_CollateralPoolAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_CollateralPoolAddress_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_CollateralPoolAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -357,6 +452,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_CollateralPoolAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_CollateralPoolAddress_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_CollateralPoolAddress_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -365,7 +480,9 @@ var ( pattern_Query_SubaccountAll_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"dydxprotocol", "subaccounts", "subaccount"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_GetWithdrawalAndTransfersBlockedInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"dydxprotocol", "subaccounts", "withdrawals_and_transfers_blocked_info"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_GetWithdrawalAndTransfersBlockedInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"dydxprotocol", "subaccounts", "withdrawals_and_transfers_blocked_info", "perpetual_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_CollateralPoolAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"dydxprotocol", "subaccounts", "collateral_pool_address", "perpetual_id"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -374,4 +491,6 @@ var ( forward_Query_SubaccountAll_0 = runtime.ForwardResponseMessage forward_Query_GetWithdrawalAndTransfersBlockedInfo_0 = runtime.ForwardResponseMessage + + forward_Query_CollateralPoolAddress_0 = runtime.ForwardResponseMessage ) diff --git a/protocol/x/subaccounts/types/types.go b/protocol/x/subaccounts/types/types.go index bb8a98ac62..cf4ac34475 100644 --- a/protocol/x/subaccounts/types/types.go +++ b/protocol/x/subaccounts/types/types.go @@ -63,4 +63,14 @@ type SubaccountsKeeper interface { ctx sdk.Context, id SubaccountId, ) (val Subaccount) + LegacyGetNegativeTncSubaccountSeenAtBlock(ctx sdk.Context) (uint32, bool) + GetNegativeTncSubaccountSeenAtBlock( + ctx sdk.Context, + perpetualId uint32, + ) (uint32, bool, error) + SetNegativeTncSubaccountSeenAtBlock( + ctx sdk.Context, + perpetualId uint32, + blockHeight uint32, + ) error } diff --git a/protocol/x/vault/abci.go b/protocol/x/vault/abci.go index 7b74f6b859..187e51fd41 100644 --- a/protocol/x/vault/abci.go +++ b/protocol/x/vault/abci.go @@ -5,6 +5,13 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/x/vault/keeper" ) +func BeginBlocker( + ctx sdk.Context, + keeper *keeper.Keeper, +) { + keeper.DecommissionNonPositiveEquityVaults(ctx) +} + func EndBlocker( ctx sdk.Context, keeper *keeper.Keeper, diff --git a/protocol/x/vault/client/cli/query.go b/protocol/x/vault/client/cli/query.go index d55ce14426..3c16747a7f 100644 --- a/protocol/x/vault/client/cli/query.go +++ b/protocol/x/vault/client/cli/query.go @@ -1,11 +1,14 @@ package cli import ( + "context" "fmt" + "strconv" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" ) @@ -20,5 +23,76 @@ func GetQueryCmd(queryRoute string) *cobra.Command { RunE: client.ValidateCmd, } + cmd.AddCommand(CmdQueryParams()) + cmd.AddCommand(CmdQueryVault()) + + return cmd +} + +func CmdQueryParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "get-params", + Short: "get x/vault parameters", + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx := client.GetClientContextFromCmd(cmd) + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.Params( + context.Background(), + &types.QueryParamsRequest{}, + ) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func CmdQueryVault() *cobra.Command { + cmd := &cobra.Command{ + Use: "get-vault [type] [number]", + Short: "get a vault by its type and number", + Long: "get a vault by its type and number. Current support types are: clob.", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) (err error) { + clientCtx := client.GetClientContextFromCmd(cmd) + queryClient := types.NewQueryClient(clientCtx) + + // Parse vault type. + rawType := args[0] + var vaultType types.VaultType + switch rawType { + case "clob": + vaultType = types.VaultType_VAULT_TYPE_CLOB + default: + return fmt.Errorf("invalid vault type %s", rawType) + } + + // Parse vault number. + vaultNumber, err := strconv.ParseUint(args[1], 10, 32) + if err != nil { + return err + } + + res, err := queryClient.Vault( + context.Background(), + &types.QueryVaultRequest{ + Type: vaultType, + Number: uint32(vaultNumber), + }, + ) + if err != nil { + return err + } + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd } diff --git a/protocol/x/vault/client/cli/query_test.go b/protocol/x/vault/client/cli/query_test.go new file mode 100644 index 0000000000..3811612cb5 --- /dev/null +++ b/protocol/x/vault/client/cli/query_test.go @@ -0,0 +1,54 @@ +//go:build all || integration_test + +package cli_test + +import ( + "strconv" + "testing" + + "github.com/cosmos/cosmos-sdk/client" + clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/stretchr/testify/require" + + "github.com/dydxprotocol/v4-chain/protocol/testutil/network" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/client/cli" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" +) + +// Prevent strconv unused error +var _ = strconv.IntSize + +func setupNetwork( + t *testing.T, +) ( + *network.Network, + client.Context, +) { + t.Helper() + cfg := network.DefaultConfig(nil) + + // Initialize state. + state := types.GenesisState{} + require.NoError(t, cfg.Codec.UnmarshalJSON(cfg.GenesisState[types.ModuleName], &state)) + + state = *types.DefaultGenesis() + + buf, err := cfg.Codec.MarshalJSON(&state) + require.NoError(t, err) + cfg.GenesisState[types.ModuleName] = buf + net := network.New(t, cfg) + ctx := net.Validators[0].ClientCtx + + return net, ctx +} + +func TestQueryParams(t *testing.T) { + net, ctx := setupNetwork(t) + + out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdQueryParams(), []string{}) + + require.NoError(t, err) + var resp types.QueryParamsResponse + require.NoError(t, net.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) + require.Equal(t, types.DefaultGenesis().Params, resp.Params) +} diff --git a/protocol/x/vault/genesis.go b/protocol/x/vault/genesis.go index 4ed3e49bea..146f43871b 100644 --- a/protocol/x/vault/genesis.go +++ b/protocol/x/vault/genesis.go @@ -8,9 +8,17 @@ import ( // InitGenesis initializes the module's state from a provided genesis state. func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { + k.InitializeForGenesis(ctx) + + if err := k.SetParams(ctx, genState.Params); err != nil { + panic(err) + } } // ExportGenesis returns the module's exported genesis. func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { - return &types.GenesisState{} + genesis := types.DefaultGenesis() + genesis.Params = k.GetParams(ctx) + + return genesis } diff --git a/protocol/x/vault/keeper/grpc_query.go b/protocol/x/vault/keeper/grpc_query.go index 2b8ac9afcf..dd9c7c4624 100644 --- a/protocol/x/vault/keeper/grpc_query.go +++ b/protocol/x/vault/keeper/grpc_query.go @@ -1,7 +1,21 @@ package keeper import ( + "context" + + "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) var _ types.QueryServer = Keeper{} + +func (k Keeper) Params(goCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := lib.UnwrapSDKContext(goCtx, types.ModuleName) + + return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil +} diff --git a/protocol/x/vault/keeper/grpc_query_test.go b/protocol/x/vault/keeper/grpc_query_test.go new file mode 100644 index 0000000000..95d8f840c0 --- /dev/null +++ b/protocol/x/vault/keeper/grpc_query_test.go @@ -0,0 +1,46 @@ +package keeper_test + +import ( + "testing" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestQueryParams(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.VaultKeeper + + for name, tc := range map[string]struct { + req *types.QueryParamsRequest + res *types.QueryParamsResponse + err error + }{ + "Success": { + req: &types.QueryParamsRequest{}, + res: &types.QueryParamsResponse{ + Params: types.DefaultGenesis().Params, + }, + err: nil, + }, + "Nil": { + req: nil, + res: nil, + err: status.Error(codes.InvalidArgument, "invalid request"), + }, + } { + t.Run(name, func(t *testing.T) { + res, err := k.Params(ctx, tc.req) + if tc.err != nil { + require.ErrorIs(t, err, tc.err) + } else { + require.NoError(t, err) + require.Equal(t, tc.res, res) + } + }) + } +} diff --git a/protocol/x/vault/keeper/grpc_query_vault.go b/protocol/x/vault/keeper/grpc_query_vault.go new file mode 100644 index 0000000000..48f7873b68 --- /dev/null +++ b/protocol/x/vault/keeper/grpc_query_vault.go @@ -0,0 +1,56 @@ +package keeper + +import ( + "context" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/dydxprotocol/v4-chain/protocol/lib" + clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" +) + +func (k Keeper) Vault( + goCtx context.Context, + req *types.QueryVaultRequest, +) (*types.QueryVaultResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := lib.UnwrapSDKContext(goCtx, types.ModuleName) + + vaultId := types.VaultId{ + Type: req.Type, + Number: req.Number, + } + + // Get total shares. + totalShares, exists := k.GetTotalShares(ctx, vaultId) + if !exists { + return nil, status.Error(codes.NotFound, "vault not found") + } + + // Get vault equity. + equity, err := k.GetVaultEquity(ctx, vaultId) + if err != nil { + return nil, status.Error(codes.Internal, "failed to get vault equity") + } + + // Get vault inventory. + clobPair, exists := k.clobKeeper.GetClobPair(ctx, clobtypes.ClobPairId(vaultId.Number)) + if !exists { + return nil, status.Error(codes.Internal, fmt.Sprintf("clob pair %d doesn't exist", vaultId.Number)) + } + perpId := clobPair.Metadata.(*clobtypes.ClobPair_PerpetualClobMetadata).PerpetualClobMetadata.PerpetualId + inventory := k.GetVaultInventoryInPerpetual(ctx, vaultId, perpId) + + return &types.QueryVaultResponse{ + VaultId: vaultId, + SubaccountId: *vaultId.ToSubaccountId(), + Equity: equity.Uint64(), + Inventory: inventory.Uint64(), + TotalShares: totalShares.NumShares.BigInt().Uint64(), + }, nil +} diff --git a/protocol/x/vault/keeper/grpc_query_vault_test.go b/protocol/x/vault/keeper/grpc_query_vault_test.go new file mode 100644 index 0000000000..07d02a166a --- /dev/null +++ b/protocol/x/vault/keeper/grpc_query_vault_test.go @@ -0,0 +1,124 @@ +package keeper_test + +import ( + "math/big" + "testing" + + "github.com/cometbft/cometbft/types" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" +) + +func TestVault(t *testing.T) { + tests := map[string]struct { + /* --- Setup --- */ + // Vault ID. + vaultId vaulttypes.VaultId + // Vault asset. + asset *big.Int + // Perp ID that corresponds to the vault. + perpId uint32 + // Vault inventory. + inventory *big.Int + // Total shares. + totalShares *big.Int + // Query request. + req *vaulttypes.QueryVaultRequest + + /* --- Expectations --- */ + expectedEquity uint64 + expectedErr string + }{ + "Success": { + req: &vaulttypes.QueryVaultRequest{ + Type: vaulttypes.VaultType_VAULT_TYPE_CLOB, + Number: 0, + }, + vaultId: constants.Vault_Clob_0, + asset: big.NewInt(100), + perpId: 0, + inventory: big.NewInt(200), + totalShares: big.NewInt(300), + expectedEquity: 500, + }, + "Error: query non-existent vault": { + req: &vaulttypes.QueryVaultRequest{ + Type: vaulttypes.VaultType_VAULT_TYPE_CLOB, + Number: 1, // Non-existent vault. + }, + vaultId: constants.Vault_Clob_0, + asset: big.NewInt(100), + perpId: 0, + inventory: big.NewInt(200), + totalShares: big.NewInt(300), + expectedErr: "vault not found", + }, + "Error: nil request": { + req: nil, + vaultId: constants.Vault_Clob_0, + asset: big.NewInt(100), + perpId: 0, + inventory: big.NewInt(200), + totalShares: big.NewInt(300), + expectedErr: "invalid request", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis types.GenesisDoc) { + genesis = testapp.DefaultGenesis() + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + genesisState.Subaccounts = []satypes.Subaccount{ + { + Id: tc.vaultId.ToSubaccountId(), + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: assettypes.AssetUsdc.Id, + Quantums: dtypes.NewIntFromBigInt(tc.asset), + }, + }, + PerpetualPositions: []*satypes.PerpetualPosition{ + { + PerpetualId: tc.perpId, + Quantums: dtypes.NewIntFromBigInt(tc.inventory), + }, + }, + }, + } + }, + ) + return genesis + }).Build() + ctx := tApp.InitChain() + k := tApp.App.VaultKeeper + + // Set total shares. + err := k.SetTotalShares(ctx, tc.vaultId, vaulttypes.BigIntToNumShares(tc.totalShares)) + require.NoError(t, err) + + // Check Vault query response is as expected. + response, err := k.Vault(ctx, tc.req) + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + expectedResponse := vaulttypes.QueryVaultResponse{ + VaultId: tc.vaultId, + SubaccountId: *tc.vaultId.ToSubaccountId(), + Equity: tc.expectedEquity, + Inventory: tc.inventory.Uint64(), + TotalShares: tc.totalShares.Uint64(), + } + require.Equal(t, expectedResponse, *response) + } + }) + } +} diff --git a/protocol/x/vault/keeper/msg_server_deposit_to_vault.go b/protocol/x/vault/keeper/msg_server_deposit_to_vault.go index f462f19f39..6be3d80de8 100644 --- a/protocol/x/vault/keeper/msg_server_deposit_to_vault.go +++ b/protocol/x/vault/keeper/msg_server_deposit_to_vault.go @@ -3,6 +3,10 @@ package keeper import ( "context" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/lib/log" + "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" + assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" ) @@ -11,5 +15,43 @@ func (k msgServer) DepositToVault( goCtx context.Context, msg *types.MsgDepositToVault, ) (*types.MsgDepositToVaultResponse, error) { + ctx := lib.UnwrapSDKContext(goCtx, types.ModuleName) + + // Mint shares for the vault. + err := k.MintShares( + ctx, + *msg.VaultId, + msg.SubaccountId.Owner, + msg.QuoteQuantums.BigInt(), + ) + if err != nil { + return nil, err + } + + // Transfer from sender subaccount to vault. + // Note: Transfer should take place after minting shares for + // shares calculation to be correct. + err = k.subaccountsKeeper.TransferFundsFromSubaccountToSubaccount( + ctx, + *msg.SubaccountId, + *msg.VaultId.ToSubaccountId(), + assettypes.AssetUsdc.Id, + msg.QuoteQuantums.BigInt(), + ) + if err != nil { + return nil, err + } + + // Emit metric on vault equity. + equity, err := k.GetVaultEquity(ctx, *msg.VaultId) + if err != nil { + log.ErrorLogWithError(ctx, "Failed to get vault equity", err, "vaultId", *msg.VaultId) + } else { + msg.VaultId.SetGaugeWithLabels( + metrics.VaultEquity, + float32(equity.Int64()), + ) + } + return &types.MsgDepositToVaultResponse{}, nil } diff --git a/protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go b/protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go new file mode 100644 index 0000000000..b803769a86 --- /dev/null +++ b/protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go @@ -0,0 +1,353 @@ +package keeper_test + +import ( + "bytes" + "math/big" + "testing" + + abcitypes "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/types" + sdktypes "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" +) + +// DepositInstance represents an instance of a deposit to test. +type DepositInstance struct { + // Depositor. + depositor satypes.SubaccountId + // Amount to deposit (in quote quantums). + depositAmount *big.Int + // Signer of the message. + msgSigner string + + // A string that CheckTx response should contain, if any. + checkTxResponseContains string + // Whether CheckTx fails. + checkTxFails bool + // Whether DeliverTx fails. + deliverTxFails bool + // Expected owner shares for depositor above. + expectedOwnerShares *big.Int +} + +// DepositorSetup represents the setup of a depositor. +type DepositorSetup struct { + // Depositor. + depositor satypes.SubaccountId + // Initial balance of the depositor (in quote quantums). + depositorBalance *big.Int +} + +func TestMsgDepositToVault(t *testing.T) { + tests := map[string]struct { + /* --- Setup --- */ + // Vault ID. + vaultId vaulttypes.VaultId + // Depositor setups. + depositorSetups []DepositorSetup + // Instances of deposits. + depositInstances []DepositInstance + + /* --- Expectations --- */ + // Vault total shares after each of the above deposit instances. + totalSharesHistory []*big.Int + // Vault equity after each of the above deposit instances. + vaultEquityHistory []*big.Int + }{ + "Two successful deposits, Same depositor": { + vaultId: constants.Vault_Clob_0, + depositorSetups: []DepositorSetup{ + { + depositor: constants.Alice_Num0, + depositorBalance: big.NewInt(1000), + }, + }, + depositInstances: []DepositInstance{ + { + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(123), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewInt(123), + }, + { + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(321), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewInt(444), + }, + }, + totalSharesHistory: []*big.Int{ + big.NewInt(123), + big.NewInt(444), + }, + vaultEquityHistory: []*big.Int{ + big.NewInt(123), + big.NewInt(444), + }, + }, + "Two successful deposits, Different depositors": { + vaultId: constants.Vault_Clob_0, + depositorSetups: []DepositorSetup{ + { + depositor: constants.Alice_Num0, + depositorBalance: big.NewInt(1_000), + }, + { + depositor: constants.Bob_Num1, + depositorBalance: big.NewInt(500), + }, + }, + depositInstances: []DepositInstance{ + { + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(1_000), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewInt(1_000), + }, + { + depositor: constants.Bob_Num1, + depositAmount: big.NewInt(500), + msgSigner: constants.Bob_Num1.Owner, + expectedOwnerShares: big.NewInt(500), + }, + }, + totalSharesHistory: []*big.Int{ + big.NewInt(1_000), + big.NewInt(1_500), + }, + vaultEquityHistory: []*big.Int{ + big.NewInt(1_000), + big.NewInt(1_500), + }, + }, + "One successful deposit, One failed deposit due to insufficient balance": { + vaultId: constants.Vault_Clob_1, + depositorSetups: []DepositorSetup{ + { + depositor: constants.Alice_Num0, + depositorBalance: big.NewInt(1_000), + }, + { + depositor: constants.Bob_Num1, + depositorBalance: big.NewInt(500), + }, + }, + depositInstances: []DepositInstance{ + { + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(1_000), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewInt(1_000), + }, + { + depositor: constants.Bob_Num1, + depositAmount: big.NewInt(501), // Greater than balance. + msgSigner: constants.Bob_Num1.Owner, + deliverTxFails: true, + expectedOwnerShares: nil, + }, + }, + totalSharesHistory: []*big.Int{ + big.NewInt(1_000), + big.NewInt(1_000), + }, + vaultEquityHistory: []*big.Int{ + big.NewInt(1_000), + big.NewInt(1_000), + }, + }, + "One failed deposit due to incorrect signer, One successful deposit": { + vaultId: constants.Vault_Clob_1, + depositorSetups: []DepositorSetup{ + { + depositor: constants.Alice_Num0, + depositorBalance: big.NewInt(1_000), + }, + { + depositor: constants.Bob_Num1, + depositorBalance: big.NewInt(500), + }, + }, + depositInstances: []DepositInstance{ + { + depositor: constants.Bob_Num1, + depositAmount: big.NewInt(500), + msgSigner: constants.Alice_Num0.Owner, // Incorrect signer. + checkTxFails: true, + checkTxResponseContains: "does not match signer address", + expectedOwnerShares: nil, + }, + { + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(1_000), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewInt(1_000), + }, + }, + totalSharesHistory: []*big.Int{ + big.NewInt(0), + big.NewInt(1_000), + }, + vaultEquityHistory: []*big.Int{ + big.NewInt(0), + big.NewInt(1_000), + }, + }, + "Two failed deposits due to non-positive amounts": { + vaultId: constants.Vault_Clob_1, + depositorSetups: []DepositorSetup{ + { + depositor: constants.Alice_Num0, + depositorBalance: big.NewInt(1_000), + }, + { + depositor: constants.Bob_Num0, + depositorBalance: big.NewInt(1_000), + }, + }, + depositInstances: []DepositInstance{ + { + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(0), + msgSigner: constants.Alice_Num0.Owner, + checkTxFails: true, + checkTxResponseContains: "Deposit amount is invalid", + expectedOwnerShares: nil, + }, + { + depositor: constants.Bob_Num0, + depositAmount: big.NewInt(-1), + msgSigner: constants.Bob_Num0.Owner, + checkTxFails: true, + checkTxResponseContains: "Deposit amount is invalid", + expectedOwnerShares: nil, + }, + }, + totalSharesHistory: []*big.Int{ + big.NewInt(0), + big.NewInt(0), + }, + vaultEquityHistory: []*big.Int{ + big.NewInt(0), + big.NewInt(0), + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // Initialize tApp and ctx. + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis types.GenesisDoc) { + genesis = testapp.DefaultGenesis() + // Initialize balances of depositors. + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + subaccounts := make([]satypes.Subaccount, len(tc.depositorSetups)) + for i, setup := range tc.depositorSetups { + subaccounts[i] = satypes.Subaccount{ + Id: &(setup.depositor), + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewIntFromBigInt(setup.depositorBalance), + }, + }, + } + } + genesisState.Subaccounts = subaccounts + }, + ) + return genesis + }).Build() + ctx := tApp.InitChain() + + // Simulate each deposit instance. + for i, depositInstance := range tc.depositInstances { + // Construct message. + msgDepositToVault := vaulttypes.MsgDepositToVault{ + VaultId: &(tc.vaultId), + SubaccountId: &(depositInstance.depositor), + QuoteQuantums: dtypes.NewIntFromBigInt(depositInstance.depositAmount), + } + + // Invoke CheckTx. + CheckTx_MsgDepositToVault := testapp.MustMakeCheckTx( + ctx, + tApp.App, + testapp.MustMakeCheckTxOptions{ + AccAddressForSigning: depositInstance.msgSigner, + Gas: constants.TestGasLimit, + FeeAmt: constants.TestFeeCoins_5Cents, + }, + &msgDepositToVault, + ) + checkTxResp := tApp.CheckTx(CheckTx_MsgDepositToVault) + + // Check that CheckTx response log contains expected string, if any. + if depositInstance.checkTxResponseContains != "" { + require.Contains(t, checkTxResp.Log, depositInstance.checkTxResponseContains) + } + // Check that CheckTx succeeds or errors out as expected. + if depositInstance.checkTxFails { + require.Conditionf(t, checkTxResp.IsErr, "Expected CheckTx to error. Response: %+v", checkTxResp) + return + } + require.Conditionf(t, checkTxResp.IsOK, "Expected CheckTx to succeed. Response: %+v", checkTxResp) + + // Advance to next block (and check that DeliverTx is as expected). + nextBlock := uint32(ctx.BlockHeight()) + 1 + if depositInstance.deliverTxFails { + // Check that DeliverTx fails on `msgDepositToVault`. + ctx = tApp.AdvanceToBlock(nextBlock, testapp.AdvanceToBlockOptions{ + ValidateFinalizeBlock: func( + context sdktypes.Context, + request abcitypes.RequestFinalizeBlock, + response abcitypes.ResponseFinalizeBlock, + ) (haltChain bool) { + for i, tx := range request.Txs { + if bytes.Equal(tx, CheckTx_MsgDepositToVault.Tx) { + require.True(t, response.TxResults[i].IsErr()) + } else { + require.True(t, response.TxResults[i].IsOK()) + } + } + return false + }, + }) + } else { + ctx = tApp.AdvanceToBlock(nextBlock, testapp.AdvanceToBlockOptions{}) + } + + // Check that total shares of the vault is as expected. + totalShares, exists := tApp.App.VaultKeeper.GetTotalShares(ctx, tc.vaultId) + require.True(t, exists) + require.Equal( + t, + vaulttypes.BigIntToNumShares(tc.totalSharesHistory[i]), + totalShares, + ) + // Check that owner shares of the depositor is as expected. + ownerShares, _ := tApp.App.VaultKeeper.GetOwnerShares( + ctx, + tc.vaultId, + depositInstance.depositor.Owner, + ) + require.Equal( + t, + vaulttypes.BigIntToNumShares(depositInstance.expectedOwnerShares), + ownerShares, + ) + // Check that equity of the vault is as expected. + vaultEquity, err := tApp.App.VaultKeeper.GetVaultEquity(ctx, tc.vaultId) + require.NoError(t, err) + require.Equal(t, tc.vaultEquityHistory[i], vaultEquity) + } + }) + } +} diff --git a/protocol/x/vault/keeper/msg_server_update_params.go b/protocol/x/vault/keeper/msg_server_update_params.go new file mode 100644 index 0000000000..4f987f5da2 --- /dev/null +++ b/protocol/x/vault/keeper/msg_server_update_params.go @@ -0,0 +1,32 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" +) + +// UpdateParams updates the parameters of the vault module. +func (k msgServer) UpdateParams( + goCtx context.Context, + msg *types.MsgUpdateParams, +) (*types.MsgUpdateParamsResponse, error) { + if !k.HasAuthority(msg.Authority) { + return nil, errorsmod.Wrapf( + govtypes.ErrInvalidSigner, + "invalid authority %s", + msg.Authority, + ) + } + + ctx := lib.UnwrapSDKContext(goCtx, types.ModuleName) + if err := k.SetParams(ctx, msg.Params); err != nil { + return nil, err + } + + return &types.MsgUpdateParamsResponse{}, nil +} diff --git a/protocol/x/vault/keeper/msg_server_update_params_test.go b/protocol/x/vault/keeper/msg_server_update_params_test.go new file mode 100644 index 0000000000..29ceef08ce --- /dev/null +++ b/protocol/x/vault/keeper/msg_server_update_params_test.go @@ -0,0 +1,69 @@ +package keeper_test + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/lib" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + + "github.com/dydxprotocol/v4-chain/protocol/x/vault/keeper" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" +) + +func TestMsgUpdateParams(t *testing.T) { + tests := map[string]struct { + // Msg. + msg *types.MsgUpdateParams + // Expected error + expectedErr string + }{ + "Success": { + msg: &types.MsgUpdateParams{ + Authority: lib.GovModuleAddress.String(), + Params: types.DefaultParams(), + }, + }, + "Failure - Invalid Authority": { + msg: &types.MsgUpdateParams{ + Authority: constants.AliceAccAddress.String(), + Params: types.DefaultParams(), + }, + expectedErr: "invalid authority", + }, + "Failure - Invalid Params": { + msg: &types.MsgUpdateParams{ + Authority: lib.GovModuleAddress.String(), + Params: types.Params{ + Layers: 3, + SpreadMinPpm: 4_000, + SpreadBufferPpm: 2_000, + SkewFactorPpm: 500_000, + OrderSizePctPpm: 0, // invalid + OrderExpirationSeconds: 5, + }, + }, + expectedErr: types.ErrInvalidOrderSizePctPpm.Error(), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.VaultKeeper + ms := keeper.NewMsgServerImpl(k) + + _, err := ms.UpdateParams(ctx, tc.msg) + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + require.Equal(t, types.DefaultParams(), k.GetParams(ctx)) + } else { + require.NoError(t, err) + require.Equal(t, tc.msg.Params, k.GetParams(ctx)) + } + }) + } +} diff --git a/protocol/x/vault/keeper/orders.go b/protocol/x/vault/keeper/orders.go index 12c87926d9..12512a72c9 100644 --- a/protocol/x/vault/keeper/orders.go +++ b/protocol/x/vault/keeper/orders.go @@ -6,7 +6,6 @@ import ( errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/dydxprotocol/v4-chain/protocol/dtypes" "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/lib/log" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" @@ -39,13 +38,16 @@ func (k Keeper) RefreshAllVaultOrders(ctx sdk.Context) { totalSharesIterator := k.getTotalSharesIterator(ctx) defer totalSharesIterator.Close() for ; totalSharesIterator.Valid(); totalSharesIterator.Next() { - var vaultId types.VaultId - k.cdc.MustUnmarshal(totalSharesIterator.Key(), &vaultId) + vaultId, err := types.GetVaultIdFromStateKey(totalSharesIterator.Key()) + if err != nil { + log.ErrorLogWithError(ctx, "Failed to get vault ID from state key", err) + continue + } var totalShares types.NumShares k.cdc.MustUnmarshal(totalSharesIterator.Value(), &totalShares) // Skip if TotalShares is non-positive. - if totalShares.NumShares.Cmp(dtypes.NewInt(0)) <= 0 { + if totalShares.NumShares.BigInt().Sign() <= 0 { continue } @@ -53,12 +55,12 @@ func (k Keeper) RefreshAllVaultOrders(ctx sdk.Context) { // Currently only supported vault type is CLOB. switch vaultId.Type { case types.VaultType_VAULT_TYPE_CLOB: - err := k.RefreshVaultClobOrders(ctx, vaultId) + err := k.RefreshVaultClobOrders(ctx, *vaultId) if err != nil { - log.ErrorLogWithError(ctx, "Failed to refresh vault clob orders", err, "vaultId", vaultId) + log.ErrorLogWithError(ctx, "Failed to refresh vault clob orders", err, "vaultId", *vaultId) } default: - log.ErrorLog(ctx, "Failed to refresh vault orders: unknown vault type", "vaultId", vaultId) + log.ErrorLog(ctx, "Failed to refresh vault orders: unknown vault type", "vaultId", *vaultId) } } } @@ -110,14 +112,16 @@ func (k Keeper) RefreshVaultClobOrders(ctx sdk.Context, vaultId types.VaultId) ( return nil } -// GetVaultClobOrders returns a list of long term orders for a given vault, with its corresponding -// clob pair, perpetual, market parameter, and market price. -// Let n be number of layers, then the function returns orders at [a_1, b_1, a_2, b_2, ..., a_n, b_n] +// GetVaultClobOrders returns a list of long term orders for a given CLOB vault. +// Let n be number of layers, then the function returns orders at [a_0, b_0, a_1, b_1, ..., a_{n-1}, b_{n-1}] // where a_i and b_i are the ask price and bid price at i-th layer. To compute a_i and b_i: -// - a_i = oraclePrice * (1 + spread)^i -// - b_i = oraclePrice * (1 - spread)^i -// TODO (TRA-144): Implement order size -// TODO (TRA-114): Implement skew +// - a_i = oraclePrice * (1 + skew_i) * (1 + spread)^{i+1} +// - b_i = oraclePrice * (1 + skew_i) / (1 + spread)^{i+1} +// - skew_i = -leverage_i * spread * skew_factor +// - leverage_i = leverage +/- i * order_size_pct\ (- for ask and + for bid) +// - leverage = open notional / equity +// - spread = max(spread_min, spread_buffer + min_price_change) +// and size of each order is calculated as `order_size * equity / oraclePrice`. func (k Keeper) GetVaultClobOrders( ctx sdk.Context, vaultId types.VaultId, @@ -155,13 +159,52 @@ func (k Keeper) GetVaultClobOrders( // Get vault (subaccount 0 of corresponding module account). vault := vaultId.ToSubaccountId() + // Calculate leverage = open notional / equity. + equity, err := k.GetVaultEquity(ctx, vaultId) + if err != nil { + return orders, err + } + if equity.Sign() <= 0 { + return orders, errorsmod.Wrap( + types.ErrNonPositiveEquity, + fmt.Sprintf("VaultId: %v", vaultId), + ) + } + inventory := k.GetVaultInventoryInPerpetual(ctx, vaultId, perpId) + openNotional := lib.BaseToQuoteQuantums( + inventory, + perpetual.Params.AtomicResolution, + marketPrice.GetPrice(), + marketPrice.GetExponent(), + ) + leverage := new(big.Rat).Quo( + new(big.Rat).SetInt(openNotional), + new(big.Rat).SetInt(equity), + ) + // Get parameters. + params := k.GetParams(ctx) + // Calculate order size (in base quantums). + // order_size = order_size_pct * equity / oracle_price + // = order_size_pct * equity / (price * 10^exponent / 10^quote_atomic_resolution) / 10^base_atomic_resolution + // = order_size_pct * equity / (price * 10^(exponent - quote_atomic_resolution + base_atomic_resolution)) + orderSizeBaseQuantums := lib.BigRatMulPpm( + new(big.Rat).SetInt(equity), + params.OrderSizePctPpm, + ) + orderSizeBaseQuantums = orderSizeBaseQuantums.Quo( + orderSizeBaseQuantums, + lib.BigMulPow10( + new(big.Int).SetUint64(marketPrice.Price), + marketPrice.Exponent-lib.QuoteCurrencyAtomicResolution+perpetual.Params.AtomicResolution, + ), + ) // Calculate spread. spreadPpm := lib.Max( - MIN_BASE_SPREAD_PPM, - BASE_SPREAD_MIN_PRICE_CHANGE_PREMIUM_PPM+marketParam.MinPriceChangePpm, + params.SpreadMinPpm, + params.SpreadBufferPpm+marketParam.MinPriceChangePpm, ) - // Get market price in subticks. - subticks := clobtypes.PriceToSubticks( + // Get oracle price in subticks. + oracleSubticks := clobtypes.PriceToSubticks( marketPrice, clobPair, perpetual.Params.AtomicResolution, @@ -169,55 +212,101 @@ func (k Keeper) GetVaultClobOrders( ) // Get order expiration time. goodTilBlockTime := &clobtypes.Order_GoodTilBlockTime{ - GoodTilBlockTime: uint32(ctx.BlockTime().Unix()) + ORDER_EXPIRATION_SECONDS, + GoodTilBlockTime: uint32(ctx.BlockTime().Unix()) + params.OrderExpirationSeconds, } + // Initialize spreadBaseMultiplier and spreadMultiplier as `1 + spread`. + spreadBaseMultiplier := new(big.Rat).SetFrac( + new(big.Int).SetUint64(uint64(lib.OneMillion+spreadPpm)), + lib.BigIntOneMillion(), + ) + spreadMultiplier := new(big.Rat).Set(spreadBaseMultiplier) // Construct one ask and one bid for each layer. - orders = make([]*clobtypes.Order, 2*NUM_LAYERS) - askSubticks := new(big.Rat).Set(subticks) - bidSubticks := new(big.Rat).Set(subticks) - for i := uint8(0); i < NUM_LAYERS; i++ { - // Calculate ask and bid subticks for this layer. - askSubticks = lib.BigRatMulPpm(askSubticks, lib.OneMillion+spreadPpm) - bidSubticks = lib.BigRatMulPpm(bidSubticks, lib.OneMillion-spreadPpm) + constructOrder := func( + side clobtypes.Order_Side, + layer uint32, + spreadMultipler *big.Rat, + ) *clobtypes.Order { + // Calculate size that will have been filled before this layer is matched. + sizeFilledByThisLayer := new(big.Rat).SetFrac( + new(big.Int).SetUint64(uint64(params.OrderSizePctPpm*layer)), + lib.BigIntOneMillion(), + ) - // Construct ask at this layer. - ask := clobtypes.Order{ - OrderId: clobtypes.OrderId{ - SubaccountId: *vault, - ClientId: k.GetVaultClobOrderClientId(ctx, clobtypes.Order_SIDE_SELL, uint8(i+1)), - OrderFlags: clobtypes.OrderIdFlags_LongTerm, - ClobPairId: clobPair.Id, - }, - Side: clobtypes.Order_SIDE_SELL, - Quantums: clobPair.StepBaseQuantums, // TODO (TRA-144): Implement order size - Subticks: lib.BigRatRoundToNearestMultiple( - askSubticks, - clobPair.SubticksPerTick, - true, // round up for asks - ), - GoodTilOneof: goodTilBlockTime, + // Ask: leverage_i = leverage - i * order_size_pct + // Bid: leverage_i = leverage + i * order_size_pct + var leverageI *big.Rat + if side == clobtypes.Order_SIDE_SELL { + leverageI = new(big.Rat).Sub( + leverage, + sizeFilledByThisLayer, + ) + } else { + leverageI = new(big.Rat).Add( + leverage, + sizeFilledByThisLayer, + ) } - // Construct bid at this layer. - bid := clobtypes.Order{ + // skew_i = -leverage_i * spread * skew_factor + skewI := lib.BigRatMulPpm(leverageI, spreadPpm) + skewI = lib.BigRatMulPpm(skewI, params.SkewFactorPpm) + skewI = skewI.Neg(skewI) + + // Ask: price = oracle price * (1 + skew_i) * (1 + spread)^(i+1) + // Bid: price = oracle price * (1 + skew_i) / (1 + spread)^(i+1) + orderSubticks := new(big.Rat).Add(skewI, new(big.Rat).SetUint64(1)) + orderSubticks = orderSubticks.Mul(orderSubticks, oracleSubticks) + if side == clobtypes.Order_SIDE_SELL { + orderSubticks = orderSubticks.Mul( + orderSubticks, + spreadMultipler, + ) + } else { + orderSubticks = orderSubticks.Quo( + orderSubticks, + spreadMultipler, + ) + } + + return &clobtypes.Order{ OrderId: clobtypes.OrderId{ SubaccountId: *vault, - ClientId: k.GetVaultClobOrderClientId(ctx, clobtypes.Order_SIDE_BUY, uint8(i+1)), + ClientId: k.GetVaultClobOrderClientId(ctx, side, uint8(layer)), OrderFlags: clobtypes.OrderIdFlags_LongTerm, ClobPairId: clobPair.Id, }, - Side: clobtypes.Order_SIDE_BUY, - Quantums: clobPair.StepBaseQuantums, // TODO (TRA-144): Implement order size + Side: side, + Quantums: lib.BigRatRoundToNearestMultiple( + orderSizeBaseQuantums, + uint32(clobPair.StepBaseQuantums), + false, + ), Subticks: lib.BigRatRoundToNearestMultiple( - bidSubticks, + orderSubticks, clobPair.SubticksPerTick, - false, // round down for bids + side == clobtypes.Order_SIDE_SELL, // round up for asks and down for bids. ), GoodTilOneof: goodTilBlockTime, } + } + orders = make([]*clobtypes.Order, 2*params.Layers) + for i := uint32(0); i < params.Layers; i++ { + // Construct ask at this layer. + orders[2*i] = constructOrder( + clobtypes.Order_SIDE_SELL, + i, + spreadMultiplier, + ) + + // Construct bid at this layer. + orders[2*i+1] = constructOrder( + clobtypes.Order_SIDE_BUY, + i, + spreadMultiplier, + ) - orders[2*i] = &ask - orders[2*i+1] = &bid + // Update spreadMultiplier for next layer. + spreadMultiplier = spreadMultiplier.Mul(spreadMultiplier, spreadBaseMultiplier) } return orders, nil diff --git a/protocol/x/vault/keeper/orders_test.go b/protocol/x/vault/keeper/orders_test.go index 0ce05c600c..ae4313499c 100644 --- a/protocol/x/vault/keeper/orders_test.go +++ b/protocol/x/vault/keeper/orders_test.go @@ -2,12 +2,14 @@ package keeper_test import ( "math" + "math/big" "testing" "github.com/cometbft/cometbft/types" "github.com/dydxprotocol/v4-chain/protocol/dtypes" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/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" @@ -16,33 +18,21 @@ import ( "github.com/stretchr/testify/require" ) -// TODO (TRA-118): store vault strategy constants in x/vault state. -const ( - numLayers = uint8(2) - minBaseSpreadPpm = uint32(3_000) // 30 bps - baseSpreadMinPriceChangePremiumPpm = uint32(1_500) // 15 bps - orderExpirationSeconds = uint32(5) // 5 seconds -) - func TestRefreshAllVaultOrders(t *testing.T) { tests := map[string]struct { // Vault IDs. vaultIds []vaulttypes.VaultId // Total Shares of each vault ID above. - totalShares []vaulttypes.NumShares + totalShares []*big.Int }{ "Two Vaults, Both Positive Shares": { vaultIds: []vaulttypes.VaultId{ constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []vaulttypes.NumShares{ - { - NumShares: dtypes.NewInt(1_000), - }, - { - NumShares: dtypes.NewInt(200), - }, + totalShares: []*big.Int{ + big.NewInt(1_000), + big.NewInt(200), }, }, "Two Vaults, One Positive Shares, One Zero Shares": { @@ -50,13 +40,9 @@ func TestRefreshAllVaultOrders(t *testing.T) { constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []vaulttypes.NumShares{ - { - NumShares: dtypes.NewInt(1_000), - }, - { - NumShares: dtypes.NewInt(0), - }, + totalShares: []*big.Int{ + big.NewInt(1_000), + big.NewInt(0), }, }, "Two Vaults, Both Zero Shares": { @@ -64,13 +50,9 @@ func TestRefreshAllVaultOrders(t *testing.T) { constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []vaulttypes.NumShares{ - { - NumShares: dtypes.NewInt(0), - }, - { - NumShares: dtypes.NewInt(0), - }, + totalShares: []*big.Int{ + big.NewInt(0), + big.NewInt(0), }, }, } @@ -90,7 +72,7 @@ func TestRefreshAllVaultOrders(t *testing.T) { Id: vaultId.ToSubaccountId(), AssetPositions: []*satypes.AssetPosition{ { - AssetId: 0, + AssetId: assettypes.AssetUsdc.Id, Quantums: dtypes.NewInt(1_000_000_000), // 1,000 USDC }, }, @@ -105,7 +87,11 @@ func TestRefreshAllVaultOrders(t *testing.T) { // Set total shares for each vault ID. for i, vaultId := range tc.vaultIds { - err := tApp.App.VaultKeeper.SetTotalShares(ctx, vaultId, tc.totalShares[i]) + err := tApp.App.VaultKeeper.SetTotalShares( + ctx, + vaultId, + vaulttypes.BigIntToNumShares(tc.totalShares[i]), + ) require.NoError(t, err) } @@ -116,7 +102,7 @@ func TestRefreshAllVaultOrders(t *testing.T) { // Simulate vault orders placed in last block. numPreviousOrders := 0 for i, vaultId := range tc.vaultIds { - if tc.totalShares[i].NumShares.Cmp(dtypes.NewInt(0)) > 0 { + if tc.totalShares[i].Sign() > 0 { orders, err := tApp.App.VaultKeeper.GetVaultClobOrders( ctx.WithBlockHeight(ctx.BlockHeight()-1), vaultId, @@ -142,7 +128,7 @@ func TestRefreshAllVaultOrders(t *testing.T) { numExpectedOrders := 0 allExpectedOrderIds := make(map[clobtypes.OrderId]bool) for i, vaultId := range tc.vaultIds { - if tc.totalShares[i].NumShares.Cmp(dtypes.NewInt(0)) > 0 { + if tc.totalShares[i].Sign() > 0 { expectedOrders, err := tApp.App.VaultKeeper.GetVaultClobOrders(ctx, vaultId) require.NoError(t, err) numExpectedOrders += len(expectedOrders) @@ -195,7 +181,7 @@ func TestRefreshVaultClobOrders(t *testing.T) { Id: tc.vaultId.ToSubaccountId(), AssetPositions: []*satypes.AssetPosition{ { - AssetId: 0, + AssetId: assettypes.AssetUsdc.Id, Quantums: dtypes.NewInt(1_000_000_000), // 1,000 USDC }, }, @@ -224,11 +210,12 @@ func TestRefreshVaultClobOrders(t *testing.T) { // Check that there's no error. require.NoError(t, err) // Check that the number of orders is as expected. - require.Len(t, allStatefulOrders, int(numLayers)*2) + params := tApp.App.VaultKeeper.GetParams(ctx) + require.Len(t, allStatefulOrders, int(params.Layers*2)) // Check that the orders are as expected. expectedOrders, err := tApp.App.VaultKeeper.GetVaultClobOrders(ctx, tc.vaultId) require.NoError(t, err) - for i := uint8(0); i < numLayers*2; i++ { + for i := uint32(0); i < params.Layers*2; i++ { require.Equal(t, *expectedOrders[i], allStatefulOrders[i]) } } @@ -239,8 +226,14 @@ func TestRefreshVaultClobOrders(t *testing.T) { func TestGetVaultClobOrders(t *testing.T) { tests := map[string]struct { /* --- Setup --- */ + // Vault params. + vaultParams vaulttypes.Params // Vault ID. vaultId vaulttypes.VaultId + // Vault asset. + vaultAssetQuoteQuantums *big.Int + // Vault inventory. + vaultInventoryBaseQuantums *big.Int // Clob pair. clobPair clobtypes.ClobPair // Market param. @@ -256,72 +249,138 @@ func TestGetVaultClobOrders(t *testing.T) { expectedErr error }{ "Success - Get orders from Vault for Clob Pair 0": { - vaultId: constants.Vault_Clob_0, - clobPair: constants.ClobPair_Btc, - marketParam: constants.TestMarketParams[0], - marketPrice: constants.TestMarketPrices[0], - perpetual: constants.BtcUsd_0DefaultFunding_10AtomicResolution, + vaultParams: vaulttypes.Params{ + Layers: 2, // 2 layers + SpreadMinPpm: 3_000, // 30 bps + SpreadBufferPpm: 1_500, // 15 bps + SkewFactorPpm: 500_000, // 0.5 + OrderSizePctPpm: 100_000, // 10% + OrderExpirationSeconds: 2, // 2 seconds + }, + vaultId: constants.Vault_Clob_0, + vaultAssetQuoteQuantums: big.NewInt(1_000_000_000), // 1,000 USDC + vaultInventoryBaseQuantums: big.NewInt(0), + clobPair: constants.ClobPair_Btc, + marketParam: constants.TestMarketParams[0], + marketPrice: constants.TestMarketPrices[0], + perpetual: constants.BtcUsd_0DefaultFunding_10AtomicResolution, // To calculate order subticks: - // 1. spreadPpm = max(minBaseSpreadPpm, baseSpreadMinPriceChangePremiumPpm + minPriceChangePpm) - // 2. priceSubticks = marketPrice.Price * 10^(marketPrice.Exponent - quantumConversionExponent + - // baseAtomicResolution - quoteAtomicResolution) - // 3. askSubticks at layer i = priceSubticks * (1 + spread)^i - // bidSubticks at layer i = priceSubticks * (1 - spread)^i - // 4. subticks needs to be a multiple of subtickPerTick (round up for asks, round down for bids) + // 1. spread = max(spread_min, spread_buffer + min_price_change) + // 2. leverage = open_notional / equity + // 3. leverage_i = leverage +/- i * order_size_pct (- for ask and + for bid) + // 4. skew_i = -leverage_i * spread * skew_factor + // 5. a_i = oracle_price * (1 + skew_i) * (1 + spread)^{i+1} + // b_i = oracle_price * (1 + skew_i) / (1 + spread)^{i+1} + // 6. subticks needs to be a multiple of subticks_per_tick (round up for asks, round down for bids) + // To calculate size of each order + // 1. `order_size_pct_ppm * equity / oracle_price`. expectedOrderSubticks: []uint64{ // spreadPpm = max(3_000, 1_500 + 50) = 3_000 - // priceSubticks = 5_000_000_000 * 10^(-5 - (-8) + (-10) - (-6)) = 5 * 10^8 - // a_1 = 5 * 10^8 * (1 + 0.003)^1 = 501_500_000 + // spread = 0.003 + // leverage = 0 / 1_000 = 0 + // oracleSubticks = 5_000_000_000 * 10^(-5 - (-8) + (-10) - (-6)) = 5 * 10^8 + // leverage_0 = leverage = 0 + // skew_0 = -0 * 3_000 * 0.5 = 0 + // a_0 = 5 * 10^8 * (1 + 0) * (1 + 0.003)^1 = 501_500_000 501_500_000, - // b_1 = 5 * 10^8 * (1 - 0.003)^1 = 498_500_000 - 498_500_000, - // a_2 = 5 * 10^8 * (1 + 0.003)^2 = 503_004_500 - 503_004_500, - // b_2 = 5 * 10^8 * (1 - 0.003)^2 = 497_004_500 - 497_004_500, + // b_0 = 5 * 10^8 * (1 + 0) / (1 + 0.003)^1 = 498_504_486 + // round down to nearest multiple of subticks_per_tick=5. + 498_504_485, + // leverage_1 = leverage - 0.1 = -0.1 + // skew_1 = 0.1 * 0.003 * 0.5 = 0.00015 + // a_1 = 5 * 10^8 * (1 + 0.00015) * (1 + 0.003)^2 = 503_079_950.675 + // round up to nearest multiple of subticks_per_tick=5. + 503_079_955, + // leverage_1 = leverage + 0.1 = 0.1 + // skew_1 = -0.1 * 0.003 * 0.5 = -0.00015 + // b_2 = 5 * 10^8 * (1 - 0.00015) / (1 + 0.003)^2 ~= 496_938_894.184 + // round down to nearest multiple of subticks_per_tick=5. + 496_938_890, }, - expectedOrderQuantums: []uint64{ // TODO (TRA-144): Implement order size - 5, - 5, - 5, - 5, + // order_size = 10% * 1_000 / 50_000 = 0.002 + // order_size_base_quantums = 0.002 * 10^10 = 20_000_000 + expectedOrderQuantums: []uint64{ + 20_000_000, + 20_000_000, + 20_000_000, + 20_000_000, }, }, "Success - Get orders from Vault for Clob Pair 1": { - vaultId: constants.Vault_Clob_1, - clobPair: constants.ClobPair_Eth, - marketParam: pricestypes.MarketParam{ - Id: constants.TestMarketParams[1].Id, - Pair: constants.TestMarketParams[1].Pair, - Exponent: constants.TestMarketParams[1].Exponent, - MinExchanges: constants.TestMarketParams[1].MinExchanges, - MinPriceChangePpm: 4_200, // Set a high min price change to test spread calculation. - ExchangeConfigJson: constants.TestMarketParams[1].ExchangeConfigJson, + vaultParams: vaulttypes.Params{ + Layers: 3, // 3 layers + SpreadMinPpm: 3_000, // 30 bps + SpreadBufferPpm: 8_500, // 85 bps + SkewFactorPpm: 900_000, // 0.9 + OrderSizePctPpm: 200_000, // 20% + OrderExpirationSeconds: 4, // 4 seconds }, - marketPrice: constants.TestMarketPrices[1], - perpetual: constants.EthUsd_20PercentInitial_10PercentMaintenance, + vaultId: constants.Vault_Clob_1, + vaultAssetQuoteQuantums: big.NewInt(2_000_000_000), // 2,000 USDC + vaultInventoryBaseQuantums: big.NewInt(-500_000_000), // -0.5 ETH + clobPair: constants.ClobPair_Eth, + marketParam: constants.TestMarketParams[1], + marketPrice: constants.TestMarketPrices[1], + perpetual: constants.EthUsd_0DefaultFunding_9AtomicResolution, + // To calculate order subticks: + // 1. spread = max(spread_min, spread_buffer + min_price_change) + // 2. leverage = open_notional / equity + // 3. leverage_i = leverage +/- i * order_size_pct (- for ask and + for bid) + // 4. skew_i = -leverage_i * spread * skew_factor + // 5. a_i = oracle_price * (1 + skew_i) * (1 + spread)^{i+1} + // b_i = oracle_price * (1 + skew_i) / (1 + spread)^{i+1} + // 6. subticks needs to be a multiple of subticks_per_tick (round up for asks, round down for bids) + // To calculate size of each order + // 1. `order_size_pct_ppm * equity / oracle_price`. expectedOrderSubticks: []uint64{ - // spreadPpm = max(3_000, 1_500 + 4_200) = 5_700 - // priceSubticks = 3_000_000_000 * 10^(-6 - (-9) + (-9) - (-6)) = 3 * 10^9 - // a_1 = 3 * 10^9 * (1 + 0.0057)^1 = 3_017_100_000 - 3_017_100_000, - // b_1 = 3 * 10^9 * (1 - 0.0057)^1 = 2_982_900_000 - 2_982_900_000, - // a_2 = 3 * 10^9 * (1 + 0.0057)^2 = 3_034_297_470 - // round up to nearest multiple of subticksPerTick=1000. - 3_034_298_000, - // b_2 = 3 * 10^9 * (1 - 0.0057)^2 = 2_965_897_470 - // round down to nearest multiple of subticksPerTick=1000. - 2_965_897_000, + // spreadPpm = max(3_000, 8_500 + 50) = 8_550 + // spread = 0.00855 + // open_notional = -500_000_000 * 10^-9 * 3_000 * 10^6 = -1_500_000_000 + // leverage = -1_500_000_000 / (2_000_000_000 - 1_500_000_000) = -3 + // oracleSubticks = 3_000_000_000 * 10^(-6 - (-9) + (-9) - (-6)) = 3 * 10^9 + // leverage_0 = leverage - 0 * 0.2 = -3 + // skew_0 = 3 * 0.00855 * 0.9 + // a_0 = 3 * 10^9 * (1 + skew_0) * (1 + 0.00855)^1 = 3_095_497_130.25 + // round up to nearest multiple of subticks_per_tick=1_000. + 3_095_498_000, + // b_0 = 3 * 10^9 * (1 + skew_0) / (1 + 0.00855)^1 ~= 3_043_235_337.86 + // round down to nearest multiple of subticks_per_tick=1_000. + 3_043_235_000, + // leverage_1 = leverage - 1 * 0.2 + // skew_1 = -leverage_1 * 0.00855 * 0.9 + // a_1 = 3 * 10^9 * (1 + skew_1) * (1 + 0.00855)^2 ~= 3_126_659_918.93 + // round up to nearest multiple of subticks_per_tick=1_000. + 3_126_660_000, + // leverage_1 = leverage + 1 * 0.2 + // skew_1 = -leverage_1 * 0.00855 * 0.9 + // b_1 = 3 * 10^9 * (1 + skew_1) / (1 + 0.00855)^2 ~= 3_012_897_207.43 + // round down to nearest multiple of subticks_per_tick=5. + 3_012_897_000, + // leverage_2 = leverage - 2 * 0.2 + // skew_2 = -leverage_2 * 0.00855 * 0.9 + // a_2 = 3 * 10^9 * (1 + skew_2) * (1 + 0.00855)^3 ~= 3_158_129_302.71 + // round up to nearest multiple of subticks_per_tick=1_000. + 3_158_130_000, + // leverage_2 = leverage + 2 * 0.2 + // skew_2 = -leverage_2 * 0.00855 * 0.9 + // b_2 = 3 * 10^9 * (1 + skew_2) / (1 + 0.00855)^3 ~= 2_982_854_748.91 + // round down to nearest multiple of subticks_per_tick=1_000. + 2_982_854_000, }, - expectedOrderQuantums: []uint64{ // TODO (TRA-144): Implement order size - 1000, - 1000, - 1000, - 1000, + // order_size = 20% * 500 / 3000 ~= 0.0333333333 + // order_size_base_quantums = 0.0333333333 * 10^9 ~= 33_333_333.33 + // round down to nearest multiple of step_base_quantums=1_000. + expectedOrderQuantums: []uint64{ + 33_333_000, + 33_333_000, + 33_333_000, + 33_333_000, + 33_333_000, + 33_333_000, }, }, "Error - Clob Pair doesn't exist": { + vaultParams: vaulttypes.DefaultParams(), vaultId: constants.Vault_Clob_0, clobPair: constants.ClobPair_Eth, marketParam: constants.TestMarketParams[1], @@ -329,6 +388,28 @@ func TestGetVaultClobOrders(t *testing.T) { perpetual: constants.EthUsd_NoMarginRequirement, expectedErr: vaulttypes.ErrClobPairNotFound, }, + "Error - Vault equity is zero": { + vaultParams: vaulttypes.DefaultParams(), + vaultId: constants.Vault_Clob_0, + vaultAssetQuoteQuantums: big.NewInt(0), + vaultInventoryBaseQuantums: big.NewInt(0), + clobPair: constants.ClobPair_Btc, + marketParam: constants.TestMarketParams[0], + marketPrice: constants.TestMarketPrices[0], + perpetual: constants.BtcUsd_0DefaultFunding_10AtomicResolution, + expectedErr: vaulttypes.ErrNonPositiveEquity, + }, + "Error - Vault equity is negative": { + vaultParams: vaulttypes.DefaultParams(), + vaultId: constants.Vault_Clob_0, + vaultAssetQuoteQuantums: big.NewInt(5_000_000), // 5 USDC + vaultInventoryBaseQuantums: big.NewInt(-10_000_000), + clobPair: constants.ClobPair_Btc, + marketParam: constants.TestMarketParams[0], + marketPrice: constants.TestMarketPrices[0], + perpetual: constants.BtcUsd_0DefaultFunding_10AtomicResolution, + expectedErr: vaulttypes.ErrNonPositiveEquity, + }, } for name, tc := range tests { @@ -359,6 +440,46 @@ func TestGetVaultClobOrders(t *testing.T) { genesisState.ClobPairs = []clobtypes.ClobPair{tc.clobPair} }, ) + // Initialize vault module with test params. + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *vaulttypes.GenesisState) { + genesisState.Params = tc.vaultParams + }, + ) + // Initialize subaccounts module with vault's equity and inventory. + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + assetPositions := []*satypes.AssetPosition{} + if tc.vaultAssetQuoteQuantums != nil && tc.vaultAssetQuoteQuantums.Sign() != 0 { + assetPositions = append( + assetPositions, + &satypes.AssetPosition{ + AssetId: assettypes.AssetUsdc.Id, + Quantums: dtypes.NewIntFromBigInt(tc.vaultAssetQuoteQuantums), + }, + ) + } + perpPositions := []*satypes.PerpetualPosition{} + if tc.vaultInventoryBaseQuantums != nil && tc.vaultInventoryBaseQuantums.Sign() != 0 { + perpPositions = append( + perpPositions, + &satypes.PerpetualPosition{ + PerpetualId: tc.perpetual.Params.Id, + Quantums: dtypes.NewIntFromBigInt(tc.vaultInventoryBaseQuantums), + }, + ) + } + genesisState.Subaccounts = []satypes.Subaccount{ + { + Id: tc.vaultId.ToSubaccountId(), + AssetPositions: assetPositions, + PerpetualPositions: perpPositions, + }, + } + }, + ) return genesis }).Build() ctx := tApp.InitChain() @@ -372,6 +493,7 @@ func TestGetVaultClobOrders(t *testing.T) { require.NoError(t, err) // Get expected orders. + params := tApp.App.VaultKeeper.GetParams(ctx) buildVaultClobOrder := func( layer uint8, side clobtypes.Order_Side, @@ -389,24 +511,24 @@ func TestGetVaultClobOrders(t *testing.T) { Quantums: quantums, Subticks: subticks, GoodTilOneof: &clobtypes.Order_GoodTilBlockTime{ - GoodTilBlockTime: uint32(ctx.BlockTime().Unix()) + orderExpirationSeconds, + GoodTilBlockTime: uint32(ctx.BlockTime().Unix()) + params.OrderExpirationSeconds, }, } } expectedOrders := make([]*clobtypes.Order, 0) - for i := uint8(0); i < numLayers; i++ { + for i := uint32(0); i < params.Layers; i++ { expectedOrders = append( expectedOrders, // ask. buildVaultClobOrder( - i+1, + uint8(i), clobtypes.Order_SIDE_SELL, tc.expectedOrderQuantums[2*i], tc.expectedOrderSubticks[2*i], ), // bid. buildVaultClobOrder( - i+1, + uint8(i), clobtypes.Order_SIDE_BUY, tc.expectedOrderQuantums[2*i+1], tc.expectedOrderSubticks[2*i+1], diff --git a/protocol/x/vault/keeper/params.go b/protocol/x/vault/keeper/params.go new file mode 100644 index 0000000000..4d913e6a96 --- /dev/null +++ b/protocol/x/vault/keeper/params.go @@ -0,0 +1,35 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" +) + +// GetParams returns `Params` in state. +func (k Keeper) GetParams( + ctx sdk.Context, +) ( + params types.Params, +) { + store := ctx.KVStore(k.storeKey) + b := store.Get([]byte(types.ParamsKey)) + k.cdc.MustUnmarshal(b, ¶ms) + return params +} + +// SetParams updates `Params` in state. +// Returns an error iff validation fails. +func (k Keeper) SetParams( + ctx sdk.Context, + params types.Params, +) error { + if err := params.Validate(); err != nil { + return err + } + + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshal(¶ms) + store.Set([]byte(types.ParamsKey), b) + + return nil +} diff --git a/protocol/x/vault/keeper/params_test.go b/protocol/x/vault/keeper/params_test.go new file mode 100644 index 0000000000..2807715b6a --- /dev/null +++ b/protocol/x/vault/keeper/params_test.go @@ -0,0 +1,45 @@ +package keeper_test + +import ( + "testing" + + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" +) + +func TestGetSetParams(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.VaultKeeper + + // Params should have default values at genesis. + params := k.GetParams(ctx) + require.Equal(t, types.DefaultParams(), params) + + // Set new params and get. + newParams := types.Params{ + Layers: 3, + SpreadMinPpm: 4_000, + SpreadBufferPpm: 2_000, + SkewFactorPpm: 999_999, + OrderSizePctPpm: 200_000, + OrderExpirationSeconds: 10, + } + err := k.SetParams(ctx, newParams) + require.NoError(t, err) + require.Equal(t, newParams, k.GetParams(ctx)) + + // Set invalid params and get. + invalidParams := types.Params{ + Layers: 3, + SpreadMinPpm: 4_000, + SpreadBufferPpm: 2_000, + SkewFactorPpm: 1_000_000, + OrderSizePctPpm: 200_000, + OrderExpirationSeconds: 0, // invalid + } + err = k.SetParams(ctx, invalidParams) + require.Error(t, err) + require.Equal(t, newParams, k.GetParams(ctx)) +} diff --git a/protocol/x/vault/keeper/shares.go b/protocol/x/vault/keeper/shares.go index de89060ad5..81948611fc 100644 --- a/protocol/x/vault/keeper/shares.go +++ b/protocol/x/vault/keeper/shares.go @@ -1,10 +1,12 @@ package keeper import ( + "math/big" + "cosmossdk.io/store/prefix" storetypes "cosmossdk.io/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" ) @@ -24,13 +26,14 @@ func (k Keeper) GetTotalShares( return val, true } -// SetTotalShares sets TotalShares for a vault. Returns error if `totalShares` is negative. +// SetTotalShares sets TotalShares for a vault. Returns error if `totalShares` fails validation +// or is negative. func (k Keeper) SetTotalShares( ctx sdk.Context, vaultId types.VaultId, totalShares types.NumShares, ) error { - if totalShares.NumShares.Cmp(dtypes.NewInt(0)) == -1 { + if totalShares.NumShares.BigInt().Sign() < 0 { return types.ErrNegativeShares } @@ -38,6 +41,12 @@ func (k Keeper) SetTotalShares( totalSharesStore := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.TotalSharesKeyPrefix)) totalSharesStore.Set(vaultId.ToStateKey(), b) + // Emit metric on TotalShares. + vaultId.SetGaugeWithLabels( + metrics.TotalShares, + float32(totalShares.NumShares.BigInt().Uint64()), + ) + return nil } @@ -47,3 +56,135 @@ func (k Keeper) getTotalSharesIterator(ctx sdk.Context) storetypes.Iterator { return storetypes.KVStorePrefixIterator(store, []byte{}) } + +// GetOwnerShares gets owner shares for an owner in a vault. +func (k Keeper) GetOwnerShares( + ctx sdk.Context, + vaultId types.VaultId, + owner string, +) (val types.NumShares, exists bool) { + store := k.getVaultOwnerSharesStore(ctx, vaultId) + + b := store.Get([]byte(owner)) + if b == nil { + return val, false + } + + k.cdc.MustUnmarshal(b, &val) + return val, true +} + +// SetOwnerShares sets owner shares for an owner in a vault. +func (k Keeper) SetOwnerShares( + ctx sdk.Context, + vaultId types.VaultId, + owner string, + ownerShares types.NumShares, +) error { + if ownerShares.NumShares.BigInt().Sign() < 0 { + return types.ErrNegativeShares + } + + b := k.cdc.MustMarshal(&ownerShares) + store := k.getVaultOwnerSharesStore(ctx, vaultId) + store.Set([]byte(owner), b) + + return nil +} + +// getVaultOwnerSharesStore returns the store for owner shares of a given vault. +func (k Keeper) getVaultOwnerSharesStore( + ctx sdk.Context, + vaultId types.VaultId, +) prefix.Store { + store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.OwnerSharesKeyPrefix)) + return prefix.NewStore(store, vaultId.ToStateKeyPrefix()) +} + +// MintShares mints shares of a vault for `owner` based on `quantumsToDeposit` by: +// 1. Increasing total shares of the vault. +// 2. Increasing owner shares of the vault for given `owner`. +func (k Keeper) MintShares( + ctx sdk.Context, + vaultId types.VaultId, + owner string, + quantumsToDeposit *big.Int, +) error { + // Quantums to deposit should be positive. + if quantumsToDeposit.Sign() <= 0 { + return types.ErrInvalidDepositAmount + } + // Get existing TotalShares of the vault. + totalShares, exists := k.GetTotalShares(ctx, vaultId) + existingTotalShares := totalShares.NumShares.BigInt() + // Calculate shares to mint. + var sharesToMint *big.Int + if !exists || existingTotalShares.Sign() <= 0 { + // Mint `quoteQuantums` number of shares. + sharesToMint = new(big.Int).Set(quantumsToDeposit) + // Initialize existingTotalShares as 0. + existingTotalShares = big.NewInt(0) + } else { + // Get vault equity. + equity, err := k.GetVaultEquity(ctx, vaultId) + if err != nil { + return err + } + // Don't mint shares if equity is non-positive. + if equity.Sign() <= 0 { + return types.ErrNonPositiveEquity + } + // Mint `deposit (in quote quantums) * existing shares / vault equity (in quote quantums)` + // number of shares. + // For example: + // - a vault currently has 5000 shares and 4000 equity (in quote quantums) + // - each quote quantum is worth 5000 / 4000 = 1.25 shares + // - a deposit of 1000 quote quantums should thus be given 1000 * 1.25 = 1250 shares + sharesToMint = new(big.Int).Set(quantumsToDeposit) + sharesToMint = sharesToMint.Mul(sharesToMint, existingTotalShares) + sharesToMint = sharesToMint.Quo(sharesToMint, equity) + } + + // Increase TotalShares of the vault. + err := k.SetTotalShares( + ctx, + vaultId, + types.BigIntToNumShares( + existingTotalShares.Add(existingTotalShares, sharesToMint), + ), + ) + if err != nil { + return err + } + + // Increase owner shares in the vault. + ownerShares, exists := k.GetOwnerShares(ctx, vaultId, owner) + if !exists { + // Set owner shares to be sharesToMint. + err := k.SetOwnerShares( + ctx, + vaultId, + owner, + types.BigIntToNumShares(sharesToMint), + ) + if err != nil { + return err + } + } else { + // Increase existing owner shares by sharesToMint. + existingOwnerShares := ownerShares.NumShares.BigInt() + err = k.SetOwnerShares( + ctx, + vaultId, + owner, + types.BigIntToNumShares( + existingOwnerShares.Add(existingOwnerShares, sharesToMint), + ), + ) + if err != nil { + return err + } + } + + return nil +} diff --git a/protocol/x/vault/keeper/shares_test.go b/protocol/x/vault/keeper/shares_test.go index 06454b0b80..0728a18cb9 100644 --- a/protocol/x/vault/keeper/shares_test.go +++ b/protocol/x/vault/keeper/shares_test.go @@ -1,12 +1,15 @@ package keeper_test import ( + "math/big" "testing" + "github.com/cometbft/cometbft/types" "github.com/dydxprotocol/v4-chain/protocol/dtypes" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" - "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" "github.com/stretchr/testify/require" ) @@ -20,48 +23,331 @@ func TestGetSetTotalShares(t *testing.T) { require.Equal(t, false, exists) // Set total shares for a vault and then get. - err := k.SetTotalShares(ctx, constants.Vault_Clob_0, types.NumShares{ - NumShares: dtypes.NewInt(7), - }) + numShares := vaulttypes.BigIntToNumShares( + big.NewInt(7), + ) + err := k.SetTotalShares(ctx, constants.Vault_Clob_0, numShares) require.NoError(t, err) - numShares, exists := k.GetTotalShares(ctx, constants.Vault_Clob_0) + got, exists := k.GetTotalShares(ctx, constants.Vault_Clob_0) require.Equal(t, true, exists) - require.Equal(t, dtypes.NewInt(7), numShares.NumShares) + require.Equal(t, numShares, got) // Set total shares for another vault and then get. - err = k.SetTotalShares(ctx, constants.Vault_Clob_1, types.NumShares{ - NumShares: dtypes.NewInt(456), - }) + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(456), + ) + err = k.SetTotalShares(ctx, constants.Vault_Clob_1, numShares) require.NoError(t, err) - numShares, exists = k.GetTotalShares(ctx, constants.Vault_Clob_1) + got, exists = k.GetTotalShares(ctx, constants.Vault_Clob_1) require.Equal(t, true, exists) - require.Equal(t, dtypes.NewInt(456), numShares.NumShares) + require.Equal(t, numShares, got) // Set total shares for second vault to 0. - err = k.SetTotalShares(ctx, constants.Vault_Clob_1, types.NumShares{ - NumShares: dtypes.NewInt(0), - }) + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(0), + ) + err = k.SetTotalShares(ctx, constants.Vault_Clob_1, numShares) require.NoError(t, err) - numShares, exists = k.GetTotalShares(ctx, constants.Vault_Clob_1) + got, exists = k.GetTotalShares(ctx, constants.Vault_Clob_1) require.Equal(t, true, exists) - require.Equal(t, dtypes.NewInt(0), numShares.NumShares) + require.Equal(t, numShares, got) // Set total shares for the first vault again and then get. - err = k.SetTotalShares(ctx, constants.Vault_Clob_0, types.NumShares{ - NumShares: dtypes.NewInt(123), - }) + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(7283133), + ) + err = k.SetTotalShares(ctx, constants.Vault_Clob_0, numShares) require.NoError(t, err) - numShares, exists = k.GetTotalShares(ctx, constants.Vault_Clob_0) + got, exists = k.GetTotalShares(ctx, constants.Vault_Clob_0) require.Equal(t, true, exists) - require.Equal(t, dtypes.NewInt(123), numShares.NumShares) + require.Equal(t, numShares, got) // Set total shares for the first vault to a negative value. // Should get error and total shares should remain unchanged. - err = k.SetTotalShares(ctx, constants.Vault_Clob_0, types.NumShares{ - NumShares: dtypes.NewInt(-1), - }) - require.Equal(t, types.ErrNegativeShares, err) - numShares, exists = k.GetTotalShares(ctx, constants.Vault_Clob_0) + negativeShares := vaulttypes.BigIntToNumShares( + big.NewInt(-1), + ) + err = k.SetTotalShares(ctx, constants.Vault_Clob_0, negativeShares) + require.Equal(t, vaulttypes.ErrNegativeShares, err) + got, exists = k.GetTotalShares(ctx, constants.Vault_Clob_0) require.Equal(t, true, exists) - require.Equal(t, dtypes.NewInt(123), numShares.NumShares) + require.Equal( + t, + numShares, + got, + ) +} + +func TestGetSetOwnerShares(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.VaultKeeper + + alice := constants.AliceAccAddress.String() + bob := constants.BobAccAddress.String() + + // Get owners shares for Alice in vault clob 0. + _, exists := k.GetOwnerShares(ctx, constants.Vault_Clob_0, alice) + require.Equal(t, false, exists) + + // Set owner shares for Alice in vault clob 0 and get. + numShares := vaulttypes.BigIntToNumShares( + big.NewInt(7), + ) + err := k.SetOwnerShares(ctx, constants.Vault_Clob_0, alice, numShares) + require.NoError(t, err) + got, exists := k.GetOwnerShares(ctx, constants.Vault_Clob_0, alice) + require.Equal(t, true, exists) + require.Equal(t, numShares, got) + + // Set owner shares for Alice in vault clob 1 and then get. + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(456), + ) + err = k.SetOwnerShares(ctx, constants.Vault_Clob_1, alice, numShares) + require.NoError(t, err) + got, exists = k.GetOwnerShares(ctx, constants.Vault_Clob_1, alice) + require.Equal(t, true, exists) + require.Equal(t, numShares, got) + + // Set owner shares for Bob in vault clob 1. + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(0), + ) + err = k.SetOwnerShares(ctx, constants.Vault_Clob_1, bob, numShares) + require.NoError(t, err) + got, exists = k.GetOwnerShares(ctx, constants.Vault_Clob_1, bob) + require.Equal(t, true, exists) + require.Equal(t, numShares, got) + + // Set owner shares for Bob in vault clob 1 to a negative value. + // Should get error and total shares should remain unchanged. + numSharesInvalid := vaulttypes.BigIntToNumShares( + big.NewInt(-1), + ) + err = k.SetOwnerShares(ctx, constants.Vault_Clob_1, bob, numSharesInvalid) + require.ErrorIs(t, err, vaulttypes.ErrNegativeShares) + got, exists = k.GetOwnerShares(ctx, constants.Vault_Clob_1, bob) + require.Equal(t, true, exists) + require.Equal(t, numShares, got) +} + +func TestMintShares(t *testing.T) { + tests := map[string]struct { + /* --- Setup --- */ + // Vault ID. + vaultId vaulttypes.VaultId + // Existing vault equity. + equity *big.Int + // Existing vault TotalShares. + totalShares *big.Int + // Owner that deposits. + owner string + // Existing owner shares. + ownerShares *big.Int + // Quote quantums to deposit. + quantumsToDeposit *big.Int + + /* --- Expectations --- */ + // Expected TotalShares after minting. + expectedTotalShares *big.Int + // Expected OwnerShares after minting. + expectedOwnerShares *big.Int + // Expected error. + expectedErr error + }{ + "Equity 0, TotalShares 0, OwnerShares 0, Deposit 1000": { + vaultId: constants.Vault_Clob_0, + equity: big.NewInt(0), + totalShares: big.NewInt(0), + owner: constants.AliceAccAddress.String(), + ownerShares: big.NewInt(0), + quantumsToDeposit: big.NewInt(1_000), + // Should mint `1_000` shares. + expectedTotalShares: big.NewInt(1_000), + expectedOwnerShares: big.NewInt(1_000), + }, + "Equity 0, TotalShares non-existent, OwnerShares non-existent, Deposit 12345654321": { + vaultId: constants.Vault_Clob_0, + equity: big.NewInt(0), + owner: constants.AliceAccAddress.String(), + quantumsToDeposit: big.NewInt(12_345_654_321), + // Should mint `12_345_654_321` shares. + expectedTotalShares: big.NewInt(12_345_654_321), + expectedOwnerShares: big.NewInt(12_345_654_321), + }, + "Equity 1000, TotalShares non-existent, OwnerShares non-existent, Deposit 500": { + vaultId: constants.Vault_Clob_0, + equity: big.NewInt(1_000), + owner: constants.AliceAccAddress.String(), + quantumsToDeposit: big.NewInt(500), + // Should mint `500` shares. + expectedTotalShares: big.NewInt(500), + expectedOwnerShares: big.NewInt(500), + }, + "Equity 4000, TotalShares 5000, OwnerShares 2500, Deposit 1000": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(4_000), + totalShares: big.NewInt(5_000), + owner: constants.AliceAccAddress.String(), + ownerShares: big.NewInt(2_500), + quantumsToDeposit: big.NewInt(1_000), + // Should mint `1_250` shares. + expectedTotalShares: big.NewInt(6_250), + expectedOwnerShares: big.NewInt(3_750), + }, + "Equity 1_000_000, TotalShares 2_000, OwnerShares 1, Deposit 1_000": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(1_000_000), + totalShares: big.NewInt(2_000), + owner: constants.BobAccAddress.String(), + ownerShares: big.NewInt(1), + quantumsToDeposit: big.NewInt(1_000), + // Should mint `2` shares. + expectedTotalShares: big.NewInt(2_002), + expectedOwnerShares: big.NewInt(3), + }, + "Equity 8000, TotalShares 4000, OwnerShares 101, Deposit 455": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(8_000), + totalShares: big.NewInt(4_000), + owner: constants.CarlAccAddress.String(), + ownerShares: big.NewInt(101), + quantumsToDeposit: big.NewInt(455), + // Should mint `227.5` shares, round down to 227. + expectedTotalShares: big.NewInt(4_227), + expectedOwnerShares: big.NewInt(328), + }, + "Equity 123456, TotalShares 654321, OwnerShares 0, Deposit 123456789": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(123_456), + totalShares: big.NewInt(654_321), + owner: constants.DaveAccAddress.String(), + quantumsToDeposit: big.NewInt(123_456_789), + // Should mint `654_325_181.727` shares, round down to 654_325_181. + expectedTotalShares: big.NewInt(654_979_502), + expectedOwnerShares: big.NewInt(654_325_181), + }, + "Equity 1000000, TotalShares 1000, OwnerShares 0, Deposit 99": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(1_000_000), + totalShares: big.NewInt(1_000), + owner: constants.DaveAccAddress.String(), + quantumsToDeposit: big.NewInt(99), + // Should mint `99 * 1_000 / 1_000_000` shares, round down to 0. + expectedTotalShares: big.NewInt(1_000), + expectedOwnerShares: big.NewInt(0), + }, + "Equity -1, TotalShares 10, Deposit 1": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(-1), + totalShares: big.NewInt(10), + owner: constants.AliceAccAddress.String(), + quantumsToDeposit: big.NewInt(1), + expectedErr: vaulttypes.ErrNonPositiveEquity, + }, + "Equity 1, TotalShares 1, Deposit 0": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(1), + totalShares: big.NewInt(1), + owner: constants.AliceAccAddress.String(), + quantumsToDeposit: big.NewInt(0), + expectedErr: vaulttypes.ErrInvalidDepositAmount, + }, + "Equity 0, TotalShares non-existent, Deposit -1": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(0), + owner: constants.AliceAccAddress.String(), + quantumsToDeposit: big.NewInt(-1), + expectedErr: vaulttypes.ErrInvalidDepositAmount, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // Initialize tApp and ctx. + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis types.GenesisDoc) { + genesis = testapp.DefaultGenesis() + // Initialize vault with its existing equity. + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + genesisState.Subaccounts = []satypes.Subaccount{ + { + Id: tc.vaultId.ToSubaccountId(), + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: 0, + Quantums: dtypes.NewIntFromBigInt(tc.equity), + }, + }, + }, + } + }, + ) + return genesis + }).Build() + ctx := tApp.InitChain() + + // Set vault's existing total shares if specified. + if tc.totalShares != nil { + err := tApp.App.VaultKeeper.SetTotalShares( + ctx, + tc.vaultId, + vaulttypes.BigIntToNumShares(tc.totalShares), + ) + require.NoError(t, err) + } + // Set vault's existing owner shares if specified. + if tc.ownerShares != nil { + err := tApp.App.VaultKeeper.SetOwnerShares( + ctx, + tc.vaultId, + tc.owner, + vaulttypes.BigIntToNumShares(tc.ownerShares), + ) + require.NoError(t, err) + } + + // Mint shares. + err := tApp.App.VaultKeeper.MintShares( + ctx, + tc.vaultId, + tc.owner, + tc.quantumsToDeposit, + ) + if tc.expectedErr != nil { + // Check that error is as expected. + require.ErrorContains(t, err, tc.expectedErr.Error()) + // Check that TotalShares is unchanged. + totalShares, _ := tApp.App.VaultKeeper.GetTotalShares(ctx, tc.vaultId) + require.Equal( + t, + vaulttypes.BigIntToNumShares(tc.totalShares), + totalShares, + ) + // Check that OwnerShares is unchanged. + ownerShares, _ := tApp.App.VaultKeeper.GetOwnerShares(ctx, tc.vaultId, tc.owner) + require.Equal(t, vaulttypes.BigIntToNumShares(tc.ownerShares), ownerShares) + } else { + require.NoError(t, err) + // Check that TotalShares is as expected. + totalShares, exists := tApp.App.VaultKeeper.GetTotalShares(ctx, tc.vaultId) + require.True(t, exists) + require.Equal( + t, + vaulttypes.BigIntToNumShares(tc.expectedTotalShares), + totalShares, + ) + // Check that OwnerShares is as expected. + ownerShares, exists := tApp.App.VaultKeeper.GetOwnerShares(ctx, tc.vaultId, tc.owner) + require.True(t, exists) + require.Equal( + t, + vaulttypes.BigIntToNumShares(tc.expectedOwnerShares), + ownerShares, + ) + } + }) + } } diff --git a/protocol/x/vault/keeper/vault.go b/protocol/x/vault/keeper/vault.go new file mode 100644 index 0000000000..6bcd3ceb75 --- /dev/null +++ b/protocol/x/vault/keeper/vault.go @@ -0,0 +1,101 @@ +package keeper + +import ( + "math/big" + + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib/log" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" +) + +// GetVaultEquity returns the equity of a vault (in quote quantums). +func (k Keeper) GetVaultEquity( + ctx sdk.Context, + vaultId types.VaultId, +) (*big.Int, error) { + netCollateral, _, _, err := k.subaccountsKeeper.GetNetCollateralAndMarginRequirements( + ctx, + satypes.Update{ + SubaccountId: *vaultId.ToSubaccountId(), + }, + ) + if err != nil { + return nil, err + } + return netCollateral, nil +} + +// GetVaultInventory returns the inventory of a vault in a given perpeutal (in base quantums). +func (k Keeper) GetVaultInventoryInPerpetual( + ctx sdk.Context, + vaultId types.VaultId, + perpId uint32, +) *big.Int { + // Get subaccount. + subaccount := k.subaccountsKeeper.GetSubaccount(ctx, *vaultId.ToSubaccountId()) + // Calculate inventory. + inventory := big.NewInt(0) + for _, p := range subaccount.PerpetualPositions { + if p.GetPerpetualId() == perpId { + inventory.Add(inventory, p.GetBigQuantums()) + break + } + } + return inventory +} + +// DecommissionVaults decommissions all vaults with positive shares and non-positive equity. +func (k Keeper) DecommissionNonPositiveEquityVaults( + ctx sdk.Context, +) { + // Iterate through all vaults. + totalSharesIterator := k.getTotalSharesIterator(ctx) + defer totalSharesIterator.Close() + for ; totalSharesIterator.Valid(); totalSharesIterator.Next() { + var totalShares types.NumShares + k.cdc.MustUnmarshal(totalSharesIterator.Value(), &totalShares) + + // Skip if TotalShares is non-positive. + if totalShares.NumShares.BigInt().Sign() <= 0 { + continue + } + + // Get vault equity. + vaultId, err := types.GetVaultIdFromStateKey(totalSharesIterator.Key()) + if err != nil { + log.ErrorLogWithError(ctx, "Failed to get vault ID from state key", err) + continue + } + equity, err := k.GetVaultEquity(ctx, *vaultId) + if err != nil { + log.ErrorLogWithError(ctx, "Failed to get vault equity", err) + continue + } + + // Decommission vault if equity is non-positive. + if equity.Sign() <= 0 { + k.DecommissionVault(ctx, *vaultId) + } + } +} + +// DecommissionVault decommissions a vault by deleting its total shares and owner shares. +func (k Keeper) DecommissionVault( + ctx sdk.Context, + vaultId types.VaultId, +) { + // Delete TotalShares of the vault. + totalSharesStore := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.TotalSharesKeyPrefix)) + totalSharesStore.Delete(vaultId.ToStateKey()) + + // Delete all OwnerShares of the vault. + ownerSharesStore := k.getVaultOwnerSharesStore(ctx, vaultId) + ownerSharesIterator := storetypes.KVStorePrefixIterator(ownerSharesStore, []byte{}) + defer ownerSharesIterator.Close() + for ; ownerSharesIterator.Valid(); ownerSharesIterator.Next() { + ownerSharesStore.Delete(ownerSharesIterator.Key()) + } +} diff --git a/protocol/x/vault/keeper/vault_test.go b/protocol/x/vault/keeper/vault_test.go new file mode 100644 index 0000000000..319fc0f4a4 --- /dev/null +++ b/protocol/x/vault/keeper/vault_test.go @@ -0,0 +1,223 @@ +package keeper_test + +import ( + "math/big" + "testing" + + "github.com/cometbft/cometbft/types" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" +) + +func TestDecommissionNonPositiveEquityVaults(t *testing.T) { + tests := map[string]struct { + /* --- Setup --- */ + // Vault IDs. + vaultIds []vaulttypes.VaultId + // Total shares of above vaults. + totalShares []*big.Int + // Equities of above vaults. + equities []*big.Int + + /* --- Expectations --- */ + // Whether the vaults are decommissioned. + decommissioned []bool + }{ + "Decommission no vault": { + vaultIds: []vaulttypes.VaultId{ + constants.Vault_Clob_0, + constants.Vault_Clob_1, + }, + totalShares: []*big.Int{ + big.NewInt(7), + big.NewInt(7), + }, + equities: []*big.Int{ + big.NewInt(1), + big.NewInt(1), + }, + decommissioned: []bool{ + false, + false, + }, + }, + "Decommission one vault": { + vaultIds: []vaulttypes.VaultId{ + constants.Vault_Clob_0, + constants.Vault_Clob_1, + }, + totalShares: []*big.Int{ + big.NewInt(7), + big.NewInt(7), + }, + equities: []*big.Int{ + big.NewInt(1), + big.NewInt(0), + }, + decommissioned: []bool{ + false, + true, // this vault should be decommissioned. + }, + }, + "Decommission two vaults": { + vaultIds: []vaulttypes.VaultId{ + constants.Vault_Clob_0, + constants.Vault_Clob_1, + }, + totalShares: []*big.Int{ + big.NewInt(7), + big.NewInt(7), + }, + equities: []*big.Int{ + big.NewInt(0), + big.NewInt(-1), + }, + decommissioned: []bool{ + true, + true, + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // Initialize vaults with their equities. + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn(func() (genesis types.GenesisDoc) { + genesis = testapp.DefaultGenesis() + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + subaccounts := []satypes.Subaccount{} + for i, vaultId := range tc.vaultIds { + if tc.equities[i].Sign() != 0 { + subaccounts = append( + subaccounts, + satypes.Subaccount{ + Id: vaultId.ToSubaccountId(), + AssetPositions: []*satypes.AssetPosition{ + { + AssetId: assettypes.AssetUsdc.Id, + Quantums: dtypes.NewIntFromBigInt(tc.equities[i]), + }, + }, + }, + ) + } + } + genesisState.Subaccounts = subaccounts + }, + ) + return genesis + }).Build() + ctx := tApp.InitChain() + k := tApp.App.VaultKeeper + + // Set total shares and owner shares for all vaults. + testOwner := constants.Alice_Num0.Owner + for i, vaultId := range tc.vaultIds { + err := k.SetTotalShares( + ctx, + vaultId, + vaulttypes.BigIntToNumShares(tc.totalShares[i]), + ) + require.NoError(t, err) + err = k.SetOwnerShares( + ctx, + vaultId, + testOwner, + vaulttypes.BigIntToNumShares(big.NewInt(7)), + ) + require.NoError(t, err) + } + + // Decommission all vaults. + k.DecommissionNonPositiveEquityVaults(ctx) + + // Check that total shares and owner shares are deleted for decommissioned + // vaults and not deleted for non-decommissioned vaults. + for i, decommissioned := range tc.decommissioned { + _, exists := k.GetTotalShares(ctx, tc.vaultIds[i]) + require.Equal(t, !decommissioned, exists) + _, exists = k.GetOwnerShares(ctx, tc.vaultIds[i], testOwner) + require.Equal(t, !decommissioned, exists) + } + }) + } +} + +func TestDecommissionVault(t *testing.T) { + tests := map[string]struct { + /* --- Setup --- */ + // Vault ID. + vaultId vaulttypes.VaultId + // Whether total shares exists. + totalSharesExists bool + // Owners. + owners []string + }{ + "Total shares doesn't exist, no owners": { + vaultId: constants.Vault_Clob_0, + }, + "Total shares exists, no owners": { + vaultId: constants.Vault_Clob_0, + totalSharesExists: true, + }, + "Total shares exists, one owner": { + vaultId: constants.Vault_Clob_1, + totalSharesExists: true, + owners: []string{constants.Alice_Num0.Owner}, + }, + "Total shares exists, two owners": { + vaultId: constants.Vault_Clob_1, + totalSharesExists: true, + owners: []string{ + constants.Alice_Num0.Owner, + constants.Bob_Num0.Owner, + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).Build() + ctx := tApp.InitChain() + k := tApp.App.VaultKeeper + + shares := vaulttypes.BigIntToNumShares( + big.NewInt(7), + ) + + if tc.totalSharesExists { + err := k.SetTotalShares( + ctx, + tc.vaultId, + shares, + ) + require.NoError(t, err) + } + for _, owner := range tc.owners { + err := k.SetOwnerShares( + ctx, + tc.vaultId, + owner, + shares, + ) + require.NoError(t, err) + } + + // Decommission vault. + k.DecommissionVault(ctx, tc.vaultId) + + // Check that total shares and owner shares are deleted. + _, exists := k.GetTotalShares(ctx, tc.vaultId) + require.Equal(t, false, exists) + for _, owner := range tc.owners { + _, exists = k.GetOwnerShares(ctx, tc.vaultId, owner) + require.Equal(t, false, exists) + } + }) + } +} diff --git a/protocol/x/vault/module.go b/protocol/x/vault/module.go index c77ad88043..1d167f8e12 100644 --- a/protocol/x/vault/module.go +++ b/protocol/x/vault/module.go @@ -28,6 +28,7 @@ var ( _ module.HasGenesisBasics = AppModuleBasic{} _ appmodule.AppModule = AppModule{} + _ appmodule.HasBeginBlocker = AppModule{} _ appmodule.HasEndBlocker = AppModule{} _ module.HasConsensusVersion = AppModule{} _ module.HasGenesis = AppModule{} @@ -147,6 +148,16 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw // be set to 1. func (AppModule) ConsensusVersion() uint64 { return 1 } +// BeginBlock executes all ABCI BeginBlock logic respective to the vault module. +func (am AppModule) BeginBlock(ctx context.Context) error { + defer telemetry.ModuleMeasureSince(am.Name(), time.Now(), telemetry.MetricKeyBeginBlocker) + BeginBlocker( + lib.UnwrapSDKContext(ctx, types.ModuleName), + &am.keeper, + ) + return nil +} + // EndBlock executes all ABCI EndBlock logic respective to the vault module. func (am AppModule) EndBlock(ctx context.Context) error { defer telemetry.ModuleMeasureSince(am.Name(), time.Now(), telemetry.MetricKeyEndBlocker) diff --git a/protocol/x/vault/types/errors.go b/protocol/x/vault/types/errors.go index fdd1c6fd4b..e38d0c94d1 100644 --- a/protocol/x/vault/types/errors.go +++ b/protocol/x/vault/types/errors.go @@ -20,4 +20,44 @@ var ( 3, "MarketParam not found", ) + ErrInvalidDepositAmount = errorsmod.Register( + ModuleName, + 4, + "Deposit amount is invalid", + ) + ErrNonPositiveEquity = errorsmod.Register( + ModuleName, + 5, + "Equity is non-positive", + ) + ErrZeroDenominator = errorsmod.Register( + ModuleName, + 6, + "Denominator is zero", + ) + ErrNilFraction = errorsmod.Register( + ModuleName, + 7, + "Fraction is nil", + ) + ErrInvalidOrderSizePctPpm = errorsmod.Register( + ModuleName, + 8, + "OrderSizePctPpm must be strictly greater than 0", + ) + ErrInvalidOrderExpirationSeconds = errorsmod.Register( + ModuleName, + 9, + "OrderExpirationSeconds must be strictly greater than 0", + ) + ErrInvalidSpreadMinPpm = errorsmod.Register( + ModuleName, + 10, + "SpreadMinPpm must be strictly greater than 0", + ) + ErrInvalidLayers = errorsmod.Register( + ModuleName, + 11, + "Layers must be less than or equal to MaxUint8", + ) ) diff --git a/protocol/x/vault/types/expected_keepers.go b/protocol/x/vault/types/expected_keepers.go index af96e94a2c..9ecaf2dc17 100644 --- a/protocol/x/vault/types/expected_keepers.go +++ b/protocol/x/vault/types/expected_keepers.go @@ -1,10 +1,13 @@ package types import ( + "math/big" + sdk "github.com/cosmos/cosmos-sdk/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" ) type ClobKeeper interface { @@ -45,4 +48,24 @@ type PricesKeeper interface { } type SubaccountsKeeper interface { + GetNetCollateralAndMarginRequirements( + ctx sdk.Context, + update satypes.Update, + ) ( + bigNetCollateral *big.Int, + bigInitialMargin *big.Int, + bigMaintenanceMargin *big.Int, + err error, + ) + GetSubaccount( + ctx sdk.Context, + id satypes.SubaccountId, + ) satypes.Subaccount + TransferFundsFromSubaccountToSubaccount( + ctx sdk.Context, + senderSubaccountId satypes.SubaccountId, + recipientSubaccountId satypes.SubaccountId, + assetId uint32, + quantums *big.Int, + ) (err error) } diff --git a/protocol/x/vault/types/genesis.go b/protocol/x/vault/types/genesis.go index 09583a5f2e..925708c8fa 100644 --- a/protocol/x/vault/types/genesis.go +++ b/protocol/x/vault/types/genesis.go @@ -2,11 +2,13 @@ package types // DefaultGenesis returns the default stats genesis state. func DefaultGenesis() *GenesisState { - return &GenesisState{} + return &GenesisState{ + Params: DefaultParams(), + } } // Validate performs basic genesis state validation returning an error upon any // failure. func (gs GenesisState) Validate() error { - return nil + return gs.Params.Validate() } diff --git a/protocol/x/vault/types/genesis.pb.go b/protocol/x/vault/types/genesis.pb.go index eec0857795..748e1e27e6 100644 --- a/protocol/x/vault/types/genesis.pb.go +++ b/protocol/x/vault/types/genesis.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -24,6 +25,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines `x/vault`'s genesis state. type GenesisState struct { + // The parameters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -59,6 +62,13 @@ func (m *GenesisState) XXX_DiscardUnknown() { var xxx_messageInfo_GenesisState proto.InternalMessageInfo +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + func init() { proto.RegisterType((*GenesisState)(nil), "dydxprotocol.vault.GenesisState") } @@ -66,16 +76,20 @@ func init() { func init() { proto.RegisterFile("dydxprotocol/vault/genesis.proto", fileDescriptor_4be4a747b209e41c) } var fileDescriptor_4be4a747b209e41c = []byte{ - // 140 bytes of a gzipped FileDescriptorProto + // 195 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0xa9, 0x4c, 0xa9, 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x2f, 0x4b, 0x2c, 0xcd, 0x29, 0xd1, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x03, 0x0b, 0x0b, 0x09, 0x21, 0xab, 0xd0, 0x03, 0xab, - 0x50, 0xe2, 0xe3, 0xe2, 0x71, 0x87, 0x28, 0x0a, 0x2e, 0x49, 0x2c, 0x49, 0x75, 0x0a, 0x3c, 0xf1, - 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, - 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xf3, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, - 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x54, 0xab, 0x4c, 0x74, 0x93, 0x33, 0x12, 0x33, 0xf3, 0xf4, 0xe1, - 0x22, 0x15, 0x50, 0xeb, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0xe2, 0xc6, 0x80, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x44, 0xfe, 0x11, 0x7d, 0xa1, 0x00, 0x00, 0x00, + 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x8b, 0xe9, 0x83, 0x58, 0x10, 0x95, 0x52, 0xf2, 0x58, + 0xcc, 0x2a, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0x1a, 0xa5, 0xe4, 0xc1, 0xc5, 0xe3, 0x0e, 0x31, 0x3b, + 0xb8, 0x24, 0xb1, 0x24, 0x55, 0xc8, 0x82, 0x8b, 0x0d, 0x22, 0x2f, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, + 0x6d, 0x24, 0xa5, 0x87, 0x69, 0x97, 0x5e, 0x00, 0x58, 0x85, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, + 0x41, 0x50, 0xf5, 0x4e, 0x81, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, + 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, + 0x9e, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x8f, 0xea, 0x1e, 0x13, 0xdd, + 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, 0x7d, 0xb8, 0x48, 0x05, 0xd4, 0x8d, 0x25, 0x95, 0x05, 0xa9, 0xc5, + 0x49, 0x6c, 0x60, 0x71, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x10, 0x33, 0x07, 0xda, 0x12, + 0x01, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -98,6 +112,16 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -118,6 +142,8 @@ func (m *GenesisState) Size() (n int) { } var l int _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) return n } @@ -156,6 +182,39 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/protocol/x/vault/types/keys.go b/protocol/x/vault/types/keys.go index 3ff5c4c37e..1130a77fa0 100644 --- a/protocol/x/vault/types/keys.go +++ b/protocol/x/vault/types/keys.go @@ -13,4 +13,11 @@ const ( const ( // TotalSharesKeyPrefix is the prefix to retrieve all TotalShares. TotalSharesKeyPrefix = "TotalShares:" + + // OwnerSharesKeyPrefix is the prefix to retrieve all OwnerShares. + // OwnerShares store: vaultId VaultId -> owner string -> shares NumShares. + OwnerSharesKeyPrefix = "OwnerShares:" + + // ParamsKey is the key to retrieve Params. + ParamsKey = "Params" ) diff --git a/protocol/x/vault/types/msg_deposit_to_vault.go b/protocol/x/vault/types/msg_deposit_to_vault.go index eefaed1c28..eb3d091402 100644 --- a/protocol/x/vault/types/msg_deposit_to_vault.go +++ b/protocol/x/vault/types/msg_deposit_to_vault.go @@ -1,5 +1,23 @@ package types +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" +) + +var _ sdk.Msg = &MsgDepositToVault{} + +// ValidateBasic performs stateless validation on a MsgDepositToVault. func (msg *MsgDepositToVault) ValidateBasic() error { + // Validate subaccount to deposit from. + if err := msg.SubaccountId.Validate(); err != nil { + return err + } + + // Validate that quote quantums is positive. + if msg.QuoteQuantums.Cmp(dtypes.NewInt(0)) <= 0 { + return ErrInvalidDepositAmount + } + return nil } diff --git a/protocol/x/vault/types/msg_deposit_to_vault_test.go b/protocol/x/vault/types/msg_deposit_to_vault_test.go new file mode 100644 index 0000000000..ab9a25c648 --- /dev/null +++ b/protocol/x/vault/types/msg_deposit_to_vault_test.go @@ -0,0 +1,64 @@ +package types_test + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" +) + +func TestMsgDepositToVault_ValidateBasic(t *testing.T) { + tests := map[string]struct { + msg types.MsgDepositToVault + expectedErr string + }{ + "Success": { + msg: types.MsgDepositToVault{ + VaultId: &constants.Vault_Clob_0, + SubaccountId: &constants.Alice_Num0, + QuoteQuantums: dtypes.NewInt(1), + }, + }, + "Failure: zero quote quantums": { + msg: types.MsgDepositToVault{ + VaultId: &constants.Vault_Clob_0, + SubaccountId: &constants.Alice_Num0, + QuoteQuantums: dtypes.NewInt(0), + }, + expectedErr: "Deposit amount is invalid", + }, + "Failure: negative quote quantums": { + msg: types.MsgDepositToVault{ + VaultId: &constants.Vault_Clob_0, + SubaccountId: &constants.Alice_Num0, + QuoteQuantums: dtypes.NewInt(-1), + }, + expectedErr: "Deposit amount is invalid", + }, + "Failure: invalid subaccount owner": { + msg: types.MsgDepositToVault{ + VaultId: &constants.Vault_Clob_0, + SubaccountId: &satypes.SubaccountId{ + Owner: "invalid-owner", + Number: 0, + }, + QuoteQuantums: dtypes.NewInt(1), + }, + expectedErr: "subaccount id owner is an invalid address", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expectedErr == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, tc.expectedErr) + } + }) + } +} diff --git a/protocol/x/vault/types/num_shares.go b/protocol/x/vault/types/num_shares.go new file mode 100644 index 0000000000..040f1b749b --- /dev/null +++ b/protocol/x/vault/types/num_shares.go @@ -0,0 +1,17 @@ +package types + +import ( + "math/big" + + "github.com/dydxprotocol/v4-chain/protocol/dtypes" +) + +// BigIntToNumShares returns a NumShares given a big.Int. +func BigIntToNumShares(num *big.Int) (n NumShares) { + if num == nil { + return n + } + return NumShares{ + NumShares: dtypes.NewIntFromBigInt(num), + } +} diff --git a/protocol/x/vault/types/num_shares_test.go b/protocol/x/vault/types/num_shares_test.go new file mode 100644 index 0000000000..ec1713dbbb --- /dev/null +++ b/protocol/x/vault/types/num_shares_test.go @@ -0,0 +1,35 @@ +package types_test + +import ( + "math/big" + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" +) + +func TestBigIntToNumShares(t *testing.T) { + tests := map[string]struct { + num *big.Int + expectedNumShares types.NumShares + }{ + "Success - 1": { + num: big.NewInt(1), + expectedNumShares: types.NumShares{ + NumShares: dtypes.NewInt(1), + }, + }, + "Success - num is nil": { + num: nil, + expectedNumShares: types.NumShares{}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + n := types.BigIntToNumShares(tc.num) + require.Equal(t, tc.expectedNumShares, n) + }) + } +} diff --git a/protocol/x/vault/types/params.go b/protocol/x/vault/types/params.go new file mode 100644 index 0000000000..ad7994a074 --- /dev/null +++ b/protocol/x/vault/types/params.go @@ -0,0 +1,37 @@ +package types + +import "math" + +// DefaultParams returns a default set of `x/vault` parameters. +func DefaultParams() Params { + return Params{ + Layers: 2, // 2 layers + SpreadMinPpm: 3_000, // 30 bps + SpreadBufferPpm: 1_500, // 15 bps + SkewFactorPpm: 500_000, // 0.5 + OrderSizePctPpm: 100_000, // 10% + OrderExpirationSeconds: 2, // 2 seconds + } +} + +// Validate validates `x/vault` parameters. +func (p Params) Validate() error { + // Layers must be less than or equal to MaxUint8. + if p.Layers > math.MaxUint8 { + return ErrInvalidLayers + } + // Spread min ppm must be positive. + if p.SpreadMinPpm == 0 { + return ErrInvalidSpreadMinPpm + } + // Order size must be positive. + if p.OrderSizePctPpm == 0 { + return ErrInvalidOrderSizePctPpm + } + // Order expiration seconds must be positive. + if p.OrderExpirationSeconds == 0 { + return ErrInvalidOrderExpirationSeconds + } + + return nil +} diff --git a/protocol/x/vault/types/params.pb.go b/protocol/x/vault/types/params.pb.go new file mode 100644 index 0000000000..8f7984ff35 --- /dev/null +++ b/protocol/x/vault/types/params.pb.go @@ -0,0 +1,492 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: dydxprotocol/vault/params.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params stores `x/vault` parameters. +type Params struct { + // The number of layers of orders a vault places. For example if + // `layers=2`, a vault places 2 asks and 2 bids. + Layers uint32 `protobuf:"varint,1,opt,name=layers,proto3" json:"layers,omitempty"` + // The minimum base spread when a vault quotes around reservation price. + SpreadMinPpm uint32 `protobuf:"varint,2,opt,name=spread_min_ppm,json=spreadMinPpm,proto3" json:"spread_min_ppm,omitempty"` + // The buffer amount to add to min_price_change_ppm to arrive at `spread` + // according to formula: + // `spread = max(spread_min_ppm, min_price_change_ppm + spread_buffer_ppm)`. + SpreadBufferPpm uint32 `protobuf:"varint,3,opt,name=spread_buffer_ppm,json=spreadBufferPpm,proto3" json:"spread_buffer_ppm,omitempty"` + // The factor that determines how aggressive a vault skews its orders. + SkewFactorPpm uint32 `protobuf:"varint,4,opt,name=skew_factor_ppm,json=skewFactorPpm,proto3" json:"skew_factor_ppm,omitempty"` + // The percentage of vault equity that each order is sized at. + OrderSizePctPpm uint32 `protobuf:"varint,5,opt,name=order_size_pct_ppm,json=orderSizePctPpm,proto3" json:"order_size_pct_ppm,omitempty"` + // The duration that a vault's orders are valid for. + OrderExpirationSeconds uint32 `protobuf:"varint,6,opt,name=order_expiration_seconds,json=orderExpirationSeconds,proto3" json:"order_expiration_seconds,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_6043e0b8bfdbca9f, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.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 *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetLayers() uint32 { + if m != nil { + return m.Layers + } + return 0 +} + +func (m *Params) GetSpreadMinPpm() uint32 { + if m != nil { + return m.SpreadMinPpm + } + return 0 +} + +func (m *Params) GetSpreadBufferPpm() uint32 { + if m != nil { + return m.SpreadBufferPpm + } + return 0 +} + +func (m *Params) GetSkewFactorPpm() uint32 { + if m != nil { + return m.SkewFactorPpm + } + return 0 +} + +func (m *Params) GetOrderSizePctPpm() uint32 { + if m != nil { + return m.OrderSizePctPpm + } + return 0 +} + +func (m *Params) GetOrderExpirationSeconds() uint32 { + if m != nil { + return m.OrderExpirationSeconds + } + return 0 +} + +func init() { + proto.RegisterType((*Params)(nil), "dydxprotocol.vault.Params") +} + +func init() { proto.RegisterFile("dydxprotocol/vault/params.proto", fileDescriptor_6043e0b8bfdbca9f) } + +var fileDescriptor_6043e0b8bfdbca9f = []byte{ + // 293 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0xd0, 0xb1, 0x4e, 0xeb, 0x30, + 0x14, 0xc6, 0xf1, 0xa6, 0xf7, 0x92, 0xc1, 0xa2, 0x54, 0x78, 0xa8, 0x32, 0x19, 0x84, 0x10, 0x42, + 0x20, 0x9a, 0x01, 0x24, 0x98, 0x2b, 0xc1, 0x86, 0x54, 0xe8, 0xc6, 0x62, 0xb9, 0x8e, 0x43, 0x2d, + 0x92, 0xd8, 0xb2, 0x1d, 0x48, 0xba, 0xf1, 0x06, 0x3c, 0x16, 0x63, 0x47, 0x46, 0x94, 0xbc, 0x08, + 0xca, 0x71, 0x54, 0xc1, 0x98, 0xff, 0xf7, 0x8b, 0x87, 0x83, 0x0e, 0x92, 0x3a, 0xa9, 0xb4, 0x51, + 0x4e, 0x71, 0x95, 0xc5, 0xaf, 0xac, 0xcc, 0x5c, 0xac, 0x99, 0x61, 0xb9, 0x9d, 0x42, 0xc5, 0xf8, + 0x37, 0x98, 0x02, 0x38, 0x7a, 0x1f, 0xa2, 0x70, 0x0e, 0x08, 0x4f, 0x50, 0x98, 0xb1, 0x5a, 0x18, + 0x1b, 0x05, 0x87, 0xc1, 0xe9, 0xe8, 0xb1, 0xff, 0xc2, 0xc7, 0x68, 0xcf, 0x6a, 0x23, 0x58, 0x42, + 0x73, 0x59, 0x50, 0xad, 0xf3, 0x68, 0x08, 0xfb, 0xae, 0xaf, 0xf7, 0xb2, 0x98, 0xeb, 0x1c, 0x9f, + 0xa1, 0xfd, 0x5e, 0x2d, 0xcb, 0x34, 0x15, 0x06, 0xe0, 0x3f, 0x80, 0x63, 0x3f, 0xcc, 0xa0, 0x77, + 0xf6, 0x04, 0x8d, 0xed, 0x8b, 0x78, 0xa3, 0x29, 0xe3, 0x4e, 0x79, 0xf9, 0x1f, 0xe4, 0xa8, 0xcb, + 0x77, 0x50, 0x3b, 0x77, 0x8e, 0xb0, 0x32, 0x89, 0x30, 0xd4, 0xca, 0xb5, 0xa0, 0x9a, 0x3b, 0xa0, + 0x3b, 0xfe, 0x51, 0x58, 0x16, 0x72, 0x2d, 0xe6, 0xdc, 0x75, 0xf8, 0x06, 0x45, 0x1e, 0x8b, 0x4a, + 0x4b, 0xc3, 0x9c, 0x54, 0x05, 0xb5, 0x82, 0xab, 0x22, 0xb1, 0x51, 0x08, 0xbf, 0x4c, 0x60, 0xbf, + 0xdd, 0xce, 0x0b, 0xbf, 0xce, 0x1e, 0x3e, 0x1b, 0x12, 0x6c, 0x1a, 0x12, 0x7c, 0x37, 0x24, 0xf8, + 0x68, 0xc9, 0x60, 0xd3, 0x92, 0xc1, 0x57, 0x4b, 0x06, 0x4f, 0xd7, 0xcf, 0xd2, 0xad, 0xca, 0xe5, + 0x94, 0xab, 0x3c, 0xfe, 0x7b, 0xdd, 0xab, 0x0b, 0xbe, 0x62, 0xb2, 0x88, 0xb7, 0xa5, 0xea, 0x2f, + 0xee, 0x6a, 0x2d, 0xec, 0x32, 0x84, 0x7e, 0xf9, 0x13, 0x00, 0x00, 0xff, 0xff, 0xf6, 0x9e, 0x6e, + 0xd5, 0x94, 0x01, 0x00, 0x00, +} + +func (m *Params) 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 *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.OrderExpirationSeconds != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.OrderExpirationSeconds)) + i-- + dAtA[i] = 0x30 + } + if m.OrderSizePctPpm != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.OrderSizePctPpm)) + i-- + dAtA[i] = 0x28 + } + if m.SkewFactorPpm != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.SkewFactorPpm)) + i-- + dAtA[i] = 0x20 + } + if m.SpreadBufferPpm != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.SpreadBufferPpm)) + i-- + dAtA[i] = 0x18 + } + if m.SpreadMinPpm != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.SpreadMinPpm)) + i-- + dAtA[i] = 0x10 + } + if m.Layers != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.Layers)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Layers != 0 { + n += 1 + sovParams(uint64(m.Layers)) + } + if m.SpreadMinPpm != 0 { + n += 1 + sovParams(uint64(m.SpreadMinPpm)) + } + if m.SpreadBufferPpm != 0 { + n += 1 + sovParams(uint64(m.SpreadBufferPpm)) + } + if m.SkewFactorPpm != 0 { + n += 1 + sovParams(uint64(m.SkewFactorPpm)) + } + if m.OrderSizePctPpm != 0 { + n += 1 + sovParams(uint64(m.OrderSizePctPpm)) + } + if m.OrderExpirationSeconds != 0 { + n += 1 + sovParams(uint64(m.OrderExpirationSeconds)) + } + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) 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 ErrIntOverflowParams + } + 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: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Layers", wireType) + } + m.Layers = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Layers |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SpreadMinPpm", wireType) + } + m.SpreadMinPpm = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SpreadMinPpm |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SpreadBufferPpm", wireType) + } + m.SpreadBufferPpm = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SpreadBufferPpm |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field SkewFactorPpm", wireType) + } + m.SkewFactorPpm = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.SkewFactorPpm |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OrderSizePctPpm", wireType) + } + m.OrderSizePctPpm = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OrderSizePctPpm |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OrderExpirationSeconds", wireType) + } + m.OrderExpirationSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OrderExpirationSeconds |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/vault/types/params_test.go b/protocol/x/vault/types/params_test.go new file mode 100644 index 0000000000..ba3131b37f --- /dev/null +++ b/protocol/x/vault/types/params_test.go @@ -0,0 +1,73 @@ +package types_test + +import ( + "testing" + + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + "github.com/stretchr/testify/require" +) + +func TestValidate(t *testing.T) { + tests := map[string]struct { + // Params to validate. + params types.Params + // Expected error + expectedErr error + }{ + "Success": { + params: types.DefaultParams(), + expectedErr: nil, + }, + "Failure - Layer is greater than MaxUint8": { + params: types.Params{ + Layers: 256, + SpreadMinPpm: 3_000, + SpreadBufferPpm: 1_500, + SkewFactorPpm: 500_000, + OrderSizePctPpm: 100_000, + OrderExpirationSeconds: 5, + }, + expectedErr: types.ErrInvalidLayers, + }, + "Failure - SpreadMinPpm is 0": { + params: types.Params{ + Layers: 2, + SpreadMinPpm: 0, + SpreadBufferPpm: 1_500, + SkewFactorPpm: 500_000, + OrderSizePctPpm: 100_000, + OrderExpirationSeconds: 5, + }, + expectedErr: types.ErrInvalidSpreadMinPpm, + }, + "Failure - OrderSizePctPpm is 0": { + params: types.Params{ + Layers: 2, + SpreadMinPpm: 3_000, + SpreadBufferPpm: 1_500, + SkewFactorPpm: 500_000, + OrderSizePctPpm: 0, + OrderExpirationSeconds: 5, + }, + expectedErr: types.ErrInvalidOrderSizePctPpm, + }, + "Failure - OrderExpirationSeconds is 0": { + params: types.Params{ + Layers: 2, + SpreadMinPpm: 3_000, + SpreadBufferPpm: 1_500, + SkewFactorPpm: 500_000, + OrderSizePctPpm: 100_000, + OrderExpirationSeconds: 0, + }, + expectedErr: types.ErrInvalidOrderExpirationSeconds, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := tc.params.Validate() + require.Equal(t, tc.expectedErr, err) + }) + } +} diff --git a/protocol/x/vault/types/query.pb.go b/protocol/x/vault/types/query.pb.go index c29c39cd30..ecc6d74eb1 100644 --- a/protocol/x/vault/types/query.pb.go +++ b/protocol/x/vault/types/query.pb.go @@ -6,10 +6,17 @@ package types import ( context "context" fmt "fmt" + _ "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" + status "google.golang.org/grpc/status" + io "io" math "math" + math_bits "math/bits" ) // Reference imports to suppress errors if they are not otherwise used. @@ -23,19 +30,261 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// QueryParamsRequest is a request type for the Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_478fb8dc0ff21ea6, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.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 *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is a response type for the Params RPC method. +type QueryParamsResponse struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_478fb8dc0ff21ea6, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.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 *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// QueryVaultRequest is a request type for the Vault RPC method. +type QueryVaultRequest struct { + Type VaultType `protobuf:"varint,1,opt,name=type,proto3,enum=dydxprotocol.vault.VaultType" json:"type,omitempty"` + Number uint32 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"` +} + +func (m *QueryVaultRequest) Reset() { *m = QueryVaultRequest{} } +func (m *QueryVaultRequest) String() string { return proto.CompactTextString(m) } +func (*QueryVaultRequest) ProtoMessage() {} +func (*QueryVaultRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_478fb8dc0ff21ea6, []int{2} +} +func (m *QueryVaultRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryVaultRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryVaultRequest.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 *QueryVaultRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryVaultRequest.Merge(m, src) +} +func (m *QueryVaultRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryVaultRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryVaultRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryVaultRequest proto.InternalMessageInfo + +func (m *QueryVaultRequest) GetType() VaultType { + if m != nil { + return m.Type + } + return VaultType_VAULT_TYPE_UNSPECIFIED +} + +func (m *QueryVaultRequest) GetNumber() uint32 { + if m != nil { + return m.Number + } + return 0 +} + +// QueryVaultResponse is a response type for the Vault RPC method. +type QueryVaultResponse struct { + VaultId VaultId `protobuf:"bytes,1,opt,name=vault_id,json=vaultId,proto3" json:"vault_id"` + SubaccountId types.SubaccountId `protobuf:"bytes,2,opt,name=subaccount_id,json=subaccountId,proto3" json:"subaccount_id"` + Equity uint64 `protobuf:"varint,3,opt,name=equity,proto3" json:"equity,omitempty"` + Inventory uint64 `protobuf:"varint,4,opt,name=inventory,proto3" json:"inventory,omitempty"` + TotalShares uint64 `protobuf:"varint,5,opt,name=total_shares,json=totalShares,proto3" json:"total_shares,omitempty"` +} + +func (m *QueryVaultResponse) Reset() { *m = QueryVaultResponse{} } +func (m *QueryVaultResponse) String() string { return proto.CompactTextString(m) } +func (*QueryVaultResponse) ProtoMessage() {} +func (*QueryVaultResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_478fb8dc0ff21ea6, []int{3} +} +func (m *QueryVaultResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryVaultResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryVaultResponse.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 *QueryVaultResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryVaultResponse.Merge(m, src) +} +func (m *QueryVaultResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryVaultResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryVaultResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryVaultResponse proto.InternalMessageInfo + +func (m *QueryVaultResponse) GetVaultId() VaultId { + if m != nil { + return m.VaultId + } + return VaultId{} +} + +func (m *QueryVaultResponse) GetSubaccountId() types.SubaccountId { + if m != nil { + return m.SubaccountId + } + return types.SubaccountId{} +} + +func (m *QueryVaultResponse) GetEquity() uint64 { + if m != nil { + return m.Equity + } + return 0 +} + +func (m *QueryVaultResponse) GetInventory() uint64 { + if m != nil { + return m.Inventory + } + return 0 +} + +func (m *QueryVaultResponse) GetTotalShares() uint64 { + if m != nil { + return m.TotalShares + } + return 0 +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "dydxprotocol.vault.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "dydxprotocol.vault.QueryParamsResponse") + proto.RegisterType((*QueryVaultRequest)(nil), "dydxprotocol.vault.QueryVaultRequest") + proto.RegisterType((*QueryVaultResponse)(nil), "dydxprotocol.vault.QueryVaultResponse") +} + func init() { proto.RegisterFile("dydxprotocol/vault/query.proto", fileDescriptor_478fb8dc0ff21ea6) } var fileDescriptor_478fb8dc0ff21ea6 = []byte{ - // 132 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4b, 0xa9, 0x4c, 0xa9, - 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x2f, 0x4b, 0x2c, 0xcd, 0x29, 0xd1, 0x2f, - 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x03, 0x0b, 0x0a, 0x09, 0x21, 0xcb, 0xeb, 0x81, 0xe5, 0x8d, 0xd8, - 0xb9, 0x58, 0x03, 0x41, 0x4a, 0x9c, 0x02, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, - 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, - 0x21, 0xca, 0x3c, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xd5, 0x06, - 0x13, 0xdd, 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, 0x7d, 0xb8, 0x48, 0x05, 0xd4, 0xd6, 0x92, 0xca, 0x82, - 0xd4, 0xe2, 0x24, 0x36, 0xb0, 0xb8, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xf0, 0xd6, 0x1e, 0xab, - 0x98, 0x00, 0x00, 0x00, + // 502 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x52, 0xcf, 0x6b, 0x13, 0x41, + 0x18, 0xcd, 0xc4, 0x24, 0xea, 0xb4, 0x15, 0x1c, 0x8b, 0x84, 0xb5, 0xdd, 0xc4, 0x85, 0xc6, 0x78, + 0xe8, 0x0e, 0x46, 0x45, 0x0f, 0x9e, 0x7a, 0xf3, 0xa4, 0xd9, 0x8a, 0x07, 0x0f, 0x96, 0xc9, 0x66, + 0xd8, 0x2c, 0x24, 0x33, 0x9b, 0x9d, 0xd9, 0xd0, 0xa5, 0x14, 0xc4, 0xbb, 0x20, 0x78, 0xf3, 0x2f, + 0xea, 0xb1, 0xe0, 0xc5, 0x93, 0x48, 0xe2, 0xdf, 0x21, 0xb2, 0xdf, 0x8e, 0xe9, 0x6e, 0x9b, 0xd0, + 0xcb, 0x30, 0xf3, 0xbe, 0x37, 0xef, 0x7d, 0xbf, 0xb0, 0x3d, 0x4c, 0x87, 0xc7, 0x51, 0x2c, 0xb5, + 0xf4, 0xe5, 0x98, 0xce, 0x58, 0x32, 0xd6, 0x74, 0x9a, 0xf0, 0x38, 0x75, 0x01, 0x24, 0xa4, 0x18, + 0x77, 0x21, 0x6e, 0x6d, 0x07, 0x32, 0x90, 0x80, 0xd1, 0xec, 0x96, 0x33, 0xad, 0x9d, 0x40, 0xca, + 0x60, 0xcc, 0x29, 0x8b, 0x42, 0xca, 0x84, 0x90, 0x9a, 0xe9, 0x50, 0x0a, 0x65, 0xa2, 0x8f, 0x4b, + 0x3e, 0x2a, 0x19, 0x30, 0xdf, 0x97, 0x89, 0xd0, 0xaa, 0x70, 0x37, 0xd4, 0xd6, 0x8a, 0x94, 0x22, + 0x16, 0xb3, 0xc9, 0x7f, 0xad, 0x55, 0x39, 0xc3, 0x99, 0xc7, 0x9d, 0x6d, 0x4c, 0xfa, 0x59, 0x09, + 0x6f, 0xe1, 0x93, 0xc7, 0xa7, 0x09, 0x57, 0xda, 0x79, 0x83, 0xef, 0x95, 0x50, 0x15, 0x49, 0xa1, + 0x38, 0x79, 0x89, 0x1b, 0xb9, 0x78, 0x13, 0xb5, 0x51, 0x77, 0xa3, 0x67, 0xb9, 0x57, 0x2b, 0x76, + 0xf3, 0x3f, 0x07, 0xb5, 0xb3, 0x5f, 0xad, 0x8a, 0x67, 0xf8, 0xce, 0x47, 0x7c, 0x17, 0x04, 0xdf, + 0x67, 0x14, 0xe3, 0x42, 0x9e, 0xe0, 0x9a, 0x4e, 0x23, 0x0e, 0x62, 0x77, 0x7a, 0xbb, 0xab, 0xc4, + 0x80, 0xff, 0x2e, 0x8d, 0xb8, 0x07, 0x54, 0x72, 0x1f, 0x37, 0x44, 0x32, 0x19, 0xf0, 0xb8, 0x59, + 0x6d, 0xa3, 0xee, 0x96, 0x67, 0x5e, 0xce, 0x5f, 0x64, 0xea, 0x30, 0x06, 0x26, 0xe1, 0x57, 0xf8, + 0x16, 0xe8, 0x1c, 0x85, 0x43, 0x93, 0xf2, 0x83, 0xb5, 0x2e, 0xaf, 0x87, 0x26, 0xe7, 0x9b, 0xb3, + 0xfc, 0x49, 0xfa, 0x78, 0xeb, 0xa2, 0xe1, 0x99, 0x44, 0x15, 0x24, 0x3a, 0x65, 0x89, 0xc2, 0x7c, + 0xdc, 0xc3, 0xe5, 0x7d, 0xa9, 0xb6, 0xa9, 0x0a, 0x58, 0x96, 0x3f, 0x9f, 0x26, 0xa1, 0x4e, 0x9b, + 0x37, 0xda, 0xa8, 0x5b, 0xf3, 0xcc, 0x8b, 0xec, 0xe0, 0xdb, 0xa1, 0x98, 0x71, 0xa1, 0x65, 0x9c, + 0x36, 0x6b, 0x10, 0xba, 0x00, 0xc8, 0x43, 0xbc, 0xa9, 0xa5, 0x66, 0xe3, 0x23, 0x35, 0x62, 0x31, + 0x57, 0xcd, 0x3a, 0x10, 0x36, 0x00, 0x3b, 0x04, 0xa8, 0xf7, 0xbd, 0x8a, 0xeb, 0xd0, 0x00, 0xf2, + 0x09, 0xe1, 0x46, 0x3e, 0x03, 0xd2, 0x59, 0x55, 0xec, 0xd5, 0x71, 0x5b, 0x8f, 0xae, 0xe5, 0xe5, + 0xfd, 0x74, 0xf6, 0x3e, 0xff, 0xf8, 0xf3, 0xad, 0xda, 0x22, 0xbb, 0xb4, 0xbc, 0x56, 0xcf, 0x4a, + 0xab, 0x47, 0xbe, 0x20, 0x5c, 0x87, 0x9e, 0x92, 0xbd, 0xb5, 0xca, 0xc5, 0x4d, 0xb0, 0x3a, 0xd7, + 0xd1, 0x8c, 0xff, 0x73, 0xf0, 0xa7, 0x64, 0x7f, 0x8d, 0x3f, 0x9c, 0x8a, 0x9e, 0x64, 0xbb, 0x72, + 0x4a, 0x4f, 0xf2, 0xe5, 0x38, 0x3d, 0xe8, 0x9f, 0xcd, 0x6d, 0x74, 0x3e, 0xb7, 0xd1, 0xef, 0xb9, + 0x8d, 0xbe, 0x2e, 0xec, 0xca, 0xf9, 0xc2, 0xae, 0xfc, 0x5c, 0xd8, 0x95, 0x0f, 0x2f, 0x82, 0x50, + 0x8f, 0x92, 0x81, 0xeb, 0xcb, 0xc9, 0x65, 0xc9, 0x7d, 0x7f, 0xc4, 0x42, 0x41, 0x97, 0xc8, 0xb1, + 0xf1, 0xc8, 0xb4, 0xd5, 0xa0, 0x01, 0xf8, 0xd3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xd2, 0xca, + 0xa6, 0x6c, 0x14, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -50,6 +299,10 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type QueryClient interface { + // Queries the Params. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // Queries a Vault by type and number. + Vault(ctx context.Context, in *QueryVaultRequest, opts ...grpc.CallOption) (*QueryVaultResponse, error) } type queryClient struct { @@ -60,22 +313,796 @@ func NewQueryClient(cc grpc1.ClientConn) QueryClient { return &queryClient{cc} } +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.vault.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Vault(ctx context.Context, in *QueryVaultRequest, opts ...grpc.CallOption) (*QueryVaultResponse, error) { + out := new(QueryVaultResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.vault.Query/Vault", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { + // Queries the Params. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // Queries a Vault by type and number. + Vault(context.Context, *QueryVaultRequest) (*QueryVaultResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. type UnimplementedQueryServer struct { } +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} +func (*UnimplementedQueryServer) Vault(ctx context.Context, req *QueryVaultRequest) (*QueryVaultResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Vault not implemented") +} + func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) } +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.vault.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Vault_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryVaultRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Vault(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.vault.Query/Vault", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Vault(ctx, req.(*QueryVaultRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "dydxprotocol.vault.Query", HandlerType: (*QueryServer)(nil), - Methods: []grpc.MethodDesc{}, - Streams: []grpc.StreamDesc{}, - Metadata: "dydxprotocol/vault/query.proto", + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + { + MethodName: "Vault", + Handler: _Query_Vault_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "dydxprotocol/vault/query.proto", +} + +func (m *QueryParamsRequest) 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 *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) 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 *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.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 *QueryVaultRequest) 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 *QueryVaultRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVaultRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Number != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Number)) + i-- + dAtA[i] = 0x10 + } + if m.Type != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil } + +func (m *QueryVaultResponse) 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 *QueryVaultResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryVaultResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TotalShares != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.TotalShares)) + i-- + dAtA[i] = 0x28 + } + if m.Inventory != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Inventory)) + i-- + dAtA[i] = 0x20 + } + if m.Equity != 0 { + i = encodeVarintQuery(dAtA, i, uint64(m.Equity)) + i-- + dAtA[i] = 0x18 + } + { + size, err := m.SubaccountId.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.VaultId.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 encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryVaultRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != 0 { + n += 1 + sovQuery(uint64(m.Type)) + } + if m.Number != 0 { + n += 1 + sovQuery(uint64(m.Number)) + } + return n +} + +func (m *QueryVaultResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.VaultId.Size() + n += 1 + l + sovQuery(uint64(l)) + l = m.SubaccountId.Size() + n += 1 + l + sovQuery(uint64(l)) + if m.Equity != 0 { + n += 1 + sovQuery(uint64(m.Equity)) + } + if m.Inventory != 0 { + n += 1 + sovQuery(uint64(m.Inventory)) + } + if m.TotalShares != 0 { + n += 1 + sovQuery(uint64(m.TotalShares)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) 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: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + 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 *QueryParamsResponse) 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: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", 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.Params.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 *QueryVaultRequest) 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: QueryVaultRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryVaultRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= VaultType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Number", wireType) + } + m.Number = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Number |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + 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 *QueryVaultResponse) 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: QueryVaultResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryVaultResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field VaultId", 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.VaultId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + 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 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Equity", wireType) + } + m.Equity = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Equity |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Inventory", wireType) + } + m.Inventory = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Inventory |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalShares", wireType) + } + m.TotalShares = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TotalShares |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + 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 skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/protocol/x/vault/types/query.pb.gw.go b/protocol/x/vault/types/query.pb.gw.go new file mode 100644 index 0000000000..7cf3f8de49 --- /dev/null +++ b/protocol/x/vault/types/query.pb.gw.go @@ -0,0 +1,282 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: dydxprotocol/vault/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Vault_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryVaultRequest + var metadata runtime.ServerMetadata + + var ( + val string + e int32 + ok bool + err error + _ = err + ) + + val, ok = pathParams["type"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "type") + } + + e, err = runtime.Enum(val, VaultType_value) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "type", err) + } + + protoReq.Type = VaultType(e) + + val, ok = pathParams["number"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "number") + } + + protoReq.Number, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "number", err) + } + + msg, err := client.Vault(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Vault_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryVaultRequest + var metadata runtime.ServerMetadata + + var ( + val string + e int32 + ok bool + err error + _ = err + ) + + val, ok = pathParams["type"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "type") + } + + e, err = runtime.Enum(val, VaultType_value) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "type", err) + } + + protoReq.Type = VaultType(e) + + val, ok = pathParams["number"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "number") + } + + protoReq.Number, err = runtime.Uint32(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "number", err) + } + + msg, err := server.Vault(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Vault_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Vault_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Vault_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Vault_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Vault_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Vault_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"dydxprotocol", "v4", "vault", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Vault_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"dydxprotocol", "v4", "vault", "vaults", "type", "number"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_Vault_0 = runtime.ForwardResponseMessage +) diff --git a/protocol/x/vault/types/tx.pb.go b/protocol/x/vault/types/tx.pb.go index 4289883ce0..8aa97ebfbc 100644 --- a/protocol/x/vault/types/tx.pb.go +++ b/protocol/x/vault/types/tx.pb.go @@ -6,6 +6,8 @@ package types import ( context "context" fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" @@ -124,37 +126,140 @@ func (m *MsgDepositToVaultResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgDepositToVaultResponse proto.InternalMessageInfo +// MsgUpdateParams is the Msg/UpdateParams request type. +type MsgUpdateParams struct { + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // The parameters to update. Each field must be set. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_ced574c6017ce006, []int{2} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.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 *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +func (m *MsgUpdateParams) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateParams) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// MsgUpdateParamsResponse is the Msg/UpdateParams response type. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ced574c6017ce006, []int{3} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.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 *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgDepositToVault)(nil), "dydxprotocol.vault.MsgDepositToVault") proto.RegisterType((*MsgDepositToVaultResponse)(nil), "dydxprotocol.vault.MsgDepositToVaultResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "dydxprotocol.vault.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "dydxprotocol.vault.MsgUpdateParamsResponse") } func init() { proto.RegisterFile("dydxprotocol/vault/tx.proto", fileDescriptor_ced574c6017ce006) } var fileDescriptor_ced574c6017ce006 = []byte{ - // 343 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4e, 0xa9, 0x4c, 0xa9, - 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0x2f, 0x4b, 0x2c, 0xcd, 0x29, 0xd1, 0x2f, - 0xa9, 0xd0, 0x03, 0x8b, 0x08, 0x09, 0x21, 0x4b, 0xea, 0x81, 0x25, 0xa5, 0x34, 0x51, 0x34, 0x14, - 0x97, 0x26, 0x25, 0x26, 0x27, 0xe7, 0x97, 0xe6, 0x95, 0x14, 0x23, 0xb1, 0x21, 0xda, 0xa5, 0xe4, - 0xb0, 0x98, 0x0d, 0x26, 0xa1, 0xf2, 0x22, 0xe9, 0xf9, 0xe9, 0xf9, 0x60, 0xa6, 0x3e, 0x88, 0x05, - 0x11, 0x55, 0xea, 0x64, 0xe2, 0x12, 0xf4, 0x2d, 0x4e, 0x77, 0x49, 0x2d, 0xc8, 0x2f, 0xce, 0x2c, - 0x09, 0xc9, 0x0f, 0x03, 0xe9, 0x10, 0x32, 0xe3, 0xe2, 0x00, 0x6b, 0x8d, 0xcf, 0x4c, 0x91, 0x60, - 0x54, 0x60, 0xd4, 0xe0, 0x36, 0x92, 0xd6, 0xc3, 0x74, 0x9d, 0x1e, 0x58, 0xb1, 0x67, 0x4a, 0x10, - 0x7b, 0x19, 0x84, 0x21, 0xe4, 0xcd, 0xc5, 0x8b, 0x70, 0x17, 0x48, 0x33, 0x13, 0x58, 0xb3, 0x1a, - 0xaa, 0x66, 0x24, 0x6f, 0xe8, 0x05, 0xc3, 0xd9, 0x9e, 0x29, 0x41, 0x3c, 0xc5, 0x48, 0x3c, 0xa1, - 0x7c, 0x2e, 0xbe, 0xc2, 0xd2, 0xfc, 0x92, 0xd4, 0xf8, 0xc2, 0xd2, 0xc4, 0xbc, 0x92, 0xd2, 0xdc, - 0x62, 0x09, 0x66, 0x05, 0x46, 0x0d, 0x1e, 0x27, 0x8f, 0x13, 0xf7, 0xe4, 0x19, 0x6e, 0xdd, 0x93, - 0x77, 0x48, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x47, 0xf5, 0xbb, 0x89, - 0x6e, 0x72, 0x46, 0x62, 0x66, 0x9e, 0x3e, 0x5c, 0x24, 0xa5, 0xa4, 0xb2, 0x20, 0xb5, 0x58, 0x2f, - 0x38, 0xb5, 0x28, 0x33, 0x31, 0x27, 0xb3, 0x2a, 0x31, 0x29, 0x27, 0xd5, 0x33, 0xaf, 0x24, 0x88, - 0x17, 0x6c, 0x7e, 0x20, 0xd4, 0x78, 0x25, 0x69, 0x2e, 0x49, 0x8c, 0xa0, 0x08, 0x4a, 0x2d, 0x2e, - 0xc8, 0xcf, 0x2b, 0x4e, 0x35, 0xca, 0xe5, 0x62, 0xf6, 0x2d, 0x4e, 0x17, 0x4a, 0xe3, 0xe2, 0x43, - 0x0b, 0x2b, 0x55, 0x6c, 0x21, 0x83, 0x61, 0x8e, 0x94, 0x2e, 0x51, 0xca, 0x60, 0xd6, 0x39, 0x05, - 0x9e, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, - 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x39, 0xf1, 0xde, 0xae, 0x80, - 0x25, 0x31, 0x90, 0xef, 0x93, 0xd8, 0xc0, 0xe2, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xa5, - 0x6a, 0xc3, 0x6b, 0x85, 0x02, 0x00, 0x00, + // 504 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0x8d, 0x5b, 0x54, 0xe8, 0x92, 0x06, 0xb1, 0xaa, 0xd4, 0xc4, 0x91, 0x9c, 0x2a, 0x08, 0x54, + 0x40, 0xb1, 0x45, 0x41, 0x05, 0xf5, 0x04, 0x11, 0x07, 0x22, 0x14, 0x89, 0x3a, 0xc0, 0x81, 0x4b, + 0xd8, 0x78, 0x17, 0xc7, 0x52, 0xec, 0x75, 0x3d, 0xeb, 0x28, 0xe1, 0xc8, 0x17, 0x20, 0x71, 0xe6, + 0x1f, 0x38, 0xf0, 0x11, 0xbd, 0x51, 0x71, 0x42, 0x1c, 0x2a, 0x94, 0x1c, 0xf8, 0x0d, 0xe4, 0xb5, + 0xdd, 0xd8, 0x89, 0x91, 0x72, 0x49, 0x66, 0x67, 0xde, 0xec, 0x7b, 0xf3, 0x76, 0x8c, 0xea, 0x74, + 0x4a, 0x27, 0x7e, 0xc0, 0x05, 0xb7, 0xf8, 0xc8, 0x18, 0x93, 0x70, 0x24, 0x0c, 0x31, 0xd1, 0x65, + 0x06, 0xe3, 0x6c, 0x51, 0x97, 0x45, 0xb5, 0x66, 0x71, 0x70, 0x39, 0xf4, 0x65, 0xda, 0x88, 0x0f, + 0x31, 0x5c, 0xdd, 0x8b, 0x4f, 0x86, 0x0b, 0xb6, 0x31, 0x7e, 0x10, 0xfd, 0x25, 0x85, 0xbb, 0x39, + 0x12, 0x08, 0x07, 0xc4, 0xb2, 0x78, 0xe8, 0x09, 0xc8, 0xc4, 0x09, 0xb4, 0x51, 0xa0, 0xc7, 0x27, + 0x01, 0x71, 0x53, 0x12, 0xad, 0x00, 0x20, 0x7f, 0x93, 0xfa, 0xae, 0xcd, 0x6d, 0x1e, 0x8b, 0x8b, + 0xa2, 0x38, 0xdb, 0xfc, 0xba, 0x81, 0x6e, 0x76, 0xc1, 0x7e, 0xce, 0x7c, 0x0e, 0x8e, 0x78, 0xcd, + 0xdf, 0x46, 0x1d, 0xf8, 0x08, 0x5d, 0x93, 0xad, 0x7d, 0x87, 0x56, 0x95, 0x7d, 0xe5, 0xe0, 0xfa, + 0x61, 0x5d, 0x5f, 0x1d, 0x59, 0x97, 0xe0, 0x0e, 0x35, 0xaf, 0x8e, 0xe3, 0x00, 0xbf, 0x44, 0x3b, + 0x0b, 0xe1, 0x51, 0xf3, 0x86, 0x6c, 0xbe, 0x93, 0x6f, 0xce, 0xcc, 0xa9, 0xf7, 0x2e, 0xe3, 0x0e, + 0x35, 0xcb, 0x90, 0x39, 0x61, 0x8e, 0x2a, 0xa7, 0x21, 0x17, 0xac, 0x7f, 0x1a, 0x12, 0x4f, 0x84, + 0x2e, 0x54, 0x37, 0xf7, 0x95, 0x83, 0x72, 0xfb, 0xc5, 0xd9, 0x45, 0xa3, 0xf4, 0xfb, 0xa2, 0xf1, + 0xd4, 0x76, 0xc4, 0x30, 0x1c, 0xe8, 0x16, 0x77, 0x8d, 0xfc, 0xec, 0x8f, 0x5a, 0xd6, 0x90, 0x38, + 0x9e, 0x71, 0x99, 0xa1, 0x62, 0xea, 0x33, 0xd0, 0x7b, 0x2c, 0x70, 0xc8, 0xc8, 0xf9, 0x48, 0x06, + 0x23, 0xd6, 0xf1, 0x84, 0xb9, 0x23, 0xef, 0x3f, 0x49, 0xae, 0x3f, 0xc6, 0x9f, 0xfe, 0x7e, 0xbb, + 0x97, 0x1f, 0xa0, 0x59, 0x47, 0xb5, 0x15, 0x7b, 0x4c, 0x06, 0x3e, 0xf7, 0x80, 0x35, 0xbf, 0x28, + 0xe8, 0x46, 0x17, 0xec, 0x37, 0x3e, 0x25, 0x82, 0xbd, 0x92, 0x8f, 0x81, 0x8f, 0xd0, 0x36, 0x09, + 0xc5, 0x90, 0x07, 0x8e, 0x98, 0x4a, 0xef, 0xb6, 0xdb, 0xd5, 0x9f, 0xdf, 0x5b, 0xbb, 0xc9, 0x42, + 0x3c, 0xa3, 0x34, 0x60, 0x00, 0x3d, 0x11, 0x38, 0x9e, 0x6d, 0x2e, 0xa0, 0xf8, 0x09, 0xda, 0x8a, + 0x9f, 0x33, 0xf1, 0x4c, 0x2d, 0x32, 0x3c, 0xe6, 0x68, 0x5f, 0x89, 0x1c, 0x30, 0x13, 0xfc, 0x71, + 0x25, 0x92, 0xbd, 0xb8, 0xa9, 0x59, 0x43, 0x7b, 0x4b, 0xa2, 0x52, 0xc1, 0x87, 0x3f, 0x14, 0xb4, + 0xd9, 0x05, 0x1b, 0x7f, 0x40, 0x95, 0xa5, 0x17, 0xbf, 0x5d, 0x44, 0xb7, 0x32, 0xb9, 0xda, 0x5a, + 0x0b, 0x96, 0xf2, 0xe1, 0xf7, 0xa8, 0x9c, 0x33, 0xe7, 0xd6, 0x7f, 0xda, 0xb3, 0x20, 0xf5, 0xfe, + 0x1a, 0xa0, 0x94, 0xa1, 0x7d, 0x72, 0x36, 0xd3, 0x94, 0xf3, 0x99, 0xa6, 0xfc, 0x99, 0x69, 0xca, + 0xe7, 0xb9, 0x56, 0x3a, 0x9f, 0x6b, 0xa5, 0x5f, 0x73, 0xad, 0xf4, 0xee, 0xf1, 0xfa, 0xeb, 0x31, + 0x49, 0xbf, 0xef, 0x68, 0x4b, 0x06, 0x5b, 0x32, 0xff, 0xf0, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x67, 0x36, 0x31, 0xb6, 0x02, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -171,6 +276,8 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { // DepositToVault deposits funds into a vault. DepositToVault(ctx context.Context, in *MsgDepositToVault, opts ...grpc.CallOption) (*MsgDepositToVaultResponse, error) + // UpdateParams updates the Params in state. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } type msgClient struct { @@ -190,10 +297,21 @@ func (c *msgClient) DepositToVault(ctx context.Context, in *MsgDepositToVault, o return out, nil } +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.vault.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // DepositToVault deposits funds into a vault. DepositToVault(context.Context, *MsgDepositToVault) (*MsgDepositToVaultResponse, error) + // UpdateParams updates the Params in state. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -203,6 +321,9 @@ type UnimplementedMsgServer struct { func (*UnimplementedMsgServer) DepositToVault(ctx context.Context, req *MsgDepositToVault) (*MsgDepositToVaultResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method DepositToVault not implemented") } +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -226,6 +347,24 @@ func _Msg_DepositToVault_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.vault.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "dydxprotocol.vault.Msg", HandlerType: (*MsgServer)(nil), @@ -234,6 +373,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "DepositToVault", Handler: _Msg_DepositToVault_Handler, }, + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "dydxprotocol/vault/tx.proto", @@ -319,6 +462,69 @@ func (m *MsgDepositToVaultResponse) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } +func (m *MsgUpdateParams) 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 *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) 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 *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -358,6 +564,30 @@ func (m *MsgDepositToVaultResponse) Size() (n int) { return n } +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -569,6 +799,171 @@ func (m *MsgDepositToVaultResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgUpdateParams) 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 ErrIntOverflowTx + } + 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: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) 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 ErrIntOverflowTx + } + 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: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/protocol/x/vault/types/types.go b/protocol/x/vault/types/types.go new file mode 100644 index 0000000000..4f84cd6fc0 --- /dev/null +++ b/protocol/x/vault/types/types.go @@ -0,0 +1,49 @@ +package types + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" +) + +type VaultKeeper interface { + // Orders. + GetVaultClobOrders( + ctx sdk.Context, + vaultId VaultId, + ) (orders []*clobtypes.Order, err error) + GetVaultClobOrderClientId( + ctx sdk.Context, + side clobtypes.Order_Side, + layer uint8, + ) uint32 + RefreshAllVaultOrders(ctx sdk.Context) + RefreshVaultClobOrders( + ctx sdk.Context, + vaultId VaultId, + ) (err error) + + // Shares. + GetTotalShares( + ctx sdk.Context, + vaultId VaultId, + ) (val NumShares, exists bool) + SetTotalShares( + ctx sdk.Context, + vaultId VaultId, + totalShares NumShares, + ) error + MintShares( + ctx sdk.Context, + vaultId VaultId, + owner string, + quantumsToDeposit *big.Int, + ) error + + // Vault info. + GetVaultEquity( + ctx sdk.Context, + vaultId VaultId, + ) (*big.Int, error) +} diff --git a/protocol/x/vault/types/vault.pb.go b/protocol/x/vault/types/vault.pb.go index f0797dd494..704b30d081 100644 --- a/protocol/x/vault/types/vault.pb.go +++ b/protocol/x/vault/types/vault.pb.go @@ -110,7 +110,7 @@ func (m *VaultId) GetNumber() uint32 { // NumShares represents the number of shares in a vault. type NumShares struct { // Number of shares. - NumShares github_com_dydxprotocol_v4_chain_protocol_dtypes.SerializableInt `protobuf:"bytes,1,opt,name=num_shares,json=numShares,proto3,customtype=github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt" json:"num_shares"` + NumShares github_com_dydxprotocol_v4_chain_protocol_dtypes.SerializableInt `protobuf:"bytes,2,opt,name=num_shares,json=numShares,proto3,customtype=github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt" json:"num_shares"` } func (m *NumShares) Reset() { *m = NumShares{} } @@ -164,17 +164,17 @@ var fileDescriptor_32accb5830bb2860 = []byte{ 0x61, 0x9a, 0xa1, 0x07, 0x56, 0x1a, 0x52, 0x59, 0x90, 0x1a, 0x04, 0x56, 0x2a, 0x24, 0xc6, 0xc5, 0x96, 0x57, 0x9a, 0x9b, 0x94, 0x5a, 0x24, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x1b, 0x04, 0xe5, 0x29, 0x95, 0x70, 0x71, 0xfa, 0x95, 0xe6, 0x06, 0x67, 0x24, 0x16, 0xa5, 0x16, 0x0b, 0xa5, 0x73, 0x71, - 0xe5, 0x95, 0xe6, 0xc6, 0x17, 0x83, 0x79, 0x60, 0xd3, 0x79, 0x9c, 0x3c, 0x4e, 0xdc, 0x93, 0x67, - 0xb8, 0x75, 0x4f, 0xde, 0x21, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, - 0xd5, 0x4f, 0x26, 0xba, 0xc9, 0x19, 0x89, 0x99, 0x79, 0xfa, 0x70, 0x91, 0x14, 0x90, 0x8d, 0xc5, - 0x7a, 0xc1, 0xa9, 0x45, 0x99, 0x89, 0x39, 0x99, 0x55, 0x89, 0x49, 0x39, 0xa9, 0x9e, 0x79, 0x25, - 0x41, 0x9c, 0x79, 0x30, 0x8b, 0xb4, 0x6c, 0xb8, 0x38, 0xe1, 0x0e, 0x14, 0x92, 0xe2, 0x12, 0x0b, - 0x73, 0x0c, 0xf5, 0x09, 0x89, 0x0f, 0x89, 0x0c, 0x70, 0x8d, 0x0f, 0xf5, 0x0b, 0x0e, 0x70, 0x75, - 0xf6, 0x74, 0xf3, 0x74, 0x75, 0x11, 0x60, 0x10, 0x12, 0xe6, 0xe2, 0x47, 0x92, 0x73, 0xf6, 0xf1, - 0x77, 0x12, 0x60, 0x74, 0x0a, 0x3c, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, - 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, - 0x73, 0xe2, 0x1d, 0x59, 0x01, 0x8d, 0x0c, 0xb0, 0x5b, 0x93, 0xd8, 0xc0, 0xe2, 0xc6, 0x80, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x22, 0x8c, 0x36, 0xdc, 0xaf, 0x01, 0x00, 0x00, + 0xe5, 0x95, 0xe6, 0xc6, 0x17, 0x83, 0x79, 0x60, 0x85, 0x3c, 0x4e, 0x1e, 0x27, 0xee, 0xc9, 0x33, + 0xdc, 0xba, 0x27, 0xef, 0x90, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x8f, + 0xea, 0x27, 0x13, 0xdd, 0xe4, 0x8c, 0xc4, 0xcc, 0x3c, 0x7d, 0xb8, 0x48, 0x0a, 0xc8, 0xc6, 0x62, + 0xbd, 0xe0, 0xd4, 0xa2, 0xcc, 0xc4, 0x9c, 0xcc, 0xaa, 0xc4, 0xa4, 0x9c, 0x54, 0xcf, 0xbc, 0x92, + 0x20, 0xce, 0x3c, 0x98, 0x45, 0x5a, 0x36, 0x5c, 0x9c, 0x70, 0x07, 0x0a, 0x49, 0x71, 0x89, 0x85, + 0x39, 0x86, 0xfa, 0x84, 0xc4, 0x87, 0x44, 0x06, 0xb8, 0xc6, 0x87, 0xfa, 0x05, 0x07, 0xb8, 0x3a, + 0x7b, 0xba, 0x79, 0xba, 0xba, 0x08, 0x30, 0x08, 0x09, 0x73, 0xf1, 0x23, 0xc9, 0x39, 0xfb, 0xf8, + 0x3b, 0x09, 0x30, 0x3a, 0x05, 0x9e, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, + 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, + 0x39, 0xf1, 0x8e, 0xac, 0x80, 0x46, 0x06, 0xd8, 0xad, 0x49, 0x6c, 0x60, 0x71, 0x63, 0x40, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xf0, 0x30, 0x49, 0x71, 0xaf, 0x01, 0x00, 0x00, } func (m *VaultId) Marshal() (dAtA []byte, err error) { @@ -239,7 +239,7 @@ func (m *NumShares) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintVault(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0xa + dAtA[i] = 0x12 return len(dAtA) - i, nil } @@ -403,7 +403,7 @@ func (m *NumShares) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: NumShares: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NumShares", wireType) } diff --git a/protocol/x/vault/types/vault_id.go b/protocol/x/vault/types/vault_id.go index b4fd10a681..af070d0eae 100644 --- a/protocol/x/vault/types/vault_id.go +++ b/protocol/x/vault/types/vault_id.go @@ -2,19 +2,55 @@ package types import ( fmt "fmt" + "strconv" + "strings" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) +// ToString returns the string representation of a vault ID. +func (id *VaultId) ToString() string { + return fmt.Sprintf("%s-%d", id.Type, id.Number) +} + // ToStateKey returns the state key for the vault ID. func (id *VaultId) ToStateKey() []byte { - b, err := id.Marshal() + return []byte(id.ToString()) +} + +// ToStateKeyPrefix returns the state key prefix for the vault ID. +func (id *VaultId) ToStateKeyPrefix() []byte { + return []byte(fmt.Sprintf("%s:", id.ToString())) +} + +// GetVaultIdFromStateKey returns a vault ID from a given state key. +func GetVaultIdFromStateKey(stateKey []byte) (*VaultId, error) { + stateKeyStr := string(stateKey) + + // Split state key string into type and number. + split := strings.Split(stateKeyStr, "-") + if len(split) != 2 { + return nil, fmt.Errorf("stateKey in string must follow format - but got %s", stateKeyStr) + } + + // Parse vault type. + vaultTypeInt, exists := VaultType_value[split[0]] + if !exists { + return nil, fmt.Errorf("unknown vault type: %s", split[0]) + } + + // Parse vault number. + number, err := strconv.ParseUint(split[1], 10, 32) if err != nil { - panic(err) + return nil, fmt.Errorf("failed to parse number: %s", err.Error()) } - return b + + return &VaultId{ + Type: VaultType(vaultTypeInt), + Number: uint32(number), + }, nil } // ToModuleAccountAddress returns the module account address for the vault ID @@ -36,8 +72,34 @@ func (id *VaultId) ToSubaccountId() *satypes.SubaccountId { // IncrCounterWithLabels increments counter with labels with added vault ID labels. func (id *VaultId) IncrCounterWithLabels(metricName string, labels ...metrics.Label) { - // Append vault type and number to labels. - labels = append( + // Append vault labels. + labels = id.addLabels(labels...) + + metrics.IncrCounterWithLabels( + metricName, + 1, + labels..., + ) +} + +// IncrCounterWithLabels sets gauge with labels with added vault ID labels. +func (id *VaultId) SetGaugeWithLabels( + metricName string, + value float32, + labels ...metrics.Label, +) { + // Append vault labels. + labels = id.addLabels(labels...) + + metrics.SetGaugeWithLabels( + metricName, + value, + labels..., + ) +} + +func (id *VaultId) addLabels(labels ...metrics.Label) []metrics.Label { + return append( labels, metrics.GetLabelForIntValue( metrics.VaultType, @@ -48,10 +110,4 @@ func (id *VaultId) IncrCounterWithLabels(metricName string, labels ...metrics.La int(id.Number), ), ) - - metrics.IncrCounterWithLabels( - metricName, - 1, - labels..., - ) } diff --git a/protocol/x/vault/types/vault_id_test.go b/protocol/x/vault/types/vault_id_test.go index cc4fef9b38..629d727d89 100644 --- a/protocol/x/vault/types/vault_id_test.go +++ b/protocol/x/vault/types/vault_id_test.go @@ -6,15 +6,114 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" "github.com/stretchr/testify/require" ) +func TestToString(t *testing.T) { + tests := map[string]struct { + // Vault ID. + vaultId types.VaultId + // Expected string. + expectedStr string + }{ + "Vault for Clob Pair 0": { + vaultId: constants.Vault_Clob_0, + expectedStr: "VAULT_TYPE_CLOB-0", + }, + "Vault for Clob Pair 1": { + vaultId: constants.Vault_Clob_1, + expectedStr: "VAULT_TYPE_CLOB-1", + }, + "Vault, missing type and number": { + vaultId: types.VaultId{}, + expectedStr: "VAULT_TYPE_UNSPECIFIED-0", + }, + "Vault, missing type": { + vaultId: types.VaultId{ + Number: 1, + }, + expectedStr: "VAULT_TYPE_UNSPECIFIED-1", + }, + "Vault, missing number": { + vaultId: types.VaultId{ + Type: types.VaultType_VAULT_TYPE_CLOB, + }, + expectedStr: "VAULT_TYPE_CLOB-0", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + require.Equal( + t, + tc.vaultId.ToString(), + tc.expectedStr, + ) + }) + } +} + func TestToStateKey(t *testing.T) { - b, _ := constants.Vault_Clob_0.Marshal() - require.Equal(t, b, constants.Vault_Clob_0.ToStateKey()) + require.Equal( + t, + []byte("VAULT_TYPE_CLOB-0"), + constants.Vault_Clob_0.ToStateKey(), + ) + + require.Equal( + t, + []byte("VAULT_TYPE_CLOB-1"), + constants.Vault_Clob_1.ToStateKey(), + ) +} + +func TestGetVaultIdFromStateKey(t *testing.T) { + tests := map[string]struct { + // State key. + stateKey []byte + // Expected vault ID. + expectedVaultId types.VaultId + // Expected error. + expectedErr string + }{ + "Vault for Clob Pair 0": { + stateKey: []byte("VAULT_TYPE_CLOB-0"), + expectedVaultId: constants.Vault_Clob_0, + }, + "Vault for Clob Pair 1": { + stateKey: []byte("VAULT_TYPE_CLOB-1"), + expectedVaultId: constants.Vault_Clob_1, + }, + "Empty bytes": { + stateKey: []byte{}, + expectedErr: "stateKey in string must follow format -", + }, + "Nil bytes": { + stateKey: nil, + expectedErr: "stateKey in string must follow format -", + }, + "Non-existent vault type": { + stateKey: []byte("VAULT_TYPE_SPOT-1"), + expectedErr: "unknown vault type", + }, + "Malformed vault number": { + stateKey: []byte("VAULT_TYPE_CLOB-abc"), + expectedErr: "failed to parse number", + }, + } - b, _ = constants.Vault_Clob_1.Marshal() - require.Equal(t, b, constants.Vault_Clob_1.ToStateKey()) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + vaultId, err := types.GetVaultIdFromStateKey(tc.stateKey) + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedVaultId, *vaultId) + } + }) + } } func TestToModuleAccountAddress(t *testing.T) {