@latticexyz/protocol-parser 0.0.0-indexing-20230707130424

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { decodeKeyTuple } from "./decodeKeyTuple";
3
+
4
+ describe("decodeKeyTuple", () => {
5
+ it("can decode bool key tuple", () => {
6
+ expect(
7
+ decodeKeyTuple({ staticFields: ["bool"], dynamicFields: [] }, [
8
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
9
+ ])
10
+ ).toStrictEqual([false]);
11
+ expect(
12
+ decodeKeyTuple({ staticFields: ["bool"], dynamicFields: [] }, [
13
+ "0x0000000000000000000000000000000000000000000000000000000000000001",
14
+ ])
15
+ ).toStrictEqual([true]);
16
+ });
17
+
18
+ it("can decode complex key tuple", () => {
19
+ expect(
20
+ decodeKeyTuple({ staticFields: ["uint256", "int32", "bytes16", "address", "bool", "int8"], dynamicFields: [] }, [
21
+ "0x000000000000000000000000000000000000000000000000000000000000002a",
22
+ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6",
23
+ "0x1234000000000000000000000000000000000000000000000000000000000000",
24
+ "0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff",
25
+ "0x0000000000000000000000000000000000000000000000000000000000000001",
26
+ "0x0000000000000000000000000000000000000000000000000000000000000003",
27
+ ])
28
+ ).toStrictEqual([
29
+ 42n,
30
+ -42,
31
+ "0x12340000000000000000000000000000",
32
+ "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF",
33
+ true,
34
+ 3,
35
+ ]);
36
+ });
37
+ });
@@ -0,0 +1,11 @@
1
+ import { Hex, decodeAbiParameters } from "viem";
2
+ import { StaticPrimitiveType } from "@latticexyz/schema-type";
3
+ import { Schema } from "./common";
4
+
5
+ // key tuples are encoded in the same way as abi.encode, so we can decode them with viem
6
+
7
+ export function decodeKeyTuple(keySchema: Schema, keyTuple: readonly Hex[]): StaticPrimitiveType[] {
8
+ return keyTuple.map(
9
+ (key, index) => decodeAbiParameters([{ type: keySchema.staticFields[index] }], key)[0] as StaticPrimitiveType
10
+ );
11
+ }
@@ -0,0 +1,13 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { decodeRecord } from "./decodeRecord";
3
+
4
+ describe("decodeRecord", () => {
5
+ it("can decode hex to record values", () => {
6
+ const schema = { staticFields: ["uint32", "uint128"], dynamicFields: ["uint32[]", "string"] } as const;
7
+ const values = decodeRecord(
8
+ schema,
9
+ "0x0000000100000000000000000000000000000002000000000000130000000008000000000b0000000000000000000000000000000000000300000004736f6d6520737472696e67"
10
+ );
11
+ expect(values).toStrictEqual([1, 2n, [3, 4], "some string"]);
12
+ });
13
+ });
@@ -0,0 +1,61 @@
1
+ import { StaticPrimitiveType, DynamicPrimitiveType, staticAbiTypeToByteLength } from "@latticexyz/schema-type";
2
+ import { Hex, sliceHex } from "viem";
3
+ import { Schema } from "./common";
4
+ import { decodeDynamicField } from "./decodeDynamicField";
5
+ import { decodeStaticField } from "./decodeStaticField";
6
+ import { hexToPackedCounter } from "./hexToPackedCounter";
7
+ import { staticDataLength } from "./staticDataLength";
8
+
9
+ export function decodeRecord(schema: Schema, data: Hex): readonly (StaticPrimitiveType | DynamicPrimitiveType)[] {
10
+ const values: (StaticPrimitiveType | DynamicPrimitiveType)[] = [];
11
+
12
+ let bytesOffset = 0;
13
+ schema.staticFields.forEach((fieldType) => {
14
+ const fieldByteLength = staticAbiTypeToByteLength[fieldType];
15
+ const value = decodeStaticField(fieldType, sliceHex(data, bytesOffset, bytesOffset + fieldByteLength));
16
+ bytesOffset += fieldByteLength;
17
+ values.push(value);
18
+ });
19
+
20
+ // Warn user if static data length doesn't match the schema, because data corruption might be possible.
21
+ const schemaStaticDataLength = staticDataLength(schema.staticFields);
22
+ const actualStaticDataLength = bytesOffset;
23
+ if (actualStaticDataLength !== schemaStaticDataLength) {
24
+ console.warn(
25
+ "Decoded static data length does not match schema's expected static data length. Data may get corrupted. Is `getStaticByteLength` outdated?",
26
+ {
27
+ expectedLength: schemaStaticDataLength,
28
+ actualLength: actualStaticDataLength,
29
+ bytesOffset,
30
+ }
31
+ );
32
+ }
33
+
34
+ if (schema.dynamicFields.length > 0) {
35
+ const dataLayout = hexToPackedCounter(sliceHex(data, bytesOffset, bytesOffset + 32));
36
+ bytesOffset += 32;
37
+
38
+ schema.dynamicFields.forEach((fieldType, i) => {
39
+ const dataLength = dataLayout.fieldByteLengths[i];
40
+ const value = decodeDynamicField(fieldType, sliceHex(data, bytesOffset, bytesOffset + dataLength));
41
+ bytesOffset += dataLength;
42
+ values.push(value);
43
+ });
44
+
45
+ // Warn user if dynamic data length doesn't match the schema, because data corruption might be possible.
46
+ const actualDynamicDataLength = bytesOffset - 32 - actualStaticDataLength;
47
+ // TODO: refactor this so we don't break for bytes offsets >UINT40
48
+ if (BigInt(actualDynamicDataLength) !== dataLayout.totalByteLength) {
49
+ console.warn(
50
+ "Decoded dynamic data length does not match data layout's expected data length. Data may get corrupted. Did the data layout change?",
51
+ {
52
+ expectedLength: dataLayout.totalByteLength,
53
+ actualLength: actualDynamicDataLength,
54
+ bytesOffset,
55
+ }
56
+ );
57
+ }
58
+ }
59
+
60
+ return values;
61
+ }
@@ -0,0 +1,40 @@
1
+ import { assertType, describe, it } from "vitest";
2
+ import { Hex } from "viem";
3
+ import { decodeStaticField } from "./decodeStaticField";
4
+
5
+ describe("decodeStaticField", () => {
6
+ it("returns a boolean for bool ABI type", () => {
7
+ assertType<boolean>(decodeStaticField("bool", "0x"));
8
+ });
9
+
10
+ it("returns a number or bigint for uint ABI types", () => {
11
+ assertType<number>(decodeStaticField("uint8", "0x"));
12
+ assertType<number>(decodeStaticField("uint16", "0x"));
13
+ assertType<number>(decodeStaticField("uint32", "0x"));
14
+ assertType<number>(decodeStaticField("uint48", "0x"));
15
+ assertType<bigint>(decodeStaticField("uint56", "0x"));
16
+ assertType<bigint>(decodeStaticField("uint128", "0x"));
17
+ assertType<bigint>(decodeStaticField("uint256", "0x"));
18
+ });
19
+
20
+ it("returns a number or bigint for int ABI types", () => {
21
+ assertType<number>(decodeStaticField("int8", "0x"));
22
+ assertType<number>(decodeStaticField("int16", "0x"));
23
+ assertType<number>(decodeStaticField("int32", "0x"));
24
+ assertType<number>(decodeStaticField("int48", "0x"));
25
+ assertType<bigint>(decodeStaticField("int56", "0x"));
26
+ assertType<bigint>(decodeStaticField("int128", "0x"));
27
+ assertType<bigint>(decodeStaticField("int256", "0x"));
28
+ });
29
+
30
+ it("returns a hex for bytes ABI types", () => {
31
+ assertType<Hex>(decodeStaticField("bytes1", "0x"));
32
+ assertType<Hex>(decodeStaticField("bytes2", "0x"));
33
+ assertType<Hex>(decodeStaticField("bytes8", "0x"));
34
+ assertType<Hex>(decodeStaticField("bytes32", "0x"));
35
+ });
36
+
37
+ it("returns a hex for address ABI type", () => {
38
+ assertType<Hex>(decodeStaticField("address", "0x"));
39
+ });
40
+ });
@@ -0,0 +1,94 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { decodeStaticField } from "./decodeStaticField";
3
+
4
+ describe("decodeStaticField", () => {
5
+ it("can decode bool", () => {
6
+ expect(decodeStaticField("bool", "0x00")).toBe(false);
7
+ expect(decodeStaticField("bool", "0x01")).toBe(true);
8
+
9
+ expect(() => decodeStaticField("bool", "0x0")).toThrow(
10
+ 'Hex value "0x0" has length of 1, but expected length of 2 for bool type.'
11
+ );
12
+ expect(() => decodeStaticField("bool", "0x1")).toThrow(
13
+ 'Hex value "0x1" has length of 1, but expected length of 2 for bool type.'
14
+ );
15
+ expect(() => decodeStaticField("bool", "0x000")).toThrow(
16
+ 'Hex value "0x000" is an odd length (3). It must be an even length.'
17
+ );
18
+ expect(() => decodeStaticField("bool", "0x001")).toThrow(
19
+ 'Hex value "0x001" is an odd length (3). It must be an even length.'
20
+ );
21
+ expect(() => decodeStaticField("bool", "0x0000")).toThrow(
22
+ 'Hex value "0x0000" has length of 4, but expected length of 2 for bool type.'
23
+ );
24
+ expect(() => decodeStaticField("bool", "0x0001")).toThrow(
25
+ 'Hex value "0x0001" has length of 4, but expected length of 2 for bool type.'
26
+ );
27
+ });
28
+
29
+ it("can decode uint8", () => {
30
+ expect(decodeStaticField("uint8", "0x00")).toBe(0);
31
+ expect(decodeStaticField("uint8", "0x01")).toBe(1);
32
+ expect(decodeStaticField("uint8", "0xff")).toBe(255);
33
+ });
34
+
35
+ it("can decode uint256", () => {
36
+ expect(decodeStaticField("uint256", "0x0000000000000000000000000000000000000000000000000000000000000000")).toBe(0n);
37
+ expect(decodeStaticField("uint256", "0x0000000000000000000000000000000000000000000000000000000000000001")).toBe(1n);
38
+ expect(decodeStaticField("uint256", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")).toBe(
39
+ 115792089237316195423570985008687907853269984665640564039457584007913129639935n
40
+ );
41
+ expect(decodeStaticField("uint256", "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe")).toBe(
42
+ 115792089237316195423570985008687907853269984665640564039457584007913129639934n
43
+ );
44
+ });
45
+
46
+ it("can decode int8", () => {
47
+ expect(decodeStaticField("int8", "0x00")).toBe(0);
48
+ expect(decodeStaticField("int8", "0x01")).toBe(1);
49
+ expect(decodeStaticField("int8", "0x7f")).toBe(127);
50
+ expect(decodeStaticField("int8", "0x80")).toBe(-128);
51
+ expect(decodeStaticField("int8", "0x81")).toBe(-127);
52
+ expect(decodeStaticField("int8", "0xff")).toBe(-1);
53
+ });
54
+
55
+ it("can decode int256", () => {
56
+ expect(decodeStaticField("int256", "0x0000000000000000000000000000000000000000000000000000000000000000")).toBe(0n);
57
+ expect(decodeStaticField("int256", "0x0000000000000000000000000000000000000000000000000000000000000001")).toBe(1n);
58
+ expect(decodeStaticField("int256", "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")).toBe(
59
+ 57896044618658097711785492504343953926634992332820282019728792003956564819967n
60
+ );
61
+ expect(decodeStaticField("int256", "0x8000000000000000000000000000000000000000000000000000000000000000")).toBe(
62
+ -57896044618658097711785492504343953926634992332820282019728792003956564819968n
63
+ );
64
+ expect(decodeStaticField("int256", "0x8000000000000000000000000000000000000000000000000000000000000001")).toBe(
65
+ -57896044618658097711785492504343953926634992332820282019728792003956564819967n
66
+ );
67
+ expect(decodeStaticField("int256", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")).toBe(-1n);
68
+ });
69
+
70
+ it("can decode bytes", () => {
71
+ expect(decodeStaticField("bytes1", "0x01")).toBe("0x01");
72
+ expect(decodeStaticField("bytes2", "0x0001")).toBe("0x0001");
73
+ expect(decodeStaticField("bytes8", "0xff00ff00ff00ff00")).toBe("0xff00ff00ff00ff00");
74
+ expect(decodeStaticField("bytes32", "0x0000000000000000000000000000000000000000000000000000000000000001")).toBe(
75
+ "0x0000000000000000000000000000000000000000000000000000000000000001"
76
+ );
77
+ });
78
+
79
+ it("can decode address", () => {
80
+ expect(decodeStaticField("address", "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266")).toBe(
81
+ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
82
+ );
83
+ expect(decodeStaticField("address", "0xffffffffffffffffffffffffffffffffffffffff")).toBe(
84
+ "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF"
85
+ );
86
+
87
+ expect(() => decodeStaticField("address", "0x00")).toThrow(
88
+ 'Hex value "0x00" has length of 2, but expected length of 40 for address type.'
89
+ );
90
+ expect(() => decodeStaticField("address", "0xffffffffffffffffffffffffffffffffffffffffff")).toThrow(
91
+ 'Hex value "0xffffffffffffffffffffffffffffffffffffffffff" has length of 42, but expected length of 40 for address type.'
92
+ );
93
+ });
94
+ });
@@ -0,0 +1,145 @@
1
+ import { Hex, getAddress, hexToBigInt, hexToBool } from "viem";
2
+ import { assertExhaustive } from "@latticexyz/common/utils";
3
+ import {
4
+ StaticAbiType,
5
+ StaticAbiTypeToPrimitiveType,
6
+ staticAbiTypeToByteLength,
7
+ staticAbiTypeToDefaultValue,
8
+ } from "@latticexyz/schema-type";
9
+ import { InvalidHexLengthError, InvalidHexLengthForStaticFieldError } from "./errors";
10
+
11
+ export function decodeStaticField<
12
+ TAbiType extends StaticAbiType,
13
+ TPrimitiveType extends StaticAbiTypeToPrimitiveType<TAbiType>
14
+ >(abiType: TAbiType, data: Hex): TPrimitiveType {
15
+ if (data.length > 3 && data.length % 2 !== 0) {
16
+ throw new InvalidHexLengthError(data);
17
+ }
18
+
19
+ const dataSize = (data.length - 2) / 2;
20
+ if (dataSize !== staticAbiTypeToByteLength[abiType]) {
21
+ throw new InvalidHexLengthForStaticFieldError(abiType, data);
22
+ }
23
+
24
+ switch (abiType) {
25
+ case "uint8":
26
+ case "uint16":
27
+ case "uint24":
28
+ case "uint32":
29
+ case "uint40":
30
+ case "uint48":
31
+ case "uint56":
32
+ case "uint64":
33
+ case "uint72":
34
+ case "uint80":
35
+ case "uint88":
36
+ case "uint96":
37
+ case "uint104":
38
+ case "uint112":
39
+ case "uint120":
40
+ case "uint128":
41
+ case "uint136":
42
+ case "uint144":
43
+ case "uint152":
44
+ case "uint160":
45
+ case "uint168":
46
+ case "uint176":
47
+ case "uint184":
48
+ case "uint192":
49
+ case "uint200":
50
+ case "uint208":
51
+ case "uint216":
52
+ case "uint224":
53
+ case "uint232":
54
+ case "uint240":
55
+ case "uint248":
56
+ case "uint256":
57
+ case "int8":
58
+ case "int16":
59
+ case "int24":
60
+ case "int32":
61
+ case "int40":
62
+ case "int48":
63
+ case "int56":
64
+ case "int64":
65
+ case "int72":
66
+ case "int80":
67
+ case "int88":
68
+ case "int96":
69
+ case "int104":
70
+ case "int112":
71
+ case "int120":
72
+ case "int128":
73
+ case "int136":
74
+ case "int144":
75
+ case "int152":
76
+ case "int160":
77
+ case "int168":
78
+ case "int176":
79
+ case "int184":
80
+ case "int192":
81
+ case "int200":
82
+ case "int208":
83
+ case "int216":
84
+ case "int224":
85
+ case "int232":
86
+ case "int240":
87
+ case "int248":
88
+ case "int256": {
89
+ const value = hexToBigInt(data, { signed: abiType.startsWith("int") });
90
+ const defaultValueType = typeof staticAbiTypeToDefaultValue[abiType];
91
+ if (defaultValueType === "number") {
92
+ return Number(value) as TPrimitiveType;
93
+ }
94
+ if (defaultValueType === "bigint") {
95
+ return value as TPrimitiveType;
96
+ }
97
+ throw new Error(`Unexpected default value type (${defaultValueType}) for ABI type (${abiType})`);
98
+ }
99
+
100
+ case "bytes1":
101
+ case "bytes2":
102
+ case "bytes3":
103
+ case "bytes4":
104
+ case "bytes5":
105
+ case "bytes6":
106
+ case "bytes7":
107
+ case "bytes8":
108
+ case "bytes9":
109
+ case "bytes10":
110
+ case "bytes11":
111
+ case "bytes12":
112
+ case "bytes13":
113
+ case "bytes14":
114
+ case "bytes15":
115
+ case "bytes16":
116
+ case "bytes17":
117
+ case "bytes18":
118
+ case "bytes19":
119
+ case "bytes20":
120
+ case "bytes21":
121
+ case "bytes22":
122
+ case "bytes23":
123
+ case "bytes24":
124
+ case "bytes25":
125
+ case "bytes26":
126
+ case "bytes27":
127
+ case "bytes28":
128
+ case "bytes29":
129
+ case "bytes30":
130
+ case "bytes31":
131
+ case "bytes32": {
132
+ return data as TPrimitiveType;
133
+ }
134
+
135
+ case "bool": {
136
+ return hexToBool(data) as TPrimitiveType;
137
+ }
138
+
139
+ case "address": {
140
+ return getAddress(data) as TPrimitiveType;
141
+ }
142
+ }
143
+
144
+ return assertExhaustive(abiType, `Unsupported static ABI type: ${abiType}`);
145
+ }
@@ -0,0 +1,17 @@
1
+ import { SchemaAbiType, arrayAbiTypeToStaticAbiType, isArrayAbiType } from "@latticexyz/schema-type";
2
+ import { AbiParameterToPrimitiveType } from "abitype";
3
+ import { Hex, encodePacked } from "viem";
4
+
5
+ export function encodeField<TSchemaAbiType extends SchemaAbiType>(
6
+ fieldType: TSchemaAbiType,
7
+ value: AbiParameterToPrimitiveType<{ type: TSchemaAbiType }>
8
+ ): Hex {
9
+ if (isArrayAbiType(fieldType) && Array.isArray(value)) {
10
+ const staticFieldType = arrayAbiTypeToStaticAbiType(fieldType);
11
+ return encodePacked(
12
+ value.map(() => staticFieldType),
13
+ value
14
+ );
15
+ }
16
+ return encodePacked([fieldType], [value]);
17
+ }
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { encodeKeyTuple } from "./encodeKeyTuple";
3
+
4
+ describe("encodeKeyTuple", () => {
5
+ it("can encode bool key tuple", () => {
6
+ expect(encodeKeyTuple({ staticFields: ["bool"], dynamicFields: [] }, [false])).toStrictEqual([
7
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
8
+ ]);
9
+ expect(
10
+ encodeKeyTuple(
11
+ {
12
+ staticFields: ["bool"],
13
+ dynamicFields: [],
14
+ },
15
+ [true]
16
+ )
17
+ ).toStrictEqual(["0x0000000000000000000000000000000000000000000000000000000000000001"]);
18
+ });
19
+
20
+ it("can encode complex key tuple", () => {
21
+ expect(
22
+ encodeKeyTuple({ staticFields: ["uint256", "int32", "bytes16", "address", "bool", "int8"], dynamicFields: [] }, [
23
+ 42n,
24
+ -42,
25
+ "0x12340000000000000000000000000000",
26
+ "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF",
27
+ true,
28
+ 3,
29
+ ])
30
+ ).toStrictEqual([
31
+ "0x000000000000000000000000000000000000000000000000000000000000002a",
32
+ "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6",
33
+ "0x1234000000000000000000000000000000000000000000000000000000000000",
34
+ "0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff",
35
+ "0x0000000000000000000000000000000000000000000000000000000000000001",
36
+ "0x0000000000000000000000000000000000000000000000000000000000000003",
37
+ ]);
38
+ });
39
+ });
@@ -0,0 +1,7 @@
1
+ import { StaticPrimitiveType } from "@latticexyz/schema-type";
2
+ import { Hex, encodeAbiParameters } from "viem";
3
+ import { Schema } from "./common";
4
+
5
+ export function encodeKeyTuple(keySchema: Schema, keyTuple: StaticPrimitiveType[]): Hex[] {
6
+ return keyTuple.map((key, index) => encodeAbiParameters([{ type: keySchema.staticFields[index] }], [key]));
7
+ }
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { encodeRecord } from "./encodeRecord";
3
+
4
+ describe("encodeRecord", () => {
5
+ it("can encode a schema and record values to hex", () => {
6
+ const schema = { staticFields: ["uint32", "uint128"], dynamicFields: ["uint32[]", "string"] } as const;
7
+ const hex = encodeRecord(schema, [1, 2n, [3, 4], "some string"]);
8
+ expect(hex).toBe(
9
+ "0x0000000100000000000000000000000000000002000000000000130000000008000000000b0000000000000000000000000000000000000300000004736f6d6520737472696e67"
10
+ );
11
+ });
12
+
13
+ it("should not include the packed dynamic lengths if there are no dynamic fields", () => {
14
+ const schema = { staticFields: ["uint32", "uint128"], dynamicFields: [] } as const;
15
+ const hex = encodeRecord(schema, [1, 2n]);
16
+ expect(hex).toBe("0x0000000100000000000000000000000000000002");
17
+ });
18
+ });
@@ -0,0 +1,30 @@
1
+ import { StaticPrimitiveType, DynamicPrimitiveType } from "@latticexyz/schema-type";
2
+ import { Hex } from "viem";
3
+ import { encodeField } from "./encodeField";
4
+ import { Schema } from "./common";
5
+
6
+ export function encodeRecord(schema: Schema, values: readonly (StaticPrimitiveType | DynamicPrimitiveType)[]): Hex {
7
+ const staticValues = values.slice(0, schema.staticFields.length) as readonly StaticPrimitiveType[];
8
+ const dynamicValues = values.slice(schema.staticFields.length) as readonly DynamicPrimitiveType[];
9
+
10
+ const staticData = staticValues
11
+ .map((value, i) => encodeField(schema.staticFields[i], value).replace(/^0x/, ""))
12
+ .join("");
13
+
14
+ if (schema.dynamicFields.length === 0) return `0x${staticData}`;
15
+
16
+ const dynamicDataItems = dynamicValues.map((value, i) =>
17
+ encodeField(schema.dynamicFields[i], value).replace(/^0x/, "")
18
+ );
19
+
20
+ const dynamicFieldByteLengths = dynamicDataItems.map((value) => value.length / 2);
21
+ const dynamicTotalByteLength = dynamicFieldByteLengths.reduce((total, length) => total + BigInt(length), 0n);
22
+
23
+ const dynamicData = dynamicDataItems.join("");
24
+
25
+ const packedCounter = `${encodeField("uint56", dynamicTotalByteLength).replace(/^0x/, "")}${dynamicFieldByteLengths
26
+ .map((length) => encodeField("uint40", length).replace(/^0x/, ""))
27
+ .join("")}`.padEnd(64, "0");
28
+
29
+ return `0x${staticData}${packedCounter}${dynamicData}`;
30
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { Hex } from "viem";
2
+ import { MUDError } from "@latticexyz/common/errors";
3
+ import { StaticAbiType, staticAbiTypeToByteLength } from "@latticexyz/schema-type";
4
+
5
+ export class InvalidHexLengthError extends MUDError {
6
+ override name = "InvalidHexValueError";
7
+ constructor(value: Hex) {
8
+ super(`Hex value "${value}" is an odd length (${value.length - 2}). It must be an even length.`);
9
+ }
10
+ }
11
+
12
+ export class InvalidHexLengthForSchemaError extends MUDError {
13
+ override name = "InvalidHexLengthForSchemaError";
14
+ constructor(value: Hex) {
15
+ super(`Hex value "${value}" has length of ${value.length - 2}, but expected length of 64 for a schema.`);
16
+ }
17
+ }
18
+
19
+ export class InvalidHexLengthForPackedCounterError extends MUDError {
20
+ override name = "InvalidHexLengthForPackedCounterError";
21
+ constructor(value: Hex) {
22
+ super(`Hex value "${value}" has length of ${value.length - 2}, but expected length of 64 for a packed counter.`);
23
+ }
24
+ }
25
+
26
+ export class InvalidHexLengthForStaticFieldError extends MUDError {
27
+ override name = "InvalidHexLengthForStaticFieldError";
28
+ constructor(abiType: StaticAbiType, value: Hex) {
29
+ super(
30
+ `Hex value "${value}" has length of ${value.length - 2}, but expected length of ${
31
+ staticAbiTypeToByteLength[abiType] * 2
32
+ } for ${abiType} type.`
33
+ );
34
+ }
35
+ }
36
+
37
+ export class InvalidHexLengthForArrayFieldError extends MUDError {
38
+ override name = "InvalidHexLengthForArrayFieldError";
39
+ constructor(abiType: StaticAbiType, value: Hex) {
40
+ super(
41
+ `Hex value "${value}" has length of ${value.length - 2}, but expected a multiple of ${
42
+ staticAbiTypeToByteLength[abiType] * 2
43
+ } for ${abiType}[] type.`
44
+ );
45
+ }
46
+ }
47
+
48
+ export class SchemaStaticLengthMismatchError extends MUDError {
49
+ override name = "SchemaStaticLengthMismatchError";
50
+ constructor(schemaData: Hex, definedLength: number, summedLength: number) {
51
+ super(
52
+ `Schema "${schemaData}" static data length (${definedLength}) did not match the summed length of all static fields (${summedLength}). Is \`staticAbiTypeToByteLength\` up to date with Solidity schema types?`
53
+ );
54
+ }
55
+ }
56
+
57
+ export class PackedCounterLengthMismatchError extends MUDError {
58
+ override name = "PackedCounterLengthMismatchError";
59
+ constructor(packedCounterData: Hex, definedLength: bigint, summedLength: bigint) {
60
+ super(
61
+ `PackedCounter "${packedCounterData}" total bytes length (${definedLength}) did not match the summed length of all field byte lengths (${summedLength}).`
62
+ );
63
+ }
64
+ }
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { hexToPackedCounter } from "./hexToPackedCounter";
3
+
4
+ describe("hexToPackedCounter", () => {
5
+ it("decodes hex data to packed counter", () => {
6
+ expect(hexToPackedCounter("0x0000000000008000000000200000000020000000004000000000000000000000"))
7
+ .toMatchInlineSnapshot(`
8
+ {
9
+ "fieldByteLengths": [
10
+ 32,
11
+ 32,
12
+ 64,
13
+ 0,
14
+ 0,
15
+ ],
16
+ "totalByteLength": 128n,
17
+ }
18
+ `);
19
+ });
20
+
21
+ it("throws if schema hex data is not bytes32", () => {
22
+ expect(() => hexToPackedCounter("0x01234")).toThrowErrorMatchingInlineSnapshot(
23
+ '"Hex value \\"0x01234\\" has length of 5, but expected length of 64 for a packed counter."'
24
+ );
25
+ });
26
+
27
+ it("throws if packed counter total byte length doesn't match summed byte length of fields", () => {
28
+ expect(() =>
29
+ hexToPackedCounter("0x0000000000004000000000200000000020000000004000000000000000000000")
30
+ ).toThrowErrorMatchingInlineSnapshot(
31
+ '"PackedCounter \\"0x0000000000004000000000200000000020000000004000000000000000000000\\" total bytes length (64) did not match the summed length of all field byte lengths (128)."'
32
+ );
33
+ });
34
+ });
@@ -0,0 +1,31 @@
1
+ import { Hex, sliceHex } from "viem";
2
+ import { decodeStaticField } from "./decodeStaticField";
3
+ import { decodeDynamicField } from "./decodeDynamicField";
4
+ import { InvalidHexLengthForPackedCounterError, PackedCounterLengthMismatchError } from "./errors";
5
+
6
+ // Keep this logic in sync with PackedCounter.sol
7
+
8
+ // - First 7 bytes (uint56) are used for the total byte length of the dynamic data
9
+ // - The next 5 byte (uint40) sections are used for the byte length of each field, in the same order as the schema's dynamic fields
10
+
11
+ // We use byte lengths rather than item counts so that, on chain, we can slice without having to get the schema first (and thus the field lengths of each dynamic type)
12
+
13
+ export function hexToPackedCounter(data: Hex): {
14
+ totalByteLength: bigint;
15
+ fieldByteLengths: readonly number[];
16
+ } {
17
+ if (data.length !== 66) {
18
+ throw new InvalidHexLengthForPackedCounterError(data);
19
+ }
20
+ const totalByteLength = decodeStaticField("uint56", sliceHex(data, 0, 7));
21
+
22
+ // TODO: use schema to make sure we only parse as many as we need (rather than zeroes at the end)?
23
+ const fieldByteLengths = decodeDynamicField("uint40[]", sliceHex(data, 7));
24
+
25
+ const summedLength = BigInt(fieldByteLengths.reduce((total, length) => total + length, 0));
26
+ if (summedLength !== totalByteLength) {
27
+ throw new PackedCounterLengthMismatchError(data, totalByteLength, summedLength);
28
+ }
29
+
30
+ return { totalByteLength, fieldByteLengths };
31
+ }