@latticexyz/cli 2.0.0-snapshot-test-32d38619 → 2.0.0

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 (85) hide show
  1. package/dist/chunk-QXUPZVZL.js +4 -0
  2. package/dist/chunk-QXUPZVZL.js.map +1 -0
  3. package/dist/commands-WWEJJZV4.js +36 -0
  4. package/dist/commands-WWEJJZV4.js.map +1 -0
  5. package/dist/errors-MZURIB7V.js +2 -0
  6. package/dist/errors-MZURIB7V.js.map +1 -0
  7. package/dist/index.js +0 -1
  8. package/dist/mud.js +1 -14
  9. package/dist/mud.js.map +1 -1
  10. package/package.json +21 -15
  11. package/src/build.ts +48 -0
  12. package/src/commands/build.ts +35 -0
  13. package/src/commands/deploy.ts +8 -31
  14. package/src/commands/dev-contracts.ts +80 -141
  15. package/src/commands/index.ts +2 -0
  16. package/src/commands/set-version.ts +24 -62
  17. package/src/commands/tablegen.ts +2 -2
  18. package/src/commands/test.ts +30 -36
  19. package/src/commands/trace.ts +15 -11
  20. package/src/commands/worldgen.ts +7 -6
  21. package/src/common.ts +1 -0
  22. package/src/debug.ts +10 -0
  23. package/src/deploy/common.ts +122 -0
  24. package/src/deploy/configToTables.ts +70 -0
  25. package/src/deploy/create2/README.md +13 -0
  26. package/src/deploy/create2/deployment.json +8 -0
  27. package/src/deploy/createPrepareDeploy.ts +28 -0
  28. package/src/deploy/debug.ts +10 -0
  29. package/src/deploy/deploy.ts +133 -0
  30. package/src/deploy/deployWorld.ts +38 -0
  31. package/src/deploy/ensureContract.ts +66 -0
  32. package/src/deploy/ensureContractsDeployed.ts +33 -0
  33. package/src/deploy/ensureDeployer.ts +75 -0
  34. package/src/deploy/ensureFunctions.ts +86 -0
  35. package/src/deploy/ensureModules.ts +79 -0
  36. package/src/deploy/ensureNamespaceOwner.ts +71 -0
  37. package/src/deploy/ensureSystems.ts +187 -0
  38. package/src/deploy/ensureTables.ts +64 -0
  39. package/src/deploy/ensureWorldFactory.ts +105 -0
  40. package/src/deploy/findLibraries.ts +36 -0
  41. package/src/deploy/getFunctions.ts +58 -0
  42. package/src/deploy/getResourceAccess.ts +51 -0
  43. package/src/deploy/getResourceIds.ts +31 -0
  44. package/src/deploy/getSystems.ts +47 -0
  45. package/src/deploy/getTableValue.ts +30 -0
  46. package/src/deploy/getTables.ts +59 -0
  47. package/src/deploy/getWorldDeploy.ts +39 -0
  48. package/src/deploy/logsToWorldDeploy.ts +49 -0
  49. package/src/deploy/orderByDependencies.ts +12 -0
  50. package/src/deploy/resolveConfig.ts +148 -0
  51. package/src/index.ts +1 -1
  52. package/src/mud.ts +37 -31
  53. package/src/mudPackages.ts +24 -0
  54. package/src/runDeploy.ts +149 -0
  55. package/src/utils/defaultModuleContracts.ts +30 -0
  56. package/src/utils/errors.ts +1 -1
  57. package/src/utils/findPlaceholders.ts +27 -0
  58. package/src/utils/getContractData.ts +38 -0
  59. package/src/utils/{utils/postDeploy.ts → postDeploy.ts} +2 -2
  60. package/src/utils/printMUD.ts +1 -1
  61. package/dist/chunk-WERDORTY.js +0 -11
  62. package/dist/chunk-WERDORTY.js.map +0 -1
  63. package/src/utils/deploy.ts +0 -255
  64. package/src/utils/deployHandler.ts +0 -93
  65. package/src/utils/modules/constants.ts +0 -23
  66. package/src/utils/modules/getInstallModuleCallData.ts +0 -27
  67. package/src/utils/modules/getUserModules.ts +0 -5
  68. package/src/utils/modules/types.ts +0 -14
  69. package/src/utils/systems/getGrantAccessCallData.ts +0 -29
  70. package/src/utils/systems/getRegisterFunctionSelectorsCallData.ts +0 -57
  71. package/src/utils/systems/getRegisterSystemCallData.ts +0 -17
  72. package/src/utils/systems/types.ts +0 -9
  73. package/src/utils/systems/utils.ts +0 -42
  74. package/src/utils/tables/getRegisterTableCallData.ts +0 -49
  75. package/src/utils/tables/getTableIds.ts +0 -21
  76. package/src/utils/tables/types.ts +0 -12
  77. package/src/utils/utils/confirmNonce.ts +0 -24
  78. package/src/utils/utils/deployContract.ts +0 -33
  79. package/src/utils/utils/fastTxExecute.ts +0 -56
  80. package/src/utils/utils/getChainId.ts +0 -10
  81. package/src/utils/utils/getContractData.ts +0 -29
  82. package/src/utils/utils/setInternalFeePerGas.ts +0 -49
  83. package/src/utils/utils/toBytes16.ts +0 -16
  84. package/src/utils/utils/types.ts +0 -21
  85. package/src/utils/world.ts +0 -28
