UNPKG

@drift-labs/sdk

Version:
322 lines (281 loc) • 10.9 kB
import { BN, BASE_PRECISION, QUOTE_PRECISION, SPOT_MARKET_BALANCE_PRECISION, PositionFlag, SpotBalanceType, ZERO, } from '../../src'; import { assert } from '../../src/assert/assert'; import { mockPerpMarkets, mockSpotMarkets } from '../dlob/helpers'; import { mockUserAccount, makeMockUser as makeMockUserFromHelpers, } from './helpers'; import * as _ from 'lodash'; const DEFAULT_ORACLES = [1, 1, 1, 1, 1, 1, 1, 1]; describe('User margin calculations', () => { describe('cross collateral', () => { it('getTotalCollateral for USDC-only account matches expected value', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; myMockUserAccount.spotPositions[0].scaledBalance = new BN(10_000).mul( SPOT_MARKET_BALANCE_PRECISION ); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, DEFAULT_ORACLES, DEFAULT_ORACLES ); const expectedValue = new BN(10_000).mul(QUOTE_PRECISION); assert(mockUser.getTotalCollateral('Initial').eq(expectedValue)); assert(mockUser.getTotalCollateral('Maintenance').eq(expectedValue)); }); it('getInitialMarginRequirement is greater than or equal to maintenance', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; myMockUserAccount.spotPositions[0].scaledBalance = new BN(200).mul( SPOT_MARKET_BALANCE_PRECISION ); 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(); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, DEFAULT_ORACLES, DEFAULT_ORACLES ); const initialMarginRequirement = mockUser.getInitialMarginRequirement(); const maintenanceMarginRequirement = mockUser.getMaintenanceMarginRequirement(); assert(initialMarginRequirement.gte(maintenanceMarginRequirement)); assert(maintenanceMarginRequirement.gt(ZERO)); }); it('getFreeCollateral is positive for solvent cross account', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; myMockUserAccount.spotPositions[0].scaledBalance = new BN(10_000).mul( SPOT_MARKET_BALANCE_PRECISION ); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, DEFAULT_ORACLES, DEFAULT_ORACLES ); assert(mockUser.getFreeCollateral('Initial').gt(ZERO)); }); it('getFreeCollateral is zero for undercollateralized account', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(20).mul( BASE_PRECISION ); myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN(10) .mul(QUOTE_PRECISION) .neg(); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, DEFAULT_ORACLES, DEFAULT_ORACLES ); assert(mockUser.getFreeCollateral('Initial').eq(ZERO)); }); }); describe('isolated collateral', () => { it('cross total collateral excludes isolated deposits', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; myMockUserAccount.spotPositions[0].scaledBalance = new BN(200).mul( SPOT_MARKET_BALANCE_PRECISION ); myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].positionFlag = PositionFlag.IsolatedPosition; 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, DEFAULT_ORACLES, DEFAULT_ORACLES ); const crossTotalCollateral = mockUser.getTotalCollateral('Initial'); assert(crossTotalCollateral.eq(new BN(200).mul(QUOTE_PRECISION))); }); it('getTotalCollateral with perpMarketIndex returns isolated collateral bucket', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].positionFlag = PositionFlag.IsolatedPosition; 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, DEFAULT_ORACLES, DEFAULT_ORACLES ); const isolatedCollateral = mockUser.getTotalCollateral( 'Maintenance', false, true, undefined, 0 ); assert(isolatedCollateral.eq(new BN(100).mul(QUOTE_PRECISION))); }); it('getFreeCollateral with perpMarketIndex reads isolated free collateral', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].positionFlag = PositionFlag.IsolatedPosition; 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, DEFAULT_ORACLES, DEFAULT_ORACLES ); assert(mockUser.getFreeCollateral('Initial', 0).gt(ZERO)); assert(mockUser.getFreeCollateral('Initial', 5).eq(ZERO)); }); }); describe('liquidationPrice', () => { it('cross liquidationPrice returns a valid value for active position', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockPerpMarkets[0].amm.orderStepSize = BASE_PRECISION; myMockUserAccount.spotPositions[0].marketIndex = 0; myMockUserAccount.spotPositions[0].balanceType = SpotBalanceType.DEPOSIT; myMockUserAccount.spotPositions[0].scaledBalance = new BN(2).mul( SPOT_MARKET_BALANCE_PRECISION ); myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(10).mul( BASE_PRECISION ); myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN(10) .mul(QUOTE_PRECISION) .neg(); myMockUserAccount.perpPositions[0].quoteEntryAmount = new BN(10) .mul(QUOTE_PRECISION) .neg(); myMockUserAccount.perpPositions[0].quoteBreakEvenAmount = new BN(10) .mul(QUOTE_PRECISION) .neg(); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, DEFAULT_ORACLES, DEFAULT_ORACLES ); const liqPrice = mockUser.liquidationPrice(0); assert(liqPrice.gte(ZERO)); assert(!liqPrice.eq(new BN(-1))); }); it('isolated liquidationPrice returns a valid value for isolated position', async () => { const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets); const myMockSpotMarkets = _.cloneDeep(mockSpotMarkets); const myMockUserAccount = _.cloneDeep(mockUserAccount); myMockPerpMarkets[0].amm.orderStepSize = BASE_PRECISION; myMockUserAccount.perpPositions[0].marketIndex = 0; myMockUserAccount.perpPositions[0].positionFlag = PositionFlag.IsolatedPosition; myMockUserAccount.perpPositions[0].baseAssetAmount = new BN(10).mul( BASE_PRECISION ); myMockUserAccount.perpPositions[0].quoteAssetAmount = new BN(10) .mul(QUOTE_PRECISION) .neg(); myMockUserAccount.perpPositions[0].quoteEntryAmount = new BN(10) .mul(QUOTE_PRECISION) .neg(); myMockUserAccount.perpPositions[0].quoteBreakEvenAmount = new BN(10) .mul(QUOTE_PRECISION) .neg(); myMockUserAccount.perpPositions[0].isolatedPositionScaledBalance = new BN( 2 ).mul(SPOT_MARKET_BALANCE_PRECISION); const mockUser = await makeMockUserFromHelpers( myMockPerpMarkets, myMockSpotMarkets, myMockUserAccount, DEFAULT_ORACLES, DEFAULT_ORACLES ); const liqPrice = mockUser.liquidationPrice( 0, ZERO, ZERO, 'Maintenance', false, ZERO, 'Isolated' ); assert(liqPrice.gte(ZERO)); assert(!liqPrice.eq(new BN(-1))); }); }); });