695 lines
22 KiB
TypeScript
695 lines
22 KiB
TypeScript
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
import {
|
|
BN,
|
|
PositionDirection,
|
|
MarketType,
|
|
ZERO,
|
|
QUOTE_PRECISION,
|
|
PRICE_PRECISION,
|
|
BASE_PRECISION,
|
|
} from '@drift-labs/sdk';
|
|
import {
|
|
createMarketBasedAuctionParams,
|
|
mapToMarketOrderParams,
|
|
formatAuctionParamsForResponse,
|
|
} from '../utils';
|
|
|
|
describe('Auction Parameters Functions', () => {
|
|
describe('createMarketBasedAuctionParams', () => {
|
|
it('should apply market-based logic for major PERP markets (0, 1, 2)', () => {
|
|
[0, 1, 2].forEach((marketIndex) => {
|
|
const args = {
|
|
marketIndex,
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: '100',
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'marketBased' as any,
|
|
auctionStartPriceOffset: 'marketBased' as any,
|
|
};
|
|
|
|
const result = createMarketBasedAuctionParams(args);
|
|
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('mark');
|
|
expect(result.auctionStartPriceOffset).toBe(0);
|
|
});
|
|
});
|
|
|
|
it('should apply market-based logic for minor PERP markets (>2)', () => {
|
|
[3, 4, 5, 10].forEach((marketIndex) => {
|
|
const args = {
|
|
marketIndex,
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: '100',
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'marketBased' as any,
|
|
auctionStartPriceOffset: 'marketBased' as any,
|
|
};
|
|
|
|
const result = createMarketBasedAuctionParams(args);
|
|
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('bestOffer');
|
|
expect(result.auctionStartPriceOffset).toBe(-0.1);
|
|
});
|
|
});
|
|
|
|
it('should apply market-based logic for SPOT markets when "marketBased" is passed', () => {
|
|
[0, 1, 2, 5, 10].forEach((marketIndex) => {
|
|
const args = {
|
|
marketIndex,
|
|
marketType: 'spot' as any,
|
|
direction: 'long' as any,
|
|
amount: '100',
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'marketBased' as any,
|
|
auctionStartPriceOffset: 'marketBased' as any,
|
|
};
|
|
|
|
const result = createMarketBasedAuctionParams(args);
|
|
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('bestOffer');
|
|
expect(result.auctionStartPriceOffset).toBe(-0.1);
|
|
});
|
|
});
|
|
|
|
it('should treat undefined values the same as "marketBased"', () => {
|
|
const majorMarketArgs = {
|
|
marketIndex: 0,
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: '100',
|
|
assetType: 'base' as any,
|
|
// auctionStartPriceOffsetFrom and auctionStartPriceOffset are undefined
|
|
};
|
|
|
|
const minorMarketArgs = {
|
|
marketIndex: 5,
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: '100',
|
|
assetType: 'base' as any,
|
|
// auctionStartPriceOffsetFrom and auctionStartPriceOffset are undefined
|
|
};
|
|
|
|
const majorResult = createMarketBasedAuctionParams(majorMarketArgs);
|
|
const minorResult = createMarketBasedAuctionParams(minorMarketArgs);
|
|
|
|
// Major market behavior
|
|
expect(majorResult.auctionStartPriceOffsetFrom).toBe('mark');
|
|
expect(majorResult.auctionStartPriceOffset).toBe(0);
|
|
|
|
// Minor market behavior
|
|
expect(minorResult.auctionStartPriceOffsetFrom).toBe('bestOffer');
|
|
expect(minorResult.auctionStartPriceOffset).toBe(-0.1);
|
|
});
|
|
|
|
it('should preserve explicit values over market-based defaults', () => {
|
|
const args = {
|
|
marketIndex: 0, // Major market
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: '100',
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'oracle' as any,
|
|
auctionStartPriceOffset: 0.5,
|
|
auctionEndPriceOffset: 0.2,
|
|
auctionDuration: 45,
|
|
};
|
|
|
|
const result = createMarketBasedAuctionParams(args);
|
|
|
|
// Should use explicit values, not market-based defaults
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('oracle');
|
|
expect(result.auctionStartPriceOffset).toBe(0.5);
|
|
expect(result.auctionEndPriceOffset).toBe(0.2);
|
|
expect(result.auctionDuration).toBe(45);
|
|
});
|
|
|
|
it('should handle mixed explicit and market-based values', () => {
|
|
const args = {
|
|
marketIndex: 0,
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: '100',
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'oracle' as any, // explicit
|
|
auctionStartPriceOffset: 'marketBased' as any, // market-based
|
|
auctionDuration: 45, // explicit
|
|
};
|
|
|
|
const result = createMarketBasedAuctionParams(args);
|
|
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('oracle'); // explicit value
|
|
expect(result.auctionStartPriceOffset).toBe(0); // market-based for major market
|
|
expect(result.auctionDuration).toBe(45); // explicit value
|
|
});
|
|
|
|
it('should include all original parameters in the result', () => {
|
|
const args = {
|
|
marketIndex: 3,
|
|
marketType: 'perp' as any,
|
|
direction: 'short' as any,
|
|
amount: '250',
|
|
assetType: 'quote' as any,
|
|
reduceOnly: true,
|
|
allowInfSlippage: false,
|
|
slippageTolerance: 0.02,
|
|
userOrderId: 12345,
|
|
auctionStartPriceOffsetFrom: 'marketBased' as any,
|
|
auctionStartPriceOffset: 'marketBased' as any,
|
|
};
|
|
|
|
const result = createMarketBasedAuctionParams(args);
|
|
|
|
// Should preserve all original parameters
|
|
expect(result.marketIndex).toBe(3);
|
|
expect(result.marketType).toBe('perp');
|
|
expect(result.direction).toBe('short');
|
|
expect(result.amount).toBe('250');
|
|
expect(result.assetType).toBe('quote');
|
|
expect(result.reduceOnly).toBe(true);
|
|
expect(result.allowInfSlippage).toBe(false);
|
|
expect(result.slippageTolerance).toBe(0.02);
|
|
expect(result.userOrderId).toBe(12345);
|
|
|
|
// Should apply market-based logic
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('bestOffer');
|
|
expect(result.auctionStartPriceOffset).toBe(-0.1);
|
|
});
|
|
});
|
|
|
|
describe('mapToMarketOrderParams with Mock L2 Data', () => {
|
|
const mockDriftClient = {
|
|
getOracleDataForPerpMarket: jest.fn(),
|
|
getOracleDataForSpotMarket: jest.fn(),
|
|
};
|
|
|
|
const mockFetchFromRedis = jest.fn() as any;
|
|
const mockSelectMostRecentBySlot = jest.fn() as any;
|
|
|
|
const validParams = {
|
|
marketIndex: 0,
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: new BN(100).mul(BASE_PRECISION).toString(), // 100 in BASE_PRECISION
|
|
assetType: 'base' as any,
|
|
auctionDuration: 30,
|
|
auctionStartPriceOffset: 0,
|
|
auctionEndPriceOffset: 0.1,
|
|
auctionStartPriceOffsetFrom: 'mark' as any,
|
|
auctionEndPriceOffsetFrom: 'mark' as any,
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
it('should successfully calculate prices with mock L2 orderbook data', async () => {
|
|
const solPrice = 160; // $160 SOL price
|
|
|
|
mockDriftClient.getOracleDataForPerpMarket.mockReturnValue({
|
|
price: new BN(solPrice).mul(PRICE_PRECISION),
|
|
});
|
|
|
|
// Mock realistic L2 orderbook data
|
|
mockFetchFromRedis.mockImplementation(async (key: string) => {
|
|
if (key.includes('dlob_orderbook')) {
|
|
return {
|
|
bids: [
|
|
{ price: '159950000', size: '1000000000' }, // $159.95, 1 SOL
|
|
{ price: '159900000', size: '2000000000' }, // $159.90, 2 SOL
|
|
{ price: '159850000', size: '1500000000' }, // $159.85, 1.5 SOL
|
|
],
|
|
asks: [
|
|
{ price: '160050000', size: '1500000000' }, // $160.05, 1.5 SOL
|
|
{ price: '160100000', size: '1000000000' }, // $160.10, 1 SOL
|
|
{ price: '160150000', size: '2000000000' }, // $160.15, 2 SOL
|
|
],
|
|
};
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const result = await mapToMarketOrderParams(
|
|
validParams,
|
|
mockDriftClient as any,
|
|
mockFetchFromRedis as any,
|
|
mockSelectMostRecentBySlot as any
|
|
);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toBeDefined();
|
|
expect(result.data?.marketOrderParams).toBeDefined();
|
|
expect(result.data?.estimatedPrices).toBeDefined();
|
|
expect(result.error).toBeUndefined();
|
|
|
|
// Verify price calculations
|
|
const prices = result.data?.estimatedPrices;
|
|
expect(prices?.entryPrice.gt(ZERO)).toBe(true);
|
|
expect(prices?.bestPrice.gt(ZERO)).toBe(true);
|
|
expect(prices?.worstPrice.gt(ZERO)).toBe(true);
|
|
expect(prices?.oraclePrice.gt(ZERO)).toBe(true);
|
|
|
|
// Verify market order params structure
|
|
const marketOrderParams = result.data?.marketOrderParams;
|
|
expect(marketOrderParams?.marketType).toBe(MarketType.PERP);
|
|
expect(marketOrderParams?.direction).toBe(PositionDirection.LONG);
|
|
expect(marketOrderParams?.baseAmount).toBeDefined();
|
|
expect(marketOrderParams?.baseAmount.gt(ZERO)).toBe(true);
|
|
});
|
|
|
|
it('should handle quote-to-base conversion with realistic prices', async () => {
|
|
const solPrice = 160; // $160 SOL price
|
|
const quoteAmount = 1000; // $1,000 worth
|
|
const quoteAmountInPrecision = new BN(quoteAmount).mul(QUOTE_PRECISION); // Convert to quote precision
|
|
|
|
mockDriftClient.getOracleDataForPerpMarket.mockReturnValue({
|
|
price: new BN(solPrice).mul(PRICE_PRECISION),
|
|
});
|
|
|
|
mockFetchFromRedis.mockImplementation(async (key: string) => {
|
|
if (key.includes('dlob_orderbook')) {
|
|
return {
|
|
bids: [
|
|
{ price: '160000000', size: '1000000000' }, // $160.00, 1 SOL
|
|
],
|
|
asks: [
|
|
{ price: '160000000', size: '1000000000' }, // $160.00, 1 SOL
|
|
],
|
|
};
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const quoteParams = {
|
|
...validParams,
|
|
amount: quoteAmountInPrecision.toString(),
|
|
assetType: 'quote' as any,
|
|
};
|
|
|
|
const result = await mapToMarketOrderParams(
|
|
quoteParams,
|
|
mockDriftClient as any,
|
|
mockFetchFromRedis as any,
|
|
mockSelectMostRecentBySlot as any
|
|
);
|
|
|
|
expect(result.success).toBe(true);
|
|
|
|
// With $1,000 at $160 per SOL, we should get ~6.25 SOL
|
|
const baseAmount = result.data?.marketOrderParams.baseAmount;
|
|
expect(baseAmount).toBeDefined();
|
|
expect(baseAmount.gt(ZERO)).toBe(true);
|
|
|
|
// Calculate expected base amount: (amount * BASE_PRECISION) / entryPrice
|
|
const expectedBaseAmount = quoteAmountInPrecision
|
|
.mul(BASE_PRECISION)
|
|
.div(result.data?.estimatedPrices.entryPrice);
|
|
|
|
expect(baseAmount.toString()).toBe(expectedBaseAmount.toString());
|
|
});
|
|
|
|
it('should handle different market types correctly', async () => {
|
|
const usdcPrice = 1; // $1 USDC price
|
|
|
|
mockDriftClient.getOracleDataForSpotMarket.mockReturnValue({
|
|
price: new BN(usdcPrice).mul(PRICE_PRECISION),
|
|
});
|
|
|
|
mockFetchFromRedis.mockImplementation(async (key: string) => {
|
|
if (key.includes('dlob_orderbook')) {
|
|
return {
|
|
bids: [
|
|
{ price: '1000000', size: '10000000000' }, // $1.00, 10,000 USDC
|
|
],
|
|
asks: [
|
|
{ price: '1000000', size: '10000000000' }, // $1.00, 10,000 USDC
|
|
],
|
|
};
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const spotParams = {
|
|
...validParams,
|
|
marketType: 'spot' as any,
|
|
};
|
|
|
|
const result = await mapToMarketOrderParams(
|
|
spotParams,
|
|
mockDriftClient as any,
|
|
mockFetchFromRedis as any,
|
|
mockSelectMostRecentBySlot as any
|
|
);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data?.marketOrderParams.marketType).toBe(MarketType.SPOT);
|
|
expect(result.data?.estimatedPrices.entryPrice.gt(ZERO)).toBe(true);
|
|
});
|
|
|
|
it('should handle different directions correctly', async () => {
|
|
const solPrice = 160; // $160 SOL price
|
|
|
|
mockDriftClient.getOracleDataForPerpMarket.mockReturnValue({
|
|
price: new BN(solPrice).mul(PRICE_PRECISION),
|
|
});
|
|
|
|
mockFetchFromRedis.mockImplementation(async (key: string) => {
|
|
if (key.includes('dlob_orderbook')) {
|
|
return {
|
|
bids: [
|
|
{ price: '159950000', size: '1000000000' }, // $159.95, 1 SOL
|
|
],
|
|
asks: [
|
|
{ price: '160050000', size: '1000000000' }, // $160.05, 1 SOL
|
|
],
|
|
};
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const shortParams = {
|
|
...validParams,
|
|
direction: 'short' as any,
|
|
};
|
|
|
|
const result = await mapToMarketOrderParams(
|
|
shortParams,
|
|
mockDriftClient as any,
|
|
mockFetchFromRedis as any,
|
|
mockSelectMostRecentBySlot as any
|
|
);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data?.marketOrderParams.direction).toBe(
|
|
PositionDirection.SHORT
|
|
);
|
|
expect(result.data?.estimatedPrices.entryPrice.gt(ZERO)).toBe(true);
|
|
});
|
|
|
|
it('should handle various order sizes with L2 depth', async () => {
|
|
const solPrice = 160; // $160 SOL price
|
|
|
|
mockDriftClient.getOracleDataForPerpMarket.mockReturnValue({
|
|
price: new BN(solPrice).mul(PRICE_PRECISION),
|
|
});
|
|
|
|
// Mock L2 with multiple levels of depth
|
|
mockFetchFromRedis.mockImplementation(async (key: string) => {
|
|
if (key.includes('dlob_orderbook')) {
|
|
return {
|
|
bids: [
|
|
{ price: '159950000', size: '500000000' }, // $159.95, 0.5 SOL
|
|
{ price: '159900000', size: '1000000000' }, // $159.90, 1 SOL
|
|
{ price: '159850000', size: '2000000000' }, // $159.85, 2 SOL
|
|
],
|
|
asks: [
|
|
{ price: '160050000', size: '500000000' }, // $160.05, 0.5 SOL
|
|
{ price: '160100000', size: '1000000000' }, // $160.10, 1 SOL
|
|
{ price: '160150000', size: '2000000000' }, // $160.15, 2 SOL
|
|
],
|
|
};
|
|
}
|
|
return null;
|
|
});
|
|
|
|
// Test small order
|
|
const smallOrderParams = {
|
|
...validParams,
|
|
amount: new BN(10).mul(BASE_PRECISION).toString(), // 10 in BASE_PRECISION
|
|
};
|
|
|
|
const smallResult = await mapToMarketOrderParams(
|
|
smallOrderParams,
|
|
mockDriftClient as any,
|
|
mockFetchFromRedis as any,
|
|
mockSelectMostRecentBySlot as any
|
|
);
|
|
|
|
expect(smallResult.success).toBe(true);
|
|
expect(smallResult.data?.estimatedPrices.entryPrice.gt(ZERO)).toBe(true);
|
|
|
|
// Test large order
|
|
const largeOrderParams = {
|
|
...validParams,
|
|
amount: new BN(1000).mul(BASE_PRECISION).toString(), // 1000 in BASE_PRECISION
|
|
};
|
|
|
|
const largeResult = await mapToMarketOrderParams(
|
|
largeOrderParams,
|
|
mockDriftClient as any,
|
|
mockFetchFromRedis as any,
|
|
mockSelectMostRecentBySlot as any
|
|
);
|
|
|
|
expect(largeResult.success).toBe(true);
|
|
expect(largeResult.data?.estimatedPrices.entryPrice.gt(ZERO)).toBe(true);
|
|
|
|
// Large orders should have higher price impact
|
|
expect(
|
|
largeResult.data?.estimatedPrices.priceImpact.gte(
|
|
smallResult.data?.estimatedPrices.priceImpact
|
|
)
|
|
).toBe(true);
|
|
});
|
|
|
|
it('should handle zero oracle price scenario', async () => {
|
|
mockDriftClient.getOracleDataForPerpMarket.mockReturnValue({
|
|
price: ZERO,
|
|
});
|
|
|
|
mockFetchFromRedis.mockImplementation(async (key: string) => {
|
|
if (key.includes('dlob_orderbook')) {
|
|
return {
|
|
bids: [
|
|
{ price: '159950000', size: '1000000000' }, // $159.95, 1 SOL
|
|
],
|
|
asks: [
|
|
{ price: '160050000', size: '1000000000' }, // $160.05, 1 SOL
|
|
],
|
|
};
|
|
}
|
|
return null;
|
|
});
|
|
|
|
const result = await mapToMarketOrderParams(
|
|
validParams,
|
|
mockDriftClient as any,
|
|
mockFetchFromRedis as any,
|
|
mockSelectMostRecentBySlot as any
|
|
);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toBeDefined();
|
|
expect(result.data?.estimatedPrices.oraclePrice.eq(ZERO)).toBe(true);
|
|
// Entry price should be calculated from L2 data even if oracle price is zero
|
|
expect(result.data?.estimatedPrices.entryPrice).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('formatAuctionParamsForResponse', () => {
|
|
it('should format BN values to strings', () => {
|
|
const mockAuctionParams = {
|
|
auctionStartPriceOffsetFrom: 'mark',
|
|
auctionStartPriceOffset: new BN(0),
|
|
auctionEndPriceOffsetFrom: 'best',
|
|
auctionEndPriceOffset: new BN(100000), // 0.1 with PRICE_PRECISION (1e6)
|
|
auctionDuration: 30,
|
|
slippageTolerance: 0.05,
|
|
additionalEndPriceBuffer: new BN(50000), // 0.05 with PRICE_PRECISION
|
|
userOrderId: 123,
|
|
isOracleOrder: false,
|
|
};
|
|
|
|
const result = formatAuctionParamsForResponse(mockAuctionParams);
|
|
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('mark');
|
|
expect(result.auctionStartPriceOffset).toBe('0');
|
|
expect(result.auctionEndPriceOffsetFrom).toBe('best');
|
|
expect(result.auctionEndPriceOffset).toBe('100000');
|
|
expect(result.auctionDuration).toBe(30);
|
|
expect(result.slippageTolerance).toBe(0.05);
|
|
expect(result.additionalEndPriceBuffer).toBe('50000');
|
|
expect(result.userOrderId).toBe(123);
|
|
expect(result.isOracleOrder).toBe(false);
|
|
});
|
|
|
|
it('should handle different BN values correctly', () => {
|
|
const mockAuctionParams = {
|
|
auctionStartPriceOffsetFrom: 'oracle',
|
|
auctionStartPriceOffset: new BN(250000), // 0.25 with PRICE_PRECISION
|
|
auctionEndPriceOffsetFrom: 'worst',
|
|
auctionEndPriceOffset: new BN(1500000), // 1.5 with PRICE_PRECISION
|
|
auctionDuration: 60,
|
|
slippageTolerance: 0.1,
|
|
additionalEndPriceBuffer: new BN(0), // 0 with PRICE_PRECISION
|
|
userOrderId: 456,
|
|
isOracleOrder: true,
|
|
};
|
|
|
|
const result = formatAuctionParamsForResponse(mockAuctionParams);
|
|
|
|
expect(result.auctionStartPriceOffset).toBe('250000');
|
|
expect(result.auctionEndPriceOffset).toBe('1500000');
|
|
expect(result.additionalEndPriceBuffer).toBe('0');
|
|
expect(result.isOracleOrder).toBe(true);
|
|
});
|
|
|
|
it('should handle undefined/null values gracefully', () => {
|
|
const mockAuctionParams = {
|
|
auctionStartPriceOffsetFrom: 'mark',
|
|
auctionStartPriceOffset: new BN(0),
|
|
auctionEndPriceOffsetFrom: 'mark',
|
|
auctionEndPriceOffset: new BN(100000),
|
|
auctionDuration: 30,
|
|
slippageTolerance: undefined,
|
|
additionalEndPriceBuffer: undefined,
|
|
userOrderId: undefined,
|
|
isOracleOrder: undefined,
|
|
};
|
|
|
|
const result = formatAuctionParamsForResponse(mockAuctionParams);
|
|
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('mark');
|
|
expect(result.auctionStartPriceOffset).toBe('0');
|
|
expect(result.auctionEndPriceOffsetFrom).toBe('mark');
|
|
expect(result.auctionEndPriceOffset).toBe('100000');
|
|
expect(result.auctionDuration).toBe(30);
|
|
expect(result.slippageTolerance).toBeUndefined();
|
|
expect(result.additionalEndPriceBuffer).toBeUndefined();
|
|
expect(result.userOrderId).toBeUndefined();
|
|
expect(result.isOracleOrder).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('Business Logic Integration', () => {
|
|
it('should demonstrate complete parameter flow for different market types', () => {
|
|
const scenarios = [
|
|
{
|
|
name: 'Major PERP market with market-based params',
|
|
input: {
|
|
marketIndex: 0,
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: new BN(100).mul(BASE_PRECISION).toString(), // 100 in BASE_PRECISION
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'marketBased' as any,
|
|
auctionStartPriceOffset: 'marketBased' as any,
|
|
},
|
|
expectedOffset: { from: 'mark', value: 0 },
|
|
},
|
|
{
|
|
name: 'Minor PERP market with market-based params',
|
|
input: {
|
|
marketIndex: 5,
|
|
marketType: 'perp' as any,
|
|
direction: 'short' as any,
|
|
amount: new BN(500).mul(QUOTE_PRECISION).toString(), // 500 in QUOTE_PRECISION
|
|
assetType: 'quote' as any,
|
|
auctionStartPriceOffsetFrom: 'marketBased' as any,
|
|
auctionStartPriceOffset: 'marketBased' as any,
|
|
},
|
|
expectedOffset: { from: 'bestOffer', value: -0.1 },
|
|
},
|
|
{
|
|
name: 'SPOT market with market-based params',
|
|
input: {
|
|
marketIndex: 2,
|
|
marketType: 'spot' as any,
|
|
direction: 'long' as any,
|
|
amount: new BN(1000).mul(BASE_PRECISION).toString(), // 1000 in BASE_PRECISION
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'marketBased' as any,
|
|
auctionStartPriceOffset: 'marketBased' as any,
|
|
},
|
|
expectedOffset: { from: 'bestOffer', value: -0.1 },
|
|
},
|
|
];
|
|
|
|
scenarios.forEach((scenario) => {
|
|
const result = createMarketBasedAuctionParams(scenario.input);
|
|
|
|
expect(result.auctionStartPriceOffsetFrom).toBe(
|
|
scenario.expectedOffset.from
|
|
);
|
|
expect(result.auctionStartPriceOffset).toBe(
|
|
scenario.expectedOffset.value
|
|
);
|
|
|
|
// Verify all original parameters are preserved
|
|
expect(result.marketIndex).toBe(scenario.input.marketIndex);
|
|
expect(result.marketType).toBe(scenario.input.marketType);
|
|
expect(result.direction).toBe(scenario.input.direction);
|
|
expect(result.amount).toBe(scenario.input.amount);
|
|
expect(result.assetType).toBe(scenario.input.assetType);
|
|
});
|
|
});
|
|
|
|
it('should handle parameter precedence correctly', () => {
|
|
// Test that explicit values always take precedence over market-based logic
|
|
const input = {
|
|
marketIndex: 0, // Major market - would normally get 'mark' and 0
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: new BN(100).mul(BASE_PRECISION).toString(), // 100 in BASE_PRECISION
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'oracle' as any, // explicit value
|
|
auctionStartPriceOffset: 0.25, // explicit value
|
|
auctionEndPriceOffset: 0.5, // explicit value
|
|
auctionDuration: 45, // explicit value
|
|
};
|
|
|
|
const result = createMarketBasedAuctionParams(input);
|
|
|
|
// Should use explicit values, not market-based defaults
|
|
expect(result.auctionStartPriceOffsetFrom).toBe('oracle');
|
|
expect(result.auctionStartPriceOffset).toBe(0.25);
|
|
expect(result.auctionEndPriceOffset).toBe(0.5);
|
|
expect(result.auctionDuration).toBe(45);
|
|
});
|
|
|
|
it('should demonstrate end-to-end parameter formatting', () => {
|
|
// Create auction params with market-based logic
|
|
const input = {
|
|
marketIndex: 1,
|
|
marketType: 'perp' as any,
|
|
direction: 'long' as any,
|
|
amount: new BN(100).mul(BASE_PRECISION).toString(), // 100 in BASE_PRECISION
|
|
assetType: 'base' as any,
|
|
auctionStartPriceOffsetFrom: 'marketBased' as any,
|
|
auctionStartPriceOffset: 'marketBased' as any,
|
|
auctionDuration: 30,
|
|
};
|
|
|
|
const auctionParams = createMarketBasedAuctionParams(input);
|
|
|
|
// Simulate the structure that would come from deriveMarketOrderParams
|
|
const mockDerivedParams = {
|
|
auctionStartPriceOffsetFrom: auctionParams.auctionStartPriceOffsetFrom,
|
|
auctionStartPriceOffset: new BN(0), // 0 for major market
|
|
auctionEndPriceOffsetFrom: auctionParams.auctionEndPriceOffsetFrom,
|
|
auctionEndPriceOffset: new BN(100000), // 0.1 with PRICE_PRECISION
|
|
auctionDuration: auctionParams.auctionDuration,
|
|
slippageTolerance: 0.05,
|
|
additionalEndPriceBuffer: new BN(50000), // 0.05 with PRICE_PRECISION
|
|
userOrderId: 123,
|
|
isOracleOrder: true,
|
|
};
|
|
|
|
const formattedResponse =
|
|
formatAuctionParamsForResponse(mockDerivedParams);
|
|
|
|
// Verify the complete flow
|
|
expect(formattedResponse.auctionStartPriceOffsetFrom).toBe('mark'); // Market-based for major market
|
|
expect(formattedResponse.auctionStartPriceOffset).toBe('0'); // Market-based for major market
|
|
expect(formattedResponse.auctionEndPriceOffset).toBe('100000'); // Formatted from BN
|
|
expect(formattedResponse.auctionDuration).toBe(30); // Preserved from input
|
|
expect(formattedResponse.slippageTolerance).toBe(0.05); // Preserved as number
|
|
expect(formattedResponse.additionalEndPriceBuffer).toBe('50000'); // Formatted from BN
|
|
expect(formattedResponse.userOrderId).toBe(123); // Preserved as number
|
|
expect(formattedResponse.isOracleOrder).toBe(true); // Preserved as boolean
|
|
});
|
|
});
|
|
});
|