From f613fa717803edf15e7b675d52a44f0dd716cc70 Mon Sep 17 00:00:00 2001 From: Cute kittie <278602811+kitWarse@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:18:44 +0100 Subject: [PATCH] test: cover indexer reorg rewrites --- .../src/mappings/mappingHandlers.ts | 7 +- .../fixtures/reorged-ledger-sequence.json | 70 +++++++++++++++ .../s03-indexer/tests/mappingHandlers.test.ts | 87 +++++++++++++++++++ 3 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 apps/s03-indexer/tests/fixtures/reorged-ledger-sequence.json diff --git a/apps/s03-indexer/src/mappings/mappingHandlers.ts b/apps/s03-indexer/src/mappings/mappingHandlers.ts index 4907cb5..e34750a 100644 --- a/apps/s03-indexer/src/mappings/mappingHandlers.ts +++ b/apps/s03-indexer/src/mappings/mappingHandlers.ts @@ -437,7 +437,7 @@ async function handleMarketCreated(event: DecodedEvent): Promise { createdTransactionHash: event.transactionHash, }); - const snapshotId = deterministicId("market-config", event.id); + const snapshotId = deterministicId("market-config", market.key, event.ledger.toString()); const snapshot = await upsert(MarketConfigSnapshot, snapshotId, { id: snapshotId, marketId: market.id, @@ -775,10 +775,11 @@ async function savePositionChange( account: string, changeType: string, ): Promise { - const id = deterministicId("position-change", event.id); + const key = fieldString(event, ["position_key", "positionKey", "key"], POSITION_INCREASE_INDEX.key) ?? event.id; + const id = deterministicId("position-change", key, event.ledger.toString(), changeType); const change = await upsert(PositionChange, id, { id, - key: fieldString(event, ["position_key", "positionKey", "key"], POSITION_INCREASE_INDEX.key) ?? event.id, + key, marketId: marketIdValue, positionId, account, diff --git a/apps/s03-indexer/tests/fixtures/reorged-ledger-sequence.json b/apps/s03-indexer/tests/fixtures/reorged-ledger-sequence.json new file mode 100644 index 0000000..64e395b --- /dev/null +++ b/apps/s03-indexer/tests/fixtures/reorged-ledger-sequence.json @@ -0,0 +1,70 @@ +{ + "orphaned": [ + { + "id": "orphan-pos-inc", + "eventName": "pos_inc", + "ledger": 200, + "transactionHash": "tx-orphan-pos", + "named": { + "position_key": "reorg-pos-1", + "market": "MARKET_TOKEN", + "account": "ACCOUNT", + "collateral_token": "MARKET_TOKEN", + "is_long": true, + "size_delta_usd": "100", + "next_size_usd": "100", + "next_collateral_amount": "5", + "execution_price": "10" + } + }, + { + "id": "orphan-ord-crt", + "eventName": "ord_crt", + "ledger": 200, + "transactionHash": "tx-orphan-order", + "named": { + "key": "reorg-order-1", + "market": "MARKET_TOKEN", + "account": "ACCOUNT", + "order_type": "LimitIncrease", + "is_long": true, + "size_delta_usd": "100", + "acceptable_price": "10" + } + } + ], + "canonical": [ + { + "id": "canonical-pos-inc", + "eventName": "pos_inc", + "ledger": 200, + "transactionHash": "tx-canonical-pos", + "named": { + "position_key": "reorg-pos-1", + "market": "MARKET_TOKEN", + "account": "ACCOUNT", + "collateral_token": "MARKET_TOKEN", + "is_long": true, + "size_delta_usd": "250", + "next_size_usd": "250", + "next_collateral_amount": "8", + "execution_price": "12" + } + }, + { + "id": "canonical-ord-crt", + "eventName": "ord_crt", + "ledger": 200, + "transactionHash": "tx-canonical-order", + "named": { + "key": "reorg-order-1", + "market": "MARKET_TOKEN", + "account": "ACCOUNT", + "order_type": "LimitIncrease", + "is_long": true, + "size_delta_usd": "250", + "acceptable_price": "12" + } + } + ] +} diff --git a/apps/s03-indexer/tests/mappingHandlers.test.ts b/apps/s03-indexer/tests/mappingHandlers.test.ts index c26be62..d3e85ac 100644 --- a/apps/s03-indexer/tests/mappingHandlers.test.ts +++ b/apps/s03-indexer/tests/mappingHandlers.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, test } from "bun:test"; +import { readFileSync } from "fs"; import { decodeAddress, decodeBoolean, @@ -19,8 +20,22 @@ const handlerContract = "CDWOFIP4YQJGMCYAOWLSRBAWN2OTJUG2I5WOFC32O2TX2SRU56RWBE5 const marketFactoryContract = "CBGX3EJFI3JRHSN5B533O2L5P57JFPTCRS55IPWFS5BNDXLJLXDWA5Z2"; const account = Keypair.random().publicKey(); const receiver = Keypair.random().publicKey(); +const reorgFixture = JSON.parse( + readFileSync(`${import.meta.dir}/fixtures/reorged-ledger-sequence.json`, "utf8"), +) as ReorgFixture; type StoreBucket = Map>; +type ReorgFixtureEvent = { + id: string; + eventName: string; + ledger: number; + transactionHash: string; + named: Record; +}; +type ReorgFixture = { + orphaned: ReorgFixtureEvent[]; + canonical: ReorgFixtureEvent[]; +}; const buckets = new Map(); const logs: string[] = []; @@ -402,6 +417,57 @@ describe("SO4 event dispatch", () => { expect(records("PositionChange")[0].changeType).toBe("INCREASE"); }); + test("rewrites stale entities when a reorged ledger is replayed", async () => { + for (const fixtureEvent of reorgFixture.orphaned) { + await dispatchEvent(reorgEvent(fixtureEvent)); + } + + expect(records("Position")).toHaveLength(1); + expect(records("PositionChange")).toHaveLength(1); + expect(records("Order")).toHaveLength(1); + expect(records("Position")[0].sizeUsd).toBe("100"); + expect(records("PositionChange")[0].transactionHash).toBe("tx-orphan-pos"); + expect(records("Order")[0].acceptablePrice).toBe("10"); + + for (const fixtureEvent of reorgFixture.canonical) { + await dispatchEvent(reorgEvent(fixtureEvent)); + } + for (const fixtureEvent of reorgFixture.canonical) { + await dispatchEvent(reorgEvent(fixtureEvent)); + } + + const [position] = records("Position"); + const [positionChange] = records("PositionChange"); + const [order] = records("Order"); + + expect(records("Position")).toHaveLength(1); + expect(records("PositionChange")).toHaveLength(1); + expect(records("Order")).toHaveLength(1); + expect(position).toMatchObject({ + id: "position:reorg-pos-1", + key: "reorg-pos-1", + sizeUsd: "250", + collateralAmount: "8", + updatedLedger: 200, + updatedTransactionHash: "tx-canonical-pos", + }); + expect(positionChange).toMatchObject({ + id: "position-change:reorg-pos-1:200:INCREASE", + key: "reorg-pos-1", + sizeDeltaUsd: "250", + executionPrice: "12", + transactionHash: "tx-canonical-pos", + }); + expect(order).toMatchObject({ + id: "order:reorg-order-1", + key: "reorg-order-1", + sizeDeltaUsd: "250", + acceptablePrice: "12", + createdLedger: 200, + createdTransactionHash: "tx-canonical-order", + }); + }); + test("indexes liquidation and ADL events", async () => { await dispatchEvent( so4Event("liq_exe", { @@ -707,6 +773,27 @@ function so4Event( }; } +function reorgEvent(fixtureEvent: ReorgFixtureEvent): DecodedEvent { + const named = Object.fromEntries( + Object.entries(fixtureEvent.named).map(([key, value]) => [ + key, + value === "MARKET_TOKEN" ? marketToken : value === "ACCOUNT" ? account : value, + ]), + ) as Record; + + return { + ...so4Event(fixtureEvent.eventName, named), + id: fixtureEvent.id, + ledger: fixtureEvent.ledger, + timestamp: new Date("2026-06-24T12:01:00Z"), + transactionHash: fixtureEvent.transactionHash, + values: { + list: Object.values(named), + named, + }, + }; +} + function lifecyclePayload( key: string, extra: Record = {},