From bf1a5c2754d097d7aaebae29ed237d7476596423 Mon Sep 17 00:00:00 2001 From: dydxwill <119354122+dydxwill@users.noreply.github.com> Date: Mon, 25 Mar 2024 13:28:48 -0400 Subject: [PATCH 01/35] [CT-709] Add flag to send subaccount websocket message from Ender for long term orders (#1223) --- .../packages/kafka/src/websocket-helper.ts | 19 ++++- .../redis/src/helpers/order-helper.ts | 28 +++++++ indexer/packages/redis/src/index.ts | 1 + .../stateful-order-placement-handler.test.ts | 20 ++++- .../helpers/indexer-proto-helpers.ts | 42 +++++++--- indexer/services/ender/src/config.ts | 3 + .../stateful-order-placement-handler.ts | 78 +++++++++++++++---- .../handlers/order-place-handler.test.ts | 13 ++-- .../services/vulcan/src/handlers/helpers.ts | 31 +------- .../src/handlers/order-place-handler.ts | 2 +- 10 files changed, 171 insertions(+), 66 deletions(-) create mode 100644 indexer/packages/redis/src/helpers/order-helper.ts 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/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/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/indexer-proto-helpers.ts b/indexer/services/ender/__tests__/helpers/indexer-proto-helpers.ts index c19b4424ec..120b19593e 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/vulcan/__tests__/handlers/order-place-handler.test.ts b/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts index ed52f67c22..ab43b71d13 100644 --- a/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts +++ b/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts @@ -56,7 +56,6 @@ 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'; @@ -118,23 +117,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, ); @@ -279,7 +278,7 @@ describe('order-place-handler', () => { expectedOrderUuid: string, expectSubaccountMessageSent: boolean, ) => { - const expectedOrder: RedisOrder = convertToRedisOrder( + const expectedOrder: RedisOrder = redisPackage.convertToRedisOrder( orderToPlace, testConstants.defaultPerpetualMarket, ); 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 b5fd605b96..874889af84 100644 --- a/indexer/services/vulcan/src/handlers/order-place-handler.ts +++ b/indexer/services/vulcan/src/handlers/order-place-handler.ts @@ -14,6 +14,7 @@ import { placeOrder, PlaceOrderResult, StatefulOrderUpdatesCache, + convertToRedisOrder, } from '@dydxprotocol-indexer/redis'; import { getOrderIdHash, @@ -36,7 +37,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. From 275f5298db4704ce5f07a5b44604d44184d59e7a Mon Sep 17 00:00:00 2001 From: dydxwill <119354122+dydxwill@users.noreply.github.com> Date: Mon, 25 Mar 2024 14:36:06 -0400 Subject: [PATCH 02/35] [CT-709] Add flag to send subaccount websocket message for long term orders in vulcan (#1224) --- .../__tests__/order-helpers.test.ts | 22 +++++++++++++- .../v4-proto-parser/src/order-helpers.ts | 9 ++++++ .../handlers/order-place-handler.test.ts | 30 +++++++++++++++++-- indexer/services/vulcan/src/config.ts | 3 ++ .../src/handlers/order-place-handler.ts | 16 +++++++++- 5 files changed, 76 insertions(+), 4 deletions(-) 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/services/vulcan/__tests__/handlers/order-place-handler.test.ts b/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts index ab43b71d13..d26015e30e 100644 --- a/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts +++ b/indexer/services/vulcan/__tests__/handlers/order-place-handler.test.ts @@ -61,7 +61,8 @@ 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 config from '../../src/config'; jest.mock('@dydxprotocol-indexer/base', () => ({ ...jest.requireActual('@dydxprotocol-indexer/base'), @@ -82,6 +83,10 @@ describe('order-place-handler', () => { jest.useRealTimers(); }); + afterEach(() => { + config.SEND_SUBACCOUNT_WEBSOCKET_MESSAGE_FOR_STATEFUL_ORDERS = true; + }); + describe('handle', () => { const replacementOrder: IndexerOrder = { ...redisTestConstants.defaultOrder, @@ -830,19 +835,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 @@ -883,7 +907,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/order-place-handler.ts b/indexer/services/vulcan/src/handlers/order-place-handler.ts index 874889af84..41449dc2fd 100644 --- a/indexer/services/vulcan/src/handlers/order-place-handler.ts +++ b/indexer/services/vulcan/src/handlers/order-place-handler.ts @@ -18,6 +18,7 @@ import { } from '@dydxprotocol-indexer/redis'; import { getOrderIdHash, + isLongTermOrder, isStatefulOrder, ORDER_FLAG_SHORT_TERM, requiresImmediateExecution, @@ -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 @@ -273,7 +279,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 From 50c712ad7e7c2fa09d7ddaa46cd583cb6c85c1ed Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Mon, 25 Mar 2024 16:01:24 -0400 Subject: [PATCH 03/35] Add upgrade handler for OIMF (#1232) --- protocol/app/upgrades/v5.0.0/upgrade.go | 52 +++++++++++++++++++ protocol/mocks/PerpetualsKeeper.go | 20 +++++++ .../scripts/genesis/sample_pregenesis.json | 4 +- protocol/testing/genesis.sh | 7 ++- protocol/x/perpetuals/types/types.go | 1 + 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/protocol/app/upgrades/v5.0.0/upgrade.go b/protocol/app/upgrades/v5.0.0/upgrade.go index 2e8176c179..bd5ccbbb11 100644 --- a/protocol/app/upgrades/v5.0.0/upgrade.go +++ b/protocol/app/upgrades/v5.0.0/upgrade.go @@ -98,6 +98,55 @@ func blockRateLimitConfigUpdate( ) } +// 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, @@ -114,6 +163,9 @@ func CreateUpgradeHandler( // Set block rate limit configuration blockRateLimitConfigUpdate(sdkCtx, clobKeeper) + // 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/mocks/PerpetualsKeeper.go b/protocol/mocks/PerpetualsKeeper.go index 85da4a3b2b..f729748879 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) diff --git a/protocol/scripts/genesis/sample_pregenesis.json b/protocol/scripts/genesis/sample_pregenesis.json index 046f53e67f..414c3d4690 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": { 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/x/perpetuals/types/types.go b/protocol/x/perpetuals/types/types.go index 81ba665c2b..7fc9ebdb9b 100644 --- a/protocol/x/perpetuals/types/types.go +++ b/protocol/x/perpetuals/types/types.go @@ -118,6 +118,7 @@ type PerpetualsKeeper interface { GetAllPerpetuals( ctx sdk.Context, ) []Perpetual + GetAllLiquidityTiers(ctx sdk.Context) (list []LiquidityTier) } type OpenInterestDelta struct { From 0dbaf0b0df4a684d58233acc275a79e6988688ff Mon Sep 17 00:00:00 2001 From: shrenujb <98204323+shrenujb@users.noreply.github.com> Date: Mon, 25 Mar 2024 16:28:30 -0400 Subject: [PATCH 04/35] [TRA-110] Implement GET /address/parentSubaccountNumber API (#1229) Signed-off-by: Shrenuj Bansal --- .../postgres/__tests__/helpers/constants.ts | 10 + .../__tests__/lib/api-translations.test.ts | 39 ---- .../postgres/src/lib/api-translations.ts | 29 +-- .../api/v4/addresses-controller.test.ts | 173 ++++++++++++++ .../comlink/__tests__/lib/helpers.test.ts | 39 +++- .../comlink/public/api-documentation.md | 219 ++++++++++++++++++ indexer/services/comlink/public/swagger.json | 69 ++++++ .../api/v4/addresses-controller.ts | 160 ++++++++++++- .../controllers/api/v4/fills-controller.ts | 2 +- indexer/services/comlink/src/lib/helpers.ts | 50 +++- indexer/services/comlink/src/types.ts | 8 + 11 files changed, 724 insertions(+), 74 deletions(-) diff --git a/indexer/packages/postgres/__tests__/helpers/constants.ts b/indexer/packages/postgres/__tests__/helpers/constants.ts index 84791623a0..7fb5214d1c 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 ============== 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/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__/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..d1af0c5567 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 @@ -2353,6 +2484,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 diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index ad36977d51..9efb4d4804 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": { @@ -1213,6 +1245,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", 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/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/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/types.ts b/indexer/services/comlink/src/types.ts index 3a1ba4402e..256f9179ea 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 ------- */ From b2d63af11130f382f59a627732bbc4d2b2ca1ce0 Mon Sep 17 00:00:00 2001 From: Mohammed Affan Date: Mon, 25 Mar 2024 16:37:20 -0400 Subject: [PATCH 05/35] [OTE-218] Add state validity test for perpetual market type (#1170) --- protocol/Makefile | 4 +- .../upgrades/v5.0.0/upgrade_container_test.go | 74 +++++++++++++++++++ .../testing/containertest/testnet_test.go | 51 +------------ .../testing/containertest/testnet_utils.go | 66 +++++++++++++++++ 4 files changed, 143 insertions(+), 52 deletions(-) create mode 100644 protocol/app/upgrades/v5.0.0/upgrade_container_test.go create mode 100644 protocol/testing/containertest/testnet_utils.go 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/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..63667592da --- /dev/null +++ b/protocol/app/upgrades/v5.0.0/upgrade_container_test.go @@ -0,0 +1,74 @@ +//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) + // 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) + } +} 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 +} From 82c2dec66f9ccab8929ff47ca9f757149329e6dc Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Mon, 25 Mar 2024 16:57:10 -0400 Subject: [PATCH 06/35] [OTE-245] Update OI after fill and added unit tests (#1231) * Update OI after fill and added unit tests * nits * fix unit test --- protocol/testutil/constants/perpetuals.go | 23 +- protocol/testutil/perpetuals/perpetuals.go | 2 +- protocol/x/clob/keeper/deleveraging_test.go | 22 + protocol/x/clob/keeper/liquidations_test.go | 31 ++ protocol/x/clob/keeper/orders_test.go | 84 +++- protocol/x/perpetuals/keeper/perpetual.go | 7 + .../x/perpetuals/keeper/perpetual_test.go | 2 +- protocol/x/perpetuals/types/types.go | 3 +- protocol/x/subaccounts/keeper/oimf.go | 6 +- protocol/x/subaccounts/keeper/oimf_test.go | 12 +- protocol/x/subaccounts/keeper/subaccount.go | 23 +- .../x/subaccounts/keeper/subaccount_test.go | 379 +++++++++++++++++- protocol/x/subaccounts/types/errors.go | 2 +- 13 files changed, 555 insertions(+), 41 deletions(-) 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/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/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/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/orders_test.go b/protocol/x/clob/keeper/orders_test.go index 0912d2be24..521e0b8f85 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{ @@ -142,6 +153,10 @@ func TestPlaceShortTermOrder(t *testing.T) { // 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`: { @@ -458,6 +517,10 @@ func TestPlaceShortTermOrder(t *testing.T) { // 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/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index 651111ea5c..502bb74523 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 } 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/types/types.go b/protocol/x/perpetuals/types/types.go index 7fc9ebdb9b..86c96c31fa 100644 --- a/protocol/x/perpetuals/types/types.go +++ b/protocol/x/perpetuals/types/types.go @@ -121,9 +121,10 @@ type PerpetualsKeeper interface { 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/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, From 806d1ea7fd29943ab5eb2d80cbe4d6ce3abd66ea Mon Sep 17 00:00:00 2001 From: Tian Date: Tue, 26 Mar 2024 14:55:06 -0400 Subject: [PATCH 07/35] [TRA-118] store x/vault parameters in state, add query, and update via governance (#1238) * store x/vault parameters in state, add query, and update via governance * update parameter validation --- .../src/codegen/dydxprotocol/bundle.ts | 214 ++++---- .../v4-protos/src/codegen/dydxprotocol/lcd.ts | 3 + .../src/codegen/dydxprotocol/vault/genesis.ts | 28 +- .../src/codegen/dydxprotocol/vault/params.ts | 153 ++++++ .../codegen/dydxprotocol/vault/query.lcd.ts | 22 + .../dydxprotocol/vault/query.rpc.Query.ts | 21 +- .../src/codegen/dydxprotocol/vault/query.ts | 99 +++- .../codegen/dydxprotocol/vault/tx.rpc.msg.ts | 12 +- .../src/codegen/dydxprotocol/vault/tx.ts | 112 ++++ .../v4-protos/src/codegen/gogoproto/bundle.ts | 4 +- .../v4-protos/src/codegen/google/bundle.ts | 22 +- proto/dydxprotocol/vault/genesis.proto | 8 +- proto/dydxprotocol/vault/params.proto | 28 + proto/dydxprotocol/vault/query.proto | 19 +- proto/dydxprotocol/vault/tx.proto | 18 + protocol/app/msgs/all_msgs.go | 2 + protocol/app/msgs/internal_msgs.go | 5 + protocol/app/msgs/internal_msgs_test.go | 4 + .../app/testdata/default_genesis_state.json | 11 +- protocol/lib/ante/internal_msg.go | 4 + .../scripts/genesis/sample_pregenesis.json | 11 +- protocol/testutil/constants/genesis.go | 11 +- protocol/x/vault/client/cli/query.go | 27 + protocol/x/vault/client/cli/query_test.go | 54 ++ protocol/x/vault/genesis.go | 10 +- protocol/x/vault/keeper/grpc_query.go | 14 + protocol/x/vault/keeper/grpc_query_test.go | 46 ++ .../vault/keeper/msg_server_update_params.go | 32 ++ .../keeper/msg_server_update_params_test.go | 69 +++ protocol/x/vault/keeper/params.go | 35 ++ protocol/x/vault/keeper/params_test.go | 45 ++ protocol/x/vault/types/errors.go | 15 + protocol/x/vault/types/genesis.go | 6 +- protocol/x/vault/types/genesis.pb.go | 73 ++- protocol/x/vault/types/keys.go | 3 + protocol/x/vault/types/params.go | 31 ++ protocol/x/vault/types/params.pb.go | 491 ++++++++++++++++++ protocol/x/vault/types/params_test.go | 62 +++ protocol/x/vault/types/query.pb.go | 475 ++++++++++++++++- protocol/x/vault/types/query.pb.gw.go | 153 ++++++ protocol/x/vault/types/tx.pb.go | 441 +++++++++++++++- 41 files changed, 2717 insertions(+), 176 deletions(-) create mode 100644 indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/params.ts create mode 100644 indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.lcd.ts create mode 100644 proto/dydxprotocol/vault/params.proto create mode 100644 protocol/x/vault/client/cli/query_test.go create mode 100644 protocol/x/vault/keeper/grpc_query_test.go create mode 100644 protocol/x/vault/keeper/msg_server_update_params.go create mode 100644 protocol/x/vault/keeper/msg_server_update_params_test.go create mode 100644 protocol/x/vault/keeper/params.go create mode 100644 protocol/x/vault/keeper/params_test.go create mode 100644 protocol/x/vault/types/params.go create mode 100644 protocol/x/vault/types/params.pb.go create mode 100644 protocol/x/vault/types/params_test.go create mode 100644 protocol/x/vault/types/query.pb.gw.go 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/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/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..af968398bc --- /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. */ + + orderSizePpm: 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_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, + orderSizePpm: 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.orderSizePpm !== 0) { + writer.uint32(40).uint32(message.orderSizePpm); + } + + 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.orderSizePpm = 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.orderSizePpm = object.orderSizePpm ?? 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..bf1e99fb60 --- /dev/null +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.lcd.ts @@ -0,0 +1,22 @@ +import { LCDClient } from "@osmonauts/lcd"; +import { QueryParamsRequest, QueryParamsResponseSDKType } from "./query"; +export class LCDQueryClient { + req: LCDClient; + + constructor({ + requestClient + }: { + requestClient: LCDClient; + }) { + this.req = requestClient; + this.params = this.params.bind(this); + } + /* Queries the Params. */ + + + async params(_params: QueryParamsRequest = {}): Promise { + const endpoint = `dydxprotocol/v4/vault/params`; + 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..26983c84e0 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,35 @@ import { Rpc } from "../../helpers"; +import * as _m0 from "protobufjs/minimal"; import { QueryClient, createProtobufRpcClient } from "@cosmjs/stargate"; +import { QueryParamsRequest, QueryParamsResponse } from "./query"; /** Query defines the gRPC querier service. */ -export interface Query {} +export interface Query { + /** Queries the Params. */ + params(request?: QueryParamsRequest): Promise; +} export class QueryClientImpl implements Query { private readonly rpc: Rpc; constructor(rpc: Rpc) { this.rpc = rpc; + this.params = this.params.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))); } } export const createRpcQueryExtension = (base: QueryClient) => { const rpc = createProtobufRpcClient(base); const queryService = new QueryClientImpl(rpc); - return {}; + return { + params(request?: QueryParamsRequest): Promise { + return queryService.params(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..095ba9f2be 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,98 @@ -export {} \ No newline at end of file +import { Params, ParamsSDKType } from "./params"; +import * as _m0 from "protobufjs/minimal"; +import { DeepPartial } 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; +} + +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; + } + +}; \ 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/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/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..8e1c8b2378 --- /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_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..e4ce844f3d 100644 --- a/proto/dydxprotocol/vault/query.proto +++ b/proto/dydxprotocol/vault/query.proto @@ -1,7 +1,24 @@ syntax = "proto3"; package dydxprotocol.vault; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "dydxprotocol/vault/params.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"; + } +} + +// 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 ]; +} diff --git a/proto/dydxprotocol/vault/tx.proto b/proto/dydxprotocol/vault/tx.proto index 90da68e40f..0483a50627 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,6 +14,8 @@ 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. @@ -31,3 +36,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/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..5a95e2d5f3 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_ppm": 100000, + "order_expiration_seconds": 2 + } + }, "vest": { "vest_entries": [ { 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/scripts/genesis/sample_pregenesis.json b/protocol/scripts/genesis/sample_pregenesis.json index 414c3d4690..68307d6092 100644 --- a/protocol/scripts/genesis/sample_pregenesis.json +++ b/protocol/scripts/genesis/sample_pregenesis.json @@ -1806,7 +1806,16 @@ "total_escrowed": [] }, "upgrade": {}, - "vault": {}, + "vault": { + "params": { + "layers": 2, + "order_expiration_seconds": 2, + "order_size_ppm": 100000, + "skew_factor_ppm": 500000, + "spread_buffer_ppm": 1500, + "spread_min_ppm": 3000 + } + }, "vest": { "vest_entries": [ { diff --git a/protocol/testutil/constants/genesis.go b/protocol/testutil/constants/genesis.go index dc56ee3590..3c0282ec6b 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_ppm": 100000, + "order_expiration_seconds": 2 + } + }, "vest": { "vest_entries": [ { diff --git a/protocol/x/vault/client/cli/query.go b/protocol/x/vault/client/cli/query.go index d55ce14426..b7220913ba 100644 --- a/protocol/x/vault/client/cli/query.go +++ b/protocol/x/vault/client/cli/query.go @@ -1,11 +1,13 @@ package cli import ( + "context" "fmt" "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 +22,30 @@ func GetQueryCmd(queryRoute string) *cobra.Command { RunE: client.ValidateCmd, } + cmd.AddCommand(CmdQueryParams()) + + 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 } 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/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..acfc221ecb --- /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, + OrderSizePpm: 0, // invalid + OrderExpirationSeconds: 5, + }, + }, + expectedErr: types.ErrInvalidOrderSizePpm.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/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..54b396c3ec --- /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, + OrderSizePpm: 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, + OrderSizePpm: 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/types/errors.go b/protocol/x/vault/types/errors.go index fdd1c6fd4b..0a84bc73d6 100644 --- a/protocol/x/vault/types/errors.go +++ b/protocol/x/vault/types/errors.go @@ -20,4 +20,19 @@ var ( 3, "MarketParam not found", ) + ErrInvalidOrderSizePpm = errorsmod.Register( + ModuleName, + 4, + "OrderSizePpm must be strictly greater than 0", + ) + ErrInvalidOrderExpirationSeconds = errorsmod.Register( + ModuleName, + 5, + "OrderExpirationSeconds must be strictly greater than 0", + ) + ErrInvalidSpreadMinPpm = errorsmod.Register( + ModuleName, + 6, + "SpreadMinPpm must be strictly greater than 0", + ) ) 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..4e37abb4d9 100644 --- a/protocol/x/vault/types/keys.go +++ b/protocol/x/vault/types/keys.go @@ -13,4 +13,7 @@ const ( const ( // TotalSharesKeyPrefix is the prefix to retrieve all TotalShares. TotalSharesKeyPrefix = "TotalShares:" + + // ParamsKey is the key to retrieve Params. + ParamsKey = "Params" ) diff --git a/protocol/x/vault/types/params.go b/protocol/x/vault/types/params.go new file mode 100644 index 0000000000..4f3cae9d26 --- /dev/null +++ b/protocol/x/vault/types/params.go @@ -0,0 +1,31 @@ +package types + +// 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 + OrderSizePpm: 100_000, // 10% + OrderExpirationSeconds: 2, // 2 seconds + } +} + +// Validate validates `x/vault` parameters. +func (p Params) Validate() error { + // Spread min ppm must be positive. + if p.SpreadMinPpm == 0 { + return ErrInvalidSpreadMinPpm + } + // Order size must be positive. + if p.OrderSizePpm == 0 { + return ErrInvalidOrderSizePpm + } + // 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..428d8b1836 --- /dev/null +++ b/protocol/x/vault/types/params.pb.go @@ -0,0 +1,491 @@ +// 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. + OrderSizePpm uint32 `protobuf:"varint,5,opt,name=order_size_ppm,json=orderSizePpm,proto3" json:"order_size_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) GetOrderSizePpm() uint32 { + if m != nil { + return m.OrderSizePpm + } + 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{ + // 287 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0xd0, 0x3f, 0x4b, 0xc4, 0x30, + 0x18, 0xc7, 0xf1, 0xc6, 0x3f, 0x1d, 0x82, 0x77, 0x87, 0x1d, 0x8e, 0x4e, 0x51, 0xe4, 0x10, 0x11, + 0x6c, 0x07, 0x05, 0x9d, 0x0f, 0x74, 0x13, 0x4e, 0x6f, 0x73, 0x29, 0x69, 0x9b, 0x7a, 0xc1, 0xb6, + 0x09, 0x49, 0xaa, 0xed, 0xbd, 0x0a, 0x5f, 0x96, 0xe3, 0x8d, 0x8e, 0xd2, 0xbe, 0x0b, 0x27, 0xe9, + 0x93, 0x72, 0x78, 0x63, 0xbe, 0xbf, 0x0f, 0x04, 0x1e, 0x7c, 0x92, 0x36, 0x69, 0x2d, 0x95, 0x30, + 0x22, 0x11, 0x79, 0xf8, 0x4e, 0xab, 0xdc, 0x84, 0x92, 0x2a, 0x5a, 0xe8, 0x00, 0xaa, 0xe7, 0xfd, + 0x07, 0x01, 0x80, 0xb3, 0x5f, 0x84, 0xdd, 0x05, 0x20, 0x6f, 0x8a, 0xdd, 0x9c, 0x36, 0x4c, 0x69, + 0x1f, 0x9d, 0xa2, 0x8b, 0xd1, 0xf3, 0xf0, 0xf2, 0x66, 0x78, 0xac, 0xa5, 0x62, 0x34, 0x8d, 0x0a, + 0x5e, 0x46, 0x52, 0x16, 0xfe, 0x1e, 0xec, 0x47, 0xb6, 0x3e, 0xf2, 0x72, 0x21, 0x0b, 0xef, 0x12, + 0x1f, 0x0f, 0x2a, 0xae, 0xb2, 0x8c, 0x29, 0x80, 0xfb, 0x00, 0x27, 0x76, 0x98, 0x43, 0xef, 0xed, + 0x39, 0x9e, 0xe8, 0x37, 0xf6, 0x11, 0x65, 0x34, 0x31, 0xc2, 0xca, 0x03, 0x90, 0xa3, 0x3e, 0x3f, + 0x40, 0xed, 0xdd, 0x0c, 0x8f, 0x85, 0x4a, 0x99, 0x8a, 0x34, 0x5f, 0x33, 0x60, 0x87, 0xf6, 0x67, + 0xa8, 0x4b, 0xbe, 0x66, 0xbd, 0xba, 0xc3, 0xbe, 0x55, 0xac, 0x96, 0x5c, 0x51, 0xc3, 0x45, 0x19, + 0x69, 0x96, 0x88, 0x32, 0xd5, 0xbe, 0x0b, 0x7e, 0x0a, 0xfb, 0xfd, 0x76, 0x5e, 0xda, 0x75, 0xfe, + 0xf4, 0xd5, 0x12, 0xb4, 0x69, 0x09, 0xfa, 0x69, 0x09, 0xfa, 0xec, 0x88, 0xb3, 0xe9, 0x88, 0xf3, + 0xdd, 0x11, 0xe7, 0xe5, 0xf6, 0x95, 0x9b, 0x55, 0x15, 0x07, 0x89, 0x28, 0xc2, 0xdd, 0xb3, 0xde, + 0x5c, 0x25, 0x2b, 0xca, 0xcb, 0x70, 0x5b, 0xea, 0xe1, 0xd4, 0xa6, 0x91, 0x4c, 0xc7, 0x2e, 0xf4, + 0xeb, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xce, 0x3a, 0xeb, 0x7c, 0x8d, 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.OrderSizePpm != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.OrderSizePpm)) + 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.OrderSizePpm != 0 { + n += 1 + sovParams(uint64(m.OrderSizePpm)) + } + 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 OrderSizePpm", wireType) + } + m.OrderSizePpm = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OrderSizePpm |= 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..82cec0b062 --- /dev/null +++ b/protocol/x/vault/types/params_test.go @@ -0,0 +1,62 @@ +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 - SpreadMinPpm is 0": { + params: types.Params{ + Layers: 2, + SpreadMinPpm: 0, + SpreadBufferPpm: 1_500, + SkewFactorPpm: 500_000, + OrderSizePpm: 100_000, + OrderExpirationSeconds: 5, + }, + expectedErr: types.ErrInvalidSpreadMinPpm, + }, + "Failure - OrderSizePpm is 0": { + params: types.Params{ + Layers: 2, + SpreadMinPpm: 3_000, + SpreadBufferPpm: 1_500, + SkewFactorPpm: 500_000, + OrderSizePpm: 0, + OrderExpirationSeconds: 5, + }, + expectedErr: types.ErrInvalidOrderSizePpm, + }, + "Failure - OrderExpirationSeconds is 0": { + params: types.Params{ + Layers: 2, + SpreadMinPpm: 3_000, + SpreadBufferPpm: 1_500, + SkewFactorPpm: 500_000, + OrderSizePpm: 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..e186b0d621 100644 --- a/protocol/x/vault/types/query.pb.go +++ b/protocol/x/vault/types/query.pb.go @@ -6,10 +6,16 @@ package types import ( context "context" fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" + _ "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 +29,115 @@ 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{} +} + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "dydxprotocol.vault.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "dydxprotocol.vault.QueryParamsResponse") +} + func init() { proto.RegisterFile("dydxprotocol/vault/query.proto", fileDescriptor_478fb8dc0ff21ea6) } var fileDescriptor_478fb8dc0ff21ea6 = []byte{ - // 132 bytes of a gzipped FileDescriptorProto + // 275 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, + 0x2c, 0x4d, 0x2d, 0xaa, 0xd4, 0x03, 0x0b, 0x0a, 0x09, 0x21, 0xcb, 0xeb, 0x81, 0xe5, 0xa5, 0x44, + 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x62, 0xfa, 0x20, 0x16, 0x44, 0xa5, 0x94, 0x4c, 0x7a, 0x7e, 0x7e, + 0x7a, 0x4e, 0xaa, 0x7e, 0x62, 0x41, 0xa6, 0x7e, 0x62, 0x5e, 0x5e, 0x7e, 0x49, 0x62, 0x49, 0x66, + 0x7e, 0x5e, 0x31, 0x54, 0x56, 0x1e, 0x8b, 0x3d, 0x05, 0x89, 0x45, 0x89, 0xb9, 0x50, 0x05, 0x4a, + 0x22, 0x5c, 0x42, 0x81, 0x20, 0x7b, 0x03, 0xc0, 0x82, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, + 0x4a, 0xfe, 0x5c, 0xc2, 0x28, 0xa2, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x16, 0x5c, 0x6c, + 0x10, 0xcd, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xdc, 0x46, 0x52, 0x7a, 0x98, 0xce, 0xd4, 0x83, 0xe8, + 0x71, 0x62, 0x39, 0x71, 0x4f, 0x9e, 0x21, 0x08, 0xaa, 0xde, 0xa8, 0x8b, 0x91, 0x8b, 0x15, 0x6c, + 0xa2, 0x50, 0x03, 0x23, 0x17, 0x1b, 0x44, 0x89, 0x90, 0x1a, 0x36, 0xed, 0x98, 0xae, 0x91, 0x52, + 0x27, 0xa8, 0x0e, 0xe2, 0x3e, 0x25, 0xd5, 0xa6, 0xcb, 0x4f, 0x26, 0x33, 0xc9, 0x0b, 0xc9, 0xea, + 0xa3, 0x7a, 0xdb, 0x04, 0xc5, 0xe7, 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, 0x8b, 0x6e, 0x84, + 0x6e, 0x72, 0x46, 0x62, 0x66, 0x9e, 0x3e, 0x5c, 0xa4, 0x02, 0x6a, 0x66, 0x49, 0x65, 0x41, 0x6a, + 0x71, 0x12, 0x1b, 0x58, 0xdc, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x2c, 0xa1, 0x80, 0x8e, 0xd8, + 0x01, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -50,6 +152,8 @@ 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) } type queryClient struct { @@ -60,22 +164,371 @@ 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 +} + // QueryServer is the server API for Query service. type QueryServer interface { + // Queries the Params. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, 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 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) +} + 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, + }, + }, + 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 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 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 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..40cafb6ba6 --- /dev/null +++ b/protocol/x/vault/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// 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 + +} + +// 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()...) + + }) + + 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()...) + + }) + + 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))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage +) diff --git a/protocol/x/vault/types/tx.pb.go b/protocol/x/vault/types/tx.pb.go index 4289883ce0..f9c5649006 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, + // 497 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0x8d, 0x5b, 0x54, 0xe8, 0x92, 0x06, 0xb1, 0xaa, 0xd4, 0xc4, 0x91, 0x9c, 0x2a, 0x08, 0x54, + 0x40, 0xb1, 0x45, 0x41, 0x05, 0x71, 0x82, 0x88, 0x03, 0x11, 0x8a, 0x44, 0x1d, 0xe0, 0xc0, 0x25, + 0x6c, 0xbc, 0x8b, 0x63, 0x29, 0xf6, 0xba, 0x9e, 0x75, 0x94, 0x70, 0xe4, 0xc6, 0x0d, 0x89, 0x3f, + 0xc2, 0x81, 0x1f, 0xd1, 0x1b, 0x15, 0x27, 0xc4, 0xa1, 0x42, 0xc9, 0x81, 0xbf, 0x81, 0xbc, 0x6b, + 0x37, 0xce, 0x47, 0xa5, 0x5c, 0x92, 0xd9, 0x99, 0xf7, 0xf6, 0xcd, 0xbc, 0x59, 0xa3, 0x2a, 0x1d, + 0xd3, 0x51, 0x18, 0x71, 0xc1, 0x1d, 0x3e, 0xb0, 0x86, 0x24, 0x1e, 0x08, 0x4b, 0x8c, 0x4c, 0x99, + 0xc1, 0x38, 0x5f, 0x34, 0x65, 0x51, 0xaf, 0x38, 0x1c, 0x7c, 0x0e, 0x5d, 0x99, 0xb6, 0xd4, 0x41, + 0xc1, 0xf5, 0x3d, 0x75, 0xb2, 0x7c, 0x70, 0xad, 0xe1, 0x83, 0xe4, 0x2f, 0x2d, 0xdc, 0x9d, 0x13, + 0x81, 0xb8, 0x47, 0x1c, 0x87, 0xc7, 0x81, 0x80, 0x5c, 0x9c, 0x42, 0x6b, 0x2b, 0xfa, 0x09, 0x49, + 0x44, 0xfc, 0x4c, 0xc4, 0x58, 0x01, 0x90, 0xbf, 0x69, 0x7d, 0xd7, 0xe5, 0x2e, 0x57, 0xcd, 0x25, + 0x91, 0xca, 0xd6, 0xbf, 0x6c, 0xa0, 0x9b, 0x6d, 0x70, 0x5f, 0xb0, 0x90, 0x83, 0x27, 0xde, 0xf0, + 0x77, 0x09, 0x03, 0x1f, 0xa1, 0x6b, 0x92, 0xda, 0xf5, 0x68, 0x59, 0xdb, 0xd7, 0x0e, 0xae, 0x1f, + 0x56, 0xcd, 0xe5, 0x91, 0x4d, 0x09, 0x6e, 0x51, 0xfb, 0xea, 0x50, 0x05, 0xf8, 0x15, 0xda, 0x99, + 0x35, 0x9e, 0x90, 0x37, 0x24, 0xf9, 0xce, 0x3c, 0x39, 0x37, 0xa7, 0xd9, 0xb9, 0x88, 0x5b, 0xd4, + 0x2e, 0x42, 0xee, 0x84, 0x39, 0x2a, 0x9d, 0xc4, 0x5c, 0xb0, 0xee, 0x49, 0x4c, 0x02, 0x11, 0xfb, + 0x50, 0xde, 0xdc, 0xd7, 0x0e, 0x8a, 0xcd, 0x97, 0xa7, 0xe7, 0xb5, 0xc2, 0x9f, 0xf3, 0xda, 0x33, + 0xd7, 0x13, 0xfd, 0xb8, 0x67, 0x3a, 0xdc, 0xb7, 0xe6, 0x67, 0x7f, 0xd4, 0x70, 0xfa, 0xc4, 0x0b, + 0xac, 0x8b, 0x0c, 0x15, 0xe3, 0x90, 0x81, 0xd9, 0x61, 0x91, 0x47, 0x06, 0xde, 0x27, 0xd2, 0x1b, + 0xb0, 0x56, 0x20, 0xec, 0x1d, 0x79, 0xff, 0x71, 0x7a, 0x7d, 0xbd, 0x8a, 0x2a, 0x4b, 0x56, 0xd8, + 0x0c, 0x42, 0x1e, 0x00, 0xab, 0x7f, 0xd3, 0xd0, 0x8d, 0x36, 0xb8, 0x6f, 0x43, 0x4a, 0x04, 0x7b, + 0x2d, 0x8d, 0xc7, 0x47, 0x68, 0x9b, 0xc4, 0xa2, 0xcf, 0x23, 0x4f, 0x8c, 0xa5, 0x4f, 0xdb, 0xcd, + 0xf2, 0xaf, 0x1f, 0x8d, 0xdd, 0x74, 0xf9, 0xcf, 0x29, 0x8d, 0x18, 0x40, 0x47, 0x44, 0x5e, 0xe0, + 0xda, 0x33, 0x28, 0x7e, 0x82, 0xb6, 0xd4, 0xea, 0x52, 0x7f, 0xf4, 0x55, 0xe6, 0x2a, 0x8d, 0xe6, + 0x95, 0x64, 0x5a, 0x3b, 0xc5, 0x3f, 0x2d, 0x7d, 0xfe, 0xf7, 0xfd, 0xde, 0xec, 0xa6, 0x7a, 0x05, + 0xed, 0x2d, 0x34, 0x95, 0x35, 0x7c, 0xf8, 0x53, 0x43, 0x9b, 0x6d, 0x70, 0xf1, 0x47, 0x54, 0x5a, + 0xd8, 0xee, 0xed, 0x55, 0x72, 0x4b, 0x93, 0xeb, 0x8d, 0xb5, 0x60, 0x99, 0x1e, 0xfe, 0x80, 0x8a, + 0x73, 0xe6, 0xdc, 0xba, 0x84, 0x9e, 0x07, 0xe9, 0xf7, 0xd7, 0x00, 0x65, 0x0a, 0xcd, 0xe3, 0xd3, + 0x89, 0xa1, 0x9d, 0x4d, 0x0c, 0xed, 0xef, 0xc4, 0xd0, 0xbe, 0x4e, 0x8d, 0xc2, 0xd9, 0xd4, 0x28, + 0xfc, 0x9e, 0x1a, 0x85, 0xf7, 0x8f, 0xd7, 0x7f, 0x0a, 0xa3, 0xec, 0x5b, 0x4e, 0x5e, 0x44, 0x6f, + 0x4b, 0xe6, 0x1f, 0xfe, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x78, 0xbf, 0xe1, 0x83, 0xee, 0x03, 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 From b59dd124acb337ee08cdd15ed5231581d2ca0227 Mon Sep 17 00:00:00 2001 From: Tian Date: Tue, 26 Mar 2024 15:29:56 -0400 Subject: [PATCH 08/35] [TRA-135] Implement MsgDepositToVault (#1220) * implement MsgDepositToVault * check vault equity in test * store shares as rational numbers --- .vscode/launch.json | 29 ++ .../src/codegen/dydxprotocol/vault/vault.ts | 44 ++- proto/dydxprotocol/vault/tx.proto | 2 + proto/dydxprotocol/vault/vault.proto | 14 +- protocol/app/module/interface_registry.go | 3 + protocol/lib/metrics/constants.go | 2 + protocol/mocks/Makefile | 1 + protocol/mocks/PerpetualsKeeper.go | 28 ++ protocol/mocks/VaultKeeper.go | 198 +++++++++++ protocol/testutil/constants/vault.go | 17 +- protocol/testutil/encoding/utils.go | 9 +- protocol/testutil/keeper/vault.go | 67 ++++ protocol/x/perpetuals/types/types.go | 4 + .../keeper/msg_server_deposit_to_vault.go | 42 +++ .../msg_server_deposit_to_vault_test.go | 330 ++++++++++++++++++ protocol/x/vault/keeper/orders.go | 4 +- protocol/x/vault/keeper/orders_test.go | 43 +-- protocol/x/vault/keeper/shares.go | 90 ++++- protocol/x/vault/keeper/shares_test.go | 230 ++++++++++-- protocol/x/vault/keeper/vault_info.go | 26 ++ protocol/x/vault/types/errors.go | 26 +- protocol/x/vault/types/expected_keepers.go | 19 + .../x/vault/types/msg_deposit_to_vault.go | 18 + .../vault/types/msg_deposit_to_vault_test.go | 64 ++++ protocol/x/vault/types/num_shares.go | 30 ++ protocol/x/vault/types/num_shares_test.go | 112 ++++++ protocol/x/vault/types/tx.pb.go | 64 ++-- protocol/x/vault/types/types.go | 49 +++ protocol/x/vault/types/vault.pb.go | 91 +++-- protocol/x/vault/types/vault_id.go | 36 +- 30 files changed, 1551 insertions(+), 141 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 protocol/mocks/VaultKeeper.go create mode 100644 protocol/testutil/keeper/vault.go create mode 100644 protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go create mode 100644 protocol/x/vault/keeper/vault_info.go create mode 100644 protocol/x/vault/types/msg_deposit_to_vault_test.go create mode 100644 protocol/x/vault/types/num_shares.go create mode 100644 protocol/x/vault/types/num_shares_test.go create mode 100644 protocol/x/vault/types/types.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..ee1f468e0f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + }, + { + "name": "Debug Go Tests", + "type": "go", + "request": "launch", + "mode": "test", + "program": "${fileDirname}", + "args": ["-test.v"], // Verbose test output + "env": { + "GOPATH": "${workspaceFolder}", + // Any other environment variables + }, + "envFile": "${workspaceFolder}/.env", + "showLog": true + } + ] +} \ 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..cd2e9f4a20 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/vault.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/vault.ts @@ -67,17 +67,29 @@ export interface VaultIdSDKType { number: number; } -/** NumShares represents the number of shares in a vault. */ +/** + * NumShares represents the number of shares in a vault in the + * format of a rational number `numerator / denominator`. + */ export interface NumShares { - /** Number of shares. */ - numShares: Uint8Array; + /** Numerator. */ + numerator: Uint8Array; + /** Denominator. */ + + denominator: Uint8Array; } -/** NumShares represents the number of shares in a vault. */ +/** + * NumShares represents the number of shares in a vault in the + * format of a rational number `numerator / denominator`. + */ export interface NumSharesSDKType { - /** Number of shares. */ - num_shares: Uint8Array; + /** Numerator. */ + numerator: Uint8Array; + /** Denominator. */ + + denominator: Uint8Array; } function createBaseVaultId(): VaultId { @@ -137,14 +149,19 @@ export const VaultId = { function createBaseNumShares(): NumShares { return { - numShares: new Uint8Array() + numerator: new Uint8Array(), + denominator: new Uint8Array() }; } 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); + if (message.numerator.length !== 0) { + writer.uint32(10).bytes(message.numerator); + } + + if (message.denominator.length !== 0) { + writer.uint32(18).bytes(message.denominator); } return writer; @@ -160,7 +177,11 @@ export const NumShares = { switch (tag >>> 3) { case 1: - message.numShares = reader.bytes(); + message.numerator = reader.bytes(); + break; + + case 2: + message.denominator = reader.bytes(); break; default: @@ -174,7 +195,8 @@ export const NumShares = { fromPartial(object: DeepPartial): NumShares { const message = createBaseNumShares(); - message.numShares = object.numShares ?? new Uint8Array(); + message.numerator = object.numerator ?? new Uint8Array(); + message.denominator = object.denominator ?? new Uint8Array(); return message; } diff --git a/proto/dydxprotocol/vault/tx.proto b/proto/dydxprotocol/vault/tx.proto index 0483a50627..9f5b77b146 100644 --- a/proto/dydxprotocol/vault/tx.proto +++ b/proto/dydxprotocol/vault/tx.proto @@ -20,6 +20,8 @@ service Msg { // 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; diff --git a/proto/dydxprotocol/vault/vault.proto b/proto/dydxprotocol/vault/vault.proto index 87b8c9dd5a..6ac6c35932 100644 --- a/proto/dydxprotocol/vault/vault.proto +++ b/proto/dydxprotocol/vault/vault.proto @@ -23,10 +23,18 @@ message VaultId { uint32 number = 2; } -// NumShares represents the number of shares in a vault. +// NumShares represents the number of shares in a vault in the +// format of a rational number `numerator / denominator`. message NumShares { - // Number of shares. - bytes num_shares = 1 [ + // Numerator. + bytes numerator = 1 [ + (gogoproto.customtype) = + "github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt", + (gogoproto.nullable) = false + ]; + + // Denominator. + bytes denominator = 2 [ (gogoproto.customtype) = "github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt", (gogoproto.nullable) = false 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/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/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/PerpetualsKeeper.go b/protocol/mocks/PerpetualsKeeper.go index f729748879..36354b18a4 100644 --- a/protocol/mocks/PerpetualsKeeper.go +++ b/protocol/mocks/PerpetualsKeeper.go @@ -251,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/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/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/x/perpetuals/types/types.go b/protocol/x/perpetuals/types/types.go index 86c96c31fa..0fec3426c9 100644 --- a/protocol/x/perpetuals/types/types.go +++ b/protocol/x/perpetuals/types/types.go @@ -115,6 +115,10 @@ type PerpetualsKeeper interface { id uint32, marketType PerpetualMarketType, ) (Perpetual, error) + GetPerpetual( + ctx sdk.Context, + id uint32, + ) (Perpetual, error) GetAllPerpetuals( ctx sdk.Context, ) []Perpetual 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..16e120924b --- /dev/null +++ b/protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go @@ -0,0 +1,330 @@ +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 +} + +// 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.Rat + // 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, + }, + { + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(321), + msgSigner: constants.Alice_Num0.Owner, + }, + }, + totalSharesHistory: []*big.Rat{ + big.NewRat(123, 1), + big.NewRat(444, 1), + }, + 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, + }, + { + depositor: constants.Bob_Num1, + depositAmount: big.NewInt(500), + msgSigner: constants.Bob_Num1.Owner, + }, + }, + totalSharesHistory: []*big.Rat{ + big.NewRat(1_000, 1), + big.NewRat(1_500, 1), + }, + 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, + }, + { + depositor: constants.Bob_Num1, + depositAmount: big.NewInt(501), // Greater than balance. + msgSigner: constants.Bob_Num1.Owner, + deliverTxFails: true, + }, + }, + totalSharesHistory: []*big.Rat{ + big.NewRat(1_000, 1), + big.NewRat(1_000, 1), + }, + 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", + }, + { + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(1_000), + msgSigner: constants.Alice_Num0.Owner, + }, + }, + totalSharesHistory: []*big.Rat{ + big.NewRat(0, 1), + big.NewRat(1_000, 1), + }, + 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", + }, + { + depositor: constants.Bob_Num0, + depositAmount: big.NewInt(-1), + msgSigner: constants.Bob_Num0.Owner, + checkTxFails: true, + checkTxResponseContains: "Deposit amount is invalid", + }, + }, + totalSharesHistory: []*big.Rat{ + big.NewRat(0, 1), + big.NewRat(0, 1), + }, + 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.BigRatToNumShares(tc.totalSharesHistory[i]), + totalShares, + ) + // 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/orders.go b/protocol/x/vault/keeper/orders.go index 12c87926d9..a1f2610bac 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" @@ -45,7 +44,8 @@ func (k Keeper) RefreshAllVaultOrders(ctx sdk.Context) { k.cdc.MustUnmarshal(totalSharesIterator.Value(), &totalShares) // Skip if TotalShares is non-positive. - if totalShares.NumShares.Cmp(dtypes.NewInt(0)) <= 0 { + totalSharesRat, err := totalShares.ToBigRat() + if err != nil || totalSharesRat.Sign() <= 0 { continue } diff --git a/protocol/x/vault/keeper/orders_test.go b/protocol/x/vault/keeper/orders_test.go index 0ce05c600c..327c3b6734 100644 --- a/protocol/x/vault/keeper/orders_test.go +++ b/protocol/x/vault/keeper/orders_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "math" + "math/big" "testing" "github.com/cometbft/cometbft/types" @@ -29,20 +30,16 @@ func TestRefreshAllVaultOrders(t *testing.T) { // Vault IDs. vaultIds []vaulttypes.VaultId // Total Shares of each vault ID above. - totalShares []vaulttypes.NumShares + totalShares []*big.Rat }{ "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.Rat{ + big.NewRat(1_000, 1), + big.NewRat(200, 1), }, }, "Two Vaults, One Positive Shares, One Zero Shares": { @@ -50,13 +47,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.Rat{ + big.NewRat(1_000, 1), + big.NewRat(0, 1), }, }, "Two Vaults, Both Zero Shares": { @@ -64,13 +57,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.Rat{ + big.NewRat(0, 1), + big.NewRat(0, 1), }, }, } @@ -105,7 +94,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.BigRatToNumShares(tc.totalShares[i]), + ) require.NoError(t, err) } @@ -116,7 +109,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 +135,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) diff --git a/protocol/x/vault/keeper/shares.go b/protocol/x/vault/keeper/shares.go index de89060ad5..20045fb1c0 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,18 @@ 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 { + totalSharesRat, err := totalShares.ToBigRat() + if err != nil { + return err + } + if totalSharesRat.Sign() < 0 { return types.ErrNegativeShares } @@ -38,6 +45,13 @@ func (k Keeper) SetTotalShares( totalSharesStore := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.TotalSharesKeyPrefix)) totalSharesStore.Set(vaultId.ToStateKey(), b) + // Emit metric on TotalShares. + totalSharesFloat, _ := totalSharesRat.Float32() + vaultId.SetGaugeWithLabels( + metrics.TotalShares, + totalSharesFloat, + ) + return nil } @@ -47,3 +61,73 @@ func (k Keeper) getTotalSharesIterator(ctx sdk.Context) storetypes.Iterator { return storetypes.KVStorePrefixIterator(store, []byte{}) } + +// 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.Cmp(big.NewInt(0)) <= 0 { + return types.ErrInvalidDepositAmount + } + // Get existing TotalShares of the vault. + totalShares, exists := k.GetTotalShares(ctx, vaultId) + var existingTotalShares *big.Rat + var err error + if exists { + existingTotalShares, err = totalShares.ToBigRat() + if err != nil { + return err + } + } else { + existingTotalShares = new(big.Rat).SetInt(big.NewInt(0)) + } + // Calculate shares to mint. + var sharesToMint *big.Rat + if !exists || existingTotalShares.Sign() <= 0 { + // Mint `quoteQuantums` number of shares. + sharesToMint = new(big.Rat).SetInt(quantumsToDeposit) + // Initialize existingTotalShares as 0. + existingTotalShares = new(big.Rat).SetInt(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.Cmp(big.NewInt(0)) <= 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.Rat).SetInt(quantumsToDeposit) + sharesToMint = sharesToMint.Mul(sharesToMint, existingTotalShares) + sharesToMint = sharesToMint.Quo(sharesToMint, new(big.Rat).SetInt(equity)) + } + + // Increase TotalShares of the vault. + err = k.SetTotalShares( + ctx, + vaultId, + types.BigRatToNumShares( + existingTotalShares.Add(existingTotalShares, sharesToMint), + ), + ) + if err != nil { + return err + } + + // TODO (TRA-170): Increase owner shares. + + return nil +} diff --git a/protocol/x/vault/keeper/shares_test.go b/protocol/x/vault/keeper/shares_test.go index 06454b0b80..0b7accc856 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,221 @@ 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.BigRatToNumShares( + big.NewRat(7, 1), + ) + 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.BigRatToNumShares( + big.NewRat(456, 3), + ) + 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.BigRatToNumShares( + big.NewRat(0, 1), + ) + 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.BigRatToNumShares( + big.NewRat(73423, 59), + ) + 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) + numShares = vaulttypes.BigRatToNumShares( + big.NewRat(-1, 1), + ) + err = k.SetTotalShares(ctx, constants.Vault_Clob_0, numShares) + 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, + vaulttypes.BigRatToNumShares( + big.NewRat(73423, 59), + ), + 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.Rat + // Quote quantums to deposit. + quantumsToDeposit *big.Int + + /* --- Expectations --- */ + // Expected TotalShares after minting. + expectedTotalShares *big.Rat + // Expected error. + expectedErr error + }{ + "Equity 0, TotalShares 0, Deposit 1000": { + vaultId: constants.Vault_Clob_0, + equity: big.NewInt(0), + totalShares: big.NewRat(0, 1), + quantumsToDeposit: big.NewInt(1_000), + // Should mint `1_000 / 1` shares. + expectedTotalShares: big.NewRat(1_000, 1), + }, + "Equity 0, TotalShares non-existent, Deposit 12345654321": { + vaultId: constants.Vault_Clob_0, + equity: big.NewInt(0), + quantumsToDeposit: big.NewInt(12_345_654_321), + // Should mint `12_345_654_321 / 1` shares. + expectedTotalShares: big.NewRat(12_345_654_321, 1), + }, + "Equity 1000, TotalShares non-existent, Deposit 500": { + vaultId: constants.Vault_Clob_0, + equity: big.NewInt(1_000), + quantumsToDeposit: big.NewInt(500), + // Should mint `500 / 1` shares. + expectedTotalShares: big.NewRat(500, 1), + }, + "Equity 4000, TotalShares 5000, Deposit 1000": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(4_000), + totalShares: big.NewRat(5_000, 1), + quantumsToDeposit: big.NewInt(1_000), + // Should mint `1_250 / 1` shares. + expectedTotalShares: big.NewRat(6_250, 1), + }, + "Equity 1_000_000, TotalShares 1, Deposit 1": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(1_000_000), + totalShares: big.NewRat(1, 1), + quantumsToDeposit: big.NewInt(1), + // Should mint `1 / 1_000_000` shares. + expectedTotalShares: big.NewRat(1_000_001, 1_000_000), + }, + "Equity 8000, TotalShares 4000, Deposit 455": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(8_000), + totalShares: big.NewRat(4_000, 1), + quantumsToDeposit: big.NewInt(455), + // Should mint `455 / 2` shares. + expectedTotalShares: big.NewRat(8_455, 2), + }, + "Equity 123456, TotalShares 654321, Deposit 123456789": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(123_456), + totalShares: big.NewRat(654_321, 1), + quantumsToDeposit: big.NewInt(123_456_789), + // Should mint `26_926_789_878_423 / 41_152` shares. + expectedTotalShares: big.NewRat(26_953_716_496_215, 41_152), + }, + "Equity -1, TotalShares 10, Deposit 1": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(-1), + totalShares: big.NewRat(10, 1), + quantumsToDeposit: big.NewInt(1), + expectedErr: vaulttypes.ErrNonPositiveEquity, + }, + "Equity 1, TotalShares 1, Deposit 0": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(1), + totalShares: big.NewRat(1, 1), + quantumsToDeposit: big.NewInt(0), + expectedErr: vaulttypes.ErrInvalidDepositAmount, + }, + "Equity 0, TotalShares non-existent, Deposit -1": { + vaultId: constants.Vault_Clob_1, + equity: big.NewInt(0), + 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.BigRatToNumShares(tc.totalShares), + ) + require.NoError(t, err) + } + + // Mint shares. + err := tApp.App.VaultKeeper.MintShares( + ctx, + tc.vaultId, + "", // TODO (TRA-170): Increase owner shares. + 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.BigRatToNumShares(tc.totalShares), + totalShares, + ) + } 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.BigRatToNumShares(tc.expectedTotalShares), + totalShares, + ) + } + }) + } } diff --git a/protocol/x/vault/keeper/vault_info.go b/protocol/x/vault/keeper/vault_info.go new file mode 100644 index 0000000000..164785b2e5 --- /dev/null +++ b/protocol/x/vault/keeper/vault_info.go @@ -0,0 +1,26 @@ +package keeper + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + 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 +} diff --git a/protocol/x/vault/types/errors.go b/protocol/x/vault/types/errors.go index 0a84bc73d6..9059a4d9e1 100644 --- a/protocol/x/vault/types/errors.go +++ b/protocol/x/vault/types/errors.go @@ -20,19 +20,39 @@ var ( 3, "MarketParam not found", ) - ErrInvalidOrderSizePpm = errorsmod.Register( + 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", + ) + ErrInvalidOrderSizePpm = errorsmod.Register( + ModuleName, + 8, "OrderSizePpm must be strictly greater than 0", ) ErrInvalidOrderExpirationSeconds = errorsmod.Register( ModuleName, - 5, + 9, "OrderExpirationSeconds must be strictly greater than 0", ) ErrInvalidSpreadMinPpm = errorsmod.Register( ModuleName, - 6, + 10, "SpreadMinPpm must be strictly greater than 0", ) ) diff --git a/protocol/x/vault/types/expected_keepers.go b/protocol/x/vault/types/expected_keepers.go index af96e94a2c..fda97ee6b4 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,20 @@ type PricesKeeper interface { } type SubaccountsKeeper interface { + GetNetCollateralAndMarginRequirements( + ctx sdk.Context, + update satypes.Update, + ) ( + bigNetCollateral *big.Int, + bigInitialMargin *big.Int, + bigMaintenanceMargin *big.Int, + err error, + ) + TransferFundsFromSubaccountToSubaccount( + ctx sdk.Context, + senderSubaccountId satypes.SubaccountId, + recipientSubaccountId satypes.SubaccountId, + assetId uint32, + quantums *big.Int, + ) (err error) } 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..0a540bb281 --- /dev/null +++ b/protocol/x/vault/types/num_shares.go @@ -0,0 +1,30 @@ +package types + +import ( + "math/big" + + "github.com/dydxprotocol/v4-chain/protocol/dtypes" +) + +// ToBigRat converts a NumShares to a big.Rat. +// Returns an error if the denominator is zero. +func (n NumShares) ToBigRat() (*big.Rat, error) { + if n.Numerator.IsNil() || n.Denominator.IsNil() { + return nil, ErrNilFraction + } + if n.Denominator.Cmp(dtypes.NewInt(0)) == 0 { + return nil, ErrZeroDenominator + } + + return new(big.Rat).SetFrac(n.Numerator.BigInt(), n.Denominator.BigInt()), nil +} + +func BigRatToNumShares(rat *big.Rat) (n NumShares) { + if rat == nil { + return n + } + return NumShares{ + Numerator: dtypes.NewIntFromBigInt(rat.Num()), + Denominator: dtypes.NewIntFromBigInt(rat.Denom()), + } +} 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..3bb15932ff --- /dev/null +++ b/protocol/x/vault/types/num_shares_test.go @@ -0,0 +1,112 @@ +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 TestNumShares_ToBigRat(t *testing.T) { + tests := map[string]struct { + n types.NumShares + expectedRat *big.Rat + expectedErr error + }{ + "Success - 1/1": { + n: types.NumShares{ + Numerator: dtypes.NewInt(1), + Denominator: dtypes.NewInt(1), + }, + expectedRat: big.NewRat(1, 1), + }, + "Success - 1/77": { + n: types.NumShares{ + Numerator: dtypes.NewInt(1), + Denominator: dtypes.NewInt(77), + }, + expectedRat: big.NewRat(1, 77), + }, + "Success - 99/2": { + n: types.NumShares{ + Numerator: dtypes.NewInt(99), + Denominator: dtypes.NewInt(2), + }, + expectedRat: big.NewRat(99, 2), + }, + "Failure - numerator is nil": { + n: types.NumShares{ + Denominator: dtypes.NewInt(77), + }, + expectedErr: types.ErrNilFraction, + }, + "Failure - denominator is nil": { + n: types.NumShares{ + Numerator: dtypes.NewInt(77), + }, + expectedErr: types.ErrNilFraction, + }, + "Failure - denominator is 0": { + n: types.NumShares{ + Numerator: dtypes.NewInt(77), + Denominator: dtypes.NewInt(0), + }, + expectedErr: types.ErrZeroDenominator, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + rat, err := tc.n.ToBigRat() + if tc.expectedErr == nil { + require.NoError(t, err) + require.Equal(t, tc.expectedRat, rat) + } else { + require.ErrorIs(t, err, tc.expectedErr) + require.True(t, rat == nil) + } + }) + } +} + +func TestBigRatToNumShares(t *testing.T) { + tests := map[string]struct { + rat *big.Rat + expectedNumShares types.NumShares + }{ + "Success - 1/1": { + rat: big.NewRat(1, 1), + expectedNumShares: types.NumShares{ + Numerator: dtypes.NewInt(1), + Denominator: dtypes.NewInt(1), + }, + }, + "Success - 1/2": { + rat: big.NewRat(1, 2), + expectedNumShares: types.NumShares{ + Numerator: dtypes.NewInt(1), + Denominator: dtypes.NewInt(2), + }, + }, + "Success - 5/3": { + rat: big.NewRat(5, 3), + expectedNumShares: types.NumShares{ + Numerator: dtypes.NewInt(5), + Denominator: dtypes.NewInt(3), + }, + }, + "Success - rat is nil": { + rat: nil, + expectedNumShares: types.NumShares{}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + n := types.BigRatToNumShares(tc.rat) + require.Equal(t, tc.expectedNumShares, n) + }) + } +} diff --git a/protocol/x/vault/types/tx.pb.go b/protocol/x/vault/types/tx.pb.go index f9c5649006..8aa97ebfbc 100644 --- a/protocol/x/vault/types/tx.pb.go +++ b/protocol/x/vault/types/tx.pb.go @@ -227,39 +227,39 @@ func init() { func init() { proto.RegisterFile("dydxprotocol/vault/tx.proto", fileDescriptor_ced574c6017ce006) } var fileDescriptor_ced574c6017ce006 = []byte{ - // 497 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0x4d, 0x6f, 0xd3, 0x40, + // 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, 0x71, 0x82, 0x88, 0x03, 0x11, 0x8a, 0x44, 0x1d, 0xe0, 0xc0, 0x25, - 0x6c, 0xbc, 0x8b, 0x63, 0x29, 0xf6, 0xba, 0x9e, 0x75, 0x94, 0x70, 0xe4, 0xc6, 0x0d, 0x89, 0x3f, - 0xc2, 0x81, 0x1f, 0xd1, 0x1b, 0x15, 0x27, 0xc4, 0xa1, 0x42, 0xc9, 0x81, 0xbf, 0x81, 0xbc, 0x6b, - 0x37, 0xce, 0x47, 0xa5, 0x5c, 0x92, 0xd9, 0x99, 0xf7, 0xf6, 0xcd, 0xbc, 0x59, 0xa3, 0x2a, 0x1d, - 0xd3, 0x51, 0x18, 0x71, 0xc1, 0x1d, 0x3e, 0xb0, 0x86, 0x24, 0x1e, 0x08, 0x4b, 0x8c, 0x4c, 0x99, - 0xc1, 0x38, 0x5f, 0x34, 0x65, 0x51, 0xaf, 0x38, 0x1c, 0x7c, 0x0e, 0x5d, 0x99, 0xb6, 0xd4, 0x41, - 0xc1, 0xf5, 0x3d, 0x75, 0xb2, 0x7c, 0x70, 0xad, 0xe1, 0x83, 0xe4, 0x2f, 0x2d, 0xdc, 0x9d, 0x13, - 0x81, 0xb8, 0x47, 0x1c, 0x87, 0xc7, 0x81, 0x80, 0x5c, 0x9c, 0x42, 0x6b, 0x2b, 0xfa, 0x09, 0x49, - 0x44, 0xfc, 0x4c, 0xc4, 0x58, 0x01, 0x90, 0xbf, 0x69, 0x7d, 0xd7, 0xe5, 0x2e, 0x57, 0xcd, 0x25, - 0x91, 0xca, 0xd6, 0xbf, 0x6c, 0xa0, 0x9b, 0x6d, 0x70, 0x5f, 0xb0, 0x90, 0x83, 0x27, 0xde, 0xf0, - 0x77, 0x09, 0x03, 0x1f, 0xa1, 0x6b, 0x92, 0xda, 0xf5, 0x68, 0x59, 0xdb, 0xd7, 0x0e, 0xae, 0x1f, - 0x56, 0xcd, 0xe5, 0x91, 0x4d, 0x09, 0x6e, 0x51, 0xfb, 0xea, 0x50, 0x05, 0xf8, 0x15, 0xda, 0x99, - 0x35, 0x9e, 0x90, 0x37, 0x24, 0xf9, 0xce, 0x3c, 0x39, 0x37, 0xa7, 0xd9, 0xb9, 0x88, 0x5b, 0xd4, - 0x2e, 0x42, 0xee, 0x84, 0x39, 0x2a, 0x9d, 0xc4, 0x5c, 0xb0, 0xee, 0x49, 0x4c, 0x02, 0x11, 0xfb, - 0x50, 0xde, 0xdc, 0xd7, 0x0e, 0x8a, 0xcd, 0x97, 0xa7, 0xe7, 0xb5, 0xc2, 0x9f, 0xf3, 0xda, 0x33, - 0xd7, 0x13, 0xfd, 0xb8, 0x67, 0x3a, 0xdc, 0xb7, 0xe6, 0x67, 0x7f, 0xd4, 0x70, 0xfa, 0xc4, 0x0b, - 0xac, 0x8b, 0x0c, 0x15, 0xe3, 0x90, 0x81, 0xd9, 0x61, 0x91, 0x47, 0x06, 0xde, 0x27, 0xd2, 0x1b, - 0xb0, 0x56, 0x20, 0xec, 0x1d, 0x79, 0xff, 0x71, 0x7a, 0x7d, 0xbd, 0x8a, 0x2a, 0x4b, 0x56, 0xd8, - 0x0c, 0x42, 0x1e, 0x00, 0xab, 0x7f, 0xd3, 0xd0, 0x8d, 0x36, 0xb8, 0x6f, 0x43, 0x4a, 0x04, 0x7b, - 0x2d, 0x8d, 0xc7, 0x47, 0x68, 0x9b, 0xc4, 0xa2, 0xcf, 0x23, 0x4f, 0x8c, 0xa5, 0x4f, 0xdb, 0xcd, - 0xf2, 0xaf, 0x1f, 0x8d, 0xdd, 0x74, 0xf9, 0xcf, 0x29, 0x8d, 0x18, 0x40, 0x47, 0x44, 0x5e, 0xe0, - 0xda, 0x33, 0x28, 0x7e, 0x82, 0xb6, 0xd4, 0xea, 0x52, 0x7f, 0xf4, 0x55, 0xe6, 0x2a, 0x8d, 0xe6, - 0x95, 0x64, 0x5a, 0x3b, 0xc5, 0x3f, 0x2d, 0x7d, 0xfe, 0xf7, 0xfd, 0xde, 0xec, 0xa6, 0x7a, 0x05, - 0xed, 0x2d, 0x34, 0x95, 0x35, 0x7c, 0xf8, 0x53, 0x43, 0x9b, 0x6d, 0x70, 0xf1, 0x47, 0x54, 0x5a, - 0xd8, 0xee, 0xed, 0x55, 0x72, 0x4b, 0x93, 0xeb, 0x8d, 0xb5, 0x60, 0x99, 0x1e, 0xfe, 0x80, 0x8a, - 0x73, 0xe6, 0xdc, 0xba, 0x84, 0x9e, 0x07, 0xe9, 0xf7, 0xd7, 0x00, 0x65, 0x0a, 0xcd, 0xe3, 0xd3, - 0x89, 0xa1, 0x9d, 0x4d, 0x0c, 0xed, 0xef, 0xc4, 0xd0, 0xbe, 0x4e, 0x8d, 0xc2, 0xd9, 0xd4, 0x28, - 0xfc, 0x9e, 0x1a, 0x85, 0xf7, 0x8f, 0xd7, 0x7f, 0x0a, 0xa3, 0xec, 0x5b, 0x4e, 0x5e, 0x44, 0x6f, - 0x4b, 0xe6, 0x1f, 0xfe, 0x0f, 0x00, 0x00, 0xff, 0xff, 0x78, 0xbf, 0xe1, 0x83, 0xee, 0x03, 0x00, - 0x00, + 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. 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..54d9648e45 100644 --- a/protocol/x/vault/types/vault.pb.go +++ b/protocol/x/vault/types/vault.pb.go @@ -107,10 +107,13 @@ func (m *VaultId) GetNumber() uint32 { return 0 } -// NumShares represents the number of shares in a vault. +// NumShares represents the number of shares in a vault in the +// format of a rational number `numerator / denominator`. 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"` + // Numerator. + Numerator github_com_dydxprotocol_v4_chain_protocol_dtypes.SerializableInt `protobuf:"bytes,1,opt,name=numerator,proto3,customtype=github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt" json:"numerator"` + // Denominator. + Denominator github_com_dydxprotocol_v4_chain_protocol_dtypes.SerializableInt `protobuf:"bytes,2,opt,name=denominator,proto3,customtype=github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt" json:"denominator"` } func (m *NumShares) Reset() { *m = NumShares{} } @@ -155,7 +158,7 @@ func init() { func init() { proto.RegisterFile("dydxprotocol/vault/vault.proto", fileDescriptor_32accb5830bb2860) } var fileDescriptor_32accb5830bb2860 = []byte{ - // 300 bytes of a gzipped FileDescriptorProto + // 320 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, 0x81, 0x90, 0x7a, 0x60, 0x41, 0x21, 0x21, 0x64, 0x79, 0x3d, 0xb0, 0x8c, 0x94, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, @@ -163,18 +166,19 @@ var fileDescriptor_32accb5830bb2860 = []byte{ 0x19, 0x72, 0xb1, 0x94, 0x54, 0x16, 0xa4, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x19, 0xc9, 0xea, 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, + 0xdd, 0x67, 0xe4, 0xe2, 0xf4, 0x2b, 0xcd, 0x0d, 0xce, 0x48, 0x2c, 0x4a, 0x2d, 0x16, 0x4a, 0xe3, + 0xe2, 0xcc, 0x2b, 0xcd, 0x4d, 0x2d, 0x4a, 0x2c, 0xc9, 0x2f, 0x02, 0x9b, 0xce, 0xe3, 0xe4, 0x71, + 0xe2, 0x9e, 0x3c, 0xc3, 0xad, 0x7b, 0xf2, 0x0e, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, + 0xf9, 0xb9, 0xfa, 0xa8, 0x7e, 0x32, 0xd1, 0x4d, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x87, 0x8b, 0xa4, + 0x80, 0x6c, 0x2c, 0xd6, 0x0b, 0x4e, 0x2d, 0xca, 0x4c, 0xcc, 0xc9, 0xac, 0x4a, 0x4c, 0xca, 0x49, + 0xf5, 0xcc, 0x2b, 0x09, 0x42, 0x18, 0x2d, 0x94, 0xc5, 0xc5, 0x9d, 0x92, 0x9a, 0x97, 0x9f, 0x9b, + 0x99, 0x07, 0xb6, 0x89, 0x89, 0xca, 0x36, 0x21, 0x1b, 0xae, 0x65, 0xc3, 0xc5, 0x09, 0x0f, 0x0c, + 0x21, 0x29, 0x2e, 0xb1, 0x30, 0xc7, 0x50, 0x9f, 0x90, 0xf8, 0x90, 0xc8, 0x00, 0xd7, 0xf8, 0x50, + 0xbf, 0xe0, 0x00, 0x57, 0x67, 0x4f, 0x37, 0x4f, 0x57, 0x17, 0x01, 0x06, 0x21, 0x61, 0x2e, 0x7e, + 0x24, 0x39, 0x67, 0x1f, 0x7f, 0x27, 0x01, 0x46, 0xa7, 0xc0, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, + 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, + 0x3c, 0x96, 0x63, 0x88, 0x32, 0x27, 0xde, 0x99, 0x15, 0xd0, 0x88, 0x07, 0xbb, 0x36, 0x89, 0x0d, + 0x2c, 0x6e, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x76, 0x09, 0x36, 0xbc, 0x1b, 0x02, 0x00, 0x00, } func (m *VaultId) Marshal() (dAtA []byte, err error) { @@ -231,9 +235,19 @@ func (m *NumShares) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l { - size := m.NumShares.Size() + size := m.Denominator.Size() i -= size - if _, err := m.NumShares.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.Denominator.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintVault(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.Numerator.Size() + i -= size + if _, err := m.Numerator.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintVault(dAtA, i, uint64(size)) @@ -275,7 +289,9 @@ func (m *NumShares) Size() (n int) { } var l int _ = l - l = m.NumShares.Size() + l = m.Numerator.Size() + n += 1 + l + sovVault(uint64(l)) + l = m.Denominator.Size() n += 1 + l + sovVault(uint64(l)) return n } @@ -405,7 +421,40 @@ func (m *NumShares) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NumShares", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Numerator", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowVault + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthVault + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthVault + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Numerator.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denominator", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -432,7 +481,7 @@ func (m *NumShares) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.NumShares.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Denominator.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/protocol/x/vault/types/vault_id.go b/protocol/x/vault/types/vault_id.go index b4fd10a681..aae07e82be 100644 --- a/protocol/x/vault/types/vault_id.go +++ b/protocol/x/vault/types/vault_id.go @@ -36,8 +36,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 +74,4 @@ func (id *VaultId) IncrCounterWithLabels(metricName string, labels ...metrics.La int(id.Number), ), ) - - metrics.IncrCounterWithLabels( - metricName, - 1, - labels..., - ) } From da25f0f4c65c352703e0bd360827cca099357eca Mon Sep 17 00:00:00 2001 From: shrenujb <98204323+shrenujb@users.noreply.github.com> Date: Tue, 26 Mar 2024 15:52:45 -0400 Subject: [PATCH 09/35] [TRA-106] Implement /transfers/parentSubaccountNumber indexer API (#1218) Signed-off-by: Shrenuj Bansal --- .../api/v4/transfers-controller.test.ts | 421 +++++++++++++++++- .../comlink/public/api-documentation.md | 128 ++++++ indexer/services/comlink/public/swagger.json | 78 ++++ .../api/v4/transfers-controller.ts | 167 ++++++- .../request-helpers/request-transformer.ts | 57 ++- indexer/services/comlink/src/types.ts | 26 ++ 6 files changed, 871 insertions(+), 6 deletions(-) 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/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index d1af0c5567..6de8e7a885 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -2087,6 +2087,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 @@ -3973,3 +4063,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 9efb4d4804..de306d3bcb 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -1170,6 +1170,21 @@ ], "type": "object", "additionalProperties": false + }, + "ParentSubaccountTransferResponse": { + "properties": { + "transfers": { + "items": { + "$ref": "#/components/schemas/TransferResponseObject" + }, + "type": "array" + } + }, + "required": [ + "transfers" + ], + "type": "object", + "additionalProperties": false } }, "securitySchemes": {} @@ -2300,6 +2315,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/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/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 256f9179ea..bb4402fa3f 100644 --- a/indexer/services/comlink/src/types.ts +++ b/indexer/services/comlink/src/types.ts @@ -165,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 { @@ -353,6 +375,10 @@ export interface AssetPositionRequest extends SubaccountRequest {} export interface TransferRequest extends SubaccountRequest, LimitAndCreatedBeforeRequest {} +export interface ParentSubaccountTransferRequest + extends ParentSubaccountRequest, LimitAndCreatedBeforeRequest { +} + export interface FillRequest extends SubaccountRequest, LimitAndCreatedBeforeRequest { market: string, marketType: MarketType, From 250ec10296fc713ef7e21e031ef91153a3e20d00 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Tue, 26 Mar 2024 13:07:17 -0700 Subject: [PATCH 10/35] [SKI-16] Slinky Dockerfile Removal (#1245) * Fix slinky exec rename * Remove slinky from docker build * Remove extra space * Don't touch local * Don't touch docker compose --- protocol/Dockerfile | 8 -------- 1 file changed, 8 deletions(-) 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 From c9e9da6cbbae044a69b770414b60bb50b74ae77f Mon Sep 17 00:00:00 2001 From: roy-dydx <133032749+roy-dydx@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:08:19 -0400 Subject: [PATCH 11/35] Store pruneable orders in a key-per-order format rather than key-per-height (#1230) * Store pruneable orders in a key-per-order format rather than key-per-height * Address comment * log and lint --- protocol/app/upgrades/v5.0.0/upgrade.go | 4 + protocol/mocks/ClobKeeper.go | 5 + protocol/x/clob/abci_test.go | 29 ---- protocol/x/clob/keeper/order_state.go | 82 +++++++---- protocol/x/clob/keeper/order_state_test.go | 164 ++++++++------------- protocol/x/clob/keeper/orders_test.go | 8 +- protocol/x/clob/types/clob_keeper.go | 1 + protocol/x/clob/types/keys.go | 10 +- protocol/x/clob/types/keys_test.go | 2 +- 9 files changed, 134 insertions(+), 171 deletions(-) diff --git a/protocol/app/upgrades/v5.0.0/upgrade.go b/protocol/app/upgrades/v5.0.0/upgrade.go index bd5ccbbb11..1833d37b18 100644 --- a/protocol/app/upgrades/v5.0.0/upgrade.go +++ b/protocol/app/upgrades/v5.0.0/upgrade.go @@ -157,6 +157,10 @@ func CreateUpgradeHandler( 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) diff --git a/protocol/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index 1b2fd17fa4..8dbd200dc3 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) 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/keeper/order_state.go b/protocol/x/clob/keeper/order_state.go index 814684e141..a5e99c557b 100644 --- a/protocol/x/clob/keeper/order_state.go +++ b/protocol/x/clob/keeper/order_state.go @@ -1,6 +1,9 @@ package keeper import ( + "bytes" + "encoding/binary" + "cosmossdk.io/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/dydxprotocol/v4-chain/protocol/lib" @@ -125,14 +128,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 +210,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 - } + potentiallyPrunableOrdersStore := k.GetPruneableOrdersStore(ctx, blockHeight) + it := potentiallyPrunableOrdersStore.Iterator(nil, nil) + defer it.Close() - var potentiallyPrunableOrders types.PotentiallyPrunableOrders - k.cdc.MustUnmarshal(potentiallyPrunableOrderBytes, &potentiallyPrunableOrders) - - 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 +230,31 @@ 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.Value()) + var potentiallyPrunableOrders types.PotentiallyPrunableOrders + k.cdc.MustUnmarshal(it.Value(), &potentiallyPrunableOrders) + k.AddOrdersForPruning(ctx, potentiallyPrunableOrders.OrderIds, height) + } } // RemoveOrderFillAmount removes the fill amount of an Order from state and the memstore. diff --git a/protocol/x/clob/keeper/order_state_test.go b/protocol/x/clob/keeper/order_state_test.go index 1ddf8e6026..6ccad5a87b 100644 --- a/protocol/x/clob/keeper/order_state_test.go +++ b/protocol/x/clob/keeper/order_state_test.go @@ -1,12 +1,9 @@ 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 +259,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 +475,70 @@ 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.AddOrdersForPruning( + ks.Ctx, + ordersA, + 10, + ) + ks.ClobKeeper.AddOrdersForPruning( + ks.Ctx, + ordersB, + 100, + ) + + ks.ClobKeeper.MigratePruneableOrders(ks.Ctx) + + getPostMigrationOrdersAtHeight := func(height uint32) []types.OrderId { + postMigrationOrders := []types.OrderId{} + storeA := ks.ClobKeeper.GetPruneableOrdersStore(ks.Ctx, height) + it := storeA.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)) +} + 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 521e0b8f85..da10a4b948 100644 --- a/protocol/x/clob/keeper/orders_test.go +++ b/protocol/x/clob/keeper/orders_test.go @@ -141,13 +141,13 @@ 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 @@ -505,13 +505,13 @@ 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 diff --git a/protocol/x/clob/types/clob_keeper.go b/protocol/x/clob/types/clob_keeper.go index d96b046ad5..8d8c966de0 100644 --- a/protocol/x/clob/types/clob_keeper.go +++ b/protocol/x/clob/types/clob_keeper.go @@ -150,4 +150,5 @@ type ClobKeeper interface { 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) } From 74cb6a6cb3aa5fab0a6c5461a9e1dc42f04b499f Mon Sep 17 00:00:00 2001 From: Tian Date: Tue, 26 Mar 2024 16:35:25 -0400 Subject: [PATCH 12/35] ignore .vscode (#1249) --- .gitignore | 1 + .vscode/launch.json | 29 ----------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 .vscode/launch.json 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/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index ee1f468e0f..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch Package", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}" - }, - { - "name": "Debug Go Tests", - "type": "go", - "request": "launch", - "mode": "test", - "program": "${fileDirname}", - "args": ["-test.v"], // Verbose test output - "env": { - "GOPATH": "${workspaceFolder}", - // Any other environment variables - }, - "envFile": "${workspaceFolder}/.env", - "showLog": true - } - ] -} \ No newline at end of file From 572244641e56a6446707f913a4f2c0ce7c6f9fa9 Mon Sep 17 00:00:00 2001 From: jayy04 <103467857+jayy04@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:37:12 -0400 Subject: [PATCH 13/35] [CT-712] send order update when short term order state fill amounts are pruned (#1241) --- protocol/x/clob/keeper/order_state.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/protocol/x/clob/keeper/order_state.go b/protocol/x/clob/keeper/order_state.go index a5e99c557b..a72340b176 100644 --- a/protocol/x/clob/keeper/order_state.go +++ b/protocol/x/clob/keeper/order_state.go @@ -284,5 +284,22 @@ 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 { + orderbookUpdate := k.MemClob.GetOrderbookUpdatesForOrderUpdate(ctx, orderId) + allUpdates.Append(orderbookUpdate) + } + } + k.SendOrderbookUpdates(allUpdates, false) + } } From 9dd90ffa52750fb9d2f7880ea5da822cf16afb1f Mon Sep 17 00:00:00 2001 From: shrenujb <98204323+shrenujb@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:16:42 -0400 Subject: [PATCH 14/35] [TRA-102] Add API to get asset positions for parent subaccount (#1243) Signed-off-by: Shrenuj Bansal --- .../postgres/__tests__/helpers/constants.ts | 38 +++ .../api/v4/asset-positions-controller.test.ts | 158 ++++++++++- .../comlink/public/api-documentation.md | 77 ++++++ indexer/services/comlink/public/swagger.json | 37 +++ .../api/v4/asset-positions-controller.ts | 247 +++++++++++++++--- indexer/services/comlink/src/types.ts | 3 + 6 files changed, 518 insertions(+), 42 deletions(-) diff --git a/indexer/packages/postgres/__tests__/helpers/constants.ts b/indexer/packages/postgres/__tests__/helpers/constants.ts index 7fb5214d1c..26f714041b 100644 --- a/indexer/packages/postgres/__tests__/helpers/constants.ts +++ b/indexer/packages/postgres/__tests__/helpers/constants.ts @@ -455,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 = { @@ -720,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/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/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index 6de8e7a885..f3dd667640 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -469,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 diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index de306d3bcb..cf3507be2c 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -1334,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", 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/types.ts b/indexer/services/comlink/src/types.ts index bb4402fa3f..83afcd18a7 100644 --- a/indexer/services/comlink/src/types.ts +++ b/indexer/services/comlink/src/types.ts @@ -373,6 +373,9 @@ 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 From da39a931208d58621a6083579615d56aadf0c52d Mon Sep 17 00:00:00 2001 From: jayy04 <103467857+jayy04@users.noreply.github.com> Date: Tue, 26 Mar 2024 17:19:49 -0400 Subject: [PATCH 15/35] [CT-712] send fill amount updates for reverted operations (#1240) --- protocol/x/clob/abci.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/protocol/x/clob/abci.go b/protocol/x/clob/abci.go index 27f4492589..f7e371c82f 100644 --- a/protocol/x/clob/abci.go +++ b/protocol/x/clob/abci.go @@ -169,7 +169,26 @@ 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) From 9c2521101818205c8f4589509613b18b417cdf08 Mon Sep 17 00:00:00 2001 From: vincentwschau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 27 Mar 2024 06:39:55 -0400 Subject: [PATCH 16/35] [TRA-167] v5.0.0 state migration negative tnc subaccount store. (#1226) --- protocol/app/upgrades.go | 1 + protocol/app/upgrades/v5.0.0/upgrade.go | 61 +++++++++++++++++++ .../keeper/negative_tnc_subaccount.go | 22 +++++++ protocol/x/subaccounts/types/types.go | 10 +++ 4 files changed, 94 insertions(+) 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 1833d37b18..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,6 +99,61 @@ 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, @@ -152,6 +208,7 @@ func CreateUpgradeHandler( 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") @@ -167,6 +224,10 @@ func CreateUpgradeHandler( // 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) 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/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 } From 9d1076f24012868c21b89b1a46a8d8c0f1039e17 Mon Sep 17 00:00:00 2001 From: jayy04 <103467857+jayy04@users.noreply.github.com> Date: Wed, 27 Mar 2024 08:46:28 -0400 Subject: [PATCH 17/35] [CT-723] add block number + stage to grpc updates (#1252) * [CT-723] add block number + stage to grpc updates * add indexer changes --- .../src/codegen/dydxprotocol/clob/query.ts | 40 +++- proto/dydxprotocol/clob/query.proto | 7 + protocol/app/app.go | 6 + protocol/lib/context.go | 7 + protocol/mocks/ClobKeeper.go | 6 +- protocol/mocks/MemClobKeeper.go | 6 +- .../streaming/grpc/grpc_streaming_manager.go | 9 +- .../streaming/grpc/noop_streaming_manager.go | 3 + protocol/streaming/grpc/types/manager.go | 3 + protocol/testutil/memclob/keeper.go | 1 + protocol/x/clob/abci.go | 2 +- protocol/x/clob/keeper/keeper.go | 10 +- protocol/x/clob/keeper/order_state.go | 2 +- protocol/x/clob/memclob/memclob.go | 6 +- protocol/x/clob/types/clob_keeper.go | 1 + protocol/x/clob/types/mem_clob_keeper.go | 1 + protocol/x/clob/types/query.pb.go | 213 ++++++++++++------ 17 files changed, 238 insertions(+), 85 deletions(-) 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/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/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/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/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index 8dbd200dc3..14000608f4 100644 --- a/protocol/mocks/ClobKeeper.go +++ b/protocol/mocks/ClobKeeper.go @@ -1135,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/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/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/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/x/clob/abci.go b/protocol/x/clob/abci.go index f7e371c82f..764e23828d 100644 --- a/protocol/x/clob/abci.go +++ b/protocol/x/clob/abci.go @@ -194,7 +194,7 @@ func PrepareCheckState( 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/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/order_state.go b/protocol/x/clob/keeper/order_state.go index a72340b176..70e06ca71a 100644 --- a/protocol/x/clob/keeper/order_state.go +++ b/protocol/x/clob/keeper/order_state.go @@ -300,6 +300,6 @@ func (k Keeper) PruneStateFillAmountsForShortTermOrders( allUpdates.Append(orderbookUpdate) } } - k.SendOrderbookUpdates(allUpdates, false) + k.SendOrderbookUpdates(ctx, allUpdates, false) } } 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 8d8c966de0..6cd6a8bd5f 100644 --- a/protocol/x/clob/types/clob_keeper.go +++ b/protocol/x/clob/types/clob_keeper.go @@ -147,6 +147,7 @@ type ClobKeeper interface { // Gprc streaming InitializeNewGrpcStreams(ctx sdk.Context) SendOrderbookUpdates( + ctx sdk.Context, offchainUpdates *OffchainUpdates, snapshot bool, ) 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:]) From f6ee1cf3d1cebde4fa27a9a3df69209361e48c56 Mon Sep 17 00:00:00 2001 From: Tian Date: Wed, 27 Mar 2024 11:48:53 -0400 Subject: [PATCH 18/35] change vault state key to not use serialization (#1248) --- protocol/x/vault/keeper/orders.go | 13 +-- protocol/x/vault/types/vault_id.go | 37 +++++++- protocol/x/vault/types/vault_id_test.go | 107 +++++++++++++++++++++++- 3 files changed, 145 insertions(+), 12 deletions(-) diff --git a/protocol/x/vault/keeper/orders.go b/protocol/x/vault/keeper/orders.go index a1f2610bac..768758340b 100644 --- a/protocol/x/vault/keeper/orders.go +++ b/protocol/x/vault/keeper/orders.go @@ -38,8 +38,11 @@ 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) @@ -53,12 +56,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) } } } diff --git a/protocol/x/vault/types/vault_id.go b/protocol/x/vault/types/vault_id.go index aae07e82be..47b0c7a625 100644 --- a/protocol/x/vault/types/vault_id.go +++ b/protocol/x/vault/types/vault_id.go @@ -2,19 +2,50 @@ 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()) +} + +// 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 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) { From 7669b1e2084211493063cb4bb66bd09b53f28cb8 Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Wed, 27 Mar 2024 09:16:05 -0700 Subject: [PATCH 19/35] Fix localnet (#1254) --- protocol/docker-compose.yml | 15 ++++----------- protocol/testing/testnet-local/local.sh | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) 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/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" From 2f6fffc5c2681b69b11c7ba5d23c70ead75f5591 Mon Sep 17 00:00:00 2001 From: vincentwschau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:47:48 -0400 Subject: [PATCH 20/35] [TRA-99] Add E2E test for isolated subaccounts having order matches / placing orders. (#1255) --- protocol/testutil/constants/subaccounts.go | 15 +- .../e2e/isolated_subaccount_orders_test.go | 493 ++++++++++++++++++ 2 files changed, 507 insertions(+), 1 deletion(-) create mode 100644 protocol/x/clob/e2e/isolated_subaccount_orders_test.go diff --git a/protocol/testutil/constants/subaccounts.go b/protocol/testutil/constants/subaccounts.go index 46532aeede..20ce1040f4 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), }, }, 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, + ) + } + }) + } +} From ddd17155662f5dab738af0805578264600de176a Mon Sep 17 00:00:00 2001 From: vincentwschau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:19:10 -0400 Subject: [PATCH 21/35] [TRA-181] Add query function for collateral pool address. (#1256) --- .../dydxprotocol/subaccounts/query.lcd.ts | 18 +- .../subaccounts/query.rpc.Query.ts | 16 +- .../codegen/dydxprotocol/subaccounts/query.ts | 136 ++++++ proto/dydxprotocol/subaccounts/query.proto | 23 +- protocol/mocks/QueryClient.go | 37 ++ .../keeper/grpc_query_collateral_pool.go | 46 ++ .../keeper/grpc_query_collateral_pool_test.go | 68 +++ protocol/x/subaccounts/types/query.pb.go | 462 ++++++++++++++++-- protocol/x/subaccounts/types/query.pb.gw.go | 145 +++++- 9 files changed, 883 insertions(+), 68 deletions(-) create mode 100644 protocol/x/subaccounts/keeper/grpc_query_collateral_pool.go create mode 100644 protocol/x/subaccounts/keeper/grpc_query_collateral_pool_test.go 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/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/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/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/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 ) From a10073916127bcfd773982091b46f17c3ebb5696 Mon Sep 17 00:00:00 2001 From: Mohammed Affan Date: Wed, 27 Mar 2024 14:53:14 -0400 Subject: [PATCH 22/35] [OTE-248] Update LiquidityTierUpsertEvent to add OI caps (#1242) * Update LiquidityTierUpsertEvent to add OI caps * add some constants * type fix --- .../dydxprotocol/indexer/events/events.ts | 34 +- .../ender/__tests__/helpers/constants.ts | 2 + .../dydxprotocol/indexer/events/events.proto | 6 + protocol/indexer/events/events.pb.go | 343 +++++++++++------- protocol/indexer/events/liquidity_tier.go | 4 + .../indexer/events/liquidity_tier_test.go | 4 + protocol/x/perpetuals/keeper/perpetual.go | 2 + 7 files changed, 260 insertions(+), 135 deletions(-) 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/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/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/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/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index 502bb74523..c95913b3b6 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -1539,6 +1539,8 @@ func (k Keeper) SetLiquidityTier( name, initialMarginPpm, maintenanceFractionPpm, + openInterestLowerCap, + openInterestUpperCap, ), ), ) From 045a112c1100cf7e2c16f3fd8e2bdddd02abf63c Mon Sep 17 00:00:00 2001 From: vincentwschau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:02:26 -0400 Subject: [PATCH 23/35] Enable isolated perpetuals in sim tests. (#1258) --- protocol/x/perpetuals/simulation/genesis.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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{ From 7492e0e36b433d620b0b00f48e2efd5012ac5f24 Mon Sep 17 00:00:00 2001 From: vincentwschau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 27 Mar 2024 15:40:34 -0400 Subject: [PATCH 24/35] [TRA-165] Update withdrawal gating E2E test for isolated subaccounts. (#1257) --- protocol/testutil/constants/orders.go | 7 + protocol/testutil/constants/subaccounts.go | 16 + protocol/x/clob/e2e/withdrawal_gating_test.go | 292 +++++++++++++++--- 3 files changed, 264 insertions(+), 51 deletions(-) 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/subaccounts.go b/protocol/testutil/constants/subaccounts.go index 20ce1040f4..f5ed8edfdf 100644 --- a/protocol/testutil/constants/subaccounts.go +++ b/protocol/testutil/constants/subaccounts.go @@ -547,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/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{}, - ) }) } } From 083762c17c3290f948c098fc83e1f4ec826fe8ce Mon Sep 17 00:00:00 2001 From: Tian Date: Wed, 27 Mar 2024 15:50:35 -0400 Subject: [PATCH 25/35] add vault owner shares (#1253) --- .../msg_server_deposit_to_vault_test.go | 67 ++++++---- protocol/x/vault/keeper/shares.go | 80 +++++++++++- protocol/x/vault/keeper/shares_test.go | 117 ++++++++++++++++-- protocol/x/vault/types/keys.go | 4 + protocol/x/vault/types/vault_id.go | 5 + 5 files changed, 242 insertions(+), 31 deletions(-) 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 index 16e120924b..b210f534ee 100644 --- a/protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go +++ b/protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go @@ -31,6 +31,8 @@ type DepositInstance struct { checkTxFails bool // Whether DeliverTx fails. deliverTxFails bool + // Expected owner shares for depositor above. + expectedOwnerShares *big.Rat } // DepositorSetup represents the setup of a depositor. @@ -67,14 +69,16 @@ func TestMsgDepositToVault(t *testing.T) { }, depositInstances: []DepositInstance{ { - depositor: constants.Alice_Num0, - depositAmount: big.NewInt(123), - msgSigner: constants.Alice_Num0.Owner, + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(123), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewRat(123, 1), }, { - depositor: constants.Alice_Num0, - depositAmount: big.NewInt(321), - msgSigner: constants.Alice_Num0.Owner, + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(321), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewRat(444, 1), }, }, totalSharesHistory: []*big.Rat{ @@ -100,14 +104,16 @@ func TestMsgDepositToVault(t *testing.T) { }, depositInstances: []DepositInstance{ { - depositor: constants.Alice_Num0, - depositAmount: big.NewInt(1_000), - msgSigner: constants.Alice_Num0.Owner, + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(1_000), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewRat(1_000, 1), }, { - depositor: constants.Bob_Num1, - depositAmount: big.NewInt(500), - msgSigner: constants.Bob_Num1.Owner, + depositor: constants.Bob_Num1, + depositAmount: big.NewInt(500), + msgSigner: constants.Bob_Num1.Owner, + expectedOwnerShares: big.NewRat(500, 1), }, }, totalSharesHistory: []*big.Rat{ @@ -133,15 +139,17 @@ func TestMsgDepositToVault(t *testing.T) { }, depositInstances: []DepositInstance{ { - depositor: constants.Alice_Num0, - depositAmount: big.NewInt(1_000), - msgSigner: constants.Alice_Num0.Owner, + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(1_000), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewRat(1_000, 1), }, { - depositor: constants.Bob_Num1, - depositAmount: big.NewInt(501), // Greater than balance. - msgSigner: constants.Bob_Num1.Owner, - deliverTxFails: true, + depositor: constants.Bob_Num1, + depositAmount: big.NewInt(501), // Greater than balance. + msgSigner: constants.Bob_Num1.Owner, + deliverTxFails: true, + expectedOwnerShares: nil, }, }, totalSharesHistory: []*big.Rat{ @@ -172,11 +180,13 @@ func TestMsgDepositToVault(t *testing.T) { 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, + depositor: constants.Alice_Num0, + depositAmount: big.NewInt(1_000), + msgSigner: constants.Alice_Num0.Owner, + expectedOwnerShares: big.NewRat(1_000, 1), }, }, totalSharesHistory: []*big.Rat{ @@ -207,6 +217,7 @@ func TestMsgDepositToVault(t *testing.T) { msgSigner: constants.Alice_Num0.Owner, checkTxFails: true, checkTxResponseContains: "Deposit amount is invalid", + expectedOwnerShares: nil, }, { depositor: constants.Bob_Num0, @@ -214,6 +225,7 @@ func TestMsgDepositToVault(t *testing.T) { msgSigner: constants.Bob_Num0.Owner, checkTxFails: true, checkTxResponseContains: "Deposit amount is invalid", + expectedOwnerShares: nil, }, }, totalSharesHistory: []*big.Rat{ @@ -320,6 +332,17 @@ func TestMsgDepositToVault(t *testing.T) { vaulttypes.BigRatToNumShares(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.BigRatToNumShares(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) diff --git a/protocol/x/vault/keeper/shares.go b/protocol/x/vault/keeper/shares.go index 20045fb1c0..5c2988741a 100644 --- a/protocol/x/vault/keeper/shares.go +++ b/protocol/x/vault/keeper/shares.go @@ -62,6 +62,54 @@ 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 { + ownerSharesRat, err := ownerShares.ToBigRat() + if err != nil { + return err + } + if ownerSharesRat.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`. @@ -127,7 +175,37 @@ func (k Keeper) MintShares( return err } - // TODO (TRA-170): Increase owner shares. + // 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.BigRatToNumShares(sharesToMint), + ) + if err != nil { + return err + } + } else { + // Increase existing owner shares by sharesToMint. + existingOwnerShares, err := ownerShares.ToBigRat() + if err != nil { + return err + } + err = k.SetOwnerShares( + ctx, + vaultId, + owner, + types.BigRatToNumShares( + 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 0b7accc856..b2da4e66b2 100644 --- a/protocol/x/vault/keeper/shares_test.go +++ b/protocol/x/vault/keeper/shares_test.go @@ -80,6 +80,60 @@ func TestGetSetTotalShares(t *testing.T) { ) } +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.BigRatToNumShares( + big.NewRat(7, 1), + ) + 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.BigRatToNumShares( + big.NewRat(456, 3), + ) + 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.BigRatToNumShares( + big.NewRat(0, 1), + ) + 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.BigRatToNumShares( + big.NewRat(-1, 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 --- */ @@ -89,73 +143,98 @@ func TestMintShares(t *testing.T) { equity *big.Int // Existing vault TotalShares. totalShares *big.Rat + // Owner that deposits. + owner string + // Existing owner shares. + ownerShares *big.Rat // Quote quantums to deposit. quantumsToDeposit *big.Int /* --- Expectations --- */ // Expected TotalShares after minting. expectedTotalShares *big.Rat + // Expected OwnerShares after minting. + expectedOwnerShares *big.Rat // Expected error. expectedErr error }{ - "Equity 0, TotalShares 0, Deposit 1000": { + "Equity 0, TotalShares 0, OwnerShares 0, Deposit 1000": { vaultId: constants.Vault_Clob_0, equity: big.NewInt(0), totalShares: big.NewRat(0, 1), + owner: constants.AliceAccAddress.String(), + ownerShares: big.NewRat(0, 1), quantumsToDeposit: big.NewInt(1_000), // Should mint `1_000 / 1` shares. expectedTotalShares: big.NewRat(1_000, 1), + expectedOwnerShares: big.NewRat(1_000, 1), }, - "Equity 0, TotalShares non-existent, Deposit 12345654321": { + "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 / 1` shares. expectedTotalShares: big.NewRat(12_345_654_321, 1), + expectedOwnerShares: big.NewRat(12_345_654_321, 1), }, - "Equity 1000, TotalShares non-existent, Deposit 500": { + "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 / 1` shares. expectedTotalShares: big.NewRat(500, 1), + expectedOwnerShares: big.NewRat(500, 1), }, - "Equity 4000, TotalShares 5000, Deposit 1000": { + "Equity 4000, TotalShares 5000, OwnerShares 2500, Deposit 1000": { vaultId: constants.Vault_Clob_1, equity: big.NewInt(4_000), totalShares: big.NewRat(5_000, 1), + owner: constants.AliceAccAddress.String(), + ownerShares: big.NewRat(2_500, 1), quantumsToDeposit: big.NewInt(1_000), // Should mint `1_250 / 1` shares. expectedTotalShares: big.NewRat(6_250, 1), + expectedOwnerShares: big.NewRat(3_750, 1), }, - "Equity 1_000_000, TotalShares 1, Deposit 1": { + "Equity 1_000_000, TotalShares 1, OwnerShares 1/2, Deposit 1": { vaultId: constants.Vault_Clob_1, equity: big.NewInt(1_000_000), totalShares: big.NewRat(1, 1), + owner: constants.BobAccAddress.String(), + ownerShares: big.NewRat(1, 2), quantumsToDeposit: big.NewInt(1), // Should mint `1 / 1_000_000` shares. expectedTotalShares: big.NewRat(1_000_001, 1_000_000), + expectedOwnerShares: big.NewRat(500_001, 1_000_000), }, - "Equity 8000, TotalShares 4000, Deposit 455": { + "Equity 8000, TotalShares 4000, OwnerShares Deposit 455": { vaultId: constants.Vault_Clob_1, equity: big.NewInt(8_000), totalShares: big.NewRat(4_000, 1), + owner: constants.CarlAccAddress.String(), + ownerShares: big.NewRat(101, 7), quantumsToDeposit: big.NewInt(455), // Should mint `455 / 2` shares. expectedTotalShares: big.NewRat(8_455, 2), + expectedOwnerShares: big.NewRat(3_387, 14), }, - "Equity 123456, TotalShares 654321, Deposit 123456789": { + "Equity 123456, TotalShares 654321, OwnerShares 0, Deposit 123456789": { vaultId: constants.Vault_Clob_1, equity: big.NewInt(123_456), totalShares: big.NewRat(654_321, 1), + owner: constants.DaveAccAddress.String(), quantumsToDeposit: big.NewInt(123_456_789), // Should mint `26_926_789_878_423 / 41_152` shares. expectedTotalShares: big.NewRat(26_953_716_496_215, 41_152), + expectedOwnerShares: big.NewRat(26_926_789_878_423, 41_152), }, "Equity -1, TotalShares 10, Deposit 1": { vaultId: constants.Vault_Clob_1, equity: big.NewInt(-1), totalShares: big.NewRat(10, 1), + owner: constants.AliceAccAddress.String(), quantumsToDeposit: big.NewInt(1), expectedErr: vaulttypes.ErrNonPositiveEquity, }, @@ -163,12 +242,14 @@ func TestMintShares(t *testing.T) { vaultId: constants.Vault_Clob_1, equity: big.NewInt(1), totalShares: big.NewRat(1, 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, }, @@ -209,12 +290,21 @@ func TestMintShares(t *testing.T) { ) require.NoError(t, err) } + if tc.ownerShares != nil { + err := tApp.App.VaultKeeper.SetOwnerShares( + ctx, + tc.vaultId, + tc.owner, + vaulttypes.BigRatToNumShares(tc.ownerShares), + ) + require.NoError(t, err) + } // Mint shares. err := tApp.App.VaultKeeper.MintShares( ctx, tc.vaultId, - "", // TODO (TRA-170): Increase owner shares. + tc.owner, tc.quantumsToDeposit, ) if tc.expectedErr != nil { @@ -227,6 +317,9 @@ func TestMintShares(t *testing.T) { vaulttypes.BigRatToNumShares(tc.totalShares), totalShares, ) + // Check that OwnerShares is unchanged. + ownerShares, _ := tApp.App.VaultKeeper.GetOwnerShares(ctx, tc.vaultId, tc.owner) + require.Equal(t, vaulttypes.BigRatToNumShares(tc.ownerShares), ownerShares) } else { require.NoError(t, err) // Check that TotalShares is as expected. @@ -237,6 +330,14 @@ func TestMintShares(t *testing.T) { vaulttypes.BigRatToNumShares(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.BigRatToNumShares(tc.expectedOwnerShares), + ownerShares, + ) } }) } diff --git a/protocol/x/vault/types/keys.go b/protocol/x/vault/types/keys.go index 4e37abb4d9..1130a77fa0 100644 --- a/protocol/x/vault/types/keys.go +++ b/protocol/x/vault/types/keys.go @@ -14,6 +14,10 @@ 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/vault_id.go b/protocol/x/vault/types/vault_id.go index 47b0c7a625..af070d0eae 100644 --- a/protocol/x/vault/types/vault_id.go +++ b/protocol/x/vault/types/vault_id.go @@ -20,6 +20,11 @@ func (id *VaultId) ToStateKey() []byte { 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) From cc909f0835f5854740ba58ed5a145468add61bc3 Mon Sep 17 00:00:00 2001 From: jayy04 <103467857+jayy04@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:23:05 -0400 Subject: [PATCH 26/35] [CT-727] avoid state reads when sending updates (#1261) --- protocol/x/clob/keeper/order_state.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/protocol/x/clob/keeper/order_state.go b/protocol/x/clob/keeper/order_state.go index 70e06ca71a..2849c9d9da 100644 --- a/protocol/x/clob/keeper/order_state.go +++ b/protocol/x/clob/keeper/order_state.go @@ -6,6 +6,7 @@ import ( "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" @@ -296,8 +297,13 @@ func (k Keeper) PruneStateFillAmountsForShortTermOrders( allUpdates := types.NewOffchainUpdates() for _, orderId := range prunedOrderIds { if _, exists := k.MemClob.GetOrder(ctx, orderId); exists { - orderbookUpdate := k.MemClob.GetOrderbookUpdatesForOrderUpdate(ctx, orderId) - allUpdates.Append(orderbookUpdate) + 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) From bfd5e7a57a965f98a0559a7e84fca0852b48fe15 Mon Sep 17 00:00:00 2001 From: roy-dydx <133032749+roy-dydx@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:23:26 -0400 Subject: [PATCH 27/35] Fix bugs in pruneable order migration (#1250) --- protocol/x/clob/keeper/order_state.go | 3 ++- protocol/x/clob/keeper/order_state_test.go | 17 +++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/protocol/x/clob/keeper/order_state.go b/protocol/x/clob/keeper/order_state.go index 2849c9d9da..a4bd805689 100644 --- a/protocol/x/clob/keeper/order_state.go +++ b/protocol/x/clob/keeper/order_state.go @@ -251,10 +251,11 @@ func (k Keeper) MigratePruneableOrders(ctx sdk.Context) { continue } - height := binary.BigEndian.Uint32(it.Value()) + 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()) } } diff --git a/protocol/x/clob/keeper/order_state_test.go b/protocol/x/clob/keeper/order_state_test.go index 6ccad5a87b..5b3a60557b 100644 --- a/protocol/x/clob/keeper/order_state_test.go +++ b/protocol/x/clob/keeper/order_state_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "testing" + "cosmossdk.io/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/dydxprotocol/v4-chain/protocol/mocks" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" @@ -508,12 +509,12 @@ func TestMigratePruneableOrders(t *testing.T) { constants.Order_Alice_Num1_Id0_Clob0_Sell10_Price15_GTB20.OrderId, } - ks.ClobKeeper.AddOrdersForPruning( + ks.ClobKeeper.LegacyAddOrdersForPruning( ks.Ctx, ordersA, 10, ) - ks.ClobKeeper.AddOrdersForPruning( + ks.ClobKeeper.LegacyAddOrdersForPruning( ks.Ctx, ordersB, 100, @@ -523,8 +524,8 @@ func TestMigratePruneableOrders(t *testing.T) { getPostMigrationOrdersAtHeight := func(height uint32) []types.OrderId { postMigrationOrders := []types.OrderId{} - storeA := ks.ClobKeeper.GetPruneableOrdersStore(ks.Ctx, height) - it := storeA.Iterator(nil, nil) + store := ks.ClobKeeper.GetPruneableOrdersStore(ks.Ctx, height) + it := store.Iterator(nil, nil) defer it.Close() for ; it.Valid(); it.Next() { var orderId types.OrderId @@ -537,6 +538,14 @@ func TestMigratePruneableOrders(t *testing.T) { 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) { From 8f5edc7b2c627a158f4451d9da9b87898bce530f Mon Sep 17 00:00:00 2001 From: roy-dydx <133032749+roy-dydx@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:23:35 -0400 Subject: [PATCH 28/35] Upgrade cosmos fork to v0.50.5 (#1246) --- protocol/go.mod | 40 ++++++++++++------------- protocol/go.sum | 79 +++++++++++++++++++++++++------------------------ 2 files changed, 60 insertions(+), 59 deletions(-) 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= From b83939defbad61b4f685856cc8fdc862b1d8ec62 Mon Sep 17 00:00:00 2001 From: Teddy Ding Date: Thu, 28 Mar 2024 11:56:30 -0400 Subject: [PATCH 29/35] postUpgradeCheckLiquidityTiers (#1247) --- .../upgrades/v5.0.0/upgrade_container_test.go | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/protocol/app/upgrades/v5.0.0/upgrade_container_test.go b/protocol/app/upgrades/v5.0.0/upgrade_container_test.go index 63667592da..57c3328ae9 100644 --- a/protocol/app/upgrades/v5.0.0/upgrade_container_test.go +++ b/protocol/app/upgrades/v5.0.0/upgrade_container_test.go @@ -38,6 +38,7 @@ func preUpgradeChecks(node *containertest.Node, t *testing.T) { func postUpgradeChecks(node *containertest.Node, t *testing.T) { postUpgradecheckPerpetualMarketType(node, t) + postUpgradeCheckLiquidityTiers(node, t) // Add test for your upgrade handler logic below } @@ -72,3 +73,58 @@ func postUpgradecheckPerpetualMarketType(node *containertest.Node, t *testing.T) 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]) +} From 1eeee455dbf4219e154b2913b5ce28d7ceede4f6 Mon Sep 17 00:00:00 2001 From: Tian Date: Thu, 28 Mar 2024 12:39:10 -0400 Subject: [PATCH 30/35] [TRA-115] decommission vaults at the beginning of a block (#1264) * decommission vaults at the beginning of a block * address comments --- protocol/x/vault/abci.go | 7 + protocol/x/vault/keeper/vault.go | 83 ++++++++++ protocol/x/vault/keeper/vault_info.go | 26 --- protocol/x/vault/keeper/vault_test.go | 223 ++++++++++++++++++++++++++ protocol/x/vault/module.go | 11 ++ 5 files changed, 324 insertions(+), 26 deletions(-) create mode 100644 protocol/x/vault/keeper/vault.go delete mode 100644 protocol/x/vault/keeper/vault_info.go create mode 100644 protocol/x/vault/keeper/vault_test.go 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/keeper/vault.go b/protocol/x/vault/keeper/vault.go new file mode 100644 index 0000000000..feb796e43a --- /dev/null +++ b/protocol/x/vault/keeper/vault.go @@ -0,0 +1,83 @@ +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 +} + +// 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. + totalSharesRat, err := totalShares.ToBigRat() + if err != nil || totalSharesRat.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_info.go b/protocol/x/vault/keeper/vault_info.go deleted file mode 100644 index 164785b2e5..0000000000 --- a/protocol/x/vault/keeper/vault_info.go +++ /dev/null @@ -1,26 +0,0 @@ -package keeper - -import ( - "math/big" - - sdk "github.com/cosmos/cosmos-sdk/types" - 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 -} diff --git a/protocol/x/vault/keeper/vault_test.go b/protocol/x/vault/keeper/vault_test.go new file mode 100644 index 0000000000..078d1ba526 --- /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.Rat + // 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.Rat{ + big.NewRat(7, 1), + big.NewRat(7, 1), + }, + 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.Rat{ + big.NewRat(7, 1), + big.NewRat(7, 1), + }, + 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.Rat{ + big.NewRat(7, 1), + big.NewRat(7, 1), + }, + 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.BigRatToNumShares(tc.totalShares[i]), + ) + require.NoError(t, err) + err = k.SetOwnerShares( + ctx, + vaultId, + testOwner, + vaulttypes.BigRatToNumShares(big.NewRat(7, 1)), + ) + 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.BigRatToNumShares( + big.NewRat(7, 1), + ) + + 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) From 347610846e505930817e6febdd260da40ef96af0 Mon Sep 17 00:00:00 2001 From: Tian Date: Thu, 28 Mar 2024 13:08:07 -0400 Subject: [PATCH 31/35] keep track of vault shares as integers (#1267) --- .../src/codegen/dydxprotocol/vault/vault.ts | 44 ++---- proto/dydxprotocol/vault/vault.proto | 14 +- .../msg_server_deposit_to_vault_test.go | 50 +++--- protocol/x/vault/keeper/orders.go | 3 +- protocol/x/vault/keeper/orders_test.go | 22 +-- protocol/x/vault/keeper/shares.go | 53 ++----- protocol/x/vault/keeper/shares_test.go | 143 ++++++++++-------- protocol/x/vault/keeper/vault.go | 3 +- protocol/x/vault/keeper/vault_test.go | 28 ++-- protocol/x/vault/types/num_shares.go | 21 +-- protocol/x/vault/types/num_shares_test.go | 93 +----------- protocol/x/vault/types/vault.pb.go | 91 +++-------- 12 files changed, 191 insertions(+), 374 deletions(-) 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 cd2e9f4a20..a9a7696cef 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/vault.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/vault.ts @@ -67,29 +67,17 @@ export interface VaultIdSDKType { number: number; } -/** - * NumShares represents the number of shares in a vault in the - * format of a rational number `numerator / denominator`. - */ +/** NumShares represents the number of shares in a vault. */ export interface NumShares { - /** Numerator. */ - numerator: Uint8Array; - /** Denominator. */ - - denominator: Uint8Array; + /** Number of shares. */ + numShares: Uint8Array; } -/** - * NumShares represents the number of shares in a vault in the - * format of a rational number `numerator / denominator`. - */ +/** NumShares represents the number of shares in a vault. */ export interface NumSharesSDKType { - /** Numerator. */ - numerator: Uint8Array; - /** Denominator. */ - - denominator: Uint8Array; + /** Number of shares. */ + num_shares: Uint8Array; } function createBaseVaultId(): VaultId { @@ -149,19 +137,14 @@ export const VaultId = { function createBaseNumShares(): NumShares { return { - numerator: new Uint8Array(), - denominator: new Uint8Array() + numShares: new Uint8Array() }; } export const NumShares = { encode(message: NumShares, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { - if (message.numerator.length !== 0) { - writer.uint32(10).bytes(message.numerator); - } - - if (message.denominator.length !== 0) { - writer.uint32(18).bytes(message.denominator); + if (message.numShares.length !== 0) { + writer.uint32(18).bytes(message.numShares); } return writer; @@ -176,12 +159,8 @@ export const NumShares = { const tag = reader.uint32(); switch (tag >>> 3) { - case 1: - message.numerator = reader.bytes(); - break; - case 2: - message.denominator = reader.bytes(); + message.numShares = reader.bytes(); break; default: @@ -195,8 +174,7 @@ export const NumShares = { fromPartial(object: DeepPartial): NumShares { const message = createBaseNumShares(); - message.numerator = object.numerator ?? new Uint8Array(); - message.denominator = object.denominator ?? new Uint8Array(); + message.numShares = object.numShares ?? new Uint8Array(); return message; } diff --git a/proto/dydxprotocol/vault/vault.proto b/proto/dydxprotocol/vault/vault.proto index 6ac6c35932..75b7766ab8 100644 --- a/proto/dydxprotocol/vault/vault.proto +++ b/proto/dydxprotocol/vault/vault.proto @@ -23,18 +23,10 @@ message VaultId { uint32 number = 2; } -// NumShares represents the number of shares in a vault in the -// format of a rational number `numerator / denominator`. +// NumShares represents the number of shares in a vault. message NumShares { - // Numerator. - bytes numerator = 1 [ - (gogoproto.customtype) = - "github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt", - (gogoproto.nullable) = false - ]; - - // Denominator. - bytes denominator = 2 [ + // Number of shares. + bytes num_shares = 2 [ (gogoproto.customtype) = "github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt", (gogoproto.nullable) = false 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 index b210f534ee..b803769a86 100644 --- a/protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go +++ b/protocol/x/vault/keeper/msg_server_deposit_to_vault_test.go @@ -32,7 +32,7 @@ type DepositInstance struct { // Whether DeliverTx fails. deliverTxFails bool // Expected owner shares for depositor above. - expectedOwnerShares *big.Rat + expectedOwnerShares *big.Int } // DepositorSetup represents the setup of a depositor. @@ -55,7 +55,7 @@ func TestMsgDepositToVault(t *testing.T) { /* --- Expectations --- */ // Vault total shares after each of the above deposit instances. - totalSharesHistory []*big.Rat + totalSharesHistory []*big.Int // Vault equity after each of the above deposit instances. vaultEquityHistory []*big.Int }{ @@ -72,18 +72,18 @@ func TestMsgDepositToVault(t *testing.T) { depositor: constants.Alice_Num0, depositAmount: big.NewInt(123), msgSigner: constants.Alice_Num0.Owner, - expectedOwnerShares: big.NewRat(123, 1), + expectedOwnerShares: big.NewInt(123), }, { depositor: constants.Alice_Num0, depositAmount: big.NewInt(321), msgSigner: constants.Alice_Num0.Owner, - expectedOwnerShares: big.NewRat(444, 1), + expectedOwnerShares: big.NewInt(444), }, }, - totalSharesHistory: []*big.Rat{ - big.NewRat(123, 1), - big.NewRat(444, 1), + totalSharesHistory: []*big.Int{ + big.NewInt(123), + big.NewInt(444), }, vaultEquityHistory: []*big.Int{ big.NewInt(123), @@ -107,18 +107,18 @@ func TestMsgDepositToVault(t *testing.T) { depositor: constants.Alice_Num0, depositAmount: big.NewInt(1_000), msgSigner: constants.Alice_Num0.Owner, - expectedOwnerShares: big.NewRat(1_000, 1), + expectedOwnerShares: big.NewInt(1_000), }, { depositor: constants.Bob_Num1, depositAmount: big.NewInt(500), msgSigner: constants.Bob_Num1.Owner, - expectedOwnerShares: big.NewRat(500, 1), + expectedOwnerShares: big.NewInt(500), }, }, - totalSharesHistory: []*big.Rat{ - big.NewRat(1_000, 1), - big.NewRat(1_500, 1), + totalSharesHistory: []*big.Int{ + big.NewInt(1_000), + big.NewInt(1_500), }, vaultEquityHistory: []*big.Int{ big.NewInt(1_000), @@ -142,7 +142,7 @@ func TestMsgDepositToVault(t *testing.T) { depositor: constants.Alice_Num0, depositAmount: big.NewInt(1_000), msgSigner: constants.Alice_Num0.Owner, - expectedOwnerShares: big.NewRat(1_000, 1), + expectedOwnerShares: big.NewInt(1_000), }, { depositor: constants.Bob_Num1, @@ -152,9 +152,9 @@ func TestMsgDepositToVault(t *testing.T) { expectedOwnerShares: nil, }, }, - totalSharesHistory: []*big.Rat{ - big.NewRat(1_000, 1), - big.NewRat(1_000, 1), + totalSharesHistory: []*big.Int{ + big.NewInt(1_000), + big.NewInt(1_000), }, vaultEquityHistory: []*big.Int{ big.NewInt(1_000), @@ -186,12 +186,12 @@ func TestMsgDepositToVault(t *testing.T) { depositor: constants.Alice_Num0, depositAmount: big.NewInt(1_000), msgSigner: constants.Alice_Num0.Owner, - expectedOwnerShares: big.NewRat(1_000, 1), + expectedOwnerShares: big.NewInt(1_000), }, }, - totalSharesHistory: []*big.Rat{ - big.NewRat(0, 1), - big.NewRat(1_000, 1), + totalSharesHistory: []*big.Int{ + big.NewInt(0), + big.NewInt(1_000), }, vaultEquityHistory: []*big.Int{ big.NewInt(0), @@ -228,9 +228,9 @@ func TestMsgDepositToVault(t *testing.T) { expectedOwnerShares: nil, }, }, - totalSharesHistory: []*big.Rat{ - big.NewRat(0, 1), - big.NewRat(0, 1), + totalSharesHistory: []*big.Int{ + big.NewInt(0), + big.NewInt(0), }, vaultEquityHistory: []*big.Int{ big.NewInt(0), @@ -329,7 +329,7 @@ func TestMsgDepositToVault(t *testing.T) { require.True(t, exists) require.Equal( t, - vaulttypes.BigRatToNumShares(tc.totalSharesHistory[i]), + vaulttypes.BigIntToNumShares(tc.totalSharesHistory[i]), totalShares, ) // Check that owner shares of the depositor is as expected. @@ -340,7 +340,7 @@ func TestMsgDepositToVault(t *testing.T) { ) require.Equal( t, - vaulttypes.BigRatToNumShares(depositInstance.expectedOwnerShares), + vaulttypes.BigIntToNumShares(depositInstance.expectedOwnerShares), ownerShares, ) // Check that equity of the vault is as expected. diff --git a/protocol/x/vault/keeper/orders.go b/protocol/x/vault/keeper/orders.go index 768758340b..dae848b36a 100644 --- a/protocol/x/vault/keeper/orders.go +++ b/protocol/x/vault/keeper/orders.go @@ -47,8 +47,7 @@ func (k Keeper) RefreshAllVaultOrders(ctx sdk.Context) { k.cdc.MustUnmarshal(totalSharesIterator.Value(), &totalShares) // Skip if TotalShares is non-positive. - totalSharesRat, err := totalShares.ToBigRat() - if err != nil || totalSharesRat.Sign() <= 0 { + if totalShares.NumShares.BigInt().Sign() <= 0 { continue } diff --git a/protocol/x/vault/keeper/orders_test.go b/protocol/x/vault/keeper/orders_test.go index 327c3b6734..fbb9b036fc 100644 --- a/protocol/x/vault/keeper/orders_test.go +++ b/protocol/x/vault/keeper/orders_test.go @@ -30,16 +30,16 @@ func TestRefreshAllVaultOrders(t *testing.T) { // Vault IDs. vaultIds []vaulttypes.VaultId // Total Shares of each vault ID above. - totalShares []*big.Rat + totalShares []*big.Int }{ "Two Vaults, Both Positive Shares": { vaultIds: []vaulttypes.VaultId{ constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []*big.Rat{ - big.NewRat(1_000, 1), - big.NewRat(200, 1), + totalShares: []*big.Int{ + big.NewInt(1_000), + big.NewInt(200), }, }, "Two Vaults, One Positive Shares, One Zero Shares": { @@ -47,9 +47,9 @@ func TestRefreshAllVaultOrders(t *testing.T) { constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []*big.Rat{ - big.NewRat(1_000, 1), - big.NewRat(0, 1), + totalShares: []*big.Int{ + big.NewInt(1_000), + big.NewInt(0), }, }, "Two Vaults, Both Zero Shares": { @@ -57,9 +57,9 @@ func TestRefreshAllVaultOrders(t *testing.T) { constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []*big.Rat{ - big.NewRat(0, 1), - big.NewRat(0, 1), + totalShares: []*big.Int{ + big.NewInt(0), + big.NewInt(0), }, }, } @@ -97,7 +97,7 @@ func TestRefreshAllVaultOrders(t *testing.T) { err := tApp.App.VaultKeeper.SetTotalShares( ctx, vaultId, - vaulttypes.BigRatToNumShares(tc.totalShares[i]), + vaulttypes.BigIntToNumShares(tc.totalShares[i]), ) require.NoError(t, err) } diff --git a/protocol/x/vault/keeper/shares.go b/protocol/x/vault/keeper/shares.go index 5c2988741a..81948611fc 100644 --- a/protocol/x/vault/keeper/shares.go +++ b/protocol/x/vault/keeper/shares.go @@ -33,11 +33,7 @@ func (k Keeper) SetTotalShares( vaultId types.VaultId, totalShares types.NumShares, ) error { - totalSharesRat, err := totalShares.ToBigRat() - if err != nil { - return err - } - if totalSharesRat.Sign() < 0 { + if totalShares.NumShares.BigInt().Sign() < 0 { return types.ErrNegativeShares } @@ -46,10 +42,9 @@ func (k Keeper) SetTotalShares( totalSharesStore.Set(vaultId.ToStateKey(), b) // Emit metric on TotalShares. - totalSharesFloat, _ := totalSharesRat.Float32() vaultId.SetGaugeWithLabels( metrics.TotalShares, - totalSharesFloat, + float32(totalShares.NumShares.BigInt().Uint64()), ) return nil @@ -86,11 +81,7 @@ func (k Keeper) SetOwnerShares( owner string, ownerShares types.NumShares, ) error { - ownerSharesRat, err := ownerShares.ToBigRat() - if err != nil { - return err - } - if ownerSharesRat.Sign() < 0 { + if ownerShares.NumShares.BigInt().Sign() < 0 { return types.ErrNegativeShares } @@ -120,28 +111,19 @@ func (k Keeper) MintShares( quantumsToDeposit *big.Int, ) error { // Quantums to deposit should be positive. - if quantumsToDeposit.Cmp(big.NewInt(0)) <= 0 { + if quantumsToDeposit.Sign() <= 0 { return types.ErrInvalidDepositAmount } // Get existing TotalShares of the vault. totalShares, exists := k.GetTotalShares(ctx, vaultId) - var existingTotalShares *big.Rat - var err error - if exists { - existingTotalShares, err = totalShares.ToBigRat() - if err != nil { - return err - } - } else { - existingTotalShares = new(big.Rat).SetInt(big.NewInt(0)) - } + existingTotalShares := totalShares.NumShares.BigInt() // Calculate shares to mint. - var sharesToMint *big.Rat + var sharesToMint *big.Int if !exists || existingTotalShares.Sign() <= 0 { // Mint `quoteQuantums` number of shares. - sharesToMint = new(big.Rat).SetInt(quantumsToDeposit) + sharesToMint = new(big.Int).Set(quantumsToDeposit) // Initialize existingTotalShares as 0. - existingTotalShares = new(big.Rat).SetInt(big.NewInt(0)) + existingTotalShares = big.NewInt(0) } else { // Get vault equity. equity, err := k.GetVaultEquity(ctx, vaultId) @@ -149,7 +131,7 @@ func (k Keeper) MintShares( return err } // Don't mint shares if equity is non-positive. - if equity.Cmp(big.NewInt(0)) <= 0 { + if equity.Sign() <= 0 { return types.ErrNonPositiveEquity } // Mint `deposit (in quote quantums) * existing shares / vault equity (in quote quantums)` @@ -158,16 +140,16 @@ func (k Keeper) MintShares( // - 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.Rat).SetInt(quantumsToDeposit) + sharesToMint = new(big.Int).Set(quantumsToDeposit) sharesToMint = sharesToMint.Mul(sharesToMint, existingTotalShares) - sharesToMint = sharesToMint.Quo(sharesToMint, new(big.Rat).SetInt(equity)) + sharesToMint = sharesToMint.Quo(sharesToMint, equity) } // Increase TotalShares of the vault. - err = k.SetTotalShares( + err := k.SetTotalShares( ctx, vaultId, - types.BigRatToNumShares( + types.BigIntToNumShares( existingTotalShares.Add(existingTotalShares, sharesToMint), ), ) @@ -183,22 +165,19 @@ func (k Keeper) MintShares( ctx, vaultId, owner, - types.BigRatToNumShares(sharesToMint), + types.BigIntToNumShares(sharesToMint), ) if err != nil { return err } } else { // Increase existing owner shares by sharesToMint. - existingOwnerShares, err := ownerShares.ToBigRat() - if err != nil { - return err - } + existingOwnerShares := ownerShares.NumShares.BigInt() err = k.SetOwnerShares( ctx, vaultId, owner, - types.BigRatToNumShares( + types.BigIntToNumShares( existingOwnerShares.Add(existingOwnerShares, sharesToMint), ), ) diff --git a/protocol/x/vault/keeper/shares_test.go b/protocol/x/vault/keeper/shares_test.go index b2da4e66b2..0728a18cb9 100644 --- a/protocol/x/vault/keeper/shares_test.go +++ b/protocol/x/vault/keeper/shares_test.go @@ -23,8 +23,8 @@ func TestGetSetTotalShares(t *testing.T) { require.Equal(t, false, exists) // Set total shares for a vault and then get. - numShares := vaulttypes.BigRatToNumShares( - big.NewRat(7, 1), + numShares := vaulttypes.BigIntToNumShares( + big.NewInt(7), ) err := k.SetTotalShares(ctx, constants.Vault_Clob_0, numShares) require.NoError(t, err) @@ -33,8 +33,8 @@ func TestGetSetTotalShares(t *testing.T) { require.Equal(t, numShares, got) // Set total shares for another vault and then get. - numShares = vaulttypes.BigRatToNumShares( - big.NewRat(456, 3), + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(456), ) err = k.SetTotalShares(ctx, constants.Vault_Clob_1, numShares) require.NoError(t, err) @@ -43,8 +43,8 @@ func TestGetSetTotalShares(t *testing.T) { require.Equal(t, numShares, got) // Set total shares for second vault to 0. - numShares = vaulttypes.BigRatToNumShares( - big.NewRat(0, 1), + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(0), ) err = k.SetTotalShares(ctx, constants.Vault_Clob_1, numShares) require.NoError(t, err) @@ -53,8 +53,8 @@ func TestGetSetTotalShares(t *testing.T) { require.Equal(t, numShares, got) // Set total shares for the first vault again and then get. - numShares = vaulttypes.BigRatToNumShares( - big.NewRat(73423, 59), + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(7283133), ) err = k.SetTotalShares(ctx, constants.Vault_Clob_0, numShares) require.NoError(t, err) @@ -64,18 +64,16 @@ func TestGetSetTotalShares(t *testing.T) { // Set total shares for the first vault to a negative value. // Should get error and total shares should remain unchanged. - numShares = vaulttypes.BigRatToNumShares( - big.NewRat(-1, 1), + negativeShares := vaulttypes.BigIntToNumShares( + big.NewInt(-1), ) - err = k.SetTotalShares(ctx, constants.Vault_Clob_0, numShares) + 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, - vaulttypes.BigRatToNumShares( - big.NewRat(73423, 59), - ), + numShares, got, ) } @@ -93,8 +91,8 @@ func TestGetSetOwnerShares(t *testing.T) { require.Equal(t, false, exists) // Set owner shares for Alice in vault clob 0 and get. - numShares := vaulttypes.BigRatToNumShares( - big.NewRat(7, 1), + numShares := vaulttypes.BigIntToNumShares( + big.NewInt(7), ) err := k.SetOwnerShares(ctx, constants.Vault_Clob_0, alice, numShares) require.NoError(t, err) @@ -103,8 +101,8 @@ func TestGetSetOwnerShares(t *testing.T) { require.Equal(t, numShares, got) // Set owner shares for Alice in vault clob 1 and then get. - numShares = vaulttypes.BigRatToNumShares( - big.NewRat(456, 3), + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(456), ) err = k.SetOwnerShares(ctx, constants.Vault_Clob_1, alice, numShares) require.NoError(t, err) @@ -113,8 +111,8 @@ func TestGetSetOwnerShares(t *testing.T) { require.Equal(t, numShares, got) // Set owner shares for Bob in vault clob 1. - numShares = vaulttypes.BigRatToNumShares( - big.NewRat(0, 1), + numShares = vaulttypes.BigIntToNumShares( + big.NewInt(0), ) err = k.SetOwnerShares(ctx, constants.Vault_Clob_1, bob, numShares) require.NoError(t, err) @@ -124,8 +122,8 @@ func TestGetSetOwnerShares(t *testing.T) { // Set owner shares for Bob in vault clob 1 to a negative value. // Should get error and total shares should remain unchanged. - numSharesInvalid := vaulttypes.BigRatToNumShares( - big.NewRat(-1, 1), + numSharesInvalid := vaulttypes.BigIntToNumShares( + big.NewInt(-1), ) err = k.SetOwnerShares(ctx, constants.Vault_Clob_1, bob, numSharesInvalid) require.ErrorIs(t, err, vaulttypes.ErrNegativeShares) @@ -142,98 +140,108 @@ func TestMintShares(t *testing.T) { // Existing vault equity. equity *big.Int // Existing vault TotalShares. - totalShares *big.Rat + totalShares *big.Int // Owner that deposits. owner string // Existing owner shares. - ownerShares *big.Rat + ownerShares *big.Int // Quote quantums to deposit. quantumsToDeposit *big.Int /* --- Expectations --- */ // Expected TotalShares after minting. - expectedTotalShares *big.Rat + expectedTotalShares *big.Int // Expected OwnerShares after minting. - expectedOwnerShares *big.Rat + 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.NewRat(0, 1), + totalShares: big.NewInt(0), owner: constants.AliceAccAddress.String(), - ownerShares: big.NewRat(0, 1), + ownerShares: big.NewInt(0), quantumsToDeposit: big.NewInt(1_000), - // Should mint `1_000 / 1` shares. - expectedTotalShares: big.NewRat(1_000, 1), - expectedOwnerShares: big.NewRat(1_000, 1), + // 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 / 1` shares. - expectedTotalShares: big.NewRat(12_345_654_321, 1), - expectedOwnerShares: big.NewRat(12_345_654_321, 1), + // 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 / 1` shares. - expectedTotalShares: big.NewRat(500, 1), - expectedOwnerShares: big.NewRat(500, 1), + // 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.NewRat(5_000, 1), + totalShares: big.NewInt(5_000), owner: constants.AliceAccAddress.String(), - ownerShares: big.NewRat(2_500, 1), + ownerShares: big.NewInt(2_500), quantumsToDeposit: big.NewInt(1_000), - // Should mint `1_250 / 1` shares. - expectedTotalShares: big.NewRat(6_250, 1), - expectedOwnerShares: big.NewRat(3_750, 1), + // Should mint `1_250` shares. + expectedTotalShares: big.NewInt(6_250), + expectedOwnerShares: big.NewInt(3_750), }, - "Equity 1_000_000, TotalShares 1, OwnerShares 1/2, Deposit 1": { + "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.NewRat(1, 1), + totalShares: big.NewInt(2_000), owner: constants.BobAccAddress.String(), - ownerShares: big.NewRat(1, 2), - quantumsToDeposit: big.NewInt(1), - // Should mint `1 / 1_000_000` shares. - expectedTotalShares: big.NewRat(1_000_001, 1_000_000), - expectedOwnerShares: big.NewRat(500_001, 1_000_000), + 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 Deposit 455": { + "Equity 8000, TotalShares 4000, OwnerShares 101, Deposit 455": { vaultId: constants.Vault_Clob_1, equity: big.NewInt(8_000), - totalShares: big.NewRat(4_000, 1), + totalShares: big.NewInt(4_000), owner: constants.CarlAccAddress.String(), - ownerShares: big.NewRat(101, 7), + ownerShares: big.NewInt(101), quantumsToDeposit: big.NewInt(455), - // Should mint `455 / 2` shares. - expectedTotalShares: big.NewRat(8_455, 2), - expectedOwnerShares: big.NewRat(3_387, 14), + // 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.NewRat(654_321, 1), + totalShares: big.NewInt(654_321), owner: constants.DaveAccAddress.String(), quantumsToDeposit: big.NewInt(123_456_789), - // Should mint `26_926_789_878_423 / 41_152` shares. - expectedTotalShares: big.NewRat(26_953_716_496_215, 41_152), - expectedOwnerShares: big.NewRat(26_926_789_878_423, 41_152), + // 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.NewRat(10, 1), + totalShares: big.NewInt(10), owner: constants.AliceAccAddress.String(), quantumsToDeposit: big.NewInt(1), expectedErr: vaulttypes.ErrNonPositiveEquity, @@ -241,7 +249,7 @@ func TestMintShares(t *testing.T) { "Equity 1, TotalShares 1, Deposit 0": { vaultId: constants.Vault_Clob_1, equity: big.NewInt(1), - totalShares: big.NewRat(1, 1), + totalShares: big.NewInt(1), owner: constants.AliceAccAddress.String(), quantumsToDeposit: big.NewInt(0), expectedErr: vaulttypes.ErrInvalidDepositAmount, @@ -286,16 +294,17 @@ func TestMintShares(t *testing.T) { err := tApp.App.VaultKeeper.SetTotalShares( ctx, tc.vaultId, - vaulttypes.BigRatToNumShares(tc.totalShares), + 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.BigRatToNumShares(tc.ownerShares), + vaulttypes.BigIntToNumShares(tc.ownerShares), ) require.NoError(t, err) } @@ -314,12 +323,12 @@ func TestMintShares(t *testing.T) { totalShares, _ := tApp.App.VaultKeeper.GetTotalShares(ctx, tc.vaultId) require.Equal( t, - vaulttypes.BigRatToNumShares(tc.totalShares), + vaulttypes.BigIntToNumShares(tc.totalShares), totalShares, ) // Check that OwnerShares is unchanged. ownerShares, _ := tApp.App.VaultKeeper.GetOwnerShares(ctx, tc.vaultId, tc.owner) - require.Equal(t, vaulttypes.BigRatToNumShares(tc.ownerShares), ownerShares) + require.Equal(t, vaulttypes.BigIntToNumShares(tc.ownerShares), ownerShares) } else { require.NoError(t, err) // Check that TotalShares is as expected. @@ -327,7 +336,7 @@ func TestMintShares(t *testing.T) { require.True(t, exists) require.Equal( t, - vaulttypes.BigRatToNumShares(tc.expectedTotalShares), + vaulttypes.BigIntToNumShares(tc.expectedTotalShares), totalShares, ) // Check that OwnerShares is as expected. @@ -335,7 +344,7 @@ func TestMintShares(t *testing.T) { require.True(t, exists) require.Equal( t, - vaulttypes.BigRatToNumShares(tc.expectedOwnerShares), + vaulttypes.BigIntToNumShares(tc.expectedOwnerShares), ownerShares, ) } diff --git a/protocol/x/vault/keeper/vault.go b/protocol/x/vault/keeper/vault.go index feb796e43a..7e3ebfe667 100644 --- a/protocol/x/vault/keeper/vault.go +++ b/protocol/x/vault/keeper/vault.go @@ -40,8 +40,7 @@ func (k Keeper) DecommissionNonPositiveEquityVaults( k.cdc.MustUnmarshal(totalSharesIterator.Value(), &totalShares) // Skip if TotalShares is non-positive. - totalSharesRat, err := totalShares.ToBigRat() - if err != nil || totalSharesRat.Sign() <= 0 { + if totalShares.NumShares.BigInt().Sign() <= 0 { continue } diff --git a/protocol/x/vault/keeper/vault_test.go b/protocol/x/vault/keeper/vault_test.go index 078d1ba526..319fc0f4a4 100644 --- a/protocol/x/vault/keeper/vault_test.go +++ b/protocol/x/vault/keeper/vault_test.go @@ -20,7 +20,7 @@ func TestDecommissionNonPositiveEquityVaults(t *testing.T) { // Vault IDs. vaultIds []vaulttypes.VaultId // Total shares of above vaults. - totalShares []*big.Rat + totalShares []*big.Int // Equities of above vaults. equities []*big.Int @@ -33,9 +33,9 @@ func TestDecommissionNonPositiveEquityVaults(t *testing.T) { constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []*big.Rat{ - big.NewRat(7, 1), - big.NewRat(7, 1), + totalShares: []*big.Int{ + big.NewInt(7), + big.NewInt(7), }, equities: []*big.Int{ big.NewInt(1), @@ -51,9 +51,9 @@ func TestDecommissionNonPositiveEquityVaults(t *testing.T) { constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []*big.Rat{ - big.NewRat(7, 1), - big.NewRat(7, 1), + totalShares: []*big.Int{ + big.NewInt(7), + big.NewInt(7), }, equities: []*big.Int{ big.NewInt(1), @@ -69,9 +69,9 @@ func TestDecommissionNonPositiveEquityVaults(t *testing.T) { constants.Vault_Clob_0, constants.Vault_Clob_1, }, - totalShares: []*big.Rat{ - big.NewRat(7, 1), - big.NewRat(7, 1), + totalShares: []*big.Int{ + big.NewInt(7), + big.NewInt(7), }, equities: []*big.Int{ big.NewInt(0), @@ -122,14 +122,14 @@ func TestDecommissionNonPositiveEquityVaults(t *testing.T) { err := k.SetTotalShares( ctx, vaultId, - vaulttypes.BigRatToNumShares(tc.totalShares[i]), + vaulttypes.BigIntToNumShares(tc.totalShares[i]), ) require.NoError(t, err) err = k.SetOwnerShares( ctx, vaultId, testOwner, - vaulttypes.BigRatToNumShares(big.NewRat(7, 1)), + vaulttypes.BigIntToNumShares(big.NewInt(7)), ) require.NoError(t, err) } @@ -186,8 +186,8 @@ func TestDecommissionVault(t *testing.T) { ctx := tApp.InitChain() k := tApp.App.VaultKeeper - shares := vaulttypes.BigRatToNumShares( - big.NewRat(7, 1), + shares := vaulttypes.BigIntToNumShares( + big.NewInt(7), ) if tc.totalSharesExists { diff --git a/protocol/x/vault/types/num_shares.go b/protocol/x/vault/types/num_shares.go index 0a540bb281..040f1b749b 100644 --- a/protocol/x/vault/types/num_shares.go +++ b/protocol/x/vault/types/num_shares.go @@ -6,25 +6,12 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/dtypes" ) -// ToBigRat converts a NumShares to a big.Rat. -// Returns an error if the denominator is zero. -func (n NumShares) ToBigRat() (*big.Rat, error) { - if n.Numerator.IsNil() || n.Denominator.IsNil() { - return nil, ErrNilFraction - } - if n.Denominator.Cmp(dtypes.NewInt(0)) == 0 { - return nil, ErrZeroDenominator - } - - return new(big.Rat).SetFrac(n.Numerator.BigInt(), n.Denominator.BigInt()), nil -} - -func BigRatToNumShares(rat *big.Rat) (n NumShares) { - if rat == nil { +// BigIntToNumShares returns a NumShares given a big.Int. +func BigIntToNumShares(num *big.Int) (n NumShares) { + if num == nil { return n } return NumShares{ - Numerator: dtypes.NewIntFromBigInt(rat.Num()), - Denominator: dtypes.NewIntFromBigInt(rat.Denom()), + NumShares: dtypes.NewIntFromBigInt(num), } } diff --git a/protocol/x/vault/types/num_shares_test.go b/protocol/x/vault/types/num_shares_test.go index 3bb15932ff..ec1713dbbb 100644 --- a/protocol/x/vault/types/num_shares_test.go +++ b/protocol/x/vault/types/num_shares_test.go @@ -9,103 +9,26 @@ import ( "github.com/stretchr/testify/require" ) -func TestNumShares_ToBigRat(t *testing.T) { +func TestBigIntToNumShares(t *testing.T) { tests := map[string]struct { - n types.NumShares - expectedRat *big.Rat - expectedErr error - }{ - "Success - 1/1": { - n: types.NumShares{ - Numerator: dtypes.NewInt(1), - Denominator: dtypes.NewInt(1), - }, - expectedRat: big.NewRat(1, 1), - }, - "Success - 1/77": { - n: types.NumShares{ - Numerator: dtypes.NewInt(1), - Denominator: dtypes.NewInt(77), - }, - expectedRat: big.NewRat(1, 77), - }, - "Success - 99/2": { - n: types.NumShares{ - Numerator: dtypes.NewInt(99), - Denominator: dtypes.NewInt(2), - }, - expectedRat: big.NewRat(99, 2), - }, - "Failure - numerator is nil": { - n: types.NumShares{ - Denominator: dtypes.NewInt(77), - }, - expectedErr: types.ErrNilFraction, - }, - "Failure - denominator is nil": { - n: types.NumShares{ - Numerator: dtypes.NewInt(77), - }, - expectedErr: types.ErrNilFraction, - }, - "Failure - denominator is 0": { - n: types.NumShares{ - Numerator: dtypes.NewInt(77), - Denominator: dtypes.NewInt(0), - }, - expectedErr: types.ErrZeroDenominator, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - rat, err := tc.n.ToBigRat() - if tc.expectedErr == nil { - require.NoError(t, err) - require.Equal(t, tc.expectedRat, rat) - } else { - require.ErrorIs(t, err, tc.expectedErr) - require.True(t, rat == nil) - } - }) - } -} - -func TestBigRatToNumShares(t *testing.T) { - tests := map[string]struct { - rat *big.Rat + num *big.Int expectedNumShares types.NumShares }{ - "Success - 1/1": { - rat: big.NewRat(1, 1), - expectedNumShares: types.NumShares{ - Numerator: dtypes.NewInt(1), - Denominator: dtypes.NewInt(1), - }, - }, - "Success - 1/2": { - rat: big.NewRat(1, 2), - expectedNumShares: types.NumShares{ - Numerator: dtypes.NewInt(1), - Denominator: dtypes.NewInt(2), - }, - }, - "Success - 5/3": { - rat: big.NewRat(5, 3), + "Success - 1": { + num: big.NewInt(1), expectedNumShares: types.NumShares{ - Numerator: dtypes.NewInt(5), - Denominator: dtypes.NewInt(3), + NumShares: dtypes.NewInt(1), }, }, - "Success - rat is nil": { - rat: nil, + "Success - num is nil": { + num: nil, expectedNumShares: types.NumShares{}, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { - n := types.BigRatToNumShares(tc.rat) + n := types.BigIntToNumShares(tc.num) require.Equal(t, tc.expectedNumShares, n) }) } diff --git a/protocol/x/vault/types/vault.pb.go b/protocol/x/vault/types/vault.pb.go index 54d9648e45..704b30d081 100644 --- a/protocol/x/vault/types/vault.pb.go +++ b/protocol/x/vault/types/vault.pb.go @@ -107,13 +107,10 @@ func (m *VaultId) GetNumber() uint32 { return 0 } -// NumShares represents the number of shares in a vault in the -// format of a rational number `numerator / denominator`. +// NumShares represents the number of shares in a vault. type NumShares struct { - // Numerator. - Numerator github_com_dydxprotocol_v4_chain_protocol_dtypes.SerializableInt `protobuf:"bytes,1,opt,name=numerator,proto3,customtype=github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt" json:"numerator"` - // Denominator. - Denominator github_com_dydxprotocol_v4_chain_protocol_dtypes.SerializableInt `protobuf:"bytes,2,opt,name=denominator,proto3,customtype=github.com/dydxprotocol/v4-chain/protocol/dtypes.SerializableInt" json:"denominator"` + // Number of 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{} } @@ -158,7 +155,7 @@ func init() { func init() { proto.RegisterFile("dydxprotocol/vault/vault.proto", fileDescriptor_32accb5830bb2860) } var fileDescriptor_32accb5830bb2860 = []byte{ - // 320 bytes of a gzipped FileDescriptorProto + // 300 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, 0x81, 0x90, 0x7a, 0x60, 0x41, 0x21, 0x21, 0x64, 0x79, 0x3d, 0xb0, 0x8c, 0x94, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, @@ -166,19 +163,18 @@ var fileDescriptor_32accb5830bb2860 = []byte{ 0x19, 0x72, 0xb1, 0x94, 0x54, 0x16, 0xa4, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x19, 0xc9, 0xea, 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, - 0xdd, 0x67, 0xe4, 0xe2, 0xf4, 0x2b, 0xcd, 0x0d, 0xce, 0x48, 0x2c, 0x4a, 0x2d, 0x16, 0x4a, 0xe3, - 0xe2, 0xcc, 0x2b, 0xcd, 0x4d, 0x2d, 0x4a, 0x2c, 0xc9, 0x2f, 0x02, 0x9b, 0xce, 0xe3, 0xe4, 0x71, - 0xe2, 0x9e, 0x3c, 0xc3, 0xad, 0x7b, 0xf2, 0x0e, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, - 0xf9, 0xb9, 0xfa, 0xa8, 0x7e, 0x32, 0xd1, 0x4d, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x87, 0x8b, 0xa4, - 0x80, 0x6c, 0x2c, 0xd6, 0x0b, 0x4e, 0x2d, 0xca, 0x4c, 0xcc, 0xc9, 0xac, 0x4a, 0x4c, 0xca, 0x49, - 0xf5, 0xcc, 0x2b, 0x09, 0x42, 0x18, 0x2d, 0x94, 0xc5, 0xc5, 0x9d, 0x92, 0x9a, 0x97, 0x9f, 0x9b, - 0x99, 0x07, 0xb6, 0x89, 0x89, 0xca, 0x36, 0x21, 0x1b, 0xae, 0x65, 0xc3, 0xc5, 0x09, 0x0f, 0x0c, - 0x21, 0x29, 0x2e, 0xb1, 0x30, 0xc7, 0x50, 0x9f, 0x90, 0xf8, 0x90, 0xc8, 0x00, 0xd7, 0xf8, 0x50, - 0xbf, 0xe0, 0x00, 0x57, 0x67, 0x4f, 0x37, 0x4f, 0x57, 0x17, 0x01, 0x06, 0x21, 0x61, 0x2e, 0x7e, - 0x24, 0x39, 0x67, 0x1f, 0x7f, 0x27, 0x01, 0x46, 0xa7, 0xc0, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, - 0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, - 0x3c, 0x96, 0x63, 0x88, 0x32, 0x27, 0xde, 0x99, 0x15, 0xd0, 0x88, 0x07, 0xbb, 0x36, 0x89, 0x0d, - 0x2c, 0x6e, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x76, 0x09, 0x36, 0xbc, 0x1b, 0x02, 0x00, 0x00, + 0x95, 0x70, 0x71, 0xfa, 0x95, 0xe6, 0x06, 0x67, 0x24, 0x16, 0xa5, 0x16, 0x0b, 0xa5, 0x73, 0x71, + 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) { @@ -235,25 +231,15 @@ func (m *NumShares) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l { - size := m.Denominator.Size() + size := m.NumShares.Size() i -= size - if _, err := m.Denominator.MarshalTo(dAtA[i:]); err != nil { + if _, err := m.NumShares.MarshalTo(dAtA[i:]); err != nil { return 0, err } i = encodeVarintVault(dAtA, i, uint64(size)) } i-- dAtA[i] = 0x12 - { - size := m.Numerator.Size() - i -= size - if _, err := m.Numerator.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintVault(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -289,9 +275,7 @@ func (m *NumShares) Size() (n int) { } var l int _ = l - l = m.Numerator.Size() - n += 1 + l + sovVault(uint64(l)) - l = m.Denominator.Size() + l = m.NumShares.Size() n += 1 + l + sovVault(uint64(l)) return n } @@ -419,42 +403,9 @@ func (m *NumShares) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: NumShares: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Numerator", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowVault - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthVault - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthVault - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if err := m.Numerator.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Denominator", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NumShares", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -481,7 +432,7 @@ func (m *NumShares) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.Denominator.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.NumShares.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex From 3555a49a9bb191f380ba639841975897ef369cf4 Mon Sep 17 00:00:00 2001 From: Tian Date: Thu, 28 Mar 2024 13:56:36 -0400 Subject: [PATCH 32/35] [TRA-114] implement full vault strategy (#1262) * implement full vault strategy * adjust tests to make skew obvious --- protocol/x/vault/keeper/orders.go | 175 ++++++++++---- protocol/x/vault/keeper/orders_test.go | 265 +++++++++++++++------ protocol/x/vault/keeper/vault.go | 19 ++ protocol/x/vault/types/errors.go | 5 + protocol/x/vault/types/expected_keepers.go | 4 + protocol/x/vault/types/params.go | 6 + protocol/x/vault/types/params_test.go | 11 + 7 files changed, 373 insertions(+), 112 deletions(-) diff --git a/protocol/x/vault/keeper/orders.go b/protocol/x/vault/keeper/orders.go index dae848b36a..b5b5c29811 100644 --- a/protocol/x/vault/keeper/orders.go +++ b/protocol/x/vault/keeper/orders.go @@ -112,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, @@ -157,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.OrderSizePpm, + ) + 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, @@ -171,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.OrderSizePpm*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 fbb9b036fc..fc8853225e 100644 --- a/protocol/x/vault/keeper/orders_test.go +++ b/protocol/x/vault/keeper/orders_test.go @@ -9,6 +9,7 @@ import ( "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" @@ -17,14 +18,6 @@ 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. @@ -79,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 }, }, @@ -188,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 }, }, @@ -217,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]) } } @@ -232,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. @@ -249,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 + OrderSizePpm: 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_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 + OrderSizePpm: 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_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], @@ -322,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 { @@ -352,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() @@ -365,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, @@ -382,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/vault.go b/protocol/x/vault/keeper/vault.go index 7e3ebfe667..6bcd3ceb75 100644 --- a/protocol/x/vault/keeper/vault.go +++ b/protocol/x/vault/keeper/vault.go @@ -28,6 +28,25 @@ func (k Keeper) GetVaultEquity( 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, diff --git a/protocol/x/vault/types/errors.go b/protocol/x/vault/types/errors.go index 9059a4d9e1..f3fd2f9180 100644 --- a/protocol/x/vault/types/errors.go +++ b/protocol/x/vault/types/errors.go @@ -55,4 +55,9 @@ var ( 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 fda97ee6b4..9ecaf2dc17 100644 --- a/protocol/x/vault/types/expected_keepers.go +++ b/protocol/x/vault/types/expected_keepers.go @@ -57,6 +57,10 @@ type SubaccountsKeeper interface { bigMaintenanceMargin *big.Int, err error, ) + GetSubaccount( + ctx sdk.Context, + id satypes.SubaccountId, + ) satypes.Subaccount TransferFundsFromSubaccountToSubaccount( ctx sdk.Context, senderSubaccountId satypes.SubaccountId, diff --git a/protocol/x/vault/types/params.go b/protocol/x/vault/types/params.go index 4f3cae9d26..e6450a5750 100644 --- a/protocol/x/vault/types/params.go +++ b/protocol/x/vault/types/params.go @@ -1,5 +1,7 @@ package types +import "math" + // DefaultParams returns a default set of `x/vault` parameters. func DefaultParams() Params { return Params{ @@ -14,6 +16,10 @@ func DefaultParams() Params { // 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 diff --git a/protocol/x/vault/types/params_test.go b/protocol/x/vault/types/params_test.go index 82cec0b062..d0e23eb04c 100644 --- a/protocol/x/vault/types/params_test.go +++ b/protocol/x/vault/types/params_test.go @@ -18,6 +18,17 @@ func TestValidate(t *testing.T) { 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, + OrderSizePpm: 100_000, + OrderExpirationSeconds: 5, + }, + expectedErr: types.ErrInvalidLayers, + }, "Failure - SpreadMinPpm is 0": { params: types.Params{ Layers: 2, From 16bc93a37584e6c77c3771865e6ebeeec1079463 Mon Sep 17 00:00:00 2001 From: dydxwill <119354122+dydxwill@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:16:30 -0400 Subject: [PATCH 33/35] change default (#1272) --- indexer/services/socks/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 }), From 31a0fb7561dce84753a4d6fd20bd336fe82a216f Mon Sep 17 00:00:00 2001 From: Tian Date: Thu, 28 Mar 2024 14:27:26 -0400 Subject: [PATCH 34/35] rename order_size_ppm to order_size_pct_ppm in vault params (#1271) --- .../src/codegen/dydxprotocol/vault/params.ts | 14 ++--- proto/dydxprotocol/vault/params.proto | 2 +- .../app/testdata/default_genesis_state.json | 2 +- .../scripts/genesis/sample_pregenesis.json | 2 +- protocol/testutil/constants/genesis.go | 2 +- .../keeper/msg_server_update_params_test.go | 4 +- protocol/x/vault/keeper/orders.go | 4 +- protocol/x/vault/keeper/orders_test.go | 8 +-- protocol/x/vault/keeper/params_test.go | 4 +- protocol/x/vault/types/errors.go | 4 +- protocol/x/vault/types/params.go | 6 +- protocol/x/vault/types/params.pb.go | 59 ++++++++++--------- protocol/x/vault/types/params_test.go | 12 ++-- 13 files changed, 62 insertions(+), 61 deletions(-) diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/params.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/params.ts index af968398bc..29ecf2563d 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/params.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/params.ts @@ -23,7 +23,7 @@ export interface Params { skewFactorPpm: number; /** The percentage of vault equity that each order is sized at. */ - orderSizePpm: number; + orderSizePctPpm: number; /** The duration that a vault's orders are valid for. */ orderExpirationSeconds: number; @@ -51,7 +51,7 @@ export interface ParamsSDKType { skew_factor_ppm: number; /** The percentage of vault equity that each order is sized at. */ - order_size_ppm: number; + order_size_pct_ppm: number; /** The duration that a vault's orders are valid for. */ order_expiration_seconds: number; @@ -63,7 +63,7 @@ function createBaseParams(): Params { spreadMinPpm: 0, spreadBufferPpm: 0, skewFactorPpm: 0, - orderSizePpm: 0, + orderSizePctPpm: 0, orderExpirationSeconds: 0 }; } @@ -86,8 +86,8 @@ export const Params = { writer.uint32(32).uint32(message.skewFactorPpm); } - if (message.orderSizePpm !== 0) { - writer.uint32(40).uint32(message.orderSizePpm); + if (message.orderSizePctPpm !== 0) { + writer.uint32(40).uint32(message.orderSizePctPpm); } if (message.orderExpirationSeconds !== 0) { @@ -123,7 +123,7 @@ export const Params = { break; case 5: - message.orderSizePpm = reader.uint32(); + message.orderSizePctPpm = reader.uint32(); break; case 6: @@ -145,7 +145,7 @@ export const Params = { message.spreadMinPpm = object.spreadMinPpm ?? 0; message.spreadBufferPpm = object.spreadBufferPpm ?? 0; message.skewFactorPpm = object.skewFactorPpm ?? 0; - message.orderSizePpm = object.orderSizePpm ?? 0; + message.orderSizePctPpm = object.orderSizePctPpm ?? 0; message.orderExpirationSeconds = object.orderExpirationSeconds ?? 0; return message; } diff --git a/proto/dydxprotocol/vault/params.proto b/proto/dydxprotocol/vault/params.proto index 8e1c8b2378..ec80d2e971 100644 --- a/proto/dydxprotocol/vault/params.proto +++ b/proto/dydxprotocol/vault/params.proto @@ -21,7 +21,7 @@ message Params { uint32 skew_factor_ppm = 4; // The percentage of vault equity that each order is sized at. - uint32 order_size_ppm = 5; + uint32 order_size_pct_ppm = 5; // The duration that a vault's orders are valid for. uint32 order_expiration_seconds = 6; diff --git a/protocol/app/testdata/default_genesis_state.json b/protocol/app/testdata/default_genesis_state.json index 5a95e2d5f3..ead2cf2d7e 100644 --- a/protocol/app/testdata/default_genesis_state.json +++ b/protocol/app/testdata/default_genesis_state.json @@ -461,7 +461,7 @@ "spread_min_ppm": 3000, "spread_buffer_ppm": 1500, "skew_factor_ppm": 500000, - "order_size_ppm": 100000, + "order_size_pct_ppm": 100000, "order_expiration_seconds": 2 } }, diff --git a/protocol/scripts/genesis/sample_pregenesis.json b/protocol/scripts/genesis/sample_pregenesis.json index 68307d6092..40b3eb7b9d 100644 --- a/protocol/scripts/genesis/sample_pregenesis.json +++ b/protocol/scripts/genesis/sample_pregenesis.json @@ -1810,7 +1810,7 @@ "params": { "layers": 2, "order_expiration_seconds": 2, - "order_size_ppm": 100000, + "order_size_pct_ppm": 100000, "skew_factor_ppm": 500000, "spread_buffer_ppm": 1500, "spread_min_ppm": 3000 diff --git a/protocol/testutil/constants/genesis.go b/protocol/testutil/constants/genesis.go index 3c0282ec6b..6ee1737fba 100644 --- a/protocol/testutil/constants/genesis.go +++ b/protocol/testutil/constants/genesis.go @@ -1498,7 +1498,7 @@ const GenesisState = `{ "spread_min_ppm": 3000, "spread_buffer_ppm": 1500, "skew_factor_ppm": 500000, - "order_size_ppm": 100000, + "order_size_pct_ppm": 100000, "order_expiration_seconds": 2 } }, diff --git a/protocol/x/vault/keeper/msg_server_update_params_test.go b/protocol/x/vault/keeper/msg_server_update_params_test.go index acfc221ecb..29ceef08ce 100644 --- a/protocol/x/vault/keeper/msg_server_update_params_test.go +++ b/protocol/x/vault/keeper/msg_server_update_params_test.go @@ -41,11 +41,11 @@ func TestMsgUpdateParams(t *testing.T) { SpreadMinPpm: 4_000, SpreadBufferPpm: 2_000, SkewFactorPpm: 500_000, - OrderSizePpm: 0, // invalid + OrderSizePctPpm: 0, // invalid OrderExpirationSeconds: 5, }, }, - expectedErr: types.ErrInvalidOrderSizePpm.Error(), + expectedErr: types.ErrInvalidOrderSizePctPpm.Error(), }, } diff --git a/protocol/x/vault/keeper/orders.go b/protocol/x/vault/keeper/orders.go index b5b5c29811..12512a72c9 100644 --- a/protocol/x/vault/keeper/orders.go +++ b/protocol/x/vault/keeper/orders.go @@ -189,7 +189,7 @@ func (k Keeper) GetVaultClobOrders( // = order_size_pct * equity / (price * 10^(exponent - quote_atomic_resolution + base_atomic_resolution)) orderSizeBaseQuantums := lib.BigRatMulPpm( new(big.Rat).SetInt(equity), - params.OrderSizePpm, + params.OrderSizePctPpm, ) orderSizeBaseQuantums = orderSizeBaseQuantums.Quo( orderSizeBaseQuantums, @@ -228,7 +228,7 @@ func (k Keeper) GetVaultClobOrders( ) *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.OrderSizePpm*layer)), + new(big.Int).SetUint64(uint64(params.OrderSizePctPpm*layer)), lib.BigIntOneMillion(), ) diff --git a/protocol/x/vault/keeper/orders_test.go b/protocol/x/vault/keeper/orders_test.go index fc8853225e..ae4313499c 100644 --- a/protocol/x/vault/keeper/orders_test.go +++ b/protocol/x/vault/keeper/orders_test.go @@ -254,7 +254,7 @@ func TestGetVaultClobOrders(t *testing.T) { SpreadMinPpm: 3_000, // 30 bps SpreadBufferPpm: 1_500, // 15 bps SkewFactorPpm: 500_000, // 0.5 - OrderSizePpm: 100_000, // 10% + OrderSizePctPpm: 100_000, // 10% OrderExpirationSeconds: 2, // 2 seconds }, vaultId: constants.Vault_Clob_0, @@ -273,7 +273,7 @@ func TestGetVaultClobOrders(t *testing.T) { // 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_ppm * equity / oracle_price`. + // 1. `order_size_pct_ppm * equity / oracle_price`. expectedOrderSubticks: []uint64{ // spreadPpm = max(3_000, 1_500 + 50) = 3_000 // spread = 0.003 @@ -312,7 +312,7 @@ func TestGetVaultClobOrders(t *testing.T) { SpreadMinPpm: 3_000, // 30 bps SpreadBufferPpm: 8_500, // 85 bps SkewFactorPpm: 900_000, // 0.9 - OrderSizePpm: 200_000, // 20% + OrderSizePctPpm: 200_000, // 20% OrderExpirationSeconds: 4, // 4 seconds }, vaultId: constants.Vault_Clob_1, @@ -331,7 +331,7 @@ func TestGetVaultClobOrders(t *testing.T) { // 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_ppm * equity / oracle_price`. + // 1. `order_size_pct_ppm * equity / oracle_price`. expectedOrderSubticks: []uint64{ // spreadPpm = max(3_000, 8_500 + 50) = 8_550 // spread = 0.00855 diff --git a/protocol/x/vault/keeper/params_test.go b/protocol/x/vault/keeper/params_test.go index 54b396c3ec..2807715b6a 100644 --- a/protocol/x/vault/keeper/params_test.go +++ b/protocol/x/vault/keeper/params_test.go @@ -23,7 +23,7 @@ func TestGetSetParams(t *testing.T) { SpreadMinPpm: 4_000, SpreadBufferPpm: 2_000, SkewFactorPpm: 999_999, - OrderSizePpm: 200_000, + OrderSizePctPpm: 200_000, OrderExpirationSeconds: 10, } err := k.SetParams(ctx, newParams) @@ -36,7 +36,7 @@ func TestGetSetParams(t *testing.T) { SpreadMinPpm: 4_000, SpreadBufferPpm: 2_000, SkewFactorPpm: 1_000_000, - OrderSizePpm: 200_000, + OrderSizePctPpm: 200_000, OrderExpirationSeconds: 0, // invalid } err = k.SetParams(ctx, invalidParams) diff --git a/protocol/x/vault/types/errors.go b/protocol/x/vault/types/errors.go index f3fd2f9180..e38d0c94d1 100644 --- a/protocol/x/vault/types/errors.go +++ b/protocol/x/vault/types/errors.go @@ -40,10 +40,10 @@ var ( 7, "Fraction is nil", ) - ErrInvalidOrderSizePpm = errorsmod.Register( + ErrInvalidOrderSizePctPpm = errorsmod.Register( ModuleName, 8, - "OrderSizePpm must be strictly greater than 0", + "OrderSizePctPpm must be strictly greater than 0", ) ErrInvalidOrderExpirationSeconds = errorsmod.Register( ModuleName, diff --git a/protocol/x/vault/types/params.go b/protocol/x/vault/types/params.go index e6450a5750..ad7994a074 100644 --- a/protocol/x/vault/types/params.go +++ b/protocol/x/vault/types/params.go @@ -9,7 +9,7 @@ func DefaultParams() Params { SpreadMinPpm: 3_000, // 30 bps SpreadBufferPpm: 1_500, // 15 bps SkewFactorPpm: 500_000, // 0.5 - OrderSizePpm: 100_000, // 10% + OrderSizePctPpm: 100_000, // 10% OrderExpirationSeconds: 2, // 2 seconds } } @@ -25,8 +25,8 @@ func (p Params) Validate() error { return ErrInvalidSpreadMinPpm } // Order size must be positive. - if p.OrderSizePpm == 0 { - return ErrInvalidOrderSizePpm + if p.OrderSizePctPpm == 0 { + return ErrInvalidOrderSizePctPpm } // Order expiration seconds must be positive. if p.OrderExpirationSeconds == 0 { diff --git a/protocol/x/vault/types/params.pb.go b/protocol/x/vault/types/params.pb.go index 428d8b1836..8f7984ff35 100644 --- a/protocol/x/vault/types/params.pb.go +++ b/protocol/x/vault/types/params.pb.go @@ -36,7 +36,7 @@ type Params struct { // 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. - OrderSizePpm uint32 `protobuf:"varint,5,opt,name=order_size_ppm,json=orderSizePpm,proto3" json:"order_size_ppm,omitempty"` + 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"` } @@ -102,9 +102,9 @@ func (m *Params) GetSkewFactorPpm() uint32 { return 0 } -func (m *Params) GetOrderSizePpm() uint32 { +func (m *Params) GetOrderSizePctPpm() uint32 { if m != nil { - return m.OrderSizePpm + return m.OrderSizePctPpm } return 0 } @@ -123,25 +123,26 @@ func init() { func init() { proto.RegisterFile("dydxprotocol/vault/params.proto", fileDescriptor_6043e0b8bfdbca9f) } var fileDescriptor_6043e0b8bfdbca9f = []byte{ - // 287 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0xd0, 0x3f, 0x4b, 0xc4, 0x30, - 0x18, 0xc7, 0xf1, 0xc6, 0x3f, 0x1d, 0x82, 0x77, 0x87, 0x1d, 0x8e, 0x4e, 0x51, 0xe4, 0x10, 0x11, - 0x6c, 0x07, 0x05, 0x9d, 0x0f, 0x74, 0x13, 0x4e, 0x6f, 0x73, 0x29, 0x69, 0x9b, 0x7a, 0xc1, 0xb6, - 0x09, 0x49, 0xaa, 0xed, 0xbd, 0x0a, 0x5f, 0x96, 0xe3, 0x8d, 0x8e, 0xd2, 0xbe, 0x0b, 0x27, 0xe9, - 0x93, 0x72, 0x78, 0x63, 0xbe, 0xbf, 0x0f, 0x04, 0x1e, 0x7c, 0x92, 0x36, 0x69, 0x2d, 0x95, 0x30, - 0x22, 0x11, 0x79, 0xf8, 0x4e, 0xab, 0xdc, 0x84, 0x92, 0x2a, 0x5a, 0xe8, 0x00, 0xaa, 0xe7, 0xfd, - 0x07, 0x01, 0x80, 0xb3, 0x5f, 0x84, 0xdd, 0x05, 0x20, 0x6f, 0x8a, 0xdd, 0x9c, 0x36, 0x4c, 0x69, - 0x1f, 0x9d, 0xa2, 0x8b, 0xd1, 0xf3, 0xf0, 0xf2, 0x66, 0x78, 0xac, 0xa5, 0x62, 0x34, 0x8d, 0x0a, - 0x5e, 0x46, 0x52, 0x16, 0xfe, 0x1e, 0xec, 0x47, 0xb6, 0x3e, 0xf2, 0x72, 0x21, 0x0b, 0xef, 0x12, - 0x1f, 0x0f, 0x2a, 0xae, 0xb2, 0x8c, 0x29, 0x80, 0xfb, 0x00, 0x27, 0x76, 0x98, 0x43, 0xef, 0xed, - 0x39, 0x9e, 0xe8, 0x37, 0xf6, 0x11, 0x65, 0x34, 0x31, 0xc2, 0xca, 0x03, 0x90, 0xa3, 0x3e, 0x3f, - 0x40, 0xed, 0xdd, 0x0c, 0x8f, 0x85, 0x4a, 0x99, 0x8a, 0x34, 0x5f, 0x33, 0x60, 0x87, 0xf6, 0x67, - 0xa8, 0x4b, 0xbe, 0x66, 0xbd, 0xba, 0xc3, 0xbe, 0x55, 0xac, 0x96, 0x5c, 0x51, 0xc3, 0x45, 0x19, - 0x69, 0x96, 0x88, 0x32, 0xd5, 0xbe, 0x0b, 0x7e, 0x0a, 0xfb, 0xfd, 0x76, 0x5e, 0xda, 0x75, 0xfe, - 0xf4, 0xd5, 0x12, 0xb4, 0x69, 0x09, 0xfa, 0x69, 0x09, 0xfa, 0xec, 0x88, 0xb3, 0xe9, 0x88, 0xf3, - 0xdd, 0x11, 0xe7, 0xe5, 0xf6, 0x95, 0x9b, 0x55, 0x15, 0x07, 0x89, 0x28, 0xc2, 0xdd, 0xb3, 0xde, - 0x5c, 0x25, 0x2b, 0xca, 0xcb, 0x70, 0x5b, 0xea, 0xe1, 0xd4, 0xa6, 0x91, 0x4c, 0xc7, 0x2e, 0xf4, - 0xeb, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xce, 0x3a, 0xeb, 0x7c, 0x8d, 0x01, 0x00, 0x00, + // 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) { @@ -169,8 +170,8 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x30 } - if m.OrderSizePpm != 0 { - i = encodeVarintParams(dAtA, i, uint64(m.OrderSizePpm)) + if m.OrderSizePctPpm != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.OrderSizePctPpm)) i-- dAtA[i] = 0x28 } @@ -226,8 +227,8 @@ func (m *Params) Size() (n int) { if m.SkewFactorPpm != 0 { n += 1 + sovParams(uint64(m.SkewFactorPpm)) } - if m.OrderSizePpm != 0 { - n += 1 + sovParams(uint64(m.OrderSizePpm)) + if m.OrderSizePctPpm != 0 { + n += 1 + sovParams(uint64(m.OrderSizePctPpm)) } if m.OrderExpirationSeconds != 0 { n += 1 + sovParams(uint64(m.OrderExpirationSeconds)) @@ -348,9 +349,9 @@ func (m *Params) Unmarshal(dAtA []byte) error { } case 5: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field OrderSizePpm", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field OrderSizePctPpm", wireType) } - m.OrderSizePpm = 0 + m.OrderSizePctPpm = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowParams @@ -360,7 +361,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.OrderSizePpm |= uint32(b&0x7F) << shift + m.OrderSizePctPpm |= uint32(b&0x7F) << shift if b < 0x80 { break } diff --git a/protocol/x/vault/types/params_test.go b/protocol/x/vault/types/params_test.go index d0e23eb04c..ba3131b37f 100644 --- a/protocol/x/vault/types/params_test.go +++ b/protocol/x/vault/types/params_test.go @@ -24,7 +24,7 @@ func TestValidate(t *testing.T) { SpreadMinPpm: 3_000, SpreadBufferPpm: 1_500, SkewFactorPpm: 500_000, - OrderSizePpm: 100_000, + OrderSizePctPpm: 100_000, OrderExpirationSeconds: 5, }, expectedErr: types.ErrInvalidLayers, @@ -35,21 +35,21 @@ func TestValidate(t *testing.T) { SpreadMinPpm: 0, SpreadBufferPpm: 1_500, SkewFactorPpm: 500_000, - OrderSizePpm: 100_000, + OrderSizePctPpm: 100_000, OrderExpirationSeconds: 5, }, expectedErr: types.ErrInvalidSpreadMinPpm, }, - "Failure - OrderSizePpm is 0": { + "Failure - OrderSizePctPpm is 0": { params: types.Params{ Layers: 2, SpreadMinPpm: 3_000, SpreadBufferPpm: 1_500, SkewFactorPpm: 500_000, - OrderSizePpm: 0, + OrderSizePctPpm: 0, OrderExpirationSeconds: 5, }, - expectedErr: types.ErrInvalidOrderSizePpm, + expectedErr: types.ErrInvalidOrderSizePctPpm, }, "Failure - OrderExpirationSeconds is 0": { params: types.Params{ @@ -57,7 +57,7 @@ func TestValidate(t *testing.T) { SpreadMinPpm: 3_000, SpreadBufferPpm: 1_500, SkewFactorPpm: 500_000, - OrderSizePpm: 100_000, + OrderSizePctPpm: 100_000, OrderExpirationSeconds: 0, }, expectedErr: types.ErrInvalidOrderExpirationSeconds, From 56761ac8f1a0ecc60a300e6875917efaa36f1889 Mon Sep 17 00:00:00 2001 From: Tian Date: Thu, 28 Mar 2024 15:23:27 -0400 Subject: [PATCH 35/35] [TRA-111] add vault query (#1273) * add vault query * remove owner shares from query response --- .../codegen/dydxprotocol/vault/query.lcd.ts | 10 +- .../dydxprotocol/vault/query.rpc.Query.ts | 16 +- .../src/codegen/dydxprotocol/vault/query.ts | 174 ++++- proto/dydxprotocol/vault/query.proto | 23 + protocol/x/vault/client/cli/query.go | 47 ++ protocol/x/vault/keeper/grpc_query_vault.go | 56 ++ .../x/vault/keeper/grpc_query_vault_test.go | 124 ++++ protocol/x/vault/types/query.pb.go | 612 +++++++++++++++++- protocol/x/vault/types/query.pb.gw.go | 129 ++++ 9 files changed, 1169 insertions(+), 22 deletions(-) create mode 100644 protocol/x/vault/keeper/grpc_query_vault.go create mode 100644 protocol/x/vault/keeper/grpc_query_vault_test.go 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 index bf1e99fb60..3d7b91613c 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.lcd.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/vault/query.lcd.ts @@ -1,5 +1,5 @@ import { LCDClient } from "@osmonauts/lcd"; -import { QueryParamsRequest, QueryParamsResponseSDKType } from "./query"; +import { QueryParamsRequest, QueryParamsResponseSDKType, QueryVaultRequest, QueryVaultResponseSDKType } from "./query"; export class LCDQueryClient { req: LCDClient; @@ -10,6 +10,7 @@ export class LCDQueryClient { }) { this.req = requestClient; this.params = this.params.bind(this); + this.vault = this.vault.bind(this); } /* Queries the Params. */ @@ -18,5 +19,12 @@ export class LCDQueryClient { 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 26983c84e0..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,12 +1,15 @@ import { Rpc } from "../../helpers"; import * as _m0 from "protobufjs/minimal"; import { QueryClient, createProtobufRpcClient } from "@cosmjs/stargate"; -import { QueryParamsRequest, QueryParamsResponse } from "./query"; +import { QueryParamsRequest, QueryParamsResponse, QueryVaultRequest, QueryVaultResponse } from "./query"; /** Query defines the gRPC querier service. */ 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; @@ -14,6 +17,7 @@ export class QueryClientImpl implements Query { constructor(rpc: Rpc) { this.rpc = rpc; this.params = this.params.bind(this); + this.vault = this.vault.bind(this); } params(request: QueryParamsRequest = {}): Promise { @@ -22,6 +26,12 @@ export class QueryClientImpl implements Query { 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); @@ -29,6 +39,10 @@ export const createRpcQueryExtension = (base: QueryClient) => { return { params(request?: QueryParamsRequest): Promise { return queryService.params(request); + }, + + vault(request: QueryVaultRequest): Promise { + return queryService.vault(request); } }; 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 095ba9f2be..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,6 +1,8 @@ +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 } from "../../helpers"; +import { DeepPartial, Long } from "../../helpers"; /** QueryParamsRequest is a request type for the Params RPC method. */ export interface QueryParamsRequest {} @@ -17,6 +19,36 @@ export interface QueryParamsResponse { 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 {}; @@ -95,4 +127,144 @@ export const QueryParamsResponse = { 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/proto/dydxprotocol/vault/query.proto b/proto/dydxprotocol/vault/query.proto index e4ce844f3d..019a07acb5 100644 --- a/proto/dydxprotocol/vault/query.proto +++ b/proto/dydxprotocol/vault/query.proto @@ -3,7 +3,9 @@ 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"; @@ -13,6 +15,11 @@ service Query { 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. @@ -22,3 +29,19 @@ message QueryParamsRequest {} 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/protocol/x/vault/client/cli/query.go b/protocol/x/vault/client/cli/query.go index b7220913ba..3c16747a7f 100644 --- a/protocol/x/vault/client/cli/query.go +++ b/protocol/x/vault/client/cli/query.go @@ -3,6 +3,7 @@ package cli import ( "context" "fmt" + "strconv" "github.com/spf13/cobra" @@ -23,6 +24,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command { } cmd.AddCommand(CmdQueryParams()) + cmd.AddCommand(CmdQueryVault()) return cmd } @@ -49,3 +51,48 @@ func CmdQueryParams() *cobra.Command { 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/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/types/query.pb.go b/protocol/x/vault/types/query.pb.go index e186b0d621..ecc6d74eb1 100644 --- a/protocol/x/vault/types/query.pb.go +++ b/protocol/x/vault/types/query.pb.go @@ -9,6 +9,7 @@ import ( _ "github.com/cosmos/gogoproto/gogoproto" grpc1 "github.com/cosmos/gogoproto/grpc" proto "github.com/cosmos/gogoproto/proto" + types "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" _ "google.golang.org/genproto/googleapis/api/annotations" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" @@ -111,33 +112,179 @@ func (m *QueryParamsResponse) GetParams() 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{ - // 275 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, 0xa5, 0x44, - 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x62, 0xfa, 0x20, 0x16, 0x44, 0xa5, 0x94, 0x4c, 0x7a, 0x7e, 0x7e, - 0x7a, 0x4e, 0xaa, 0x7e, 0x62, 0x41, 0xa6, 0x7e, 0x62, 0x5e, 0x5e, 0x7e, 0x49, 0x62, 0x49, 0x66, - 0x7e, 0x5e, 0x31, 0x54, 0x56, 0x1e, 0x8b, 0x3d, 0x05, 0x89, 0x45, 0x89, 0xb9, 0x50, 0x05, 0x4a, - 0x22, 0x5c, 0x42, 0x81, 0x20, 0x7b, 0x03, 0xc0, 0x82, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, - 0x4a, 0xfe, 0x5c, 0xc2, 0x28, 0xa2, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x16, 0x5c, 0x6c, - 0x10, 0xcd, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xdc, 0x46, 0x52, 0x7a, 0x98, 0xce, 0xd4, 0x83, 0xe8, - 0x71, 0x62, 0x39, 0x71, 0x4f, 0x9e, 0x21, 0x08, 0xaa, 0xde, 0xa8, 0x8b, 0x91, 0x8b, 0x15, 0x6c, - 0xa2, 0x50, 0x03, 0x23, 0x17, 0x1b, 0x44, 0x89, 0x90, 0x1a, 0x36, 0xed, 0x98, 0xae, 0x91, 0x52, - 0x27, 0xa8, 0x0e, 0xe2, 0x3e, 0x25, 0xd5, 0xa6, 0xcb, 0x4f, 0x26, 0x33, 0xc9, 0x0b, 0xc9, 0xea, - 0xa3, 0x7a, 0xdb, 0x04, 0xc5, 0xe7, 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, 0x8b, 0x6e, 0x84, - 0x6e, 0x72, 0x46, 0x62, 0x66, 0x9e, 0x3e, 0x5c, 0xa4, 0x02, 0x6a, 0x66, 0x49, 0x65, 0x41, 0x6a, - 0x71, 0x12, 0x1b, 0x58, 0xdc, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x2c, 0xa1, 0x80, 0x8e, 0xd8, - 0x01, 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. @@ -154,6 +301,8 @@ const _ = grpc.SupportPackageIsVersion4 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 { @@ -173,10 +322,21 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . 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. @@ -186,6 +346,9 @@ 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) @@ -209,6 +372,24 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf 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), @@ -217,6 +398,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, + { + MethodName: "Vault", + Handler: _Query_Vault_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "dydxprotocol/vault/query.proto", @@ -278,6 +463,97 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { 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 @@ -309,6 +585,43 @@ func (m *QueryParamsResponse) Size() (n int) { 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 } @@ -448,6 +761,267 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } 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 diff --git a/protocol/x/vault/types/query.pb.gw.go b/protocol/x/vault/types/query.pb.gw.go index 40cafb6ba6..7cf3f8de49 100644 --- a/protocol/x/vault/types/query.pb.gw.go +++ b/protocol/x/vault/types/query.pb.gw.go @@ -51,6 +51,88 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } +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. @@ -80,6 +162,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + 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 } @@ -141,13 +246,37 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + 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 )