Skip to content

Commit

Permalink
[TRA-110] Implement GET /address/parentSubaccountNumber API (#1229)
Browse files Browse the repository at this point in the history
Signed-off-by: Shrenuj Bansal <[email protected]>
  • Loading branch information
shrenujb authored Mar 25, 2024
1 parent 50c712a commit 0dbaf0b
Show file tree
Hide file tree
Showing 11 changed files with 724 additions and 74 deletions.
10 changes: 10 additions & 0 deletions indexer/packages/postgres/__tests__/helpers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ==============

Expand Down
39 changes: 0 additions & 39 deletions indexer/packages/postgres/__tests__/lib/api-translations.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { APITimeInForce, TimeInForce } from '../../src/types';
import {
getChildSubaccountNums,
getParentSubaccountNum,
isOrderTIFPostOnly,
orderTIFToAPITIF,
} from '../../src/lib/api-translations';
Expand Down Expand Up @@ -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');
});
});
});
29 changes: 1 addition & 28 deletions indexer/packages/postgres/src/lib/api-translations.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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',
},
],
});
});

});
39 changes: 38 additions & 1 deletion indexer/services/comlink/__tests__/lib/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
getSignedNotionalAndRisk,
getTotalUnsettledFunding,
getPerpetualPositionsWithUpdatedFunding,
initializePerpetualPositionsWithFunding,
initializePerpetualPositionsWithFunding, getChildSubaccountNums, getParentSubaccountNum,
} from '../../src/lib/helpers';
import _ from 'lodash';
import Big from 'big.js';
Expand Down Expand Up @@ -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');
});
});
});
Loading

0 comments on commit 0dbaf0b

Please sign in to comment.