diff --git a/.changeset/loud-mayflies-divide.md b/.changeset/loud-mayflies-divide.md new file mode 100644 index 0000000000..a255e5539b --- /dev/null +++ b/.changeset/loud-mayflies-divide.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/cli": patch +--- + +Changed deploy order so that system/module contracts are fully deployed before registering/installing them on the world. diff --git a/packages/cli/src/deploy/common.ts b/packages/cli/src/deploy/common.ts index 0e4a5b3306..8b515b9e8b 100644 --- a/packages/cli/src/deploy/common.ts +++ b/packages/cli/src/deploy/common.ts @@ -38,25 +38,25 @@ export type WorldFunction = { readonly systemFunctionSelector: Hex; }; -export type System = { +export type DeterministicContract = { + readonly address: Address; + readonly bytecode: Hex; + readonly abi: Abi; +}; + +export type System = DeterministicContract & { readonly namespace: string; readonly name: string; readonly systemId: Hex; readonly allowAll: boolean; readonly allowedAddresses: readonly Hex[]; - readonly address: Address; - readonly bytecode: Hex; - readonly abi: Abi; readonly functions: readonly WorldFunction[]; }; -export type Module = { +export type Module = DeterministicContract & { readonly name: string; readonly installAsRoot: boolean; readonly installData: Hex; // TODO: figure out better naming for this - readonly address: Address; - readonly bytecode: Hex; - readonly abi: Abi; }; export type ConfigInput = StoreConfig & WorldConfig; diff --git a/packages/cli/src/deploy/deploy.ts b/packages/cli/src/deploy/deploy.ts index 54b1085ead..6dafe380c5 100644 --- a/packages/cli/src/deploy/deploy.ts +++ b/packages/cli/src/deploy/deploy.ts @@ -11,6 +11,9 @@ import { ensureModules } from "./ensureModules"; import { Table } from "./configToTables"; import { assertNamespaceOwner } from "./assertNamespaceOwner"; import { debug } from "./debug"; +import { resourceLabel } from "./resourceLabel"; +import { ensureContract } from "./ensureContract"; +import { uniqueBy } from "@latticexyz/common/utils"; type DeployOptions = { client: Client; @@ -51,6 +54,27 @@ export async function deploy({ resourceIds: [...tables.map((table) => table.tableId), ...systems.map((system) => system.systemId)], }); + // deploy all dependent contracts, because system registration, module install, etc. all expect these contracts to be callable. + const contractTxs = ( + await Promise.all([ + ...uniqueBy(systems, (system) => system.address).map((system) => + ensureContract({ client, bytecode: system.bytecode, label: `${resourceLabel(system)} system` }) + ), + ...uniqueBy(config.modules, (mod) => mod.address).map((mod) => + ensureContract({ client, bytecode: mod.bytecode, label: `${mod.name} module` }) + ), + ]) + ).flat(); + + if (contractTxs.length) { + debug("waiting for contracts"); + // wait for each tx separately/serially, because parallelizing results in RPC errors + for (const tx of contractTxs) { + await waitForTransactionReceipt(client, { hash: tx }); + // TODO: throw if there was a revert? + } + } + const tableTxs = await ensureTables({ client, worldDeploy, @@ -75,7 +99,7 @@ export async function deploy({ const txs = [...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs]; // wait for each tx separately/serially, because parallelizing results in RPC errors - debug("waiting for transactions to confirm"); + debug("waiting for all transactions to confirm"); for (const tx of txs) { await waitForTransactionReceipt(client, { hash: tx }); // TODO: throw if there was a revert? diff --git a/packages/cli/src/deploy/ensureModules.ts b/packages/cli/src/deploy/ensureModules.ts index 7d7e992567..98c331f09a 100644 --- a/packages/cli/src/deploy/ensureModules.ts +++ b/packages/cli/src/deploy/ensureModules.ts @@ -1,9 +1,8 @@ import { Client, Transport, Chain, Account, Hex } from "viem"; import { writeContract } from "@latticexyz/common"; import { Module, WorldDeploy, worldAbi } from "./common"; -import { ensureContract } from "./ensureContract"; import { debug } from "./debug"; -import { uniqueBy, wait } from "@latticexyz/common/utils"; +import { wait } from "@latticexyz/common/utils"; import pRetry from "p-retry"; export async function ensureModules({ @@ -17,14 +16,6 @@ export async function ensureModules({ }): Promise { if (!modules.length) return []; - // kick off contract deployments first, otherwise installing modules can fail - const contractTxs = await Promise.all( - uniqueBy(modules, (mod) => mod.address).map((mod) => - ensureContract({ client, bytecode: mod.bytecode, label: `${mod.name} module` }) - ) - ); - - // then start installing modules debug("installing modules:", modules.map((mod) => mod.name).join(", ")); const installTxs = await Promise.all( modules.map((mod) => @@ -70,5 +61,5 @@ export async function ensureModules({ ) ); - return (await Promise.all([...contractTxs, ...installTxs])).flat(); + return await Promise.all(installTxs); } diff --git a/packages/cli/src/deploy/ensureSystems.ts b/packages/cli/src/deploy/ensureSystems.ts index b7dfa6db4d..ab530696c4 100644 --- a/packages/cli/src/deploy/ensureSystems.ts +++ b/packages/cli/src/deploy/ensureSystems.ts @@ -1,12 +1,11 @@ import { Client, Transport, Chain, Account, Hex, getAddress } from "viem"; import { writeContract } from "@latticexyz/common"; import { System, WorldDeploy, worldAbi } from "./common"; -import { ensureContract } from "./ensureContract"; import { debug } from "./debug"; import { resourceLabel } from "./resourceLabel"; import { getSystems } from "./getSystems"; import { getResourceAccess } from "./getResourceAccess"; -import { uniqueBy, wait } from "@latticexyz/common/utils"; +import { wait } from "@latticexyz/common/utils"; import pRetry from "p-retry"; export async function ensureSystems({ @@ -53,7 +52,7 @@ export async function ensureSystems({ debug("adding", accessToAdd.length, "access grants"); } - const accessTxs = await Promise.all([ + const accessTxs = [ ...accessToRemove.map((access) => pRetry( () => @@ -94,7 +93,7 @@ export async function ensureSystems({ } ) ), - ]); + ]; const existingSystems = systems.filter((system) => worldSystems.some( @@ -127,37 +126,27 @@ export async function ensureSystems({ debug("registering new systems", systemsToAdd.map(resourceLabel).join(", ")); } - // kick off contract deployments first, otherwise registering systems can fail - const contractTxs = await Promise.all( - uniqueBy(missingSystems, (system) => system.address).map((system) => - ensureContract({ client, bytecode: system.bytecode, label: `${resourceLabel(system)} system` }) - ) - ); - - // then start registering systems - const registerTxs = await Promise.all( - missingSystems.map((system) => - pRetry( - () => - writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: worldAbi, - // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) - functionName: "registerSystem", - args: [system.systemId, system.address, system.allowAll], - }), - { - retries: 3, - onFailedAttempt: async (error) => { - const delay = error.attemptNumber * 500; - debug(`failed to register system ${resourceLabel(system)}, retrying in ${delay}ms...`); - await wait(delay); - }, - } - ) + const registerTxs = missingSystems.map((system) => + pRetry( + () => + writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: worldAbi, + // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) + functionName: "registerSystem", + args: [system.systemId, system.address, system.allowAll], + }), + { + retries: 3, + onFailedAttempt: async (error) => { + const delay = error.attemptNumber * 500; + debug(`failed to register system ${resourceLabel(system)}, retrying in ${delay}ms...`); + await wait(delay); + }, + } ) ); - return (await Promise.all([...accessTxs, ...contractTxs, ...registerTxs])).flat(); + return await Promise.all([...accessTxs, ...registerTxs]); }