@latticexyz/cli 2.0.0-alpha.94 → 2.0.0-main-0d434816

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 (100) hide show
  1. package/dist/chunk-LFIOIY67.js +30 -0
  2. package/dist/chunk-LFIOIY67.js.map +1 -0
  3. package/dist/index.js +2 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/mud.js +15 -8340
  6. package/dist/mud.js.map +1 -0
  7. package/package.json +45 -43
  8. package/src/commands/deploy.ts +49 -0
  9. package/src/commands/dev-contracts.ts +176 -0
  10. package/src/commands/devnode.ts +7 -2
  11. package/src/commands/faucet.ts +1 -1
  12. package/src/commands/index.ts +18 -13
  13. package/src/commands/set-version.ts +38 -10
  14. package/src/commands/tablegen.ts +5 -4
  15. package/src/commands/{test-v2.ts → test.ts} +5 -6
  16. package/src/commands/trace.ts +108 -0
  17. package/src/commands/tsgen.ts +4 -3
  18. package/src/commands/worldgen.ts +29 -28
  19. package/src/index.ts +2 -1
  20. package/src/modules.d.ts +11 -0
  21. package/src/mud.ts +10 -4
  22. package/src/render-ts/recsV1TableOptions.ts +4 -4
  23. package/src/render-ts/renderRecsV1Tables.ts +3 -3
  24. package/src/render-ts/schemaTypesToRecsTypeStrings.ts +33 -33
  25. package/src/render-ts/tsgen.ts +4 -4
  26. package/src/utils/{deploy-v2.ts → deploy.ts} +207 -123
  27. package/src/utils/deployHandler.ts +87 -0
  28. package/src/utils/errors.ts +1 -1
  29. package/src/utils/getExistingContracts.ts +12 -0
  30. package/src/utils/index.ts +7 -5
  31. package/src/utils/printMUD.ts +14 -0
  32. package/src/utils/worldtypes.ts +21 -0
  33. package/dist/chunk-6V563IAZ.js +0 -283
  34. package/dist/chunk-FPG73MVN.js +0 -71
  35. package/dist/chunk-L4YLJHLJ.js +0 -30
  36. package/dist/chunk-SKNB74MT.js +0 -726
  37. package/dist/chunk-T2YHSEJD.js +0 -3842
  38. package/dist/chunk-VQTZJIFF.js +0 -353
  39. package/dist/chunk-WZFXLDPK.js +0 -761
  40. package/dist/chunk-YL4GJLLL.js +0 -26139
  41. package/dist/index.d.ts +0 -2
  42. package/dist/mud.d.ts +0 -1
  43. package/dist/mud2.d.ts +0 -1
  44. package/dist/mud2.js +0 -25
  45. package/dist/render-solidity/index.d.ts +0 -171
  46. package/dist/render-solidity/index.js +0 -49
  47. package/dist/render-ts/index.d.ts +0 -26
  48. package/dist/render-ts/index.js +0 -14
  49. package/dist/utils/deprecated/index.d.ts +0 -76
  50. package/dist/utils/deprecated/index.js +0 -36
  51. package/dist/utils/index.d.ts +0 -88
  52. package/dist/utils/index.js +0 -39
  53. package/src/commands/deploy-v2.ts +0 -112
  54. package/src/commands/deprecated/bulkupload.ts +0 -50
  55. package/src/commands/deprecated/call-system.ts +0 -70
  56. package/src/commands/deprecated/codegen-libdeploy.ts +0 -29
  57. package/src/commands/deprecated/deploy-contracts.ts +0 -120
  58. package/src/commands/deprecated/index.ts +0 -22
  59. package/src/commands/deprecated/system-types.ts +0 -29
  60. package/src/commands/deprecated/test.ts +0 -53
  61. package/src/commands/deprecated/trace.ts +0 -81
  62. package/src/commands/deprecated/types.ts +0 -33
  63. package/src/commands/gas-report.ts +0 -219
  64. package/src/contracts/BulkUpload.sol +0 -174
  65. package/src/contracts/Deploy.sol +0 -23
  66. package/src/contracts/LibDeploy.ejs +0 -121
  67. package/src/contracts/LibDeploy.sol +0 -23
  68. package/src/contracts/LibDeployStub.sol +0 -23
  69. package/src/mud2.ts +0 -29
  70. package/src/render-solidity/common.ts +0 -138
  71. package/src/render-solidity/field.ts +0 -160
  72. package/src/render-solidity/index.ts +0 -10
  73. package/src/render-solidity/record.ts +0 -158
  74. package/src/render-solidity/renderSystemInterface.ts +0 -31
  75. package/src/render-solidity/renderTable.ts +0 -164
  76. package/src/render-solidity/renderTableIndex.ts +0 -15
  77. package/src/render-solidity/renderTypeHelpers.ts +0 -99
  78. package/src/render-solidity/renderTypes.ts +0 -19
  79. package/src/render-solidity/renderTypesFromConfig.ts +0 -13
  80. package/src/render-solidity/renderWorld.ts +0 -24
  81. package/src/render-solidity/tableOptions.ts +0 -107
  82. package/src/render-solidity/tablegen.ts +0 -35
  83. package/src/render-solidity/types.ts +0 -111
  84. package/src/render-solidity/userType.ts +0 -131
  85. package/src/render-solidity/worldgen.ts +0 -59
  86. package/src/utils/contractToInterface.ts +0 -132
  87. package/src/utils/deprecated/build.ts +0 -55
  88. package/src/utils/deprecated/codegen.ts +0 -42
  89. package/src/utils/deprecated/constants.ts +0 -2
  90. package/src/utils/deprecated/deploy.ts +0 -111
  91. package/src/utils/deprecated/exec.ts +0 -18
  92. package/src/utils/deprecated/findLog.ts +0 -8
  93. package/src/utils/deprecated/hsr.ts +0 -71
  94. package/src/utils/deprecated/ids.ts +0 -14
  95. package/src/utils/deprecated/index.ts +0 -8
  96. package/src/utils/deprecated/typegen.ts +0 -133
  97. package/src/utils/execLog.ts +0 -22
  98. package/src/utils/format.ts +0 -39
  99. package/src/utils/formatAndWrite.ts +0 -21
  100. package/src/utils/foundry.ts +0 -103
