@latticexyz/cli 2.0.0-next.16 → 2.0.0-next.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/{chunk-22IIKR4S.js → chunk-QXUPZVZL.js} +2 -2
  2. package/dist/chunk-QXUPZVZL.js.map +1 -0
  3. package/dist/commands-MGE24C7Z.js +36 -0
  4. package/dist/commands-MGE24C7Z.js.map +1 -0
  5. package/dist/errors-MZURIB7V.js +2 -0
  6. package/dist/mud.js +1 -1
  7. package/dist/mud.js.map +1 -1
  8. package/package.json +17 -16
  9. package/src/build.ts +9 -5
  10. package/src/commands/build.ts +2 -3
  11. package/src/commands/deploy.ts +1 -1
  12. package/src/commands/dev-contracts.ts +7 -5
  13. package/src/commands/set-version.ts +1 -1
  14. package/src/commands/tablegen.ts +2 -2
  15. package/src/commands/trace.ts +7 -5
  16. package/src/commands/worldgen.ts +7 -6
  17. package/src/deploy/common.ts +54 -8
  18. package/src/deploy/configToTables.ts +8 -6
  19. package/src/deploy/create2/README.md +4 -0
  20. package/src/deploy/create2/deployment.json +2 -1
  21. package/src/deploy/createPrepareDeploy.ts +28 -0
  22. package/src/deploy/deploy.ts +36 -16
  23. package/src/deploy/deployWorld.ts +9 -4
  24. package/src/deploy/ensureContract.ts +12 -7
  25. package/src/deploy/ensureContractsDeployed.ts +9 -1
  26. package/src/deploy/ensureDeployer.ts +61 -22
  27. package/src/deploy/ensureFunctions.ts +5 -5
  28. package/src/deploy/ensureModules.ts +16 -10
  29. package/src/deploy/ensureNamespaceOwner.ts +4 -4
  30. package/src/deploy/ensureSystems.ts +108 -83
  31. package/src/deploy/ensureTables.ts +8 -9
  32. package/src/deploy/ensureWorldFactory.ts +83 -98
  33. package/src/deploy/findLibraries.ts +36 -0
  34. package/src/deploy/getFunctions.ts +5 -5
  35. package/src/deploy/getResourceAccess.ts +3 -3
  36. package/src/deploy/getSystems.ts +6 -7
  37. package/src/deploy/getTableValue.ts +1 -1
  38. package/src/deploy/getTables.ts +2 -2
  39. package/src/deploy/logsToWorldDeploy.ts +4 -4
  40. package/src/deploy/orderByDependencies.ts +12 -0
  41. package/src/deploy/resolveConfig.ts +56 -59
  42. package/src/mud.ts +1 -1
  43. package/src/mudPackages.ts +1 -1
  44. package/src/runDeploy.ts +39 -11
  45. package/src/utils/{modules/constants.ts → defaultModuleContracts.ts} +4 -0
  46. package/src/utils/errors.ts +1 -1
  47. package/src/utils/findPlaceholders.ts +27 -0
  48. package/src/utils/{utils/getContractData.ts → getContractData.ts} +11 -5
  49. package/src/utils/{utils/postDeploy.ts → postDeploy.ts} +2 -2
  50. package/src/utils/printMUD.ts +1 -1
  51. package/dist/chunk-22IIKR4S.js.map +0 -1
  52. package/dist/commands-DSIRJZEM.js +0 -27
  53. package/dist/commands-DSIRJZEM.js.map +0 -1
  54. package/dist/errors-XGN6V2Y3.js +0 -2
  55. package/src/deploy/resourceLabel.ts +0 -3
  56. /package/dist/{errors-XGN6V2Y3.js.map → errors-MZURIB7V.js.map} +0 -0
@@ -1,7 +1,6 @@
1
1
  import type { CommandModule } from "yargs";
2
2
  import { loadConfig } from "@latticexyz/config/node";
3
- import { StoreConfig } from "@latticexyz/store";
4
- import { WorldConfig } from "@latticexyz/world";
3
+ import { World as WorldConfig } from "@latticexyz/world";
5
4
 
6
5
  import { getSrcDirectory } from "@latticexyz/common/foundry";
7
6
  import { build } from "../build";
@@ -24,7 +23,7 @@ const commandModule: CommandModule<Options, Options> = {
24
23
  },
25
24
 
