From 6b782bd9401b47d4068236fb5a0e286bd2420da7 Mon Sep 17 00:00:00 2001 From: alvrs Date: Wed, 18 Sep 2024 23:34:17 +0100 Subject: [PATCH 1/8] fix(cli): improve performance of linked library resolution during deployment --- packages/cli/src/deploy/common.ts | 3 ++- .../cli/src/deploy/createPrepareDeploy.ts | 19 ++++++++++--------- packages/cli/src/deploy/deploy.ts | 14 ++++++++------ packages/cli/src/deploy/ensureModules.ts | 11 ++++++----- packages/cli/src/deploy/ensureResourceTags.ts | 9 +++++---- packages/cli/src/deploy/ensureSystems.ts | 17 +++++++++-------- packages/cli/src/deploy/getLibraryMap.ts | 12 ++++++++++++ packages/cli/src/runDeploy.ts | 1 + packages/cli/src/verify.ts | 2 +- 9 files changed, 54 insertions(+), 34 deletions(-) create mode 100644 packages/cli/src/deploy/getLibraryMap.ts diff --git a/packages/cli/src/deploy/common.ts b/packages/cli/src/deploy/common.ts index 30c762843c..7ede9c8f4f 100644 --- a/packages/cli/src/deploy/common.ts +++ b/packages/cli/src/deploy/common.ts @@ -2,6 +2,7 @@ import { Abi, Address, Hex, padHex } from "viem"; import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" }; import { helloStoreEvent } from "@latticexyz/store"; import { helloWorldEvent } from "@latticexyz/world"; +import { LibraryMap } from "./getLibraryMap"; export const salt = padHex("0x", { size: 32 }); @@ -61,7 +62,7 @@ export type LibraryPlaceholder = { export type DeterministicContract = { readonly prepareDeploy: ( deployer: Address, - libraries: readonly Library[], + libraryMap: LibraryMap, ) => { readonly address: Address; readonly bytecode: Hex; diff --git a/packages/cli/src/deploy/createPrepareDeploy.ts b/packages/cli/src/deploy/createPrepareDeploy.ts index a1846de350..8ec3e75579 100644 --- a/packages/cli/src/deploy/createPrepareDeploy.ts +++ b/packages/cli/src/deploy/createPrepareDeploy.ts @@ -1,24 +1,25 @@ -import { DeterministicContract, Library, LibraryPlaceholder, salt } from "./common"; +import { DeterministicContract, LibraryPlaceholder, salt } from "./common"; import { spliceHex } from "@latticexyz/common"; import { Hex, getCreate2Address, Address } from "viem"; +import { LibraryMap, getLibraryKey } from "./getLibraryMap"; export function createPrepareDeploy( bytecodeWithPlaceholders: Hex, placeholders: readonly LibraryPlaceholder[], ): DeterministicContract["prepareDeploy"] { - return function prepareDeploy(deployer: Address, libraries: readonly Library[]) { + return function prepareDeploy(deployer: Address, libraryMap: LibraryMap) { let bytecode = bytecodeWithPlaceholders; for (const placeholder of placeholders) { - const library = libraries.find((lib) => lib.path === placeholder.path && lib.name === placeholder.name); + const libraryKey = getLibraryKey(placeholder); + const library = libraryMap[libraryKey]; if (!library) { throw new Error(`Could not find library for bytecode placeholder ${placeholder.path}:${placeholder.name}`); } - bytecode = spliceHex( - bytecode, - placeholder.start, - placeholder.length, - library.prepareDeploy(deployer, libraries).address, - ); + + // Store the prepared address in the library map to avoid preparing the same library twice + library.address ??= library.prepareDeploy(deployer, libraryMap).address; + + bytecode = spliceHex(bytecode, placeholder.start, placeholder.length, library.address); } return { bytecode, diff --git a/packages/cli/src/deploy/deploy.ts b/packages/cli/src/deploy/deploy.ts index bbd13d5beb..e0a7e3688e 100644 --- a/packages/cli/src/deploy/deploy.ts +++ b/packages/cli/src/deploy/deploy.ts @@ -20,6 +20,7 @@ import { ContractArtifact } from "@latticexyz/world/node"; import { World } from "@latticexyz/world"; import { deployCustomWorld } from "./deployCustomWorld"; import { uniqueBy } from "@latticexyz/common/utils"; +import { getLibraryMap } from "./getLibraryMap"; type DeployOptions = { config: World; @@ -64,22 +65,23 @@ export async function deploy({ await ensureWorldFactory(client, deployerAddress, config.deploy.upgradeableWorldImplementation); // deploy all dependent contracts, because system registration, module install, etc. all expect these contracts to be callable. + const libraryMap = getLibraryMap(libraries); await ensureContractsDeployed({ client, deployerAddress, contracts: [ ...libraries.map((library) => ({ - bytecode: library.prepareDeploy(deployerAddress, libraries).bytecode, + bytecode: library.prepareDeploy(deployerAddress, libraryMap).bytecode, deployedBytecodeSize: library.deployedBytecodeSize, debugLabel: `${library.path}:${library.name} library`, })), ...systems.map((system) => ({ - bytecode: system.prepareDeploy(deployerAddress, libraries).bytecode, + bytecode: system.prepareDeploy(deployerAddress, libraryMap).bytecode, deployedBytecodeSize: system.deployedBytecodeSize, debugLabel: `${resourceToLabel(system)} system`, })), ...modules.map((mod) => ({ - bytecode: mod.prepareDeploy(deployerAddress, libraries).bytecode, + bytecode: mod.prepareDeploy(deployerAddress, libraryMap).bytecode, deployedBytecodeSize: mod.deployedBytecodeSize, debugLabel: `${mod.name} module`, })), @@ -126,7 +128,7 @@ export async function deploy({ const systemTxs = await ensureSystems({ client, deployerAddress, - libraries, + libraryMap, worldDeploy, systems, }); @@ -146,7 +148,7 @@ export async function deploy({ const moduleTxs = await ensureModules({ client, deployerAddress, - libraries, + libraryMap, worldDeploy, modules, }); @@ -178,7 +180,7 @@ export async function deploy({ const tagTxs = await ensureResourceTags({ client, deployerAddress, - libraries, + libraryMap, worldDeploy, tags: [...namespaceTags, ...tableTags, ...systemTags], valueToHex: stringToHex, diff --git a/packages/cli/src/deploy/ensureModules.ts b/packages/cli/src/deploy/ensureModules.ts index 04b20c2433..5dc2128514 100644 --- a/packages/cli/src/deploy/ensureModules.ts +++ b/packages/cli/src/deploy/ensureModules.ts @@ -1,21 +1,22 @@ import { Client, Transport, Chain, Account, Hex, BaseError } from "viem"; import { writeContract } from "@latticexyz/common"; -import { Library, Module, WorldDeploy, worldAbi } from "./common"; +import { Module, WorldDeploy, worldAbi } from "./common"; import { debug } from "./debug"; import { isDefined } from "@latticexyz/common/utils"; import pRetry from "p-retry"; import { ensureContractsDeployed } from "./ensureContractsDeployed"; +import { LibraryMap } from "./getLibraryMap"; export async function ensureModules({ client, deployerAddress, - libraries, + libraryMap, worldDeploy, modules, }: { readonly client: Client; readonly deployerAddress: Hex; - readonly libraries: readonly Library[]; + readonly libraryMap: LibraryMap; readonly worldDeploy: WorldDeploy; readonly modules: readonly Module[]; }): Promise { @@ -25,7 +26,7 @@ export async function ensureModules({ client, deployerAddress, contracts: modules.map((mod) => ({ - bytecode: mod.prepareDeploy(deployerAddress, libraries).bytecode, + bytecode: mod.prepareDeploy(deployerAddress, libraryMap).bytecode, deployedBytecodeSize: mod.deployedBytecodeSize, debugLabel: `${mod.name} module`, })), @@ -40,7 +41,7 @@ export async function ensureModules({ try { // append module's ABI so that we can decode any custom errors const abi = [...worldAbi, ...mod.abi]; - const moduleAddress = mod.prepareDeploy(deployerAddress, libraries).address; + const moduleAddress = mod.prepareDeploy(deployerAddress, libraryMap).address; // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) const params = mod.installAsRoot ? ({ functionName: "installRootModule", args: [moduleAddress, mod.installData] } as const) diff --git a/packages/cli/src/deploy/ensureResourceTags.ts b/packages/cli/src/deploy/ensureResourceTags.ts index c279649132..5d4ae14185 100644 --- a/packages/cli/src/deploy/ensureResourceTags.ts +++ b/packages/cli/src/deploy/ensureResourceTags.ts @@ -1,5 +1,5 @@ import { Hex, Client, Transport, Chain, Account, stringToHex, BaseError } from "viem"; -import { Library, WorldDeploy } from "./common"; +import { WorldDeploy } from "./common"; import { debug } from "./debug"; import { hexToResource, writeContract } from "@latticexyz/common"; import { identity, isDefined } from "@latticexyz/common/utils"; @@ -11,6 +11,7 @@ import metadataModule from "@latticexyz/world-module-metadata/out/MetadataModule import { getContractArtifact } from "../utils/getContractArtifact"; import { createPrepareDeploy } from "./createPrepareDeploy"; import { waitForTransactions } from "./waitForTransactions"; +import { LibraryMap } from "./getLibraryMap"; const metadataModuleArtifact = getContractArtifact(metadataModule); @@ -23,14 +24,14 @@ export type ResourceTag = { export async function ensureResourceTags({ client, deployerAddress, - libraries, + libraryMap, worldDeploy, tags, valueToHex = identity, }: { readonly client: Client; readonly deployerAddress: Hex; - readonly libraries: readonly Library[]; + readonly libraryMap: LibraryMap; readonly worldDeploy: WorldDeploy; readonly tags: readonly ResourceTag[]; } & (value extends Hex @@ -59,7 +60,7 @@ export async function ensureResourceTags({ client, deployerAddress, worldDeploy, - libraries, + libraryMap, modules: [ { optional: true, diff --git a/packages/cli/src/deploy/ensureSystems.ts b/packages/cli/src/deploy/ensureSystems.ts index c018db1043..48da5611c3 100644 --- a/packages/cli/src/deploy/ensureSystems.ts +++ b/packages/cli/src/deploy/ensureSystems.ts @@ -1,24 +1,25 @@ import { Client, Transport, Chain, Account, Hex, getAddress, Address } from "viem"; import { writeContract, resourceToLabel } from "@latticexyz/common"; -import { Library, System, WorldDeploy, worldAbi } from "./common"; +import { System, WorldDeploy, worldAbi } from "./common"; import { debug } from "./debug"; import { getSystems } from "./getSystems"; import { getResourceAccess } from "./getResourceAccess"; import pRetry from "p-retry"; import { ensureContractsDeployed } from "./ensureContractsDeployed"; +import { LibraryMap } from "./getLibraryMap"; // TODO: move each system registration+access to batch call to be atomic export async function ensureSystems({ client, deployerAddress, - libraries, + libraryMap, worldDeploy, systems, }: { readonly client: Client; readonly deployerAddress: Hex; - readonly libraries: readonly Library[]; + readonly libraryMap: LibraryMap; readonly worldDeploy: WorldDeploy; readonly systems: readonly System[]; }): Promise { @@ -33,7 +34,7 @@ export async function ensureSystems({ worldSystems.some( (worldSystem) => worldSystem.systemId === system.systemId && - getAddress(worldSystem.address) === getAddress(system.prepareDeploy(deployerAddress, libraries).address), + getAddress(worldSystem.address) === getAddress(system.prepareDeploy(deployerAddress, libraryMap).address), ), ); if (existingSystems.length) { @@ -48,7 +49,7 @@ export async function ensureSystems({ worldSystems.some( (worldSystem) => worldSystem.systemId === system.systemId && - getAddress(worldSystem.address) !== getAddress(system.prepareDeploy(deployerAddress, libraries).address), + getAddress(worldSystem.address) !== getAddress(system.prepareDeploy(deployerAddress, libraryMap).address), ), ); if (systemsToUpgrade.length) { @@ -66,7 +67,7 @@ export async function ensureSystems({ client, deployerAddress, contracts: missingSystems.map((system) => ({ - bytecode: system.prepareDeploy(deployerAddress, libraries).bytecode, + bytecode: system.prepareDeploy(deployerAddress, libraryMap).bytecode, deployedBytecodeSize: system.deployedBytecodeSize, debugLabel: `${resourceToLabel(system)} system`, })), @@ -82,7 +83,7 @@ export async function ensureSystems({ abi: worldAbi, // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) functionName: "registerSystem", - args: [system.systemId, system.prepareDeploy(deployerAddress, libraries).address, system.allowAll], + args: [system.systemId, system.prepareDeploy(deployerAddress, libraryMap).address, system.allowAll], }), { retries: 3, @@ -106,7 +107,7 @@ export async function ensureSystems({ resourceId: system.systemId, address: worldSystems.find((s) => s.systemId === systemId)?.address ?? - systems.find((s) => s.systemId === systemId)?.prepareDeploy(deployerAddress, libraries).address, + systems.find((s) => s.systemId === systemId)?.prepareDeploy(deployerAddress, libraryMap).address, })) .filter((access): access is typeof access & { address: Address } => access.address != null), ), diff --git a/packages/cli/src/deploy/getLibraryMap.ts b/packages/cli/src/deploy/getLibraryMap.ts new file mode 100644 index 0000000000..766f2a0f72 --- /dev/null +++ b/packages/cli/src/deploy/getLibraryMap.ts @@ -0,0 +1,12 @@ +import { Hex } from "viem"; +import { Library } from "./common"; + +export type LibraryMap = { [key: string]: Library & { address?: Hex } }; + +export function getLibraryMap(libraries: readonly Library[]): LibraryMap { + return Object.fromEntries(libraries.map((library) => [getLibraryKey(library), library])); +} + +export function getLibraryKey({ path, name }: { path: string; name: string }): string { + return `${path}:${name}`; +} diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts index fb3fb3ebba..5d15843ac7 100644 --- a/packages/cli/src/runDeploy.ts +++ b/packages/cli/src/runDeploy.ts @@ -133,6 +133,7 @@ export async function runDeploy(opts: DeployOptions): Promise { : undefined, }), account, + pollingInterval: 100, }); console.log("Deploying from", client.account.address); diff --git a/packages/cli/src/verify.ts b/packages/cli/src/verify.ts index b5acaad720..a1698b1488 100644 --- a/packages/cli/src/verify.ts +++ b/packages/cli/src/verify.ts @@ -105,7 +105,7 @@ export async function verify({ ); modules.map(({ name, prepareDeploy }) => { - const { address } = prepareDeploy(deployerAddress, []); + const { address } = prepareDeploy(deployerAddress, {}); return verifyQueue.add(() => verifyContract({ // TODO: figure out dir from artifactPath via import.meta.resolve? From 10ea71cadc3b0236a19a3c7d89651ac856a03f1b Mon Sep 17 00:00:00 2001 From: alvarius Date: Wed, 18 Sep 2024 23:48:06 +0100 Subject: [PATCH 2/8] Create few-olives-judge.md --- .changeset/few-olives-judge.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/few-olives-judge.md diff --git a/.changeset/few-olives-judge.md b/.changeset/few-olives-judge.md new file mode 100644 index 0000000000..09489132e6 --- /dev/null +++ b/.changeset/few-olives-judge.md @@ -0,0 +1,6 @@ +--- +"@latticexyz/cli": patch +--- + +Significantly improved the deployment performance for large projects with public libraries by implementing a more efficient algorithm to resolve public libraries during deployment. +The local deployment time on a large reference project was reduced from over 10 minutes to 4 seconds. From e5d7b58622f7f528ee9b980ee5f8143da0308c2e Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Sep 2024 00:08:32 +0100 Subject: [PATCH 3/8] move polling interval to waitForTransactions, add better retry logic --- packages/cli/src/deploy/deployWorld.ts | 10 ++-------- packages/cli/src/deploy/waitForTransactions.ts | 16 ++++++++++++---- packages/cli/src/runDeploy.ts | 1 - 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/cli/src/deploy/deployWorld.ts b/packages/cli/src/deploy/deployWorld.ts index 054a859a22..e041a25d22 100644 --- a/packages/cli/src/deploy/deployWorld.ts +++ b/packages/cli/src/deploy/deployWorld.ts @@ -1,11 +1,11 @@ import { Account, Chain, Client, Hex, Transport } from "viem"; -import { waitForTransactionReceipt } from "viem/actions"; import { ensureWorldFactory } from "./ensureWorldFactory"; import WorldFactoryAbi from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.abi.json" assert { type: "json" }; import { writeContract } from "@latticexyz/common"; import { debug } from "./debug"; import { logsToWorldDeploy } from "./logsToWorldDeploy"; import { WorldDeploy } from "./common"; +import { waitForTransactions } from "./waitForTransactions"; export async function deployWorld( client: Client, @@ -24,13 +24,7 @@ export async function deployWorld( args: [salt], }); - debug("waiting for world deploy"); - const receipt = await waitForTransactionReceipt(client, { hash: tx }); - if (receipt.status !== "success") { - console.error("world deploy failed", receipt); - throw new Error("world deploy failed"); - } - + const [receipt] = await waitForTransactions({ client, hashes: [tx], debugLabel: "world deploy" }); const deploy = logsToWorldDeploy(receipt.logs); debug("deployed world to", deploy.address, "at block", deploy.deployBlock); diff --git a/packages/cli/src/deploy/waitForTransactions.ts b/packages/cli/src/deploy/waitForTransactions.ts index 934233de22..134e91d607 100644 --- a/packages/cli/src/deploy/waitForTransactions.ts +++ b/packages/cli/src/deploy/waitForTransactions.ts @@ -1,4 +1,4 @@ -import { Client, Transport, Chain, Account, Hex } from "viem"; +import { Client, Transport, Chain, Account, Hex, TransactionReceipt } from "viem"; import { debug } from "./debug"; import { waitForTransactionReceipt } from "viem/actions"; @@ -10,15 +10,23 @@ export async function waitForTransactions({ readonly client: Client; readonly hashes: readonly Hex[]; readonly debugLabel: string; -}): Promise { - if (!hashes.length) return; +}): Promise { + if (!hashes.length) return []; debug(`waiting for ${debugLabel} to confirm`); + const receipts: TransactionReceipt[] = []; // wait for each tx separately/serially, because parallelizing results in RPC errors for (const hash of hashes) { - const receipt = await waitForTransactionReceipt(client, { hash }); + const receipt = await waitForTransactionReceipt(client, { + hash, + pollingInterval: 100, + retryDelay: ({ count }) => 2 ** count * 200, + }); if (receipt.status === "reverted") { throw new Error(`Transaction reverted: ${hash}`); } + receipts.push(receipt); } + + return receipts; } diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts index 5d15843ac7..fb3fb3ebba 100644 --- a/packages/cli/src/runDeploy.ts +++ b/packages/cli/src/runDeploy.ts @@ -133,7 +133,6 @@ export async function runDeploy(opts: DeployOptions): Promise { : undefined, }), account, - pollingInterval: 100, }); console.log("Deploying from", client.account.address); From 6247103537d19f16f8ca26105a0e0736cff6b8d7 Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Sep 2024 00:46:53 +0100 Subject: [PATCH 4/8] increase retry count --- packages/cli/src/deploy/waitForTransactions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/deploy/waitForTransactions.ts b/packages/cli/src/deploy/waitForTransactions.ts index 134e91d607..81fbc58851 100644 --- a/packages/cli/src/deploy/waitForTransactions.ts +++ b/packages/cli/src/deploy/waitForTransactions.ts @@ -21,6 +21,7 @@ export async function waitForTransactions({ hash, pollingInterval: 100, retryDelay: ({ count }) => 2 ** count * 200, + retryCount: 8, }); if (receipt.status === "reverted") { throw new Error(`Transaction reverted: ${hash}`); From c816de9d1a4b2255fb11ed772481fb67a39e2ee2 Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Sep 2024 09:35:51 +0100 Subject: [PATCH 5/8] temporarily revert polling changes --- packages/cli/src/deploy/waitForTransactions.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/cli/src/deploy/waitForTransactions.ts b/packages/cli/src/deploy/waitForTransactions.ts index 81fbc58851..519c5290d8 100644 --- a/packages/cli/src/deploy/waitForTransactions.ts +++ b/packages/cli/src/deploy/waitForTransactions.ts @@ -17,12 +17,7 @@ export async function waitForTransactions({ const receipts: TransactionReceipt[] = []; // wait for each tx separately/serially, because parallelizing results in RPC errors for (const hash of hashes) { - const receipt = await waitForTransactionReceipt(client, { - hash, - pollingInterval: 100, - retryDelay: ({ count }) => 2 ** count * 200, - retryCount: 8, - }); + const receipt = await waitForTransactionReceipt(client, { hash }); if (receipt.status === "reverted") { throw new Error(`Transaction reverted: ${hash}`); } From 701b78a6ad61f2a002fb510ea23eff246b66d4a8 Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Sep 2024 13:11:43 +0100 Subject: [PATCH 6/8] change library map interface --- .../cli/src/deploy/createPrepareDeploy.ts | 14 ++------ packages/cli/src/deploy/getLibraryMap.ts | 35 +++++++++++++++---- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/cli/src/deploy/createPrepareDeploy.ts b/packages/cli/src/deploy/createPrepareDeploy.ts index 8ec3e75579..7d536b309e 100644 --- a/packages/cli/src/deploy/createPrepareDeploy.ts +++ b/packages/cli/src/deploy/createPrepareDeploy.ts @@ -1,7 +1,7 @@ import { DeterministicContract, LibraryPlaceholder, salt } from "./common"; import { spliceHex } from "@latticexyz/common"; import { Hex, getCreate2Address, Address } from "viem"; -import { LibraryMap, getLibraryKey } from "./getLibraryMap"; +import { LibraryMap } from "./getLibraryMap"; export function createPrepareDeploy( bytecodeWithPlaceholders: Hex, @@ -10,16 +10,8 @@ export function createPrepareDeploy( return function prepareDeploy(deployer: Address, libraryMap: LibraryMap) { let bytecode = bytecodeWithPlaceholders; for (const placeholder of placeholders) { - const libraryKey = getLibraryKey(placeholder); - const library = libraryMap[libraryKey]; - if (!library) { - throw new Error(`Could not find library for bytecode placeholder ${placeholder.path}:${placeholder.name}`); - } - - // Store the prepared address in the library map to avoid preparing the same library twice - library.address ??= library.prepareDeploy(deployer, libraryMap).address; - - bytecode = spliceHex(bytecode, placeholder.start, placeholder.length, library.address); + const address = libraryMap.getAddress({ name: placeholder.name, path: placeholder.path, deployer }); + bytecode = spliceHex(bytecode, placeholder.start, placeholder.length, address); } return { bytecode, diff --git a/packages/cli/src/deploy/getLibraryMap.ts b/packages/cli/src/deploy/getLibraryMap.ts index 766f2a0f72..49153b7671 100644 --- a/packages/cli/src/deploy/getLibraryMap.ts +++ b/packages/cli/src/deploy/getLibraryMap.ts @@ -1,12 +1,35 @@ -import { Hex } from "viem"; +import { Address } from "viem"; import { Library } from "./common"; -export type LibraryMap = { [key: string]: Library & { address?: Hex } }; +export type LibraryMap = { + getAddress: (opts: { path: string; name: string; deployer: Address }) => Address; +}; -export function getLibraryMap(libraries: readonly Library[]): LibraryMap { - return Object.fromEntries(libraries.map((library) => [getLibraryKey(library), library])); +function getLibraryKey({ path, name }: { path: string; name: string }): string { + return `${path}:${name}`; } -export function getLibraryKey({ path, name }: { path: string; name: string }): string { - return `${path}:${name}`; +type LibraryCache = { + [key: string]: Library & { + address?: { + [deployer: Address]: Address; + }; + }; +}; + +export function getLibraryMap(libraries: readonly Library[]): LibraryMap { + const cache: LibraryCache = Object.fromEntries(libraries.map((library) => [getLibraryKey(library), library])); + const libraryMap = { + getAddress: ({ path, name, deployer }) => { + const library = cache[getLibraryKey({ path, name })]; + if (!library) { + throw new Error(`Could not find library for bytecode placeholder ${path}:${name}`); + } + library.address ??= {}; + // Store the prepared address in the library cache to avoid preparing the same library twice + library.address[deployer] ??= library.prepareDeploy(deployer, libraryMap).address; + return library.address[deployer]; + }, + } satisfies LibraryMap; + return libraryMap; } From 50d4b9c685b2784283bfb3f189039d87911f1277 Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Sep 2024 13:14:16 +0100 Subject: [PATCH 7/8] remove unrelated change --- packages/cli/src/deploy/deployWorld.ts | 10 ++++++++-- packages/cli/src/deploy/waitForTransactions.ts | 10 +++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/deploy/deployWorld.ts b/packages/cli/src/deploy/deployWorld.ts index e041a25d22..054a859a22 100644 --- a/packages/cli/src/deploy/deployWorld.ts +++ b/packages/cli/src/deploy/deployWorld.ts @@ -1,11 +1,11 @@ import { Account, Chain, Client, Hex, Transport } from "viem"; +import { waitForTransactionReceipt } from "viem/actions"; import { ensureWorldFactory } from "./ensureWorldFactory"; import WorldFactoryAbi from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.abi.json" assert { type: "json" }; import { writeContract } from "@latticexyz/common"; import { debug } from "./debug"; import { logsToWorldDeploy } from "./logsToWorldDeploy"; import { WorldDeploy } from "./common"; -import { waitForTransactions } from "./waitForTransactions"; export async function deployWorld( client: Client, @@ -24,7 +24,13 @@ export async function deployWorld( args: [salt], }); - const [receipt] = await waitForTransactions({ client, hashes: [tx], debugLabel: "world deploy" }); + debug("waiting for world deploy"); + const receipt = await waitForTransactionReceipt(client, { hash: tx }); + if (receipt.status !== "success") { + console.error("world deploy failed", receipt); + throw new Error("world deploy failed"); + } + const deploy = logsToWorldDeploy(receipt.logs); debug("deployed world to", deploy.address, "at block", deploy.deployBlock); diff --git a/packages/cli/src/deploy/waitForTransactions.ts b/packages/cli/src/deploy/waitForTransactions.ts index 519c5290d8..934233de22 100644 --- a/packages/cli/src/deploy/waitForTransactions.ts +++ b/packages/cli/src/deploy/waitForTransactions.ts @@ -1,4 +1,4 @@ -import { Client, Transport, Chain, Account, Hex, TransactionReceipt } from "viem"; +import { Client, Transport, Chain, Account, Hex } from "viem"; import { debug } from "./debug"; import { waitForTransactionReceipt } from "viem/actions"; @@ -10,19 +10,15 @@ export async function waitForTransactions({ readonly client: Client; readonly hashes: readonly Hex[]; readonly debugLabel: string; -}): Promise { - if (!hashes.length) return []; +}): Promise { + if (!hashes.length) return; debug(`waiting for ${debugLabel} to confirm`); - const receipts: TransactionReceipt[] = []; // wait for each tx separately/serially, because parallelizing results in RPC errors for (const hash of hashes) { const receipt = await waitForTransactionReceipt(client, { hash }); if (receipt.status === "reverted") { throw new Error(`Transaction reverted: ${hash}`); } - receipts.push(receipt); } - - return receipts; } From 0bce659d32fe0fa5a6420f171eb39032d5c9cd47 Mon Sep 17 00:00:00 2001 From: alvrs Date: Thu, 19 Sep 2024 13:23:47 +0100 Subject: [PATCH 8/8] default prepareDeploy param --- packages/cli/src/deploy/common.ts | 2 +- packages/cli/src/deploy/createPrepareDeploy.ts | 11 ++++++++++- packages/cli/src/verify.ts | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/deploy/common.ts b/packages/cli/src/deploy/common.ts index 7ede9c8f4f..f88db9f65f 100644 --- a/packages/cli/src/deploy/common.ts +++ b/packages/cli/src/deploy/common.ts @@ -62,7 +62,7 @@ export type LibraryPlaceholder = { export type DeterministicContract = { readonly prepareDeploy: ( deployer: Address, - libraryMap: LibraryMap, + libraryMap?: LibraryMap, ) => { readonly address: Address; readonly bytecode: Hex; diff --git a/packages/cli/src/deploy/createPrepareDeploy.ts b/packages/cli/src/deploy/createPrepareDeploy.ts index 7d536b309e..065ade5e0b 100644 --- a/packages/cli/src/deploy/createPrepareDeploy.ts +++ b/packages/cli/src/deploy/createPrepareDeploy.ts @@ -7,8 +7,17 @@ export function createPrepareDeploy( bytecodeWithPlaceholders: Hex, placeholders: readonly LibraryPlaceholder[], ): DeterministicContract["prepareDeploy"] { - return function prepareDeploy(deployer: Address, libraryMap: LibraryMap) { + return function prepareDeploy(deployer: Address, libraryMap?: LibraryMap) { let bytecode = bytecodeWithPlaceholders; + + if (placeholders.length === 0) { + return { bytecode, address: getCreate2Address({ from: deployer, bytecode, salt }) }; + } + + if (!libraryMap) { + throw new Error("Libraries must be provided if there are placeholders"); + } + for (const placeholder of placeholders) { const address = libraryMap.getAddress({ name: placeholder.name, path: placeholder.path, deployer }); bytecode = spliceHex(bytecode, placeholder.start, placeholder.length, address); diff --git a/packages/cli/src/verify.ts b/packages/cli/src/verify.ts index a1698b1488..c6e20663a0 100644 --- a/packages/cli/src/verify.ts +++ b/packages/cli/src/verify.ts @@ -105,7 +105,7 @@ export async function verify({ ); modules.map(({ name, prepareDeploy }) => { - const { address } = prepareDeploy(deployerAddress, {}); + const { address } = prepareDeploy(deployerAddress); return verifyQueue.add(() => verifyContract({ // TODO: figure out dir from artifactPath via import.meta.resolve?