From 30c2e0ec7c9d6804b4466b9c1ee13b39f68384f0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 05:43:46 +0000 Subject: [PATCH 1/2] test: mutation-harden log unit coverage Strengthen and extend the unit tests for src/core/process/log.ts so they kill previously-surviving mutants in the income/clear-amount accounting helpers. The existing tests asserted only weak properties (a "_18" suffix for getActualPrice, and typeof/positivity for getTotalIncome), which let arithmetic and argument mutations pass undetected. - getActualPrice: pin the exact computed price and add a non-18 decimals case so the scaleTo18 decimals argument and the ONE18 scaling are checked. - getTotalIncome: pin exact per-branch and summed values, and add a case with distinct prices and incomes so each income is verified to pair with its own token price (not swapped) and the ONE18 divisor is verified. - getActualClearAmount: cover the AfterClearV2 normalize-failure branch returning undefined. Tests-only; no source changes. Co-Authored-By: Claude Opus 4.8 --- src/core/process/log.test.ts | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/core/process/log.test.ts b/src/core/process/log.test.ts index 48008fdd..e80a82c8 100644 --- a/src/core/process/log.test.ts +++ b/src/core/process/log.test.ts @@ -116,6 +116,23 @@ describe("Test log functions", async () => { expect(result).toBe(999n); }); + it("should return undefined when AfterClearV2 aliceOutput cannot be normalized", () => { + // an unparsable float hex makes normalizeFloat return an Err, + // so the normalize-failure branch is taken and undefined is returned + vi.mocked(parseEventLogs).mockReturnValue([ + { + eventName: "AfterClearV2", + args: { + clearStateChange: { + aliceOutput: "0x00", + }, + }, + } as any, + ]); + const result = getActualClearAmount("0xOb", "0xOb", { logs: [] } as any, 18); + expect(result).toBeUndefined(); + }); + it("should return undefined if parseEventLogs throws", () => { vi.mocked(parseEventLogs).mockImplementation(() => { throw new Error("fail"); @@ -139,6 +156,28 @@ describe("Test log functions", async () => { ]); const result = getActualPrice({ logs: [] } as any, "0xOrderbook", "0xArb", "10", 18); expect(result).toContain("_18"); + // price = (scaleTo18(1000n, 18) * ONE18) / 10n, formatted at 18 decimals + // = (1000n * 1e18) / 10n = 1e20 -> mocked formatUnits => "1e20_18" + expect(result).toBe("100000000000000000000_18"); + }); + + it("should scale the transferred value by the token decimals when computing price", () => { + vi.mocked(parseEventLogs).mockReturnValue([ + { + eventName: "Transfer", + args: { + to: "0xArb", + from: "0xOther", + value: 1000n, + }, + } as any, + ]); + // tokenDecimals = 6 (non-18) so the scaleTo18 decimals arg is exercised: + // price = (scaleTo18(1000n, 6) * ONE18) / 10n + // = (1000n * 1e12 * 1e18) / 10n = 1e33 / 10n = 1e32 + // -> mocked formatUnits => "1e32_18"; a value that ignores tokenDecimals differs + const result = getActualPrice({ logs: [] } as any, "0xOrderbook", "0xArb", "10", 6); + expect(result).toBe("100000000000000000000000000000000_18"); }); it("should return undefined if no matching log", () => { @@ -180,18 +219,36 @@ describe("Test log functions", async () => { const result = getTotalIncome(2n, undefined, "2", "1", 18, 18); expect(typeof result).toBe("bigint"); expect(result).toBeGreaterThan(0n); + // input only: (parseUnits("2",18) * scaleTo18(2n,18)) / ONE18 + // = (2e18 * 2n) / 1e18 = 4n; output branch contributes 0 + expect(result).toBe(4n); }); it("should calculate total income for output only", () => { const result = getTotalIncome(undefined, 3n, "1", "3", 18, 18); expect(typeof result).toBe("bigint"); expect(result).toBeGreaterThan(0n); + // output only: (parseUnits("3",18) * scaleTo18(3n,18)) / ONE18 + // = (3e18 * 3n) / 1e18 = 9n; input branch contributes 0 + expect(result).toBe(9n); }); it("should calculate total income for both input and output", () => { const result = getTotalIncome(2n, 3n, "2", "3", 18, 18); expect(typeof result).toBe("bigint"); expect(result).toBeGreaterThan(0n); + // input: (2e18 * 2n)/1e18 = 4n ; output: (3e18 * 3n)/1e18 = 9n ; sum = 13n + expect(result).toBe(13n); + }); + + it("should pair each income with its own token price (not swapped)", () => { + // distinct prices AND distinct incomes so a swap of price<->income + // between the input and output branches changes the exact result. + // input: (parseUnits("2",18) * scaleTo18(2n,18)) / ONE18 = (2e18 * 2n)/1e18 = 4n + // output: (parseUnits("5",18) * scaleTo18(3n,18)) / ONE18 = (5e18 * 3n)/1e18 = 15n + // sum = 19n ; pairing the input income with the output price instead yields 25n + const result = getTotalIncome(2n, 3n, "2", "5", 18, 18); + expect(result).toBe(19n); }); }); }); From 234754ecf22566c4d8c777b02104acd8cd7cb55d Mon Sep 17 00:00:00 2001 From: David Meister Date: Fri, 19 Jun 2026 17:33:08 +0000 Subject: [PATCH 2/2] merge(main): resolve conflicts [merge-update]