@latticexyz/cli 2.0.0-next.7 → 2.0.0-next.9
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.
- package/dist/chunk-WO5OF7CG.js +11 -0
- package/dist/chunk-WO5OF7CG.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/mud.js +7 -7
- package/dist/mud.js.map +1 -1
- package/package.json +14 -12
- package/src/commands/deploy.ts +1 -1
- package/src/commands/dev-contracts.ts +16 -8
- package/src/commands/tablegen.ts +3 -2
- package/src/commands/test.ts +1 -1
- package/src/commands/trace.ts +14 -9
- package/src/commands/worldgen.ts +1 -1
- package/src/utils/deploy.ts +189 -540
- package/src/utils/deployHandler.ts +9 -3
- package/src/utils/modules/constants.ts +23 -0
- package/src/utils/modules/getInstallModuleCallData.ts +27 -0
- package/src/utils/modules/getUserModules.ts +5 -0
- package/src/utils/modules/types.ts +14 -0
- package/src/utils/systems/getGrantAccessCallData.ts +29 -0
- package/src/utils/systems/getRegisterFunctionSelectorsCallData.ts +57 -0
- package/src/utils/systems/getRegisterSystemCallData.ts +17 -0
- package/src/utils/systems/types.ts +9 -0
- package/src/utils/systems/utils.ts +42 -0
- package/src/utils/tables/getRegisterTableCallData.ts +49 -0
- package/src/utils/tables/getTableIds.ts +21 -0
- package/src/utils/tables/types.ts +12 -0
- package/src/utils/utils/confirmNonce.ts +24 -0
- package/src/utils/utils/deployContract.ts +33 -0
- package/src/utils/utils/fastTxExecute.ts +56 -0
- package/src/utils/utils/getContractData.ts +29 -0
- package/src/utils/utils/postDeploy.ts +25 -0
- package/src/utils/utils/setInternalFeePerGas.ts +49 -0
- package/src/utils/utils/toBytes16.ts +16 -0
- package/src/utils/utils/types.ts +21 -0
- package/src/utils/world.ts +28 -0
- package/dist/chunk-KRDCJ5DZ.js +0 -11
- package/dist/chunk-KRDCJ5DZ.js.map +0 -1
- package/src/utils/index.ts +0 -6
- /package/src/utils/{getChainId.ts → utils/getChainId.ts} +0 -0
package/src/utils/deploy.ts
CHANGED
@@ -1,23 +1,28 @@
|
|
1
|
-
import { existsSync, readFileSync } from "fs";
|
2
|
-
import path from "path";
|
3
1
|
import chalk from "chalk";
|
4
|
-
import
|
5
|
-
import {
|
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";
|
2
|
+
import path from "path";
|
3
|
+
import { ethers } from "ethers";
|
4
|
+
import { getOutDirectory, cast, getSrcDirectory, getRemappings } from "@latticexyz/common/foundry";
|
11
5
|
import { StoreConfig } from "@latticexyz/store";
|
12
|
-
import { resolveAbiOrUserType } from "@latticexyz/store/codegen";
|
13
6
|
import { WorldConfig, resolveWorldConfig } from "@latticexyz/world";
|
14
|
-
import
|
15
|
-
import
|
16
|
-
import CoreModuleData from "@latticexyz/world/
|
17
|
-
import
|
18
|
-
import
|
19
|
-
import
|
20
|
-
import {
|
7
|
+
import { deployWorldContract } from "./world";
|
8
|
+
import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" };
|
9
|
+
import CoreModuleData from "@latticexyz/world/out/CoreModule.sol/CoreModule.json" assert { type: "json" };
|
10
|
+
import { defaultModuleContracts } from "./modules/constants";
|
11
|
+
import { getInstallModuleCallData } from "./modules/getInstallModuleCallData";
|
12
|
+
import { getUserModules } from "./modules/getUserModules";
|
13
|
+
import { getGrantAccessCallData } from "./systems/getGrantAccessCallData";
|
14
|
+
import { getRegisterFunctionSelectorsCallData } from "./systems/getRegisterFunctionSelectorsCallData";
|
15
|
+
import { getRegisterSystemCallData } from "./systems/getRegisterSystemCallData";
|
16
|
+
import { getRegisterTableCallData } from "./tables/getRegisterTableCallData";
|
17
|
+
import { getTableIds } from "./tables/getTableIds";
|
18
|
+
import { confirmNonce } from "./utils/confirmNonce";
|
19
|
+
import { deployContract } from "./utils/deployContract";
|
20
|
+
import { fastTxExecute } from "./utils/fastTxExecute";
|
21
|
+
import { getContractData } from "./utils/getContractData";
|
22
|
+
import { postDeploy } from "./utils/postDeploy";
|
23
|
+
import { setInternalFeePerGas } from "./utils/setInternalFeePerGas";
|
24
|
+
import { toBytes16 } from "./utils/toBytes16";
|
25
|
+
import { ContractCode } from "./utils/types";
|
21
26
|
|
22
27
|
export interface DeployConfig {
|
23
28
|
profile?: string;
|
@@ -40,13 +45,13 @@ export async function deploy(
|
|
40
45
|
existingContractNames: string[],
|
41
46
|
deployConfig: DeployConfig
|
42
47
|
): Promise<DeploymentInfo> {
|
43
|
-
const resolvedConfig = resolveWorldConfig(mudConfig, existingContractNames);
|
44
|
-
|
45
48
|
const startTime = Date.now();
|
46
|
-
const { worldContractName, namespace, postDeployScript } = mudConfig;
|
47
49
|
const { profile, rpc, privateKey, priorityFeeMultiplier, debug, worldAddress, disableTxWait, pollInterval } =
|
48
50
|
deployConfig;
|
51
|
+
const resolvedConfig = resolveWorldConfig(mudConfig, existingContractNames);
|
49
52
|
const forgeOutDirectory = await getOutDirectory(profile);
|
53
|
+
const remappings = await getRemappings(profile);
|
54
|
+
const outputBaseDirectory = path.join(await getSrcDirectory(profile), mudConfig.codegenDirectory);
|
50
55
|
|
51
56
|
// Set up signer for deployment
|
52
57
|
const provider = new ethers.providers.StaticJsonRpcProvider(rpc);
|
@@ -54,552 +59,196 @@ export async function deploy(
|
|
54
59
|
const signer = new ethers.Wallet(privateKey, provider);
|
55
60
|
console.log("Deploying from", signer.address);
|
56
61
|
|
57
|
-
// Manual nonce handling to allow for faster sending of transactions without waiting for previous transactions
|
58
62
|
let nonce = await signer.getTransactionCount();
|
59
63
|
console.log("Initial nonce", nonce);
|
60
64
|
|
61
|
-
|
62
|
-
let maxPriorityFeePerGas: number | undefined;
|
63
|
-
let maxFeePerGas: BigNumber | undefined;
|
64
|
-
let gasPrice: BigNumber | undefined;
|
65
|
+
const txParams = await setInternalFeePerGas(signer, priorityFeeMultiplier);
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
const txConfig = {
|
68
|
+
...txParams,
|
69
|
+
signer,
|
70
|
+
debug: Boolean(debug),
|
71
|
+
disableTxWait,
|
72
|
+
confirmations: disableTxWait ? 0 : 1,
|
73
|
+
};
|
70
74
|
|
71
75
|
// Get block number before deploying
|
72
76
|
const blockNumber = Number(await cast(["block-number", "--rpc-url", rpc], { profile }));
|
73
77
|
console.log("Start deployment at block", blockNumber);
|
74
78
|
|
75
|
-
// Deploy World
|
76
|
-
const worldPromise =
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
// Deploy Systems
|
85
|
-
const systemPromises = Object.keys(resolvedConfig.systems).reduce<Record<string, Promise<string>>>(
|
86
|
-
(acc, systemName) => {
|
87
|
-
acc[systemName] = deployContractByName(systemName, disableTxWait);
|
88
|
-
return acc;
|
89
|
-
},
|
90
|
-
{}
|
91
|
-
);
|
92
|
-
|
93
|
-
// Deploy default World modules
|
94
|
-
const defaultModules: Record<string, Promise<string>> = {
|
95
|
-
// TODO: these only need to be deployed once per chain, add a check if they exist already
|
96
|
-
CoreModule: deployContract(CoreModuleData.abi, CoreModuleData.bytecode, disableTxWait, "CoreModule"),
|
97
|
-
KeysWithValueModule: deployContract(
|
98
|
-
KeysWithValueModuleData.abi,
|
99
|
-
KeysWithValueModuleData.bytecode,
|
100
|
-
disableTxWait,
|
101
|
-
"KeysWithValueModule"
|
102
|
-
),
|
103
|
-
KeysInTableModule: deployContract(
|
104
|
-
KeysInTableModuleData.abi,
|
105
|
-
KeysInTableModuleData.bytecode,
|
106
|
-
disableTxWait,
|
107
|
-
"KeysInTableModule"
|
108
|
-
),
|
109
|
-
UniqueEntityModule: deployContract(
|
110
|
-
UniqueEntityModuleData.abi,
|
111
|
-
UniqueEntityModuleData.bytecode,
|
112
|
-
disableTxWait,
|
113
|
-
"UniqueEntityModule"
|
114
|
-
),
|
115
|
-
};
|
116
|
-
|
117
|
-
// Deploy user Modules
|
118
|
-
const modulePromises = mudConfig.modules
|
119
|
-
.filter((module) => !defaultModules[module.name]) // Only deploy user modules here, not default modules
|
120
|
-
.reduce<Record<string, Promise<string>>>((acc, module) => {
|
121
|
-
acc[module.name] = deployContractByName(module.name, disableTxWait);
|
122
|
-
return acc;
|
123
|
-
}, defaultModules);
|
124
|
-
|
125
|
-
// Combine all contracts into one object
|
126
|
-
const contractPromises: Record<string, Promise<string>> = { ...worldPromise, ...systemPromises, ...modulePromises };
|
127
|
-
|
128
|
-
// Create World contract instance from deployed address
|
129
|
-
const WorldContract = new ethers.Contract(await contractPromises.World, IBaseWorldData.abi, signer);
|
130
|
-
|
131
|
-
const confirmations = disableTxWait ? 0 : 1;
|
132
|
-
|
133
|
-
// Install core Modules
|
134
|
-
if (!worldAddress) {
|
135
|
-
console.log(chalk.blue("Installing core World modules"));
|
136
|
-
await fastTxExecute(WorldContract, "installRootModule", [await modulePromises.CoreModule, "0x"], confirmations);
|
137
|
-
console.log(chalk.green("Installed core World modules"));
|
138
|
-
}
|
139
|
-
|
140
|
-
// Register namespace
|
141
|
-
if (namespace) await fastTxExecute(WorldContract, "registerNamespace", [toBytes16(namespace)], confirmations);
|
142
|
-
|
143
|
-
// Register tables
|
144
|
-
const tableIds: { [tableName: string]: Uint8Array } = {};
|
145
|
-
promises = [
|
146
|
-
...promises,
|
147
|
-
...Object.entries(mudConfig.tables).map(async ([tableName, { name, schema, keySchema }]) => {
|
148
|
-
console.log(chalk.blue(`Registering table ${tableName} at ${namespace}/${name}`));
|
149
|
-
|
150
|
-
// Store the tableId for later use
|
151
|
-
tableIds[tableName] = toResourceSelector(namespace, name);
|
152
|
-
|
153
|
-
// Register table
|
154
|
-
const schemaTypes = Object.values(schema).map((abiOrUserType) => {
|
155
|
-
const { schemaType } = resolveAbiOrUserType(abiOrUserType, mudConfig);
|
156
|
-
return schemaType;
|
157
|
-
});
|
158
|
-
|
159
|
-
const keyTypes = Object.values(keySchema).map((abiOrUserType) => {
|
160
|
-
const { schemaType } = resolveAbiOrUserType(abiOrUserType, mudConfig);
|
161
|
-
return schemaType;
|
79
|
+
// Deploy the World contract. Non-blocking.
|
80
|
+
const worldPromise: Promise<string> = worldAddress
|
81
|
+
? Promise.resolve(worldAddress)
|
82
|
+
: deployWorldContract({
|
83
|
+
...txConfig,
|
84
|
+
nonce: nonce++,
|
85
|
+
worldContractName: mudConfig.worldContractName,
|
86
|
+
forgeOutDirectory,
|
162
87
|
});
|
163
88
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
);
|
194
|
-
console.log(chalk.green(`Registered system ${systemName} at ${namespace}/${name}`));
|
195
|
-
|
196
|
-
// Register function selectors for the system
|
197
|
-
if (registerFunctionSelectors) {
|
198
|
-
const functionSignatures: FunctionSignature[] = await loadFunctionSignatures(systemName);
|
199
|
-
const isRoot = namespace === "";
|
200
|
-
// Using Promise.all to avoid blocking on async calls
|
201
|
-
await Promise.all(
|
202
|
-
functionSignatures.map(async ({ functionName, functionArgs }) => {
|
203
|
-
const functionSignature = isRoot
|
204
|
-
? functionName + functionArgs
|
205
|
-
: `${namespace}_${name}_${functionName}${functionArgs}`;
|
206
|
-
|
207
|
-
console.log(chalk.blue(`Registering function "${functionSignature}"`));
|
208
|
-
if (isRoot) {
|
209
|
-
const worldFunctionSelector = toFunctionSelector(
|
210
|
-
functionSignature === ""
|
211
|
-
? { functionName: systemName, functionArgs } // Register the system's fallback function as `<systemName>(<args>)`
|
212
|
-
: { functionName, functionArgs }
|
213
|
-
);
|
214
|
-
const systemFunctionSelector = toFunctionSelector({ functionName, functionArgs });
|
215
|
-
await fastTxExecute(
|
216
|
-
WorldContract,
|
217
|
-
"registerRootFunctionSelector",
|
218
|
-
[tableIdToHex(namespace, name), worldFunctionSelector, systemFunctionSelector],
|
219
|
-
confirmations
|
220
|
-
);
|
221
|
-
} else {
|
222
|
-
await fastTxExecute(
|
223
|
-
WorldContract,
|
224
|
-
"registerFunctionSelector",
|
225
|
-
[tableIdToHex(namespace, name), functionName, functionArgs],
|
226
|
-
confirmations
|
227
|
-
);
|
228
|
-
}
|
229
|
-
console.log(chalk.green(`Registered function "${functionSignature}"`));
|
230
|
-
})
|
231
|
-
);
|
232
|
-
}
|
233
|
-
}
|
234
|
-
),
|
235
|
-
];
|
236
|
-
|
237
|
-
// Wait for resources to be registered before granting access to them
|
238
|
-
await Promise.all(promises); // ----------------------------------------------------------------------------------------------
|
239
|
-
promises = [];
|
240
|
-
|
241
|
-
// Grant access to systems
|
242
|
-
for (const [systemName, { name, accessListAddresses, accessListSystems }] of Object.entries(resolvedConfig.systems)) {
|
243
|
-
const resourceSelector = `${namespace}/${name}`;
|
244
|
-
|
245
|
-
// Grant access to addresses
|
246
|
-
promises = [
|
247
|
-
...promises,
|
248
|
-
...accessListAddresses.map(async (address) => {
|
249
|
-
console.log(chalk.blue(`Grant ${address} access to ${systemName} (${resourceSelector})`));
|
250
|
-
await fastTxExecute(WorldContract, "grantAccess", [tableIdToHex(namespace, name), address], confirmations);
|
251
|
-
console.log(chalk.green(`Granted ${address} access to ${systemName} (${namespace}/${name})`));
|
252
|
-
}),
|
253
|
-
];
|
254
|
-
|
255
|
-
// Grant access to other systems
|
256
|
-
promises = [
|
257
|
-
...promises,
|
258
|
-
...accessListSystems.map(async (granteeSystem) => {
|
259
|
-
console.log(chalk.blue(`Grant ${granteeSystem} access to ${systemName} (${resourceSelector})`));
|
260
|
-
await fastTxExecute(
|
261
|
-
WorldContract,
|
262
|
-
"grantAccess",
|
263
|
-
[tableIdToHex(namespace, name), await contractPromises[granteeSystem]],
|
264
|
-
confirmations
|
265
|
-
);
|
266
|
-
console.log(chalk.green(`Granted ${granteeSystem} access to ${systemName} (${resourceSelector})`));
|
267
|
-
}),
|
268
|
-
];
|
269
|
-
}
|
270
|
-
|
271
|
-
// Wait for access to be granted before installing modules
|
272
|
-
await Promise.all(promises); // ----------------------------------------------------------------------------------------------
|
273
|
-
promises = [];
|
274
|
-
|
275
|
-
// Install modules
|
276
|
-
promises = [
|
277
|
-
...promises,
|
278
|
-
...mudConfig.modules.map(async (module) => {
|
279
|
-
console.log(chalk.blue(`Installing${module.root ? " root " : " "}module ${module.name}`));
|
280
|
-
// Resolve arguments
|
281
|
-
const resolvedArgs = await Promise.all(
|
282
|
-
module.args.map((arg) => resolveWithContext(arg, { tableIds, systemAddresses: contractPromises }))
|
283
|
-
);
|
284
|
-
const values = resolvedArgs.map((arg) => arg.value);
|
285
|
-
const types = resolvedArgs.map((arg) => arg.type);
|
286
|
-
const moduleAddress = await contractPromises[module.name];
|
287
|
-
if (!moduleAddress) throw new Error(`Module ${module.name} not found`);
|
288
|
-
|
289
|
-
// Send transaction to install module
|
290
|
-
await fastTxExecute(
|
291
|
-
WorldContract,
|
292
|
-
module.root ? "installRootModule" : "installModule",
|
293
|
-
[moduleAddress, abi.encode(types, values)],
|
294
|
-
confirmations
|
295
|
-
);
|
296
|
-
|
297
|
-
console.log(chalk.green(`Installed${module.root ? " root " : " "}module ${module.name}`));
|
298
|
-
}),
|
89
|
+
// Filters any default modules from config
|
90
|
+
const userModules = getUserModules(defaultModuleContracts, mudConfig.modules);
|
91
|
+
const userModuleContracts = Object.keys(userModules).map((name) => {
|
92
|
+
const { abi, bytecode } = getContractData(name, forgeOutDirectory);
|
93
|
+
return {
|
94
|
+
name,
|
95
|
+
abi,
|
96
|
+
bytecode,
|
97
|
+
} as ContractCode;
|
98
|
+
});
|
99
|
+
|
100
|
+
const systemContracts = Object.keys(resolvedConfig.systems).map((name) => {
|
101
|
+
const { abi, bytecode } = getContractData(name, forgeOutDirectory);
|
102
|
+
return {
|
103
|
+
name,
|
104
|
+
abi,
|
105
|
+
bytecode,
|
106
|
+
} as ContractCode;
|
107
|
+
});
|
108
|
+
|
109
|
+
const contracts: ContractCode[] = [
|
110
|
+
{
|
111
|
+
name: "CoreModule",
|
112
|
+
abi: CoreModuleData.abi,
|
113
|
+
bytecode: CoreModuleData.bytecode,
|
114
|
+
},
|
115
|
+
...defaultModuleContracts,
|
116
|
+
...userModuleContracts,
|
117
|
+
...systemContracts,
|
299
118
|
];
|
300
119
|
|
301
|
-
//
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
chalk.gray(
|
311
|
-
`Waiting for transactions to be included before executing ${postDeployScript} (local nonce: ${nonce}, remote nonce: ${remoteNonce}, retry number ${retryCount}/${maxRetries})`
|
312
|
-
)
|
313
|
-
);
|
314
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
315
|
-
retryCount++;
|
316
|
-
remoteNonce = await signer.getTransactionCount();
|
317
|
-
}
|
318
|
-
if (remoteNonce !== nonce) {
|
319
|
-
throw new MUDError(
|
320
|
-
"Remote nonce doesn't match local nonce, indicating that not all deploy transactions were included."
|
321
|
-
);
|
322
|
-
}
|
323
|
-
|
324
|
-
promises = [];
|
325
|
-
|
326
|
-
// Execute postDeploy forge script
|
327
|
-
const postDeployPath = path.join(await getScriptDirectory(), postDeployScript + ".s.sol");
|
328
|
-
if (existsSync(postDeployPath)) {
|
329
|
-
console.log(chalk.blue(`Executing post deploy script at ${postDeployPath}`));
|
330
|
-
await forge(
|
331
|
-
[
|
332
|
-
"script",
|
333
|
-
postDeployScript,
|
334
|
-
"--sig",
|
335
|
-
"run(address)",
|
336
|
-
await contractPromises.World,
|
337
|
-
"--broadcast",
|
338
|
-
"--rpc-url",
|
339
|
-
rpc,
|
340
|
-
"-vvv",
|
341
|
-
],
|
342
|
-
{
|
343
|
-
profile,
|
344
|
-
}
|
345
|
-
);
|
346
|
-
} else {
|
347
|
-
console.log(`No script at ${postDeployPath}, skipping post deploy hook`);
|
348
|
-
}
|
349
|
-
|
350
|
-
console.log(chalk.green("Deployment completed in", (Date.now() - startTime) / 1000, "seconds"));
|
351
|
-
|
352
|
-
return { worldAddress: await contractPromises.World, blockNumber };
|
353
|
-
|
354
|
-
// ------------------- INTERNAL FUNCTIONS -------------------
|
355
|
-
// (Inlined to avoid having to pass around nonce, signer and forgeOutDir)
|
356
|
-
|
357
|
-
/**
|
358
|
-
* Deploy a contract and return the address
|
359
|
-
* @param contractName Name of the contract to deploy (must exist in the file system)
|
360
|
-
* @param disableTxWait Disable waiting for contract deployment
|
361
|
-
* @returns Address of the deployed contract
|
362
|
-
*/
|
363
|
-
async function deployContractByName(contractName: string, disableTxWait: boolean): Promise<string> {
|
364
|
-
console.log(chalk.blue("Deploying", contractName));
|
365
|
-
|
366
|
-
const { abi, bytecode } = await getContractData(contractName);
|
367
|
-
return deployContract(abi, bytecode, disableTxWait, contractName);
|
368
|
-
}
|
369
|
-
|
370
|
-
/**
|
371
|
-
* Deploy a contract and return the address
|
372
|
-
* @param abi The contract interface
|
373
|
-
* @param bytecode The contract bytecode
|
374
|
-
* @param disableTxWait Disable waiting for contract deployment
|
375
|
-
* @param contractName The contract name (optional, used for logs)
|
376
|
-
* @param retryCount
|
377
|
-
* @returns Address of the deployed contract
|
378
|
-
*/
|
379
|
-
async function deployContract(
|
380
|
-
abi: ContractInterface,
|
381
|
-
bytecode: string | { object: string },
|
382
|
-
disableTxWait: boolean,
|
383
|
-
contractName?: string,
|
384
|
-
retryCount = 0
|
385
|
-
): Promise<string> {
|
386
|
-
try {
|
387
|
-
const factory = new ethers.ContractFactory(abi, bytecode, signer);
|
388
|
-
console.log(chalk.gray(`executing deployment of ${contractName} with nonce ${nonce}`));
|
389
|
-
const deployPromise = factory
|
390
|
-
.deploy({
|
391
|
-
nonce: nonce++,
|
392
|
-
maxPriorityFeePerGas,
|
393
|
-
maxFeePerGas,
|
394
|
-
gasPrice,
|
395
|
-
})
|
396
|
-
.then((c) => (disableTxWait ? c : c.deployed()));
|
397
|
-
|
398
|
-
promises.push(deployPromise);
|
399
|
-
const { address } = await deployPromise;
|
400
|
-
|
401
|
-
console.log(chalk.green("Deployed", contractName, "to", address));
|
402
|
-
return address;
|
403
|
-
} catch (error: any) {
|
404
|
-
if (debug) console.error(error);
|
405
|
-
if (retryCount === 0 && error?.message.includes("transaction already imported")) {
|
406
|
-
// If the deployment failed because the transaction was already imported,
|
407
|
-
// retry with a higher priority fee
|
408
|
-
setInternalFeePerGas(priorityFeeMultiplier * 1.1);
|
409
|
-
return deployContract(abi, bytecode, disableTxWait, contractName, retryCount++);
|
410
|
-
} else if (error?.message.includes("invalid bytecode")) {
|
411
|
-
throw new MUDError(
|
412
|
-
`Error deploying ${contractName}: invalid bytecode. Note that linking of public libraries is not supported yet, make sure none of your libraries use "external" functions.`
|
413
|
-
);
|
414
|
-
} else if (error?.message.includes("CreateContractLimit")) {
|
415
|
-
throw new MUDError(`Error deploying ${contractName}: CreateContractLimit exceeded.`);
|
416
|
-
} else throw error;
|
417
|
-
}
|
418
|
-
}
|
419
|
-
|
420
|
-
/**
|
421
|
-
* Deploy a contract and return the address
|
422
|
-
* @param contractName Name of the contract to deploy (must exist in the file system)
|
423
|
-
* @returns Address of the deployed contract
|
424
|
-
*
|
425
|
-
* NOTE: Forge deploy seems to be slightly slower than ethers
|
426
|
-
* (probably due to the extra overhead spawning a child process to run forge),
|
427
|
-
* so we mostly use ethersDeployContract here.
|
428
|
-
* However, for contracts not in the user directory (eg. the vanilla World contract),
|
429
|
-
* using forge is more convenient because it automatically finds the contract in the @latticexyz/world package.
|
430
|
-
*/
|
431
|
-
// async function forgeDeployContract(contractName: string): Promise<string> {
|
432
|
-
// console.log(chalk.blue("Deploying", contractName));
|
433
|
-
|
434
|
-
// const { deployedTo } = JSON.parse(
|
435
|
-
// await forge(
|
436
|
-
// ["create", contractName, "--rpc-url", rpc, "--private-key", privateKey, "--json", "--nonce", String(nonce++)],
|
437
|
-
// { profile, silent: true }
|
438
|
-
// )
|
439
|
-
// );
|
440
|
-
// return deployedTo;
|
441
|
-
// }
|
442
|
-
|
443
|
-
async function loadFunctionSignatures(contractName: string): Promise<FunctionSignature[]> {
|
444
|
-
const { abi } = await getContractData(contractName);
|
445
|
-
|
446
|
-
return abi
|
447
|
-
.filter((item) => ["fallback", "function"].includes(item.type))
|
448
|
-
.map((item) => {
|
449
|
-
if (item.type === "fallback") return { functionName: "", functionArgs: "" };
|
120
|
+
// Deploy the System and Module contracts
|
121
|
+
const deployedContracts = contracts.reduce<Record<string, Promise<string>>>((acc, contract) => {
|
122
|
+
acc[contract.name] = deployContract({
|
123
|
+
...txConfig,
|
124
|
+
nonce: nonce++,
|
125
|
+
contract,
|
126
|
+
});
|
127
|
+
return acc;
|
128
|
+
}, {});
|
450
129
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
};
|
455
|
-
});
|
456
|
-
}
|
130
|
+
// Wait for world to be deployed
|
131
|
+
const deployedWorldAddress = await worldPromise;
|
132
|
+
const worldContract = new ethers.Contract(deployedWorldAddress, IBaseWorldAbi);
|
457
133
|
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
return parseComponents(param.components) + tupleMatch[1];
|
468
|
-
} else {
|
469
|
-
return param.type;
|
470
|
-
}
|
134
|
+
// If an existing World is passed assume its coreModule is already installed - blocking to install if not
|
135
|
+
if (!worldAddress) {
|
136
|
+
console.log(chalk.blue("Installing CoreModule"));
|
137
|
+
await fastTxExecute({
|
138
|
+
...txConfig,
|
139
|
+
nonce: nonce++,
|
140
|
+
contract: worldContract,
|
141
|
+
func: "initialize",
|
142
|
+
args: [await deployedContracts["CoreModule"]],
|
471
143
|
});
|
472
|
-
|
473
|
-
}
|
474
|
-
|
475
|
-
/**
|
476
|
-
* Only await gas estimation (for speed), only execute if gas estimation succeeds (for safety)
|
477
|
-
*/
|
478
|
-
async function fastTxExecute<C extends { estimateGas: any; [key: string]: any }, F extends keyof C>(
|
479
|
-
contract: C,
|
480
|
-
func: F,
|
481
|
-
args: Parameters<C[F]>,
|
482
|
-
confirmations = 1,
|
483
|
-
retryCount = 0
|
484
|
-
): Promise<Awaited<ReturnType<Awaited<ReturnType<C[F]>>["wait"]>>> {
|
485
|
-
const functionName = `${func as string}(${args.map((arg) => `'${arg}'`).join(",")})`;
|
486
|
-
try {
|
487
|
-
const gasLimit = await contract.estimateGas[func].apply(null, args);
|
488
|
-
console.log(chalk.gray(`executing transaction: ${functionName} with nonce ${nonce}`));
|
489
|
-
const txPromise = contract[func]
|
490
|
-
.apply(null, [...args, { gasLimit, nonce: nonce++, maxPriorityFeePerGas, maxFeePerGas, gasPrice }])
|
491
|
-
.then((tx: any) => (confirmations === 0 ? tx : tx.wait(confirmations)));
|
492
|
-
promises.push(txPromise);
|
493
|
-
return txPromise;
|
494
|
-
} catch (error: any) {
|
495
|
-
if (debug) console.error(error);
|
496
|
-
if (retryCount === 0 && error?.message.includes("transaction already imported")) {
|
497
|
-
// If the deployment failed because the transaction was already imported,
|
498
|
-
// retry with a higher priority fee
|
499
|
-
setInternalFeePerGas(priorityFeeMultiplier * 1.1);
|
500
|
-
return fastTxExecute(contract, func, args, confirmations, retryCount++);
|
501
|
-
} else throw new MUDError(`Gas estimation error for ${functionName}: ${error?.reason}`);
|
502
|
-
}
|
144
|
+
console.log(chalk.green("Installed CoreModule"));
|
503
145
|
}
|
504
146
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
throw new MUDError(`Error reading file at ${contractDataPath}`);
|
516
|
-
}
|
517
|
-
|
518
|
-
const bytecode = data?.bytecode?.object;
|
519
|
-
if (!bytecode) throw new MUDError(`No bytecode found in ${contractDataPath}`);
|
520
|
-
|
521
|
-
const abi = data?.abi;
|
522
|
-
if (!abi) throw new MUDError(`No ABI found in ${contractDataPath}`);
|
523
|
-
|
524
|
-
return { abi, bytecode };
|
147
|
+
if (mudConfig.namespace) {
|
148
|
+
console.log(chalk.blue("Registering Namespace"));
|
149
|
+
await fastTxExecute({
|
150
|
+
...txConfig,
|
151
|
+
nonce: nonce++,
|
152
|
+
contract: worldContract,
|
153
|
+
func: "registerNamespace",
|
154
|
+
args: [toBytes16(mudConfig.namespace)],
|
155
|
+
});
|
156
|
+
console.log(chalk.green("Namespace registered"));
|
525
157
|
}
|
526
158
|
|
527
|
-
|
528
|
-
* Set the maxFeePerGas and maxPriorityFeePerGas based on the current base fee and the given multiplier.
|
529
|
-
* The multiplier is used to allow replacing pending transactions.
|
530
|
-
* @param multiplier Multiplier to apply to the base fee
|
531
|
-
*/
|
532
|
-
async function setInternalFeePerGas(multiplier: number) {
|
533
|
-
// Compute maxFeePerGas and maxPriorityFeePerGas like ethers, but allow for a multiplier to allow replacing pending transactions
|
534
|
-
const feeData = await provider.getFeeData();
|
535
|
-
|
536
|
-
if (feeData.lastBaseFeePerGas) {
|
537
|
-
if (!feeData.lastBaseFeePerGas.eq(0) && (await signer.getBalance()).eq(0)) {
|
538
|
-
throw new MUDError(`Attempting to deploy to a chain with non-zero base fee with an account that has no balance.
|
539
|
-
If you're deploying to the Lattice testnet, you can fund your account by running 'pnpm mud faucet --address ${await signer.getAddress()}'`);
|
540
|
-
}
|
159
|
+
const tableIds = getTableIds(mudConfig);
|
541
160
|
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
} else if (feeData.gasPrice) {
|
546
|
-
// Legacy chains with gasPrice instead of maxFeePerGas
|
547
|
-
if (!feeData.gasPrice.eq(0) && (await signer.getBalance()).eq(0)) {
|
548
|
-
throw new MUDError(
|
549
|
-
`Attempting to deploy to a chain with non-zero gas price with an account that has no balance.`
|
550
|
-
);
|
551
|
-
}
|
161
|
+
const registerTableCalls = Object.values(mudConfig.tables).map((table) =>
|
162
|
+
getRegisterTableCallData(table, mudConfig, outputBaseDirectory, remappings)
|
163
|
+
);
|
552
164
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
165
|
+
console.log(chalk.blue("Registering tables"));
|
166
|
+
await Promise.all(
|
167
|
+
registerTableCalls.map((call) =>
|
168
|
+
fastTxExecute({
|
169
|
+
...txConfig,
|
170
|
+
nonce: nonce++,
|
171
|
+
contract: worldContract,
|
172
|
+
...call,
|
173
|
+
})
|
174
|
+
)
|
175
|
+
);
|
176
|
+
console.log(chalk.green(`Tables registered`));
|
177
|
+
|
178
|
+
console.log(chalk.blue("Registering Systems and Functions"));
|
179
|
+
const systemCalls = await Promise.all(
|
180
|
+
Object.entries(resolvedConfig.systems).map(([systemKey, system]) =>
|
181
|
+
getRegisterSystemCallData({
|
182
|
+
systemContracts: deployedContracts,
|
183
|
+
systemKey,
|
184
|
+
system,
|
185
|
+
namespace: mudConfig.namespace,
|
186
|
+
})
|
187
|
+
)
|
188
|
+
);
|
189
|
+
const functionCalls = Object.entries(resolvedConfig.systems).flatMap(([systemKey, system]) =>
|
190
|
+
getRegisterFunctionSelectorsCallData({
|
191
|
+
systemContractName: systemKey,
|
192
|
+
system,
|
193
|
+
namespace: mudConfig.namespace,
|
194
|
+
forgeOutDirectory,
|
195
|
+
})
|
196
|
+
);
|
197
|
+
await Promise.all(
|
198
|
+
[...systemCalls, ...functionCalls].map((call) =>
|
199
|
+
fastTxExecute({
|
200
|
+
...txConfig,
|
201
|
+
nonce: nonce++,
|
202
|
+
contract: worldContract,
|
203
|
+
...call,
|
204
|
+
})
|
205
|
+
)
|
206
|
+
);
|
207
|
+
console.log(chalk.green(`Systems and Functions registered`));
|
208
|
+
|
209
|
+
// Wait for System access to be granted before installing modules
|
210
|
+
const grantCalls = await getGrantAccessCallData({
|
211
|
+
systems: Object.values(resolvedConfig.systems),
|
212
|
+
systemContracts: deployedContracts,
|
213
|
+
namespace: mudConfig.namespace,
|
214
|
+
});
|
215
|
+
|
216
|
+
console.log(chalk.blue("Granting Access"));
|
217
|
+
await Promise.all(
|
218
|
+
grantCalls.map((call) =>
|
219
|
+
fastTxExecute({
|
220
|
+
...txConfig,
|
221
|
+
nonce: nonce++,
|
222
|
+
contract: worldContract,
|
223
|
+
...call,
|
224
|
+
})
|
225
|
+
)
|
226
|
+
);
|
227
|
+
console.log(chalk.green(`Access granted`));
|
559
228
|
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
if (input.length > 16) throw new Error("String does not fit into 16 bytes");
|
229
|
+
const moduleCalls = await Promise.all(
|
230
|
+
mudConfig.modules.map((m) => getInstallModuleCallData(deployedContracts, m, tableIds))
|
231
|
+
);
|
564
232
|
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
233
|
+
console.log(chalk.blue("Installing User Modules"));
|
234
|
+
await Promise.all(
|
235
|
+
moduleCalls.map((call) =>
|
236
|
+
fastTxExecute({
|
237
|
+
...txConfig,
|
238
|
+
nonce: nonce++,
|
239
|
+
contract: worldContract,
|
240
|
+
...call,
|
241
|
+
})
|
242
|
+
)
|
243
|
+
);
|
244
|
+
console.log(chalk.green(`User Modules Installed`));
|
576
245
|
|
577
|
-
//
|
578
|
-
|
579
|
-
function toResourceSelector(namespace: string, file: string): Uint8Array {
|
580
|
-
const namespaceBytes = toBytes16(namespace);
|
581
|
-
const fileBytes = toBytes16(file);
|
582
|
-
const result = new Uint8Array(32);
|
583
|
-
result.set(namespaceBytes);
|
584
|
-
result.set(fileBytes, 16);
|
585
|
-
return result;
|
586
|
-
}
|
246
|
+
// Double check that all transactions have been included by confirming the current nonce is the expected nonce
|
247
|
+
await confirmNonce(signer, nonce, pollInterval);
|
587
248
|
|
588
|
-
|
589
|
-
functionName: string;
|
590
|
-
functionArgs: string;
|
591
|
-
}
|
249
|
+
await postDeploy(mudConfig.postDeployScript, deployedWorldAddress, rpc, profile);
|
592
250
|
|
593
|
-
|
594
|
-
// (see https://github.com/latticexyz/mud/issues/499)
|
595
|
-
function toFunctionSelector({ functionName, functionArgs }: FunctionSignature): string {
|
596
|
-
const functionSignature = functionName + functionArgs;
|
597
|
-
if (functionSignature === "") return "0x";
|
598
|
-
return sigHash(functionSignature);
|
599
|
-
}
|
251
|
+
console.log(chalk.green("Deployment completed in", (Date.now() - startTime) / 1000, "seconds"));
|
600
252
|
|
601
|
-
|
602
|
-
// (see https://github.com/latticexyz/mud/issues/499)
|
603
|
-
function sigHash(signature: string) {
|
604
|
-
return ethers.utils.hexDataSlice(ethers.utils.keccak256(ethers.utils.toUtf8Bytes(signature)), 0, 4);
|
253
|
+
return { worldAddress: deployedWorldAddress, blockNumber };
|
605
254
|
}
|