UNPKG

@drift-labs/sdk

Version:
827 lines (716 loc) • 28.2 kB
import { BN, ZERO, User, UserAccount, PublicKey, PerpMarketAccount, SpotMarketAccount, PRICE_PRECISION, OraclePriceData, MMOraclePriceData, BASE_PRECISION, QUOTE_PRECISION, calculatePositionPNL, SPOT_MARKET_BALANCE_PRECISION, getWorstCaseTokenAmounts, StrictOraclePrice, LAMPORTS_PRECISION, SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION, SpotBalanceType, MARGIN_PRECISION, getSpotAssetValue, } from '../../src'; import { MockUserMap, mockPerpMarkets, mockSpotMarkets } from '../dlob/helpers'; import { assert } from '../../src/assert/assert'; import { mockUserAccount, makeMockUser as makeMockUserFromHelpers, } from './helpers'; import * as _ from 'lodash'; async function makeMockUser( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, perpOraclePriceList, spotOraclePriceList ): Promise<User> { const umap = new MockUserMap(); const mockUser: User = await umap.mustGet('1'); mockUser._isSubscribed = true; mockUser.driftClient._isSubscribed = true; mockUser.driftClient.accountSubscriber.isSubscribed = true; const oraclePriceMap = {}; // console.log(perpOraclePriceList, myMockPerpMarkets.length); // console.log(spotOraclePriceList, myMockSpotMarkets.length); for (let i = 0; i < myMockPerpMarkets.length; i++) { oraclePriceMap[myMockPerpMarkets[i].amm.oracle.toString()] = perpOraclePriceList[i]; } for (let i = 0; i < myMockSpotMarkets.length; i++) { oraclePriceMap[myMockSpotMarkets[i].oracle.toString()] = spotOraclePriceList[i]; } // console.log('oraclePriceMap:', oraclePriceMap); function getMockUserAccount(): UserAccount { return myMockUserAccount; } function getMockPerpMarket(marketIndex): PerpMarketAccount { return myMockPerpMarkets[marketIndex]; } function getMockSpotMarket(marketIndex): SpotMarketAccount { return myMockSpotMarkets[marketIndex]; } function getMockOracle(oracleKey: PublicKey) { // console.log('oracleKey.toString():', oracleKey.toString()); // console.log( // 'oraclePriceMap[oracleKey.toString()]:', // oraclePriceMap[oracleKey.toString()] // ); const QUOTE_ORACLE_PRICE_DATA: OraclePriceData = { price: new BN( oraclePriceMap[oracleKey.toString()] * PRICE_PRECISION.toNumber() ), slot: new BN(0), confidence: new BN(1), hasSufficientNumberOfDataPoints: true, }; return { data: QUOTE_ORACLE_PRICE_DATA, slot: 0, }; } function getOracleDataForPerpMarket(marketIndex) { const oracle = getMockPerpMarket(marketIndex).amm.oracle; return getMockOracle(oracle).data; } function getOracleDataForSpotMarket(marketIndex) { const oracle = getMockSpotMarket(marketIndex).oracle; return getMockOracle(oracle).data; } function getMMOracleDataForPerpMarket( marketIndex: number ): MMOraclePriceData { const oracle = getMockPerpMarket(marketIndex).amm.oracle; return getMockOracle(oracle).data as unknown as MMOraclePriceData; } mockUser.getUserAccount = getMockUserAccount; mockUser.driftClient.getPerpMarketAccount = getMockPerpMarket; mockUser.driftClient.getSpotMarketAccount = getMockSpotMarket; mockUser.driftClient.getOraclePriceDataAndSlot = getMockOracle; mockUser.driftClient.getOracleDataForPerpMarket = getOracleDataForPerpMarket; mockUser.driftClient.getOracleDataForSpotMarket = getOracleDataForSpotMarket; mockUser.driftClient.getMMOracleDataForPerpMarket = getMMOracleDataForPerpMarket; return mockUser; } describe('User Tests', () => { it('empty user account', async () => { console.log(mockSpotMarkets[0]); const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); console.log( 'spot cumulativeDepositInterest:', mockSpotMarkets[0].cumulativeDepositInterest.toString() ); const mockUser: User = await makeMockUser( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const uA = mockUser.getUserAccount(); assert(uA.idle == false); console.log( 'spot cumulativeDepositInterest:', myMockSpotMarkets[0].cumulativeDepositInterest.toString() ); assert(mockUser.getFreeCollateral().eq(ZERO)); console.log(mockUser.getHealth()); assert(mockUser.getHealth() == 100); console.log(mockUser.getMaxLeverageForPerp(0)); assert(mockUser.getMaxLeverageForPerp(0).eq(ZERO)); }); it('user account unsettled pnl', async () => { // no collateral, but positive upnl no liability const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.perpPositions[0].baseAssetAmount = new BN( 0 * BASE_PRECISION.toNumber() ); myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN( 10 * QUOTE_PRECISION.toNumber() ); assert( myMockUserAccount.perpPositions[0].quoteAssetAmount.eq(new BN('10000000')) ); // $10 const mockUser: User = await makeMockUser( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const uA = mockUser.getUserAccount(); assert(uA.idle == false); const activePerps = mockUser.getActivePerpPositions(); assert(activePerps.length == 1); assert(uA.perpPositions[0].quoteAssetAmount.eq(new BN('10000000'))); // $10 assert(mockUser.getFreeCollateral().eq(ZERO)); const quotePrice = mockUser.driftClient.getOracleDataForSpotMarket(0).price; console.log('quotePrice:', quotePrice.toString()); assert(quotePrice.eq(new BN('1000000'))); const pnl1 = calculatePositionPNL( myMockPerpMarkets[0], activePerps[0], false, mockUser.driftClient.getOracleDataForPerpMarket(0) ); console.log('pnl1:', pnl1.toString()); assert(pnl1.eq(new BN('10000000'))); const upnl = mockUser.getUnrealizedPNL(false, undefined, undefined, false); console.log('upnl:', upnl.toString()); assert(upnl.eq(new BN('10000000'))); // $10 const liqResult = mockUser.canBeLiquidated(); console.log(liqResult); assert(liqResult.canBeLiquidated == false); assert(liqResult.marginRequirement.eq(ZERO)); assert(liqResult.totalCollateral.eq(ZERO)); console.log(mockUser.getHealth()); assert(mockUser.getHealth() == 100); console.log(mockUser.getMaxLeverageForPerp(0)); assert(mockUser.getMaxLeverageForPerp(0).eq(ZERO)); }); it('liquidatable long user account', async () => { // no collateral, but positive upnl w/ liability const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.perpPositions[0].baseAssetAmount = new BN( 20 * BASE_PRECISION.toNumber() ); myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN( -10 * QUOTE_PRECISION.toNumber() ); const mockUser: User = await makeMockUser( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const uA = mockUser.getUserAccount(); assert(uA.idle == false); assert(mockUser.getFreeCollateral().eq(ZERO)); const upnl = mockUser.getUnrealizedPNL(true, 0, undefined, false); console.log('upnl:', upnl.toString()); assert(upnl.eq(new BN('10000000'))); // $10 const liqResult = mockUser.canBeLiquidated(); console.log(liqResult); assert(liqResult.canBeLiquidated == true); assert(liqResult.marginRequirement.eq(new BN('2000000'))); //10x maint leverage assert(liqResult.totalCollateral.eq(ZERO)); console.log(mockUser.getHealth()); assert(mockUser.getHealth() == 0); console.log(mockUser.getMaxLeverageForPerp(0)); assert(mockUser.getMaxLeverageForPerp(0).eq(new BN('20000'))); }); it('large usdc user account', async () => { // no collateral, but positive upnl w/ liability const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockPerpMarkets[0].imfFactor = 550; myMockUserAccount.spotPositions[0].scaledBalance = new BN( 10000 * SPOT_MARKET_BALANCE_PRECISION.toNumber() ); //10k const mockUser: User = await makeMockUser( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const uA = mockUser.getUserAccount(); assert(uA.idle == false); assert(uA.perpPositions[0].baseAssetAmount.eq(ZERO)); assert(uA.perpPositions[0].quoteAssetAmount.eq(ZERO)); assert(mockUser.getActivePerpPositions().length == 0); assert( uA.spotPositions[0].scaledBalance.eq( new BN(10000 * SPOT_MARKET_BALANCE_PRECISION.toNumber()) ) ); for (let i = 1; i < 8; i++) { assert(uA.spotPositions[i].scaledBalance.eq(ZERO)); } console.log( 'mockUser.getTokenAmount():', mockUser.getTokenAmount(0).toString() ); console.log( 'spot cumulativeDepositInterest:', mockSpotMarkets[0].cumulativeDepositInterest.toString() ); const expectedAmount = new BN('10000000000'); assert(mockUser.getTokenAmount(0).eq(expectedAmount)); assert(mockUser.getNetSpotMarketValue().eq(expectedAmount)); assert( mockUser .getSpotMarketAssetAndLiabilityValue() .totalLiabilityValue.eq(ZERO) ); assert(mockUser.getFreeCollateral().gt(ZERO)); const upnl = mockUser.getUnrealizedPNL(true, 0, undefined, false); console.log('upnl:', upnl.toString()); assert(upnl.eq(new BN('0'))); // $10 const liqResult = mockUser.canBeLiquidated(); console.log(liqResult); assert(liqResult.canBeLiquidated == false); assert(liqResult.marginRequirement.eq(new BN('0'))); //10x maint leverage assert(liqResult.totalCollateral.eq(expectedAmount)); console.log(mockUser.getHealth()); assert(mockUser.getHealth() == 100); console.log( 'getMaxLeverageForPerp:', mockUser.getMaxLeverageForPerp(0).toString() ); assert(mockUser.getMaxLeverageForPerp(0).eq(new BN('37358'))); // ~3.7x assert( mockUser.getMaxLeverageForPerp(0, 'Maintenance').eq(new BN('37358')) ); // same (marginCategory unused) }); it('worst case token amount', async () => { const myMockUserAccount = _.cloneDeep(mockUserAccount); const solMarket = Object.assign({}, _.cloneDeep(mockSpotMarkets[1]), { initialAssetWeight: 8000, initialLiabilityWeight: 12000, cumulativeDepositInterest: SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION, cumulativeBorrowInterest: SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION, }); const strictOraclePrice = new StrictOraclePrice(PRICE_PRECISION.muln(100)); let spotPosition = Object.assign({}, myMockUserAccount.spotPositions[1], { marketIndex: 1, openBids: new BN(100).mul(LAMPORTS_PRECISION), }); let worstCase = getWorstCaseTokenAmounts( spotPosition, solMarket, strictOraclePrice, 'Initial' ); assert(worstCase.tokenAmount.eq(new BN(100).mul(LAMPORTS_PRECISION))); // 100 assert(worstCase.tokenValue.eq(new BN(10000).mul(PRICE_PRECISION))); // $10k assert(worstCase.weightedTokenValue.eq(new BN(8000).mul(PRICE_PRECISION))); // $8k assert(worstCase.ordersValue.eq(new BN(-10000).mul(PRICE_PRECISION))); // -$10k assert( worstCase.freeCollateralContribution.eq( new BN(-2000).mul(QUOTE_PRECISION) ) ); // -$2k spotPosition = Object.assign({}, myMockUserAccount.spotPositions[1], { marketIndex: 1, scaledBalance: new BN(100).mul(SPOT_MARKET_BALANCE_PRECISION), openBids: new BN(100).mul(LAMPORTS_PRECISION), }); worstCase = getWorstCaseTokenAmounts( spotPosition, solMarket, strictOraclePrice, 'Initial' ); assert(worstCase.tokenAmount.eq(new BN(200).mul(LAMPORTS_PRECISION))); // 200 assert(worstCase.tokenValue.eq(new BN(20000).mul(PRICE_PRECISION))); // $20k assert(worstCase.weightedTokenValue.eq(new BN(16000).mul(PRICE_PRECISION))); // $16k assert(worstCase.ordersValue.eq(new BN(-10000).mul(PRICE_PRECISION))); // -$10k assert( worstCase.freeCollateralContribution.eq(new BN(6000).mul(QUOTE_PRECISION)) ); // $6k spotPosition = Object.assign({}, myMockUserAccount.spotPositions[1], { marketIndex: 1, openAsks: new BN(-100).mul(LAMPORTS_PRECISION), }); worstCase = getWorstCaseTokenAmounts( spotPosition, solMarket, strictOraclePrice, 'Initial' ); assert(worstCase.tokenAmount.eq(new BN(-100).mul(LAMPORTS_PRECISION))); assert(worstCase.tokenValue.eq(new BN(-10000).mul(PRICE_PRECISION))); // -$10k assert( worstCase.weightedTokenValue.eq(new BN(-12000).mul(PRICE_PRECISION)) ); // -$12k assert(worstCase.ordersValue.eq(new BN(10000).mul(PRICE_PRECISION))); // $10k assert( worstCase.freeCollateralContribution.eq( new BN(-2000).mul(QUOTE_PRECISION) ) ); // -$2k spotPosition = Object.assign({}, myMockUserAccount.spotPositions[1], { marketIndex: 1, balanceType: SpotBalanceType.BORROW, scaledBalance: new BN(100).mul(SPOT_MARKET_BALANCE_PRECISION), openAsks: new BN(-100).mul(LAMPORTS_PRECISION), }); worstCase = getWorstCaseTokenAmounts( spotPosition, solMarket, strictOraclePrice, 'Initial' ); assert(worstCase.tokenAmount.eq(new BN(-200).mul(LAMPORTS_PRECISION))); assert(worstCase.tokenValue.eq(new BN(-20000).mul(PRICE_PRECISION))); // -$20k assert( worstCase.weightedTokenValue.eq(new BN(-24000).mul(PRICE_PRECISION)) ); // -$24k assert(worstCase.ordersValue.eq(new BN(10000).mul(PRICE_PRECISION))); // $10k assert( worstCase.freeCollateralContribution.eq( new BN(-14000).mul(QUOTE_PRECISION) ) ); // -$2k }); it('custom margin ratio (sol spot)', async () => { const myMockUserAccount = _.cloneDeep(mockUserAccount); const solMarket = Object.assign({}, _.cloneDeep(mockSpotMarkets[1]), { initialAssetWeight: 8000, initialLiabilityWeight: 12000, cumulativeDepositInterest: SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION, cumulativeBorrowInterest: SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION, }); // $25 const strictOraclePrice = new StrictOraclePrice(PRICE_PRECISION.muln(25)); const spotPosition = Object.assign({}, myMockUserAccount.spotPositions[1], { marketIndex: 1, openBids: new BN(100).mul(LAMPORTS_PRECISION), }); const worstCase = getWorstCaseTokenAmounts( spotPosition, solMarket, strictOraclePrice, 'Initial', myMockUserAccount.maxMarginRatio ); console.log(worstCase); assert(worstCase.weight.eq(new BN(8000))); myMockUserAccount.maxMarginRatio = MARGIN_PRECISION.toNumber(); // max 1x pls const worstCaseAfter = getWorstCaseTokenAmounts( spotPosition, solMarket, strictOraclePrice, 'Initial', myMockUserAccount.maxMarginRatio ); console.log(worstCaseAfter); assert(worstCaseAfter.weight.eq(new BN(0))); // not allowed to increase exposure // customMarginRatio > SPOT weight precision must not throw (BN subn asserted non-negative) const depositOnlyNonQuote = Object.assign( {}, myMockUserAccount.spotPositions[1], { marketIndex: 1, scaledBalance: new BN(100).mul(SPOT_MARKET_BALANCE_PRECISION), openBids: ZERO, openAsks: ZERO, } ); for (const ratio of [ MARGIN_PRECISION.toNumber() * 2, MARGIN_PRECISION.toNumber() * 10, 89478485, ]) { const wc = getWorstCaseTokenAmounts( depositOnlyNonQuote, solMarket, strictOraclePrice, 'Initial', ratio ); assert(wc.weight.eq(new BN(0))); assert(wc.weightedTokenValue.eq(ZERO)); } }); it('getSpotAssetValue: large maxMarginRatio does not throw (matches getFreeCollateral path)', () => { const solMarket = Object.assign({}, _.cloneDeep(mockSpotMarkets[1]), { marketIndex: 1, initialAssetWeight: 8000, initialLiabilityWeight: 12000, cumulativeDepositInterest: SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION, cumulativeBorrowInterest: SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION, }); const strictOraclePrice = new StrictOraclePrice(PRICE_PRECISION.muln(25)); const tokenAmount = new BN(100).mul(LAMPORTS_PRECISION); for (const ratio of [MARGIN_PRECISION.toNumber() * 2, 89478485]) { const assetValue = getSpotAssetValue( tokenAmount, strictOraclePrice, solMarket, ratio, 'Initial' ); assert(assetValue.eq(ZERO)); } }); it('custom margin ratio (sol perp)', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); // myMockPerpMarkets[0].imfFactor = 550; myMockPerpMarkets[0].marginRatioInitial = 2000; // 5x myMockPerpMarkets[0].marginRatioMaintenance = 1000; // 10x myMockSpotMarkets[0].initialAssetWeight = 1000; myMockSpotMarkets[0].initialLiabilityWeight = 1000; myMockUserAccount.spotPositions[0].scaledBalance = new BN( 10000 * SPOT_MARKET_BALANCE_PRECISION.toNumber() ); //10k const mockUser: User = await makeMockUser( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); assert(mockUser.getTokenAmount(0).eq(new BN('10000000000'))); assert(mockUser.getNetSpotMarketValue().eq(new BN('10000000000'))); assert( mockUser .getSpotMarketAssetAndLiabilityValue() .totalLiabilityValue.eq(ZERO) ); assert(mockUser.getFreeCollateral().gt(ZERO)); let iLev = mockUser.getMaxLeverageForPerp(0, 'Initial').toNumber(); let mLev = mockUser.getMaxLeverageForPerp(0, 'Maintenance').toNumber(); console.log(iLev, mLev); assert(iLev == 5000); assert(mLev == 5000); myMockUserAccount.maxMarginRatio = MARGIN_PRECISION.div( new BN(2) ).toNumber(); // 2x max pls const mockUser2: User = await makeMockUser( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); iLev = mockUser2.getMaxLeverageForPerp(0, 'Initial').toNumber(); mLev = mockUser2.getMaxLeverageForPerp(0, 'Maintenance').toNumber(); console.log(iLev, mLev); assert(iLev == 2000); assert(mLev == 2000); }); it('getTotalIsolatedPositionDeposits sums isolated USDC deposits', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); // Give perp position 0 an isolated deposit of 100 USDC // mockSpotMarkets[0] is USDC with cumulativeDepositInterest = SPOT_MARKET_CUMULATIVE_INTEREST_PRECISION // so scaledBalance of 100 * SPOT_MARKET_BALANCE_PRECISION = 100 USDC token amount myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(1); // make position active myMockUserAccount.perpPositions[0].isolatedPositionScaledBalance = new BN( 100 ).mul(SPOT_MARKET_BALANCE_PRECISION); // Give perp position 1 an isolated deposit of 50 USDC myMockUserAccount.perpPositions[1].marketIndex = 1; myMockUserAccount.perpPositions[1].baseAssetAmount = new BN(1); myMockUserAccount.perpPositions[1].isolatedPositionScaledBalance = new BN( 50 ).mul(SPOT_MARKET_BALANCE_PRECISION); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const totalIsolatedDeposits = mockUser.getTotalIsolatedPositionDeposits(); // 150 USDC = 150 * QUOTE_PRECISION assert(totalIsolatedDeposits.eq(new BN(150).mul(QUOTE_PRECISION))); }); it('getTotalIsolatedPositionDeposits applies oracle price for depeg', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); // Give spot market 0 (USDC) a unique oracle so it gets its own price // (all mock markets share PublicKey.default, causing oracle map overwrites) const usdcOracle = new PublicKey( 'Erq8cpkof3kitj7rkzKba3j1Hdib6gFFZ7QktwGpsa3w' ); myMockSpotMarkets[0].oracle = usdcOracle; // 100 USDC isolated deposit on perp position 0 myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(1); myMockUserAccount.perpPositions[0].isolatedPositionScaledBalance = new BN( 100 ).mul(SPOT_MARKET_BALANCE_PRECISION); // Spot oracle price list: index 0 = USDC at $0.99 (depeg) const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [0.99, 1, 1, 1, 1, 1, 1, 1] ); const totalIsolatedDeposits = mockUser.getTotalIsolatedPositionDeposits(); // 100 tokens * $0.99 = $99 = 99 * QUOTE_PRECISION assert(totalIsolatedDeposits.eq(new BN(99).mul(QUOTE_PRECISION))); }); it('getNetUsdValue includes isolated position deposits', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); // 200 USDC cross-margin deposit in spot position 0 (USDC market) myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].scaledBalance = new BN(200).mul( SPOT_MARKET_BALANCE_PRECISION ); myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; // 100 USDC isolated deposit on perp position 0 myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = BASE_PRECISION; // 1 unit long myMockUserAccount.perpPositions[0].quoteAssetAmount = QUOTE_PRECISION.neg(); // entered at $1, PnL=0 myMockUserAccount.perpPositions[0].quoteEntryAmount = QUOTE_PRECISION.neg(); // entered at $1 myMockUserAccount.perpPositions[0].quoteBreakEvenAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].isolatedPositionScaledBalance = new BN( 100 ).mul(SPOT_MARKET_BALANCE_PRECISION); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const netUsdValue = mockUser.getNetUsdValue(); // Cross spot: 200 USDC + Isolated deposit: 100 USDC + PnL: 0 = 300 USDC assert(netUsdValue.eq(new BN(300).mul(QUOTE_PRECISION))); }); it('getTotalAssetValue includes isolated position deposits', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); // 200 USDC cross-margin deposit myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].scaledBalance = new BN(200).mul( SPOT_MARKET_BALANCE_PRECISION ); myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; // 100 USDC isolated deposit on perp position 0 myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = BASE_PRECISION; myMockUserAccount.perpPositions[0].quoteAssetAmount = QUOTE_PRECISION.neg(); // PnL=0 at oracle $1 myMockUserAccount.perpPositions[0].quoteEntryAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].quoteBreakEvenAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].isolatedPositionScaledBalance = new BN( 100 ).mul(SPOT_MARKET_BALANCE_PRECISION); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const totalAssetValue = mockUser.getTotalAssetValue(); // Cross spot asset: 200 USDC + Isolated: 100 USDC + PnL: 0 = 300 USDC assert(totalAssetValue.eq(new BN(300).mul(QUOTE_PRECISION))); }); it('getTotalAssetValue with Initial margin excludes isolated position deposits', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); // 200 USDC cross-margin deposit myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].scaledBalance = new BN(200).mul( SPOT_MARKET_BALANCE_PRECISION ); myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; // 100 USDC isolated deposit on perp position 0 myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = BASE_PRECISION; myMockUserAccount.perpPositions[0].quoteAssetAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].quoteEntryAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].quoteBreakEvenAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].isolatedPositionScaledBalance = new BN( 100 ).mul(SPOT_MARKET_BALANCE_PRECISION); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const totalAssetValue = mockUser.getTotalAssetValue('Initial'); // Cross spot asset only: 200 USDC. Isolated collateral is handled separately. assert(totalAssetValue.eq(new BN(200).mul(QUOTE_PRECISION))); }); it('getLeverageComponents aggregate path includes isolated deposits in spotAssetValue', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); // 200 USDC cross-margin deposit myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].scaledBalance = new BN(200).mul( SPOT_MARKET_BALANCE_PRECISION ); myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; // 100 USDC isolated deposit on perp position 0 with 1 unit long at $1 myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = BASE_PRECISION; myMockUserAccount.perpPositions[0].quoteEntryAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].quoteBreakEvenAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].isolatedPositionScaledBalance = new BN( 100 ).mul(SPOT_MARKET_BALANCE_PRECISION); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const { spotAssetValue } = mockUser.getLeverageComponents(); // Cross spot: 200 USDC + Isolated: 100 USDC = 300 USDC assert(spotAssetValue.eq(new BN(300).mul(QUOTE_PRECISION))); }); it('getLeverageComponents with Initial margin excludes isolated deposits from aggregate spotAssetValue', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); // 200 USDC cross-margin deposit myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].scaledBalance = new BN(200).mul( SPOT_MARKET_BALANCE_PRECISION ); myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; // 100 USDC isolated deposit on perp position 0 with 1 unit long at $1 myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = BASE_PRECISION; myMockUserAccount.perpPositions[0].quoteEntryAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].quoteBreakEvenAmount = QUOTE_PRECISION.neg(); myMockUserAccount.perpPositions[0].isolatedPositionScaledBalance = new BN( 100 ).mul(SPOT_MARKET_BALANCE_PRECISION); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1] ); const { spotAssetValue } = mockUser.getLeverageComponents(true, 'Initial'); // Cross spot asset only: 200 USDC. Isolated collateral is handled separately. assert(spotAssetValue.eq(new BN(200).mul(QUOTE_PRECISION))); }); });