@@ -1,21 +1,24 @@
1
1
  import { existsSync, readFileSync } from "fs";
2
2
  import path from "path";
3
- import { MUDConfig, resolveWithContext } from "@latticexyz/config";
4
- import { MUDError } from "@latticexyz/config";
5
- import { getOutDirectory, getScriptDirectory, cast, forge } from "./foundry.js";
6
- import { BigNumber, ContractInterface, ethers } from "ethers";
7
- import { IBaseWorld } from "@latticexyz/world/types/ethers-contracts/IBaseWorld.js";
8
3
  import chalk from "chalk";
9
- import { encodeSchema } from "@latticexyz/schema-type";
10
- import { resolveAbiOrUserType } from "../render-solidity/userType.js";
11
- import { defaultAbiCoder as abi, Fragment } from "ethers/lib/utils.js";
12
-
13
- import WorldData from "@latticexyz/world/abi/World.json" assert { type: "json" };
14
- import IBaseWorldData from "@latticexyz/world/abi/IBaseWorld.json" assert { type: "json" };
15
- import CoreModuleData from "@latticexyz/world/abi/CoreModule.json" assert { type: "json" };
16
- import RegistrationModuleData from "@latticexyz/world/abi/RegistrationModule.json" assert { type: "json" };
17
- import KeysWithValueModuleData from "@latticexyz/world/abi/KeysWithValueModule.json" assert { type: "json" };
18
- import UniqueEntityModuleData from "@latticexyz/world/abi/UniqueEntityModule.json" assert { type: "json" };
4
+ import { BigNumber, ContractInterface, ethers } from "ethers";
5
+ import { defaultAbiCoder as abi, Fragment, ParamType } from "ethers/lib/utils.js";
6
+
7
+ import { getOutDirectory, getScriptDirectory, cast, forge } from "@latticexyz/common/foundry";
8
+ import { resolveWithContext } from "@latticexyz/config";
9
+ import { MUDError } from "@latticexyz/common/errors";
10
+ import { encodeSchema } from "@latticexyz/schema-type/deprecated";
11
+ import { StoreConfig } from "@latticexyz/store";
12
+ import { resolveAbiOrUserType } from "@latticexyz/store/codegen";
13
+ import { WorldConfig, resolveWorldConfig } from "@latticexyz/world";
14
+ import { IBaseWorld } from "@latticexyz/world/types/ethers-contracts/IBaseWorld";
15
+ import WorldData from "@latticexyz/world/abi/World.sol/World.json" assert { type: "json" };
16
+ import IBaseWorldData from "@latticexyz/world/abi/IBaseWorld.sol/IBaseWorld.json" assert { type: "json" };
17
+ import CoreModuleData from "@latticexyz/world/abi/CoreModule.sol/CoreModule.json" assert { type: "json" };
18
+ import KeysWithValueModuleData from "@latticexyz/world/abi/KeysWithValueModule.sol/KeysWithValueModule.json" assert { type: "json" };
19
+ import KeysInTableModuleData from "@latticexyz/world/abi/KeysInTableModule.sol/KeysInTableModule.json" assert { type: "json" };
20
+ import UniqueEntityModuleData from "@latticexyz/world/abi/UniqueEntityModule.sol/UniqueEntityModule.json" assert { type: "json" };
21
+ import SnapSyncModuleData from "@latticexyz/world/abi/SnapSyncModule.sol/SnapSyncModule.json" assert { type: "json" };
19
22
 
