Skip to content

Commit

Permalink
Merge branch 'main' into jv/withdraw-rewrite-3
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredvu committed Jan 15, 2025
2 parents 71c863b + 16e64c9 commit 45f05b6
Show file tree
Hide file tree
Showing 29 changed files with 804 additions and 306 deletions.
6 changes: 3 additions & 3 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@
"error",
{
"patterns": [
"@/abacus-ts/*",
"@/abacus-ts/*/*",
"!@/abacus-ts/ontology",
"!@/abacus-ts/lib",
"!@/abacus-ts/summaryTypes"
"!@/abacus-ts/lib/*",
"!@/abacus-ts/types/summaryTypes"
]
}
],
Expand Down
184 changes: 115 additions & 69 deletions src/abacus-ts/calculators/accountActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,100 +7,146 @@ import {

import { MustBigNumber } from '@/lib/numbers';

import { SubaccountBatchedOperations, SubaccountOperations } from '../types/operationTypes';
import { ChildSubaccountData, ParentSubaccountData } from '../types/rawTypes';

export function createUsdcDepositOperations({
subaccountNumber,
depositAmount,
}: {
subaccountNumber: number;
depositAmount: string;
}): SubaccountBatchedOperations {
import { freshChildSubaccount, newUsdcAssetPosition } from '../lib/subaccountUtils';
import {
ModifyUsdcAssetPositionProps,
SubaccountBatchedOperations,
SubaccountOperations,
} from '../types/operationTypes';
import { ParentSubaccountData } from '../types/rawTypes';

function addUsdcAssetPosition(
parentSubaccount: ParentSubaccountData,
payload: Pick<IndexerAssetPositionResponseObject, 'side' | 'size' | 'subaccountNumber'>
): ParentSubaccountData {
const { side, size, subaccountNumber } = payload;
return produce(parentSubaccount, (draftParentSubaccountData) => {
let childSubaccount = draftParentSubaccountData.childSubaccounts[subaccountNumber];

if (childSubaccount == null) {
// Upsert ChildSubaccountData into parentSubaccountData.childSubaccounts
const updatedChildSubaccount = freshChildSubaccount({
address: draftParentSubaccountData.address,
subaccountNumber,
});

childSubaccount = {
...updatedChildSubaccount,
assetPositions: {
...updatedChildSubaccount.assetPositions,
USDC: newUsdcAssetPosition({
side,
size,
subaccountNumber,
}),
},
};
} else {
if (childSubaccount.assetPositions.USDC == null) {
// Upsert USDC Asset Position
childSubaccount.assetPositions.USDC = newUsdcAssetPosition({
side,
size,
subaccountNumber,
});
} else {
if (childSubaccount.assetPositions.USDC.side !== side) {
const signedSizeBN = MustBigNumber(childSubaccount.assetPositions.USDC.size).minus(size);

if (signedSizeBN.lte(0)) {
// New size flips the Asset Position Side
childSubaccount.assetPositions.USDC.side =
side === IndexerPositionSide.LONG
? IndexerPositionSide.SHORT
: IndexerPositionSide.LONG;
childSubaccount.assetPositions.USDC.size = signedSizeBN.abs().toString();
} else {
// Set the new size of the Asset Position
childSubaccount.assetPositions.USDC.size = signedSizeBN.toString();
}
} else {
// Side is maintained, add the size to the existing position
childSubaccount.assetPositions.USDC.size = MustBigNumber(
childSubaccount.assetPositions.USDC.size
)
.plus(size)
.toString();
}
}
}
});
}

export function createUsdcDepositOperations(
parentSubaccount: ParentSubaccountData,
{
subaccountNumber,
depositAmount,
}: {
subaccountNumber: number;
depositAmount: string;
}
): SubaccountBatchedOperations {
const updatedParentSubaccountData = addUsdcAssetPosition(parentSubaccount, {
side: IndexerPositionSide.LONG,
size: depositAmount,
subaccountNumber,
});

if (updatedParentSubaccountData.childSubaccounts[subaccountNumber]?.assetPositions.USDC == null) {
throw new Error('USDC Asset Position was improperly modified');
}

return {
operations: [
SubaccountOperations.ModifyUsdcAssetPosition({
subaccountNumber,
changes: {
size: depositAmount,
},
changes: updatedParentSubaccountData.childSubaccounts[subaccountNumber].assetPositions.USDC,
}),
],
};
}

export function createUsdcWithdrawalOperations({
subaccountNumber,
withdrawAmount,
}: {
subaccountNumber: number;
withdrawAmount: string;
}): SubaccountBatchedOperations {
export function createUsdcWithdrawalOperations(
parentSubaccount: ParentSubaccountData,
{
subaccountNumber,
withdrawAmount,
}: {
subaccountNumber: number;
withdrawAmount: string;
}
): SubaccountBatchedOperations {
const updatedParentSubaccountData = addUsdcAssetPosition(parentSubaccount, {
side: IndexerPositionSide.SHORT,
size: withdrawAmount,
subaccountNumber,
});

if (updatedParentSubaccountData.childSubaccounts[subaccountNumber]?.assetPositions.USDC == null) {
throw new Error('USDC Asset Position was improperly modified');
}

return {
operations: [
SubaccountOperations.ModifyUsdcAssetPosition({
subaccountNumber,
changes: {
size: MustBigNumber(withdrawAmount).negated().toString(),
},
changes: updatedParentSubaccountData.childSubaccounts[subaccountNumber].assetPositions.USDC,
}),
],
};
}

function modifyUsdcAssetPosition(
parentSubaccountData: ParentSubaccountData,
payload: {
subaccountNumber: number;
changes: Partial<Pick<IndexerAssetPositionResponseObject, 'size'>>;
}
payload: ModifyUsdcAssetPositionProps
): ParentSubaccountData {
const { subaccountNumber, changes } = payload;
if (!changes.size) return parentSubaccountData;

return produce(parentSubaccountData, (draftParentSubaccountData) => {
const sizeBN = MustBigNumber(changes.size);

let childSubaccount: ChildSubaccountData | undefined =
draftParentSubaccountData.childSubaccounts[subaccountNumber];

if (childSubaccount != null) {
// Modify childSubaccount
if (childSubaccount.assetPositions.USDC != null) {
const size = MustBigNumber(childSubaccount.assetPositions.USDC.size)
.plus(sizeBN)
.toString();

childSubaccount.assetPositions.USDC.size = size;
} else if (sizeBN.gt(0)) {
// Upsert USDC Asset Position
childSubaccount.assetPositions.USDC = {
assetId: '0',
symbol: 'USDC',
size: sizeBN.toString(),
side: IndexerPositionSide.LONG,
subaccountNumber,
} satisfies IndexerAssetPositionResponseObject;
}
} else {
// Upsert ChildSubaccountData into parentSubaccountData.childSubaccounts
childSubaccount = {
address: parentSubaccountData.address,
subaccountNumber,
openPerpetualPositions: {},
assetPositions: {
USDC: {
assetId: '0',
symbol: 'USDC',
size: sizeBN.toString(),
side: IndexerPositionSide.LONG,
subaccountNumber,
},
},
} satisfies ChildSubaccountData;
if (draftParentSubaccountData.childSubaccounts[subaccountNumber]?.assetPositions.USDC != null) {
draftParentSubaccountData.childSubaccounts[subaccountNumber].assetPositions.USDC = changes;
}

draftParentSubaccountData.childSubaccounts[subaccountNumber] = childSubaccount;
});
}

Expand Down
84 changes: 72 additions & 12 deletions src/abacus-ts/calculators/subaccount.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BigNumber from 'bignumber.js';
import { mapValues, orderBy } from 'lodash';
import { groupBy, map, mapValues, orderBy, pickBy } from 'lodash';
import { weakMapMemoize } from 'reselect';

import { NUM_PARENT_SUBACCOUNTS } from '@/constants/account';
import {
Expand All @@ -9,14 +10,21 @@ import {
} from '@/types/indexer/indexerApiGen';
import { IndexerWsBaseMarketObject } from '@/types/indexer/indexerManual';

import { getAssetFromMarketId } from '@/lib/assetUtils';
import {
getAssetFromMarketId,
getDisplayableAssetFromBaseAsset,
getDisplayableTickerFromMarket,
} from '@/lib/assetUtils';
import { calc } from '@/lib/do';
import { isTruthy } from '@/lib/isTruthy';
import { BIG_NUMBERS, MaybeBigNumber, MustBigNumber, ToBigNumber } from '@/lib/numbers';
import { isPresent } from '@/lib/typeUtils';

import { ChildSubaccountData, MarketsData, ParentSubaccountData } from '../types/rawTypes';
import {
GroupedSubaccountSummary,
PendingIsolatedPosition,
SubaccountOrder,
SubaccountPosition,
SubaccountPositionBase,
SubaccountPositionDerivedCore,
Expand Down Expand Up @@ -74,16 +82,15 @@ export function calculateMarketsNeededForSubaccount(parent: Omit<ParentSubaccoun
);
}

function calculateSubaccountSummary(
subaccountData: ChildSubaccountData,
markets: MarketsData
): SubaccountSummary {
const core = calculateSubaccountSummaryCore(subaccountData, markets);
return {
...core,
...calculateSubaccountSummaryDerived(core),
};
}
const calculateSubaccountSummary = weakMapMemoize(
(subaccountData: ChildSubaccountData, markets: MarketsData): SubaccountSummary => {
const core = calculateSubaccountSummaryCore(subaccountData, markets);
return {
...core,
...calculateSubaccountSummaryDerived(core),
};
}
);

function calculateSubaccountSummaryCore(
subaccountData: ChildSubaccountData,
Expand Down Expand Up @@ -288,3 +295,56 @@ function calculatePositionDerivedExtra(
updatedUnrealizedPnlPercent,
};
}

export function calculateChildSubaccountSummaries(
parent: Omit<ParentSubaccountData, 'live'>,
markets: MarketsData
): Record<string, SubaccountSummary> {
return pickBy(
mapValues(
parent.childSubaccounts,
(subaccount) => subaccount && calculateSubaccountSummary(subaccount, markets)
),
isTruthy
);
}

/**
* @returns a list of pending isolated positions
* PendingIsolatedPosition is exists if there are any orders that meet the following criteria:
* - marginMode is ISOLATED
* - no existing position exists
* - childSubaccount has equity
*/
export function calculateUnopenedIsolatedPositions(
childSubaccounts: Record<string, SubaccountSummary>,
orders: SubaccountOrder[],
positions: SubaccountPosition[]
): PendingIsolatedPosition[] {
const setOfOpenPositionMarkets = new Set(positions.map(({ market }) => market));

const filteredOrders = orders.filter(
(o) => !setOfOpenPositionMarkets.has(o.marketId) && o.marginMode === 'ISOLATED'
);

const filteredOrdersMap = groupBy(filteredOrders, 'marketId');
const marketIdToSubaccountNumber = mapValues(
filteredOrdersMap,
(filteredOrder) => filteredOrder[0]?.subaccountNumber
);

return map(filteredOrdersMap, (orderList, marketId) => {
const subaccountNumber = marketIdToSubaccountNumber[marketId];
if (subaccountNumber == null) return undefined;
const assetId = getAssetFromMarketId(marketId);

return {
assetId,
displayableAsset: getDisplayableAssetFromBaseAsset(assetId),
marketId,
displayId: getDisplayableTickerFromMarket(marketId),
equity: childSubaccounts[subaccountNumber]?.equity ?? BIG_NUMBERS.ZERO,
orders: orderList,
};
}).filter(isTruthy);
}
8 changes: 6 additions & 2 deletions src/abacus-ts/lib/createStoreEffect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ export function createStoreEffect<T>(
lastValue = thisValue;
// NOTE: some cleanups dispatch actions which cause this to happen recursively.
// we must ensure that those actions don't change the state they subscribe to or this will go infinitely
lastCleanup?.();
const lastCleanupCached = lastCleanup;
lastCleanup = undefined;
lastCleanupCached?.();
lastCleanup = handleChange(thisValue);
}
});

return () => {
lastCleanup?.();
const lastCleanupCached = lastCleanup;
lastCleanup = undefined;
lastCleanupCached?.();
removeStoreListener();
};
}
Loading

0 comments on commit 45f05b6

Please sign in to comment.