Skip to content

Commit

Permalink
refactor(cli): register labels as tags (#3063)
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored Aug 27, 2024
1 parent 69cd0a1 commit e583fc9
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 147 deletions.
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"test:ci": "pnpm run test"
},
"dependencies": {
"@ark/util": "catalog:",
"@aws-sdk/client-kms": "^3.556.0",
"@latticexyz/abi-ts": "workspace:*",
"@latticexyz/common": "workspace:*",
Expand Down
20 changes: 10 additions & 10 deletions packages/cli/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Account, Address, Chain, Client, Hex, Transport } from "viem";
import { Account, Address, Chain, Client, Hex, Transport, stringToHex } from "viem";
import { ensureDeployer } from "./ensureDeployer";
import { deployWorld } from "./deployWorld";
import { ensureTables } from "./ensureTables";
Expand All @@ -15,7 +15,7 @@ import { ensureContractsDeployed } from "./ensureContractsDeployed";
import { randomBytes } from "crypto";
import { ensureWorldFactory } from "./ensureWorldFactory";
import { Table } from "@latticexyz/config";
import { ensureResourceLabels } from "./ensureResourceLabels";
import { ensureResourceTags } from "./ensureResourceTags";

type DeployOptions = {
client: Client<Transport, Chain | undefined, Account>;
Expand Down Expand Up @@ -90,14 +90,10 @@ export async function deploy({
throw new Error(`Unsupported World version: ${worldDeploy.worldVersion}`);
}

const resources = [
...tables.map((table) => ({ resourceId: table.tableId, label: table.label })),
...systems.map((system) => ({ resourceId: system.systemId, label: system.label })),
];
const namespaceTxs = await ensureNamespaceOwner({
client,
worldDeploy,
resourceIds: resources.map(({ resourceId }) => resourceId),
resourceIds: [...tables.map(({ tableId }) => tableId), ...systems.map(({ systemId }) => systemId)],
});

debug("waiting for all namespace registration transactions to confirm");
Expand Down Expand Up @@ -129,13 +125,17 @@ export async function deploy({
worldDeploy,
modules,
});
const labelTxs = await ensureResourceLabels({

const tableTags = tables.map(({ tableId: resourceId, label }) => ({ resourceId, tag: "label", value: label }));

const tagTxs = await ensureResourceTags({
client,
worldDeploy,
resources,
tags: tableTags,
valueToHex: stringToHex,
});

const txs = [...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs, ...labelTxs];
const txs = [...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs, ...tagTxs];

// wait for each tx separately/serially, because parallelizing results in RPC errors
debug("waiting for all transactions to confirm");
Expand Down
66 changes: 0 additions & 66 deletions packages/cli/src/deploy/ensureResourceLabels.ts

This file was deleted.

70 changes: 70 additions & 0 deletions packages/cli/src/deploy/ensureResourceTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Hex, Client, Transport, Chain, Account, stringToHex, BaseError } from "viem";
import { WorldDeploy } from "./common";
import { debug } from "./debug";
import { hexToResource, writeContract } from "@latticexyz/common";
import { identity, isDefined } from "@latticexyz/common/utils";
import metadataConfig from "@latticexyz/world-module-metadata/mud.config";
import metadataAbi from "@latticexyz/world-module-metadata/out/IMetadataSystem.sol/IMetadataSystem.abi.json" assert { type: "json" };
import { getRecord } from "./getRecord";

export type ResourceTag<value> = {
resourceId: Hex;
tag: string;
value: value;
};

export async function ensureResourceTags<const value>({
client,
worldDeploy,
tags,
valueToHex = identity,
}: {
readonly client: Client<Transport, Chain | undefined, Account>;
readonly worldDeploy: WorldDeploy;
readonly tags: readonly ResourceTag<value>[];
} & (value extends Hex
? { readonly valueToHex?: (value: value) => Hex }
: { readonly valueToHex: (value: value) => Hex })): Promise<readonly Hex[]> {
const pendingTags = (
await Promise.all(
tags.map(async (tag) => {
const { value } = await getRecord({
client,
worldDeploy,
table: metadataConfig.tables.metadata__ResourceTag,
key: { resource: tag.resourceId, tag: stringToHex(tag.tag, { size: 32 }) },
});
if (value === valueToHex(tag.value)) return;
return tag;
}),
)
).filter(isDefined);

if (pendingTags.length === 0) return [];

debug("setting", pendingTags.length, "resource tags");
return (
await Promise.all(
pendingTags.map(async (tag) => {
const resource = hexToResource(tag.resourceId);
// TODO: move to resourceToDebugString
const resourceString = `${resource.type}:${resource.namespace}:${resource.name}`;
debug(`tagging ${resourceString} with ${tag.tag}: ${JSON.stringify(tag.value)}`);
try {
return await writeContract(client, {
chain: client.chain ?? null,
address: worldDeploy.address,
abi: metadataAbi,
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
functionName: "metadata__setResourceTag",
args: [tag.resourceId, stringToHex(tag.tag, { size: 32 }), valueToHex(tag.value)],
});
} catch (error) {
debug(
`failed to set resource tag for ${resourceString}, skipping\n ${error instanceof BaseError ? error.shortMessage : error}`,
);
}
}),
)
).filter(isDefined);
}
46 changes: 46 additions & 0 deletions packages/cli/src/deploy/getRecord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
decodeValueArgs,
getKeySchema,
getKeyTuple,
getSchemaPrimitives,
getSchemaTypes,
getValueSchema,
} from "@latticexyz/protocol-parser/internal";
import { WorldDeploy, worldAbi } from "./common";
import { Client, Hex } from "viem";
import { readContract } from "viem/actions";
import { Table } from "@latticexyz/config";
import { mapObject } from "@latticexyz/common/utils";
import { show } from "@ark/util";

export async function getRecord<table extends Table>({
client,
worldDeploy,
table,
key,
}: {
readonly client: Client;
readonly worldDeploy: WorldDeploy;
readonly table: table;
readonly key: getSchemaPrimitives<getKeySchema<table>>;
}): Promise<show<getSchemaPrimitives<table["schema"]>>> {
const [staticData, encodedLengths, dynamicData] = (await readContract(client, {
blockNumber: worldDeploy.stateBlock,
address: worldDeploy.address,
abi: worldAbi,
functionName: "getRecord",
args: [table.tableId, getKeyTuple(table, key)],
// TODO: remove cast once https://github.com/wevm/viem/issues/2125 is resolved
// has something to do function overloads and TS having a hard time inferring which args to use
})) as [Hex, Hex, Hex];
const record = {
...key,
...decodeValueArgs(getSchemaTypes(getValueSchema(table)), {
staticData,
encodedLengths,
dynamicData,
}),
};
// return in schema order
return mapObject(table.schema, (value, key) => record[key as never]) as never;
}
8 changes: 4 additions & 4 deletions packages/cli/src/runDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export async function runDeploy(opts: DeployOptions): Promise<WorldDeploy> {
});
const modules = await configToModules(config, outDir);

const tables = Object.values(config.namespaces)
.flatMap((namespace) => Object.values(namespace.tables))
.filter((table) => !table.deploy.disabled);

const account = await (async () => {
if (opts.kms) {
const keyId = process.env.AWS_KMS_KEY_ID;
Expand Down Expand Up @@ -129,10 +133,6 @@ export async function runDeploy(opts: DeployOptions): Promise<WorldDeploy> {

console.log("Deploying from", client.account.address);

const tables = Object.values(config.namespaces)
.flatMap((namespace) => Object.values(namespace.tables))
.filter((table) => !table.deploy.disabled);

const startTime = Date.now();
const worldDeploy = await deploy({
deployerAddress: opts.deployerAddress as Hex | undefined,
Expand Down
1 change: 1 addition & 0 deletions packages/protocol-parser/src/exports/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export * from "../valueSchemaToHex";

export * from "../getFieldIndex";
export * from "../getKeySchema";
export * from "../getKeyTuple";
export * from "../getValueSchema";
export * from "../getSchemaTypes";
export * from "../getSchemaPrimitives";
53 changes: 53 additions & 0 deletions packages/protocol-parser/src/getKeyTuple.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import { getKeyTuple } from "./getKeyTuple";
import { Table } from "@latticexyz/config";

type PartialTable = Pick<Table, "schema" | "key">;

describe("getKeyTuple", () => {
it("can encode bool key tuple", () => {
const table = {
schema: { id: { type: "bool", internalType: "bool" } },
key: ["id"],
} as const satisfies PartialTable;

expect(getKeyTuple(table, { id: false })).toStrictEqual([
"0x0000000000000000000000000000000000000000000000000000000000000000",
]);
expect(getKeyTuple(table, { id: true })).toStrictEqual([
"0x0000000000000000000000000000000000000000000000000000000000000001",
]);
});

it("can encode complex key tuple", () => {
const table = {
schema: {
a: { type: "uint256", internalType: "uint256" },
b: { type: "int32", internalType: "int32" },
c: { type: "bytes16", internalType: "bytes16" },
d: { type: "address", internalType: "address" },
e: { type: "bool", internalType: "bool" },
f: { type: "int8", internalType: "int8" },
},
key: ["a", "b", "c", "d", "e", "f"],
} as const satisfies PartialTable;

expect(
getKeyTuple(table, {
a: 42n,
b: -42,
c: "0x12340000000000000000000000000000",
d: "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF",
e: true,
f: 3,
}),
).toStrictEqual([
"0x000000000000000000000000000000000000000000000000000000000000002a",
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6",
"0x1234000000000000000000000000000000000000000000000000000000000000",
"0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff",
"0x0000000000000000000000000000000000000000000000000000000000000001",
"0x0000000000000000000000000000000000000000000000000000000000000003",
]);
});
});
19 changes: 19 additions & 0 deletions packages/protocol-parser/src/getKeyTuple.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Hex, encodeAbiParameters } from "viem";
import { getSchemaPrimitives } from "./getSchemaPrimitives";
import { getKeySchema } from "./getKeySchema";
import { Table } from "@latticexyz/config";

type PartialTable = Pick<Table, "schema" | "key">;

export type getKeyTuple<table extends PartialTable, key extends readonly unknown[] = table["key"]> = {
[i in keyof key]: Hex;
};

export function getKeyTuple<const table extends PartialTable>(
table: table,
key: getSchemaPrimitives<getKeySchema<table>>,
): getKeyTuple<table> {
return table.key.map((fieldName) =>
encodeAbiParameters([table.schema[fieldName]], [key[fieldName as never]]),
) as never;
}
Loading

0 comments on commit e583fc9

Please sign in to comment.