From edcaf94804dcac645c62712f7bb3bfd35e02073f Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 21 Aug 2024 02:18:50 -0400 Subject: [PATCH 1/9] Initial vault pnl endpoints. --- .../__tests__/helpers/mock-generators.ts | 2 + .../api/v4/vault-controller.test.ts | 253 +++++++++++++++++ .../comlink/__tests__/lib/helpers.test.ts | 72 ++++- .../comlink/public/api-documentation.md | 263 ++++++++++++++++++ indexer/services/comlink/public/swagger.json | 87 ++++++ indexer/services/comlink/src/config.ts | 5 + .../comlink/src/controllers/api/index-v4.ts | 2 + .../api/v4/historical-pnl-controller.ts | 21 +- .../controllers/api/v4/vault-controller.ts | 167 +++++++++++ indexer/services/comlink/src/lib/helpers.ts | 36 +++ indexer/services/comlink/src/types.ts | 26 ++ 11 files changed, 914 insertions(+), 20 deletions(-) create mode 100644 indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts create mode 100644 indexer/services/comlink/src/controllers/api/v4/vault-controller.ts diff --git a/indexer/packages/postgres/__tests__/helpers/mock-generators.ts b/indexer/packages/postgres/__tests__/helpers/mock-generators.ts index 86d6f6e3b7..f2f0099314 100644 --- a/indexer/packages/postgres/__tests__/helpers/mock-generators.ts +++ b/indexer/packages/postgres/__tests__/helpers/mock-generators.ts @@ -33,6 +33,7 @@ import { isolatedPerpetualMarket2, isolatedSubaccount, isolatedSubaccount2, + vaultSubaccount, } from './constants'; export async function seedData() { @@ -41,6 +42,7 @@ export async function seedData() { SubaccountTable.create(defaultSubaccount2), SubaccountTable.create(isolatedSubaccount), SubaccountTable.create(isolatedSubaccount2), + SubaccountTable.create(vaultSubaccount), ]); await Promise.all([ MarketTable.create(defaultMarket), diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts new file mode 100644 index 0000000000..54b2ae569c --- /dev/null +++ b/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts @@ -0,0 +1,253 @@ +import { + dbHelpers, + testConstants, + testMocks, + PnlTicksCreateObject, + PnlTicksTable, + perpetualMarketRefresher, + BlockTable, + liquidityTierRefresher, +} from '@dydxprotocol-indexer/postgres'; +import { PnlTicksResponseObject, RequestMethod, VaultHistoricalPnl } from '../../../../src/types'; +import request from 'supertest'; +import { sendRequest } from '../../../helpers/helpers'; +import config from '../../../../src/config'; + +describe('vault-controller#V4', () => { + const experimentVaultsPrevVal: string = config.EXPERIMENT_VAULTS; + const experimentVaultMarketsPrevVal: string = config.EXPERIMENT_VAULT_MARKETS; + const blockHeight: string = '3'; + + beforeAll(async () => { + await dbHelpers.migrate(); + }); + + afterAll(async () => { + await dbHelpers.teardown(); + }); + + describe('GET /v1', () => { + beforeEach(async () => { + config.EXPERIMENT_VAULTS = testConstants.defaultPnlTick.subaccountId; + config.EXPERIMENT_VAULT_MARKETS = testConstants.defaultPerpetualMarket.clobPairId; + await testMocks.seedData(); + await perpetualMarketRefresher.updatePerpetualMarkets(); + await liquidityTierRefresher.updateLiquidityTiers(); + await BlockTable.create({ + ...testConstants.defaultBlock, + blockHeight, + }); + }); + + afterEach(async () => { + config.EXPERIMENT_VAULTS = experimentVaultsPrevVal; + config.EXPERIMENT_VAULT_MARKETS = experimentVaultMarketsPrevVal; + await dbHelpers.clearData(); + }); + + it('Get /megavault/historicalPnl with single vault subaccount', async () => { + const createdAt: string = '2000-05-25T00:00:00.000Z'; + const pnlTick2: PnlTicksCreateObject = { + ...testConstants.defaultPnlTick, + createdAt, + blockHeight, + }; + await Promise.all([ + PnlTicksTable.create(testConstants.defaultPnlTick), + PnlTicksTable.create(pnlTick2), + ]); + + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: '/v4/vault/v1/megavault/historicalPnl' + }); + + const expectedPnlTickResponse: PnlTicksResponseObject = { + ...testConstants.defaultPnlTick, + id: PnlTicksTable.uuid( + testConstants.defaultPnlTick.subaccountId, + testConstants.defaultPnlTick.createdAt, + ), + }; + + const expectedPnlTick2Response: any = { + ...testConstants.defaultPnlTick, + createdAt, + blockHeight, + id: PnlTicksTable.uuid( + testConstants.defaultPnlTick.subaccountId, + createdAt, + ), + }; + + expect(response.body.megavaultsPnl).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ...expectedPnlTick2Response, + }), + expect.objectContaining({ + ...expectedPnlTickResponse, + }), + ]), + ); + }); + + it('Get /megavault/historicalPnl with 2 vault subaccounts', async () => { + config.EXPERIMENT_VAULTS = [ + testConstants.defaultPnlTick.subaccountId, + testConstants.vaultSubaccountId, + ].join(','); + config.EXPERIMENT_VAULT_MARKETS = [ + testConstants.defaultPerpetualMarket.clobPairId, + testConstants.defaultPerpetualMarket2.clobPairId, + ].join(','); + + const pnlTick2: PnlTicksCreateObject = { + ...testConstants.defaultPnlTick, + subaccountId: testConstants.vaultSubaccountId, + }; + await Promise.all([ + PnlTicksTable.create(testConstants.defaultPnlTick), + PnlTicksTable.create(pnlTick2), + ]); + + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/vault/v1/megavault/historicalPnl`, + }); + + const expectedPnlTickResponse: any = { + // id and subaccountId don't matter + equity: (parseFloat(testConstants.defaultPnlTick.equity) + + parseFloat(pnlTick2.equity)).toString(), + totalPnl: (parseFloat(testConstants.defaultPnlTick.totalPnl) + + parseFloat(pnlTick2.totalPnl)).toString(), + netTransfers: (parseFloat(testConstants.defaultPnlTick.netTransfers) + + parseFloat(pnlTick2.netTransfers)).toString(), + createdAt: testConstants.defaultPnlTick.createdAt, + blockHeight: testConstants.defaultPnlTick.blockHeight, + blockTime: testConstants.defaultPnlTick.blockTime, + }; + + expect(response.body.megavaultsPnl).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ...expectedPnlTickResponse, + }), + ]), + ); + }); + + it('Get /vaults/historicalPnl with single vault subaccount', async () => { + const createdAt: string = '2000-05-25T00:00:00.000Z'; + const pnlTick2: PnlTicksCreateObject = { + ...testConstants.defaultPnlTick, + createdAt, + blockHeight, + }; + await Promise.all([ + PnlTicksTable.create(testConstants.defaultPnlTick), + PnlTicksTable.create(pnlTick2), + ]); + + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: '/v4/vault/v1/vaults/historicalPnl' + }); + + const expectedPnlTickResponse: PnlTicksResponseObject = { + ...testConstants.defaultPnlTick, + id: PnlTicksTable.uuid( + testConstants.defaultPnlTick.subaccountId, + testConstants.defaultPnlTick.createdAt, + ), + }; + + const expectedPnlTick2Response: any = { + ...testConstants.defaultPnlTick, + createdAt, + blockHeight, + id: PnlTicksTable.uuid( + testConstants.defaultPnlTick.subaccountId, + createdAt, + ), + }; + + expect(response.body.vaultsPnl).toHaveLength(1); + + expect(response.body.vaultsPnl[0]).toEqual({ + ticker: testConstants.defaultPerpetualMarket.ticker, + historicalPnl: expect.arrayContaining([ + expect.objectContaining({ + ...expectedPnlTick2Response, + }), + expect.objectContaining({ + ...expectedPnlTickResponse, + }), + ]), + }); + }); + + it('Get /vault/v1/vaults/historicalPnl with 2 vault subaccounts', async () => { + config.EXPERIMENT_VAULTS = [ + testConstants.defaultPnlTick.subaccountId, + testConstants.vaultSubaccountId, + ].join(','); + config.EXPERIMENT_VAULT_MARKETS = [ + testConstants.defaultPerpetualMarket.clobPairId, + testConstants.defaultPerpetualMarket2.clobPairId, + ].join(','); + + const pnlTick2: PnlTicksCreateObject = { + ...testConstants.defaultPnlTick, + subaccountId: testConstants.vaultSubaccountId, + }; + await Promise.all([ + PnlTicksTable.create(testConstants.defaultPnlTick), + PnlTicksTable.create(pnlTick2), + ]); + + const response: request.Response = await sendRequest({ + type: RequestMethod.GET, + path: `/v4/vault/v1/vaults/historicalPnl`, + }); + + const expectedVaultPnl: VaultHistoricalPnl = { + ticker: testConstants.defaultPerpetualMarket.ticker, + historicalPnl: [ + { + ...testConstants.defaultPnlTick, + id: PnlTicksTable.uuid( + testConstants.defaultPnlTick.subaccountId, + testConstants.defaultPnlTick.createdAt, + ), + }, + ] + }; + + const expectedVaultPnl2: VaultHistoricalPnl = { + ticker: testConstants.defaultPerpetualMarket2.ticker, + historicalPnl: [ + { + ...pnlTick2, + id: PnlTicksTable.uuid( + pnlTick2.subaccountId, + pnlTick2.createdAt, + ) + } + ] + }; + + expect(response.body.vaultsPnl).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + ...expectedVaultPnl, + }), + expect.objectContaining({ + ...expectedVaultPnl2, + }) + ]), + ); + }); + }); +}); diff --git a/indexer/services/comlink/__tests__/lib/helpers.test.ts b/indexer/services/comlink/__tests__/lib/helpers.test.ts index b2444d96f1..9c8b05cb5c 100644 --- a/indexer/services/comlink/__tests__/lib/helpers.test.ts +++ b/indexer/services/comlink/__tests__/lib/helpers.test.ts @@ -28,6 +28,8 @@ import { LiquidityTiersFromDatabase, LiquidityTiersTable, liquidityTierRefresher, + PnlTicksFromDatabase, + PnlTicksTable, } from '@dydxprotocol-indexer/postgres'; import { adjustUSDCAssetPosition, @@ -41,6 +43,7 @@ import { getPerpetualPositionsWithUpdatedFunding, initializePerpetualPositionsWithFunding, getChildSubaccountNums, + aggregatePnlTicks, } from '../../src/lib/helpers'; import _ from 'lodash'; import Big from 'big.js'; @@ -53,7 +56,7 @@ import { defaultTendermintEventId2, defaultTendermintEventId3, } from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants'; -import { AssetPositionsMap, PerpetualPositionWithFunding } from '../../src/types'; +import { AssetPositionsMap, PerpetualPositionWithFunding, PnlTicksResponseObject } from '../../src/types'; import { ZERO, ZERO_USDC_POSITION } from '../../src/lib/constants'; describe('helpers', () => { @@ -721,4 +724,71 @@ describe('helpers', () => { expect(() => getChildSubaccountNums(128)).toThrowError('Parent subaccount number must be less than 128'); }); }); + + describe('aggregatePnlTicks', () => { + it('aggregates single pnl tick', () => { + const pnlTick: PnlTicksFromDatabase = { + ...testConstants.defaultPnlTick, + id: PnlTicksTable.uuid( + testConstants.defaultPnlTick.subaccountId, + testConstants.defaultPnlTick.createdAt, + ), + }; + + const aggregatedPnlTicks: Map = aggregatePnlTicks([pnlTick]); + expect( + aggregatedPnlTicks.get(parseInt(pnlTick.blockHeight,10)) + ).toEqual(expect.objectContaining({...testConstants.defaultPnlTick})); + }); + + it('aggregates multiple pnl ticks same height', () => { + const pnlTick: PnlTicksFromDatabase = { + ...testConstants.defaultPnlTick, + id: PnlTicksTable.uuid( + testConstants.defaultPnlTick.subaccountId, + testConstants.defaultPnlTick.createdAt, + ), + }; + const pnlTick2: PnlTicksFromDatabase = { + ...testConstants.defaultPnlTick, + id: PnlTicksTable.uuid( + testConstants.defaultSubaccountId2, + testConstants.defaultPnlTick.createdAt, + ), + }; + const blockHeight2: string = '80'; + const pnlTick3: PnlTicksFromDatabase = { + ...testConstants.defaultPnlTick, + id: PnlTicksTable.uuid( + testConstants.defaultPnlTick.subaccountId, + testConstants.defaultPnlTick.createdAt, + ), + blockHeight: blockHeight2, + } + + const aggregatedPnlTicks: Map = aggregatePnlTicks( + [pnlTick, pnlTick2, pnlTick3] + ); + // Combined pnl tick at initial block height. + expect( + aggregatedPnlTicks.get(parseInt(pnlTick.blockHeight,10)) + ).toEqual(expect.objectContaining({ + equity: (parseFloat(testConstants.defaultPnlTick.equity) + + parseFloat(pnlTick2.equity)).toString(), + totalPnl: (parseFloat(testConstants.defaultPnlTick.totalPnl) + + parseFloat(pnlTick2.totalPnl)).toString(), + netTransfers: (parseFloat(testConstants.defaultPnlTick.netTransfers) + + parseFloat(pnlTick2.netTransfers)).toString(), + createdAt: testConstants.defaultPnlTick.createdAt, + blockHeight: testConstants.defaultPnlTick.blockHeight, + blockTime: testConstants.defaultPnlTick.blockTime, + })); + // Single pnl tick at second block height. + expect( + aggregatedPnlTicks.get(parseInt(blockHeight2, 10)) + ).toEqual(expect.objectContaining({ + ...pnlTick3, + })); + }); + }); }); diff --git a/indexer/services/comlink/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index 83001d797f..5281ee1256 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -3076,6 +3076,169 @@ fetch(`${baseURL}/transfers/between?sourceAddress=string&sourceSubaccountNumber= This operation does not require authentication +## GetMegavaultHistoricalPnl + + + +> Code samples + +```python +import requests +headers = { + 'Accept': 'application/json' +} + +# For the deployment by DYDX token holders, use +# baseURL = 'https://indexer.dydx.trade/v4' +baseURL = 'https://dydx-testnet.imperator.co/v4' + +r = requests.get(f'{baseURL}/vault/v1/megavault/historicalPnl', headers = headers) + +print(r.json()) + +``` + +```javascript + +const headers = { + 'Accept':'application/json' +}; + +// For the deployment by DYDX token holders, use +// const baseURL = 'https://indexer.dydx.trade/v4'; +const baseURL = 'https://dydx-testnet.imperator.co/v4'; + +fetch(`${baseURL}/vault/v1/megavault/historicalPnl`, +{ + method: 'GET', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +`GET /vault/v1/megavault/historicalPnl` + +> Example responses + +> 200 Response + +```json +{ + "megavaultsPnl": [ + { + "id": "string", + "subaccountId": "string", + "equity": "string", + "totalPnl": "string", + "netTransfers": "string", + "createdAt": "string", + "blockHeight": "string", + "blockTime": "string" + } + ] +} +``` + +### Responses + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[MegavaultHistoricalPnlResponse](#schemamegavaulthistoricalpnlresponse)| + + + +## GetVaultsHistoricalPnl + + + +> Code samples + +```python +import requests +headers = { + 'Accept': 'application/json' +} + +# For the deployment by DYDX token holders, use +# baseURL = 'https://indexer.dydx.trade/v4' +baseURL = 'https://dydx-testnet.imperator.co/v4' + +r = requests.get(f'{baseURL}/vault/v1/v1/vaults/historicalPnl', headers = headers) + +print(r.json()) + +``` + +```javascript + +const headers = { + 'Accept':'application/json' +}; + +// For the deployment by DYDX token holders, use +// const baseURL = 'https://indexer.dydx.trade/v4'; +const baseURL = 'https://dydx-testnet.imperator.co/v4'; + +fetch(`${baseURL}/vault/v1/v1/vaults/historicalPnl`, +{ + method: 'GET', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +`GET /vault/v1/v1/vaults/historicalPnl` + +> Example responses + +> 200 Response + +```json +{ + "vaultsPnl": [ + { + "ticker": "string", + "historicalPnl": [ + { + "id": "string", + "subaccountId": "string", + "equity": "string", + "totalPnl": "string", + "netTransfers": "string", + "createdAt": "string", + "blockHeight": "string", + "blockTime": "string" + } + ] + } + ] +} +``` + +### Responses + +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Ok|[VaultsHistoricalPnlResponse](#schemavaultshistoricalpnlresponse)| + + + # Schemas ## PerpetualPositionStatus @@ -5196,3 +5359,103 @@ or |transfersSubset|[[TransferResponseObject](#schematransferresponseobject)]|true|none|none| |totalNetTransfers|string|true|none|none| +## MegavaultHistoricalPnlResponse + + + + + + +```json +{ + "megavaultsPnl": [ + { + "id": "string", + "subaccountId": "string", + "equity": "string", + "totalPnl": "string", + "netTransfers": "string", + "createdAt": "string", + "blockHeight": "string", + "blockTime": "string" + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|megavaultsPnl|[[PnlTicksResponseObject](#schemapnlticksresponseobject)]|true|none|none| + +## VaultHistoricalPnl + + + + + + +```json +{ + "ticker": "string", + "historicalPnl": [ + { + "id": "string", + "subaccountId": "string", + "equity": "string", + "totalPnl": "string", + "netTransfers": "string", + "createdAt": "string", + "blockHeight": "string", + "blockTime": "string" + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|ticker|string|true|none|none| +|historicalPnl|[[PnlTicksResponseObject](#schemapnlticksresponseobject)]|true|none|none| + +## VaultsHistoricalPnlResponse + + + + + + +```json +{ + "vaultsPnl": [ + { + "ticker": "string", + "historicalPnl": [ + { + "id": "string", + "subaccountId": "string", + "equity": "string", + "totalPnl": "string", + "netTransfers": "string", + "createdAt": "string", + "blockHeight": "string", + "blockTime": "string" + } + ] + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|vaultsPnl|[[VaultHistoricalPnl](#schemavaulthistoricalpnl)]|true|none|none| + diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index 2974e402d2..cd12b53f76 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -1359,6 +1359,55 @@ ], "type": "object", "additionalProperties": false + }, + "MegavaultHistoricalPnlResponse": { + "properties": { + "megavaultsPnl": { + "items": { + "$ref": "#/components/schemas/PnlTicksResponseObject" + }, + "type": "array" + } + }, + "required": [ + "megavaultsPnl" + ], + "type": "object", + "additionalProperties": false + }, + "VaultHistoricalPnl": { + "properties": { + "ticker": { + "type": "string" + }, + "historicalPnl": { + "items": { + "$ref": "#/components/schemas/PnlTicksResponseObject" + }, + "type": "array" + } + }, + "required": [ + "ticker", + "historicalPnl" + ], + "type": "object", + "additionalProperties": false + }, + "VaultsHistoricalPnlResponse": { + "properties": { + "vaultsPnl": { + "items": { + "$ref": "#/components/schemas/VaultHistoricalPnl" + }, + "type": "array" + } + }, + "required": [ + "vaultsPnl" + ], + "type": "object", + "additionalProperties": false } }, "securitySchemes": {} @@ -3005,6 +3054,44 @@ } ] } + }, + "/vault/v1/megavault/historicalPnl": { + "get": { + "operationId": "GetMegavaultHistoricalPnl", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MegavaultHistoricalPnlResponse" + } + } + } + } + }, + "security": [], + "parameters": [] + } + }, + "/vault/v1/v1/vaults/historicalPnl": { + "get": { + "operationId": "GetVaultsHistoricalPnl", + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VaultsHistoricalPnlResponse" + } + } + } + } + }, + "security": [], + "parameters": [] + } } }, "servers": [ diff --git a/indexer/services/comlink/src/config.ts b/indexer/services/comlink/src/config.ts index 4df2192034..eb3713bc14 100644 --- a/indexer/services/comlink/src/config.ts +++ b/indexer/services/comlink/src/config.ts @@ -55,6 +55,11 @@ export const configSchema = { MAX_AGE_SCREENED_ADDRESS_COMPLIANCE_DATA_SECONDS: parseInteger({ default: 86_400 }), // 1 day // Expose setting compliance status, only set to true in dev/staging. EXPOSE_SET_COMPLIANCE_ENDPOINT: parseBoolean({ default: false }), + + // TODO(TRA-570): Placeholder data for vaults and matching set of markets for each vault until + // vaults table is added. + EXPERIMENT_VAULTS: parseString({ default: '' }), + EXPERIMENT_VAULT_MARKETS: parseString({ default: '' }), }; //////////////////////////////////////////////////////////////////////////////// diff --git a/indexer/services/comlink/src/controllers/api/index-v4.ts b/indexer/services/comlink/src/controllers/api/index-v4.ts index 347491e261..d3e9f4f412 100644 --- a/indexer/services/comlink/src/controllers/api/index-v4.ts +++ b/indexer/services/comlink/src/controllers/api/index-v4.ts @@ -20,6 +20,7 @@ import SparklinesController from './v4/sparklines-controller'; import TimeController from './v4/time-controller'; import TradesController from './v4/trades-controller'; import TransfersController from './v4/transfers-controller'; +import VaultController from './v4/vault-controller'; // Keep routers in alphabetical order @@ -44,5 +45,6 @@ router.use('/transfers', TransfersController); router.use('/screen', ComplianceController); router.use('/compliance', ComplianceV2Controller); router.use('/trader', SocialTradingController); +router.use('/vault', VaultController); export default router; diff --git a/indexer/services/comlink/src/controllers/api/v4/historical-pnl-controller.ts b/indexer/services/comlink/src/controllers/api/v4/historical-pnl-controller.ts index 2778436aec..6ea7a5ce58 100644 --- a/indexer/services/comlink/src/controllers/api/v4/historical-pnl-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/historical-pnl-controller.ts @@ -19,7 +19,7 @@ import { getReqRateLimiter } from '../../../caches/rate-limiters'; import config from '../../../config'; import { complianceAndGeoCheck } from '../../../lib/compliance-and-geo-check'; import { NotFoundError } from '../../../lib/errors'; -import { getChildSubaccountIds, handleControllerError } from '../../../lib/helpers'; +import { aggregatePnlTicks, getChildSubaccountIds, handleControllerError } from '../../../lib/helpers'; import { rateLimiterMiddleware } from '../../../lib/rate-limit'; import { CheckLimitAndCreatedBeforeOrAtAndOnOrAfterSchema, @@ -156,24 +156,7 @@ class HistoricalPnlController extends Controller { } // aggregate pnlTicks for all subaccounts grouped by blockHeight - const aggregatedPnlTicks: Map = new Map(); - for (const pnlTick of pnlTicks) { - const blockHeight: number = parseInt(pnlTick.blockHeight, 10); - if (aggregatedPnlTicks.has(blockHeight)) { - const currentPnlTick: PnlTicksFromDatabase = aggregatedPnlTicks.get( - blockHeight, - ) as PnlTicksFromDatabase; - aggregatedPnlTicks.set(blockHeight, { - ...currentPnlTick, - equity: (parseFloat(currentPnlTick.equity) + parseFloat(pnlTick.equity)).toString(), - totalPnl: (parseFloat(currentPnlTick.totalPnl) + parseFloat(pnlTick.totalPnl)).toString(), - netTransfers: (parseFloat(currentPnlTick.netTransfers) + - parseFloat(pnlTick.netTransfers)).toString(), - }); - } else { - aggregatedPnlTicks.set(blockHeight, pnlTick); - } - } + const aggregatedPnlTicks: Map = aggregatePnlTicks(pnlTicks); return { historicalPnl: Array.from(aggregatedPnlTicks.values()).map( diff --git a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts new file mode 100644 index 0000000000..2f3e683506 --- /dev/null +++ b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts @@ -0,0 +1,167 @@ +import { stats } from '@dydxprotocol-indexer/base'; +import { + DEFAULT_POSTGRES_OPTIONS, + Ordering, PaginationFromDatabase, + PnlTicksFromDatabase, + PnlTicksTable, + QueryableField, + perpetualMarketRefresher, + PerpetualMarketFromDatabase, +} from '@dydxprotocol-indexer/postgres'; +import express from 'express'; +import { + Controller, Get, Route, +} from 'tsoa'; +import _ from 'lodash'; + +import { getReqRateLimiter } from '../../../caches/rate-limiters'; +import config from '../../../config'; +import { aggregatePnlTicks, handleControllerError } from '../../../lib/helpers'; +import { rateLimiterMiddleware } from '../../../lib/rate-limit'; +import ExportResponseCodeStats from '../../../request-helpers/export-response-code-stats'; +import { pnlTicksToResponseObject } from '../../../request-helpers/request-transformer'; +import { MegavaultHistoricalPnlResponse, VaultsHistoricalPnlResponse, VaultHistoricalPnl } from '../../../types'; + +const router: express.Router = express.Router(); +const controllerName: string = 'vault-controller'; + +// TODO(TRA-570): Placeholder interface for mapping of vault subaccounts to tickers until vaults +// table is added. +interface VaultMapping { + [subaccountId: string]: string; +} + +@Route('vault/v1') +class VaultController extends Controller { + @Get('/megavault/historicalPnl') + async getMegavaultHistoricalPnl(): Promise { + const vaultPnlTicks: PnlTicksFromDatabase[] = await getVaultSubaccountPnlTicks(); + + // aggregate pnlTicks for all vault subaccounts grouped by blockHeight + const aggregatedPnlTicks: Map = aggregatePnlTicks(vaultPnlTicks); + + return { + megavaultsPnl: Array.from(aggregatedPnlTicks.values()).map( + (pnlTick: PnlTicksFromDatabase) => { + return pnlTicksToResponseObject(pnlTick); + }), + }; + } + + @Get('/v1/vaults/historicalPnl') + async getVaultsHistoricalPnl(): Promise { + const vaultSubaccounts: VaultMapping = getVaultSubaccountsFromConfig(); + const vaultPnlTicks: PnlTicksFromDatabase[] = await getVaultSubaccountPnlTicks(); + + const groupedVaultPnlTicks: VaultHistoricalPnl[] = _(vaultPnlTicks) + .groupBy('subaccountId') + .mapValues((pnlTicks: PnlTicksFromDatabase[], subaccountId: string): VaultHistoricalPnl => { + const market: PerpetualMarketFromDatabase | undefined = perpetualMarketRefresher + .getPerpetualMarketFromClobPairId( + vaultSubaccounts[subaccountId], + ); + + if (market === undefined) { + throw new Error(`Vault clob pair id ${vaultSubaccounts[subaccountId]} does not correspond to a perpetual market.`) + } + + return { + ticker: market.ticker, + historicalPnl: pnlTicks, + }}) + .values() + .value(); + + return { + vaultsPnl: groupedVaultPnlTicks, + }; + } +} + +router.get( + '/v1/megavault/historicalPnl', + rateLimiterMiddleware(getReqRateLimiter), + ExportResponseCodeStats({ controllerName }), + async (req: express.Request, res: express.Response) => { + const start: number = Date.now(); + + try { + const controllers: VaultController = new VaultController(); + const response: MegavaultHistoricalPnlResponse = await controllers.getMegavaultHistoricalPnl(); + return res.send(response); + } catch (error) { + return handleControllerError( + 'VaulController GET /megavault/historicalPnl', + 'Megavault Historical Pnl error', + error, + req, + res, + ); + } finally { + stats.timing( + `${config.SERVICE_NAME}.${controllerName}.get_megavault_historical_pnl.timing`, + Date.now() - start, + ); + } +}); + +router.get( + '/v1/vaults/historicalPnl', + rateLimiterMiddleware(getReqRateLimiter), + ExportResponseCodeStats({ controllerName }), + async (req: express.Request, res: express.Response) => { + const start: number = Date.now(); + + try { + const controllers: VaultController = new VaultController(); + const response: VaultsHistoricalPnlResponse = await controllers.getVaultsHistoricalPnl(); + return res.send(response); + } catch (error) { + return handleControllerError( + 'VaultHistoricalPnlController GET /vaults/historicalPnl', + 'Vaults Historical Pnl error', + error, + req, + res, + ); + } finally { + stats.timing( + `${config.SERVICE_NAME}.${controllerName}.get_vaults_historical_pnl.timing`, + Date.now() - start, + ); + } +}); + +async function getVaultSubaccountPnlTicks(): Promise { + const subVaultSubaccountIds: string[] = _.keys(getVaultSubaccountsFromConfig()); + const { + results: pnlTicks, + }: PaginationFromDatabase = await + PnlTicksTable.findAll( + { + subaccountId: subVaultSubaccountIds, + limit: config.API_LIMIT_V4, + }, + [QueryableField.LIMIT], + { + ...DEFAULT_POSTGRES_OPTIONS, + orderBy: [[QueryableField.BLOCK_HEIGHT, Ordering.DESC]], + }, + ); + return pnlTicks; +} + +// TODO(TRA-570): Placeholder for getting vault subaccount ids until vault table is added. +function getVaultSubaccountsFromConfig(): VaultMapping { + const vaultSubaccountIds: string[] = config.EXPERIMENT_VAULTS.split(',');; + const vaultClobPairIds: string[] = config.EXPERIMENT_VAULT_MARKETS.split(','); + if (vaultSubaccountIds.length !== vaultClobPairIds.length) { + throw new Error('Expected number of vaults to match number of markets'); + } + return _.zipObject( + vaultSubaccountIds, + vaultClobPairIds, + ); +} + +export default router; diff --git a/indexer/services/comlink/src/lib/helpers.ts b/indexer/services/comlink/src/lib/helpers.ts index efcf0a358f..31d2dcc6a5 100644 --- a/indexer/services/comlink/src/lib/helpers.ts +++ b/indexer/services/comlink/src/lib/helpers.ts @@ -16,6 +16,7 @@ import { PerpetualMarketTable, PerpetualPositionFromDatabase, PerpetualPositionStatus, + PnlTicksFromDatabase, PositionSide, SubaccountFromDatabase, SubaccountTable, @@ -500,6 +501,8 @@ export function initializePerpetualPositionsWithFunding( }); } +/* ------- PARENT/CHILD SUBACCOUNT HELPERS ------- */ + /** * 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] @@ -531,3 +534,36 @@ export function checkIfValidDydxAddress(address: string): boolean { const pattern: RegExp = /^dydx[0-9a-z]{39}$/; return pattern.test(address); } + +/* ------- PNL HELPERS ------- */ + +/** + * Aggregates a list of PnL ticks, combining any PnL ticks for the same blockheight by summing + * the equity, totalPnl, and net transfers. + * Returns a map of block height to the resulting PnL tick. + * @param pnlTicks + * @returns + */ +export function aggregatePnlTicks( + pnlTicks: PnlTicksFromDatabase[], +): Map { + const aggregatedPnlTicks: Map = new Map(); + for (const pnlTick of pnlTicks) { + const blockHeight: number = parseInt(pnlTick.blockHeight, 10); + if (aggregatedPnlTicks.has(blockHeight)) { + const currentPnlTick: PnlTicksFromDatabase = aggregatedPnlTicks.get( + blockHeight, + ) as PnlTicksFromDatabase; + aggregatedPnlTicks.set(blockHeight, { + ...currentPnlTick, + equity: (parseFloat(currentPnlTick.equity) + parseFloat(pnlTick.equity)).toString(), + totalPnl: (parseFloat(currentPnlTick.totalPnl) + parseFloat(pnlTick.totalPnl)).toString(), + netTransfers: (parseFloat(currentPnlTick.netTransfers) + + parseFloat(pnlTick.netTransfers)).toString(), + }); + } else { + aggregatedPnlTicks.set(blockHeight, pnlTick); + } + } + return aggregatedPnlTicks; +} \ No newline at end of file diff --git a/indexer/services/comlink/src/types.ts b/indexer/services/comlink/src/types.ts index c3d5d270c9..3a6916b364 100644 --- a/indexer/services/comlink/src/types.ts +++ b/indexer/services/comlink/src/types.ts @@ -640,3 +640,29 @@ export interface TraderSearchResponseObject { subaccountId: string, username: string, } + +/* ------- Vault Types ------- */ + +export interface VaultHistoricalPnl { + ticker: string; + historicalPnl: PnlTicksResponseObject[]; +} + +export interface MegavaultHistoricalPnlResponse { + megavaultsPnl: PnlTicksResponseObject[]; +} + +export interface VaultsHistoricalPnlResponse { + vaultsPnl: VaultHistoricalPnl[]; +} + +export interface VaultPosition { + ticker: string; + assetPosition: AssetPositionResponseObject; + perpetualPosition?: PerpetualPositionResponseObject; + equity: string; +} + +export interface MegavaultPositionResponse { + positions: VaultPosition[]; +} From 2fe104cf8aaf6a697ebaf33d6c38e21119440514 Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 21 Aug 2024 02:22:39 -0400 Subject: [PATCH 2/9] Add TODO. --- .../services/comlink/src/controllers/api/v4/vault-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts index 2f3e683506..4f46511205 100644 --- a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts @@ -140,6 +140,7 @@ async function getVaultSubaccountPnlTicks(): Promise { PnlTicksTable.findAll( { subaccountId: subVaultSubaccountIds, + // TODO(TRA-571): Configure limits based on hourly vs daily resolution and # of vaults. limit: config.API_LIMIT_V4, }, [QueryableField.LIMIT], From b7a0a4a43846bd11bca9e1b176bc7b223d40f842 Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:31:51 -0400 Subject: [PATCH 3/9] Fix lint errors / comments. --- .../api/v4/vault-controller.test.ts | 24 +++---- .../comlink/__tests__/lib/helpers.test.ts | 14 ++--- .../comlink/public/api-documentation.md | 6 +- indexer/services/comlink/public/swagger.json | 4 +- .../controllers/api/v4/vault-controller.ts | 62 ++++++++++--------- indexer/services/comlink/src/lib/helpers.ts | 6 +- indexer/services/comlink/src/types.ts | 2 +- 7 files changed, 61 insertions(+), 57 deletions(-) diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts index 54b2ae569c..317423c938 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts @@ -38,7 +38,7 @@ describe('vault-controller#V4', () => { blockHeight, }); }); - + afterEach(async () => { config.EXPERIMENT_VAULTS = experimentVaultsPrevVal; config.EXPERIMENT_VAULT_MARKETS = experimentVaultMarketsPrevVal; @@ -59,7 +59,7 @@ describe('vault-controller#V4', () => { const response: request.Response = await sendRequest({ type: RequestMethod.GET, - path: '/v4/vault/v1/megavault/historicalPnl' + path: '/v4/vault/v1/megavault/historicalPnl', }); const expectedPnlTickResponse: PnlTicksResponseObject = { @@ -80,7 +80,7 @@ describe('vault-controller#V4', () => { ), }; - expect(response.body.megavaultsPnl).toEqual( + expect(response.body.megavaultPnl).toEqual( expect.arrayContaining([ expect.objectContaining({ ...expectedPnlTick2Response, @@ -113,7 +113,7 @@ describe('vault-controller#V4', () => { const response: request.Response = await sendRequest({ type: RequestMethod.GET, - path: `/v4/vault/v1/megavault/historicalPnl`, + path: '/v4/vault/v1/megavault/historicalPnl', }); const expectedPnlTickResponse: any = { @@ -129,7 +129,7 @@ describe('vault-controller#V4', () => { blockTime: testConstants.defaultPnlTick.blockTime, }; - expect(response.body.megavaultsPnl).toEqual( + expect(response.body.megavaultPnl).toEqual( expect.arrayContaining([ expect.objectContaining({ ...expectedPnlTickResponse, @@ -152,7 +152,7 @@ describe('vault-controller#V4', () => { const response: request.Response = await sendRequest({ type: RequestMethod.GET, - path: '/v4/vault/v1/vaults/historicalPnl' + path: '/v4/vault/v1/vaults/historicalPnl', }); const expectedPnlTickResponse: PnlTicksResponseObject = { @@ -209,7 +209,7 @@ describe('vault-controller#V4', () => { const response: request.Response = await sendRequest({ type: RequestMethod.GET, - path: `/v4/vault/v1/vaults/historicalPnl`, + path: '/v4/vault/v1/vaults/historicalPnl', }); const expectedVaultPnl: VaultHistoricalPnl = { @@ -222,7 +222,7 @@ describe('vault-controller#V4', () => { testConstants.defaultPnlTick.createdAt, ), }, - ] + ], }; const expectedVaultPnl2: VaultHistoricalPnl = { @@ -233,9 +233,9 @@ describe('vault-controller#V4', () => { id: PnlTicksTable.uuid( pnlTick2.subaccountId, pnlTick2.createdAt, - ) - } - ] + ), + }, + ], }; expect(response.body.vaultsPnl).toEqual( @@ -245,7 +245,7 @@ describe('vault-controller#V4', () => { }), expect.objectContaining({ ...expectedVaultPnl2, - }) + }), ]), ); }); diff --git a/indexer/services/comlink/__tests__/lib/helpers.test.ts b/indexer/services/comlink/__tests__/lib/helpers.test.ts index 9c8b05cb5c..c5d7870ef8 100644 --- a/indexer/services/comlink/__tests__/lib/helpers.test.ts +++ b/indexer/services/comlink/__tests__/lib/helpers.test.ts @@ -56,7 +56,7 @@ import { defaultTendermintEventId2, defaultTendermintEventId3, } from '@dydxprotocol-indexer/postgres/build/__tests__/helpers/constants'; -import { AssetPositionsMap, PerpetualPositionWithFunding, PnlTicksResponseObject } from '../../src/types'; +import { AssetPositionsMap, PerpetualPositionWithFunding } from '../../src/types'; import { ZERO, ZERO_USDC_POSITION } from '../../src/lib/constants'; describe('helpers', () => { @@ -737,8 +737,8 @@ describe('helpers', () => { const aggregatedPnlTicks: Map = aggregatePnlTicks([pnlTick]); expect( - aggregatedPnlTicks.get(parseInt(pnlTick.blockHeight,10)) - ).toEqual(expect.objectContaining({...testConstants.defaultPnlTick})); + aggregatedPnlTicks.get(parseInt(pnlTick.blockHeight, 10)), + ).toEqual(expect.objectContaining({ ...testConstants.defaultPnlTick })); }); it('aggregates multiple pnl ticks same height', () => { @@ -764,14 +764,14 @@ describe('helpers', () => { testConstants.defaultPnlTick.createdAt, ), blockHeight: blockHeight2, - } + }; const aggregatedPnlTicks: Map = aggregatePnlTicks( - [pnlTick, pnlTick2, pnlTick3] + [pnlTick, pnlTick2, pnlTick3], ); // Combined pnl tick at initial block height. expect( - aggregatedPnlTicks.get(parseInt(pnlTick.blockHeight,10)) + aggregatedPnlTicks.get(parseInt(pnlTick.blockHeight, 10)), ).toEqual(expect.objectContaining({ equity: (parseFloat(testConstants.defaultPnlTick.equity) + parseFloat(pnlTick2.equity)).toString(), @@ -785,7 +785,7 @@ describe('helpers', () => { })); // Single pnl tick at second block height. expect( - aggregatedPnlTicks.get(parseInt(blockHeight2, 10)) + aggregatedPnlTicks.get(parseInt(blockHeight2, 10)), ).toEqual(expect.objectContaining({ ...pnlTick3, })); diff --git a/indexer/services/comlink/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index 5281ee1256..cc751db1b6 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -3130,7 +3130,7 @@ fetch(`${baseURL}/vault/v1/megavault/historicalPnl`, ```json { - "megavaultsPnl": [ + "megavaultPnl": [ { "id": "string", "subaccountId": "string", @@ -5368,7 +5368,7 @@ or ```json { - "megavaultsPnl": [ + "megavaultPnl": [ { "id": "string", "subaccountId": "string", @@ -5388,7 +5388,7 @@ or |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| -|megavaultsPnl|[[PnlTicksResponseObject](#schemapnlticksresponseobject)]|true|none|none| +|megavaultPnl|[[PnlTicksResponseObject](#schemapnlticksresponseobject)]|true|none|none| ## VaultHistoricalPnl diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index cd12b53f76..50beb2ed9b 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -1362,7 +1362,7 @@ }, "MegavaultHistoricalPnlResponse": { "properties": { - "megavaultsPnl": { + "megavaultPnl": { "items": { "$ref": "#/components/schemas/PnlTicksResponseObject" }, @@ -1370,7 +1370,7 @@ } }, "required": [ - "megavaultsPnl" + "megavaultPnl" ], "type": "object", "additionalProperties": false diff --git a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts index 4f46511205..0fcb264608 100644 --- a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts @@ -9,10 +9,10 @@ import { PerpetualMarketFromDatabase, } from '@dydxprotocol-indexer/postgres'; import express from 'express'; +import _ from 'lodash'; import { Controller, Get, Route, } from 'tsoa'; -import _ from 'lodash'; import { getReqRateLimiter } from '../../../caches/rate-limiters'; import config from '../../../config'; @@ -41,7 +41,7 @@ class VaultController extends Controller { const aggregatedPnlTicks: Map = aggregatePnlTicks(vaultPnlTicks); return { - megavaultsPnl: Array.from(aggregatedPnlTicks.values()).map( + megavaultPnl: Array.from(aggregatedPnlTicks.values()).map( (pnlTick: PnlTicksFromDatabase) => { return pnlTicksToResponseObject(pnlTick); }), @@ -57,18 +57,19 @@ class VaultController extends Controller { .groupBy('subaccountId') .mapValues((pnlTicks: PnlTicksFromDatabase[], subaccountId: string): VaultHistoricalPnl => { const market: PerpetualMarketFromDatabase | undefined = perpetualMarketRefresher - .getPerpetualMarketFromClobPairId( - vaultSubaccounts[subaccountId], - ); + .getPerpetualMarketFromClobPairId( + vaultSubaccounts[subaccountId], + ); if (market === undefined) { - throw new Error(`Vault clob pair id ${vaultSubaccounts[subaccountId]} does not correspond to a perpetual market.`) + throw new Error(`Vault clob pair id ${vaultSubaccounts[subaccountId]} does not correspond to a perpetual market.`); } return { ticker: market.ticker, historicalPnl: pnlTicks, - }}) + }; + }) .values() .value(); @@ -87,11 +88,12 @@ router.get( try { const controllers: VaultController = new VaultController(); - const response: MegavaultHistoricalPnlResponse = await controllers.getMegavaultHistoricalPnl(); - return res.send(response); + const response: MegavaultHistoricalPnlResponse = await controllers + .getMegavaultHistoricalPnl(); + return res.send(response); } catch (error) { return handleControllerError( - 'VaulController GET /megavault/historicalPnl', + 'VaultController GET /megavault/historicalPnl', 'Megavault Historical Pnl error', error, req, @@ -103,7 +105,8 @@ router.get( Date.now() - start, ); } -}); + }, +); router.get( '/v1/vaults/historicalPnl', @@ -115,7 +118,7 @@ router.get( try { const controllers: VaultController = new VaultController(); const response: VaultsHistoricalPnlResponse = await controllers.getVaultsHistoricalPnl(); - return res.send(response); + return res.send(response); } catch (error) { return handleControllerError( 'VaultHistoricalPnlController GET /vaults/historicalPnl', @@ -130,31 +133,32 @@ router.get( Date.now() - start, ); } -}); + }, +); async function getVaultSubaccountPnlTicks(): Promise { const subVaultSubaccountIds: string[] = _.keys(getVaultSubaccountsFromConfig()); - const { - results: pnlTicks, - }: PaginationFromDatabase = await - PnlTicksTable.findAll( - { - subaccountId: subVaultSubaccountIds, - // TODO(TRA-571): Configure limits based on hourly vs daily resolution and # of vaults. - limit: config.API_LIMIT_V4, - }, - [QueryableField.LIMIT], - { - ...DEFAULT_POSTGRES_OPTIONS, - orderBy: [[QueryableField.BLOCK_HEIGHT, Ordering.DESC]], - }, - ); + const { + results: pnlTicks, + }: PaginationFromDatabase = await + PnlTicksTable.findAll( + { + subaccountId: subVaultSubaccountIds, + // TODO(TRA-571): Configure limits based on hourly vs daily resolution and # of vaults. + limit: config.API_LIMIT_V4, + }, + [QueryableField.LIMIT], + { + ...DEFAULT_POSTGRES_OPTIONS, + orderBy: [[QueryableField.BLOCK_HEIGHT, Ordering.DESC]], + }, + ); return pnlTicks; } // TODO(TRA-570): Placeholder for getting vault subaccount ids until vault table is added. function getVaultSubaccountsFromConfig(): VaultMapping { - const vaultSubaccountIds: string[] = config.EXPERIMENT_VAULTS.split(',');; + const vaultSubaccountIds: string[] = config.EXPERIMENT_VAULTS.split(','); const vaultClobPairIds: string[] = config.EXPERIMENT_VAULT_MARKETS.split(','); if (vaultSubaccountIds.length !== vaultClobPairIds.length) { throw new Error('Expected number of vaults to match number of markets'); diff --git a/indexer/services/comlink/src/lib/helpers.ts b/indexer/services/comlink/src/lib/helpers.ts index 31d2dcc6a5..ee76eb7071 100644 --- a/indexer/services/comlink/src/lib/helpers.ts +++ b/indexer/services/comlink/src/lib/helpers.ts @@ -541,8 +541,8 @@ export function checkIfValidDydxAddress(address: string): boolean { * Aggregates a list of PnL ticks, combining any PnL ticks for the same blockheight by summing * the equity, totalPnl, and net transfers. * Returns a map of block height to the resulting PnL tick. - * @param pnlTicks - * @returns + * @param pnlTicks + * @returns */ export function aggregatePnlTicks( pnlTicks: PnlTicksFromDatabase[], @@ -566,4 +566,4 @@ export function aggregatePnlTicks( } } return aggregatedPnlTicks; -} \ No newline at end of file +} diff --git a/indexer/services/comlink/src/types.ts b/indexer/services/comlink/src/types.ts index 3a6916b364..bf9db5c2f6 100644 --- a/indexer/services/comlink/src/types.ts +++ b/indexer/services/comlink/src/types.ts @@ -649,7 +649,7 @@ export interface VaultHistoricalPnl { } export interface MegavaultHistoricalPnlResponse { - megavaultsPnl: PnlTicksResponseObject[]; + megavaultPnl: PnlTicksResponseObject[]; } export interface VaultsHistoricalPnlResponse { From 33ecaf0bdd4ae877547ed5eb3223d891452d5f71 Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:33:21 -0400 Subject: [PATCH 4/9] Fix pnl ticks table test. --- .../packages/postgres/__tests__/stores/pnl-ticks-table.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts index 4f9cd9c078..a866503dab 100644 --- a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts @@ -421,7 +421,6 @@ describe('PnlTicks store', () => { await setupRankedPnlTicksData(); await WalletTable.create(vaultWallet); - await SubaccountTable.create(vaultSubaccount); await PnlTicksTable.create({ subaccountId: vaultSubaccountId, equity: '100', From c0371c66ed5523d6074b83daf6017281dc5be7e9 Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:36:44 -0400 Subject: [PATCH 5/9] Remove extra 'v1' in path. --- indexer/services/comlink/public/api-documentation.md | 6 +++--- indexer/services/comlink/public/swagger.json | 2 +- .../comlink/src/controllers/api/v4/vault-controller.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indexer/services/comlink/public/api-documentation.md b/indexer/services/comlink/public/api-documentation.md index cc751db1b6..b0152c1129 100644 --- a/indexer/services/comlink/public/api-documentation.md +++ b/indexer/services/comlink/public/api-documentation.md @@ -3171,7 +3171,7 @@ headers = { # baseURL = 'https://indexer.dydx.trade/v4' baseURL = 'https://dydx-testnet.imperator.co/v4' -r = requests.get(f'{baseURL}/vault/v1/v1/vaults/historicalPnl', headers = headers) +r = requests.get(f'{baseURL}/vault/v1/vaults/historicalPnl', headers = headers) print(r.json()) @@ -3187,7 +3187,7 @@ const headers = { // const baseURL = 'https://indexer.dydx.trade/v4'; const baseURL = 'https://dydx-testnet.imperator.co/v4'; -fetch(`${baseURL}/vault/v1/v1/vaults/historicalPnl`, +fetch(`${baseURL}/vault/v1/vaults/historicalPnl`, { method: 'GET', @@ -3201,7 +3201,7 @@ fetch(`${baseURL}/vault/v1/v1/vaults/historicalPnl`, ``` -`GET /vault/v1/v1/vaults/historicalPnl` +`GET /vault/v1/vaults/historicalPnl` > Example responses diff --git a/indexer/services/comlink/public/swagger.json b/indexer/services/comlink/public/swagger.json index 50beb2ed9b..089550239a 100644 --- a/indexer/services/comlink/public/swagger.json +++ b/indexer/services/comlink/public/swagger.json @@ -3074,7 +3074,7 @@ "parameters": [] } }, - "/vault/v1/v1/vaults/historicalPnl": { + "/vault/v1/vaults/historicalPnl": { "get": { "operationId": "GetVaultsHistoricalPnl", "responses": { diff --git a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts index 0fcb264608..b75d9c73b3 100644 --- a/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts +++ b/indexer/services/comlink/src/controllers/api/v4/vault-controller.ts @@ -48,7 +48,7 @@ class VaultController extends Controller { }; } - @Get('/v1/vaults/historicalPnl') + @Get('/vaults/historicalPnl') async getVaultsHistoricalPnl(): Promise { const vaultSubaccounts: VaultMapping = getVaultSubaccountsFromConfig(); const vaultPnlTicks: PnlTicksFromDatabase[] = await getVaultSubaccountPnlTicks(); From fb2d7046a338c465748770d79d099ce1cad7c101 Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:37:40 -0400 Subject: [PATCH 6/9] Fix lint. --- .../packages/postgres/__tests__/stores/pnl-ticks-table.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts index a866503dab..8be862ddc3 100644 --- a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts @@ -22,7 +22,6 @@ import { defaultSubaccountIdWithAlternateAddress, defaultSubaccountWithAlternateAddress, defaultWallet2, - vaultSubaccount, vaultSubaccountId, vaultWallet, } from '../helpers/constants'; From b9336d93b24aee9f49512a3c22ec51f809d91dd6 Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:03:33 -0400 Subject: [PATCH 7/9] Move vault subaccount initialization into vault tests. --- indexer/packages/postgres/__tests__/helpers/mock-generators.ts | 2 -- .../__tests__/controllers/api/v4/vault-controller.test.ts | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer/packages/postgres/__tests__/helpers/mock-generators.ts b/indexer/packages/postgres/__tests__/helpers/mock-generators.ts index f2f0099314..86d6f6e3b7 100644 --- a/indexer/packages/postgres/__tests__/helpers/mock-generators.ts +++ b/indexer/packages/postgres/__tests__/helpers/mock-generators.ts @@ -33,7 +33,6 @@ import { isolatedPerpetualMarket2, isolatedSubaccount, isolatedSubaccount2, - vaultSubaccount, } from './constants'; export async function seedData() { @@ -42,7 +41,6 @@ export async function seedData() { SubaccountTable.create(defaultSubaccount2), SubaccountTable.create(isolatedSubaccount), SubaccountTable.create(isolatedSubaccount2), - SubaccountTable.create(vaultSubaccount), ]); await Promise.all([ MarketTable.create(defaultMarket), diff --git a/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts b/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts index 317423c938..12032d5bdf 100644 --- a/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts +++ b/indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts @@ -7,6 +7,7 @@ import { perpetualMarketRefresher, BlockTable, liquidityTierRefresher, + SubaccountTable, } from '@dydxprotocol-indexer/postgres'; import { PnlTicksResponseObject, RequestMethod, VaultHistoricalPnl } from '../../../../src/types'; import request from 'supertest'; @@ -37,6 +38,7 @@ describe('vault-controller#V4', () => { ...testConstants.defaultBlock, blockHeight, }); + await SubaccountTable.create(testConstants.vaultSubaccount); }); afterEach(async () => { From 168bf23cb9af7da7322cb84b02c8ee1aee3d47d8 Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:10:08 -0400 Subject: [PATCH 8/9] Update pnl ticks table test. --- .../packages/postgres/__tests__/stores/pnl-ticks-table.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts index 8be862ddc3..3d387fa826 100644 --- a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts @@ -420,6 +420,7 @@ describe('PnlTicks store', () => { await setupRankedPnlTicksData(); await WalletTable.create(vaultWallet); + await SubaccountTable.create(vaultSubaccount); await PnlTicksTable.create({ subaccountId: vaultSubaccountId, equity: '100', From 589af51b1e45270d15cb5498b6064c762805e99e Mon Sep 17 00:00:00 2001 From: Vincent Chau <99756290+vincentwschau@users.noreply.github.com> Date: Thu, 22 Aug 2024 02:38:39 -0400 Subject: [PATCH 9/9] Fix missing import. --- .../packages/postgres/__tests__/stores/pnl-ticks-table.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts index 3d387fa826..4f9cd9c078 100644 --- a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts @@ -22,6 +22,7 @@ import { defaultSubaccountIdWithAlternateAddress, defaultSubaccountWithAlternateAddress, defaultWallet2, + vaultSubaccount, vaultSubaccountId, vaultWallet, } from '../helpers/constants';