26
25
  async handler({ configPath, profile }) {
27
- const config = (await loadConfig(configPath)) as StoreConfig & WorldConfig;
26
+ const config = (await loadConfig(configPath)) as WorldConfig;
28
27
  const srcDir = await getSrcDirectory();
29
28
 
30
29
  await build({ config, srcDir, foundryProfile: profile });
@@ -15,7 +15,7 @@ const commandModule: CommandModule<typeof deployOptions, DeployOptions> = {
15
15
  // Wrap in try/catch, because yargs seems to swallow errors
16
16
  try {
17
17
  await runDeploy(opts);
18
- } catch (error: any) {
18
+ } catch (error) {
19
19
  logError(error);
20
20
  process.exit(1);
21
21
  }
@@ -3,9 +3,8 @@ import { anvil, getScriptDirectory, getSrcDirectory } from "@latticexyz/common/f
3
3
  import chalk from "chalk";
4
4
  import chokidar from "chokidar";
5
5
  import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
6
- import { StoreConfig } from "@latticexyz/store";
7
6
  import path from "path";
8
- import { WorldConfig } from "@latticexyz/world";
7
+ import { World as WorldConfig } from "@latticexyz/world";
9
8
  import { homedir } from "os";
10
9
  import { rmSync } from "fs";
11
10
  import { deployOptions, runDeploy } from "../runDeploy";
@@ -34,7 +33,7 @@ const commandModule: CommandModule<typeof devOptions, InferredOptionTypes<typeof
34
33
  const configPath = opts.configPath ?? (await resolveConfigPath(opts.configPath));
35
34
  const srcDir = await getSrcDirectory();
36
35
  const scriptDir = await getScriptDirectory();
37
- const initialConfig = (await loadConfig(configPath)) as StoreConfig & WorldConfig;
36
+ const initialConfig = (await loadConfig(configPath)) as WorldConfig;
38
37
 
39
38
  // Start an anvil instance in the background if no RPC url is provided
40
39
  if (!opts.rpc) {
@@ -61,7 +60,7 @@ const commandModule: CommandModule<typeof devOptions, InferredOptionTypes<typeof
61
60
  }
62
61
  if (updatePath.includes(srcDir) || updatePath.includes(scriptDir)) {
63
62
  // Ignore changes to codegen files to avoid an infinite loop
64
- if (!updatePath.includes(initialConfig.codegenDirectory)) {
63
+ if (!updatePath.includes(initialConfig.codegen.outputDirectory)) {
65
64
  console.log(chalk.blue("Contracts changed, queuing deploy…"));
66
65
  lastChange$.next(Date.now());
67
66
  }
@@ -83,12 +82,15 @@ const commandModule: CommandModule<typeof devOptions, InferredOptionTypes<typeof
83
82
  ...opts,
84
83
  configPath,
85
84
  rpc,
85
+ rpcBatch: false,
86
86
  skipBuild: false,
87
87
  printConfig: false,
88
88
  profile: undefined,
89
89
  saveDeployment: true,
90
+ deployerAddress: undefined,
90
91
  worldAddress,
91
92
  srcDir,
93
+ salt: "0x",
92
94
  });
93
95
  worldAddress = deploy.address;
94
96
  // if there were changes while we were deploying, trigger it again
@@ -104,7 +106,7 @@ const commandModule: CommandModule<typeof devOptions, InferredOptionTypes<typeof
104
106
  console.log(chalk.gray("\nWaiting for file changes…\n"));
105
107
  }
106
108
  }),
107
- filter(isDefined)
109
+ filter(isDefined),
108
110
  );
109
111
 
110
112
  deploys$.subscribe();
