Skip to content

Commit

Permalink
fix(cli): support enums in deploy, only deploy modules/systems once (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored Oct 12, 2023
1 parent 7ce82b6 commit 4fe0793
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 29 deletions.
9 changes: 9 additions & 0 deletions .changeset/red-sheep-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@latticexyz/cli": patch
---

Fixed a few issues with deploys:

- properly handle enums in MUD config
- only deploy each unique module/system once
- waits for transactions serially instead of in parallel, to avoid RPC errors
28 changes: 18 additions & 10 deletions packages/cli/src/deploy/configToTables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { Hex } from "viem";

// TODO: we shouldn't need this file once our config parsing returns nicely formed tables

type UserTypes<config extends StoreConfig = StoreConfig> = config["userTypes"];
// TODO: fix strong enum types and avoid every schema getting `{ [k: string]: "uint8" }`
// type UserTypes<config extends StoreConfig = StoreConfig> = config["userTypes"] & {
// [k in keyof config["enums"]]: { internalType: "uint8" };
// };

export type TableKey<
config extends StoreConfig = StoreConfig,
table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]]
Expand All @@ -18,19 +24,17 @@ export type Table<
readonly namespace: config["namespace"];
readonly name: table["name"];
readonly tableId: Hex;
// TODO: support enums
readonly keySchema: table["keySchema"] extends KeySchema<config["userTypes"]>
readonly keySchema: table["keySchema"] extends KeySchema<UserTypes<config>>
? KeySchema & {
readonly [k in keyof table["keySchema"]]: config["userTypes"][table["keySchema"][k]]["internalType"] extends StaticAbiType
? config["userTypes"][table["keySchema"][k]]["internalType"]
readonly [k in keyof table["keySchema"]]: UserTypes<config>[table["keySchema"][k]]["internalType"] extends StaticAbiType
? UserTypes<config>[table["keySchema"][k]]["internalType"]
: table["keySchema"][k];
}
: KeySchema;
// TODO: support enums
readonly valueSchema: table["valueSchema"] extends ValueSchema<config["userTypes"]>
readonly valueSchema: table["valueSchema"] extends ValueSchema<UserTypes<config>>
? {
readonly [k in keyof table["valueSchema"]]: config["userTypes"][table["valueSchema"][k]]["internalType"] extends SchemaAbiType
? config["userTypes"][table["valueSchema"][k]]["internalType"]
readonly [k in keyof table["valueSchema"]]: UserTypes<config>[table["valueSchema"][k]]["internalType"] extends SchemaAbiType
? UserTypes<config>[table["valueSchema"][k]]["internalType"]
: table["valueSchema"][k];
}
: ValueSchema;
Expand All @@ -41,6 +45,10 @@ export type Tables<config extends StoreConfig = StoreConfig> = {
};

export function configToTables<config extends StoreConfig>(config: config): Tables<config> {
const userTypes = {
...config.userTypes,
...Object.fromEntries(Object.entries(config.enums).map(([key]) => [key, { internalType: "uint8" }] as const)),
};
return Object.fromEntries(
Object.entries(config.tables).map(([tableName, table]) => [
`${config.namespace}_${tableName}` satisfies TableKey<config, config["tables"][keyof config["tables"]]>,
Expand All @@ -52,8 +60,8 @@ export function configToTables<config extends StoreConfig>(config: config): Tabl
namespace: config.namespace,
name: table.name,
}),
keySchema: resolveUserTypes(table.keySchema, config.userTypes) as any,
valueSchema: resolveUserTypes(table.valueSchema, config.userTypes) as any,
keySchema: resolveUserTypes(table.keySchema, userTypes) as any,
valueSchema: resolveUserTypes(table.valueSchema, userTypes) as any,
} satisfies Table<config, config["tables"][keyof config["tables"]]>,
])
) as Tables<config>;
Expand Down
18 changes: 10 additions & 8 deletions packages/cli/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { deployWorld } from "./deployWorld";
import { ensureTables } from "./ensureTables";
import { Config, ConfigInput, WorldDeploy, supportedStoreVersions, supportedWorldVersions } from "./common";
import { ensureSystems } from "./ensureSystems";
import { waitForTransactionReceipt } from "viem/actions";
import { getTransactionReceipt, waitForTransactionReceipt } from "viem/actions";
import { getWorldDeploy } from "./getWorldDeploy";
import { ensureFunctions } from "./ensureFunctions";
import { ensureModules } from "./ensureModules";
import { Table } from "./configToTables";
import { getResourceIds } from "./getResourceIds";
import { assertNamespaceOwner } from "./assertNamespaceOwner";
import { debug } from "./debug";

