Skip to content

Commit

Permalink
fix(cli): deployer should wait for prereq txs (#3113)
Browse files Browse the repository at this point in the history
  • Loading branch information
holic authored Sep 1, 2024
1 parent c0764a5 commit 0738d29
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 125 deletions.
5 changes: 5 additions & 0 deletions .changeset/long-dogs-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@latticexyz/cli": patch
---

Deployer now waits for prerequisite transactions before continuing.
21 changes: 1 addition & 20 deletions packages/cli/src/deploy/configToModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import { SchemaAbiType, SchemaAbiTypeToPrimitiveType } from "@latticexyz/schema-
import { bytesToHex } from "viem";
import { createPrepareDeploy } from "./createPrepareDeploy";
import { World } from "@latticexyz/world";
import { getContractArtifact } from "../utils/getContractArtifact";
import { importContractArtifact } from "../utils/importContractArtifact";
import { resolveWithContext } from "@latticexyz/world/internal";
import metadataModule from "@latticexyz/world-module-metadata/out/MetadataModule.sol/MetadataModule.json" assert { type: "json" };

/** Please don't add to this list! These are kept for backwards compatibility and assumes the downstream project has this module installed as a dependency. */
const knownModuleArtifacts = {
Expand All @@ -19,28 +17,11 @@ const knownModuleArtifacts = {
"@latticexyz/world-modules/out/Unstable_CallWithSignatureModule.sol/Unstable_CallWithSignatureModule.json",
};

const metadataModuleArtifact = getContractArtifact(metadataModule);

export async function configToModules<config extends World>(
config: config,
// TODO: remove/replace `forgeOutDir`
forgeOutDir: string,
): Promise<readonly Module[]> {
const defaultModules: Module[] = [
// TODO: replace metadata install here with custom logic inside `ensureModules` or an `ensureDefaultModules` to check
// if metadata namespace exists, if we own it, and if so transfer ownership to the module before reinstalling
// (https://github.com/latticexyz/mud/issues/3035)
{
optional: true,
name: "MetadataModule",
installAsRoot: false,
installData: "0x",
prepareDeploy: createPrepareDeploy(metadataModuleArtifact.bytecode, metadataModuleArtifact.placeholders),
deployedBytecodeSize: metadataModuleArtifact.deployedBytecodeSize,
abi: metadataModuleArtifact.abi,
},
];

const modules = await Promise.all(
config.modules.map(async (mod): Promise<Module> => {
let artifactPath = mod.artifactPath;
Expand Down Expand Up @@ -98,5 +79,5 @@ export async function configToModules<config extends World>(
}),
);

return [...defaultModules, ...modules];
return modules;
}
33 changes: 19 additions & 14 deletions packages/cli/src/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { deployWorld } from "./deployWorld";
import { ensureTables } from "./ensureTables";
import { Library, Module, System, WorldDeploy, supportedStoreVersions, supportedWorldVersions } from "./common";
import { ensureSystems } from "./ensureSystems";
import { waitForTransactionReceipt } from "viem/actions";
import { getWorldDeploy } from "./getWorldDeploy";
import { ensureFunctions } from "./ensureFunctions";
import { ensureModules } from "./ensureModules";
Expand All @@ -16,6 +15,7 @@ import { randomBytes } from "crypto";
import { ensureWorldFactory } from "./ensureWorldFactory";
import { Table } from "@latticexyz/config";
import { ensureResourceTags } from "./ensureResourceTags";
import { waitForTransactions } from "./waitForTransactions";

type DeployOptions = {
client: Client<Transport, Chain | undefined, Account>;
Expand Down Expand Up @@ -95,11 +95,9 @@ export async function deploy({
worldDeploy,
resourceIds: [...tables.map(({ tableId }) => tableId), ...systems.map(({ systemId }) => systemId)],
});

debug("waiting for all namespace registration transactions to confirm");
for (const tx of namespaceTxs) {
await waitForTransactionReceipt(client, { hash: tx });
}
// Wait for namespaces to be available, otherwise referencing them below may fail.
// This is only here because OPStack chains don't let us estimate gas with pending block tag.
await waitForTransactions({ client, hashes: namespaceTxs, debugLabel: "namespace registrations" });

const tableTxs = await ensureTables({
client,
Expand All @@ -113,6 +111,14 @@ export async function deploy({
worldDeploy,
systems,
});
// Wait for tables and systems to be available, otherwise referencing their resource IDs below may fail.
// This is only here because OPStack chains don't let us estimate gas with pending block tag.
await waitForTransactions({
client,
hashes: [...tableTxs, ...systemTxs],
debugLabel: "table and system registrations",
});

const functionTxs = await ensureFunctions({
client,
worldDeploy,
Expand All @@ -135,19 +141,18 @@ export async function deploy({

const tagTxs = await ensureResourceTags({
client,
deployerAddress,
libraries,
worldDeploy,
tags: [...tableTags, ...systemTags],
valueToHex: stringToHex,
});

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");
for (const tx of txs) {
await waitForTransactionReceipt(client, { hash: tx });
// TODO: throw if there was a revert?
}
await waitForTransactions({
client,
hashes: [...functionTxs, ...moduleTxs, ...tagTxs],
debugLabel: "remaining transactions",
});

debug("deploy complete");
return worldDeploy;
Expand Down
7 changes: 1 addition & 6 deletions packages/cli/src/deploy/ensureContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { contractSizeLimit, salt } from "./common";
import { sendTransaction } from "@latticexyz/common";
import { debug } from "./debug";
import pRetry from "p-retry";
import { wait } from "@latticexyz/common/utils";

export type Contract = {
bytecode: Hex;
Expand Down Expand Up @@ -56,11 +55,7 @@ export async function ensureContract({
}),
{
retries: 3,
onFailedAttempt: async (error) => {
const delay = error.attemptNumber * 500;
debug(`failed to deploy ${debugLabel}, retrying in ${delay}ms...`);
await wait(delay);
},
onFailedAttempt: () => debug(`failed to deploy ${debugLabel}, retrying...`),
},
),
];
Expand Down
16 changes: 6 additions & 10 deletions packages/cli/src/deploy/ensureContractsDeployed.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Client, Transport, Chain, Account, Hex } from "viem";
import { waitForTransactionReceipt } from "viem/actions";
import { debug } from "./debug";
import { Contract, ensureContract } from "./ensureContract";
import { uniqueBy } from "@latticexyz/common/utils";
import { waitForTransactions } from "./waitForTransactions";

export async function ensureContractsDeployed({
client,
Expand All @@ -20,14 +19,11 @@ export async function ensureContractsDeployed({
await Promise.all(uniqueContracts.map((contract) => ensureContract({ client, deployerAddress, ...contract })))
).flat();

if (txs.length) {
debug("waiting for contracts");
// wait for each tx separately/serially, because parallelizing results in RPC errors
for (const tx of txs) {
await waitForTransactionReceipt(client, { hash: tx });
// TODO: throw if there was a revert?
}
}
await waitForTransactions({
client,
hashes: txs,
debugLabel: "contract deploys",
});

return txs;
}
48 changes: 19 additions & 29 deletions packages/cli/src/deploy/ensureFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { getFunctions } from "@latticexyz/world/internal";
import { WorldDeploy, WorldFunction, worldAbi } from "./common";
import { debug } from "./debug";
import pRetry from "p-retry";
import { wait } from "@latticexyz/common/utils";

export async function ensureFunctions({
client,
Expand Down Expand Up @@ -46,44 +45,35 @@ export async function ensureFunctions({
return Promise.all(
toAdd.map((func) => {
const { namespace } = hexToResource(func.systemId);
if (namespace === "") {
return pRetry(
() =>
writeContract(client, {
chain: client.chain ?? null,
address: worldDeploy.address,
abi: worldAbi,
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)

// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
const params =
namespace === ""
? ({
functionName: "registerRootFunctionSelector",
args: [func.systemId, func.systemFunctionSignature, func.systemFunctionSignature],
}),
{
retries: 3,
onFailedAttempt: async (error) => {
const delay = error.attemptNumber * 500;
debug(`failed to register function ${func.signature}, retrying in ${delay}ms...`);
await wait(delay);
},
},
);
}
args: [
func.systemId,
// use system function signature as world signature
func.systemFunctionSignature,
func.systemFunctionSignature,
],
} as const)
: ({
functionName: "registerFunctionSelector",
args: [func.systemId, func.systemFunctionSignature],
} as const);

return pRetry(
() =>
writeContract(client, {
chain: client.chain ?? null,
address: worldDeploy.address,
abi: worldAbi,
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
functionName: "registerFunctionSelector",
args: [func.systemId, func.systemFunctionSignature],
...params,
}),
{
retries: 3,
onFailedAttempt: async (error) => {
const delay = error.attemptNumber * 500;
debug(`failed to register function ${func.signature}, retrying in ${delay}ms...`);
await wait(delay);
},
onFailedAttempt: () => debug(`failed to register function ${func.signature}, retrying...`),
},
);
}),
Expand Down
35 changes: 12 additions & 23 deletions packages/cli/src/deploy/ensureModules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Client, Transport, Chain, Account, Hex, BaseError } from "viem";
import { writeContract } from "@latticexyz/common";
import { Library, Module, WorldDeploy, worldAbi } from "./common";
import { debug } from "./debug";
import { isDefined, wait } from "@latticexyz/common/utils";
import { isDefined } from "@latticexyz/common/utils";
import pRetry from "p-retry";
import { ensureContractsDeployed } from "./ensureContractsDeployed";

Expand Down Expand Up @@ -41,23 +41,16 @@ export async function ensureModules({
// append module's ABI so that we can decode any custom errors
const abi = [...worldAbi, ...mod.abi];
const moduleAddress = mod.prepareDeploy(deployerAddress, libraries).address;
return mod.installAsRoot
? await writeContract(client, {
chain: client.chain ?? null,
address: worldDeploy.address,
abi,
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
functionName: "installRootModule",
args: [moduleAddress, mod.installData],
})
: await writeContract(client, {
chain: client.chain ?? null,
address: worldDeploy.address,
abi,
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
functionName: "installModule",
args: [moduleAddress, mod.installData],
});
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
const params = mod.installAsRoot
? ({ functionName: "installRootModule", args: [moduleAddress, mod.installData] } as const)
: ({ functionName: "installModule", args: [moduleAddress, mod.installData] } as const);
return writeContract(client, {
chain: client.chain ?? null,
address: worldDeploy.address,
abi,
...params,
});
} catch (error) {
if (error instanceof BaseError && error.message.includes("Module_AlreadyInstalled")) {
debug(`module ${mod.name} already installed`);
Expand All @@ -73,11 +66,7 @@ export async function ensureModules({
},
{
retries: 3,
onFailedAttempt: async (error) => {
const delay = error.attemptNumber * 500;
debug(`failed to install module ${mod.name}, retrying in ${delay}ms...`);
await wait(delay);
},
onFailedAttempt: () => debug(`failed to install module ${mod.name}, retrying...`),
},
),
),
Expand Down
41 changes: 40 additions & 1 deletion packages/cli/src/deploy/ensureResourceTags.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Hex, Client, Transport, Chain, Account, stringToHex, BaseError } from "viem";
import { WorldDeploy } from "./common";
import { Library, 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";
import { ensureModules } from "./ensureModules";
import metadataModule from "@latticexyz/world-module-metadata/out/MetadataModule.sol/MetadataModule.json" assert { type: "json" };
import { getContractArtifact } from "../utils/getContractArtifact";
import { createPrepareDeploy } from "./createPrepareDeploy";
import { waitForTransactions } from "./waitForTransactions";

const metadataModuleArtifact = getContractArtifact(metadataModule);

export type ResourceTag<value> = {
resourceId: Hex;
Expand All @@ -15,11 +22,15 @@ export type ResourceTag<value> = {

export async function ensureResourceTags<const value>({
client,
deployerAddress,
libraries,
worldDeploy,
tags,
valueToHex = identity,
}: {
readonly client: Client<Transport, Chain | undefined, Account>;
readonly deployerAddress: Hex;
readonly libraries: readonly Library[];
readonly worldDeploy: WorldDeploy;
readonly tags: readonly ResourceTag<value>[];
} & (value extends Hex
Expand All @@ -42,6 +53,34 @@ export async function ensureResourceTags<const value>({

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

// TODO: check if metadata namespace exists, if we own it, and if so transfer ownership to the module before reinstalling
// (https://github.com/latticexyz/mud/issues/3035)
const moduleTxs = await ensureModules({
client,
deployerAddress,
worldDeploy,
libraries,
modules: [
{
optional: true,
name: "MetadataModule",
installAsRoot: false,
installData: "0x",
prepareDeploy: createPrepareDeploy(metadataModuleArtifact.bytecode, metadataModuleArtifact.placeholders),
deployedBytecodeSize: metadataModuleArtifact.deployedBytecodeSize,
abi: metadataModuleArtifact.abi,
},
],
});

// Wait for metadata module to be available, otherwise calling the metadata system below may fail.
// This is only here because OPStack chains don't let us estimate gas with pending block tag.
await waitForTransactions({
client,
hashes: moduleTxs,
debugLabel: "metadata module installation",
});

debug("setting", pendingTags.length, "resource tags");
return (
await Promise.all(
Expand Down
Loading

0 comments on commit 0738d29

Please sign in to comment.