Skip to content

Commit

Permalink
fix(protocol-parser): allow arbitrary key order when encoding values (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored Oct 3, 2023
1 parent 12a9eb1 commit a2f41ad
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/wicked-cheetahs-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/protocol-parser": patch
---

Allow arbitrary key order when encoding values
6 changes: 6 additions & 0 deletions packages/protocol-parser/src/encodeLengths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import { describe, expect, it } from "vitest";
import { encodeLengths } from "./encodeLengths";

describe("encodeLengths", () => {
it("can encode empty tuple", () => {
expect(encodeLengths([])).toMatchInlineSnapshot(
'"0x0000000000000000000000000000000000000000000000000000000000000000"'
);
});

it("can encode bool key tuple", () => {
expect(encodeLengths(["0x1234", "0x12345678"])).toMatchInlineSnapshot(
'"0x0000000000000000000000000000000000000004000000000200000000000006"'
Expand Down
53 changes: 53 additions & 0 deletions packages/protocol-parser/src/encodeValueArgs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import { encodeValueArgs } from "./encodeValueArgs";
import { stringToHex } from "viem";

describe("encodeValueArgs", () => {
it("can encode record value to hex", () => {
const valueSchema = {
entityId: "bytes32",
exists: "bool",
playerName: "string",
badges: "uint256[]",
} as const;

const result = encodeValueArgs(valueSchema, {
entityId: stringToHex("hello", { size: 32 }),
exists: true,
playerName: "henry",
badges: [42n],
});

expect(result).toMatchInlineSnapshot(`
{
"dynamicData": "0x68656e7279000000000000000000000000000000000000000000000000000000000000002a",
"encodedLengths": "0x0000000000000000000000000000000000000020000000000500000000000025",
"staticData": "0x68656c6c6f00000000000000000000000000000000000000000000000000000001",
}
`);
});

it("encodes record when key order of value and valueSchema do not match", () => {
const valueSchema = {
entityId: "bytes32",
playerName: "string",
exists: "bool",
badges: "uint256[]",
} as const;

const result = encodeValueArgs(valueSchema, {
exists: true,
playerName: "henry",
entityId: stringToHex("hello", { size: 32 }),
badges: [42n],
});

expect(result).toMatchInlineSnapshot(`
{
"dynamicData": "0x68656e7279000000000000000000000000000000000000000000000000000000000000002a",
"encodedLengths": "0x0000000000000000000000000000000000000020000000000500000000000025",
"staticData": "0x68656c6c6f00000000000000000000000000000000000000000000000000000001",
}
`);
});
});
25 changes: 17 additions & 8 deletions packages/protocol-parser/src/encodeValueArgs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { StaticPrimitiveType, DynamicPrimitiveType, isStaticAbiType, isDynamicAbiType } from "@latticexyz/schema-type";
import {
StaticPrimitiveType,
DynamicPrimitiveType,
isStaticAbiType,
isDynamicAbiType,
StaticAbiType,
DynamicAbiType,
} from "@latticexyz/schema-type";
import { concatHex } from "viem";
import { encodeField } from "./encodeField";
import { SchemaToPrimitives, ValueArgs, ValueSchema } from "./common";
Expand All @@ -8,15 +15,17 @@ export function encodeValueArgs<TSchema extends ValueSchema>(
valueSchema: TSchema,
value: SchemaToPrimitives<TSchema>
): ValueArgs {
const staticFields = Object.values(valueSchema).filter(isStaticAbiType);
const dynamicFields = Object.values(valueSchema).filter(isDynamicAbiType);
const valueSchemaEntries = Object.entries(valueSchema);
const staticFields = valueSchemaEntries.filter(([, type]) => isStaticAbiType(type)) as [string, StaticAbiType][];
const dynamicFields = valueSchemaEntries.filter(([, type]) => isDynamicAbiType(type)) as [string, DynamicAbiType][];
// TODO: validate <=5 dynamic fields
// TODO: validate <=28 total fields

const values = Object.values(value);
const staticValues = values.slice(0, staticFields.length) as readonly StaticPrimitiveType[];
const dynamicValues = values.slice(staticFields.length) as readonly DynamicPrimitiveType[];
const encodedStaticValues = staticFields.map(([name, type]) => encodeField(type, value[name] as StaticPrimitiveType));
const encodedDynamicValues = dynamicFields.map(([name, type]) =>
encodeField(type, value[name] as DynamicPrimitiveType)
);

const encodedStaticValues = staticValues.map((value, i) => encodeField(staticFields[i], value));
const encodedDynamicValues = dynamicValues.map((value, i) => encodeField(dynamicFields[i], value));
const encodedLengths = encodeLengths(encodedDynamicValues);

return {
Expand Down

1 comment on commit a2f41ad

@vercel
Copy link

@vercel vercel bot commented on a2f41ad Oct 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.