Skip to content

Commit

Permalink
feat(bonsai-core): modifyUsdcAssetPosition (#1423)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredvu authored Jan 15, 2025
1 parent 800dd22 commit 5218d74
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 53 deletions.
174 changes: 174 additions & 0 deletions src/abacus-ts/calculators/accountActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { produce } from 'immer';

import {
IndexerAssetPositionResponseObject,
IndexerPositionSide,
} from '@/types/indexer/indexerApiGen';

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

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: updatedParentSubaccountData.childSubaccounts[subaccountNumber].assetPositions.USDC,
}),
],
};
}

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: updatedParentSubaccountData.childSubaccounts[subaccountNumber].assetPositions.USDC,
}),
],
};
}

function modifyUsdcAssetPosition(
parentSubaccountData: ParentSubaccountData,
payload: ModifyUsdcAssetPositionProps
): ParentSubaccountData {
const { subaccountNumber, changes } = payload;

return produce(parentSubaccountData, (draftParentSubaccountData) => {
if (draftParentSubaccountData.childSubaccounts[subaccountNumber]?.assetPositions.USDC != null) {
draftParentSubaccountData.childSubaccounts[subaccountNumber].assetPositions.USDC = changes;
}
});
}

export function applyOperationsToSubaccount(
parentSubaccount: ParentSubaccountData,
batchedOperations: SubaccountBatchedOperations
): ParentSubaccountData {
let parentSubaccountData: ParentSubaccountData = parentSubaccount;

batchedOperations.operations.forEach((op) => {
SubaccountOperations.match(op, {
AddPerpetualPosition: () => {
// TODO: Implement addPerpetualPosition
},
ModifyPerpetualPosition: () => {
// TODO: Implement modifyPerpetualPosition
},
ModifyUsdcAssetPosition: (args) => {
parentSubaccountData = modifyUsdcAssetPosition(parentSubaccountData, args);
},
});
});

return parentSubaccountData;
}
60 changes: 60 additions & 0 deletions src/abacus-ts/lib/subaccountUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
IndexerPositionSide,
IndexerSubaccountResponseObject,
} from '@/types/indexer/indexerApiGen';

import { ChildSubaccountData } from '../types/rawTypes';

export function isValidSubaccount(childSubaccount: IndexerSubaccountResponseObject) {
return (
Object.keys(childSubaccount.assetPositions).length > 0 ||
Object.keys(childSubaccount.openPerpetualPositions).length > 0
);
}

export function convertToStoredChildSubaccount({
address,
subaccountNumber,
assetPositions,
openPerpetualPositions,
}: IndexerSubaccountResponseObject): ChildSubaccountData {
return {
address,
subaccountNumber,
assetPositions,
openPerpetualPositions,
};
}

export function freshChildSubaccount({
address,
subaccountNumber,
}: {
address: string;
subaccountNumber: number;
}): ChildSubaccountData {
return {
address,
subaccountNumber,
assetPositions: {},
openPerpetualPositions: {},
};
}

export function newUsdcAssetPosition({
side,
size,
subaccountNumber,
}: {
side: IndexerPositionSide;
size: string;
subaccountNumber: number;
}) {
return {
assetId: '0',
size,
subaccountNumber,
side,
symbol: 'USDC',
};
}
12 changes: 12 additions & 0 deletions src/abacus-ts/ontology.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
selectParentSubaccountSummaryLoading,
selectUnopenedIsolatedPositions,
} from './selectors/account';
import {
createSelectParentSubaccountSummaryDeposit,
createSelectParentSubaccountSummaryWithdrawal,
} from './selectors/accountActions';
import {
selectAllAssetsInfo,
selectAllAssetsInfoLoading,
Expand Down Expand Up @@ -98,6 +102,14 @@ export const BonsaiHelpers = {
fills: getCurrentMarketAccountFills,
},
},
forms: {
deposit: {
createSelectParentSubaccountSummary: createSelectParentSubaccountSummaryDeposit,
},
withdraw: {
createSelectParentSubaccountSummary: createSelectParentSubaccountSummaryWithdrawal,
},
},
unopenedIsolatedPositions: selectUnopenedIsolatedPositions,
} as const satisfies NestedSelectors;