@@ -3,6 +3,7 @@ import { CommandModule } from "yargs";
3
3
  import gasReport from "@latticexyz/gas-report";
4
4
  import abiTs from "@latticexyz/abi-ts";
5
5
 
6
+ import build from "./build";
6
7
  import devnode from "./devnode";
7
8
  import faucet from "./faucet";
8
9
  import hello from "./hello";
@@ -16,6 +17,7 @@ import devContracts from "./dev-contracts";
16
17
 
17
18
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Each command has different options
18
19
  export const commands: CommandModule<any, any>[] = [
20
+ build,
19
21
  deploy,
20
22
  devnode,
21
23
  faucet,
@@ -1,11 +1,12 @@
1
1
  import chalk from "chalk";
2
- import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
2
+ import { readFileSync, writeFileSync } from "fs";
3
3
  import path from "path";
4
4
  import type { CommandModule } from "yargs";
5
5
  import { MUDError } from "@latticexyz/common/errors";
6
6
  import { logError } from "../utils/errors";
7
7
  import localPackageJson from "../../package.json" assert { type: "json" };
8
8
  import glob from "glob";
9
+ import { mudPackages } from "../mudPackages";
9
10
 
10
11
  type Options = {
11
12
  backup?: boolean;
@@ -17,9 +18,6 @@ type Options = {
17
18
  link?: string;
18
19
  };
19
20
 
20
- const BACKUP_FILE = ".mudbackup";
21
- const MUD_PREFIX = "@latticexyz";
22
-
23
21
  const commandModule: CommandModule<Options, Options> = {
24
22
  command: "set-version",
25
23
 
@@ -27,12 +25,6 @@ const commandModule: CommandModule<Options, Options> = {
27
25
 
28
26
  builder(yargs) {
29
27
  return yargs.options({
30
- backup: { type: "boolean", description: `Back up the current MUD versions to "${BACKUP_FILE}"` },
31
- force: {
32
- type: "boolean",
33
- description: `Backup fails if a "${BACKUP_FILE}" file is found, unless --force is provided`,
34
- },
35
- restore: { type: "boolean", description: `Restore the previous MUD versions from "${BACKUP_FILE}"` },
36
28
  mudVersion: { alias: "v", type: "string", description: "Set MUD to the given version" },
37
29
  tag: {
38
30
  alias: "t",
@@ -53,7 +45,7 @@ const commandModule: CommandModule<Options, Options> = {
53
45
  const mutuallyExclusiveOptions = ["mudVersion", "link", "tag", "commit", "restore"];
54
46
  const numMutuallyExclusiveOptions = mutuallyExclusiveOptions.reduce(
55
47
  (acc, opt) => (options[opt] ? acc + 1 : acc),
56
- 0
48
+ 0,
57
49
  );
58
50
 
59
51
  if (numMutuallyExclusiveOptions === 0) {
@@ -64,8 +56,11 @@ const commandModule: CommandModule<Options, Options> = {
64
56
  throw new MUDError(`These options are mutually exclusive: ${mutuallyExclusiveOptions.join(", ")}`);
65
57
  }
66
58
 
59
+ // If the --link flag is not set, we call resolveVersion to get the version
67
60
  // Resolve the version number from available options like `tag` or `commit`
68
- options.mudVersion = await resolveVersion(options);
61
+ if (!options.link) {
62
+ options.mudVersion = await resolveVersion(options);
63
+ }
69
64
 
70
65
  // Update all package.json below the current working directory (except in node_modules)
71
66
  const packageJsons = glob.sync("**/package.json").filter((p) => !p.includes("node_modules"));
@@ -117,63 +112,39 @@ async function resolveVersion(options: Options) {
117
112
  }
118
113
 
119
114
  function updatePackageJson(filePath: string, options: Options): { workspaces?: string[] } {
120
- const { restore, force, link } = options;
121
- let { backup, mudVersion } = options;
122
-
123
- const backupFilePath = path.join(path.dirname(filePath), BACKUP_FILE);
124
- const backupFileExists = existsSync(backupFilePath);
125
-
126
- // Create a backup file for previous MUD versions by default if linking to local MUD
127
- if (link && !backupFileExists) backup = true;
128
-
129
- // If `backup` is true and force not set, check if a backup file already exists and throw an error if it does
130
- if (backup && !force && backupFileExists) {
131
- throw new MUDError(
132
- `A backup file already exists at ${backupFilePath}.\nUse --force to overwrite it or --restore to restore it.`
133
- );
134
- }
115
+ const { link } = options;
116
+ let { mudVersion } = options;
135
117
 
136
118
  const packageJson = readPackageJson(filePath);
137
-
138
- // Load .mudbackup if `restore` is true
139
- const backupJson = restore ? readPackageJson(backupFilePath) : undefined;
119
+ const mudPackageNames = Object.keys(mudPackages);
140
120
 
141
121
  // Find all MUD dependencies
142
122
  const mudDependencies: Record<string, string> = {};
143
- for (const key in packageJson.dependencies) {
144
- if (key.startsWith(MUD_PREFIX)) {
145
- mudDependencies[key] = packageJson.dependencies[key];
123
+ for (const packageName in packageJson.dependencies) {
124
+ if (mudPackageNames.includes(packageName)) {
125
+ mudDependencies[packageName] = packageJson.dependencies[packageName];
146
126
  }
147
127
  }
148
128
 
149
129
  // Find all MUD devDependencies
150
130
  const mudDevDependencies: Record<string, string> = {};
151
- for (const key in packageJson.devDependencies) {
152
- if (key.startsWith(MUD_PREFIX)) {
153
- mudDevDependencies[key] = packageJson.devDependencies[key];
131
+ for (const packageName in packageJson.devDependencies) {
132
+ if (mudPackageNames.includes(packageName)) {
133
+ mudDevDependencies[packageName] = packageJson.devDependencies[packageName];
154
134
  }
155
135
  }
156
136
 
157
- // Back up the current dependencies if `backup` is true
158
- if (backup) {
159
- writeFileSync(
160
- backupFilePath,
161
- JSON.stringify({ dependencies: mudDependencies, devDependencies: mudDevDependencies }, null, 2)
162
- );
163
- console.log(chalk.green(`Backed up MUD dependencies from ${filePath} to ${backupFilePath}`));
164
- }
165
-
166
137
  // Update the dependencies
167
- for (const key in packageJson.dependencies) {
168
- if (key.startsWith(MUD_PREFIX)) {
169
- packageJson.dependencies[key] = resolveMudVersion(key, "dependencies");
138
+ for (const packageName in packageJson.dependencies) {
139
+ if (mudPackageNames.includes(packageName)) {
140
+ packageJson.dependencies[packageName] = resolveMudVersion(packageName, "dependencies");
170
141
  }
171
142
  }
172
143
 
173
144
  // Update the devDependencies
174
- for (const key in packageJson.devDependencies) {
175
- if (key.startsWith(MUD_PREFIX)) {
176
- packageJson.devDependencies[key] = resolveMudVersion(key, "devDependencies");
145
+ for (const packageName in packageJson.devDependencies) {
146
+ if (mudPackageNames.includes(packageName)) {
147
+ packageJson.devDependencies[packageName] = resolveMudVersion(packageName, "devDependencies");
177
148
  }
178
149
  }
179
150
 
@@ -184,17 +155,9 @@ function updatePackageJson(filePath: string, options: Options): { workspaces?: s
184
155
  logComparison(mudDependencies, packageJson.dependencies);
185
156
  logComparison(mudDevDependencies, packageJson.devDependencies);
186
157
 
187
- // Remove the backup file if `restore` is true and `backup` is false
188
- // because the old backup file is no longer needed
189
- if (restore && !backup) {
190
- rmSync(backupFilePath);
191
- console.log(chalk.green(`Cleaned up ${backupFilePath}`));
192
- }
193
-
194
158
  return packageJson;
195
159
 
196
160
  function resolveMudVersion(key: string, type: "dependencies" | "devDependencies") {
197
- if (restore && backupJson) return backupJson[type][key];
198
161
  if (link) mudVersion = resolveLinkPath(filePath, link, key);
199
162
  if (!mudVersion) return packageJson[type][key];
200
163
  return mudVersion;
@@ -225,10 +188,9 @@ function logComparison(prev: Record<string, string>, curr: Record<string, string
225
188
  /**
226
189
  * Returns path of the package to link, given a path to a local MUD clone and a package
227
190
  */
228
- function resolveLinkPath(packageJsonPath: string, mudLinkPath: string, pkg: string) {
229
- const pkgName = pkg.replace(MUD_PREFIX, "");
191
+ function resolveLinkPath(packageJsonPath: string, mudLinkPath: string, packageName: string) {
230
192
  const packageJsonToRootPath = path.relative(path.dirname(packageJsonPath), process.cwd());
231
- const linkPath = path.join(packageJsonToRootPath, mudLinkPath, "packages", pkgName);
193
+ const linkPath = path.join(packageJsonToRootPath, mudLinkPath, mudPackages[packageName].localPath);
232
194
  return "link:" + linkPath;
233
195
  }
234
196
 
@@ -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
  },
@@ -1,70 +1,64 @@
1
- import type { CommandModule } from "yargs";
1
+ import type { CommandModule, InferredOptionTypes, Options } from "yargs";
2
2
  import { anvil, forge, getRpcUrl } from "@latticexyz/common/foundry";
3
3
  import chalk from "chalk";
4
- import { rmSync, writeFileSync } from "fs";
5
- import { yDeployOptions } from "./deploy";
6
- import { DeployOptions, deployHandler } from "../utils/deployHandler";
4
+ import { deployOptions, runDeploy } from "../runDeploy";
7
5
 
8
- type Options = DeployOptions & { port?: number; worldAddress?: string; forgeOptions?: string };
6
+ const testOptions = {
7
+ ...deployOptions,
8
+ port: { type: "number", description: "Port to run internal node for fork testing on", default: 4242 },
9
+ worldAddress: {
10
+ type: "string",
11
+ description:
12
+ "Address of an existing world contract. If provided, deployment is skipped and the RPC provided in the foundry.toml is used for fork testing.",
13
+ },
14
+ forgeOptions: { type: "string", description: "Options to pass to forge test" },
15
+ } as const satisfies Record<string, Options>;
9
16
 
10
- const WORLD_ADDRESS_FILE = ".mudtest";
17
+ type TestOptions = InferredOptionTypes<typeof testOptions>;
11
18
 
12
- const commandModule: CommandModule<Options, Options> = {
19
+ const commandModule: CommandModule<typeof testOptions, TestOptions> = {
13
20
  command: "test",
14
21
 
15
22
  describe: "Run tests in MUD contracts",
16
23
 
17
24
  builder(yargs) {
18
- return yargs.options({
19
- ...yDeployOptions,
20
- port: { type: "number", description: "Port to run internal node for fork testing on", default: 4242 },
21
- worldAddress: {
22
- type: "string",
23
- description:
24
- "Address of an existing world contract. If provided, deployment is skipped and the RPC provided in the foundry.toml is used for fork testing.",
25
- },
26
- forgeOptions: { type: "string", description: "Options to pass to forge test" },
27
- });
25
+ return yargs.options(testOptions);
28
26
  },
29
27
 
30
- async handler(args) {
28
+ async handler(opts) {
31
29
  // Start an internal anvil process if no world address is provided
32
- if (!args.worldAddress) {
33
- const anvilArgs = ["--block-base-fee-per-gas", "0", "--port", String(args.port)];
30
+ if (!opts.worldAddress) {
31
+ const anvilArgs = ["--block-base-fee-per-gas", "0", "--port", String(opts.port)];
34
32
  anvil(anvilArgs);
35
33
  }
36
34
 
37
- const forkRpc = args.worldAddress ? await getRpcUrl(args.profile) : `http://127.0.0.1:${args.port}`;
35
+ const forkRpc = opts.worldAddress ? await getRpcUrl(opts.profile) : `http://127.0.0.1:${opts.port}`;
38
36
 
39
37
  const worldAddress =
40
- args.worldAddress ??
38
+ opts.worldAddress ??
41
39
  (
42
- await deployHandler({
43
- ...args,
40
+ await runDeploy({
41
+ ...opts,
44
42
  saveDeployment: false,
45
43
  rpc: forkRpc,
46
44
  })
47
- ).worldAddress;
45
+ ).address;
48
46
 
49
47
  console.log(chalk.blue("World address", worldAddress));
50
48
 
51
- // Create a temporary file to pass the world address to the tests
52
- writeFileSync(WORLD_ADDRESS_FILE, worldAddress);
53
-
54
- const userOptions = args.forgeOptions?.replaceAll("\\", "").split(" ") ?? [];
49
+ const userOptions = opts.forgeOptions?.replaceAll("\\", "").split(" ") ?? [];
55
50
  try {
56
- const testResult = await forge(["test", "--fork-url", forkRpc, ...userOptions], {
57
- profile: args.profile,
51
+ await forge(["test", "--fork-url", forkRpc, ...userOptions], {
52
+ profile: opts.profile,
53
+ env: {
54
+ WORLD_ADDRESS: worldAddress,
55
+ },
58
56
  });
59
- console.log(testResult);
57
+ process.exit(0);
60
58
  } catch (e) {
61
59
  console.error(e);
62
- rmSync(WORLD_ADDRESS_FILE);
63
60
  process.exit(1);
64
61
  }
65
-
66
- rmSync(WORLD_ADDRESS_FILE);
67
- process.exit(0);
68
62
  },
69
63
  };
70
64
 
@@ -5,19 +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
- import worldConfig from "@latticexyz/world/mud.config.js";
12
- import { resourceIdToHex } from "@latticexyz/common";
10
+ import worldConfig from "@latticexyz/world/mud.config";
11
+ import { resourceToHex } from "@latticexyz/common";
13
12
  import { getExistingContracts } from "../utils/getExistingContracts";
14
- import { getChainId } from "../utils/utils/getChainId";
13
+ import { createClient, http } from "viem";
14
+ import { getChainId } from "viem/actions";
15
+ import { World as WorldConfig } from "@latticexyz/world";
16
+ import { worldToV1 } from "@latticexyz/world/config/v2";
15
17
 
16
18
  // TODO account for multiple namespaces (https://github.com/latticexyz/mud/issues/994)
17
- const systemsTableId = resourceIdToHex({
19
+ const systemsTableId = resourceToHex({
18
20
  type: "system",
19
21
  namespace: worldConfig.namespace,
20
- name: worldConfig.tables.Systems.name,
22
+ name: worldConfig.tables.world__Systems.name,
21
23
  });
22
24
 
23
25
  type Options = {
@@ -58,11 +60,12 @@ const commandModule: CommandModule<Options, Options> = {
58
60
  const existingContracts = getExistingContracts(srcDir);
59
61
 
60
62
  // Load the config
61
- const mudConfig = (await loadConfig(configPath)) as StoreConfig & WorldConfig;
63
+ const configV2 = (await loadConfig(configPath)) as WorldConfig;
64
+ const mudConfig = worldToV1(configV2);
62
65
 
63
66
  const resolvedConfig = resolveWorldConfig(
64
67
  mudConfig,
65
- existingContracts.map(({ basename }) => basename)
68
+ existingContracts.map(({ basename }) => basename),
66
69
  );
67
70
 
68
71
  // Get worldAddress either from args or from worldsFile
@@ -80,7 +83,7 @@ const commandModule: CommandModule<Options, Options> = {
80
83
  const systemTableFieldLayout = await WorldContract.getFieldLayout(systemsTableId);
81
84
  const labels: { name: string; address: string }[] = [];
82
85
  for (const name of names) {
83
- const systemSelector = resourceIdToHex({ type: "system", namespace, name });
86
+ const systemSelector = resourceToHex({ type: "system", namespace, name });
84
87
  // Get the first field of `Systems` table (the table maps system name to its address and other data)
85
88
  const address = await WorldContract.getField(systemsTableId, [systemSelector], 0, systemTableFieldLayout);
86
89
  labels.push({ name, address });
@@ -103,7 +106,8 @@ export default commandModule;
103
106
 
104
107
  async function getWorldAddress(worldsFile: string, rpc: string) {
105
108
  if (existsSync(worldsFile)) {
106
- const chainId = await getChainId(rpc);
109
+ const client = createClient({ transport: http(rpc) });
110
+ const chainId = await getChainId(client);
107
111
  const deploys = JSON.parse(readFileSync(worldsFile, "utf-8"));
108
112
 
109
113
  if (!deploys[chainId]) {
@@ -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);
package/src/common.ts ADDED
@@ -0,0 +1 @@
1
+ export type MudPackages = Record<string, { localPath: string }>;
package/src/debug.ts ADDED
@@ -0,0 +1,10 @@
1
+ import createDebug from "debug";
2
+
3
+ export const debug = createDebug("mud:cli");
4
+ export const error = createDebug("mud:cli");
5
+
6
+ // Pipe debug output to stdout instead of stderr
7
+ debug.log = console.debug.bind(console);
8
+
9
+ // Pipe error output to stderr
10
+ error.log = console.error.bind(console);
@@ -0,0 +1,122 @@
1
+ import { Abi, Address, Hex, padHex } from "viem";
2
+ import storeConfig from "@latticexyz/store/mud.config";
3
+ import worldConfig from "@latticexyz/world/mud.config";
4
+ import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" };
5
+ import IModuleAbi from "@latticexyz/world-modules/out/IModule.sol/IModule.abi.json" assert { type: "json" };
6
+ import { Tables, configToTables } from "./configToTables";
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";
13
+
14
+ export const salt = padHex("0x", { size: 32 });
15
+
16
+ // https://eips.ethereum.org/EIPS/eip-170
17
+ export const contractSizeLimit = parseInt("6000", 16);
18
+
19
+ // TODO: add `as const` to mud config so these get more strongly typed (blocked by current config parsing not using readonly)
20
+ export const storeTables = configToTables(storeToV1(storeConfig));
21
+ export const worldTables = configToTables(worldToV1(worldConfig));
22
+
23
+ export const worldDeployEvents = [helloStoreEvent, helloWorldEvent] as const;
24
+
25
+ export const worldAbi = [...IBaseWorldAbi, ...IModuleAbi] as const;
26
+
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.
28
+ export const supportedStoreVersions = ["2.0.0"];
29
+ export const supportedWorldVersions = ["2.0.0"];
30
+
31
+ // TODO: extend this to include factory+deployer address? so we can reuse the deployer for a world?
32
+ export type WorldDeploy = {
33
+ readonly address: Address;
34
+ readonly worldVersion: string;
35
+ readonly storeVersion: string;
36
+ /** Block number where the world was deployed */
37
+ readonly deployBlock: bigint;
38
+ /**
39
+ * Block number at the time of fetching world deploy.
40
+ * We use this block number when requesting data from the chain to align chain state
41
+ * with the same block during the introspection steps of the deploy.
42
+ */
43
+ readonly stateBlock: bigint;
44
+ };
45
+
46
+ export type WorldFunction = {
47
+ readonly signature: string;
48
+ readonly selector: Hex;
49
+ readonly systemId: Hex;
50
+ readonly systemFunctionSignature: string;
51
+ readonly systemFunctionSelector: Hex;
52
+ };
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
+
73
+ export type DeterministicContract = {
74
+ readonly prepareDeploy: (
75
+ deployer: Address,
76
+ libraries: readonly Library[],
77
+ ) => {
78
+ readonly address: Address;
79
+ readonly bytecode: Hex;
80
+ };
81
+ readonly deployedBytecodeSize: number;
82
+ readonly abi: Abi;
83
+ };
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
+
96
+ export type System = DeterministicContract & {
97
+ readonly namespace: string;
98
+ readonly name: string;
99
+ readonly systemId: Hex;
100
+ readonly allowAll: boolean;
101
+ readonly allowedAddresses: readonly Hex[];
102
+ readonly allowedSystemIds: readonly Hex[];
103
+ readonly functions: readonly WorldFunction[];
104
+ };
105
+
106
+ export type DeployedSystem = Omit<System, "abi" | "prepareDeploy" | "deployedBytecodeSize" | "allowedSystemIds"> & {
107
+ address: Address;
108
+ };
109
+
110
+ export type Module = DeterministicContract & {
111
+ readonly name: string;
112
+ readonly installAsRoot: boolean;
113
+ readonly installData: Hex; // TODO: figure out better naming for this
114
+ };
115
+
116
+ export type ConfigInput = StoreConfig & WorldConfig;
117
+ export type Config<config extends ConfigInput> = {
118
+ readonly tables: Tables<config>;
119
+ readonly systems: readonly System[];
120
+ readonly modules: readonly Module[];
121
+ readonly libraries: readonly Library[];
122
+ };
@@ -0,0 +1,70 @@
1
+ import { resourceToHex } from "@latticexyz/common";
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
+ import { Hex } from "viem";
6
+
7
+ // TODO: we shouldn't need this file once our config parsing returns nicely formed tables
8
+
9
+ type UserTypes<config extends StoreConfig = StoreConfig> = config["userTypes"];
10
+ // TODO: fix strong enum types and avoid every schema getting `{ [k: string]: "uint8" }`
11
+ // type UserTypes<config extends StoreConfig = StoreConfig> = config["userTypes"] & {
12
+ // [k in keyof config["enums"]]: { internalType: "uint8" };
13
+ // };
14
+
15
+ export type TableKey<
16
+ config extends StoreConfig = StoreConfig,
17
+ table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]],
18
+ > = `${config["namespace"]}_${table["name"]}`;
19
+
20
+ export type Table<
21
+ config extends StoreConfig = StoreConfig,
22
+ table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]],
23
+ > = {
24
+ readonly namespace: config["namespace"];
25
+ readonly name: table["name"];
26
+ readonly tableId: Hex;
27
+ readonly keySchema: table["keySchema"] extends KeySchema<UserTypes<config>>
28
+ ? KeySchema & {
29
+ readonly [k in keyof table["keySchema"]]: UserTypes<config>[table["keySchema"][k]]["internalType"] extends StaticAbiType
30
+ ? UserTypes<config>[table["keySchema"][k]]["internalType"]
31
+ : table["keySchema"][k];
32
+ }
33
+ : KeySchema;
34
+ readonly valueSchema: table["valueSchema"] extends ValueSchema<UserTypes<config>>
35
+ ? {
36
+ readonly [k in keyof table["valueSchema"]]: UserTypes<config>[table["valueSchema"][k]]["internalType"] extends SchemaAbiType
37
+ ? UserTypes<config>[table["valueSchema"][k]]["internalType"]
38
+ : table["valueSchema"][k];
39
+ }
40
+ : ValueSchema;
41
+ };
42
+
43
+ export type Tables<config extends StoreConfig = StoreConfig> = {
44
+ readonly [k in keyof config["tables"] as TableKey<config, config["tables"][k]>]: Table<config, config["tables"][k]>;
45
+ };
46
+
47
+ export function configToTables<config extends StoreConfig>(config: config): Tables<config> {
48
+ const userTypes = {
49
+ ...config.userTypes,
50
+ ...Object.fromEntries(Object.entries(config.enums).map(([key]) => [key, { internalType: "uint8" }] as const)),
51
+ };
52
+ return Object.fromEntries(
53
+ Object.entries(config.tables).map(([tableName, table]) => [
54
+ `${config.namespace}_${tableName}` satisfies TableKey<config, config["tables"][keyof config["tables"]]>,
55
+ {
56
+ namespace: config.namespace,
57
+ name: table.name,
58
+ tableId: resourceToHex({
59
+ type: table.offchainOnly ? "offchainTable" : "table",
60
+ namespace: config.namespace,
61
+ name: table.name,
62
+ }),
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ keySchema: resolveUserTypes(table.keySchema, userTypes) as any,
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ valueSchema: resolveUserTypes(table.valueSchema, userTypes) as any,
67
+ } satisfies Table<config, config["tables"][keyof config["tables"]]>,
68
+ ]),
69
+ ) as Tables<config>;
70
+ }
@@ -0,0 +1,13 @@
1
+ Files generated by
2
+
3
+ ```
4
+ git clone https://github.com/Arachnid/deterministic-deployment-proxy.git
5
+ cd deterministic-deployment-proxy
6
+ git checkout b3bb19c
7
+ npm install
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
13
+ ```
@@ -0,0 +1,8 @@
1
+ {
2
+ "gasPrice": 100000000000,
3
+ "gasLimit": 100000,
4
+ "signerAddress": "3fab184622dc19b6109349b94811493bf2a45362",
5
+ "transaction": "f8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222",
6
+ "address": "4e59b44847b379578588920ca78fbf26c0b4956c",
7
+ "bytecode": "604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"
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
+ }