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): deployer should wait for prereq txs #3113

Merged
merged 5 commits into from
Sep 1, 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/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
Loading