diff --git a/.changeset/tender-badgers-ring.md b/.changeset/tender-badgers-ring.md new file mode 100644 index 0000000000..1115a5ab94 --- /dev/null +++ b/.changeset/tender-badgers-ring.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/common": patch +--- + +`resourceToHex` will now throw if provided namespace is >14 characters. Since namespaces are used to determine access control, it's not safe to automatically truncate to fit into `bytes14` as that may change the indended namespace for resource access. diff --git a/packages/common/src/resourceToHex.test.ts b/packages/common/src/resourceToHex.test.ts index 77f82251dc..ceae742ebe 100644 --- a/packages/common/src/resourceToHex.test.ts +++ b/packages/common/src/resourceToHex.test.ts @@ -37,21 +37,16 @@ describe("resourceToHex", () => { `); }); - it("truncates namespaces >14 bytes", () => { - const hex = resourceToHex({ - type: "table", - namespace: "AVeryLongNamespace", - name: "name", - }); - expect(hex).toMatchInlineSnapshot('"0x746241566572794c6f6e674e616d65736e616d65000000000000000000000000"'); - expect(hexToResource(hex)).toMatchInlineSnapshot(` - { - "name": "name", - "namespace": "AVeryLongNames", - "resourceId": "0x746241566572794c6f6e674e616d65736e616d65000000000000000000000000", - "type": "table", - } - `); + it("throws for namespaces >14 bytes", () => { + expect(() => + resourceToHex({ + type: "table", + namespace: "AVeryLongNamespace", + name: "name", + }), + ).toThrowErrorMatchingInlineSnapshot( + '"Namespaces must fit into `bytes14`, but \\"AVeryLongNamespace\\" is too long."', + ); }); it("truncates names >16 bytes", () => { diff --git a/packages/common/src/resourceToHex.ts b/packages/common/src/resourceToHex.ts index 39ecd904d9..ec8153c2ad 100644 --- a/packages/common/src/resourceToHex.ts +++ b/packages/common/src/resourceToHex.ts @@ -15,9 +15,13 @@ export const resourceTypeIds = { export function resourceToHex(resource: Omit): Hex { const typeId = resourceTypeIds[resource.type]; + // Because namespaces are tied to access control, it's not safe to automatically truncate. Instead, we'll throw an error. + if (resource.namespace.length > 14) { + throw new Error(`Namespaces must fit into \`bytes14\`, but "${resource.namespace}" is too long.`); + } return concatHex([ stringToHex(typeId, { size: 2 }), - stringToHex(resource.namespace.slice(0, 14), { size: 14 }), + stringToHex(resource.namespace, { size: 14 }), stringToHex(resource.name.slice(0, 16), { size: 16 }), ]); } diff --git a/packages/store/ts/config/v2/store.test.ts b/packages/store/ts/config/v2/store.test.ts index ade2f1809d..d41466e84d 100644 --- a/packages/store/ts/config/v2/store.test.ts +++ b/packages/store/ts/config/v2/store.test.ts @@ -484,7 +484,7 @@ describe("defineStore", () => { it("should throw if name is overridden in the store context", () => { attest(() => defineStore({ - namespace: "CustomNamespace", + namespace: "CustomNS", tables: { Example: { schema: { id: "address" }, @@ -500,7 +500,7 @@ describe("defineStore", () => { it("should throw if namespace is overridden in the store context", () => { attest(() => defineStore({ - namespace: "CustomNamespace", + namespace: "CustomNS", tables: { Example: { schema: { id: "address" }, diff --git a/packages/world/ts/config/v2/world.test.ts b/packages/world/ts/config/v2/world.test.ts index ccc069c855..3cfc166464 100644 --- a/packages/world/ts/config/v2/world.test.ts +++ b/packages/world/ts/config/v2/world.test.ts @@ -23,7 +23,7 @@ describe("defineWorld", () => { const config = defineWorld({ // @ts-expect-error TODO: remove once namespaces support ships namespaces: { - ExampleNamespace: { + ExampleNS: { tables: { ExampleTable: { schema: { @@ -42,8 +42,8 @@ describe("defineWorld", () => { ...CONFIG_DEFAULTS, codegen: CODEGEN_DEFAULTS, tables: { - ExampleNamespace__ExampleTable: { - tableId: resourceToHex({ type: "table", namespace: "ExampleNamespace", name: "ExampleTable" }), + ExampleNS__ExampleTable: { + tableId: resourceToHex({ type: "table", namespace: "ExampleNS", name: "ExampleTable" }), schema: { id: { type: "address", @@ -60,7 +60,7 @@ describe("defineWorld", () => { }, key: ["id"], name: "ExampleTable", - namespace: "ExampleNamespace", + namespace: "ExampleNS", codegen: { ...TABLE_CODEGEN_DEFAULTS, dataStruct: true as boolean }, type: "table", deploy: TABLE_DEPLOY_DEFAULTS, @@ -79,7 +79,7 @@ describe("defineWorld", () => { const config = defineWorld({ // @ts-expect-error TODO: remove once namespaces support ships namespaces: { - ExampleNamespace: { + ExampleNS: { tables: { ExampleTable: { schema: { @@ -105,8 +105,8 @@ describe("defineWorld", () => { ...CONFIG_DEFAULTS, codegen: CODEGEN_DEFAULTS, tables: { - ExampleNamespace__ExampleTable: { - tableId: resourceToHex({ type: "table", namespace: "ExampleNamespace", name: "ExampleTable" }), + ExampleNS__ExampleTable: { + tableId: resourceToHex({ type: "table", namespace: "ExampleNS", name: "ExampleTable" }), schema: { id: { type: "address", @@ -123,7 +123,7 @@ describe("defineWorld", () => { }, key: ["id"], name: "ExampleTable", - namespace: "ExampleNamespace", + namespace: "ExampleNS", codegen: { ...TABLE_CODEGEN_DEFAULTS, dataStruct: true as boolean }, type: "table", deploy: TABLE_DEPLOY_DEFAULTS, @@ -152,7 +152,7 @@ describe("defineWorld", () => { const config = defineWorld({ // @ts-expect-error TODO: remove once namespaces support ships namespaces: { - ExampleNamespace: { + ExampleNS: { tables: { ExampleTable: { schema: { @@ -663,7 +663,7 @@ describe("defineWorld", () => { it("should use the custom name and namespace as table index", () => { const config = defineWorld({ - namespace: "CustomNamespace", + namespace: "CustomNS", tables: { Example: { schema: { id: "address" }, @@ -672,13 +672,13 @@ describe("defineWorld", () => { }, }); - attest<"CustomNamespace__Example", keyof typeof config.tables>(); + attest<"CustomNS__Example", keyof typeof config.tables>(); }); it("should throw if namespace is overridden in top level tables", () => { attest(() => defineWorld({ - namespace: "CustomNamespace", + namespace: "CustomNS", tables: { Example: { schema: { id: "address" }, diff --git a/packages/world/ts/config/v2/worldWithShorthands.test.ts b/packages/world/ts/config/v2/worldWithShorthands.test.ts index 15078b0b8d..d901b43fc9 100644 --- a/packages/world/ts/config/v2/worldWithShorthands.test.ts +++ b/packages/world/ts/config/v2/worldWithShorthands.test.ts @@ -17,7 +17,7 @@ describe("defineWorldWithShorthands", () => { it("should resolve namespaced shorthand table config with user types and enums", () => { const config = defineWorldWithShorthands({ namespaces: { - ExampleNamespace: { + ExampleNS: { tables: { ExampleTable: "Static", }, @@ -36,8 +36,8 @@ describe("defineWorldWithShorthands", () => { ...CONFIG_DEFAULTS, codegen: CODEGEN_DEFAULTS, tables: { - ExampleNamespace__ExampleTable: { - tableId: resourceToHex({ type: "table", namespace: "ExampleNamespace", name: "ExampleTable" }), + ExampleNS__ExampleTable: { + tableId: resourceToHex({ type: "table", namespace: "ExampleNS", name: "ExampleTable" }), schema: { id: { type: "bytes32", @@ -50,7 +50,7 @@ describe("defineWorldWithShorthands", () => { }, key: ["id"], name: "ExampleTable", - namespace: "ExampleNamespace", + namespace: "ExampleNS", codegen: { ...TABLE_CODEGEN_DEFAULTS, dataStruct: false as boolean }, type: "table", deploy: TABLE_DEPLOY_DEFAULTS, @@ -78,7 +78,7 @@ describe("defineWorldWithShorthands", () => { it("should resolve namespaced shorthand schema table config with user types and enums", () => { const config = defineWorldWithShorthands({ namespaces: { - ExampleNamespace: { + ExampleNS: { tables: { ExampleTable: { id: "Static", @@ -101,8 +101,8 @@ describe("defineWorldWithShorthands", () => { ...CONFIG_DEFAULTS, codegen: CODEGEN_DEFAULTS, tables: { - ExampleNamespace__ExampleTable: { - tableId: resourceToHex({ type: "table", namespace: "ExampleNamespace", name: "ExampleTable" }), + ExampleNS__ExampleTable: { + tableId: resourceToHex({ type: "table", namespace: "ExampleNS", name: "ExampleTable" }), schema: { id: { type: "address", @@ -119,7 +119,7 @@ describe("defineWorldWithShorthands", () => { }, key: ["id"], name: "ExampleTable", - namespace: "ExampleNamespace", + namespace: "ExampleNS", codegen: { ...TABLE_CODEGEN_DEFAULTS, dataStruct: true as boolean }, type: "table", deploy: TABLE_DEPLOY_DEFAULTS, @@ -358,7 +358,7 @@ describe("defineWorldWithShorthands", () => { attest(() => defineWorldWithShorthands({ namespaces: { - ExampleNamespace: { + ExampleNS: { tables: { // @ts-expect-error Type '"number"' is not assignable to type 'AbiType'. ExampleTable: "number", @@ -373,7 +373,7 @@ describe("defineWorldWithShorthands", () => { attest(() => defineWorldWithShorthands({ namespaces: { - ExampleNamespace: { + ExampleNS: { // @ts-expect-error Type 'true' is not assignable to type '"`invalidProperty` is not a valid namespace config option. invalidProperty: true, },