20
23
  export interface DeployConfig {
21
24
  profile?: string;
@@ -24,6 +27,8 @@ export interface DeployConfig {
24
27
  priorityFeeMultiplier: number;
25
28
  debug?: boolean;
26
29
  worldAddress?: string;
30
+ disableTxWait: boolean;
31
+ pollInterval: number;
27
32
  }
28
33
 
29
34
  export interface DeploymentInfo {
@@ -31,14 +36,22 @@ export interface DeploymentInfo {
31
36
  worldAddress: string;
32
37
  }
33
38
 
34
- export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig): Promise<DeploymentInfo> {
39
+ export async function deploy(
40
+ mudConfig: StoreConfig & WorldConfig,
41
+ existingContractNames: string[],
42
+ deployConfig: DeployConfig
43
+ ): Promise<DeploymentInfo> {
44
+ const resolvedConfig = resolveWorldConfig(mudConfig, existingContractNames);
45
+
35
46
  const startTime = Date.now();
36
47
  const { worldContractName, namespace, postDeployScript } = mudConfig;
37
- const { profile, rpc, privateKey, priorityFeeMultiplier, debug, worldAddress } = deployConfig;
48
+ const { profile, rpc, privateKey, priorityFeeMultiplier, debug, worldAddress, disableTxWait, pollInterval } =
49
+ deployConfig;
38
50
  const forgeOutDirectory = await getOutDirectory(profile);
39
51
 
40
52
  // Set up signer for deployment
41
53
  const provider = new ethers.providers.StaticJsonRpcProvider(rpc);
54
+ provider.pollingInterval = pollInterval;
42
55
  const signer = new ethers.Wallet(privateKey, provider);
43
56
 
44
57
  // Manual nonce handling to allow for faster sending of transactions without waiting for previous transactions
@@ -48,7 +61,7 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
48
61
  // Compute maxFeePerGas and maxPriorityFeePerGas like ethers, but allow for a multiplier to allow replacing pending transactions
49
62
  let maxPriorityFeePerGas: number;
50
63
  let maxFeePerGas: BigNumber;
51
- setInternalFeePerGas(priorityFeeMultiplier);
64
+ await setInternalFeePerGas(priorityFeeMultiplier);
52
65
 
53
66
  // Catch all to await any promises before exiting the script
54
67
  let promises: Promise<unknown>[] = [];
@@ -62,42 +75,54 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
62
75
  World: worldAddress
63
76
  ? Promise.resolve(worldAddress)
64
77
  : worldContractName
65
- ? deployContractByName(worldContractName)
66
- : deployContract(IBaseWorldData.abi, WorldData.bytecode, "World"),
78
+ ? deployContractByName(worldContractName, disableTxWait)
79
+ : deployContract(IBaseWorldData.abi, WorldData.bytecode, disableTxWait, "World"),
67
80
  };
68
81
 
69
82
  // Deploy Systems
70
- const systemPromises = Object.keys(mudConfig.systems).reduce<Record<string, Promise<string>>>((acc, systemName) => {
71
- acc[systemName] = deployContractByName(systemName);
72
- return acc;
73
- }, {});
83
+ const systemPromises = Object.keys(resolvedConfig.systems).reduce<Record<string, Promise<string>>>(
84
+ (acc, systemName) => {
85
+ acc[systemName] = deployContractByName(systemName, disableTxWait);
86
+ return acc;
87
+ },
88
+ {}
89
+ );
74
90
 
75
91
  // Deploy default World modules
76
92
  const defaultModules: Record<string, Promise<string>> = {
77
93
  // TODO: these only need to be deployed once per chain, add a check if they exist already
78
- CoreModule: deployContract(CoreModuleData.abi, CoreModuleData.bytecode, "CoreModule"),
79
- RegistrationModule: deployContract(
80
- RegistrationModuleData.abi,
81
- RegistrationModuleData.bytecode,
82
- "RegistrationModule"
83
- ),
94
+ CoreModule: deployContract(CoreModuleData.abi, CoreModuleData.bytecode, disableTxWait, "CoreModule"),
84
95
  KeysWithValueModule: deployContract(
85
96
  KeysWithValueModuleData.abi,
86
97
  KeysWithValueModuleData.bytecode,
98
+ disableTxWait,
87
99
  "KeysWithValueModule"
88
100
  ),
101
+ KeysInTableModule: deployContract(
102
+ KeysInTableModuleData.abi,
103
+ KeysInTableModuleData.bytecode,
104
+ disableTxWait,
105
+ "KeysInTableModule"
106
+ ),
89
107
  UniqueEntityModule: deployContract(
90
108
  UniqueEntityModuleData.abi,
91
109
  UniqueEntityModuleData.bytecode,
110
+ disableTxWait,
92
111
  "UniqueEntityModule"
93
112
  ),
113
+ SnapSyncModule: deployContract(
114
+ SnapSyncModuleData.abi,
115
+ SnapSyncModuleData.bytecode,
116
+ disableTxWait,
117
+ "SnapSyncModule"
118
+ ),
94
119
  };
95
120
 
96
121
  // Deploy user Modules
97
122
  const modulePromises = mudConfig.modules
98
123
  .filter((module) => !defaultModules[module.name]) // Only deploy user modules here, not default modules
99
124
  .reduce<Record<string, Promise<string>>>((acc, module) => {
100
- acc[module.name] = deployContractByName(module.name);
125
+ acc[module.name] = deployContractByName(module.name, disableTxWait);
101
126
  return acc;
102
127
  }, defaultModules);
103
128
 
@@ -107,22 +132,23 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
107
132
  // Create World contract instance from deployed address
108
133
  const WorldContract = new ethers.Contract(await contractPromises.World, IBaseWorldData.abi, signer) as IBaseWorld;
109
134
 
135
+ const confirmations = disableTxWait ? 0 : 1;
136
+
110
137
  // Install core Modules
111
138
  if (!worldAddress) {
112
139
  console.log(chalk.blue("Installing core World modules"));
113
- await fastTxExecute(WorldContract, "installRootModule", [await modulePromises.CoreModule, "0x"]);
114
- await fastTxExecute(WorldContract, "installRootModule", [await modulePromises.RegistrationModule, "0x"]);
140
+ await fastTxExecute(WorldContract, "installRootModule", [await modulePromises.CoreModule, "0x"], confirmations);
115
141
  console.log(chalk.green("Installed core World modules"));
116
142
  }
117
143
 
118
144
  // Register namespace
119
- if (namespace) await fastTxExecute(WorldContract, "registerNamespace", [toBytes16(namespace)]);
145
+ if (namespace) await fastTxExecute(WorldContract, "registerNamespace", [toBytes16(namespace)], confirmations);
120
146
 
121
147
  // Register tables
122
148
  const tableIds: { [tableName: string]: Uint8Array } = {};
123
149
  promises = [
124
150
  ...promises,
125
- ...Object.entries(mudConfig.tables).map(async ([tableName, { name, schema, primaryKeys }]) => {
151
+ ...Object.entries(mudConfig.tables).map(async ([tableName, { name, schema, keySchema }]) => {
126
152
  console.log(chalk.blue(`Registering table ${tableName} at ${namespace}/${name}`));
127
153
 
128
154
  // Store the tableId for later use
@@ -134,25 +160,25 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
134
160
  return schemaType;
135
161
  });
136
162
 
137
- const keyTypes = Object.values(primaryKeys).map((abiOrUserType) => {
163
+ const keyTypes = Object.values(keySchema).map((abiOrUserType) => {
138
164
  const { schemaType } = resolveAbiOrUserType(abiOrUserType, mudConfig);
139
165
  return schemaType;
140
166
  });
141
167
 
142
- await fastTxExecute(WorldContract, "registerTable", [
143
- toBytes16(namespace),
144
- toBytes16(name),
145
- encodeSchema(schemaTypes),
146
- encodeSchema(keyTypes),
147
- ]);
168
+ await fastTxExecute(
169
+ WorldContract,
170
+ "registerTable",
171
+ [toBytes16(namespace), toBytes16(name), encodeSchema(schemaTypes), encodeSchema(keyTypes)],
172
+ confirmations
173
+ );
148
174
 
149
175
  // Register table metadata
150
- await fastTxExecute(WorldContract, "setMetadata(bytes16,bytes16,string,string[])", [
151
- toBytes16(namespace),
152
- toBytes16(name),
153
- tableName,
154
- Object.keys(schema),
155
- ]);
176
+ await fastTxExecute(
177
+ WorldContract,
178
+ "setMetadata(bytes16,bytes16,string,string[])",
179
+ [toBytes16(namespace), toBytes16(name), tableName, Object.keys(schema)],
180
+ confirmations
181
+ );
156
182
 
157
183
  console.log(chalk.green(`Registered table ${tableName} at ${name}`));
158
184
  }),
@@ -161,55 +187,57 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
161
187
  // Register systems (using forEach instead of for..of to avoid blocking on async calls)
162
188
  promises = [
163
189
  ...promises,
164
- ...Object.entries(mudConfig.systems).map(async ([systemName, { name, openAccess, registerFunctionSelectors }]) => {
165
- // Register system at route
166
- console.log(chalk.blue(`Registering system ${systemName} at ${namespace}/${name}`));
167
- await fastTxExecute(WorldContract, "registerSystem", [
168
- toBytes16(namespace),
169
- toBytes16(name),
170
- await contractPromises[systemName],
171
- openAccess,
172
- ]);
173
- console.log(chalk.green(`Registered system ${systemName} at ${namespace}/${name}`));
174
-
175
- // Register function selectors for the system
176
- if (registerFunctionSelectors) {
177
- const functionSignatures: FunctionSignature[] = await loadFunctionSignatures(systemName);
178
- const isRoot = namespace === "";
179
- // Using Promise.all to avoid blocking on async calls
180
- await Promise.all(
181
- functionSignatures.map(async ({ functionName, functionArgs }) => {
182
- const functionSignature = isRoot
183
- ? functionName + functionArgs
184
- : `${namespace}_${name}_${functionName}${functionArgs}`;
185
-
186
- console.log(chalk.blue(`Registering function "${functionSignature}"`));
187
- if (isRoot) {
188
- const worldFunctionSelector = toFunctionSelector(
189
- functionSignature === ""
190
- ? { functionName: systemName, functionArgs } // Register the system's fallback function as `<systemName>(<args>)`
191
- : { functionName, functionArgs }
192
- );
193
- const systemFunctionSelector = toFunctionSelector({ functionName, functionArgs });
194
- await fastTxExecute(WorldContract, "registerRootFunctionSelector", [
195
- toBytes16(namespace),
196
- toBytes16(name),
197
- worldFunctionSelector,
198
- systemFunctionSelector,
199
- ]);
200
- } else {
201
- await fastTxExecute(WorldContract, "registerFunctionSelector", [
202
- toBytes16(namespace),
203
- toBytes16(name),
204
- functionName,
205
- functionArgs,
206
- ]);
207
- }
208
- console.log(chalk.green(`Registered function "${functionSignature}"`));
209
- })
190
+ ...Object.entries(resolvedConfig.systems).map(
191
+ async ([systemName, { name, openAccess, registerFunctionSelectors }]) => {
192
+ // Register system at route
193
+ console.log(chalk.blue(`Registering system ${systemName} at ${namespace}/${name}`));
194
+ await fastTxExecute(
195
+ WorldContract,
196
+ "registerSystem",
197
+ [toBytes16(namespace), toBytes16(name), await contractPromises[systemName], openAccess],
198
+ confirmations
210
199
  );
200
+ console.log(chalk.green(`Registered system ${systemName} at ${namespace}/${name}`));
201
+
202
+ // Register function selectors for the system
203
+ if (registerFunctionSelectors) {
204
+ const functionSignatures: FunctionSignature[] = await loadFunctionSignatures(systemName);
205
+ const isRoot = namespace === "";
206
+ // Using Promise.all to avoid blocking on async calls
207
+ await Promise.all(
208
+ functionSignatures.map(async ({ functionName, functionArgs }) => {
209
+ const functionSignature = isRoot
210
+ ? functionName + functionArgs
211
+ : `${namespace}_${name}_${functionName}${functionArgs}`;
212
+
213
+ console.log(chalk.blue(`Registering function "${functionSignature}"`));
214
+ if (isRoot) {
215
+ const worldFunctionSelector = toFunctionSelector(
216
+ functionSignature === ""
217
+ ? { functionName: systemName, functionArgs } // Register the system's fallback function as `<systemName>(<args>)`
218
+ : { functionName, functionArgs }
219
+ );
220
+ const systemFunctionSelector = toFunctionSelector({ functionName, functionArgs });
221
+ await fastTxExecute(
222
+ WorldContract,
223
+ "registerRootFunctionSelector",
224
+ [toBytes16(namespace), toBytes16(name), worldFunctionSelector, systemFunctionSelector],
225
+ confirmations
226
+ );
227
+ } else {
228
+ await fastTxExecute(
229
+ WorldContract,
230
+ "registerFunctionSelector",
231
+ [toBytes16(namespace), toBytes16(name), functionName, functionArgs],
232
+ confirmations
233
+ );
234
+ }
235
+ console.log(chalk.green(`Registered function "${functionSignature}"`));
236
+ })
237
+ );
238
+ }
211
239
  }
212
- }),
240
+ ),
213
241
  ];
214
242
 
215
243
  // Wait for resources to be registered before granting access to them
@@ -217,7 +245,7 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
217
245
  promises = [];
218
246
 
219
247
  // Grant access to systems
220
- for (const [systemName, { name, accessListAddresses, accessListSystems }] of Object.entries(mudConfig.systems)) {
248
+ for (const [systemName, { name, accessListAddresses, accessListSystems }] of Object.entries(resolvedConfig.systems)) {
221
249
  const resourceSelector = `${namespace}/${name}`;
222
250
 
223
251
  // Grant access to addresses
@@ -225,11 +253,12 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
225
253
  ...promises,
226
254
  ...accessListAddresses.map(async (address) => {
227
255
  console.log(chalk.blue(`Grant ${address} access to ${systemName} (${resourceSelector})`));
228
- await fastTxExecute(WorldContract, "grantAccess(bytes16,bytes16,address)", [
229
- toBytes16(namespace),
230
- toBytes16(name),
231
- address,
232
- ]);
256
+ await fastTxExecute(
257
+ WorldContract,
258
+ "grantAccess",
259
+ [toBytes16(namespace), toBytes16(name), address],
260
+ confirmations
261
+ );
233
262
  console.log(chalk.green(`Granted ${address} access to ${systemName} (${namespace}/${name})`));
234
263
  }),
235
264
  ];
@@ -239,11 +268,12 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
239
268
  ...promises,
240
269
  ...accessListSystems.map(async (granteeSystem) => {
241
270
  console.log(chalk.blue(`Grant ${granteeSystem} access to ${systemName} (${resourceSelector})`));
242
- await fastTxExecute(WorldContract, "grantAccess(bytes16,bytes16,address)", [
243
- toBytes16(namespace),
244
- toBytes16(name),
245
- await contractPromises[granteeSystem],
246
- ]);
271
+ await fastTxExecute(
272
+ WorldContract,
273
+ "grantAccess",
274
+ [toBytes16(namespace), toBytes16(name), await contractPromises[granteeSystem]],
275
+ confirmations
276
+ );
247
277
  console.log(chalk.green(`Granted ${granteeSystem} access to ${systemName} (${resourceSelector})`));
248
278
  }),
249
279
  ];
@@ -268,10 +298,12 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
268
298
  if (!moduleAddress) throw new Error(`Module ${module.name} not found`);
269
299
 
270
300
  // Send transaction to install module
271
- await fastTxExecute(WorldContract, module.root ? "installRootModule" : "installModule", [
272
- moduleAddress,
273
- abi.encode(types, values),
274
- ]);
301
+ await fastTxExecute(
302
+ WorldContract,
303
+ module.root ? "installRootModule" : "installModule",
304
+ [moduleAddress, abi.encode(types, values)],
305
+ confirmations
306
+ );
275
307
 
276
308
  console.log(chalk.green(`Installed${module.root ? " root " : " "}module ${module.name}`));
277
309
  }),
@@ -279,6 +311,27 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
279
311
 
280
312
  // Await all promises before executing PostDeploy script
281
313
  await Promise.all(promises); // ----------------------------------------------------------------------------------------------
314
+
315
+ // Confirm the current nonce is the expected nonce to make sure all transactions have been included
316
+ let remoteNonce = await signer.getTransactionCount();
317
+ let retryCount = 0;
318
+ const maxRetries = 100;
319
+ while (remoteNonce !== nonce && retryCount < maxRetries) {
320
+ console.log(
321
+ chalk.gray(
322
+ `Waiting for transactions to be included before executing ${postDeployScript} (local nonce: ${nonce}, remote nonce: ${remoteNonce}, retry number ${retryCount}/${maxRetries})`
323
+ )
324
+ );
325
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
326
+ retryCount++;
327
+ remoteNonce = await signer.getTransactionCount();
328
+ }
329
+ if (remoteNonce !== nonce) {
330
+ throw new MUDError(
331
+ "Remote nonce doesn't match local nonce, indicating that not all deploy transactions were included."
332
+ );
333
+ }
334
+
282
335
  promises = [];
283
336
 
284
337
  // Execute postDeploy forge script
@@ -315,36 +368,43 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
315
368
  /**
316
369
  * Deploy a contract and return the address
317
370
  * @param contractName Name of the contract to deploy (must exist in the file system)
371
+ * @param disableTxWait Disable waiting for contract deployment
318
372
  * @returns Address of the deployed contract
319
373
  */
320
- async function deployContractByName(contractName: string): Promise<string> {
374
+ async function deployContractByName(contractName: string, disableTxWait: boolean): Promise<string> {
321
375
  console.log(chalk.blue("Deploying", contractName));
322
376
 
323
377
  const { abi, bytecode } = await getContractData(contractName);
324
- return deployContract(abi, bytecode, contractName);
378
+ return deployContract(abi, bytecode, disableTxWait, contractName);
325
379
  }
326
380
 
327
381
  /**
328
382
  * Deploy a contract and return the address
329
383
  * @param abi The contract interface
330
384
  * @param bytecode The contract bytecode
385
+ * @param disableTxWait Disable waiting for contract deployment
331
386
  * @param contractName The contract name (optional, used for logs)
387
+ * @param retryCount
332
388
  * @returns Address of the deployed contract
333
389
  */
334
390
  async function deployContract(
335
391
  abi: ContractInterface,
336
392
  bytecode: string | { object: string },
393
+ disableTxWait: boolean,
337
394
  contractName?: string,
338
395
  retryCount = 0
339
396
  ): Promise<string> {
340
397
  try {
341
398
  const factory = new ethers.ContractFactory(abi, bytecode, signer);
342
399
  console.log(chalk.gray(`executing deployment of ${contractName} with nonce ${nonce}`));
343
- const deployPromise = factory.deploy({
344
- nonce: nonce++,
345
- maxPriorityFeePerGas,
346
- maxFeePerGas,
347
- });
400
+ const deployPromise = factory
401
+ .deploy({
402
+ nonce: nonce++,
403
+ maxPriorityFeePerGas,
404
+ maxFeePerGas,
405
+ })
406
+ .then((c) => (disableTxWait ? c : c.deployed()));
407
+
348
408
  promises.push(deployPromise);
349
409
  const { address } = await deployPromise;
350
410
 
@@ -356,7 +416,7 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
356
416
  // If the deployment failed because the transaction was already imported,
357
417
  // retry with a higher priority fee
358
418
  setInternalFeePerGas(priorityFeeMultiplier * 1.1);
359
- return deployContract(abi, bytecode, contractName, retryCount++);
419
+ return deployContract(abi, bytecode, disableTxWait, contractName, retryCount++);
360
420
  } else if (error?.message.includes("invalid bytecode")) {
361
421
  throw new MUDError(
362
422
  `Error deploying ${contractName}: invalid bytecode. Note that linking of public libraries is not supported yet, make sure none of your libraries use "external" functions.`
@@ -398,10 +458,30 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
398
458
  .map((item) => {
399
459
  if (item.type === "fallback") return { functionName: "", functionArgs: "" };
400
460
 
401
- return { functionName: item.name, functionArgs: `(${item.inputs.map((arg) => arg.type).join(",")})` };
461
+ return {
462
+ functionName: item.name,
463
+ functionArgs: parseComponents(item.inputs),
464
+ };
402
465
  });
403
466
  }
404
467
 
468
+ /**
469
+ * Recursively turn (nested) structs in signatures into tuples
470
+ */
471
+ function parseComponents(params: ParamType[]): string {
472
+ const components = params.map((param) => {
473
+ const tupleMatch = param.type.match(/tuple(.*)/);
474
+ if (tupleMatch) {
475
+ // there can be arrays of tuples,
476
+ // `tupleMatch[1]` preserves the array brackets (or is empty string for non-arrays)
477
+ return parseComponents(param.components) + tupleMatch[1];
478
+ } else {
479
+ return param.type;
480
+ }
481
+ });
482
+ return `(${components})`;
483
+ }
484
+
405
485
  /**
406
486
  * Only await gas estimation (for speed), only execute if gas estimation succeeds (for safety)
407
487
  */
@@ -409,8 +489,8 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
409
489
  contract: C,
410
490
  func: F,
411
491
  args: Parameters<C[F]>,
412
- retryCount = 0,
413
- confirmations = 1
492
+ confirmations = 1,
493
+ retryCount = 0
414
494
  ): Promise<Awaited<ReturnType<Awaited<ReturnType<C[F]>>["wait"]>>> {
415
495
  const functionName = `${func as string}(${args.map((arg) => `'${arg}'`).join(",")})`;
416
496
  try {
@@ -418,7 +498,7 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
418
498
  console.log(chalk.gray(`executing transaction: ${functionName} with nonce ${nonce}`));
419
499
  const txPromise = contract[func]
420
500
  .apply(null, [...args, { gasLimit, nonce: nonce++, maxPriorityFeePerGas, maxFeePerGas }])
421
- .then((tx: any) => tx.wait(confirmations));
501
+ .then((tx: any) => (confirmations === 0 ? tx : tx.wait(confirmations)));
422
502
  promises.push(txPromise);
423
503
  return txPromise;
424
504
  } catch (error: any) {
@@ -427,7 +507,7 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
427
507
  // If the deployment failed because the transaction was already imported,
428
508
  // retry with a higher priority fee
429
509
  setInternalFeePerGas(priorityFeeMultiplier * 1.1);
430
- return fastTxExecute(contract, func, args, retryCount++, confirmations);
510
+ return fastTxExecute(contract, func, args, confirmations, retryCount++);
431
511
  } else throw new MUDError(`Gas estimation error for ${functionName}: ${error?.reason}`);
432
512
  }
433
513
  }
@@ -463,6 +543,10 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
463
543
  // Compute maxFeePerGas and maxPriorityFeePerGas like ethers, but allow for a multiplier to allow replacing pending transactions
464
544
  const feeData = await provider.getFeeData();
465
545
  if (!feeData.lastBaseFeePerGas) throw new MUDError("Can not fetch lastBaseFeePerGas from RPC");
546
+ if (!feeData.lastBaseFeePerGas.eq(0) && (await signer.getBalance()).eq(0)) {
547
+ throw new MUDError(`Attempting to deploy to a chain with non-zero base fee with an account that has no balance.
548
+ If you're deploying to the Lattice testnet, you can fund your account by running 'pnpm mud faucet --address ${await signer.getAddress()}'`);
549
+ }
466
550
 
467
551
  // Set the priority fee to 0 for development chains with no base fee, to allow transactions from unfunded wallets
468
552
  maxPriorityFeePerGas = feeData.lastBaseFeePerGas.eq(0) ? 0 : Math.floor(1_500_000_000 * multiplier);
@@ -0,0 +1,87 @@
1
+ import chalk from "chalk";
2
+ import path from "path";
3
+ import { MUDError } from "@latticexyz/common/errors";
4
+ import { loadConfig } from "@latticexyz/config/node";
5
+ import { StoreConfig } from "@latticexyz/store";
6
+ import { WorldConfig } from "@latticexyz/world";
7
+ import { deploy } from "../utils/deploy";
8
+ import { forge, getRpcUrl, getSrcDirectory } from "@latticexyz/common/foundry";
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
10
+ import { getChainId } from "../utils/getChainId";
11
+ import { getExistingContracts } from "./getExistingContracts";
12
+
13
+ export type DeployOptions = {
14
+ configPath?: string;
15
+ printConfig?: boolean;
16
+ profile?: string;
17
+ priorityFeeMultiplier: number;
18
+ clean?: boolean;
19
+ debug?: boolean;
20
+ saveDeployment: boolean;
21
+ rpc?: string;
22
+ worldAddress?: string;
23
+ srcDir?: string;
24
+ disableTxWait: boolean;
25
+ pollInterval: number;
26
+ skipBuild?: boolean;
27
+ };
28
+
29
+ export async function deployHandler(args: DeployOptions) {
30
+ args.profile ??= process.env.FOUNDRY_PROFILE;
31
+ const { configPath, printConfig, profile, clean, skipBuild } = args;
32
+
33
+ const rpc = args.rpc ?? (await getRpcUrl(profile));
34
+ console.log(
35
+ chalk.bgBlue(
36
+ chalk.whiteBright(`\n Deploying MUD contracts${profile ? " with profile " + profile : ""} to RPC ${rpc} \n`)
37
+ )
38
+ );
39
+
40
+ if (clean) await forge(["clean"], { profile });
41
+
42
+ // Run forge build
43
+ if (!skipBuild) await forge(["build"], { profile });
44
+
45
+ // Get a list of all contract names
46
+ const srcDir = args?.srcDir ?? (await getSrcDirectory());
47
+ const existingContractNames = getExistingContracts(srcDir).map(({ basename }) => basename);
48
+
49
+ // Load the config
50
+ const mudConfig = (await loadConfig(configPath)) as StoreConfig & WorldConfig;
51
+
52
+ if (printConfig) console.log(chalk.green("\nResolved config:\n"), JSON.stringify(mudConfig, null, 2));
53
+
54
+ const privateKey = process.env.PRIVATE_KEY;
55
+ if (!privateKey)
56
+ throw new MUDError(
57
+ `Missing PRIVATE_KEY environment variable.
58
+ Run 'echo "PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" > .env'
59
+ in your contracts directory to use the default anvil private key.`
60
+ );
61
+ const deploymentInfo = await deploy(mudConfig, existingContractNames, { ...args, rpc, privateKey });
62
+
63
+ if (args.saveDeployment) {
64
+ // Write deployment result to file (latest and timestamp)
65
+ const chainId = await getChainId(rpc);
66
+ const outputDir = path.join(mudConfig.deploysDirectory, chainId.toString());
67
+ mkdirSync(outputDir, { recursive: true });
68
+ writeFileSync(path.join(outputDir, "latest.json"), JSON.stringify(deploymentInfo, null, 2));
69
+ writeFileSync(path.join(outputDir, Date.now() + ".json"), JSON.stringify(deploymentInfo, null, 2));
70
+
71
+ const localChains = [1337, 31337];
72
+ const deploys = existsSync(mudConfig.worldsFile) ? JSON.parse(readFileSync(mudConfig.worldsFile, "utf-8")) : {};
73
+ deploys[chainId] = {
74
+ address: deploymentInfo.worldAddress,
75
+ // We expect the worlds file to be committed and since local deployments are often a consistent address but different block number, we'll ignore the block number.
76
+ blockNumber: localChains.includes(chainId) ? undefined : deploymentInfo.blockNumber,
77
+ };
78
+ writeFileSync(mudConfig.worldsFile, JSON.stringify(deploys, null, 2));
79
+
80
+ console.log(
81
+ chalk.bgGreen(chalk.whiteBright(`\n Deployment result (written to ${mudConfig.worldsFile} and ${outputDir}): \n`))
82
+ );
83
+ }
84
+
85
+ console.log(deploymentInfo);
86
+ return deploymentInfo;
87
+ }
@@ -2,7 +2,7 @@ import chalk from "chalk";
2
2
  import { ZodError } from "zod";
3
3
  import { fromZodError, ValidationError } from "zod-validation-error";
4
4
  import { NotInsideProjectError } from "@latticexyz/config";
5
- import { MUDError } from "@latticexyz/config";
5
+ import { MUDError } from "@latticexyz/common/errors";
6
6
 
7
7
  export function logError(error: unknown) {
8
8
  if (error instanceof ValidationError) {
@@ -0,0 +1,12 @@
1
+ import glob from "glob";
2
+ import { basename } from "path";
3
+
4
+ /**
5
+ * Get a list of all contract paths/names within the provided src directory
6
+ */
7
+ export function getExistingContracts(srcDir: string) {
8
+ return glob.sync(`${srcDir}/**/*.sol`).map((path) => ({
9
+ path,
10
+ basename: basename(path, ".sol"),
11
+ }));
12
+ }
@@ -1,5 +1,7 @@
1
- export * from "./errors.js";
2
- export * from "./foundry.js";
3
- export * from "./format.js";
4
- export * from "./formatAndWrite.js";
5
- export * from "./deploy-v2.js";
1
+ export * from "./deploy";
2
+ export * from "./deployHandler";
3
+ export * from "./errors";
4
+ export * from "./getChainId";
5
+ export * from "./getExistingContracts";
6
+ export * from "./printMUD";
7
+ export * from "./worldtypes";