Expand Down
2 changes: 1 addition & 1 deletion src/abacus-ts/selectors/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const selectRelevantMarketsList = createAppSelector(
}
);

const selectRelevantMarketsData = createAppSelector(
export const selectRelevantMarketsData = createAppSelector(
[selectRelevantMarketsList, selectRawMarketsData],
(marketIds, markets) => {
if (markets == null || marketIds == null) {
Expand Down
60 changes: 60 additions & 0 deletions src/abacus-ts/selectors/accountActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { createAppSelector } from '@/state/appTypes';

import {
applyOperationsToSubaccount,
createUsdcDepositOperations,
createUsdcWithdrawalOperations,
} from '../calculators/accountActions';
import { calculateParentSubaccountSummary } from '../calculators/subaccount';
import { selectRelevantMarketsData } from './account';
import { selectRawParentSubaccountData } from './base';

export const createSelectParentSubaccountSummaryDeposit = () =>
createAppSelector(
[
selectRawParentSubaccountData,
selectRelevantMarketsData,
(
_s,
input: {
subaccountNumber: number;
depositAmount: string;
}
) => input,
],
(parentSubaccount, markets, depositInputs) => {
if (parentSubaccount == null || markets == null) {
return undefined;
}

const operations = createUsdcDepositOperations(parentSubaccount, depositInputs);
const modifiedParentSubaccount = applyOperationsToSubaccount(parentSubaccount, operations);
const result = calculateParentSubaccountSummary(modifiedParentSubaccount, markets);
return result;
}
);

export const createSelectParentSubaccountSummaryWithdrawal = () =>
createAppSelector(
[
selectRawParentSubaccountData,
selectRelevantMarketsData,
(
_s,
input: {
subaccountNumber: number;
withdrawAmount: string;
}
) => input,
],
(parentSubaccount, markets, withdrawalInputs) => {
if (parentSubaccount == null || markets == null) {
return undefined;
}

const operations = createUsdcWithdrawalOperations(parentSubaccount, withdrawalInputs);
const modifiedParentSubaccount = applyOperationsToSubaccount(parentSubaccount, operations);
const result = calculateParentSubaccountSummary(modifiedParentSubaccount, markets);
return result;
}
);
32 changes: 18 additions & 14 deletions src/abacus-ts/types/operationTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@ import {
IndexerPerpetualPositionResponseObject,
} from '@/types/indexer/indexerApiGen';

export type AddPerpetualPositionProps = {
subaccountNumber: number;
market: string;
changes: Omit<IndexerPerpetualPositionResponseObject, 'market' | 'subaccountNumber'>;
};

export type ModifyPerpetualPositionProps = {
changes: Partial<Omit<IndexerPerpetualPositionResponseObject, 'market' | 'subaccountNumber'>>;
};

export type ModifyUsdcAssetPositionProps = {
subaccountNumber: number;
changes: IndexerAssetPositionResponseObject;
};

export const SubaccountOperations = unionize(
{
AddPerpetualPosition: ofType<{
subaccountNumber: string;
market: string;
position: Omit<IndexerPerpetualPositionResponseObject, 'market' | 'subaccountNumber'>;
}>(),
ModifyPerpetualPosition: ofType<{
subaccountNumber: string;
market: string;
changes: Partial<Omit<IndexerPerpetualPositionResponseObject, 'market' | 'subaccountNumber'>>;
}>(),
ModifyUsdcAssetPosition: ofType<{
subaccountNumber: string;
changes: Partial<Pick<IndexerAssetPositionResponseObject, 'size' | 'side'>>;
}>(),
AddPerpetualPosition: ofType<AddPerpetualPositionProps>(),
ModifyPerpetualPosition: ofType<ModifyPerpetualPositionProps>(),
ModifyUsdcAssetPosition: ofType<ModifyUsdcAssetPositionProps>(),
},
{ tag: 'operation' as const, value: 'payload' as const }
);
Expand Down
Loading

0 comments on commit 5218d74

Please sign in to comment.