diff --git a/packages/protocol-parser/package.json b/packages/protocol-parser/package.json index e326cde81b..8555bea2f2 100644 --- a/packages/protocol-parser/package.json +++ b/packages/protocol-parser/package.json @@ -24,9 +24,7 @@ }, "dependencies": { "@latticexyz/common": "workspace:*", - "@latticexyz/config": "workspace:*", "@latticexyz/schema-type": "workspace:*", - "@latticexyz/store": "workspace:*", "abitype": "0.8.7", "viem": "1.1.7" }, diff --git a/packages/protocol-parser/src/abiTypesToSchema.test.ts b/packages/protocol-parser/src/abiTypesToSchema.test.ts deleted file mode 100644 index 7837dffc5b..0000000000 --- a/packages/protocol-parser/src/abiTypesToSchema.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { abiTypesToSchema } from "./abiTypesToSchema"; - -describe("abiTypesToSchema", () => { - it("encodes schema from ABI types", () => { - expect(abiTypesToSchema(["bool"])).toStrictEqual({ - staticDataLength: 1, - staticFields: ["bool"], - dynamicFields: [], - isEmpty: false, - schemaData: "0x0001010060000000000000000000000000000000000000000000000000000000", - }); - expect(abiTypesToSchema(["bool"], ["bool[]"])).toStrictEqual({ - staticDataLength: 1, - staticFields: ["bool"], - dynamicFields: ["bool[]"], - isEmpty: false, - schemaData: "0x0001010160c20000000000000000000000000000000000000000000000000000", - }); - expect(abiTypesToSchema(["bytes32", "int32"], ["uint256[]", "address[]", "bytes", "string"])).toStrictEqual({ - staticDataLength: 36, - staticFields: ["bytes32", "int32"], - dynamicFields: ["uint256[]", "address[]", "bytes", "string"], - isEmpty: false, - schemaData: "0x002402045f2381c3c4c500000000000000000000000000000000000000000000", - }); - }); -}); diff --git a/packages/protocol-parser/src/abiTypesToSchema.ts b/packages/protocol-parser/src/abiTypesToSchema.ts deleted file mode 100644 index d619a066e9..0000000000 --- a/packages/protocol-parser/src/abiTypesToSchema.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { StaticAbiType, DynamicAbiType, staticAbiTypeToByteLength } from "@latticexyz/schema-type"; -import { Schema } from "./common"; -import { abiTypesToSchemaData } from "./abiTypesToSchemaData"; - -export function abiTypesToSchema(staticFields: StaticAbiType[], dynamicFields: DynamicAbiType[] = []): Schema { - const staticDataLength = staticFields.reduce((acc, fieldType) => acc + staticAbiTypeToByteLength[fieldType], 0); - return { - staticDataLength, - staticFields, - dynamicFields, - isEmpty: false, - schemaData: abiTypesToSchemaData(staticFields, dynamicFields), - }; -} diff --git a/packages/protocol-parser/src/abiTypesToSchemaData.test.ts b/packages/protocol-parser/src/abiTypesToSchemaData.test.ts deleted file mode 100644 index e27851e587..0000000000 --- a/packages/protocol-parser/src/abiTypesToSchemaData.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { abiTypesToSchemaData } from "./abiTypesToSchemaData"; - -describe("abiTypesToSchemaData", () => { - it("encodes schema data from ABI types", () => { - expect(abiTypesToSchemaData(["bool"])).toBe("0x0001010060000000000000000000000000000000000000000000000000000000"); - expect(abiTypesToSchemaData(["bool"], ["bool[]"])).toBe( - "0x0001010160c20000000000000000000000000000000000000000000000000000" - ); - expect(abiTypesToSchemaData(["bytes32", "int32"], ["uint256[]", "address[]", "bytes", "string"])).toBe( - "0x002402045f2381c3c4c500000000000000000000000000000000000000000000" - ); - }); -}); diff --git a/packages/protocol-parser/src/abiTypesToSchemaData.ts b/packages/protocol-parser/src/abiTypesToSchemaData.ts deleted file mode 100644 index 7ee29d4e24..0000000000 --- a/packages/protocol-parser/src/abiTypesToSchemaData.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Hex } from "viem"; -import { schemaAbiTypes, StaticAbiType, DynamicAbiType, staticAbiTypeToByteLength } from "@latticexyz/schema-type"; - -export function abiTypesToSchemaData(staticFields: StaticAbiType[], dynamicFields: DynamicAbiType[] = []): Hex { - const staticDataLength = staticFields.reduce((acc, fieldType) => acc + staticAbiTypeToByteLength[fieldType], 0); - const numStaticFields = staticFields.length; - const numDynamicFields = dynamicFields.length; - const staticSchemaTypes = staticFields.map((abiType) => schemaAbiTypes.indexOf(abiType)); - const dynamicSchemaTypes = dynamicFields.map((abiType) => schemaAbiTypes.indexOf(abiType)); - return `0x${[ - staticDataLength.toString(16).padStart(4, "0"), - numStaticFields.toString(16).padStart(2, "0"), - numDynamicFields.toString(16).padStart(2, "0"), - ...staticSchemaTypes.map((schemaType) => schemaType.toString(16).padStart(2, "0")), - ...dynamicSchemaTypes.map((schemaType) => schemaType.toString(16).padStart(2, "0")), - ] - .join("") - .padEnd(64, "0")}`; -} diff --git a/packages/protocol-parser/src/common.ts b/packages/protocol-parser/src/common.ts index c79e5cc650..ba9cc72a69 100644 --- a/packages/protocol-parser/src/common.ts +++ b/packages/protocol-parser/src/common.ts @@ -1,12 +1,11 @@ -import { Hex } from "viem"; import { DynamicAbiType, StaticAbiType } from "@latticexyz/schema-type"; -export type Schema = Readonly<{ - staticDataLength: number; - staticFields: StaticAbiType[]; - dynamicFields: DynamicAbiType[]; - isEmpty: boolean; - schemaData: Hex; -}>; +export type Schema = { + readonly staticFields: readonly StaticAbiType[]; + readonly dynamicFields: readonly DynamicAbiType[]; +}; -export type TableSchema = { keySchema: Schema; valueSchema: Schema; isEmpty: boolean; schemaData: Hex }; +export type TableSchema = { + readonly keySchema: Schema; // TODO: refine to set dynamicFields to [] + readonly valueSchema: Schema; +}; diff --git a/packages/protocol-parser/src/decodeKeyTuple.test.ts b/packages/protocol-parser/src/decodeKeyTuple.test.ts index e951e9d32d..7b47ac731d 100644 --- a/packages/protocol-parser/src/decodeKeyTuple.test.ts +++ b/packages/protocol-parser/src/decodeKeyTuple.test.ts @@ -1,20 +1,24 @@ import { describe, expect, it } from "vitest"; import { decodeKeyTuple } from "./decodeKeyTuple"; -import { abiTypesToSchema } from "./abiTypesToSchema"; +import { Schema } from "./common"; describe("decodeKeyTuple", () => { it("can decode bool key tuple", () => { expect( - decodeKeyTuple(abiTypesToSchema(["bool"]), ["0x0000000000000000000000000000000000000000000000000000000000000000"]) + decodeKeyTuple({ staticFields: ["bool"], dynamicFields: [] }, [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + ]) ).toStrictEqual([false]); expect( - decodeKeyTuple(abiTypesToSchema(["bool"]), ["0x0000000000000000000000000000000000000000000000000000000000000001"]) + decodeKeyTuple({ staticFields: ["bool"], dynamicFields: [] }, [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + ]) ).toStrictEqual([true]); }); it("can decode complex key tuple", () => { expect( - decodeKeyTuple(abiTypesToSchema(["uint256", "int32", "bytes16", "address", "bool", "int8"]), [ + decodeKeyTuple({ staticFields: ["uint256", "int32", "bytes16", "address", "bool", "int8"], dynamicFields: [] }, [ "0x000000000000000000000000000000000000000000000000000000000000002a", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6", "0x1234000000000000000000000000000000000000000000000000000000000000", diff --git a/packages/protocol-parser/src/decodeKeyTuple.ts b/packages/protocol-parser/src/decodeKeyTuple.ts index 8e6ba5ce46..554068ea4d 100644 --- a/packages/protocol-parser/src/decodeKeyTuple.ts +++ b/packages/protocol-parser/src/decodeKeyTuple.ts @@ -4,7 +4,7 @@ import { Schema } from "./common"; // key tuples are encoded in the same way as abi.encode, so we can decode them with viem -export function decodeKeyTuple(keySchema: Schema, keyTuple: Hex[]): StaticPrimitiveType[] { +export function decodeKeyTuple(keySchema: Schema, keyTuple: readonly Hex[]): StaticPrimitiveType[] { return keyTuple.map( (key, index) => decodeAbiParameters([{ type: keySchema.staticFields[index] }], key)[0] as StaticPrimitiveType ); diff --git a/packages/protocol-parser/src/decodeRecord.test.ts b/packages/protocol-parser/src/decodeRecord.test.ts new file mode 100644 index 0000000000..c201ba08e1 --- /dev/null +++ b/packages/protocol-parser/src/decodeRecord.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from "vitest"; +import { decodeRecord } from "./decodeRecord"; + +describe("decodeRecord", () => { + it("can decode hex to record values", () => { + const schema = { staticFields: ["uint32", "uint128"], dynamicFields: ["uint32[]", "string"] } as const; + const values = decodeRecord( + schema, + "0x0000000100000000000000000000000000000002000000000000130000000008000000000b0000000000000000000000000000000000000300000004736f6d6520737472696e67" + ); + expect(values).toStrictEqual([1, 2n, [3, 4], "some string"]); + }); +}); diff --git a/packages/protocol-parser/src/decodeRecord.ts b/packages/protocol-parser/src/decodeRecord.ts new file mode 100644 index 0000000000..d1b65eb401 --- /dev/null +++ b/packages/protocol-parser/src/decodeRecord.ts @@ -0,0 +1,61 @@ +import { StaticPrimitiveType, DynamicPrimitiveType, staticAbiTypeToByteLength } from "@latticexyz/schema-type"; +import { Hex, sliceHex } from "viem"; +import { Schema } from "./common"; +import { decodeDynamicField } from "./decodeDynamicField"; +import { decodeStaticField } from "./decodeStaticField"; +import { hexToPackedCounter } from "./hexToPackedCounter"; +import { staticDataLength } from "./staticDataLength"; + +export function decodeRecord(schema: Schema, data: Hex): readonly (StaticPrimitiveType | DynamicPrimitiveType)[] { + const values: (StaticPrimitiveType | DynamicPrimitiveType)[] = []; + + let bytesOffset = 0; + schema.staticFields.forEach((fieldType) => { + const fieldByteLength = staticAbiTypeToByteLength[fieldType]; + const value = decodeStaticField(fieldType, sliceHex(data, bytesOffset, bytesOffset + fieldByteLength)); + bytesOffset += fieldByteLength; + values.push(value); + }); + + // Warn user if static data length doesn't match the schema, because data corruption might be possible. + const schemaStaticDataLength = staticDataLength(schema.staticFields); + const actualStaticDataLength = bytesOffset; + if (actualStaticDataLength !== schemaStaticDataLength) { + console.warn( + "Decoded static data length does not match schema's expected static data length. Data may get corrupted. Is `getStaticByteLength` outdated?", + { + expectedLength: schemaStaticDataLength, + actualLength: actualStaticDataLength, + bytesOffset, + } + ); + } + + if (schema.dynamicFields.length > 0) { + const dataLayout = hexToPackedCounter(sliceHex(data, bytesOffset, bytesOffset + 32)); + bytesOffset += 32; + + schema.dynamicFields.forEach((fieldType, i) => { + const dataLength = dataLayout.fieldByteLengths[i]; + const value = decodeDynamicField(fieldType, sliceHex(data, bytesOffset, bytesOffset + dataLength)); + bytesOffset += dataLength; + values.push(value); + }); + + // Warn user if dynamic data length doesn't match the schema, because data corruption might be possible. + const actualDynamicDataLength = bytesOffset - 32 - actualStaticDataLength; + // TODO: refactor this so we don't break for bytes offsets >UINT40 + if (BigInt(actualDynamicDataLength) !== dataLayout.totalByteLength) { + console.warn( + "Decoded dynamic data length does not match data layout's expected data length. Data may get corrupted. Did the data layout change?", + { + expectedLength: dataLayout.totalByteLength, + actualLength: actualDynamicDataLength, + bytesOffset, + } + ); + } + } + + return values; +} diff --git a/packages/protocol-parser/src/encodeField.ts b/packages/protocol-parser/src/encodeField.ts new file mode 100644 index 0000000000..89155d6531 --- /dev/null +++ b/packages/protocol-parser/src/encodeField.ts @@ -0,0 +1,17 @@ +import { SchemaAbiType, arrayAbiTypeToStaticAbiType, isArrayAbiType } from "@latticexyz/schema-type"; +import { AbiParameterToPrimitiveType } from "abitype"; +import { Hex, encodePacked } from "viem"; + +export function encodeField( + fieldType: TSchemaAbiType, + value: AbiParameterToPrimitiveType<{ type: TSchemaAbiType }> +): Hex { + if (isArrayAbiType(fieldType) && Array.isArray(value)) { + const staticFieldType = arrayAbiTypeToStaticAbiType(fieldType); + return encodePacked( + value.map(() => staticFieldType), + value + ); + } + return encodePacked([fieldType], [value]); +} diff --git a/packages/protocol-parser/src/encodeRecord.test.ts b/packages/protocol-parser/src/encodeRecord.test.ts new file mode 100644 index 0000000000..395cbeaa6a --- /dev/null +++ b/packages/protocol-parser/src/encodeRecord.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from "vitest"; +import { encodeRecord } from "./encodeRecord"; + +describe("encodeRecord", () => { + it("can encode a schema and record values to hex", () => { + const schema = { staticFields: ["uint32", "uint128"], dynamicFields: ["uint32[]", "string"] } as const; + const hex = encodeRecord(schema, [1, 2n, [3, 4], "some string"]); + expect(hex).toBe( + "0x0000000100000000000000000000000000000002000000000000130000000008000000000b0000000000000000000000000000000000000300000004736f6d6520737472696e67" + ); + }); +}); diff --git a/packages/protocol-parser/src/encodeRecord.ts b/packages/protocol-parser/src/encodeRecord.ts new file mode 100644 index 0000000000..14c3f19a07 --- /dev/null +++ b/packages/protocol-parser/src/encodeRecord.ts @@ -0,0 +1,28 @@ +import { StaticPrimitiveType, DynamicPrimitiveType } from "@latticexyz/schema-type"; +import { Hex } from "viem"; +import { encodeField } from "./encodeField"; +import { Schema } from "./common"; + +export function encodeRecord(schema: Schema, values: readonly (StaticPrimitiveType | DynamicPrimitiveType)[]): Hex { + const staticValues = values.slice(0, schema.staticFields.length) as readonly StaticPrimitiveType[]; + const dynamicValues = values.slice(schema.staticFields.length) as readonly DynamicPrimitiveType[]; + + const staticData = staticValues + .map((value, i) => encodeField(schema.staticFields[i], value).replace(/^0x/, "")) + .join(""); + + const dynamicDataItems = dynamicValues.map((value, i) => + encodeField(schema.dynamicFields[i], value).replace(/^0x/, "") + ); + + const dynamicFieldByteLengths = dynamicDataItems.map((value) => value.length / 2); + const dynamicTotalByteLength = dynamicFieldByteLengths.reduce((total, length) => total + BigInt(length), 0n); + + const dynamicData = dynamicDataItems.join(""); + + const packedCounter = `${encodeField("uint56", dynamicTotalByteLength).replace(/^0x/, "")}${dynamicFieldByteLengths + .map((length) => encodeField("uint40", length).replace(/^0x/, "")) + .join("")}`.padEnd(64, "0"); + + return `0x${staticData}${packedCounter}${dynamicData}`; +} diff --git a/packages/protocol-parser/src/hexToPackedCounter.ts b/packages/protocol-parser/src/hexToPackedCounter.ts index c33201c575..5ab6cf3ac2 100644 --- a/packages/protocol-parser/src/hexToPackedCounter.ts +++ b/packages/protocol-parser/src/hexToPackedCounter.ts @@ -12,7 +12,7 @@ import { InvalidHexLengthForPackedCounterError, PackedCounterLengthMismatchError export function hexToPackedCounter(data: Hex): { totalByteLength: bigint; - fieldByteLengths: number[]; + fieldByteLengths: readonly number[]; } { if (data.length !== 66) { throw new InvalidHexLengthForPackedCounterError(data); @@ -22,7 +22,7 @@ export function hexToPackedCounter(data: Hex): { // TODO: use schema to make sure we only parse as many as we need (rather than zeroes at the end)? const fieldByteLengths = decodeDynamicField("uint40[]", sliceHex(data, 7)); - const summedLength = BigInt(fieldByteLengths.reduce((a, b) => a + b, 0)); + const summedLength = BigInt(fieldByteLengths.reduce((total, length) => total + length, 0)); if (summedLength !== totalByteLength) { throw new PackedCounterLengthMismatchError(data, totalByteLength, summedLength); } diff --git a/packages/protocol-parser/src/hexToSchema.test.ts b/packages/protocol-parser/src/hexToSchema.test.ts index c62f39e600..37c97fdc34 100644 --- a/packages/protocol-parser/src/hexToSchema.test.ts +++ b/packages/protocol-parser/src/hexToSchema.test.ts @@ -2,28 +2,39 @@ import { describe, expect, it } from "vitest"; import { hexToSchema } from "./hexToSchema"; describe("hexToSchema", () => { - it("decodes schema hex data to schema", () => { - expect(hexToSchema("0x0001010060000000000000000000000000000000000000000000000000000000")).toStrictEqual({ - staticDataLength: 1, - staticFields: ["bool"], - dynamicFields: [], - isEmpty: false, - schemaData: "0x0001010060000000000000000000000000000000000000000000000000000000", - }); - expect(hexToSchema("0x0001010160c20000000000000000000000000000000000000000000000000000")).toStrictEqual({ - staticDataLength: 1, - staticFields: ["bool"], - dynamicFields: ["bool[]"], - isEmpty: false, - schemaData: "0x0001010160c20000000000000000000000000000000000000000000000000000", - }); - expect(hexToSchema("0x002402045f2381c3c4c500000000000000000000000000000000000000000000")).toStrictEqual({ - staticDataLength: 36, - staticFields: ["bytes32", "int32"], - dynamicFields: ["uint256[]", "address[]", "bytes", "string"], - isEmpty: false, - schemaData: "0x002402045f2381c3c4c500000000000000000000000000000000000000000000", - }); + it("converts hex to schema", () => { + expect(hexToSchema("0x0001010060000000000000000000000000000000000000000000000000000000")).toMatchInlineSnapshot(` + { + "dynamicFields": [], + "staticFields": [ + "bool", + ], + } + `); + expect(hexToSchema("0x0001010160c20000000000000000000000000000000000000000000000000000")).toMatchInlineSnapshot(` + { + "dynamicFields": [ + "bool[]", + ], + "staticFields": [ + "bool", + ], + } + `); + expect(hexToSchema("0x002402045f2381c3c4c500000000000000000000000000000000000000000000")).toMatchInlineSnapshot(` + { + "dynamicFields": [ + "uint256[]", + "address[]", + "bytes", + "string", + ], + "staticFields": [ + "bytes32", + "int32", + ], + } + `); }); it("throws if schema hex data is not bytes32", () => { diff --git a/packages/protocol-parser/src/hexToSchema.ts b/packages/protocol-parser/src/hexToSchema.ts index d097ff82bc..a07ed85ab8 100644 --- a/packages/protocol-parser/src/hexToSchema.ts +++ b/packages/protocol-parser/src/hexToSchema.ts @@ -1,5 +1,5 @@ +import { StaticAbiType, DynamicAbiType, schemaAbiTypes, staticAbiTypeToByteLength } from "@latticexyz/schema-type"; import { Hex, hexToNumber, sliceHex } from "viem"; -import { StaticAbiType, DynamicAbiType, staticAbiTypeToByteLength, schemaAbiTypes } from "@latticexyz/schema-type"; import { Schema } from "./common"; import { InvalidHexLengthForSchemaError, SchemaStaticLengthMismatchError } from "./errors"; @@ -29,11 +29,5 @@ export function hexToSchema(data: Hex): Schema { throw new SchemaStaticLengthMismatchError(data, staticDataLength, actualStaticDataLength); } - return { - staticDataLength, - staticFields, - dynamicFields, - isEmpty: data === "0x", - schemaData: data, - }; + return { staticFields, dynamicFields }; } diff --git a/packages/protocol-parser/src/hexToTableSchema.ts b/packages/protocol-parser/src/hexToTableSchema.ts index 9680eb375c..f86637ee63 100644 --- a/packages/protocol-parser/src/hexToTableSchema.ts +++ b/packages/protocol-parser/src/hexToTableSchema.ts @@ -8,7 +8,5 @@ export function hexToTableSchema(data: Hex): TableSchema { return { keySchema, valueSchema, - isEmpty: data === "0x", - schemaData: data, }; } diff --git a/packages/protocol-parser/src/index.ts b/packages/protocol-parser/src/index.ts index ae7f44101e..cf9af4b812 100644 --- a/packages/protocol-parser/src/index.ts +++ b/packages/protocol-parser/src/index.ts @@ -1,10 +1,7 @@ -export * from "./abiTypesToSchema"; -export * from "./abiTypesToSchemaData"; export * from "./common"; export * from "./decodeDynamicField"; export * from "./decodeKeyTuple"; export * from "./decodeStaticField"; export * from "./errors"; export * from "./hexToPackedCounter"; -export * from "./hexToSchema"; export * from "./hexToTableSchema"; diff --git a/packages/protocol-parser/src/schemaToHex.test.ts b/packages/protocol-parser/src/schemaToHex.test.ts new file mode 100644 index 0000000000..57e051e5fe --- /dev/null +++ b/packages/protocol-parser/src/schemaToHex.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { schemaToHex } from "./schemaToHex"; + +describe("schemaToHex", () => { + it("converts schema to hex", () => { + expect(schemaToHex({ staticFields: ["bool"], dynamicFields: [] })).toBe( + "0x0001010060000000000000000000000000000000000000000000000000000000" + ); + expect(schemaToHex({ staticFields: ["bool"], dynamicFields: ["bool[]"] })).toBe( + "0x0001010160c20000000000000000000000000000000000000000000000000000" + ); + expect( + schemaToHex({ staticFields: ["bytes32", "int32"], dynamicFields: ["uint256[]", "address[]", "bytes", "string"] }) + ).toBe("0x002402045f2381c3c4c500000000000000000000000000000000000000000000"); + }); +}); diff --git a/packages/protocol-parser/src/schemaToHex.ts b/packages/protocol-parser/src/schemaToHex.ts new file mode 100644 index 0000000000..c153539c61 --- /dev/null +++ b/packages/protocol-parser/src/schemaToHex.ts @@ -0,0 +1,18 @@ +import { schemaAbiTypes } from "@latticexyz/schema-type"; +import { Hex } from "viem"; +import { Schema } from "./common"; +import { staticDataLength } from "./staticDataLength"; + +export function schemaToHex(schema: Schema): Hex { + const staticSchemaTypes = schema.staticFields.map((abiType) => schemaAbiTypes.indexOf(abiType)); + const dynamicSchemaTypes = schema.dynamicFields.map((abiType) => schemaAbiTypes.indexOf(abiType)); + return `0x${[ + staticDataLength(schema.staticFields).toString(16).padStart(4, "0"), + schema.staticFields.length.toString(16).padStart(2, "0"), + schema.dynamicFields.length.toString(16).padStart(2, "0"), + ...staticSchemaTypes.map((schemaType) => schemaType.toString(16).padStart(2, "0")), + ...dynamicSchemaTypes.map((schemaType) => schemaType.toString(16).padStart(2, "0")), + ] + .join("") + .padEnd(64, "0")}`; +} diff --git a/packages/protocol-parser/src/staticDataLength.ts b/packages/protocol-parser/src/staticDataLength.ts new file mode 100644 index 0000000000..aeb28f4115 --- /dev/null +++ b/packages/protocol-parser/src/staticDataLength.ts @@ -0,0 +1,5 @@ +import { StaticAbiType, staticAbiTypeToByteLength } from "@latticexyz/schema-type"; + +export function staticDataLength(staticFields: readonly StaticAbiType[]): number { + return staticFields.reduce((length, fieldType) => length + staticAbiTypeToByteLength[fieldType], 0); +} diff --git a/packages/schema-type/src/typescript/arrayAbiTypes.ts b/packages/schema-type/src/typescript/arrayAbiTypes.ts new file mode 100644 index 0000000000..76c7d69673 --- /dev/null +++ b/packages/schema-type/src/typescript/arrayAbiTypes.ts @@ -0,0 +1,17 @@ +import { StaticAbiType } from "./schemaAbiTypes"; + +const arrayAbiTypePattern = /\[\]$/; + +export type ArrayAbiTypeToStaticAbiType = T extends `${infer StaticAbiType}[]` + ? StaticAbiType + : never; + +export function isArrayAbiType(abiType: string): abiType is T { + return arrayAbiTypePattern.test(abiType); +} + +export function arrayAbiTypeToStaticAbiType( + abiType: T +): ArrayAbiTypeToStaticAbiType { + return abiType.replace(arrayAbiTypePattern, "") as ArrayAbiTypeToStaticAbiType; +} diff --git a/packages/schema-type/src/typescript/dynamicAbiTypes.test-d.ts b/packages/schema-type/src/typescript/dynamicAbiTypes.test-d.ts index e58b4fcb40..8c5f7b73a7 100644 --- a/packages/schema-type/src/typescript/dynamicAbiTypes.test-d.ts +++ b/packages/schema-type/src/typescript/dynamicAbiTypes.test-d.ts @@ -4,56 +4,56 @@ import { DynamicAbiTypeToPrimitiveType } from "./dynamicAbiTypes"; describe("DynamicAbiTypeToPrimitiveType", () => { it("maps uint8 array to number array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps uint32 array to number array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps uint48 array to number array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps uint56 array to bigint array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps uint256 array to bigint array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps int8 array to number array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps int32 array to number array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps int48 array to number array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps int56 array to bigint array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps int256 array to bigint array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps bytes1 array to hex array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps bytes2 array to hex array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps bytes8 array to hex array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps bytes32 array to hex array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps bool array to boolean array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps address array to hex array", () => { - expectTypeOf>().toMatchTypeOf(); + expectTypeOf>().toMatchTypeOf(); }); it("maps bytes to hex", () => { diff --git a/packages/schema-type/src/typescript/dynamicAbiTypes.ts b/packages/schema-type/src/typescript/dynamicAbiTypes.ts index 7c5c642e4c..338c1d190d 100644 --- a/packages/schema-type/src/typescript/dynamicAbiTypes.ts +++ b/packages/schema-type/src/typescript/dynamicAbiTypes.ts @@ -1,113 +1,119 @@ import { Hex } from "viem"; -import { StaticAbiType, DynamicAbiType } from "./schemaAbiTypes"; +import { DynamicAbiType } from "./schemaAbiTypes"; import { LiteralToBroad } from "./utils"; // Variable-length ABI types, where their lengths are encoded by a PackedCounter within the record -export type DynamicPrimitiveType = number[] | bigint[] | Hex[] | boolean[] | Hex | string; +export type DynamicPrimitiveType = + | readonly number[] + | readonly bigint[] + | readonly Hex[] + | readonly boolean[] + | Hex + | string; export const dynamicAbiTypeToDefaultValue = { - "uint8[]": [] as number[], - "uint16[]": [] as number[], - "uint24[]": [] as number[], - "uint32[]": [] as number[], - "uint40[]": [] as number[], - "uint48[]": [] as number[], - "uint56[]": [] as bigint[], - "uint64[]": [] as bigint[], - "uint72[]": [] as bigint[], - "uint80[]": [] as bigint[], - "uint88[]": [] as bigint[], - "uint96[]": [] as bigint[], - "uint104[]": [] as bigint[], - "uint112[]": [] as bigint[], - "uint120[]": [] as bigint[], - "uint128[]": [] as bigint[], - "uint136[]": [] as bigint[], - "uint144[]": [] as bigint[], - "uint152[]": [] as bigint[], - "uint160[]": [] as bigint[], - "uint168[]": [] as bigint[], - "uint176[]": [] as bigint[], - "uint184[]": [] as bigint[], - "uint192[]": [] as bigint[], - "uint200[]": [] as bigint[], - "uint208[]": [] as bigint[], - "uint216[]": [] as bigint[], - "uint224[]": [] as bigint[], - "uint232[]": [] as bigint[], - "uint240[]": [] as bigint[], - "uint248[]": [] as bigint[], - "uint256[]": [] as bigint[], + "uint8[]": [] as readonly number[], + "uint16[]": [] as readonly number[], + "uint24[]": [] as readonly number[], + "uint32[]": [] as readonly number[], + "uint40[]": [] as readonly number[], + "uint48[]": [] as readonly number[], + "uint56[]": [] as readonly bigint[], + "uint64[]": [] as readonly bigint[], + "uint72[]": [] as readonly bigint[], + "uint80[]": [] as readonly bigint[], + "uint88[]": [] as readonly bigint[], + "uint96[]": [] as readonly bigint[], + "uint104[]": [] as readonly bigint[], + "uint112[]": [] as readonly bigint[], + "uint120[]": [] as readonly bigint[], + "uint128[]": [] as readonly bigint[], + "uint136[]": [] as readonly bigint[], + "uint144[]": [] as readonly bigint[], + "uint152[]": [] as readonly bigint[], + "uint160[]": [] as readonly bigint[], + "uint168[]": [] as readonly bigint[], + "uint176[]": [] as readonly bigint[], + "uint184[]": [] as readonly bigint[], + "uint192[]": [] as readonly bigint[], + "uint200[]": [] as readonly bigint[], + "uint208[]": [] as readonly bigint[], + "uint216[]": [] as readonly bigint[], + "uint224[]": [] as readonly bigint[], + "uint232[]": [] as readonly bigint[], + "uint240[]": [] as readonly bigint[], + "uint248[]": [] as readonly bigint[], + "uint256[]": [] as readonly bigint[], - "int8[]": [] as number[], - "int16[]": [] as number[], - "int24[]": [] as number[], - "int32[]": [] as number[], - "int40[]": [] as number[], - "int48[]": [] as number[], - "int56[]": [] as bigint[], - "int64[]": [] as bigint[], - "int72[]": [] as bigint[], - "int80[]": [] as bigint[], - "int88[]": [] as bigint[], - "int96[]": [] as bigint[], - "int104[]": [] as bigint[], - "int112[]": [] as bigint[], - "int120[]": [] as bigint[], - "int128[]": [] as bigint[], - "int136[]": [] as bigint[], - "int144[]": [] as bigint[], - "int152[]": [] as bigint[], - "int160[]": [] as bigint[], - "int168[]": [] as bigint[], - "int176[]": [] as bigint[], - "int184[]": [] as bigint[], - "int192[]": [] as bigint[], - "int200[]": [] as bigint[], - "int208[]": [] as bigint[], - "int216[]": [] as bigint[], - "int224[]": [] as bigint[], - "int232[]": [] as bigint[], - "int240[]": [] as bigint[], - "int248[]": [] as bigint[], - "int256[]": [] as bigint[], + "int8[]": [] as readonly number[], + "int16[]": [] as readonly number[], + "int24[]": [] as readonly number[], + "int32[]": [] as readonly number[], + "int40[]": [] as readonly number[], + "int48[]": [] as readonly number[], + "int56[]": [] as readonly bigint[], + "int64[]": [] as readonly bigint[], + "int72[]": [] as readonly bigint[], + "int80[]": [] as readonly bigint[], + "int88[]": [] as readonly bigint[], + "int96[]": [] as readonly bigint[], + "int104[]": [] as readonly bigint[], + "int112[]": [] as readonly bigint[], + "int120[]": [] as readonly bigint[], + "int128[]": [] as readonly bigint[], + "int136[]": [] as readonly bigint[], + "int144[]": [] as readonly bigint[], + "int152[]": [] as readonly bigint[], + "int160[]": [] as readonly bigint[], + "int168[]": [] as readonly bigint[], + "int176[]": [] as readonly bigint[], + "int184[]": [] as readonly bigint[], + "int192[]": [] as readonly bigint[], + "int200[]": [] as readonly bigint[], + "int208[]": [] as readonly bigint[], + "int216[]": [] as readonly bigint[], + "int224[]": [] as readonly bigint[], + "int232[]": [] as readonly bigint[], + "int240[]": [] as readonly bigint[], + "int248[]": [] as readonly bigint[], + "int256[]": [] as readonly bigint[], - "bytes1[]": [] as Hex[], - "bytes2[]": [] as Hex[], - "bytes3[]": [] as Hex[], - "bytes4[]": [] as Hex[], - "bytes5[]": [] as Hex[], - "bytes6[]": [] as Hex[], - "bytes7[]": [] as Hex[], - "bytes8[]": [] as Hex[], - "bytes9[]": [] as Hex[], - "bytes10[]": [] as Hex[], - "bytes11[]": [] as Hex[], - "bytes12[]": [] as Hex[], - "bytes13[]": [] as Hex[], - "bytes14[]": [] as Hex[], - "bytes15[]": [] as Hex[], - "bytes16[]": [] as Hex[], - "bytes17[]": [] as Hex[], - "bytes18[]": [] as Hex[], - "bytes19[]": [] as Hex[], - "bytes20[]": [] as Hex[], - "bytes21[]": [] as Hex[], - "bytes22[]": [] as Hex[], - "bytes23[]": [] as Hex[], - "bytes24[]": [] as Hex[], - "bytes25[]": [] as Hex[], - "bytes26[]": [] as Hex[], - "bytes27[]": [] as Hex[], - "bytes28[]": [] as Hex[], - "bytes29[]": [] as Hex[], - "bytes30[]": [] as Hex[], - "bytes31[]": [] as Hex[], - "bytes32[]": [] as Hex[], + "bytes1[]": [] as readonly Hex[], + "bytes2[]": [] as readonly Hex[], + "bytes3[]": [] as readonly Hex[], + "bytes4[]": [] as readonly Hex[], + "bytes5[]": [] as readonly Hex[], + "bytes6[]": [] as readonly Hex[], + "bytes7[]": [] as readonly Hex[], + "bytes8[]": [] as readonly Hex[], + "bytes9[]": [] as readonly Hex[], + "bytes10[]": [] as readonly Hex[], + "bytes11[]": [] as readonly Hex[], + "bytes12[]": [] as readonly Hex[], + "bytes13[]": [] as readonly Hex[], + "bytes14[]": [] as readonly Hex[], + "bytes15[]": [] as readonly Hex[], + "bytes16[]": [] as readonly Hex[], + "bytes17[]": [] as readonly Hex[], + "bytes18[]": [] as readonly Hex[], + "bytes19[]": [] as readonly Hex[], + "bytes20[]": [] as readonly Hex[], + "bytes21[]": [] as readonly Hex[], + "bytes22[]": [] as readonly Hex[], + "bytes23[]": [] as readonly Hex[], + "bytes24[]": [] as readonly Hex[], + "bytes25[]": [] as readonly Hex[], + "bytes26[]": [] as readonly Hex[], + "bytes27[]": [] as readonly Hex[], + "bytes28[]": [] as readonly Hex[], + "bytes29[]": [] as readonly Hex[], + "bytes30[]": [] as readonly Hex[], + "bytes31[]": [] as readonly Hex[], + "bytes32[]": [] as readonly Hex[], - "bool[]": [] as boolean[], - "address[]": [] as Hex[], + "bool[]": [] as readonly boolean[], + "address[]": [] as readonly Hex[], bytes: "0x", string: "", @@ -116,13 +122,3 @@ export const dynamicAbiTypeToDefaultValue = { export type DynamicAbiTypeToPrimitiveType = LiteralToBroad< (typeof dynamicAbiTypeToDefaultValue)[TDynamicAbiType] >; - -export type ArrayAbiTypeToStaticAbiType = T extends `${infer StaticAbiType}[]` - ? StaticAbiType - : never; - -export function arrayAbiTypeToStaticAbiType( - abiType: T -): ArrayAbiTypeToStaticAbiType { - return abiType.replace(/\[\]$/g, "") as ArrayAbiTypeToStaticAbiType; -} diff --git a/packages/schema-type/src/typescript/index.ts b/packages/schema-type/src/typescript/index.ts index e0031491e1..eafb2eb6e6 100644 --- a/packages/schema-type/src/typescript/index.ts +++ b/packages/schema-type/src/typescript/index.ts @@ -1,3 +1,4 @@ +export * from "./arrayAbiTypes"; export * from "./dynamicAbiTypes"; export * from "./schemaAbiTypes"; export * from "./schemaAbiTypeToDefaultValue"; diff --git a/packages/schema-type/src/typescript/schemaAbiTypes.ts b/packages/schema-type/src/typescript/schemaAbiTypes.ts index f831a3fe3e..c7f3886e15 100644 --- a/packages/schema-type/src/typescript/schemaAbiTypes.ts +++ b/packages/schema-type/src/typescript/schemaAbiTypes.ts @@ -2,7 +2,6 @@ import { AbiType } from "abitype"; import { TupleSplit } from "./utils"; // Keep this array in sync with SchemaType.sol enum - export const schemaAbiTypes = [ "uint8", "uint16", diff --git a/packages/schema-type/src/typescript/utils.ts b/packages/schema-type/src/typescript/utils.ts index 9b75d371c0..65d1a05c65 100644 --- a/packages/schema-type/src/typescript/utils.ts +++ b/packages/schema-type/src/typescript/utils.ts @@ -6,7 +6,9 @@ export type TupleSplit : [O, T]; -export type LiteralToBroad = T extends Array +export type LiteralToBroad = T extends Readonly> + ? readonly LiteralToBroad[] + : T extends Array ? LiteralToBroad[] : T extends number ? number diff --git a/packages/store/test/Mixed.t.sol b/packages/store/test/Mixed.t.sol index 93589810f3..c59375b3a7 100644 --- a/packages/store/test/Mixed.t.sol +++ b/packages/store/test/Mixed.t.sol @@ -56,4 +56,16 @@ contract MixedTest is Test, GasReporter, StoreReadWithStubs { testMixed = mixed; endGasReport(); } + + function testEncode() public { + uint32[] memory a32 = new uint32[](2); + a32[0] = 3; + a32[1] = 4; + string memory s = "some string"; + + assertEq( + Mixed.encode(1, 2, a32, s), + hex"0000000100000000000000000000000000000002000000000000130000000008000000000b0000000000000000000000000000000000000300000004736f6d6520737472696e67" + ); + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b6dc5b620..c6feaa0dfa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -554,15 +554,9 @@ importers: '@latticexyz/common': specifier: workspace:* version: link:../common - '@latticexyz/config': - specifier: workspace:* - version: link:../config '@latticexyz/schema-type': specifier: workspace:* version: link:../schema-type - '@latticexyz/store': - specifier: workspace:* - version: link:../store abitype: specifier: 0.8.7 version: 0.8.7(typescript@5.0.4)