Skip to content
markpaper

src/assets/assetId.test.ts

v0.3.0 · 2.8 KB

Download file
import { describe, expect, it } from 'vitest';
import { decodeAssetId, hip3AssetId, perpAssetId, spotAssetId } from './assetId.js';

describe('asset id encoders', () => {
  it('main perp id is the universe index', () => {
    expect(perpAssetId(0)).toBe(0);
    expect(perpAssetId(159)).toBe(159);
  });

  it('spot id is 10000 + pair index (@85 -> 10085)', () => {
    expect(spotAssetId(85)).toBe(10085);
    expect(spotAssetId(0)).toBe(10000);
  });

  it('HIP-3 id matches mainnet observations for xyz (dexIndex 1)', () => {
    expect(hip3AssetId(1, 1)).toBe(110001); // xyz:TSLA
    expect(hip3AssetId(1, 15)).toBe(110015); // xyz:MU
    expect(hip3AssetId(1, 29)).toBe(110029); // xyz:CL
    expect(hip3AssetId(1, 52)).toBe(110052); // xyz:SP500
    expect(hip3AssetId(10, 3)).toBe(200003);
  });

  it('rejects out-of-range input instead of producing a colliding id', () => {
    expect(() => perpAssetId(10_000)).toThrow(RangeError);
    expect(() => perpAssetId(-1)).toThrow(RangeError);
    expect(() => perpAssetId(1.5)).toThrow(RangeError);
    expect(() => spotAssetId(90_000)).toThrow(RangeError);
    expect(() => hip3AssetId(0, 1)).toThrow(RangeError);
    expect(() => hip3AssetId(1, 10_000)).toThrow(RangeError);
    expect(() => hip3AssetId(1, Number.NaN)).toThrow(RangeError);
  });
});

describe('decodeAssetId', () => {
  it('round-trips all three ranges', () => {
    expect(decodeAssetId(159)).toEqual({ market: 'perp', dexIndex: 0, index: 159 });
    expect(decodeAssetId(10085)).toEqual({ market: 'spot', index: 85 });
    expect(decodeAssetId(110052)).toEqual({ market: 'perp', dexIndex: 1, index: 52 });
    expect(decodeAssetId(hip3AssetId(7, 9999))).toEqual({ market: 'perp', dexIndex: 7, index: 9999 });
  });

  it('throws for invalid ids', () => {
    expect(() => decodeAssetId(-1)).toThrow(RangeError);
    expect(() => decodeAssetId(1.2)).toThrow(RangeError);
  });
});

describe('asset id range boundaries', () => {
  it('decodes the edges of each range', () => {
    expect(decodeAssetId(9_999)).toEqual({ market: 'perp', dexIndex: 0, index: 9_999 });
    expect(decodeAssetId(10_000)).toEqual({ market: 'spot', index: 0 });
    expect(decodeAssetId(99_999)).toEqual({ market: 'spot', index: 89_999 });
    expect(decodeAssetId(110_000)).toEqual({ market: 'perp', dexIndex: 1, index: 0 });
    expect(decodeAssetId(119_999)).toEqual({ market: 'perp', dexIndex: 1, index: 9_999 });
  });

  it('ids 100000..109999 belong to no dex (dex 0 is the main dex) and are rejected', () => {
    expect(() => decodeAssetId(100_000)).toThrow(RangeError);
    expect(() => decodeAssetId(109_999)).toThrow(RangeError);
  });

  it('live perpDexs order (2026-09): io is dex 10, so its ids start at 200000', () => {
    expect(hip3AssetId(10, 0)).toBe(200_000);
    expect(spotAssetId(89_999)).toBe(99_999);
  });
});
All files