diff --git a/e2e/packages/contracts/mud.config.ts b/e2e/packages/contracts/mud.config.ts index 318ed576670..762ce21584e 100644 --- a/e2e/packages/contracts/mud.config.ts +++ b/e2e/packages/contracts/mud.config.ts @@ -25,5 +25,17 @@ export default mudConfig({ value: "uint32[]", }, }, + Multi: { + keySchema: { + a: "uint32", + b: "bool", + c: "uint256", + d: "int120", + }, + schema: { + num: "int256", + value: "bool", + }, + }, }, }); diff --git a/e2e/packages/sync-test/data/callWorld.ts b/e2e/packages/sync-test/data/callWorld.ts index d7d94551653..ec8bfa8c594 100644 --- a/e2e/packages/sync-test/data/callWorld.ts +++ b/e2e/packages/sync-test/data/callWorld.ts @@ -8,7 +8,11 @@ type Args = IWorld[Method] extends (...args: any) = export function callWorld(page: Page, method: Method, args: Args) { return page.evaluate( ([_method, _args]) => { - return window["worldSend"](_method, _args).then((tx) => tx.wait()); + return window["worldSend"](_method, _args) + .then((tx) => tx.wait()) + .catch((e) => { + throw new Error([`Error executing ${_method} with args:`, JSON.stringify(_args), e].join("\n\n")); + }); }, [method, args] ); diff --git a/e2e/packages/sync-test/data/encodeTestData.test.ts b/e2e/packages/sync-test/data/encodeTestData.test.ts index 59a5631fa6d..90507dc4660 100644 --- a/e2e/packages/sync-test/data/encodeTestData.test.ts +++ b/e2e/packages/sync-test/data/encodeTestData.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { encodeTestData } from "./encodeTestData"; describe("encodeTestData", () => { - it("should encode test data", () => { + it("should encode numbers", () => { expect(encodeTestData({ Number: [{ key: { key: 42 }, value: { value: 1337 } }] })).toStrictEqual({ Number: [{ key: ["0x000000000000000000000000000000000000000000000000000000000000002a"], value: "0x00000539" }], }); diff --git a/e2e/packages/sync-test/data/encodeTestData.ts b/e2e/packages/sync-test/data/encodeTestData.ts index 2661c706811..f4ba27153b3 100644 --- a/e2e/packages/sync-test/data/encodeTestData.ts +++ b/e2e/packages/sync-test/data/encodeTestData.ts @@ -1,6 +1,6 @@ import { mapObject } from "@latticexyz/utils"; import { Data, EncodedData } from "./types"; -import { encodeAbiParameters, toHex, encodePacked } from "viem"; +import { encodeAbiParameters, toHex, encodePacked, numberToHex } from "viem"; import config from "../../contracts/mud.config"; /** @@ -10,9 +10,13 @@ export function encodeTestData(testData: Data) { return mapObject(testData, (records, table) => records ? records.map((record) => ({ - key: Object.values(record.key).map((key) => - encodeAbiParameters([{ type: "bytes32" }], [toHex(key as any, { size: 32 })]) - ), + key: Object.entries(record.key).map(([keyName, keyValue]) => { + const signed = config.tables[table].keySchema[keyName]?.startsWith("int"); + return encodeAbiParameters( + [{ type: "bytes32" }], + [signed ? numberToHex(keyValue as any, { size: 32, signed }) : toHex(keyValue as any, { size: 32 })] + ); + }), value: encodePacked(Object.values(config.tables[table].schema), Object.values(record.value)), })) : undefined diff --git a/e2e/packages/sync-test/data/mergeTestData.ts b/e2e/packages/sync-test/data/mergeTestData.ts index e03aa54e084..1ef76b2e753 100644 --- a/e2e/packages/sync-test/data/mergeTestData.ts +++ b/e2e/packages/sync-test/data/mergeTestData.ts @@ -1,5 +1,6 @@ import { mapObject } from "@latticexyz/utils"; import { Data, Datum } from "./types"; +import { serialize } from "./utils"; /** * Merges an array of test data by merging and filtering the records for each table, @@ -12,7 +13,7 @@ export function mergeTestData(...testDatas: Data[]) { for (const [table, records] of Object.entries(testData)) { recordsByTableByKeys[table] ??= {}; for (const record of records) { - recordsByTableByKeys[table][JSON.stringify(record.key)] = record; + recordsByTableByKeys[table][serialize(record.key)] = record; } } } diff --git a/e2e/packages/sync-test/data/readClientStore.ts b/e2e/packages/sync-test/data/readClientStore.ts index 1fb293ab4e9..5e645191100 100644 --- a/e2e/packages/sync-test/data/readClientStore.ts +++ b/e2e/packages/sync-test/data/readClientStore.ts @@ -2,6 +2,7 @@ import { Page } from "@playwright/test"; // Extract the storeCache type directly from the client import { setup } from "../../client-vanilla/src/mud/setup"; +import { deserialize, serialize } from "./utils"; type StoreCache = Awaited>["network"]["storeCache"]; /** @@ -9,8 +10,45 @@ type StoreCache = Awaited>["network"]["storeCache"]; * This is necessary because `page.evaluate` can only transmit serialisable data, * so we can't just return the entire client store (which includes functions to read data) */ -export async function readClientStore(page: Page, selector: Parameters): Promise { - return page.evaluate((_selector) => { - return window["storeCache"].get(..._selector); +export async function readClientStore( + page: Page, + [namespace, table, key]: Parameters +): Promise | undefined> { + const selector = [namespace, table, serialize(key)]; + const serializedValue = await page.evaluate((_selector) => { + const [_namespace, _table, _key] = _selector; + const result = window["storeCache"].get(_namespace, _table, deserialize(_key)); + return result ? serialize(result) : undefined; + + /** + * Helper to serialize values that are not natively serializable and therefore not transferrable to the page + * For now only `bigint` needs serialization. + */ + function serialize(obj: unknown): string { + return JSON.stringify(obj, (_, v) => (typeof v === "bigint" ? `bigint(${v.toString()})` : v)); + } + + /** + * Helper to deserialize values that were serialized by `serialize` (because they are not natively serializable). + * For now only `bigint` is serialized and need to be deserialized here. + */ + function deserialize(blob: string): Record { + const obj = JSON.parse(blob); + + // Check whether the value matches the mattern `${number}n` + // (serialization of bigint in `serialize`) + // and turn it back into a bigint + const regex = /^bigint\((.*)\)$/; // Regular expression pattern. + for (const [key, value] of Object.entries(obj)) { + const match = typeof value === "string" && value.match(regex); // Attempt to match the pattern. + if (match) { + obj[key] = BigInt(match[1]); + } + } + + return obj; + } }, selector); + + return serializedValue ? deserialize(serializedValue) : undefined; } diff --git a/e2e/packages/sync-test/data/testData.ts b/e2e/packages/sync-test/data/testData.ts index 0384d5f4970..9c24b99fe68 100644 --- a/e2e/packages/sync-test/data/testData.ts +++ b/e2e/packages/sync-test/data/testData.ts @@ -7,6 +7,12 @@ import { Data } from "./types"; export const testData1 = { Number: [{ key: { key: 1 }, value: { value: 42 } }], Vector: [{ key: { key: 1 }, value: { x: 1, y: 2 } }], + Multi: [ + { + key: { a: 9999, b: true, c: BigInt(42), d: BigInt(-999) }, + value: { num: BigInt(1337), value: true }, + }, + ], } satisfies Data; export const testData2 = { @@ -18,4 +24,14 @@ export const testData2 = { { key: { key: 1 }, value: { x: 1, y: 42 } }, { key: { key: 32 }, value: { x: 1, y: -42 } }, ], + Multi: [ + { + key: { a: 9999, b: true, c: BigInt(42), d: BigInt(-999) }, + value: { num: BigInt(31337), value: false }, + }, + { + key: { a: 9998, b: true, c: BigInt(42), d: BigInt(-999) }, + value: { num: BigInt(0), value: true }, + }, + ], } satisfies Data; diff --git a/e2e/packages/sync-test/data/utils.ts b/e2e/packages/sync-test/data/utils.ts new file mode 100644 index 00000000000..878f94fdf38 --- /dev/null +++ b/e2e/packages/sync-test/data/utils.ts @@ -0,0 +1,28 @@ +/** + * Helper to serialize values that are not natively serializable and therefore not transferrable to the page + * For now only `bigint` needs serialization. + */ +export function serialize(obj: unknown): string { + return JSON.stringify(obj, (_, v) => (typeof v === "bigint" ? `bigint(${v.toString()})` : v)); +} + +/** + * Helper to deserialize values that were serialized by `serialize` (because they are not natively serializable). + * For now only `bigint` is serialized and need to be deserialized here. + */ +export function deserialize(blob: string): Record { + const obj = JSON.parse(blob); + + // Check whether the value matches the mattern `${number}n` + // (serialization of bigint in `serialize`) + // and turn it back into a bigint + const regex = /^bigint\((.*)\)$/; // Regular expression pattern. + for (const [key, value] of Object.entries(obj)) { + const match = typeof value === "string" && value.match(regex); // Attempt to match the pattern. + if (match) { + obj[key] = BigInt(match[1]); + } + } + + return obj; +}