@@ -45,7 +45,7 @@ const commandModule: CommandModule<Options, Options> = {
45
45
  const mutuallyExclusiveOptions = ["mudVersion", "link", "tag", "commit", "restore"];
46
46
  const numMutuallyExclusiveOptions = mutuallyExclusiveOptions.reduce(
47
47
  (acc, opt) => (options[opt] ? acc + 1 : acc),
48
- 0
48
+ 0,
49
49
  );
50
50
 
51
51
  if (numMutuallyExclusiveOptions === 0) {
@@ -1,7 +1,7 @@
1
1
  import path from "path";
2
2
  import type { CommandModule } from "yargs";
3
3
  import { loadConfig } from "@latticexyz/config/node";
4
- import { StoreConfig } from "@latticexyz/store";
4
+ import { Store as StoreConfig } from "@latticexyz/store";
5
5
  import { tablegen } from "@latticexyz/store/codegen";
6
6
  import { getRemappings, getSrcDirectory } from "@latticexyz/common/foundry";
7
7
 
@@ -25,7 +25,7 @@ const commandModule: CommandModule<Options, Options> = {
25
25
  const srcDir = await getSrcDirectory();
26
26
  const remappings = await getRemappings();
27
27
 
28
- await tablegen(config, path.join(srcDir, config.codegenDirectory), remappings);
28
+ await tablegen(config, path.join(srcDir, config.codegen.outputDirectory), remappings);
29
29
 
30
30
  process.exit(0);
31
31
  },
@@ -5,20 +5,21 @@ import { ethers } from "ethers";
5
5
  import { loadConfig } from "@latticexyz/config/node";
6
6
  import { MUDError } from "@latticexyz/common/errors";
7
7
  import { cast, getRpcUrl, getSrcDirectory } from "@latticexyz/common/foundry";
8
- import { StoreConfig } from "@latticexyz/store";
9
- import { resolveWorldConfig, WorldConfig } from "@latticexyz/world";
8
+ import { resolveWorldConfig } from "@latticexyz/world/internal";
10
9
  import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" };
11
10
  import worldConfig from "@latticexyz/world/mud.config";
12
11
  import { resourceToHex } from "@latticexyz/common";
13
12
  import { getExistingContracts } from "../utils/getExistingContracts";
14
13
  import { createClient, http } from "viem";
15
14
  import { getChainId } from "viem/actions";
15
+ import { World as WorldConfig } from "@latticexyz/world";
16
+ import { worldToV1 } from "@latticexyz/world/config/v2";
16
17
 
17
18
  // TODO account for multiple namespaces (https://github.com/latticexyz/mud/issues/994)
18
19
  const systemsTableId = resourceToHex({
19
20
  type: "system",
20
21
  namespace: worldConfig.namespace,
21
- name: worldConfig.tables.Systems.name,
22
+ name: worldConfig.tables.world__Systems.name,
22
23
  });
23
24
 
24
25
  type Options = {
@@ -59,11 +60,12 @@ const commandModule: CommandModule<Options, Options> = {
59
60
  const existingContracts = getExistingContracts(srcDir);
60
61
 
61
62
  // Load the config
62
- const mudConfig = (await loadConfig(configPath)) as StoreConfig & WorldConfig;
63
+ const configV2 = (await loadConfig(configPath)) as WorldConfig;
64
+ const mudConfig = worldToV1(configV2);
63
65
 
64
66
  const resolvedConfig = resolveWorldConfig(
65
67
  mudConfig,
66
- existingContracts.map(({ basename }) => basename)
68
+ existingContracts.map(({ basename }) => basename),
67
69
  );
68
70
 
69
71
  // Get worldAddress either from args or from worldsFile
@@ -1,7 +1,6 @@
1
1
  import type { CommandModule } from "yargs";
2
2
  import { loadConfig } from "@latticexyz/config/node";
3
- import { StoreConfig } from "@latticexyz/store";
4
- import { WorldConfig } from "@latticexyz/world";
3
+ import { World as WorldConfig } from "@latticexyz/world";
5
4
  import { worldgen } from "@latticexyz/world/node";
6
5
  import { getSrcDirectory } from "@latticexyz/common/foundry";
7
6
  import path from "path";
@@ -12,7 +11,7 @@ type Options = {
12
11
  configPath?: string;
13
12
  clean?: boolean;
14
13
  srcDir?: string;
15
- config?: StoreConfig & WorldConfig;
14
+ config?: WorldConfig;
16
15
  };
17
16
 
18
17
  const commandModule: CommandModule<Options, Options> = {
@@ -43,12 +42,14 @@ export async function worldgenHandler(args: Options) {
43
42
  const existingContracts = getExistingContracts(srcDir);
44
43
 
45
44
  // Load the config
46
- const mudConfig = args.config ?? ((await loadConfig(args.configPath)) as StoreConfig & WorldConfig);
45
+ const mudConfig = args.config ?? ((await loadConfig(args.configPath)) as WorldConfig);
47
46
 
48
- const outputBaseDirectory = path.join(srcDir, mudConfig.codegenDirectory);
47
+ const outputBaseDirectory = path.join(srcDir, mudConfig.codegen.outputDirectory);
49
48
 
50
49
  // clear the worldgen directory
51
- if (args.clean) rmSync(path.join(outputBaseDirectory, mudConfig.worldgenDirectory), { recursive: true, force: true });
50
+ if (args.clean) {
51
+ rmSync(path.join(outputBaseDirectory, mudConfig.codegen.worldgenDirectory), { recursive: true, force: true });
52
+ }
52
53
 
53
54
  // generate new interfaces
54
55
  await worldgen(mudConfig, existingContracts, outputBaseDirectory);
@@ -4,8 +4,12 @@ import worldConfig from "@latticexyz/world/mud.config";
4
4
  import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" };
5
5
  import IModuleAbi from "@latticexyz/world-modules/out/IModule.sol/IModule.abi.json" assert { type: "json" };
6
6
  import { Tables, configToTables } from "./configToTables";
7
- import { StoreConfig, helloStoreEvent } from "@latticexyz/store";
8
- import { WorldConfig, helloWorldEvent } from "@latticexyz/world";
7
+ import { helloStoreEvent } from "@latticexyz/store";
8
+ import { StoreConfig } from "@latticexyz/store/internal";
9
+ import { helloWorldEvent } from "@latticexyz/world";
10
+ import { WorldConfig } from "@latticexyz/world/internal";
11
+ import { storeToV1 } from "@latticexyz/store/config/v2";
12
+ import { worldToV1 } from "@latticexyz/world/config/v2";
9
13
 
10
14
  export const salt = padHex("0x", { size: 32 });
11
15
 
@@ -13,17 +17,18 @@ export const salt = padHex("0x", { size: 32 });
13
17
  export const contractSizeLimit = parseInt("6000", 16);
14
18
 
15
19
  // TODO: add `as const` to mud config so these get more strongly typed (blocked by current config parsing not using readonly)
16
- export const storeTables = configToTables(storeConfig);
17
- export const worldTables = configToTables(worldConfig);
20
+ export const storeTables = configToTables(storeToV1(storeConfig));
21
+ export const worldTables = configToTables(worldToV1(worldConfig));
18
22
 
19
23
  export const worldDeployEvents = [helloStoreEvent, helloWorldEvent] as const;
20
24
 
21
25
  export const worldAbi = [...IBaseWorldAbi, ...IModuleAbi] as const;
22
26
 
23
27
  // Ideally, this should be an append-only list. Before adding more versions here, be sure to add backwards-compatible support for old Store/World versions.
24
- export const supportedStoreVersions = ["1.0.0-unaudited"];
25
- export const supportedWorldVersions = ["1.0.0-unaudited"];
28
+ export const supportedStoreVersions = ["2.0.0"];
29
+ export const supportedWorldVersions = ["2.0.0"];
26
30
 
31
+ // TODO: extend this to include factory+deployer address? so we can reuse the deployer for a world?
27
32
  export type WorldDeploy = {
28
33
  readonly address: Address;
29
34
  readonly worldVersion: string;
@@ -46,22 +51,62 @@ export type WorldFunction = {
46
51
  readonly systemFunctionSelector: Hex;
47
52
  };
48
53
 
54
+ export type LibraryPlaceholder = {
55
+ /**
56
+ * Path to library source file, e.g. `src/libraries/SomeLib.sol`
57
+ */
58
+ path: string;
59
+ /**
60
+ * Library name, e.g. `SomeLib`
61
+ */
62
+ name: string;
63
+ /**
64
+ * Byte offset of placeholder in bytecode
65
+ */
66
+ start: number;
67
+ /**
68
+ * Size of placeholder to replace in bytes
69
+ */
70
+ length: number;
71
+ };
72
+
49
73
  export type DeterministicContract = {
50
- readonly address: Address;
51
- readonly bytecode: Hex;
74
+ readonly prepareDeploy: (
75
+ deployer: Address,
76
+ libraries: readonly Library[],
77
+ ) => {
78
+ readonly address: Address;
79
+ readonly bytecode: Hex;
80
+ };
52
81
  readonly deployedBytecodeSize: number;
53
82
  readonly abi: Abi;
54
83
  };
55
84
 
85
+ export type Library = DeterministicContract & {
86
+ /**
87
+ * Path to library source file, e.g. `src/libraries/SomeLib.sol`
88
+ */
89
+ path: string;
90
+ /**
91
+ * Library name, e.g. `SomeLib`
92
+ */
93
+ name: string;
94
+ };
95
+
56
96
  export type System = DeterministicContract & {
57
97
  readonly namespace: string;
58
98
  readonly name: string;
59
99
  readonly systemId: Hex;
60
100
  readonly allowAll: boolean;
61
101
  readonly allowedAddresses: readonly Hex[];
102
+ readonly allowedSystemIds: readonly Hex[];
62
103
  readonly functions: readonly WorldFunction[];
63
104
  };
64
105
 
106
+ export type DeployedSystem = Omit<System, "abi" | "prepareDeploy" | "deployedBytecodeSize" | "allowedSystemIds"> & {
107
+ address: Address;
108
+ };
109
+
65
110
  export type Module = DeterministicContract & {
66
111
  readonly name: string;
67
112
  readonly installAsRoot: boolean;
@@ -73,4 +118,5 @@ export type Config<config extends ConfigInput> = {
73
118
  readonly tables: Tables<config>;
74
119
  readonly systems: readonly System[];
75
120
  readonly modules: readonly Module[];
121
+ readonly libraries: readonly Library[];
76
122
  };
@@ -1,7 +1,7 @@
1
1
  import { resourceToHex } from "@latticexyz/common";
2
- import { KeySchema, ValueSchema } from "@latticexyz/protocol-parser";
3
- import { SchemaAbiType, StaticAbiType } from "@latticexyz/schema-type";
4
- import { StoreConfig, resolveUserTypes } from "@latticexyz/store";
2
+ import { KeySchema, ValueSchema } from "@latticexyz/protocol-parser/internal";
3
+ import { SchemaAbiType, StaticAbiType } from "@latticexyz/schema-type/internal";
4
+ import { StoreConfig, resolveUserTypes } from "@latticexyz/store/internal";
5
5
  import { Hex } from "viem";
6
6
 
7
7
  // TODO: we shouldn't need this file once our config parsing returns nicely formed tables
@@ -14,12 +14,12 @@ type UserTypes<config extends StoreConfig = StoreConfig> = config["userTypes"];
14
14
 
15
15
  export type TableKey<
16
16
  config extends StoreConfig = StoreConfig,
17
- table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]]
17
+ table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]],
18
18
  > = `${config["namespace"]}_${table["name"]}`;
19
19
 
20
20
  export type Table<
21
21
  config extends StoreConfig = StoreConfig,
22
- table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]]
22
+ table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]],
23
23
  > = {
24
24
  readonly namespace: config["namespace"];
25
25
  readonly name: table["name"];
@@ -60,9 +60,11 @@ export function configToTables<config extends StoreConfig>(config: config): Tabl
60
60
  namespace: config.namespace,
61
61
  name: table.name,
62
62
  }),
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
64
  keySchema: resolveUserTypes(table.keySchema, userTypes) as any,
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
66
  valueSchema: resolveUserTypes(table.valueSchema, userTypes) as any,
