Skip to content
markpaper

src/pending/memory.test.ts

v0.1.0 · 5.4 KB

Download file
import { describe, expect, it } from 'vitest';
import { decodeWriteAnswer } from '../signer/decode.js';
import { memoryPolicy, signature } from '../testing/fixtures.js';
import { createWriteMemory } from './memory.js';

const seq = ((1n << 63n) + 15n).toString();
const row = { symbol: 'BTC', side: 'bid' as const, priceTicks: '800', seq, lots: 3n, reduceOnly: false };
const unknown = {
  intentId: 'test-intent-1',
  kind: 'ioc' as const,
  symbol: 'BTC',
  side: 'bid' as const,
  placementKey: 'BTC|bid|800',
  reduceOnly: false,
  signature: null,
  lastValidSlot: null,
  sentSlot: 10,
  sentAt: 10,
};
describe('write memory', () => {
  it('has no cross-instance state', () => {
    const a = createWriteMemory(memoryPolicy);
    const b = createWriteMemory(memoryPolicy);
    a.noteFillSlot(20, 10);
    expect(a.judgeStateSlot(19, 10).trusted).toBe(false);
    expect(b.judgeStateSlot(19, 10).trusted).toBe(true);
  });
  it('rejects backward replicas and reads before an own fill', () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteStateSlot(10, 1);
    expect(m.judgeStateSlot(9, 1).trusted).toBe(false);
    m.noteFillSlot(15, 1);
    expect(m.judgeStateSlot(14, 1).trusted).toBe(false);
    expect(m.judgeStateSlot(15, 1).trusted).toBe(true);
  });
  it('expires the uncertain fill gate instead of permanently locking idle accounts', () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteUncertainFillSlot(50, 10);
    expect(m.judgeStateSlot(10, 20).trusted).toBe(false);
    expect(m.judgeStateSlot(10, 111).trusted).toBe(true);
  });
  it('requires indexer catch-up for confirmed state-changing writes', () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteStateChangeSlot(30, 1);
    expect(m.judgeStateSlot(19, 2).trusted).toBe(false);
    expect(m.judgeStateSlot(20, 2).trusted).toBe(true);
  });
  it('substitutes placements without duplicates and forgets caught-up echoes', () => {
    const m = createWriteMemory(memoryPolicy);
    m.notePlacedEcho({ ...row, slot: 20 }, 1);
    expect(m.applyEcho([], 19, 2)).toHaveLength(1);
    expect(m.applyEcho([row], 19, 2)).toHaveLength(1);
    expect(m.applyEcho([], 20, 2)).toEqual([]);
  });
  it('removes confirmed cancels and expires obsolete echoes', () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteCancelledEcho('BTC', '800', seq, 20, 1);
    expect(m.applyEcho([row], 19, 2)).toEqual([]);
    expect(m.applyEcho([row], 20, 2)).toEqual([row]);
    m.notePlacedEcho({ ...row, slot: 30 }, 1);
    expect(m.applyEcho([], 20, 202)).toEqual([]);
  });
  it('keeps no-response IoC locks until a read starts after the physical window', async () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteUnknown(unknown);
    m.noteStateSlot(100, 15);
    await m.resolveUnknowns(async () => null, 100);
    expect(m.iocLockOf('BTC')).toBeDefined();
    m.noteStateSlot(100, 60);
    await m.resolveUnknowns(async () => null, 100);
    expect(m.iocLockOf('BTC')).toBeUndefined();
  });
  it('releases a known lastValidSlot only after the accepted state passes it', async () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteUnknown({ ...unknown, lastValidSlot: 20 });
    m.noteStateSlot(22, 40);
    await m.resolveUnknowns(async () => null, 40);
    expect(m.unknownCount()).toBe(1);
    m.noteStateSlot(23, 41);
    await m.resolveUnknowns(async () => null, 41);
    expect(m.unknownCount()).toBe(0);
  });
  it('resolves late confirmed fills before allowing further reads', async () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteUnknown({ ...unknown, signature });
    const a = decodeWriteAnswer({
      status: 'confirmed',
      signature,
      intentId: unknown.intentId,
      slot: 25,
      cpi: { priceInTicks: '0', seq: '0', basePosted: '0', filledBaseLots: '2', filledQuoteLots: '300' },
    });
    await m.resolveUnknowns(async () => a, 50);
    expect(m.unknownCount()).toBe(0);
    expect(m.judgeStateSlot(24, 50).trusted).toBe(false);
  });
  it('releases a confirmed failure without claiming a fill', async () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteUnknown({ ...unknown, signature });
    await m.resolveUnknowns(async () => decodeWriteAnswer({ status: 'failed', signature }), 50);
    expect(m.unknownCount()).toBe(0);
    expect(m.judgeStateSlot(10, 50).trusted).toBe(true);
  });
  it('retains signed resting placements until an actual transaction outcome is known', async () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteUnknown({ ...unknown, kind: 'gtc', signature });
    expect(m.placementLockOf(unknown.placementKey)).toBeDefined();
    expect(m.iocLockOf('BTC')).toBeUndefined();
    await m.resolveUnknowns(async () => null, 91);
    expect(m.placementLockOf(unknown.placementKey)).toBeDefined();
  });
  it('refuses an intent overwrite and ignores a foreign transaction response', async () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteUnknown({ ...unknown, signature });
    expect(() => m.noteUnknown({ ...unknown, signature, symbol: 'ETH' })).toThrow('conflicting');
    await m.resolveUnknowns(async () => decodeWriteAnswer({ status: 'failed', signature: 'foreign-signature' }), 20);
    expect(m.unknownCount()).toBe(1);
  });
  it('supports opt-in band memory with an explicit time ceiling', () => {
    const m = createWriteMemory(memoryPolicy);
    m.noteBandReject('BTC|bid|800', 1, true);
    expect(m.isBandRejected('BTC|bid|800', 30, true)).toBe(true);
    expect(m.isBandRejected('BTC|bid|800', 30, false)).toBe(false);
  });
});
All files