src/pending/memory.test.ts
v0.1.0 · 5.4 KB
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);
});
});