65
67
  } satisfies Table<config, config["tables"][keyof config["tables"]]>,
66
- ])
68
+ ]),
67
69
  ) as Tables<config>;
68
70
  }
@@ -6,4 +6,8 @@ cd deterministic-deployment-proxy
6
6
  git checkout b3bb19c
7
7
  npm install
8
8
  npm run build
9
+ cd output
10
+ jq --arg bc "$(cat bytecode.txt)" '. + {bytecode: $bc}' deployment.json > deployment-with-bytecode.json
11
+ mv deployment-with-bytecode.json deployment.json
12
+ cp deployment.json ../path/to/this/dir
9
13
  ```
@@ -3,5 +3,6 @@
3
3
  "gasLimit": 100000,
4
4
  "signerAddress": "3fab184622dc19b6109349b94811493bf2a45362",
5
5
  "transaction": "f8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222",
6
- "address": "4e59b44847b379578588920ca78fbf26c0b4956c"
6
+ "address": "4e59b44847b379578588920ca78fbf26c0b4956c",
7
+ "bytecode": "604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
7
8
  }
@@ -0,0 +1,28 @@
1
+ import { DeterministicContract, Library, LibraryPlaceholder, salt } from "./common";
2
+ import { spliceHex } from "@latticexyz/common";
3
+ import { Hex, getCreate2Address, Address } from "viem";
4
+
5
+ export function createPrepareDeploy(
6
+ bytecodeWithPlaceholders: Hex,
7
+ placeholders: readonly LibraryPlaceholder[],
8
+ ): DeterministicContract["prepareDeploy"] {
9
+ return function prepareDeploy(deployer: Address, libraries: readonly Library[]) {
10
+ let bytecode = bytecodeWithPlaceholders;
11
+ for (const placeholder of placeholders) {
12
+ const library = libraries.find((lib) => lib.path === placeholder.path && lib.name === placeholder.name);
13
+ if (!library) {
14
+ throw new Error(`Could not find library for bytecode placeholder ${placeholder.path}:${placeholder.name}`);
15
+ }
16
+ bytecode = spliceHex(
17
+ bytecode,
18
+ placeholder.start,
19
+ placeholder.length,
20
+ library.prepareDeploy(deployer, libraries).address,
21
+ );
22
+ }
23
+ return {
24
+ bytecode,
25
+ address: getCreate2Address({ from: deployer, bytecode, salt }),
26
+ };
27
+ };
28
+ }
@@ -1,4 +1,4 @@
1
- import { Account, Address, Chain, Client, Transport, getAddress } from "viem";
1
+ import { Account, Address, Chain, Client, Hex, Transport } from "viem";
2
2
  import { ensureDeployer } from "./ensureDeployer";
3
3
  import { deployWorld } from "./deployWorld";
4
4
  import { ensureTables } from "./ensureTables";
@@ -11,15 +11,23 @@ import { ensureModules } from "./ensureModules";
11
11
  import { Table } from "./configToTables";
12
12
  import { ensureNamespaceOwner } from "./ensureNamespaceOwner";
13
13
  import { debug } from "./debug";
14
- import { resourceLabel } from "./resourceLabel";
15
- import { uniqueBy } from "@latticexyz/common/utils";
14
+ import { resourceToLabel } from "@latticexyz/common";
16
15
  import { ensureContractsDeployed } from "./ensureContractsDeployed";
17
- import { worldFactoryContracts } from "./ensureWorldFactory";
16
+ import { randomBytes } from "crypto";
17
+ import { ensureWorldFactory } from "./ensureWorldFactory";
18
18
 
19
19
  type DeployOptions<configInput extends ConfigInput> = {
20
20
  client: Client<Transport, Chain | undefined, Account>;
21
21
  config: Config<configInput>;
22
+ salt?: Hex;
22
23
  worldAddress?: Address;
24
+ /**
25
+ * Address of determinstic deployment proxy: https://github.com/Arachnid/deterministic-deployment-proxy
26
+ * By default, we look for a deployment at 0x4e59b44847b379578588920ca78fbf26c0b4956c and, if not, deploy one.
27
+ * If the target chain does not support legacy transactions, we deploy the proxy bytecode anyway, but it will
28
+ * not have a deterministic address.
29
+ */
30
+ deployerAddress?: Hex;
23
31
  };
24
32
 
25
33
  /**
@@ -31,25 +39,33 @@ type DeployOptions<configInput extends ConfigInput> = {
31
39
  export async function deploy<configInput extends ConfigInput>({
32
40
  client,
33
41
  config,
42
+ salt,
34
43
  worldAddress: existingWorldAddress,
44
+ deployerAddress: initialDeployerAddress,
35
45
  }: DeployOptions<configInput>): Promise<WorldDeploy> {
36
46
  const tables = Object.values(config.tables) as Table[];
37
- const systems = Object.values(config.systems);
38
47
 
39
- await ensureDeployer(client);
48
+ const deployerAddress = initialDeployerAddress ?? (await ensureDeployer(client));
49
+
50
+ await ensureWorldFactory(client, deployerAddress);
40
51
 
41
52
  // deploy all dependent contracts, because system registration, module install, etc. all expect these contracts to be callable.
42
53
  await ensureContractsDeployed({
43
54
  client,
55
+ deployerAddress,
44
56
  contracts: [
45
- ...worldFactoryContracts,
46
- ...uniqueBy(systems, (system) => getAddress(system.address)).map((system) => ({
47
- bytecode: system.bytecode,
57
+ ...config.libraries.map((library) => ({
58
+ bytecode: library.prepareDeploy(deployerAddress, config.libraries).bytecode,
59
+ deployedBytecodeSize: library.deployedBytecodeSize,
60
+ label: `${library.path}:${library.name} library`,
61
+ })),
62
+ ...config.systems.map((system) => ({
63
+ bytecode: system.prepareDeploy(deployerAddress, config.libraries).bytecode,
48
64
  deployedBytecodeSize: system.deployedBytecodeSize,
49
- label: `${resourceLabel(system)} system`,
65
+ label: `${resourceToLabel(system)} system`,
50
66
  })),
51
- ...uniqueBy(config.modules, (mod) => getAddress(mod.address)).map((mod) => ({
52
- bytecode: mod.bytecode,
67
+ ...config.modules.map((mod) => ({
68
+ bytecode: mod.prepareDeploy(deployerAddress, config.libraries).bytecode,
53
69
  deployedBytecodeSize: mod.deployedBytecodeSize,
54
70
  label: `${mod.name} module`,
55
71
  })),
@@ -58,7 +74,7 @@ export async function deploy<configInput extends ConfigInput>({
58
74
 
59
75
  const worldDeploy = existingWorldAddress
60
76
  ? await getWorldDeploy(client, existingWorldAddress)
61
- : await deployWorld(client);
77
+ : await deployWorld(client, deployerAddress, salt ?? `0x${randomBytes(32).toString("hex")}`);
62
78
 
63
79
  if (!supportedStoreVersions.includes(worldDeploy.storeVersion)) {
64
80
  throw new Error(`Unsupported Store version: ${worldDeploy.storeVersion}`);
@@ -70,7 +86,7 @@ export async function deploy<configInput extends ConfigInput>({
70
86
  const namespaceTxs = await ensureNamespaceOwner({
71
87
  client,
72
88
  worldDeploy,
73
- resourceIds: [...tables.map((table) => table.tableId), ...systems.map((system) => system.systemId)],
89
+ resourceIds: [...tables.map((table) => table.tableId), ...config.systems.map((system) => system.systemId)],
74
90
  });
75
91
 
76
92
  debug("waiting for all namespace registration transactions to confirm");
@@ -85,16 +101,20 @@ export async function deploy<configInput extends ConfigInput>({
85
101
  });
86
102
  const systemTxs = await ensureSystems({
87
103
  client,
104
+ deployerAddress,
105
+ libraries: config.libraries,
88
106
  worldDeploy,
89
- systems,
107
+ systems: config.systems,
90
108
  });
91
109
  const functionTxs = await ensureFunctions({
92
110
  client,
93
111
  worldDeploy,
94
- functions: systems.flatMap((system) => system.functions),
112
+ functions: config.systems.flatMap((system) => system.functions),
95
113
  });
96
114
  const moduleTxs = await ensureModules({
97
115
  client,
116
+ deployerAddress,
117
+ libraries: config.libraries,
98
118
  worldDeploy,
99
119
  modules: config.modules,
100
120
  });
@@ -1,14 +1,18 @@
1
- import { Account, Chain, Client, Log, Transport } from "viem";
1
+ import { Account, Chain, Client, Hex, Log, Transport } from "viem";
2
2
  import { waitForTransactionReceipt } from "viem/actions";
3
- import { ensureWorldFactory, worldFactory } from "./ensureWorldFactory";
3
+ import { ensureWorldFactory } from "./ensureWorldFactory";
4
4
  import WorldFactoryAbi from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.abi.json" assert { type: "json" };
5
5
  import { writeContract } from "@latticexyz/common";
6
6
  import { debug } from "./debug";
7
7
  import { logsToWorldDeploy } from "./logsToWorldDeploy";
8
8
  import { WorldDeploy } from "./common";
9
9
 
10
- export async function deployWorld(client: Client<Transport, Chain | undefined, Account>): Promise<WorldDeploy> {
11
- await ensureWorldFactory(client);
10
+ export async function deployWorld(
11
+ client: Client<Transport, Chain | undefined, Account>,
12
+ deployerAddress: Hex,
13
+ salt: Hex,
14
+ ): Promise<WorldDeploy> {
15
+ const worldFactory = await ensureWorldFactory(client, deployerAddress);
12
16
 
13
17
  debug("deploying world");
14
18
  const tx = await writeContract(client, {
@@ -16,6 +20,7 @@ export async function deployWorld(client: Client<Transport, Chain | undefined, A
16
20
  address: worldFactory,
17
21
  abi: WorldFactoryAbi,
18
22
  functionName: "deployWorld",
23
+ args: [salt],
19
24
  });
20
25
 
21
26
  debug("waiting for world deploy");
@@ -1,6 +1,5 @@
1
- import { Client, Transport, Chain, Account, concatHex, getCreate2Address, Hex, size } from "viem";
1
+ import { Client, Transport, Chain, Account, concatHex, getCreate2Address, Hex } from "viem";
2
2
  import { getBytecode } from "viem/actions";
3
- import { deployer } from "./ensureDeployer";
4
3
  import { contractSizeLimit, salt } from "./common";
5
4
  import { sendTransaction } from "@latticexyz/common";
6
5
  import { debug } from "./debug";
@@ -15,13 +14,19 @@ export type Contract = {
15
14
 
16
15
  export async function ensureContract({
17
16
  client,
17
+ deployerAddress,
18
18
  bytecode,
19
19
  deployedBytecodeSize,
20
20
  label = "contract",
21
21
  }: {
22
22
  readonly client: Client<Transport, Chain | undefined, Account>;
23
+ readonly deployerAddress: Hex;
23
24
  } & Contract): Promise<readonly Hex[]> {
24
- const address = getCreate2Address({ from: deployer, salt, bytecode });
25
+ if (bytecode.includes("__$")) {
26
+ throw new Error(`Found unlinked public library in ${label} bytecode`);
27
+ }
28
+
29
+ const address = getCreate2Address({ from: deployerAddress, salt, bytecode });
25
30
 
26
31
  const contractCode = await getBytecode(client, { address, blockTag: "pending" });
27
32
  if (contractCode) {
@@ -31,11 +36,11 @@ export async function ensureContract({
31
36
 
32
37
  if (deployedBytecodeSize > contractSizeLimit) {
33
38
  console.warn(
34
- `\nBytecode for ${label} (${deployedBytecodeSize} bytes) is over the contract size limit (${contractSizeLimit} bytes). Run \`forge build --sizes\` for more info.\n`
39
+ `\nBytecode for ${label} (${deployedBytecodeSize} bytes) is over the contract size limit (${contractSizeLimit} bytes). Run \`forge build --sizes\` for more info.\n`,
35
40
  );