type DeployOptions<configInput extends ConfigInput> = {
client: Client<Transport, Chain | undefined, Account>;
Expand Down Expand Up @@ -72,13 +72,15 @@ export async function deploy<configInput extends ConfigInput>({
modules: config.modules,
});

const receipts = await Promise.all(
[...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs].map((tx) =>
waitForTransactionReceipt(client, { hash: tx })
)
);
const txs = [...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs];

// TODO: throw if there was a revert? or attempt to re-run deploy?
// wait for each tx separately/serially, because parallelizing results in RPC errors
debug("waiting for transactions to confirm");
for (const tx of txs) {
await waitForTransactionReceipt(client, { hash: tx });
// TODO: throw if there was a revert?
}

debug("deploy complete");
return worldDeploy;
}
5 changes: 4 additions & 1 deletion packages/cli/src/deploy/ensureModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { writeContract } from "@latticexyz/common";
import { Module, WorldDeploy, worldAbi } from "./common";
import { ensureContract } from "./ensureContract";
import { debug } from "./debug";
import { uniqueBy } from "@latticexyz/common/utils";

export async function ensureModules({
client,
Expand All @@ -17,7 +18,9 @@ export async function ensureModules({

// kick off contract deployments first, otherwise installing modules can fail
const contractTxs = await Promise.all(
modules.map((mod) => ensureContract({ client, bytecode: mod.bytecode, label: `${mod.name} module` }))
uniqueBy(modules, (mod) => mod.address).map((mod) =>
ensureContract({ client, bytecode: mod.bytecode, label: `${mod.name} module` })
)
);

// then start installing modules
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/deploy/ensureSystems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { debug } from "./debug";
import { resourceLabel } from "./resourceLabel";
import { getSystems } from "./getSystems";
import { getResourceAccess } from "./getResourceAccess";
import { uniqueBy } from "@latticexyz/common/utils";

export async function ensureSystems({
client,
Expand Down Expand Up @@ -105,7 +106,7 @@ export async function ensureSystems({

// kick off contract deployments first, otherwise registering systems can fail
const contractTxs = await Promise.all(
missingSystems.map((system) =>
uniqueBy(missingSystems, (system) => system.address).map((system) =>
ensureContract({ client, bytecode: system.bytecode, label: `${resourceLabel(system)} system` })
)
);
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ export * from "./identity";
export * from "./isDefined";
export * from "./isNotNull";
export * from "./iteratorToArray";
export * from "./uniqueBy";
export * from "./wait";
export * from "./waitForIdle";
7 changes: 7 additions & 0 deletions packages/common/src/utils/uniqueBy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function uniqueBy<value, key>(values: readonly value[], getKey: (value: value) => key): readonly value[] {
const map = new Map<key, value>();
for (const value of values) {
map.set(getKey(value), value);
}
return Array.from(map.values());
}
17 changes: 8 additions & 9 deletions packages/store/ts/config/storeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,21 @@ const zShorthandSchemaConfig = zFieldData.transform((fieldData) => {

export const zSchemaConfig = zFullSchemaConfig.or(zShorthandSchemaConfig);

export type ResolvedSchema<TSchema extends Record<string, string>, TUserTypes extends Record<string, UserType>> = {
export type ResolvedSchema<
TSchema extends Record<string, string>,
TUserTypes extends Record<string, Pick<UserType, "internalType">>
> = {
[key in keyof TSchema]: TSchema[key] extends keyof TUserTypes
? TUserTypes[TSchema[key]]["internalType"]
: TSchema[key];
};

// TODO: add strong types to UserTypes config and use them here
// (see https://github.com/latticexyz/mud/pull/1588)
export function resolveUserTypes<TSchema extends Record<string, string>, TUserTypes extends Record<string, UserType>>(
schema: TSchema,
userTypes: TUserTypes
): ResolvedSchema<TSchema, TUserTypes> {
export function resolveUserTypes<
TSchema extends Record<string, string>,
TUserTypes extends Record<string, Pick<UserType, "internalType">>
>(schema: TSchema, userTypes: TUserTypes): ResolvedSchema<TSchema, TUserTypes> {
const resolvedSchema: Record<string, SchemaAbiType> = {};
for (const [key, value] of Object.entries(schema)) {
resolvedSchema[key] = (userTypes[value]?.internalType as SchemaAbiType) ?? value;
Expand Down Expand Up @@ -295,10 +298,6 @@ export type UserTypesConfig<UserTypeNames extends StringForUnion = StringForUnio
userTypes: Record<UserTypeNames, UserType>;
};

export type FullUserTypesConfig<UserTypeNames extends StringForUnion> = {
userTypes: Record<UserTypeNames, string>;
};

const zUserTypeConfig = z.object({
filePath: z.string(),
internalType: z.enum(schemaAbiTypes),
Expand Down

0 comments on commit 4fe0793

Please sign in to comment.