Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(cli): throw error on schema changes #3336

Merged
merged 2 commits into from
Oct 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nice-ligers-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/cli": patch
---

Deployer will now throw an error if it detects an already registered table with a different schema than the one you are trying to deploy.
53 changes: 47 additions & 6 deletions packages/cli/src/deploy/ensureTables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,15 +24,56 @@ export async function ensureTables({
readonly worldDeploy: WorldDeploy;
readonly tables: readonly Table[];
}): Promise<readonly Hex[]> {
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");
Comment on lines +55 to +59
Copy link
Member

Choose a reason for hiding this comment

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

oh that's a nice approach for formatted multi line strings

}
})
.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(
Expand 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),
Expand Down
28 changes: 21 additions & 7 deletions packages/cli/src/deploy/getTables.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<Table, "label" | "namespaceLabel">;
type DeployedTable = Omit<Table, "label" | "namespaceLabel"> & {
readonly keySchema: Schema;
readonly keySchemaHex: Hex;
readonly valueSchema: Schema;
readonly valueSchemaHex: Hex;
};

export async function getTables({
client,
Expand Down Expand Up @@ -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];

Expand All @@ -73,6 +83,10 @@ export async function getTables({
tableId,
schema: { ...keySchema, ...valueSchema },
key: Object.keys(keySchema),
keySchema,
keySchemaHex,
valueSchema,
valueSchemaHex,
};
});

Expand Down
2 changes: 1 addition & 1 deletion packages/protocol-parser/src/getKeySchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type getKeySchema<table extends PartialTable> = PartialTable extends tabl
? ResolvedKeySchema
: {
readonly [fieldName in Extract<keyof table["schema"], table["key"][number]>]: table["schema"][fieldName] & {
type: StaticAbiType;
readonly type: StaticAbiType;
};
};

Expand Down
Loading