36
41
  } else if (deployedBytecodeSize > contractSizeLimit * 0.95) {
37
42
  console.warn(
38
- `\nBytecode for ${label} (${deployedBytecodeSize} bytes) is almost over the contract size limit (${contractSizeLimit} bytes). Run \`forge build --sizes\` for more info.\n`
43
+ `\nBytecode for ${label} (${deployedBytecodeSize} bytes) is almost over the contract size limit (${contractSizeLimit} bytes). Run \`forge build --sizes\` for more info.\n`,
39
44
  );
40
45
  }
41
46
 
@@ -45,7 +50,7 @@ export async function ensureContract({
45
50
  () =>
46
51
  sendTransaction(client, {
47
52
  chain: client.chain ?? null,
48
- to: deployer,
53
+ to: deployerAddress,
49
54
  data: concatHex([salt, bytecode]),
50
55
  }),
51
56
  {
@@ -55,7 +60,7 @@ export async function ensureContract({
55
60
  debug(`failed to deploy ${label}, retrying in ${delay}ms...`);
56
61
  await wait(delay);
57
62
  },
58
- }
63
+ },
59
64
  ),
60
65
  ];
61
66
  }
@@ -2,15 +2,23 @@ import { Client, Transport, Chain, Account, Hex } from "viem";
2
2
  import { waitForTransactionReceipt } from "viem/actions";
3
3
  import { debug } from "./debug";
4
4
  import { Contract, ensureContract } from "./ensureContract";
5
+ import { uniqueBy } from "@latticexyz/common/utils";
5
6
 
6
7
  export async function ensureContractsDeployed({
7
8
  client,
9
+ deployerAddress,
8
10
  contracts,
9
11
  }: {
10
12
  readonly client: Client<Transport, Chain | undefined, Account>;
13
+ readonly deployerAddress: Hex;
11
14
  readonly contracts: readonly Contract[];
12
15
  }): Promise<readonly Hex[]> {
13
- const txs = (await Promise.all(contracts.map((contract) => ensureContract({ client, ...contract })))).flat();
16
+ // Deployments assume a deterministic deployer, so we only need to deploy the unique bytecode
17
+ const uniqueContracts = uniqueBy(contracts, (contract) => contract.bytecode);
18
+
19
+ const txs = (
20
+ await Promise.all(uniqueContracts.map((contract) => ensureContract({ client, deployerAddress, ...contract })))
21
+ ).flat();
14
22
 
15
23
  if (txs.length) {
16
24
  debug("waiting for contracts");