diff --git a/packages/cli/src/deploy/ensureTables.ts b/packages/cli/src/deploy/ensureTables.ts index 6f2deac46b..c8ef52e2e2 100644 --- a/packages/cli/src/deploy/ensureTables.ts +++ b/packages/cli/src/deploy/ensureTables.ts @@ -8,12 +8,12 @@ import { getSchemaTypes, getValueSchema, getKeySchema, - KeySchema, } from "@latticexyz/protocol-parser/internal"; import { debug } from "./debug"; import { getTables } from "./getTables"; import pRetry from "p-retry"; import { Table } from "@latticexyz/config"; +import { isDefined } from "@latticexyz/common/utils"; export async function ensureTables({ client, @@ -24,15 +24,56 @@ export async function ensureTables({ readonly worldDeploy: WorldDeploy; readonly tables: readonly Table[]; }): Promise { - const worldTables = await getTables({ client, worldDeploy }); - const worldTableIds = worldTables.map((table) => table.tableId); + const configTables = new Map( + tables.map((table) => { + const keySchema = getSchemaTypes(getKeySchema(table)); + const valueSchema = getSchemaTypes(getValueSchema(table)); + const keySchemaHex = keySchemaToHex(keySchema); + const valueSchemaHex = valueSchemaToHex(valueSchema); + return [ + table.tableId, + { + ...table, + keySchema, + keySchemaHex, + valueSchema, + valueSchemaHex, + }, + ]; + }), + ); - const existingTables = tables.filter((table) => worldTableIds.includes(table.tableId)); + const worldTables = await getTables({ client, worldDeploy }); + const existingTables = worldTables.filter(({ tableId }) => configTables.has(tableId)); if (existingTables.length) { debug("existing tables:", existingTables.map(resourceToLabel).join(", ")); + + const schemaErrors = existingTables + .map((table) => { + const configTable = configTables.get(table.tableId)!; + if (table.keySchemaHex !== configTable.keySchemaHex || table.valueSchemaHex !== configTable.valueSchemaHex) { + return [ + `"${resourceToLabel(table)}" table:`, + ` Registered schema: ${JSON.stringify({ schema: getSchemaTypes(table.schema), key: table.key })}`, + ` Config schema: ${JSON.stringify({ schema: getSchemaTypes(configTable.schema), key: configTable.key })}`, + ].join("\n"); + } + }) + .filter(isDefined); + + if (schemaErrors.length) { + throw new Error( + [ + "Table schemas are immutable, but found registered tables with a different schema than what you have configured.", + ...schemaErrors, + "You can either update your config with the registered schema or change the table name to register a new table.", + ].join("\n\n") + "\n", + ); + } } - const missingTables = tables.filter((table) => !worldTableIds.includes(table.tableId)); + const existingTableIds = new Set(existingTables.map(({ tableId }) => tableId)); + const missingTables = tables.filter((table) => !existingTableIds.has(table.tableId)); if (missingTables.length) { debug("registering tables:", missingTables.map(resourceToLabel).join(", ")); return await Promise.all( @@ -50,7 +91,7 @@ export async function ensureTables({ args: [ table.tableId, valueSchemaToFieldLayoutHex(valueSchema), - keySchemaToHex(keySchema as KeySchema), + keySchemaToHex(keySchema), valueSchemaToHex(valueSchema), Object.keys(keySchema), Object.keys(valueSchema), diff --git a/packages/cli/src/deploy/getTables.ts b/packages/cli/src/deploy/getTables.ts index 196b24421e..54b0b717cf 100644 --- a/packages/cli/src/deploy/getTables.ts +++ b/packages/cli/src/deploy/getTables.ts @@ -1,4 +1,4 @@ -import { Client, decodeAbiParameters, parseAbiParameters } from "viem"; +import { Client, Hex, decodeAbiParameters, parseAbiParameters } from "viem"; import { hexToResource } from "@latticexyz/common"; import { WorldDeploy } from "./common"; import { debug } from "./debug"; @@ -16,7 +16,12 @@ import { fetchBlockLogs } from "@latticexyz/block-logs-stream"; import { flattenStoreLogs, getStoreLogs } from "@latticexyz/store/internal"; // TODO: add label and namespaceLabel once we register it onchain -type DeployedTable = Omit; +type DeployedTable = Omit & { + readonly keySchema: Schema; + readonly keySchemaHex: Hex; + readonly valueSchema: Schema; + readonly valueSchemaHex: Hex; +}; export async function getTables({ client, @@ -49,12 +54,17 @@ export async function getTables({ log.args.keyTuple, ); const { type, namespace, name } = hexToResource(tableId); - const value = decodeValueArgs(getSchemaTypes(getValueSchema(storeConfig.namespaces.store.tables.Tables)), log.args); + const recordValue = decodeValueArgs( + getSchemaTypes(getValueSchema(storeConfig.namespaces.store.tables.Tables)), + log.args, + ); - const solidityKeySchema = hexToSchema(value.keySchema); - const solidityValueSchema = hexToSchema(value.valueSchema); - const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedKeyNames)[0]; - const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedFieldNames)[0]; + const keySchemaHex = recordValue.keySchema; + const valueSchemaHex = recordValue.valueSchema; + const solidityKeySchema = hexToSchema(keySchemaHex); + const solidityValueSchema = hexToSchema(valueSchemaHex); + const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), recordValue.abiEncodedKeyNames)[0]; + const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), recordValue.abiEncodedFieldNames)[0]; const valueAbiTypes = [...solidityValueSchema.staticFields, ...solidityValueSchema.dynamicFields]; @@ -73,6 +83,10 @@ export async function getTables({ tableId, schema: { ...keySchema, ...valueSchema }, key: Object.keys(keySchema), + keySchema, + keySchemaHex, + valueSchema, + valueSchemaHex, }; }); diff --git a/packages/protocol-parser/src/getKeySchema.ts b/packages/protocol-parser/src/getKeySchema.ts index 3d6ed45efe..7c035cb729 100644 --- a/packages/protocol-parser/src/getKeySchema.ts +++ b/packages/protocol-parser/src/getKeySchema.ts @@ -15,7 +15,7 @@ export type getKeySchema = PartialTable extends tabl ? ResolvedKeySchema : { readonly [fieldName in Extract]: table["schema"][fieldName] & { - type: StaticAbiType; + readonly type: StaticAbiType; }; };