@latticexyz/cli 2.2.18-8d0ce55e964e646a1c804c401df01c4deb866f30 → 2.2.18-9fa07c8489f1fbf167d0db01cd9aaa645a29c8e2
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-7SEEAWAF.js +25 -0
- package/dist/{chunk-PZL6GJOK.js.map → chunk-7SEEAWAF.js.map} +1 -1
- package/dist/commands-HW6E5GIK.js +3025 -0
- package/dist/commands-HW6E5GIK.js.map +1 -0
- package/dist/errors-ZD2NCPFD.js +7 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2 -0
- package/dist/mud.cjs +3566 -0
- package/dist/mud.cjs.map +1 -0
- package/dist/mud.d.cts +1 -0
- package/dist/mud.js +26 -1
- package/dist/mud.js.map +1 -1
- package/package.json +25 -16
- package/dist/chunk-PZL6GJOK.js +0 -4
- package/dist/commands-ORMBXY4R.js +0 -48
- package/dist/commands-ORMBXY4R.js.map +0 -1
- package/dist/errors-R4UWN5VJ.js +0 -2
- /package/dist/{errors-R4UWN5VJ.js.map → errors-ZD2NCPFD.js.map} +0 -0
@@ -0,0 +1,3025 @@
|
|
1
|
+
import {
|
2
|
+
logError
|
3
|
+
} from "./chunk-7SEEAWAF.js";
|
4
|
+
|
5
|
+
// src/commands/index.ts
|
6
|
+
import { command as gasReport } from "@latticexyz/gas-report/internal";
|
7
|
+
import { command as abiTs } from "@latticexyz/abi-ts/internal";
|
8
|
+
|
9
|
+
// src/commands/build.ts
|
10
|
+
import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
|
11
|
+
|
12
|
+
// src/build.ts
|
13
|
+
import { tablegen } from "@latticexyz/store/codegen";
|
14
|
+
import { buildSystemsManifest, worldgen } from "@latticexyz/world/node";
|
15
|
+
import { forge } from "@latticexyz/common/foundry";
|
16
|
+
import { execa } from "execa";
|
17
|
+
async function build({ rootDir, config, foundryProfile }) {
|
18
|
+
await Promise.all([tablegen({ rootDir, config }), worldgen({ rootDir, config })]);
|
19
|
+
await forge(["build"], { profile: foundryProfile });
|
20
|
+
await buildSystemsManifest({ rootDir, config });
|
21
|
+
await execa("mud", ["abi-ts"], { stdio: "inherit" });
|
22
|
+
}
|
23
|
+
|
24
|
+
// src/commands/build.ts
|
25
|
+
import path from "node:path";
|
26
|
+
var commandModule = {
|
27
|
+
command: "build",
|
28
|
+
describe: "Build contracts and generate MUD artifacts (table libraries, world interface, ABI)",
|
29
|
+
builder(yargs) {
|
30
|
+
return yargs.options({
|
31
|
+
configPath: { type: "string", desc: "Path to the MUD config file" },
|
32
|
+
profile: { type: "string", desc: "The foundry profile to use" }
|
33
|
+
});
|
34
|
+
},
|
35
|
+
async handler(opts) {
|
36
|
+
const configPath = await resolveConfigPath(opts.configPath);
|
37
|
+
const config = await loadConfig(configPath);
|
38
|
+
await build({ rootDir: path.dirname(configPath), config, foundryProfile: opts.profile });
|
39
|
+
process.exit(0);
|
40
|
+
}
|
41
|
+
};
|
42
|
+
var build_default = commandModule;
|
43
|
+
|
44
|
+
// src/commands/devnode.ts
|
45
|
+
import { rmSync } from "fs";
|
46
|
+
import { homedir } from "os";
|
47
|
+
import path2 from "path";
|
48
|
+
import { execa as execa2 } from "execa";
|
49
|
+
var commandModule2 = {
|
50
|
+
command: "devnode",
|
51
|
+
describe: "Start a local Ethereum node for development",
|
52
|
+
builder(yargs) {
|
53
|
+
return yargs.options({
|
54
|
+
blocktime: { type: "number", default: 1, decs: "Interval in which new blocks are produced" }
|
55
|
+
});
|
56
|
+
},
|
57
|
+
async handler({ blocktime }) {
|
58
|
+
console.log("Clearing devnode history");
|
59
|
+
const userHomeDir = homedir();
|
60
|
+
rmSync(path2.join(userHomeDir, ".foundry", "anvil", "tmp"), { recursive: true, force: true });
|
61
|
+
const anvilArgs = ["-b", String(blocktime), "--block-base-fee-per-gas", "0"];
|
62
|
+
console.log(`Running: anvil ${anvilArgs.join(" ")}`);
|
63
|
+
const child = execa2("anvil", anvilArgs, {
|
64
|
+
stdio: ["inherit", "inherit", "inherit"]
|
65
|
+
});
|
66
|
+
process.on("SIGINT", () => {
|
67
|
+
console.log("\ngracefully shutting down from SIGINT (Crtl-C)");
|
68
|
+
child.kill();
|
69
|
+
process.exit();
|
70
|
+
});
|
71
|
+
await child;
|
72
|
+
}
|
73
|
+
};
|
74
|
+
var devnode_default = commandModule2;
|
75
|
+
|
76
|
+
// src/commands/hello.ts
|
77
|
+
var commandModule3 = {
|
78
|
+
command: "hello <name>",
|
79
|
+
describe: "Greet <name> with Hello",
|
80
|
+
builder(yargs) {
|
81
|
+
return yargs.options({
|
82
|
+
upper: { type: "boolean" }
|
83
|
+
}).positional("name", { type: "string", demandOption: true });
|
84
|
+
},
|
85
|
+
handler({ name }) {
|
86
|
+
const greeting = `Gm, ${name}!`;
|
87
|
+
console.log(greeting);
|
88
|
+
process.exit(0);
|
89
|
+
}
|
90
|
+
};
|
91
|
+
var hello_default = commandModule3;
|
92
|
+
|
93
|
+
// src/commands/tablegen.ts
|
94
|
+
import { loadConfig as loadConfig2, resolveConfigPath as resolveConfigPath2 } from "@latticexyz/config/node";
|
95
|
+
import { tablegen as tablegen2 } from "@latticexyz/store/codegen";
|
96
|
+
import path3 from "node:path";
|
97
|
+
var commandModule4 = {
|
98
|
+
command: "tablegen",
|
99
|
+
describe: "Autogenerate MUD Store table libraries based on the config file",
|
100
|
+
builder(yargs) {
|
101
|
+
return yargs.options({
|
102
|
+
configPath: { type: "string", desc: "Path to the MUD config file" }
|
103
|
+
});
|
104
|
+
},
|
105
|
+
async handler(opts) {
|
106
|
+
const configPath = await resolveConfigPath2(opts.configPath);
|
107
|
+
const config = await loadConfig2(configPath);
|
108
|
+
await tablegen2({ rootDir: path3.dirname(configPath), config });
|
109
|
+
process.exit(0);
|
110
|
+
}
|
111
|
+
};
|
112
|
+
var tablegen_default = commandModule4;
|
113
|
+
|
114
|
+
// src/runDeploy.ts
|
115
|
+
import path8 from "node:path";
|
116
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
|
117
|
+
|
118
|
+
// package.json
|
119
|
+
var package_default = {
|
120
|
+
name: "@latticexyz/cli",
|
121
|
+
version: "2.2.17",
|
122
|
+
description: "Command line interface for mud",
|
123
|
+
repository: {
|
124
|
+
type: "git",
|
125
|
+
url: "https://github.com/latticexyz/mud.git",
|
126
|
+
directory: "packages/cli"
|
127
|
+
},
|
128
|
+
license: "MIT",
|
129
|
+
type: "module",
|
130
|
+
exports: {
|
131
|
+
".": {
|
132
|
+
import: {
|
133
|
+
import: "./dist/index.js",
|
134
|
+
types: "./dist/index.d.ts"
|
135
|
+
},
|
136
|
+
require: {
|
137
|
+
require: "./dist/index.cjs",
|
138
|
+
types: "./dist/index.d.cts"
|
139
|
+
}
|
140
|
+
}
|
141
|
+
},
|
142
|
+
typesVersions: {
|
143
|
+
"*": {
|
144
|
+
index: [
|
145
|
+
"./dist/index.d.ts"
|
146
|
+
]
|
147
|
+
}
|
148
|
+
},
|
149
|
+
bin: {
|
150
|
+
mud: "./bin/mud.js"
|
151
|
+
},
|
152
|
+
files: [
|
153
|
+
"bin",
|
154
|
+
"dist"
|
155
|
+
],
|
156
|
+
scripts: {
|
157
|
+
build: "pnpm run build:js && pnpm run build:test-tables",
|
158
|
+
"build:js": "tsup",
|
159
|
+
"build:test-tables": "tsx ./scripts/generate-test-tables.ts",
|
160
|
+
clean: "pnpm run clean:js && pnpm run clean:test-tables",
|
161
|
+
"clean:js": "shx rm -rf dist",
|
162
|
+
"clean:test-tables": "shx rm -rf src/**/codegen",
|
163
|
+
dev: "tsup --watch",
|
164
|
+
lint: "eslint . --ext .ts",
|
165
|
+
test: "tsc --noEmit && forge test",
|
166
|
+
"test:ci": "pnpm run test"
|
167
|
+
},
|
168
|
+
dependencies: {
|
169
|
+
"@ark/util": "0.2.2",
|
170
|
+
"@aws-sdk/client-kms": "^3.556.0",
|
171
|
+
"@latticexyz/abi-ts": "workspace:*",
|
172
|
+
"@latticexyz/block-logs-stream": "workspace:*",
|
173
|
+
"@latticexyz/common": "workspace:*",
|
174
|
+
"@latticexyz/config": "workspace:*",
|
175
|
+
"@latticexyz/gas-report": "workspace:*",
|
176
|
+
"@latticexyz/protocol-parser": "workspace:*",
|
177
|
+
"@latticexyz/schema-type": "workspace:*",
|
178
|
+
"@latticexyz/store": "workspace:*",
|
179
|
+
"@latticexyz/store-sync": "workspace:*",
|
180
|
+
"@latticexyz/utils": "workspace:*",
|
181
|
+
"@latticexyz/world": "workspace:*",
|
182
|
+
"@latticexyz/world-module-callwithsignature": "workspace:*",
|
183
|
+
"@latticexyz/world-module-metadata": "workspace:*",
|
184
|
+
abitype: "1.0.6",
|
185
|
+
"asn1.js": "^5.4.1",
|
186
|
+
chalk: "^5.0.1",
|
187
|
+
chokidar: "^3.5.3",
|
188
|
+
debug: "^4.3.4",
|
189
|
+
dotenv: "^16.0.3",
|
190
|
+
execa: "^9.5.2",
|
191
|
+
"find-up": "^6.3.0",
|
192
|
+
glob: "^10.4.2",
|
193
|
+
openurl: "^1.1.1",
|
194
|
+
"p-queue": "^7.4.1",
|
195
|
+
"p-retry": "^5.1.2",
|
196
|
+
path: "^0.12.7",
|
197
|
+
rxjs: "7.5.5",
|
198
|
+
"throttle-debounce": "^5.0.0",
|
199
|
+
toposort: "^2.0.2",
|
200
|
+
viem: "2.21.19",
|
201
|
+
yargs: "^17.7.1",
|
202
|
+
zod: "3.23.8",
|
203
|
+
"zod-validation-error": "^1.3.0"
|
204
|
+
},
|
205
|
+
devDependencies: {
|
206
|
+
"@types/debug": "^4.1.7",
|
207
|
+
"@types/ejs": "^3.1.1",
|
208
|
+
"@types/openurl": "^1.0.0",
|
209
|
+
"@types/throttle-debounce": "^5.0.0",
|
210
|
+
"@types/toposort": "^2.0.6",
|
211
|
+
"@types/yargs": "^17.0.10",
|
212
|
+
"ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0",
|
213
|
+
"forge-std": "https://github.com/foundry-rs/forge-std.git#74cfb77e308dd188d2f58864aaf44963ae6b88b1"
|
214
|
+
}
|
215
|
+
};
|
216
|
+
|
217
|
+
// src/deploy/deploy.ts
|
218
|
+
import { stringToHex as stringToHex2 } from "viem";
|
219
|
+
|
220
|
+
// src/deploy/deployWorld.ts
|
221
|
+
import { waitForTransactionReceipt } from "viem/actions";
|
222
|
+
|
223
|
+
// src/deploy/getWorldFactoryContracts.ts
|
224
|
+
import worldFactoryBuild from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.json" assert { type: "json" };
|
225
|
+
import worldFactoryAbi from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.abi.json" assert { type: "json" };
|
226
|
+
import { encodeDeployData as encodeDeployData2, size as size2 } from "viem";
|
227
|
+
|
228
|
+
// src/deploy/getWorldContracts.ts
|
229
|
+
import accessManagementSystemBuild from "@latticexyz/world/out/AccessManagementSystem.sol/AccessManagementSystem.json" assert { type: "json" };
|
230
|
+
import balanceTransferSystemBuild from "@latticexyz/world/out/BalanceTransferSystem.sol/BalanceTransferSystem.json" assert { type: "json" };
|
231
|
+
import batchCallSystemBuild from "@latticexyz/world/out/BatchCallSystem.sol/BatchCallSystem.json" assert { type: "json" };
|
232
|
+
import registrationSystemBuild from "@latticexyz/world/out/RegistrationSystem.sol/RegistrationSystem.json" assert { type: "json" };
|
233
|
+
import initModuleBuild from "@latticexyz/world/out/InitModule.sol/InitModule.json" assert { type: "json" };
|
234
|
+
import initModuleAbi from "@latticexyz/world/out/InitModule.sol/InitModule.abi.json" assert { type: "json" };
|
235
|
+
import { encodeDeployData, size } from "viem";
|
236
|
+
import { getContractAddress } from "@latticexyz/common/internal";
|
237
|
+
function getWorldContracts(deployerAddress) {
|
238
|
+
const accessManagementSystemDeployedBytecodeSize = size(accessManagementSystemBuild.deployedBytecode.object);
|
239
|
+
const accessManagementSystemBytecode = accessManagementSystemBuild.bytecode.object;
|
240
|
+
const accessManagementSystem = getContractAddress({
|
241
|
+
deployerAddress,
|
242
|
+
bytecode: accessManagementSystemBytecode
|
243
|
+
});
|
244
|
+
const balanceTransferSystemDeployedBytecodeSize = size(balanceTransferSystemBuild.deployedBytecode.object);
|
245
|
+
const balanceTransferSystemBytecode = balanceTransferSystemBuild.bytecode.object;
|
246
|
+
const balanceTransferSystem = getContractAddress({
|
247
|
+
deployerAddress,
|
248
|
+
bytecode: balanceTransferSystemBytecode
|
249
|
+
});
|
250
|
+
const batchCallSystemDeployedBytecodeSize = size(batchCallSystemBuild.deployedBytecode.object);
|
251
|
+
const batchCallSystemBytecode = batchCallSystemBuild.bytecode.object;
|
252
|
+
const batchCallSystem = getContractAddress({ deployerAddress, bytecode: batchCallSystemBytecode });
|
253
|
+
const registrationDeployedBytecodeSize = size(registrationSystemBuild.deployedBytecode.object);
|
254
|
+
const registrationBytecode = registrationSystemBuild.bytecode.object;
|
255
|
+
const registration = getContractAddress({
|
256
|
+
deployerAddress,
|
257
|
+
bytecode: registrationBytecode
|
258
|
+
});
|
259
|
+
const initModuleDeployedBytecodeSize = size(initModuleBuild.deployedBytecode.object);
|
260
|
+
const initModuleBytecode = encodeDeployData({
|
261
|
+
bytecode: initModuleBuild.bytecode.object,
|
262
|
+
abi: initModuleAbi,
|
263
|
+
args: [accessManagementSystem, balanceTransferSystem, batchCallSystem, registration]
|
264
|
+
});
|
265
|
+
const initModule = getContractAddress({ deployerAddress, bytecode: initModuleBytecode });
|
266
|
+
return {
|
267
|
+
AccessManagementSystem: {
|
268
|
+
bytecode: accessManagementSystemBytecode,
|
269
|
+
deployedBytecodeSize: accessManagementSystemDeployedBytecodeSize,
|
270
|
+
debugLabel: "access management system",
|
271
|
+
address: accessManagementSystem
|
272
|
+
},
|
273
|
+
BalanceTransferSystem: {
|
274
|
+
bytecode: balanceTransferSystemBytecode,
|
275
|
+
deployedBytecodeSize: balanceTransferSystemDeployedBytecodeSize,
|
276
|
+
debugLabel: "balance transfer system",
|
277
|
+
address: balanceTransferSystem
|
278
|
+
},
|
279
|
+
BatchCallSystem: {
|
280
|
+
bytecode: batchCallSystemBytecode,
|
281
|
+
deployedBytecodeSize: batchCallSystemDeployedBytecodeSize,
|
282
|
+
debugLabel: "batch call system",
|
283
|
+
address: batchCallSystem
|
284
|
+
},
|
285
|
+
RegistrationSystem: {
|
286
|
+
bytecode: registrationBytecode,
|
287
|
+
deployedBytecodeSize: registrationDeployedBytecodeSize,
|
288
|
+
debugLabel: "core registration system",
|
289
|
+
address: registration
|
290
|
+
},
|
291
|
+
InitModule: {
|
292
|
+
bytecode: initModuleBytecode,
|
293
|
+
deployedBytecodeSize: initModuleDeployedBytecodeSize,
|
294
|
+
debugLabel: "core module",
|
295
|
+
address: initModule
|
296
|
+
}
|
297
|
+
};
|
298
|
+
}
|
299
|
+
|
300
|
+
// src/deploy/getWorldFactoryContracts.ts
|
301
|
+
import { getContractAddress as getContractAddress2 } from "@latticexyz/common/internal";
|
302
|
+
function getWorldFactoryContracts(deployerAddress) {
|
303
|
+
const worldContracts = getWorldContracts(deployerAddress);
|
304
|
+
const worldFactoryDeployedBytecodeSize = size2(worldFactoryBuild.deployedBytecode.object);
|
305
|
+
const worldFactoryBytecode = encodeDeployData2({
|
306
|
+
bytecode: worldFactoryBuild.bytecode.object,
|
307
|
+
abi: worldFactoryAbi,
|
308
|
+
args: [worldContracts.InitModule.address]
|
309
|
+
});
|
310
|
+
const worldFactory = getContractAddress2({ deployerAddress, bytecode: worldFactoryBytecode });
|
311
|
+
return {
|
312
|
+
...worldContracts,
|
313
|
+
WorldFactory: {
|
314
|
+
bytecode: worldFactoryBytecode,
|
315
|
+
deployedBytecodeSize: worldFactoryDeployedBytecodeSize,
|
316
|
+
debugLabel: "world factory",
|
317
|
+
address: worldFactory
|
318
|
+
}
|
319
|
+
};
|
320
|
+
}
|
321
|
+
|
322
|
+
// src/deploy/getWorldProxyFactoryContracts.ts
|
323
|
+
import worldProxyFactoryBuild from "@latticexyz/world/out/WorldProxyFactory.sol/WorldProxyFactory.json" assert { type: "json" };
|
324
|
+
import worldProxyFactoryAbi from "@latticexyz/world/out/WorldProxyFactory.sol/WorldProxyFactory.abi.json" assert { type: "json" };
|
325
|
+
import { encodeDeployData as encodeDeployData3, size as size3 } from "viem";
|
326
|
+
import { getContractAddress as getContractAddress3 } from "@latticexyz/common/internal";
|
327
|
+
function getWorldProxyFactoryContracts(deployerAddress) {
|
328
|
+
const worldContracts = getWorldContracts(deployerAddress);
|
329
|
+
const worldProxyFactoryDeployedBytecodeSize = size3(worldProxyFactoryBuild.deployedBytecode.object);
|
330
|
+
const worldProxyFactoryBytecode = encodeDeployData3({
|
331
|
+
bytecode: worldProxyFactoryBuild.bytecode.object,
|
332
|
+
abi: worldProxyFactoryAbi,
|
333
|
+
args: [worldContracts.InitModule.address]
|
334
|
+
});
|
335
|
+
const worldProxyFactory = getContractAddress3({ deployerAddress, bytecode: worldProxyFactoryBytecode });
|
336
|
+
return {
|
337
|
+
...worldContracts,
|
338
|
+
WorldProxyFactory: {
|
339
|
+
bytecode: worldProxyFactoryBytecode,
|
340
|
+
deployedBytecodeSize: worldProxyFactoryDeployedBytecodeSize,
|
341
|
+
debugLabel: "world proxy factory",
|
342
|
+
address: worldProxyFactory
|
343
|
+
}
|
344
|
+
};
|
345
|
+
}
|
346
|
+
|
347
|
+
// src/deploy/ensureWorldFactory.ts
|
348
|
+
import { ensureContractsDeployed } from "@latticexyz/common/internal";
|
349
|
+
async function ensureWorldFactory(client, deployerAddress, withWorldProxy) {
|
350
|
+
if (withWorldProxy) {
|
351
|
+
const contracts2 = getWorldProxyFactoryContracts(deployerAddress);
|
352
|
+
await ensureContractsDeployed({
|
353
|
+
client,
|
354
|
+
deployerAddress,
|
355
|
+
contracts: Object.values(contracts2)
|
356
|
+
});
|
357
|
+
return contracts2.WorldProxyFactory.address;
|
358
|
+
}
|
359
|
+
const contracts = getWorldFactoryContracts(deployerAddress);
|
360
|
+
await ensureContractsDeployed({
|
361
|
+
client,
|
362
|
+
deployerAddress,
|
363
|
+
contracts: Object.values(contracts)
|
364
|
+
});
|
365
|
+
return contracts.WorldFactory.address;
|
366
|
+
}
|
367
|
+
|
368
|
+
// src/deploy/deployWorld.ts
|
369
|
+
import WorldFactoryAbi from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.abi.json" assert { type: "json" };
|
370
|
+
import { writeContract } from "@latticexyz/common";
|
371
|
+
|
372
|
+
// src/debug.ts
|
373
|
+
import createDebug from "debug";
|
374
|
+
var debug = createDebug("mud:cli");
|
375
|
+
var error = createDebug("mud:cli");
|
376
|
+
debug.log = console.debug.bind(console);
|
377
|
+
error.log = console.error.bind(console);
|
378
|
+
|
379
|
+
// src/deploy/debug.ts
|
380
|
+
var debug2 = debug.extend("deploy");
|
381
|
+
var error2 = debug.extend("deploy");
|
382
|
+
debug2.log = console.debug.bind(console);
|
383
|
+
error2.log = console.error.bind(console);
|
384
|
+
|
385
|
+
// src/deploy/logsToWorldDeploy.ts
|
386
|
+
import { AbiEventSignatureNotFoundError, decodeEventLog, hexToString, parseAbi, getAddress } from "viem";
|
387
|
+
|
388
|
+
// src/deploy/common.ts
|
389
|
+
import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" };
|
390
|
+
import { helloStoreEvent } from "@latticexyz/store";
|
391
|
+
import { helloWorldEvent } from "@latticexyz/world";
|
392
|
+
var worldDeployEvents = [helloStoreEvent, helloWorldEvent];
|
393
|
+
var worldAbi = IBaseWorldAbi;
|
394
|
+
var supportedStoreVersions = ["2.0.0", "2.0.1", "2.0.2"];
|
395
|
+
var supportedWorldVersions = ["2.0.0", "2.0.1", "2.0.2"];
|
396
|
+
|
397
|
+
// src/deploy/logsToWorldDeploy.ts
|
398
|
+
import { isDefined } from "@latticexyz/common/utils";
|
399
|
+
function logsToWorldDeploy(logs) {
|
400
|
+
const deployLogs = logs.map((log) => {
|
401
|
+
try {
|
402
|
+
return {
|
403
|
+
...log,
|
404
|
+
...decodeEventLog({
|
405
|
+
strict: true,
|
406
|
+
abi: parseAbi(worldDeployEvents),
|
407
|
+
topics: log.topics,
|
408
|
+
data: log.data
|
409
|
+
}),
|
410
|
+
// Log addresses are not checksummed, but we want them checksummed before writing to disk.
|
411
|
+
// https://github.com/wevm/viem/issues/2207
|
412
|
+
address: getAddress(log.address)
|
413
|
+
};
|
414
|
+
} catch (error4) {
|
415
|
+
if (error4 instanceof AbiEventSignatureNotFoundError) {
|
416
|
+
return;
|
417
|
+
}
|
418
|
+
throw error4;
|
419
|
+
}
|
420
|
+
}).filter(isDefined);
|
421
|
+
const { address, deployBlock, worldVersion, storeVersion } = deployLogs.reduce(
|
422
|
+
(deploy2, log) => ({
|
423
|
+
...deploy2,
|
424
|
+
address: log.address,
|
425
|
+
deployBlock: log.blockNumber,
|
426
|
+
...log.eventName === "HelloWorld" ? { worldVersion: hexToString(log.args.worldVersion).replace(/\0+$/, "") } : null,
|
427
|
+
...log.eventName === "HelloStore" ? { storeVersion: hexToString(log.args.storeVersion).replace(/\0+$/, "") } : null
|
428
|
+
}),
|
429
|
+
{}
|
430
|
+
);
|
431
|
+
if (address == null) throw new Error("could not find world address");
|
432
|
+
if (deployBlock == null) throw new Error("could not find world deploy block number");
|
433
|
+
if (worldVersion == null) throw new Error("could not find world version");
|
434
|
+
if (storeVersion == null) throw new Error("could not find store version");
|
435
|
+
return { address, deployBlock, worldVersion, storeVersion };
|
436
|
+
}
|
437
|
+
|
438
|
+
// src/deploy/deployWorld.ts
|
439
|
+
async function deployWorld(client, deployerAddress, salt, withWorldProxy) {
|
440
|
+
const worldFactory = await ensureWorldFactory(client, deployerAddress, withWorldProxy);
|
441
|
+
debug2("deploying world");
|
442
|
+
const tx = await writeContract(client, {
|
443
|
+
chain: client.chain ?? null,
|
444
|
+
address: worldFactory,
|
445
|
+
abi: WorldFactoryAbi,
|
446
|
+
functionName: "deployWorld",
|
447
|
+
args: [salt]
|
448
|
+
});
|
449
|
+
debug2("waiting for world deploy");
|
450
|
+
const receipt = await waitForTransactionReceipt(client, { hash: tx });
|
451
|
+
if (receipt.status !== "success") {
|
452
|
+
console.error("world deploy failed", receipt);
|
453
|
+
throw new Error("world deploy failed");
|
454
|
+
}
|
455
|
+
const deploy2 = logsToWorldDeploy(receipt.logs);
|
456
|
+
debug2("deployed world to", deploy2.address, "at block", deploy2.deployBlock);
|
457
|
+
return { ...deploy2, stateBlock: deploy2.deployBlock };
|
458
|
+
}
|
459
|
+
|
460
|
+
// src/deploy/ensureTables.ts
|
461
|
+
import { resourceToLabel, writeContract as writeContract2 } from "@latticexyz/common";
|
462
|
+
import {
|
463
|
+
valueSchemaToFieldLayoutHex,
|
464
|
+
keySchemaToHex,
|
465
|
+
valueSchemaToHex,
|
466
|
+
getSchemaTypes,
|
467
|
+
getValueSchema,
|
468
|
+
getKeySchema
|
469
|
+
} from "@latticexyz/protocol-parser/internal";
|
470
|
+
|
471
|
+
// src/deploy/getTables.ts
|
472
|
+
import { decodeAbiParameters, parseAbiParameters } from "viem";
|
473
|
+
import { hexToResource } from "@latticexyz/common";
|
474
|
+
import { hexToSchema } from "@latticexyz/protocol-parser/internal";
|
475
|
+
import storeConfig from "@latticexyz/store/mud.config";
|
476
|
+
import { getRecords } from "@latticexyz/store-sync";
|
477
|
+
async function getTables({
|
478
|
+
client,
|
479
|
+
worldDeploy,
|
480
|
+
indexerUrl,
|
481
|
+
chainId
|
482
|
+
}) {
|
483
|
+
debug2("looking up tables for", worldDeploy.address);
|
484
|
+
const { records } = await getRecords({
|
485
|
+
table: storeConfig.namespaces.store.tables.Tables,
|
486
|
+
worldAddress: worldDeploy.address,
|
487
|
+
indexerUrl,
|
488
|
+
chainId,
|
489
|
+
client,
|
490
|
+
fromBlock: worldDeploy.deployBlock,
|
491
|
+
toBlock: worldDeploy.stateBlock
|
492
|
+
});
|
493
|
+
const tables = records.map((record) => {
|
494
|
+
const { type, namespace, name } = hexToResource(record.tableId);
|
495
|
+
const solidityKeySchema = hexToSchema(record.keySchema);
|
496
|
+
const solidityValueSchema = hexToSchema(record.valueSchema);
|
497
|
+
const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), record.abiEncodedKeyNames)[0];
|
498
|
+
const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), record.abiEncodedFieldNames)[0];
|
499
|
+
const valueAbiTypes = [...solidityValueSchema.staticFields, ...solidityValueSchema.dynamicFields];
|
500
|
+
const keySchema = Object.fromEntries(
|
501
|
+
solidityKeySchema.staticFields.map((abiType, i) => [keyNames[i], { type: abiType, internalType: abiType }])
|
502
|
+
);
|
503
|
+
const valueSchema = Object.fromEntries(
|
504
|
+
valueAbiTypes.map((abiType, i) => [fieldNames[i], { type: abiType, internalType: abiType }])
|
505
|
+
);
|
506
|
+
return {
|
507
|
+
type,
|
508
|
+
namespace,
|
509
|
+
name,
|
510
|
+
tableId: record.tableId,
|
511
|
+
schema: { ...keySchema, ...valueSchema },
|
512
|
+
key: Object.keys(keySchema),
|
513
|
+
keySchema,
|
514
|
+
keySchemaHex: record.keySchema,
|
515
|
+
valueSchema,
|
516
|
+
valueSchemaHex: record.valueSchema
|
517
|
+
};
|
518
|
+
});
|
519
|
+
debug2("found", tables.length, "tables for", worldDeploy.address);
|
520
|
+
return tables;
|
521
|
+
}
|
522
|
+
|
523
|
+
// src/deploy/ensureTables.ts
|
524
|
+
import pRetry from "p-retry";
|
525
|
+
import { isDefined as isDefined2 } from "@latticexyz/common/utils";
|
526
|
+
async function ensureTables({
|
527
|
+
client,
|
528
|
+
worldDeploy,
|
529
|
+
tables,
|
530
|
+
indexerUrl,
|
531
|
+
chainId
|
532
|
+
}) {
|
533
|
+
const configTables = new Map(
|
534
|
+
tables.map((table) => {
|
535
|
+
const keySchema = getSchemaTypes(getKeySchema(table));
|
536
|
+
const valueSchema = getSchemaTypes(getValueSchema(table));
|
537
|
+
const keySchemaHex = keySchemaToHex(keySchema);
|
538
|
+
const valueSchemaHex = valueSchemaToHex(valueSchema);
|
539
|
+
return [
|
540
|
+
table.tableId,
|
541
|
+
{
|
542
|
+
...table,
|
543
|
+
keySchema,
|
544
|
+
keySchemaHex,
|
545
|
+
valueSchema,
|
546
|
+
valueSchemaHex
|
547
|
+
}
|
548
|
+
];
|
549
|
+
})
|
550
|
+
);
|
551
|
+
const worldTables = await getTables({ client, worldDeploy, indexerUrl, chainId });
|
552
|
+
const existingTables = worldTables.filter(({ tableId }) => configTables.has(tableId));
|
553
|
+
if (existingTables.length) {
|
554
|
+
debug2("existing tables:", existingTables.map(resourceToLabel).join(", "));
|
555
|
+
const schemaErrors = existingTables.map((table) => {
|
556
|
+
const configTable = configTables.get(table.tableId);
|
557
|
+
if (table.keySchemaHex !== configTable.keySchemaHex || table.valueSchemaHex !== configTable.valueSchemaHex) {
|
558
|
+
return [
|
559
|
+
`"${resourceToLabel(table)}" table:`,
|
560
|
+
` Registered schema: ${JSON.stringify({ schema: getSchemaTypes(table.schema), key: table.key })}`,
|
561
|
+
` Config schema: ${JSON.stringify({ schema: getSchemaTypes(configTable.schema), key: configTable.key })}`
|
562
|
+
].join("\n");
|
563
|
+
}
|
564
|
+
}).filter(isDefined2);
|
565
|
+
if (schemaErrors.length) {
|
566
|
+
throw new Error(
|
567
|
+
[
|
568
|
+
"Table schemas are immutable, but found registered tables with a different schema than what you have configured.",
|
569
|
+
...schemaErrors,
|
570
|
+
"You can either update your config with the registered schema or change the table name to register a new table."
|
571
|
+
].join("\n\n") + "\n"
|
572
|
+
);
|
573
|
+
}
|
574
|
+
}
|
575
|
+
const existingTableIds = new Set(existingTables.map(({ tableId }) => tableId));
|
576
|
+
const missingTables = tables.filter((table) => !existingTableIds.has(table.tableId));
|
577
|
+
if (missingTables.length) {
|
578
|
+
debug2("registering tables:", missingTables.map(resourceToLabel).join(", "));
|
579
|
+
return await Promise.all(
|
580
|
+
missingTables.map((table) => {
|
581
|
+
const keySchema = getSchemaTypes(getKeySchema(table));
|
582
|
+
const valueSchema = getSchemaTypes(getValueSchema(table));
|
583
|
+
return pRetry(
|
584
|
+
() => writeContract2(client, {
|
585
|
+
chain: client.chain ?? null,
|
586
|
+
address: worldDeploy.address,
|
587
|
+
abi: worldAbi,
|
588
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
589
|
+
functionName: "registerTable",
|
590
|
+
args: [
|
591
|
+
table.tableId,
|
592
|
+
valueSchemaToFieldLayoutHex(valueSchema),
|
593
|
+
keySchemaToHex(keySchema),
|
594
|
+
valueSchemaToHex(valueSchema),
|
595
|
+
Object.keys(keySchema),
|
596
|
+
Object.keys(valueSchema)
|
597
|
+
]
|
598
|
+
}),
|
599
|
+
{
|
600
|
+
retries: 3,
|
601
|
+
onFailedAttempt: () => debug2(`failed to register table ${resourceToLabel(table)}, retrying...`)
|
602
|
+
}
|
603
|
+
);
|
604
|
+
})
|
605
|
+
);
|
606
|
+
}
|
607
|
+
return [];
|
608
|
+
}
|
609
|
+
|
610
|
+
// src/deploy/ensureSystems.ts
|
611
|
+
import { getAddress as getAddress3 } from "viem";
|
612
|
+
import { writeContract as writeContract3, resourceToLabel as resourceToLabel3 } from "@latticexyz/common";
|
613
|
+
|
614
|
+
// src/deploy/getSystems.ts
|
615
|
+
import { hexToResource as hexToResource2, resourceToLabel as resourceToLabel2 } from "@latticexyz/common";
|
616
|
+
import { getFunctions } from "@latticexyz/store-sync/world";
|
617
|
+
|
618
|
+
// src/deploy/getResourceIds.ts
|
619
|
+
import storeConfig2 from "@latticexyz/store/mud.config";
|
620
|
+
import { getRecords as getRecords2 } from "@latticexyz/store-sync";
|
621
|
+
async function getResourceIds({
|
622
|
+
client,
|
623
|
+
worldDeploy,
|
624
|
+
indexerUrl,
|
625
|
+
chainId
|
626
|
+
}) {
|
627
|
+
debug2("looking up resource IDs for", worldDeploy.address);
|
628
|
+
const { records } = await getRecords2({
|
629
|
+
table: storeConfig2.namespaces.store.tables.ResourceIds,
|
630
|
+
worldAddress: worldDeploy.address,
|
631
|
+
indexerUrl,
|
632
|
+
chainId,
|
633
|
+
client,
|
634
|
+
fromBlock: worldDeploy.deployBlock,
|
635
|
+
toBlock: worldDeploy.stateBlock
|
636
|
+
});
|
637
|
+
const resourceIds = records.map((record) => record.resourceId);
|
638
|
+
debug2("found", resourceIds.length, "resource IDs for", worldDeploy.address);
|
639
|
+
return resourceIds;
|
640
|
+
}
|
641
|
+
|
642
|
+
// src/deploy/getTableValue.ts
|
643
|
+
import {
|
644
|
+
decodeValueArgs,
|
645
|
+
encodeKey,
|
646
|
+
getKeySchema as getKeySchema2,
|
647
|
+
getSchemaTypes as getSchemaTypes2,
|
648
|
+
getValueSchema as getValueSchema2
|
649
|
+
} from "@latticexyz/protocol-parser/internal";
|
650
|
+
import { readContract } from "viem/actions";
|
651
|
+
async function getTableValue({
|
652
|
+
client,
|
653
|
+
worldDeploy,
|
654
|
+
table,
|
655
|
+
key
|
656
|
+
}) {
|
657
|
+
const [staticData, encodedLengths, dynamicData] = await readContract(client, {
|
658
|
+
blockNumber: worldDeploy.stateBlock,
|
659
|
+
address: worldDeploy.address,
|
660
|
+
abi: worldAbi,
|
661
|
+
functionName: "getRecord",
|
662
|
+
args: [table.tableId, encodeKey(getSchemaTypes2(getKeySchema2(table)), key)]
|
663
|
+
// TODO: remove cast once https://github.com/wevm/viem/issues/2125 is resolved
|
664
|
+
});
|
665
|
+
return decodeValueArgs(getSchemaTypes2(getValueSchema2(table)), {
|
666
|
+
staticData,
|
667
|
+
encodedLengths,
|
668
|
+
dynamicData
|
669
|
+
});
|
670
|
+
}
|
671
|
+
|
672
|
+
// src/deploy/getResourceAccess.ts
|
673
|
+
import { getAddress as getAddress2 } from "viem";
|
674
|
+
import worldConfig from "@latticexyz/world/mud.config";
|
675
|
+
import { getRecords as getRecords3 } from "@latticexyz/store-sync";
|
676
|
+
async function getResourceAccess({
|
677
|
+
client,
|
678
|
+
worldDeploy,
|
679
|
+
indexerUrl,
|
680
|
+
chainId
|
681
|
+
}) {
|
682
|
+
debug2("looking up resource access for", worldDeploy.address);
|
683
|
+
const { records } = await getRecords3({
|
684
|
+
table: worldConfig.namespaces.world.tables.ResourceAccess,
|
685
|
+
worldAddress: worldDeploy.address,
|
686
|
+
indexerUrl,
|
687
|
+
chainId,
|
688
|
+
client,
|
689
|
+
fromBlock: worldDeploy.deployBlock,
|
690
|
+
toBlock: worldDeploy.stateBlock
|
691
|
+
});
|
692
|
+
const access = records.filter((record) => record.access).map((record) => ({
|
693
|
+
resourceId: record.resourceId,
|
694
|
+
address: getAddress2(record.caller)
|
695
|
+
}));
|
696
|
+
debug2("found", access.length, "resource<>address access pairs");
|
697
|
+
return access;
|
698
|
+
}
|
699
|
+
|
700
|
+
// src/deploy/getSystems.ts
|
701
|
+
import worldConfig2 from "@latticexyz/world/mud.config";
|
702
|
+
async function getSystems({
|
703
|
+
client,
|
704
|
+
worldDeploy,
|
705
|
+
indexerUrl,
|
706
|
+
chainId
|
707
|
+
}) {
|
708
|
+
const [resourceIds, functions, resourceAccess] = await Promise.all([
|
709
|
+
getResourceIds({ client, worldDeploy, indexerUrl, chainId }),
|
710
|
+
getFunctions({
|
711
|
+
client,
|
712
|
+
worldAddress: worldDeploy.address,
|
713
|
+
fromBlock: worldDeploy.deployBlock,
|
714
|
+
toBlock: worldDeploy.stateBlock,
|
715
|
+
indexerUrl,
|
716
|
+
chainId
|
717
|
+
}),
|
718
|
+
getResourceAccess({ client, worldDeploy, indexerUrl, chainId })
|
719
|
+
]);
|
720
|
+
const systems = resourceIds.map(hexToResource2).filter((resource) => resource.type === "system");
|
721
|
+
debug2("looking up systems:", systems.map(resourceToLabel2).join(", "));
|
722
|
+
return await Promise.all(
|
723
|
+
systems.map(async (system) => {
|
724
|
+
const { system: address, publicAccess } = await getTableValue({
|
725
|
+
client,
|
726
|
+
worldDeploy,
|
727
|
+
table: worldConfig2.namespaces.world.tables.Systems,
|
728
|
+
key: { systemId: system.resourceId }
|
729
|
+
});
|
730
|
+
const worldFunctions = functions.filter((func) => func.systemId === system.resourceId);
|
731
|
+
return {
|
732
|
+
address,
|
733
|
+
namespace: system.namespace,
|
734
|
+
name: system.name,
|
735
|
+
systemId: system.resourceId,
|
736
|
+
allowAll: publicAccess,
|
737
|
+
allowedAddresses: resourceAccess.filter(({ resourceId }) => resourceId === system.resourceId).map(({ address: address2 }) => address2),
|
738
|
+
worldFunctions
|
739
|
+
};
|
740
|
+
})
|
741
|
+
);
|
742
|
+
}
|
743
|
+
|
744
|
+
// src/deploy/ensureSystems.ts
|
745
|
+
import pRetry2 from "p-retry";
|
746
|
+
import { ensureContractsDeployed as ensureContractsDeployed2 } from "@latticexyz/common/internal";
|
747
|
+
async function ensureSystems({
|
748
|
+
client,
|
749
|
+
deployerAddress,
|
750
|
+
libraryMap,
|
751
|
+
worldDeploy,
|
752
|
+
systems,
|
753
|
+
indexerUrl,
|
754
|
+
chainId
|
755
|
+
}) {
|
756
|
+
const [worldSystems, worldAccess] = await Promise.all([
|
757
|
+
getSystems({ client, worldDeploy, indexerUrl, chainId }),
|
758
|
+
getResourceAccess({ client, worldDeploy, indexerUrl, chainId })
|
759
|
+
]);
|
760
|
+
const existingSystems = systems.filter(
|
761
|
+
(system) => worldSystems.some(
|
762
|
+
(worldSystem) => worldSystem.systemId === system.systemId && getAddress3(worldSystem.address) === getAddress3(system.prepareDeploy(deployerAddress, libraryMap).address)
|
763
|
+
)
|
764
|
+
);
|
765
|
+
if (existingSystems.length) {
|
766
|
+
debug2("existing systems:", existingSystems.map(resourceToLabel3).join(", "));
|
767
|
+
}
|
768
|
+
const existingSystemIds = existingSystems.map((system) => system.systemId);
|
769
|
+
const missingSystems = systems.filter((system) => !existingSystemIds.includes(system.systemId));
|
770
|
+
if (!missingSystems.length) return [];
|
771
|
+
const systemsToUpgrade = missingSystems.filter(
|
772
|
+
(system) => worldSystems.some(
|
773
|
+
(worldSystem) => worldSystem.systemId === system.systemId && getAddress3(worldSystem.address) !== getAddress3(system.prepareDeploy(deployerAddress, libraryMap).address)
|
774
|
+
)
|
775
|
+
);
|
776
|
+
if (systemsToUpgrade.length) {
|
777
|
+
debug2("upgrading systems:", systemsToUpgrade.map(resourceToLabel3).join(", "));
|
778
|
+
}
|
779
|
+
const systemsToAdd = missingSystems.filter(
|
780
|
+
(system) => !worldSystems.some((worldSystem) => worldSystem.systemId === system.systemId)
|
781
|
+
);
|
782
|
+
if (systemsToAdd.length) {
|
783
|
+
debug2("registering new systems:", systemsToAdd.map(resourceToLabel3).join(", "));
|
784
|
+
}
|
785
|
+
await ensureContractsDeployed2({
|
786
|
+
client,
|
787
|
+
deployerAddress,
|
788
|
+
contracts: missingSystems.map((system) => ({
|
789
|
+
bytecode: system.prepareDeploy(deployerAddress, libraryMap).bytecode,
|
790
|
+
deployedBytecodeSize: system.deployedBytecodeSize,
|
791
|
+
debugLabel: `${resourceToLabel3(system)} system`
|
792
|
+
}))
|
793
|
+
});
|
794
|
+
const registerTxs = await Promise.all(
|
795
|
+
missingSystems.map(
|
796
|
+
(system) => pRetry2(
|
797
|
+
() => writeContract3(client, {
|
798
|
+
chain: client.chain ?? null,
|
799
|
+
address: worldDeploy.address,
|
800
|
+
abi: worldAbi,
|
801
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
802
|
+
functionName: "registerSystem",
|
803
|
+
args: [system.systemId, system.prepareDeploy(deployerAddress, libraryMap).address, system.allowAll]
|
804
|
+
}),
|
805
|
+
{
|
806
|
+
retries: 3,
|
807
|
+
onFailedAttempt: () => debug2(`failed to register system ${resourceToLabel3(system)}, retrying...`)
|
808
|
+
}
|
809
|
+
)
|
810
|
+
)
|
811
|
+
);
|
812
|
+
const systemIds = systems.map((system) => system.systemId);
|
813
|
+
const currentAccess = worldAccess.filter(({ resourceId }) => systemIds.includes(resourceId));
|
814
|
+
const desiredAccess = [
|
815
|
+
...systems.flatMap(
|
816
|
+
(system) => system.allowedAddresses.map((address) => ({ resourceId: system.systemId, address }))
|
817
|
+
),
|
818
|
+
...systems.flatMap(
|
819
|
+
(system) => system.allowedSystemIds.map((systemId) => ({
|
820
|
+
resourceId: system.systemId,
|
821
|
+
address: worldSystems.find((s) => s.systemId === systemId)?.address ?? systems.find((s) => s.systemId === systemId)?.prepareDeploy(deployerAddress, libraryMap).address
|
822
|
+
})).filter((access) => access.address != null)
|
823
|
+
)
|
824
|
+
];
|
825
|
+
const accessToAdd = desiredAccess.filter(
|
826
|
+
(access) => !currentAccess.some(
|
827
|
+
({ resourceId, address }) => resourceId === access.resourceId && getAddress3(address) === getAddress3(access.address)
|
828
|
+
)
|
829
|
+
);
|
830
|
+
const accessToRemove = currentAccess.filter(
|
831
|
+
(access) => !desiredAccess.some(
|
832
|
+
({ resourceId, address }) => resourceId === access.resourceId && getAddress3(address) === getAddress3(access.address)
|
833
|
+
)
|
834
|
+
);
|
835
|
+
if (accessToRemove.length) {
|
836
|
+
debug2("revoking", accessToRemove.length, "access grants");
|
837
|
+
}
|
838
|
+
if (accessToAdd.length) {
|
839
|
+
debug2("adding", accessToAdd.length, "access grants");
|
840
|
+
}
|
841
|
+
const accessTxs = await Promise.all([
|
842
|
+
...accessToRemove.map(
|
843
|
+
(access) => pRetry2(
|
844
|
+
() => writeContract3(client, {
|
845
|
+
chain: client.chain ?? null,
|
846
|
+
address: worldDeploy.address,
|
847
|
+
abi: worldAbi,
|
848
|
+
functionName: "revokeAccess",
|
849
|
+
args: [access.resourceId, access.address]
|
850
|
+
}),
|
851
|
+
{
|
852
|
+
retries: 3,
|
853
|
+
onFailedAttempt: () => debug2("failed to revoke access, retrying...")
|
854
|
+
}
|
855
|
+
)
|
856
|
+
),
|
857
|
+
...accessToAdd.map(
|
858
|
+
(access) => pRetry2(
|
859
|
+
() => writeContract3(client, {
|
860
|
+
chain: client.chain ?? null,
|
861
|
+
address: worldDeploy.address,
|
862
|
+
abi: worldAbi,
|
863
|
+
functionName: "grantAccess",
|
864
|
+
args: [access.resourceId, access.address]
|
865
|
+
}),
|
866
|
+
{
|
867
|
+
retries: 3,
|
868
|
+
onFailedAttempt: () => debug2("failed to grant access, retrying...")
|
869
|
+
}
|
870
|
+
)
|
871
|
+
)
|
872
|
+
]);
|
873
|
+
return [...registerTxs, ...accessTxs];
|
874
|
+
}
|
875
|
+
|
876
|
+
// src/deploy/getWorldDeploy.ts
|
877
|
+
import { getAddress as getAddress4, parseAbi as parseAbi2 } from "viem";
|
878
|
+
import { getBlock } from "viem/actions";
|
879
|
+
import { fetchBlockLogs } from "@latticexyz/block-logs-stream";
|
880
|
+
var deploys = /* @__PURE__ */ new Map();
|
881
|
+
async function getWorldDeploy(client, worldAddress, deployBlock) {
|
882
|
+
const address = getAddress4(worldAddress);
|
883
|
+
const stateBlock = await getBlock(client, { blockTag: "latest" });
|
884
|
+
let deploy2 = deploys.get(address);
|
885
|
+
if (deploy2 != null) {
|
886
|
+
return {
|
887
|
+
...deploy2,
|
888
|
+
stateBlock: stateBlock.number
|
889
|
+
};
|
890
|
+
}
|
891
|
+
debug2("looking up world deploy for", address);
|
892
|
+
const [fromBlock, toBlock] = deployBlock ? [{ number: deployBlock }, { number: deployBlock }] : [await getBlock(client, { blockTag: "earliest" }), stateBlock];
|
893
|
+
const blockLogs = await fetchBlockLogs({
|
894
|
+
publicClient: client,
|
895
|
+
address,
|
896
|
+
events: parseAbi2(worldDeployEvents),
|
897
|
+
fromBlock: fromBlock.number,
|
898
|
+
toBlock: toBlock.number,
|
899
|
+
maxBlockRange: 100000n
|
900
|
+
});
|
901
|
+
if (blockLogs.length === 0) {
|
902
|
+
throw new Error("could not find `HelloWorld` or `HelloStore` event");
|
903
|
+
}
|
904
|
+
deploy2 = {
|
905
|
+
...logsToWorldDeploy(blockLogs.flatMap((block) => block.logs)),
|
906
|
+
stateBlock: stateBlock.number
|
907
|
+
};
|
908
|
+
deploys.set(address, deploy2);
|
909
|
+
debug2("found world deploy for", address, "at block", deploy2.deployBlock);
|
910
|
+
return deploy2;
|
911
|
+
}
|
912
|
+
|
913
|
+
// src/deploy/ensureFunctions.ts
|
914
|
+
import { hexToResource as hexToResource3, writeContract as writeContract4 } from "@latticexyz/common";
|
915
|
+
import { getFunctions as getFunctions2 } from "@latticexyz/store-sync/world";
|
916
|
+
import pRetry3 from "p-retry";
|
917
|
+
async function ensureFunctions({
|
918
|
+
client,
|
919
|
+
worldDeploy,
|
920
|
+
functions,
|
921
|
+
indexerUrl,
|
922
|
+
chainId
|
923
|
+
}) {
|
924
|
+
const worldFunctions = await getFunctions2({
|
925
|
+
client,
|
926
|
+
worldAddress: worldDeploy.address,
|
927
|
+
fromBlock: worldDeploy.deployBlock,
|
928
|
+
toBlock: worldDeploy.stateBlock,
|
929
|
+
indexerUrl,
|
930
|
+
chainId
|
931
|
+
});
|
932
|
+
const worldSelectorToFunction = Object.fromEntries(worldFunctions.map((func) => [func.selector, func]));
|
933
|
+
const toSkip = functions.filter((func) => worldSelectorToFunction[func.selector]);
|
934
|
+
const toAdd = functions.filter((func) => !toSkip.includes(func));
|
935
|
+
if (toSkip.length) {
|
936
|
+
debug2("functions already registered:", toSkip.map((func) => func.signature).join(", "));
|
937
|
+
const wrongSystem = toSkip.filter((func) => func.systemId !== worldSelectorToFunction[func.selector]?.systemId);
|
938
|
+
if (wrongSystem.length) {
|
939
|
+
console.warn(
|
940
|
+
"found",
|
941
|
+
wrongSystem.length,
|
942
|
+
"functions already registered but pointing at a different system ID:",
|
943
|
+
wrongSystem.map((func) => func.signature).join(", ")
|
944
|
+
);
|
945
|
+
}
|
946
|
+
}
|
947
|
+
if (!toAdd.length) return [];
|
948
|
+
debug2("registering functions:", toAdd.map((func) => func.signature).join(", "));
|
949
|
+
return Promise.all(
|
950
|
+
toAdd.map((func) => {
|
951
|
+
const { namespace } = hexToResource3(func.systemId);
|
952
|
+
const params = namespace === "" ? {
|
953
|
+
functionName: "registerRootFunctionSelector",
|
954
|
+
args: [
|
955
|
+
func.systemId,
|
956
|
+
// use system function signature as world signature
|
957
|
+
func.systemFunctionSignature,
|
958
|
+
func.systemFunctionSignature
|
959
|
+
]
|
960
|
+
} : {
|
961
|
+
functionName: "registerFunctionSelector",
|
962
|
+
args: [func.systemId, func.systemFunctionSignature]
|
963
|
+
};
|
964
|
+
return pRetry3(
|
965
|
+
() => writeContract4(client, {
|
966
|
+
chain: client.chain ?? null,
|
967
|
+
address: worldDeploy.address,
|
968
|
+
abi: worldAbi,
|
969
|
+
...params
|
970
|
+
}),
|
971
|
+
{
|
972
|
+
retries: 3,
|
973
|
+
onFailedAttempt: () => debug2(`failed to register function ${func.signature}, retrying...`)
|
974
|
+
}
|
975
|
+
);
|
976
|
+
})
|
977
|
+
);
|
978
|
+
}
|
979
|
+
|
980
|
+
// src/deploy/ensureModules.ts
|
981
|
+
import { BaseError } from "viem";
|
982
|
+
import { writeContract as writeContract5 } from "@latticexyz/common";
|
983
|
+
import { isDefined as isDefined3 } from "@latticexyz/common/utils";
|
984
|
+
import pRetry4 from "p-retry";
|
985
|
+
import { ensureContractsDeployed as ensureContractsDeployed3 } from "@latticexyz/common/internal";
|
986
|
+
async function ensureModules({
|
987
|
+
client,
|
988
|
+
deployerAddress,
|
989
|
+
libraryMap,
|
990
|
+
worldDeploy,
|
991
|
+
modules
|
992
|
+
}) {
|
993
|
+
if (!modules.length) return [];
|
994
|
+
await ensureContractsDeployed3({
|
995
|
+
client,
|
996
|
+
deployerAddress,
|
997
|
+
contracts: modules.map((mod) => ({
|
998
|
+
bytecode: mod.prepareDeploy(deployerAddress, libraryMap).bytecode,
|
999
|
+
deployedBytecodeSize: mod.deployedBytecodeSize,
|
1000
|
+
debugLabel: `${mod.name} module`
|
1001
|
+
}))
|
1002
|
+
});
|
1003
|
+
debug2("installing modules:", modules.map((mod) => mod.name).join(", "));
|
1004
|
+
return (await Promise.all(
|
1005
|
+
modules.map(
|
1006
|
+
(mod) => pRetry4(
|
1007
|
+
async () => {
|
1008
|
+
try {
|
1009
|
+
const abi = [...worldAbi, ...mod.abi];
|
1010
|
+
const moduleAddress = mod.prepareDeploy(deployerAddress, libraryMap).address;
|
1011
|
+
const params = mod.installAsRoot ? { functionName: "installRootModule", args: [moduleAddress, mod.installData] } : { functionName: "installModule", args: [moduleAddress, mod.installData] };
|
1012
|
+
return await writeContract5(client, {
|
1013
|
+
chain: client.chain ?? null,
|
1014
|
+
address: worldDeploy.address,
|
1015
|
+
abi,
|
1016
|
+
...params
|
1017
|
+
});
|
1018
|
+
} catch (error4) {
|
1019
|
+
if (error4 instanceof BaseError && error4.message.includes("Module_AlreadyInstalled")) {
|
1020
|
+
debug2(`module ${mod.name} already installed`);
|
1021
|
+
return;
|
1022
|
+
}
|
1023
|
+
if (mod.optional) {
|
1024
|
+
debug2(`optional module ${mod.name} install failed, skipping`);
|
1025
|
+
debug2(error4);
|
1026
|
+
return;
|
1027
|
+
}
|
1028
|
+
throw error4;
|
1029
|
+
}
|
1030
|
+
},
|
1031
|
+
{
|
1032
|
+
retries: 3,
|
1033
|
+
onFailedAttempt: () => debug2(`failed to install module ${mod.name}, retrying...`)
|
1034
|
+
}
|
1035
|
+
)
|
1036
|
+
)
|
1037
|
+
)).filter(isDefined3);
|
1038
|
+
}
|
1039
|
+
|
1040
|
+
// src/deploy/ensureNamespaceOwner.ts
|
1041
|
+
import { getAddress as getAddress5 } from "viem";
|
1042
|
+
import { hexToResource as hexToResource4, resourceToHex, writeContract as writeContract6 } from "@latticexyz/common";
|
1043
|
+
import worldConfig3 from "@latticexyz/world/mud.config";
|
1044
|
+
async function ensureNamespaceOwner({
|
1045
|
+
client,
|
1046
|
+
worldDeploy,
|
1047
|
+
resourceIds,
|
1048
|
+
indexerUrl,
|
1049
|
+
chainId
|
1050
|
+
}) {
|
1051
|
+
const desiredNamespaces = Array.from(new Set(resourceIds.map((resourceId) => hexToResource4(resourceId).namespace)));
|
1052
|
+
const existingResourceIds = await getResourceIds({ client, worldDeploy, indexerUrl, chainId });
|
1053
|
+
const existingNamespaces = new Set(existingResourceIds.map((resourceId) => hexToResource4(resourceId).namespace));
|
1054
|
+
if (existingNamespaces.size) {
|
1055
|
+
debug2(
|
1056
|
+
"found",
|
1057
|
+
existingNamespaces.size,
|
1058
|
+
"existing namespaces:",
|
1059
|
+
Array.from(existingNamespaces).map((namespace) => namespace === "" ? "<root>" : namespace).join(", ")
|
1060
|
+
);
|
1061
|
+
}
|
1062
|
+
const existingDesiredNamespaces = desiredNamespaces.filter((namespace) => existingNamespaces.has(namespace));
|
1063
|
+
const namespaceOwners = await Promise.all(
|
1064
|
+
existingDesiredNamespaces.map(async (namespace) => {
|
1065
|
+
const { owner } = await getTableValue({
|
1066
|
+
client,
|
1067
|
+
worldDeploy,
|
1068
|
+
table: worldConfig3.namespaces.world.tables.NamespaceOwner,
|
1069
|
+
key: { namespaceId: resourceToHex({ type: "namespace", namespace, name: "" }) }
|
1070
|
+
});
|
1071
|
+
return [namespace, owner];
|
1072
|
+
})
|
1073
|
+
);
|
1074
|
+
const unauthorizedNamespaces = namespaceOwners.filter(([, owner]) => getAddress5(owner) !== getAddress5(client.account.address)).map(([namespace]) => namespace);
|
1075
|
+
if (unauthorizedNamespaces.length) {
|
1076
|
+
throw new Error(`You are attempting to deploy to namespaces you do not own: ${unauthorizedNamespaces.join(", ")}`);
|
1077
|
+
}
|
1078
|
+
const missingNamespaces = desiredNamespaces.filter((namespace) => !existingNamespaces.has(namespace));
|
1079
|
+
if (missingNamespaces.length > 0) {
|
1080
|
+
debug2("registering namespaces:", Array.from(missingNamespaces).join(", "));
|
1081
|
+
}
|
1082
|
+
const registrationTxs = Promise.all(
|
1083
|
+
missingNamespaces.map(
|
1084
|
+
(namespace) => writeContract6(client, {
|
1085
|
+
chain: client.chain ?? null,
|
1086
|
+
address: worldDeploy.address,
|
1087
|
+
abi: worldAbi,
|
1088
|
+
functionName: "registerNamespace",
|
1089
|
+
args: [resourceToHex({ namespace, type: "namespace", name: "" })]
|
1090
|
+
})
|
1091
|
+
)
|
1092
|
+
);
|
1093
|
+
return registrationTxs;
|
1094
|
+
}
|
1095
|
+
|
1096
|
+
// src/deploy/deploy.ts
|
1097
|
+
import { resourceToHex as resourceToHex3, resourceToLabel as resourceToLabel4 } from "@latticexyz/common";
|
1098
|
+
import { randomBytes } from "crypto";
|
1099
|
+
|
1100
|
+
// src/deploy/ensureResourceTags.ts
|
1101
|
+
import { stringToHex, BaseError as BaseError2, concatHex } from "viem";
|
1102
|
+
import { hexToResource as hexToResource5, writeContract as writeContract7 } from "@latticexyz/common";
|
1103
|
+
import { identity, isDefined as isDefined4 } from "@latticexyz/common/utils";
|
1104
|
+
import metadataConfig from "@latticexyz/world-module-metadata/mud.config";
|
1105
|
+
import metadataAbi from "@latticexyz/world-module-metadata/out/IMetadataSystem.sol/IMetadataSystem.abi.json" assert { type: "json" };
|
1106
|
+
import metadataModule from "@latticexyz/world-module-metadata/out/MetadataModule.sol/MetadataModule.json" assert { type: "json" };
|
1107
|
+
|
1108
|
+
// src/utils/getContractArtifact.ts
|
1109
|
+
import { isHex, size as size4 } from "viem";
|
1110
|
+
|
1111
|
+
// src/utils/findPlaceholders.ts
|
1112
|
+
function findPlaceholders(linkReferences) {
|
1113
|
+
return Object.entries(linkReferences).flatMap(
|
1114
|
+
([path16, contracts]) => Object.entries(contracts).flatMap(
|
1115
|
+
([contractName, locations]) => locations.map(
|
1116
|
+
(location) => ({
|
1117
|
+
path: path16,
|
1118
|
+
name: contractName,
|
1119
|
+
start: location.start,
|
1120
|
+
length: location.length
|
1121
|
+
})
|
1122
|
+
)
|
1123
|
+
)
|
1124
|
+
);
|
1125
|
+
}
|
1126
|
+
|
1127
|
+
// src/utils/getContractArtifact.ts
|
1128
|
+
import { z } from "zod";
|
1129
|
+
import { Abi as abiSchema } from "abitype/zod";
|
1130
|
+
function isBytecode(value) {
|
1131
|
+
return isHex(value, { strict: false });
|
1132
|
+
}
|
1133
|
+
var bytecodeSchema = z.object({
|
1134
|
+
object: z.string().refine(isBytecode),
|
1135
|
+
linkReferences: z.record(
|
1136
|
+
z.record(
|
1137
|
+
z.array(
|
1138
|
+
z.object({
|
1139
|
+
start: z.number(),
|
1140
|
+
length: z.number()
|
1141
|
+
})
|
1142
|
+
)
|
1143
|
+
)
|
1144
|
+
).optional()
|
1145
|
+
});
|
1146
|
+
var artifactSchema = z.object({
|
1147
|
+
bytecode: bytecodeSchema,
|
1148
|
+
deployedBytecode: bytecodeSchema,
|
1149
|
+
abi: abiSchema
|
1150
|
+
});
|
1151
|
+
function getContractArtifact(artifactJson) {
|
1152
|
+
const artifact = artifactSchema.parse(artifactJson);
|
1153
|
+
const placeholders = findPlaceholders(artifact.bytecode.linkReferences ?? {});
|
1154
|
+
return {
|
1155
|
+
abi: artifact.abi,
|
1156
|
+
bytecode: artifact.bytecode.object,
|
1157
|
+
placeholders,
|
1158
|
+
deployedBytecodeSize: size4(artifact.deployedBytecode.object)
|
1159
|
+
};
|
1160
|
+
}
|
1161
|
+
|
1162
|
+
// src/deploy/createPrepareDeploy.ts
|
1163
|
+
import { spliceHex } from "@latticexyz/common";
|
1164
|
+
import { getContractAddress as getContractAddress4 } from "@latticexyz/common/internal";
|
1165
|
+
function createPrepareDeploy(bytecodeWithPlaceholders, placeholders) {
|
1166
|
+
return function prepareDeploy(deployerAddress, libraryMap) {
|
1167
|
+
let bytecode = bytecodeWithPlaceholders;
|
1168
|
+
if (placeholders.length === 0) {
|
1169
|
+
return { bytecode, address: getContractAddress4({ deployerAddress, bytecode }) };
|
1170
|
+
}
|
1171
|
+
if (!libraryMap) {
|
1172
|
+
throw new Error("Libraries must be provided if there are placeholders");
|
1173
|
+
}
|
1174
|
+
for (const placeholder of placeholders) {
|
1175
|
+
const address = libraryMap.getAddress({
|
1176
|
+
name: placeholder.name,
|
1177
|
+
path: placeholder.path,
|
1178
|
+
deployer: deployerAddress
|
1179
|
+
});
|
1180
|
+
bytecode = spliceHex(bytecode, placeholder.start, placeholder.length, address);
|
1181
|
+
}
|
1182
|
+
return {
|
1183
|
+
bytecode,
|
1184
|
+
address: getContractAddress4({ deployerAddress, bytecode })
|
1185
|
+
};
|
1186
|
+
};
|
1187
|
+
}
|
1188
|
+
|
1189
|
+
// src/deploy/ensureResourceTags.ts
|
1190
|
+
import { getKeyTuple } from "@latticexyz/protocol-parser/internal";
|
1191
|
+
import { getRecords as getRecords4 } from "@latticexyz/store-sync";
|
1192
|
+
import { waitForTransactions } from "@latticexyz/common/internal";
|
1193
|
+
var metadataModuleArtifact = getContractArtifact(metadataModule);
|
1194
|
+
async function ensureResourceTags({
|
1195
|
+
client,
|
1196
|
+
deployerAddress,
|
1197
|
+
libraryMap,
|
1198
|
+
worldDeploy,
|
1199
|
+
tags,
|
1200
|
+
valueToHex = identity,
|
1201
|
+
indexerUrl,
|
1202
|
+
chainId
|
1203
|
+
}) {
|
1204
|
+
debug2("ensuring", tags.length, "resource tags");
|
1205
|
+
debug2("looking up existing resource tags");
|
1206
|
+
const { records } = await getRecords4({
|
1207
|
+
table: metadataConfig.tables.metadata__ResourceTag,
|
1208
|
+
worldAddress: worldDeploy.address,
|
1209
|
+
chainId,
|
1210
|
+
indexerUrl,
|
1211
|
+
client,
|
1212
|
+
fromBlock: worldDeploy.deployBlock,
|
1213
|
+
toBlock: worldDeploy.stateBlock
|
1214
|
+
});
|
1215
|
+
debug2("found", records.length, "resource tags");
|
1216
|
+
const existingTags = new Map(
|
1217
|
+
records.map((tag) => {
|
1218
|
+
const key = concatHex(getKeyTuple(metadataConfig.tables.metadata__ResourceTag, tag));
|
1219
|
+
return [key, tag.value];
|
1220
|
+
})
|
1221
|
+
);
|
1222
|
+
const desiredTags = tags.map(
|
1223
|
+
(tag) => ({
|
1224
|
+
resource: tag.resourceId,
|
1225
|
+
tag: stringToHex(tag.tag, { size: 32 }),
|
1226
|
+
value: valueToHex(tag.value)
|
1227
|
+
})
|
1228
|
+
);
|
1229
|
+
const pendingTags = desiredTags.filter((tag) => {
|
1230
|
+
const key = concatHex(getKeyTuple(metadataConfig.tables.metadata__ResourceTag, tag));
|
1231
|
+
return existingTags.get(key) !== tag.value;
|
1232
|
+
});
|
1233
|
+
if (pendingTags.length === 0) return [];
|
1234
|
+
const moduleTxs = await ensureModules({
|
1235
|
+
client,
|
1236
|
+
deployerAddress,
|
1237
|
+
worldDeploy,
|
1238
|
+
libraryMap,
|
1239
|
+
modules: [
|
1240
|
+
{
|
1241
|
+
optional: true,
|
1242
|
+
name: "MetadataModule",
|
1243
|
+
installAsRoot: false,
|
1244
|
+
installData: "0x",
|
1245
|
+
prepareDeploy: createPrepareDeploy(metadataModuleArtifact.bytecode, metadataModuleArtifact.placeholders),
|
1246
|
+
deployedBytecodeSize: metadataModuleArtifact.deployedBytecodeSize,
|
1247
|
+
abi: metadataModuleArtifact.abi
|
1248
|
+
}
|
1249
|
+
]
|
1250
|
+
});
|
1251
|
+
await waitForTransactions({
|
1252
|
+
client,
|
1253
|
+
hashes: moduleTxs,
|
1254
|
+
debugLabel: "metadata module installation"
|
1255
|
+
});
|
1256
|
+
debug2("setting", pendingTags.length, "resource tags");
|
1257
|
+
return (await Promise.all(
|
1258
|
+
pendingTags.map(async (tag) => {
|
1259
|
+
const resource = hexToResource5(tag.resource);
|
1260
|
+
const resourceString = `${resource.type}:${resource.namespace}:${resource.name}`;
|
1261
|
+
debug2(`tagging ${resourceString} with ${tag.tag}: ${JSON.stringify(tag.value)}`);
|
1262
|
+
try {
|
1263
|
+
return await writeContract7(client, {
|
1264
|
+
chain: client.chain ?? null,
|
1265
|
+
address: worldDeploy.address,
|
1266
|
+
abi: metadataAbi,
|
1267
|
+
// TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
|
1268
|
+
functionName: "metadata__setResourceTag",
|
1269
|
+
args: [tag.resource, tag.tag, tag.value]
|
1270
|
+
});
|
1271
|
+
} catch (error4) {
|
1272
|
+
debug2(
|
1273
|
+
`failed to set resource tag for ${resourceString}, skipping
|
1274
|
+
${error4 instanceof BaseError2 ? error4.shortMessage : error4}`
|
1275
|
+
);
|
1276
|
+
}
|
1277
|
+
})
|
1278
|
+
)).filter(isDefined4);
|
1279
|
+
}
|
1280
|
+
|
1281
|
+
// src/deploy/deployCustomWorld.ts
|
1282
|
+
import { concatHex as concatHex2, encodeDeployData as encodeDeployData4, isHex as isHex2 } from "viem";
|
1283
|
+
import { waitForTransactionReceipt as waitForTransactionReceipt2 } from "viem/actions";
|
1284
|
+
import { resourceToHex as resourceToHex2, sendTransaction, writeContract as writeContract8 } from "@latticexyz/common";
|
1285
|
+
import { ensureContractsDeployed as ensureContractsDeployed4, getContractAddress as getContractAddress5, waitForTransactions as waitForTransactions2 } from "@latticexyz/common/internal";
|
1286
|
+
function findArtifact(ref, artifacts) {
|
1287
|
+
const artifact = artifacts.find((a) => a.sourcePath === ref.sourcePath && a.name === ref.name);
|
1288
|
+
if (!artifact) throw new Error(`Could not find referenced artifact at "${ref.sourcePath}:${ref.name}".`);
|
1289
|
+
return artifact;
|
1290
|
+
}
|
1291
|
+
function getDependencies(artifact, artifacts) {
|
1292
|
+
return artifact.bytecode.filter((part) => !isHex2(part)).flatMap((ref) => {
|
1293
|
+
return getDependencies(findArtifact(ref, artifacts), artifacts);
|
1294
|
+
});
|
1295
|
+
}
|
1296
|
+
function getDeployable(deployerAddress, artifact, artifacts) {
|
1297
|
+
return concatHex2(
|
1298
|
+
artifact.bytecode.map((ref) => {
|
1299
|
+
if (isHex2(ref)) return ref;
|
1300
|
+
return getContractAddress5({
|
1301
|
+
deployerAddress,
|
1302
|
+
bytecode: getDeployable(deployerAddress, findArtifact(ref, artifacts), artifacts)
|
1303
|
+
});
|
1304
|
+
})
|
1305
|
+
);
|
1306
|
+
}
|
1307
|
+
async function deployCustomWorld({
|
1308
|
+
client,
|
1309
|
+
deployerAddress,
|
1310
|
+
artifacts,
|
1311
|
+
customWorld
|
1312
|
+
}) {
|
1313
|
+
const contracts = getWorldContracts(deployerAddress);
|
1314
|
+
await ensureContractsDeployed4({
|
1315
|
+
client,
|
1316
|
+
deployerAddress,
|
1317
|
+
contracts: Object.values(contracts)
|
1318
|
+
});
|
1319
|
+
const worldArtifact = findArtifact(customWorld, artifacts);
|
1320
|
+
const deps = getDependencies(worldArtifact, artifacts);
|
1321
|
+
if (deps.length) {
|
1322
|
+
debug2(`deploying ${deps.length} world dependencies`);
|
1323
|
+
await ensureContractsDeployed4({
|
1324
|
+
client,
|
1325
|
+
deployerAddress,
|
1326
|
+
contracts: deps.map((dep) => getDeployable(deployerAddress, dep, artifacts)).reverse().map((bytecode) => ({ bytecode }))
|
1327
|
+
});
|
1328
|
+
}
|
1329
|
+
debug2("deploying custom world");
|
1330
|
+
const deployTx = await sendTransaction(client, {
|
1331
|
+
chain: client.chain ?? null,
|
1332
|
+
data: encodeDeployData4({
|
1333
|
+
abi: worldArtifact.abi,
|
1334
|
+
args: [],
|
1335
|
+
// TODO (https://github.com/latticexyz/mud/issues/3150)
|
1336
|
+
bytecode: getDeployable(deployerAddress, worldArtifact, artifacts)
|
1337
|
+
})
|
1338
|
+
});
|
1339
|
+
debug2("waiting for custom world deploy");
|
1340
|
+
const receipt = await waitForTransactionReceipt2(client, { hash: deployTx });
|
1341
|
+
if (receipt.status !== "success") {
|
1342
|
+
console.error("world deploy failed", receipt);
|
1343
|
+
throw new Error("world deploy failed");
|
1344
|
+
}
|
1345
|
+
const deploy2 = logsToWorldDeploy(receipt.logs);
|
1346
|
+
debug2("deployed custom world to", deploy2.address, "at block", deploy2.deployBlock);
|
1347
|
+
const initTx = await writeContract8(client, {
|
1348
|
+
chain: client.chain ?? null,
|
1349
|
+
address: deploy2.address,
|
1350
|
+
abi: worldAbi,
|
1351
|
+
functionName: "initialize",
|
1352
|
+
args: [contracts.InitModule.address]
|
1353
|
+
});
|
1354
|
+
await waitForTransactions2({ client, hashes: [initTx], debugLabel: "world init" });
|
1355
|
+
const transferOwnershipTx = await writeContract8(client, {
|
1356
|
+
chain: client.chain ?? null,
|
1357
|
+
address: deploy2.address,
|
1358
|
+
abi: worldAbi,
|
1359
|
+
functionName: "transferOwnership",
|
1360
|
+
args: [resourceToHex2({ type: "namespace", namespace: "", name: "" }), client.account.address]
|
1361
|
+
});
|
1362
|
+
await waitForTransactions2({ client, hashes: [transferOwnershipTx], debugLabel: "world ownership transfer" });
|
1363
|
+
return { ...deploy2, stateBlock: deploy2.deployBlock };
|
1364
|
+
}
|
1365
|
+
|
1366
|
+
// src/deploy/deploy.ts
|
1367
|
+
import { uniqueBy } from "@latticexyz/common/utils";
|
1368
|
+
|
1369
|
+
// src/deploy/getLibraryMap.ts
|
1370
|
+
function getLibraryKey({ path: path16, name }) {
|
1371
|
+
return `${path16}:${name}`;
|
1372
|
+
}
|
1373
|
+
function getLibraryMap(libraries) {
|
1374
|
+
const cache = Object.fromEntries(libraries.map((library) => [getLibraryKey(library), library]));
|
1375
|
+
const libraryMap = {
|
1376
|
+
getAddress: ({ path: path16, name, deployer }) => {
|
1377
|
+
const library = cache[getLibraryKey({ path: path16, name })];
|
1378
|
+
if (!library) {
|
1379
|
+
throw new Error(`Could not find library for bytecode placeholder ${path16}:${name}`);
|
1380
|
+
}
|
1381
|
+
library.address ??= {};
|
1382
|
+
library.address[deployer] ??= library.prepareDeploy(deployer, libraryMap).address;
|
1383
|
+
return library.address[deployer];
|
1384
|
+
}
|
1385
|
+
};
|
1386
|
+
return libraryMap;
|
1387
|
+
}
|
1388
|
+
|
1389
|
+
// src/deploy/deploy.ts
|
1390
|
+
import { ensureContractsDeployed as ensureContractsDeployed5, ensureDeployer, waitForTransactions as waitForTransactions3 } from "@latticexyz/common/internal";
|
1391
|
+
async function deploy({
|
1392
|
+
config,
|
1393
|
+
client,
|
1394
|
+
tables,
|
1395
|
+
systems,
|
1396
|
+
libraries,
|
1397
|
+
modules = [],
|
1398
|
+
artifacts,
|
1399
|
+
salt,
|
1400
|
+
worldAddress: existingWorldAddress,
|
1401
|
+
worldDeployBlock,
|
1402
|
+
deployerAddress: initialDeployerAddress,
|
1403
|
+
indexerUrl,
|
1404
|
+
chainId
|
1405
|
+
}) {
|
1406
|
+
const deployerAddress = initialDeployerAddress ?? await ensureDeployer(client);
|
1407
|
+
const worldDeploy = existingWorldAddress ? await getWorldDeploy(client, existingWorldAddress, worldDeployBlock) : config.deploy.customWorld ? await deployCustomWorld({
|
1408
|
+
client,
|
1409
|
+
deployerAddress,
|
1410
|
+
artifacts,
|
1411
|
+
customWorld: config.deploy.customWorld
|
1412
|
+
}) : await deployWorld(
|
1413
|
+
client,
|
1414
|
+
deployerAddress,
|
1415
|
+
salt ?? `0x${randomBytes(32).toString("hex")}`,
|
1416
|
+
config.deploy.upgradeableWorldImplementation
|
1417
|
+
);
|
1418
|
+
const commonDeployOptions = {
|
1419
|
+
client,
|
1420
|
+
indexerUrl,
|
1421
|
+
chainId,
|
1422
|
+
worldDeploy
|
1423
|
+
};
|
1424
|
+
if (!supportedStoreVersions.includes(worldDeploy.storeVersion)) {
|
1425
|
+
throw new Error(`Unsupported Store version: ${worldDeploy.storeVersion}`);
|
1426
|
+
}
|
1427
|
+
if (!supportedWorldVersions.includes(worldDeploy.worldVersion)) {
|
1428
|
+
throw new Error(`Unsupported World version: ${worldDeploy.worldVersion}`);
|
1429
|
+
}
|
1430
|
+
const libraryMap = getLibraryMap(libraries);
|
1431
|
+
await ensureContractsDeployed5({
|
1432
|
+
...commonDeployOptions,
|
1433
|
+
deployerAddress,
|
1434
|
+
contracts: [
|
1435
|
+
...libraries.map((library) => ({
|
1436
|
+
bytecode: library.prepareDeploy(deployerAddress, libraryMap).bytecode,
|
1437
|
+
deployedBytecodeSize: library.deployedBytecodeSize,
|
1438
|
+
debugLabel: `${library.path}:${library.name} library`
|
1439
|
+
})),
|
1440
|
+
...systems.map((system) => ({
|
1441
|
+
bytecode: system.prepareDeploy(deployerAddress, libraryMap).bytecode,
|
1442
|
+
deployedBytecodeSize: system.deployedBytecodeSize,
|
1443
|
+
debugLabel: `${resourceToLabel4(system)} system`
|
1444
|
+
})),
|
1445
|
+
...modules.map((mod) => ({
|
1446
|
+
bytecode: mod.prepareDeploy(deployerAddress, libraryMap).bytecode,
|
1447
|
+
deployedBytecodeSize: mod.deployedBytecodeSize,
|
1448
|
+
debugLabel: `${mod.name} module`
|
1449
|
+
}))
|
1450
|
+
]
|
1451
|
+
});
|
1452
|
+
const namespaceTxs = await ensureNamespaceOwner({
|
1453
|
+
...commonDeployOptions,
|
1454
|
+
resourceIds: [...tables.map(({ tableId }) => tableId), ...systems.map(({ systemId }) => systemId)]
|
1455
|
+
});
|
1456
|
+
await waitForTransactions3({ client, hashes: namespaceTxs, debugLabel: "namespace registrations" });
|
1457
|
+
const tableTxs = await ensureTables({
|
1458
|
+
...commonDeployOptions,
|
1459
|
+
tables
|
1460
|
+
});
|
1461
|
+
const systemTxs = await ensureSystems({
|
1462
|
+
...commonDeployOptions,
|
1463
|
+
deployerAddress,
|
1464
|
+
libraryMap,
|
1465
|
+
systems
|
1466
|
+
});
|
1467
|
+
await waitForTransactions3({
|
1468
|
+
client,
|
1469
|
+
hashes: [...tableTxs, ...systemTxs],
|
1470
|
+
debugLabel: "table and system registrations"
|
1471
|
+
});
|
1472
|
+
const functionTxs = await ensureFunctions({
|
1473
|
+
...commonDeployOptions,
|
1474
|
+
functions: systems.flatMap((system) => system.worldFunctions)
|
1475
|
+
});
|
1476
|
+
const moduleTxs = await ensureModules({
|
1477
|
+
...commonDeployOptions,
|
1478
|
+
deployerAddress,
|
1479
|
+
libraryMap,
|
1480
|
+
modules
|
1481
|
+
});
|
1482
|
+
const namespaceTags = uniqueBy(
|
1483
|
+
[...tables, ...systems].filter(({ namespace, namespaceLabel }) => namespaceLabel !== namespace).map(({ namespace, namespaceLabel }) => ({
|
1484
|
+
resourceId: resourceToHex3({ type: "namespace", namespace, name: "" }),
|
1485
|
+
tag: "label",
|
1486
|
+
value: namespaceLabel
|
1487
|
+
})),
|
1488
|
+
(tag) => tag.resourceId
|
1489
|
+
);
|
1490
|
+
const tableTags = tables.filter((table) => table.label !== table.name).map(({ tableId: resourceId, label }) => ({ resourceId, tag: "label", value: label }));
|
1491
|
+
const systemTags = systems.flatMap(({ name, systemId: resourceId, label, metadata }) => [
|
1492
|
+
// only register labels if they differ from the resource ID
|
1493
|
+
...label !== name ? [{ resourceId, tag: "label", value: label }] : [],
|
1494
|
+
{ resourceId, tag: "abi", value: metadata.abi.join("\n") },
|
1495
|
+
{ resourceId, tag: "worldAbi", value: metadata.worldAbi.join("\n") }
|
1496
|
+
]);
|
1497
|
+
const tagTxs = await ensureResourceTags({
|
1498
|
+
...commonDeployOptions,
|
1499
|
+
deployerAddress,
|
1500
|
+
libraryMap,
|
1501
|
+
tags: [...namespaceTags, ...tableTags, ...systemTags],
|
1502
|
+
valueToHex: stringToHex2
|
1503
|
+
});
|
1504
|
+
await waitForTransactions3({
|
1505
|
+
client,
|
1506
|
+
hashes: [...functionTxs, ...moduleTxs, ...tagTxs],
|
1507
|
+
debugLabel: "remaining transactions"
|
1508
|
+
});
|
1509
|
+
debug2("deploy complete");
|
1510
|
+
return worldDeploy;
|
1511
|
+
}
|
1512
|
+
|
1513
|
+
// src/runDeploy.ts
|
1514
|
+
import { createWalletClient, http, isHex as isHex4, stringToHex as stringToHex3 } from "viem";
|
1515
|
+
import { privateKeyToAccount } from "viem/accounts";
|
1516
|
+
import { loadConfig as loadConfig3, resolveConfigPath as resolveConfigPath3 } from "@latticexyz/config/node";
|
1517
|
+
import { getOutDirectory, getRpcUrl } from "@latticexyz/common/foundry";
|
1518
|
+
import chalk2 from "chalk";
|
1519
|
+
import { MUDError as MUDError2 } from "@latticexyz/common/errors";
|
1520
|
+
|
1521
|
+
// src/deploy/resolveConfig.ts
|
1522
|
+
import path5 from "path";
|
1523
|
+
import { loadSystemsManifest, resolveSystems } from "@latticexyz/world/node";
|
1524
|
+
import { isHex as isHex3, toFunctionSelector, toFunctionSignature } from "viem";
|
1525
|
+
|
1526
|
+
// src/utils/getContractData.ts
|
1527
|
+
import { readFileSync } from "fs";
|
1528
|
+
import path4 from "path";
|
1529
|
+
import { MUDError } from "@latticexyz/common/errors";
|
1530
|
+
import { size as size5 } from "viem";
|
1531
|
+
function getContractData(filename, contractName, forgeOutDirectory) {
|
1532
|
+
let data;
|
1533
|
+
const contractDataPath = path4.join(forgeOutDirectory, filename, contractName + ".json");
|
1534
|
+
try {
|
1535
|
+
data = JSON.parse(readFileSync(contractDataPath, "utf8"));
|
1536
|
+
} catch (error4) {
|
1537
|
+
throw new MUDError(`Error reading file at ${contractDataPath}`);
|
1538
|
+
}
|
1539
|
+
const bytecode = data?.bytecode?.object;
|
1540
|
+
if (!bytecode) throw new MUDError(`No bytecode found in ${contractDataPath}`);
|
1541
|
+
const deployedBytecode = data?.deployedBytecode?.object;
|
1542
|
+
if (!deployedBytecode) throw new MUDError(`No deployed bytecode found in ${contractDataPath}`);
|
1543
|
+
const abi = data?.abi;
|
1544
|
+
if (!abi) throw new MUDError(`No ABI found in ${contractDataPath}`);
|
1545
|
+
const placeholders = findPlaceholders(data?.bytecode?.linkReferences ?? {});
|
1546
|
+
return { abi, bytecode, placeholders, deployedBytecodeSize: size5(deployedBytecode) };
|
1547
|
+
}
|
1548
|
+
|
1549
|
+
// src/deploy/resolveConfig.ts
|
1550
|
+
import { groupBy } from "@latticexyz/common/utils";
|
1551
|
+
|
1552
|
+
// src/deploy/findLibraries.ts
|
1553
|
+
import { readFileSync as readFileSync2 } from "fs";
|
1554
|
+
import { globSync } from "glob";
|
1555
|
+
|
1556
|
+
// src/deploy/orderByDependencies.ts
|
1557
|
+
import toposort from "toposort";
|
1558
|
+
function orderByDependencies(items, itemKey, dependencyKeys) {
|
1559
|
+
const dependencyOrder = toposort(
|
1560
|
+
items.flatMap((item) => dependencyKeys(item).map((dependency) => [itemKey(item), dependency]))
|
1561
|
+
);
|
1562
|
+
return [...items].sort((a, b) => dependencyOrder.indexOf(itemKey(a)) - dependencyOrder.indexOf(itemKey(b)));
|
1563
|
+
}
|
1564
|
+
|
1565
|
+
// src/deploy/findLibraries.ts
|
1566
|
+
function findLibraries(forgeOutDir) {
|
1567
|
+
const artifacts = globSync(`${forgeOutDir}/**/*.json`, { ignore: "**/*.abi.json" }).sort().map((path16) => JSON.parse(readFileSync2(path16, "utf8")));
|
1568
|
+
const libraries = artifacts.flatMap((artifact) => {
|
1569
|
+
if (!artifact.metadata) return [];
|
1570
|
+
const contractPath = Object.keys(artifact.metadata.settings.compilationTarget)[0];
|
1571
|
+
const contractName = artifact.metadata.settings.compilationTarget[contractPath];
|
1572
|
+
const linkReferences = artifact.bytecode.linkReferences;
|
1573
|
+
return Object.entries(linkReferences).flatMap(
|
1574
|
+
([libraryPath, reference]) => Object.keys(reference).map((libraryName) => ({
|
1575
|
+
path: libraryPath,
|
1576
|
+
name: libraryName,
|
1577
|
+
dependentPath: contractPath,
|
1578
|
+
dependentName: contractName
|
1579
|
+
}))
|
1580
|
+
);
|
1581
|
+
});
|
1582
|
+
return orderByDependencies(
|
1583
|
+
libraries,
|
1584
|
+
(lib) => `${lib.path}:${lib.name}`,
|
1585
|
+
(lib) => [`${lib.dependentPath}:${lib.dependentName}`]
|
1586
|
+
);
|
1587
|
+
}
|
1588
|
+
|
1589
|
+
// src/deploy/resolveConfig.ts
|
1590
|
+
import { findUp } from "find-up";
|
1591
|
+
import { createRequire } from "node:module";
|
1592
|
+
|
1593
|
+
// src/deploy/compat/excludeUnstableCallWithSignatureModule.ts
|
1594
|
+
function excludeCallWithSignatureModule(mod) {
|
1595
|
+
if (mod.artifactPath === "@latticexyz/world-modules/out/Unstable_CallWithSignatureModule.sol/Unstable_CallWithSignatureModule.json") {
|
1596
|
+
console.warn(
|
1597
|
+
[
|
1598
|
+
"",
|
1599
|
+
`\u26A0\uFE0F Your \`mud.config.ts\` is using \`Unstable_CallWithSignatureModule\`. This module can be removed from your config as it is now installed by default during deploy.`,
|
1600
|
+
""
|
1601
|
+
].join("\n")
|
1602
|
+
);
|
1603
|
+
return false;
|
1604
|
+
}
|
1605
|
+
return true;
|
1606
|
+
}
|
1607
|
+
|
1608
|
+
// src/deploy/resolveConfig.ts
|
1609
|
+
async function resolveConfig({
|
1610
|
+
rootDir,
|
1611
|
+
config,
|
1612
|
+
forgeOutDir
|
1613
|
+
}) {
|
1614
|
+
const requirePath = await findUp("package.json");
|
1615
|
+
if (!requirePath) throw new Error("Could not find package.json to import relative to.");
|
1616
|
+
const require2 = createRequire(requirePath);
|
1617
|
+
const moduleOutDirs = config.modules.filter(excludeCallWithSignatureModule).flatMap((mod) => {
|
1618
|
+
if (mod.artifactPath == void 0) {
|
1619
|
+
return [];
|
1620
|
+
}
|
1621
|
+
const moduleOutDir = path5.join(require2.resolve(mod.artifactPath), "../../");
|
1622
|
+
return [moduleOutDir];
|
1623
|
+
});
|
1624
|
+
const libraries = [forgeOutDir, ...moduleOutDirs].flatMap(
|
1625
|
+
(outDir) => findLibraries(outDir).map((library) => {
|
1626
|
+
const contractData = getContractData(path5.basename(library.path), library.name, outDir);
|
1627
|
+
return {
|
1628
|
+
path: library.path,
|
1629
|
+
name: library.name,
|
1630
|
+
abi: contractData.abi,
|
1631
|
+
prepareDeploy: createPrepareDeploy(contractData.bytecode, contractData.placeholders),
|
1632
|
+
deployedBytecodeSize: contractData.deployedBytecodeSize
|
1633
|
+
};
|
1634
|
+
})
|
1635
|
+
);
|
1636
|
+
const baseSystemContractData = getContractData("System.sol", "System", forgeOutDir);
|
1637
|
+
const baseSystemFunctions = baseSystemContractData.abi.filter((item) => item.type === "function").map(toFunctionSignature);
|
1638
|
+
const configSystems = await resolveSystems({ rootDir, config });
|
1639
|
+
const systemsManifest = await loadSystemsManifest({ rootDir, config });
|
1640
|
+
const systems = configSystems.filter((system) => !system.deploy.disabled).map((system) => {
|
1641
|
+
const manifest = systemsManifest.systems.find(({ systemId }) => systemId === system.systemId);
|
1642
|
+
if (!manifest) {
|
1643
|
+
throw new Error(
|
1644
|
+
`System "${system.label}" not found in systems manifest. Run \`mud build\` before trying again.`
|
1645
|
+
);
|
1646
|
+
}
|
1647
|
+
const contractData = getContractData(`${system.label}.sol`, system.label, forgeOutDir);
|
1648
|
+
const worldFunctions = system.deploy.registerWorldFunctions ? contractData.abi.filter((item) => item.type === "function").map(toFunctionSignature).filter((sig) => !baseSystemFunctions.includes(sig)).map((sig) => {
|
1649
|
+
const worldSignature = system.namespace === "" ? sig : `${system.namespace}__${sig}`;
|
1650
|
+
return {
|
1651
|
+
signature: worldSignature,
|
1652
|
+
selector: toFunctionSelector(worldSignature),
|
1653
|
+
systemId: system.systemId,
|
1654
|
+
systemFunctionSignature: sig,
|
1655
|
+
systemFunctionSelector: toFunctionSelector(sig)
|
1656
|
+
};
|
1657
|
+
}) : [];
|
1658
|
+
const allowedAddresses = system.accessList.filter((target) => isHex3(target));
|
1659
|
+
const allowedSystemIds = system.accessList.filter((target) => !isHex3(target)).map((label) => {
|
1660
|
+
const system2 = configSystems.find((s) => s.label === label);
|
1661
|
+
return system2.systemId;
|
1662
|
+
});
|
1663
|
+
return {
|
1664
|
+
...system,
|
1665
|
+
allowAll: system.openAccess,
|
1666
|
+
allowedAddresses,
|
1667
|
+
allowedSystemIds,
|
1668
|
+
prepareDeploy: createPrepareDeploy(contractData.bytecode, contractData.placeholders),
|
1669
|
+
deployedBytecodeSize: contractData.deployedBytecodeSize,
|
1670
|
+
worldFunctions,
|
1671
|
+
abi: contractData.abi,
|
1672
|
+
metadata: {
|
1673
|
+
abi: manifest.abi,
|
1674
|
+
worldAbi: manifest.worldAbi
|
1675
|
+
}
|
1676
|
+
};
|
1677
|
+
});
|
1678
|
+
const systemsById = groupBy(systems, (system) => system.systemId);
|
1679
|
+
const overlappingSystems = Array.from(systemsById.values()).filter((matches) => matches.length > 1).flat();
|
1680
|
+
if (overlappingSystems.length) {
|
1681
|
+
const names = overlappingSystems.map((system) => system.name);
|
1682
|
+
throw new Error(
|
1683
|
+
`Found systems with overlapping system ID: ${names.join(
|
1684
|
+
", "
|
1685
|
+
)}.
|
1686
|
+
|
1687
|
+
System IDs are generated from the first 16 bytes of the name, so you may need to rename them to avoid the overlap.`
|
1688
|
+
);
|
1689
|
+
}
|
1690
|
+
return {
|
1691
|
+
systems,
|
1692
|
+
libraries
|
1693
|
+
};
|
1694
|
+
}
|
1695
|
+
|
1696
|
+
// src/runDeploy.ts
|
1697
|
+
import { getChainId } from "viem/actions";
|
1698
|
+
|
1699
|
+
// src/utils/postDeploy.ts
|
1700
|
+
import { existsSync } from "fs";
|
1701
|
+
import path6 from "path";
|
1702
|
+
import chalk from "chalk";
|
1703
|
+
import { getScriptDirectory, forge as forge2 } from "@latticexyz/common/foundry";
|
1704
|
+
async function postDeploy(postDeployScript, worldAddress, rpc, profile, forgeOptions, kms) {
|
1705
|
+
const userOptions = forgeOptions?.replaceAll("\\", "").split(" ") ?? [];
|
1706
|
+
const postDeployPath = path6.join(await getScriptDirectory(), postDeployScript + ".s.sol");
|
1707
|
+
if (!existsSync(postDeployPath)) {
|
1708
|
+
console.log(`No script at ${postDeployPath}, skipping post deploy hook`);
|
1709
|
+
return;
|
1710
|
+
}
|
1711
|
+
console.log(chalk.blue(`Executing post deploy script at ${postDeployPath}`));
|
1712
|
+
await forge2(
|
1713
|
+
[
|
1714
|
+
"script",
|
1715
|
+
postDeployScript,
|
1716
|
+
"--broadcast",
|
1717
|
+
"--sig",
|
1718
|
+
"run(address)",
|
1719
|
+
worldAddress,
|
1720
|
+
"--rpc-url",
|
1721
|
+
rpc,
|
1722
|
+
"-vvv",
|
1723
|
+
kms ? "--aws" : "",
|
1724
|
+
...userOptions
|
1725
|
+
],
|
1726
|
+
{
|
1727
|
+
profile
|
1728
|
+
}
|
1729
|
+
);
|
1730
|
+
}
|
1731
|
+
|
1732
|
+
// src/runDeploy.ts
|
1733
|
+
import { kmsKeyToAccount } from "@latticexyz/common/kms";
|
1734
|
+
|
1735
|
+
// src/deploy/configToModules.ts
|
1736
|
+
import path7 from "node:path";
|
1737
|
+
import { encodeField } from "@latticexyz/protocol-parser/internal";
|
1738
|
+
import { bytesToHex } from "viem";
|
1739
|
+
|
1740
|
+
// src/utils/importContractArtifact.ts
|
1741
|
+
import { createRequire as createRequire2 } from "node:module";
|
1742
|
+
import { findUp as findUp2 } from "find-up";
|
1743
|
+
async function importContractArtifact({
|
1744
|
+
packageJsonPath,
|
1745
|
+
artifactPath
|
1746
|
+
}) {
|
1747
|
+
let artfactJson;
|
1748
|
+
try {
|
1749
|
+
const requirePath = packageJsonPath ?? await findUp2("package.json", { cwd: process.cwd() });
|
1750
|
+
if (!requirePath) throw new Error("Could not find package.json to import relative to.");
|
1751
|
+
const require2 = createRequire2(requirePath);
|
1752
|
+
artfactJson = require2(artifactPath);
|
1753
|
+
} catch (error4) {
|
1754
|
+
console.error();
|
1755
|
+
console.error("Could not import contract artifact at", artifactPath);
|
1756
|
+
console.error();
|
1757
|
+
throw error4;
|
1758
|
+
}
|
1759
|
+
return getContractArtifact(artfactJson);
|
1760
|
+
}
|
1761
|
+
|
1762
|
+
// src/deploy/configToModules.ts
|
1763
|
+
import { resolveWithContext } from "@latticexyz/world/internal";
|
1764
|
+
import callWithSignatureModule from "@latticexyz/world-module-callwithsignature/out/CallWithSignatureModule.sol/CallWithSignatureModule.json" assert { type: "json" };
|
1765
|
+
var callWithSignatureModuleArtifact = getContractArtifact(callWithSignatureModule);
|
1766
|
+
var knownModuleArtifacts = {
|
1767
|
+
KeysWithValueModule: "@latticexyz/world-modules/out/KeysWithValueModule.sol/KeysWithValueModule.json",
|
1768
|
+
KeysInTableModule: "@latticexyz/world-modules/out/KeysInTableModule.sol/KeysInTableModule.json",
|
1769
|
+
UniqueEntityModule: "@latticexyz/world-modules/out/UniqueEntityModule.sol/UniqueEntityModule.json"
|
1770
|
+
};
|
1771
|
+
async function configToModules(config, forgeOutDir) {
|
1772
|
+
const defaultModules = [
|
1773
|
+
{
|
1774
|
+
// optional for now
|
1775
|
+
// TODO: figure out approach to install on existing worlds where deployer may not own root namespace
|
1776
|
+
optional: true,
|
1777
|
+
name: "CallWithSignatureModule",
|
1778
|
+
installAsRoot: true,
|
1779
|
+
installData: "0x",
|
1780
|
+
prepareDeploy: createPrepareDeploy(
|
1781
|
+
callWithSignatureModuleArtifact.bytecode,
|
1782
|
+
callWithSignatureModuleArtifact.placeholders
|
1783
|
+
),
|
1784
|
+
deployedBytecodeSize: callWithSignatureModuleArtifact.deployedBytecodeSize,
|
1785
|
+
abi: callWithSignatureModuleArtifact.abi
|
1786
|
+
}
|
1787
|
+
];
|
1788
|
+
const modules = await Promise.all(
|
1789
|
+
config.modules.filter(excludeCallWithSignatureModule).map(async (mod) => {
|
1790
|
+
let artifactPath = mod.artifactPath;
|
1791
|
+
if (!artifactPath) {
|
1792
|
+
if (mod.name) {
|
1793
|
+
artifactPath = knownModuleArtifacts[mod.name] ?? path7.join(forgeOutDir, `${mod.name}.sol`, `${mod.name}.json`);
|
1794
|
+
console.warn(
|
1795
|
+
[
|
1796
|
+
"",
|
1797
|
+
`\u26A0\uFE0F Your \`mud.config.ts\` is using a module with a \`name\`, but this option is deprecated.`,
|
1798
|
+
"",
|
1799
|
+
"To resolve this, you can replace this:",
|
1800
|
+
"",
|
1801
|
+
` name: ${JSON.stringify(mod.name)}`,
|
1802
|
+
"",
|
1803
|
+
"with this:",
|
1804
|
+
"",
|
1805
|
+
` artifactPath: ${JSON.stringify(artifactPath)}`,
|
1806
|
+
""
|
1807
|
+
].join("\n")
|
1808
|
+
);
|
1809
|
+
} else {
|
1810
|
+
throw new Error("No `artifactPath` provided for module.");
|
1811
|
+
}
|
1812
|
+
}
|
1813
|
+
const name = path7.basename(artifactPath, ".json");
|
1814
|
+
const artifact = await importContractArtifact({ artifactPath });
|
1815
|
+
const installArgs = mod.args.map((arg) => resolveWithContext(arg, { config })).map((arg) => {
|
1816
|
+
const value = arg.value instanceof Uint8Array ? bytesToHex(arg.value) : arg.value;
|
1817
|
+
return encodeField(arg.type, value);
|
1818
|
+
});
|
1819
|
+
if (installArgs.length > 1) {
|
1820
|
+
throw new Error(`${name} module should only have 0-1 args, but had ${installArgs.length} args.`);
|
1821
|
+
}
|
1822
|
+
return {
|
1823
|
+
name,
|
1824
|
+
installAsRoot: mod.root,
|
1825
|
+
installData: installArgs.length === 0 ? "0x" : installArgs[0],
|
1826
|
+
prepareDeploy: createPrepareDeploy(artifact.bytecode, artifact.placeholders),
|
1827
|
+
deployedBytecodeSize: artifact.deployedBytecodeSize,
|
1828
|
+
abi: artifact.abi
|
1829
|
+
};
|
1830
|
+
})
|
1831
|
+
);
|
1832
|
+
return [...defaultModules, ...modules];
|
1833
|
+
}
|
1834
|
+
|
1835
|
+
// src/runDeploy.ts
|
1836
|
+
import { findContractArtifacts } from "@latticexyz/world/node";
|
1837
|
+
|
1838
|
+
// src/utils/enableAutomine.ts
|
1839
|
+
import { getAutomine, getBlock as getBlock2, setAutomine, setIntervalMining } from "viem/actions";
|
1840
|
+
import { getAction } from "viem/utils";
|
1841
|
+
async function enableAutomine(client) {
|
1842
|
+
const miningMode = await getMiningMode(client).catch(() => void 0);
|
1843
|
+
if (!miningMode || miningMode.type === "automine") return;
|
1844
|
+
debug("Enabling automine");
|
1845
|
+
await setMiningMode(client, { type: "automine" });
|
1846
|
+
return {
|
1847
|
+
reset: () => {
|
1848
|
+
debug("Disabling automine");
|
1849
|
+
return setMiningMode(client, miningMode);
|
1850
|
+
}
|
1851
|
+
};
|
1852
|
+
}
|
1853
|
+
async function getMiningMode(client) {
|
1854
|
+
const localClient = { mode: "anvil", ...client };
|
1855
|
+
const isAutomine = await getAction(localClient, getAutomine, "getAutomine")({});
|
1856
|
+
if (isAutomine) {
|
1857
|
+
return { type: "automine" };
|
1858
|
+
}
|
1859
|
+
const blockTime = await getBlockTime(client);
|
1860
|
+
return { type: "interval", blockTime };
|
1861
|
+
}
|
1862
|
+
async function setMiningMode(client, miningMode) {
|
1863
|
+
if (miningMode.type === "automine") {
|
1864
|
+
await getAction(client, setAutomine, "setAutomine")(true);
|
1865
|
+
} else {
|
1866
|
+
await getAction(client, setIntervalMining, "setIntervalMining")({ interval: miningMode.blockTime });
|
1867
|
+
}
|
1868
|
+
}
|
1869
|
+
async function getBlockTime(client) {
|
1870
|
+
const latestBlock = await getAction(client, getBlock2, "getBlock")({ blockTag: "latest" });
|
1871
|
+
const previousBlock = await getAction(client, getBlock2, "getBlock")({ blockNumber: latestBlock.number - 1n });
|
1872
|
+
const blockTime = latestBlock.timestamp - previousBlock.timestamp;
|
1873
|
+
return Number(blockTime);
|
1874
|
+
}
|
1875
|
+
|
1876
|
+
// src/defaultChains.ts
|
1877
|
+
import { redstone, garnet, rhodolite } from "@latticexyz/common/chains";
|
1878
|
+
var defaultChains = [redstone, garnet, rhodolite];
|
1879
|
+
|
1880
|
+
// src/runDeploy.ts
|
1881
|
+
var deployOptions = {
|
1882
|
+
configPath: { type: "string", desc: "Path to the MUD config file" },
|
1883
|
+
printConfig: { type: "boolean", desc: "Print the resolved config" },
|
1884
|
+
profile: { type: "string", desc: "The foundry profile to use" },
|
1885
|
+
saveDeployment: { type: "boolean", desc: "Save the deployment info to a file", default: true },
|
1886
|
+
rpc: { type: "string", desc: "The RPC URL to use. Defaults to the RPC url from the local foundry.toml" },
|
1887
|
+
rpcBatch: {
|
1888
|
+
type: "boolean",
|
1889
|
+
desc: "Enable batch processing of RPC requests in viem client (defaults to batch size of 100 and wait of 1s)"
|
1890
|
+
},
|
1891
|
+
deployerAddress: {
|
1892
|
+
type: "string",
|
1893
|
+
desc: "Deploy using an existing deterministic deployer (https://github.com/Arachnid/deterministic-deployment-proxy)"
|
1894
|
+
},
|
1895
|
+
worldAddress: { type: "string", desc: "Deploy to an existing World at the given address" },
|
1896
|
+
skipBuild: { type: "boolean", desc: "Skip rebuilding the contracts before deploying" },
|
1897
|
+
alwaysRunPostDeploy: {
|
1898
|
+
type: "boolean",
|
1899
|
+
desc: "Always run PostDeploy.s.sol after each deploy (including during upgrades). By default, PostDeploy.s.sol is only run once after a new world is deployed."
|
1900
|
+
},
|
1901
|
+
forgeScriptOptions: { type: "string", description: "Options to pass to forge script PostDeploy.s.sol" },
|
1902
|
+
salt: {
|
1903
|
+
type: "string",
|
1904
|
+
desc: "The deployment salt to use. Defaults to a random salt."
|
1905
|
+
},
|
1906
|
+
kms: {
|
1907
|
+
type: "boolean",
|
1908
|
+
desc: "Deploy the World with an AWS KMS key instead of local private key."
|
1909
|
+
},
|
1910
|
+
indexerUrl: {
|
1911
|
+
type: "string",
|
1912
|
+
desc: "The indexer URL to use.",
|
1913
|
+
required: false
|
1914
|
+
}
|
1915
|
+
};
|
1916
|
+
async function runDeploy(opts) {
|
1917
|
+
const salt = opts.salt != null ? isHex4(opts.salt) ? opts.salt : stringToHex3(opts.salt) : void 0;
|
1918
|
+
const profile = opts.profile;
|
1919
|
+
const configPath = await resolveConfigPath3(opts.configPath);
|
1920
|
+
const config = await loadConfig3(configPath);
|
1921
|
+
const rootDir = path8.dirname(configPath);
|
1922
|
+
console.log(chalk2.green(`
|
1923
|
+
Using ${package_default.name}@${package_default.version}`));
|
1924
|
+
if (opts.printConfig) {
|
1925
|
+
console.log(chalk2.green("\nResolved config:\n"), JSON.stringify(config, null, 2));
|
1926
|
+
}
|
1927
|
+
const outDir = await getOutDirectory(profile);
|
1928
|
+
const rpc = opts.rpc ?? await getRpcUrl(profile);
|
1929
|
+
console.log(
|
1930
|
+
chalk2.bgBlue(
|
1931
|
+
chalk2.whiteBright(`
|
1932
|
+
Deploying MUD contracts${profile ? " with profile " + profile : ""} to RPC ${rpc}
|
1933
|
+
`)
|
1934
|
+
)
|
1935
|
+
);
|
1936
|
+
if (!opts.skipBuild) {
|
1937
|
+
await build({ rootDir, config, foundryProfile: profile });
|
1938
|
+
}
|
1939
|
+
const { systems, libraries } = await resolveConfig({
|
1940
|
+
rootDir,
|
1941
|
+
config,
|
1942
|
+
forgeOutDir: outDir
|
1943
|
+
});
|
1944
|
+
const artifacts = await findContractArtifacts({ forgeOutDir: outDir });
|
1945
|
+
const modules = await configToModules(config, outDir);
|
1946
|
+
const tables = Object.values(config.namespaces).flatMap((namespace) => Object.values(namespace.tables)).filter((table) => !table.deploy.disabled);
|
1947
|
+
const account = await (async () => {
|
1948
|
+
if (opts.kms) {
|
1949
|
+
const keyId = process.env.AWS_KMS_KEY_ID;
|
1950
|
+
if (!keyId) {
|
1951
|
+
throw new MUDError2(
|
1952
|
+
"Missing `AWS_KMS_KEY_ID` environment variable. This is required when using with `--kms` option."
|
1953
|
+
);
|
1954
|
+
}
|
1955
|
+
return await kmsKeyToAccount({ keyId });
|
1956
|
+
} else {
|
1957
|
+
const privateKey = process.env.PRIVATE_KEY;
|
1958
|
+
if (!privateKey) {
|
1959
|
+
throw new MUDError2(
|
1960
|
+
`Missing PRIVATE_KEY environment variable.
|
1961
|
+
Run 'echo "PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" > .env'
|
1962
|
+
in your contracts directory to use the default anvil private key.`
|
1963
|
+
);
|
1964
|
+
}
|
1965
|
+
return privateKeyToAccount(privateKey);
|
1966
|
+
}
|
1967
|
+
})();
|
1968
|
+
const client = createWalletClient({
|
1969
|
+
transport: http(rpc, {
|
1970
|
+
batch: opts.rpcBatch ? {
|
1971
|
+
batchSize: 100,
|
1972
|
+
wait: 1e3
|
1973
|
+
} : void 0
|
1974
|
+
}),
|
1975
|
+
account
|
1976
|
+
});
|
1977
|
+
const chainId = await getChainId(client);
|
1978
|
+
const indexerUrl = opts.indexerUrl ?? defaultChains.find((chain) => chain.id === chainId)?.indexerUrl;
|
1979
|
+
const worldDeployBlock = opts.worldAddress ? getWorldDeployBlock({
|
1980
|
+
worldAddress: opts.worldAddress,
|
1981
|
+
worldsFile: config.deploy.worldsFile,
|
1982
|
+
chainId
|
1983
|
+
}) : void 0;
|
1984
|
+
console.log("Deploying from", client.account.address);
|
1985
|
+
const automine = await enableAutomine(client);
|
1986
|
+
const startTime = Date.now();
|
1987
|
+
const worldDeploy = await deploy({
|
1988
|
+
config,
|
1989
|
+
deployerAddress: opts.deployerAddress,
|
1990
|
+
salt,
|
1991
|
+
worldAddress: opts.worldAddress,
|
1992
|
+
worldDeployBlock,
|
1993
|
+
client,
|
1994
|
+
tables,
|
1995
|
+
systems,
|
1996
|
+
libraries,
|
1997
|
+
modules,
|
1998
|
+
artifacts,
|
1999
|
+
indexerUrl,
|
2000
|
+
chainId
|
2001
|
+
});
|
2002
|
+
if (opts.worldAddress == null || opts.alwaysRunPostDeploy) {
|
2003
|
+
await postDeploy(
|
2004
|
+
config.deploy.postDeployScript,
|
2005
|
+
worldDeploy.address,
|
2006
|
+
rpc,
|
2007
|
+
profile,
|
2008
|
+
opts.forgeScriptOptions,
|
2009
|
+
opts.kms ? true : false
|
2010
|
+
);
|
2011
|
+
}
|
2012
|
+
await automine?.reset();
|
2013
|
+
console.log(chalk2.green("Deployment completed in", (Date.now() - startTime) / 1e3, "seconds"));
|
2014
|
+
const deploymentInfo = {
|
2015
|
+
worldAddress: worldDeploy.address,
|
2016
|
+
blockNumber: Number(worldDeploy.deployBlock)
|
2017
|
+
};
|
2018
|
+
if (opts.saveDeployment) {
|
2019
|
+
const deploysDir = path8.join(config.deploy.deploysDirectory, chainId.toString());
|
2020
|
+
mkdirSync(deploysDir, { recursive: true });
|
2021
|
+
writeFileSync(path8.join(deploysDir, "latest.json"), JSON.stringify(deploymentInfo, null, 2) + "\n");
|
2022
|
+
writeFileSync(path8.join(deploysDir, Date.now() + ".json"), JSON.stringify(deploymentInfo, null, 2) + "\n");
|
2023
|
+
const localChains = [1337, 31337];
|
2024
|
+
const deploys2 = existsSync2(config.deploy.worldsFile) ? JSON.parse(readFileSync3(config.deploy.worldsFile, "utf-8")) : {};
|
2025
|
+
deploys2[chainId] = {
|
2026
|
+
address: deploymentInfo.worldAddress,
|
2027
|
+
// We expect the worlds file to be committed and since local deployments are often
|
2028
|
+
// a consistent address but different block number, we'll ignore the block number.
|
2029
|
+
blockNumber: localChains.includes(chainId) ? void 0 : deploymentInfo.blockNumber
|
2030
|
+
};
|
2031
|
+
writeFileSync(config.deploy.worldsFile, JSON.stringify(deploys2, null, 2) + "\n");
|
2032
|
+
console.log(
|
2033
|
+
chalk2.bgGreen(
|
2034
|
+
chalk2.whiteBright(`
|
2035
|
+
Deployment result (written to ${config.deploy.worldsFile} and ${deploysDir}):
|
2036
|
+
`)
|
2037
|
+
)
|
2038
|
+
);
|
2039
|
+
}
|
2040
|
+
console.log(deploymentInfo);
|
2041
|
+
return worldDeploy;
|
2042
|
+
}
|
2043
|
+
function getWorldDeployBlock({
|
2044
|
+
chainId,
|
2045
|
+
worldAddress,
|
2046
|
+
worldsFile
|
2047
|
+
}) {
|
2048
|
+
const deploys2 = existsSync2(worldsFile) ? JSON.parse(readFileSync3(worldsFile, "utf-8")) : {};
|
2049
|
+
const worldDeployBlock = deploys2[chainId]?.address === worldAddress ? deploys2[chainId].blockNumber : void 0;
|
2050
|
+
return worldDeployBlock ? BigInt(worldDeployBlock) : void 0;
|
2051
|
+
}
|
2052
|
+
|
2053
|
+
// src/commands/deploy.ts
|
2054
|
+
var commandModule5 = {
|
2055
|
+
command: "deploy",
|
2056
|
+
describe: "Deploy MUD contracts",
|
2057
|
+
builder(yargs) {
|
2058
|
+
return yargs.options(deployOptions);
|
2059
|
+
},
|
2060
|
+
async handler(opts) {
|
2061
|
+
try {
|
2062
|
+
await runDeploy(opts);
|
2063
|
+
} catch (error4) {
|
2064
|
+
logError(error4);
|
2065
|
+
process.exit(1);
|
2066
|
+
}
|
2067
|
+
process.exit(0);
|
2068
|
+
}
|
2069
|
+
};
|
2070
|
+
var deploy_default = commandModule5;
|
2071
|
+
|
2072
|
+
// src/commands/worldgen.ts
|
2073
|
+
import path9 from "node:path";
|
2074
|
+
import { loadConfig as loadConfig4, resolveConfigPath as resolveConfigPath4 } from "@latticexyz/config/node";
|
2075
|
+
import { worldgen as worldgen2 } from "@latticexyz/world/node";
|
2076
|
+
var commandModule6 = {
|
2077
|
+
command: "worldgen",
|
2078
|
+
describe: "Autogenerate interfaces for Systems and World based on existing contracts and the config file",
|
2079
|
+
builder(yargs) {
|
2080
|
+
return yargs.options({
|
2081
|
+
configPath: { type: "string", desc: "Path to the MUD config file" },
|
2082
|
+
clean: {
|
2083
|
+
type: "boolean",
|
2084
|
+
desc: "Clear the worldgen directory before generating new interfaces (defaults to true)",
|
2085
|
+
default: true
|
2086
|
+
}
|
2087
|
+
});
|
2088
|
+
},
|
2089
|
+
async handler(args) {
|
2090
|
+
await worldgenHandler(args);
|
2091
|
+
process.exit(0);
|
2092
|
+
}
|
2093
|
+
};
|
2094
|
+
async function worldgenHandler(args) {
|
2095
|
+
const configPath = await resolveConfigPath4(args.configPath);
|
2096
|
+
const config = await loadConfig4(configPath);
|
2097
|
+
const rootDir = path9.dirname(configPath);
|
2098
|
+
await worldgen2({ rootDir, config, clean: args.clean });
|
2099
|
+
}
|
2100
|
+
var worldgen_default = commandModule6;
|
2101
|
+
|
2102
|
+
// src/commands/set-version.ts
|
2103
|
+
import chalk3 from "chalk";
|
2104
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
2105
|
+
import path10 from "path";
|
2106
|
+
import { MUDError as MUDError3 } from "@latticexyz/common/errors";
|
2107
|
+
import { globSync as globSync2 } from "glob";
|
2108
|
+
|
2109
|
+
// src/mudPackages.ts
|
2110
|
+
import { ZodError, z as z2 } from "zod";
|
2111
|
+
var envSchema = z2.object({
|
2112
|
+
MUD_PACKAGES: z2.string().transform((value) => JSON.parse(value))
|
2113
|
+
});
|
2114
|
+
function parseEnv() {
|
2115
|
+
try {
|
2116
|
+
return envSchema.parse({
|
2117
|
+
// tsup replaces the env vars with their values at compile time
|
2118
|
+
MUD_PACKAGES: '{"@latticexyz/abi-ts":{"localPath":"packages/abi-ts"},"@latticexyz/block-logs-stream":{"localPath":"packages/block-logs-stream"},"@latticexyz/cli":{"localPath":"packages/cli"},"@latticexyz/common":{"localPath":"packages/common"},"@latticexyz/config":{"localPath":"packages/config"},"create-mud":{"localPath":"packages/create-mud"},"@latticexyz/dev-tools":{"localPath":"packages/dev-tools"},"@latticexyz/entrykit":{"localPath":"packages/entrykit"},"@latticexyz/explorer":{"localPath":"packages/explorer"},"@latticexyz/faucet":{"localPath":"packages/faucet"},"@latticexyz/gas-report":{"localPath":"packages/gas-report"},"@latticexyz/paymaster":{"localPath":"packages/paymaster"},"@latticexyz/protocol-parser":{"localPath":"packages/protocol-parser"},"@latticexyz/react":{"localPath":"packages/react"},"@latticexyz/recs":{"localPath":"packages/recs"},"@latticexyz/schema-type":{"localPath":"packages/schema-type"},"solhint-config-mud":{"localPath":"packages/solhint-config-mud"},"solhint-plugin-mud":{"localPath":"packages/solhint-plugin-mud"},"@latticexyz/stash":{"localPath":"packages/stash"},"@latticexyz/store-consumer":{"localPath":"packages/store-consumer"},"@latticexyz/store-indexer":{"localPath":"packages/store-indexer"},"@latticexyz/store-sync":{"localPath":"packages/store-sync"},"@latticexyz/store":{"localPath":"packages/store"},"@latticexyz/utils":{"localPath":"packages/utils"},"vite-plugin-mud":{"localPath":"packages/vite-plugin-mud"},"@latticexyz/world-module-callwithsignature":{"localPath":"packages/world-module-callwithsignature"},"@latticexyz/world-module-erc20":{"localPath":"packages/world-module-erc20"},"@latticexyz/world-module-metadata":{"localPath":"packages/world-module-metadata"},"@latticexyz/world-modules":{"localPath":"packages/world-modules"},"@latticexyz/world":{"localPath":"packages/world"}}'
|
2119
|
+
});
|
2120
|
+
} catch (error4) {
|
2121
|
+
if (error4 instanceof ZodError) {
|
2122
|
+
const { ...invalidEnvVars } = error4.format();
|
2123
|
+
console.error(`
|
2124
|
+
Missing or invalid environment variables:
|
2125
|
+
|
2126
|
+
${Object.keys(invalidEnvVars).join("\n ")}
|
2127
|
+
`);
|
2128
|
+
process.exit(1);
|
2129
|
+
}
|
2130
|
+
throw error4;
|
2131
|
+
}
|
2132
|
+
}
|
2133
|
+
var mudPackages = parseEnv().MUD_PACKAGES;
|
2134
|
+
|
2135
|
+
// src/commands/set-version.ts
|
2136
|
+
var commandModule7 = {
|
2137
|
+
command: "set-version",
|
2138
|
+
describe: "Set MUD version in all package.json files and optionally backup the previously installed version",
|
2139
|
+
builder(yargs) {
|
2140
|
+
return yargs.options({
|
2141
|
+
mudVersion: { alias: "v", type: "string", description: "Set MUD to the given version" },
|
2142
|
+
tag: {
|
2143
|
+
alias: "t",
|
2144
|
+
type: "string",
|
2145
|
+
description: "Set MUD to the latest version with the given tag from npm"
|
2146
|
+
},
|
2147
|
+
commit: {
|
2148
|
+
alias: "c",
|
2149
|
+
type: "string",
|
2150
|
+
description: "Set MUD to the version based on a given git commit hash from npm"
|
2151
|
+
},
|
2152
|
+
link: { alias: "l", type: "string", description: "Relative path to the local MUD root directory to link" }
|
2153
|
+
});
|
2154
|
+
},
|
2155
|
+
async handler(options2) {
|
2156
|
+
try {
|
2157
|
+
const mutuallyExclusiveOptions = ["mudVersion", "link", "tag", "commit", "restore"];
|
2158
|
+
const numMutuallyExclusiveOptions = mutuallyExclusiveOptions.reduce(
|
2159
|
+
(acc, opt) => options2[opt] ? acc + 1 : acc,
|
2160
|
+
0
|
2161
|
+
);
|
2162
|
+
if (numMutuallyExclusiveOptions === 0) {
|
2163
|
+
throw new MUDError3(`You need to provide one these options: ${mutuallyExclusiveOptions.join(", ")}`);
|
2164
|
+
}
|
2165
|
+
if (numMutuallyExclusiveOptions > 1) {
|
2166
|
+
throw new MUDError3(`These options are mutually exclusive: ${mutuallyExclusiveOptions.join(", ")}`);
|
2167
|
+
}
|
2168
|
+
if (!options2.link) {
|
2169
|
+
options2.mudVersion = await resolveVersion(options2);
|
2170
|
+
}
|
2171
|
+
const packageJsons = globSync2("**/package.json").sort().filter((p) => !p.includes("node_modules"));
|
2172
|
+
for (const packageJson of packageJsons) {
|
2173
|
+
updatePackageJson(packageJson, options2);
|
2174
|
+
}
|
2175
|
+
} catch (e) {
|
2176
|
+
logError(e);
|
2177
|
+
} finally {
|
2178
|
+
process.exit(0);
|
2179
|
+
}
|
2180
|
+
}
|
2181
|
+
};
|
2182
|
+
async function resolveVersion(options2) {
|
2183
|
+
if (options2.mudVersion === "canary") options2.tag = "main";
|
2184
|
+
let npmResult;
|
2185
|
+
try {
|
2186
|
+
console.log(chalk3.blue(`Fetching available versions`));
|
2187
|
+
npmResult = await (await fetch(`https://registry.npmjs.org/${package_default.name}`)).json();
|
2188
|
+
} catch (e) {
|
2189
|
+
throw new MUDError3(`Could not fetch available MUD versions`);
|
2190
|
+
}
|
2191
|
+
if (options2.tag) {
|
2192
|
+
const version = npmResult["dist-tags"][options2.tag];
|
2193
|
+
if (!version) {
|
2194
|
+
throw new MUDError3(`Could not find npm version with tag "${options2.tag}"`);
|
2195
|
+
}
|
2196
|
+
console.log(chalk3.green(`Latest version with tag ${options2.tag}: ${version}`));
|
2197
|
+
return version;
|
2198
|
+
}
|
2199
|
+
if (options2.commit) {
|
2200
|
+
const commit = options2.commit.substring(0, 8);
|
2201
|
+
const version = Object.keys(npmResult["versions"]).find((v) => v.includes(commit));
|
2202
|
+
if (!version) {
|
2203
|
+
throw new MUDError3(`Could not find npm version based on commit "${options2.commit}"`);
|
2204
|
+
}
|
2205
|
+
console.log(chalk3.green(`Version from commit ${options2.commit}: ${version}`));
|
2206
|
+
return version;
|
2207
|
+
}
|
2208
|
+
return options2.mudVersion;
|
2209
|
+
}
|
2210
|
+
function updatePackageJson(filePath, options2) {
|
2211
|
+
const { link } = options2;
|
2212
|
+
let { mudVersion } = options2;
|
2213
|
+
const packageJson = readPackageJson(filePath);
|
2214
|
+
const mudPackageNames = Object.keys(mudPackages);
|
2215
|
+
const mudDependencies = {};
|
2216
|
+
for (const packageName in packageJson.dependencies) {
|
2217
|
+
if (mudPackageNames.includes(packageName)) {
|
2218
|
+
mudDependencies[packageName] = packageJson.dependencies[packageName];
|
2219
|
+
}
|
2220
|
+
}
|
2221
|
+
const mudDevDependencies = {};
|
2222
|
+
for (const packageName in packageJson.devDependencies) {
|
2223
|
+
if (mudPackageNames.includes(packageName)) {
|
2224
|
+
mudDevDependencies[packageName] = packageJson.devDependencies[packageName];
|
2225
|
+
}
|
2226
|
+
}
|
2227
|
+
for (const packageName in packageJson.dependencies) {
|
2228
|
+
if (mudPackageNames.includes(packageName)) {
|
2229
|
+
packageJson.dependencies[packageName] = resolveMudVersion(packageName, "dependencies");
|
2230
|
+
}
|
2231
|
+
}
|
2232
|
+
for (const packageName in packageJson.devDependencies) {
|
2233
|
+
if (mudPackageNames.includes(packageName)) {
|
2234
|
+
packageJson.devDependencies[packageName] = resolveMudVersion(packageName, "devDependencies");
|
2235
|
+
}
|
2236
|
+
}
|
2237
|
+
writeFileSync2(filePath, JSON.stringify(packageJson, null, 2) + "\n");
|
2238
|
+
console.log(`Updating ${filePath}`);
|
2239
|
+
logComparison(mudDependencies, packageJson.dependencies);
|
2240
|
+
logComparison(mudDevDependencies, packageJson.devDependencies);
|
2241
|
+
return packageJson;
|
2242
|
+
function resolveMudVersion(key, type) {
|
2243
|
+
if (link) mudVersion = resolveLinkPath(filePath, link, key);
|
2244
|
+
if (!mudVersion) return packageJson[type][key];
|
2245
|
+
return mudVersion;
|
2246
|
+
}
|
2247
|
+
}
|
2248
|
+
function readPackageJson(path16) {
|
2249
|
+
try {
|
2250
|
+
const jsonString = readFileSync4(path16, "utf8");
|
2251
|
+
return JSON.parse(jsonString);
|
2252
|
+
} catch {
|
2253
|
+
throw new MUDError3("Could not read JSON at " + path16);
|
2254
|
+
}
|
2255
|
+
}
|
2256
|
+
function logComparison(prev, curr) {
|
2257
|
+
for (const key in prev) {
|
2258
|
+
if (prev[key] !== curr[key]) {
|
2259
|
+
console.log(`${key}: ${chalk3.red(prev[key])} -> ${chalk3.green(curr[key])}`);
|
2260
|
+
}
|
2261
|
+
}
|
2262
|
+
}
|
2263
|
+
function resolveLinkPath(packageJsonPath, mudLinkPath, packageName) {
|
2264
|
+
const packageJsonToRootPath = path10.relative(path10.dirname(packageJsonPath), process.cwd());
|
2265
|
+
const linkPath = path10.join(packageJsonToRootPath, mudLinkPath, mudPackages[packageName].localPath);
|
2266
|
+
return "link:" + linkPath;
|
2267
|
+
}
|
2268
|
+
var set_version_default = commandModule7;
|
2269
|
+
|
2270
|
+
// src/commands/test.ts
|
2271
|
+
import { anvil, forge as forge3, getRpcUrl as getRpcUrl2 } from "@latticexyz/common/foundry";
|
2272
|
+
import chalk4 from "chalk";
|
2273
|
+
var testOptions = {
|
2274
|
+
...deployOptions,
|
2275
|
+
port: { type: "number", description: "Port to run internal node for fork testing on", default: 4242 },
|
2276
|
+
worldAddress: {
|
2277
|
+
type: "string",
|
2278
|
+
description: "Address of an existing world contract. If provided, deployment is skipped and the RPC provided in the foundry.toml is used for fork testing."
|
2279
|
+
},
|
2280
|
+
forgeOptions: { type: "string", description: "Options to pass to forge test" }
|
2281
|
+
};
|
2282
|
+
var commandModule8 = {
|
2283
|
+
command: "test",
|
2284
|
+
describe: "Run tests in MUD contracts",
|
2285
|
+
builder(yargs) {
|
2286
|
+
return yargs.options(testOptions);
|
2287
|
+
},
|
2288
|
+
async handler(opts) {
|
2289
|
+
if (!opts.worldAddress) {
|
2290
|
+
const anvilArgs = ["--block-base-fee-per-gas", "0", "--port", String(opts.port)];
|
2291
|
+
anvil(anvilArgs);
|
2292
|
+
}
|
2293
|
+
const forkRpc = opts.worldAddress ? await getRpcUrl2(opts.profile) : `http://127.0.0.1:${opts.port}`;
|
2294
|
+
const worldAddress = opts.worldAddress ?? (await runDeploy({
|
2295
|
+
...opts,
|
2296
|
+
saveDeployment: false,
|
2297
|
+
rpc: forkRpc
|
2298
|
+
})).address;
|
2299
|
+
console.log(chalk4.blue("World address", worldAddress));
|
2300
|
+
const userOptions = opts.forgeOptions?.replaceAll("\\", "").split(" ") ?? [];
|
2301
|
+
try {
|
2302
|
+
await forge3(["test", "--fork-url", forkRpc, ...userOptions], {
|
2303
|
+
profile: opts.profile,
|
2304
|
+
env: {
|
2305
|
+
WORLD_ADDRESS: worldAddress
|
2306
|
+
}
|
2307
|
+
});
|
2308
|
+
process.exit(0);
|
2309
|
+
} catch (e) {
|
2310
|
+
console.error(e);
|
2311
|
+
process.exit(1);
|
2312
|
+
}
|
2313
|
+
}
|
2314
|
+
};
|
2315
|
+
var test_default = commandModule8;
|
2316
|
+
|
2317
|
+
// src/commands/trace.ts
|
2318
|
+
import path11 from "node:path";
|
2319
|
+
import fs from "node:fs";
|
2320
|
+
import { loadConfig as loadConfig5, resolveConfigPath as resolveConfigPath5 } from "@latticexyz/config/node";
|
2321
|
+
import { MUDError as MUDError4 } from "@latticexyz/common/errors";
|
2322
|
+
import { cast, getRpcUrl as getRpcUrl3 } from "@latticexyz/common/foundry";
|
2323
|
+
import worldConfig4 from "@latticexyz/world/mud.config";
|
2324
|
+
import { createClient, http as http2 } from "viem";
|
2325
|
+
import { getChainId as getChainId2, readContract as readContract2 } from "viem/actions";
|
2326
|
+
import { resolveSystems as resolveSystems2 } from "@latticexyz/world/node";
|
2327
|
+
var systemsTableId = worldConfig4.namespaces.world.tables.Systems.tableId;
|
2328
|
+
function getWorldAddress(worldsFile, chainId) {
|
2329
|
+
if (!fs.existsSync(worldsFile)) {
|
2330
|
+
throw new MUDError4(`Missing expected worlds.json file at "${worldsFile}"`);
|
2331
|
+
}
|
2332
|
+
const deploys2 = JSON.parse(fs.readFileSync(worldsFile, "utf-8"));
|
2333
|
+
if (!deploys2[chainId]) {
|
2334
|
+
throw new MUDError4(`Missing chain ID ${chainId} in "${worldsFile}"`);
|
2335
|
+
}
|
2336
|
+
return deploys2[chainId].address;
|
2337
|
+
}
|
2338
|
+
var commandModule9 = {
|
2339
|
+
command: "trace",
|
2340
|
+
describe: "Display the trace of a transaction",
|
2341
|
+
builder(yargs) {
|
2342
|
+
return yargs.options({
|
2343
|
+
tx: { type: "string", required: true, description: "Transaction hash to replay" },
|
2344
|
+
worldAddress: {
|
2345
|
+
type: "string",
|
2346
|
+
description: "World contract address. Defaults to the value from worlds.json, based on rpc's chainId"
|
2347
|
+
},
|
2348
|
+
configPath: { type: "string", description: "Path to the MUD config file" },
|
2349
|
+
profile: { type: "string", description: "The foundry profile to use" },
|
2350
|
+
rpc: { type: "string", description: "json rpc endpoint. Defaults to foundry's configured eth_rpc_url" }
|
2351
|
+
});
|
2352
|
+
},
|
2353
|
+
async handler(args) {
|
2354
|
+
const configPath = await resolveConfigPath5(args.configPath);
|
2355
|
+
const rootDir = path11.dirname(configPath);
|
2356
|
+
const profile = args.profile;
|
2357
|
+
const rpc = args.rpc ?? await getRpcUrl3(profile);
|
2358
|
+
const config = await loadConfig5(configPath);
|
2359
|
+
const client = createClient({ transport: http2(rpc) });
|
2360
|
+
const chainId = await getChainId2(client);
|
2361
|
+
const worldAddress = args.worldAddress ?? getWorldAddress(config.deploy.worldsFile, chainId);
|
2362
|
+
const systems = await resolveSystems2({ rootDir, config });
|
2363
|
+
const labels = await Promise.all(
|
2364
|
+
systems.map(async (system) => ({
|
2365
|
+
label: system.label,
|
2366
|
+
address: await readContract2(client, {
|
2367
|
+
abi: worldAbi,
|
2368
|
+
address: worldAddress,
|
2369
|
+
functionName: "getField",
|
2370
|
+
args: [systemsTableId, [system.systemId], 0]
|
2371
|
+
})
|
2372
|
+
}))
|
2373
|
+
);
|
2374
|
+
const result = await cast([
|
2375
|
+
"run",
|
2376
|
+
"--label",
|
2377
|
+
`${worldAddress}:World`,
|
2378
|
+
...labels.map(({ label, address }) => ["--label", `${address}:${label}`]).flat(),
|
2379
|
+
`${args.tx}`
|
2380
|
+
]);
|
2381
|
+
console.log(result);
|
2382
|
+
process.exit(0);
|
2383
|
+
}
|
2384
|
+
};
|
2385
|
+
var trace_default = commandModule9;
|
2386
|
+
|
2387
|
+
// src/commands/dev-contracts.ts
|
2388
|
+
import { anvil as anvil2, getScriptDirectory as getScriptDirectory2, getSrcDirectory } from "@latticexyz/common/foundry";
|
2389
|
+
import chalk5 from "chalk";
|
2390
|
+
import chokidar from "chokidar";
|
2391
|
+
import { loadConfig as loadConfig6, resolveConfigPath as resolveConfigPath6 } from "@latticexyz/config/node";
|
2392
|
+
import path12 from "path";
|
2393
|
+
import { homedir as homedir2 } from "os";
|
2394
|
+
import { rmSync as rmSync2 } from "fs";
|
2395
|
+
import { BehaviorSubject, debounceTime, exhaustMap, filter } from "rxjs";
|
2396
|
+
import { isDefined as isDefined5 } from "@latticexyz/common/utils";
|
2397
|
+
var devOptions = {
|
2398
|
+
rpc: deployOptions.rpc,
|
2399
|
+
configPath: deployOptions.configPath,
|
2400
|
+
alwaysRunPostDeploy: deployOptions.alwaysRunPostDeploy,
|
2401
|
+
forgeScriptOptions: deployOptions.forgeScriptOptions,
|
2402
|
+
worldAddress: deployOptions.worldAddress
|
2403
|
+
};
|
2404
|
+
var commandModule10 = {
|
2405
|
+
command: "dev-contracts",
|
2406
|
+
describe: "Start a development server for MUD contracts",
|
2407
|
+
builder(yargs) {
|
2408
|
+
return yargs.options(devOptions);
|
2409
|
+
},
|
2410
|
+
async handler(opts) {
|
2411
|
+
let rpc = opts.rpc;
|
2412
|
+
const configPath = opts.configPath ?? await resolveConfigPath6(opts.configPath);
|
2413
|
+
const srcDir = await getSrcDirectory();
|
2414
|
+
const scriptDir = await getScriptDirectory2();
|
2415
|
+
const initialConfig = await loadConfig6(configPath);
|
2416
|
+
if (!opts.rpc) {
|
2417
|
+
console.log(chalk5.gray("Cleaning devnode cache"));
|
2418
|
+
const userHomeDir = homedir2();
|
2419
|
+
rmSync2(path12.join(userHomeDir, ".foundry", "anvil", "tmp"), { recursive: true, force: true });
|
2420
|
+
const anvilArgs = ["--block-time", "1", "--block-base-fee-per-gas", "0"];
|
2421
|
+
anvil2(anvilArgs);
|
2422
|
+
rpc = "http://127.0.0.1:8545";
|
2423
|
+
}
|
2424
|
+
const lastChange$ = new BehaviorSubject(Date.now());
|
2425
|
+
chokidar.watch([configPath, srcDir, scriptDir], { ignoreInitial: true }).on("all", async (_, updatePath) => {
|
2426
|
+
if (updatePath.includes(configPath)) {
|
2427
|
+
console.log(chalk5.blue("Config changed, queuing deploy\u2026"));
|
2428
|
+
lastChange$.next(Date.now());
|
2429
|
+
}
|
2430
|
+
if (updatePath.includes(srcDir) || updatePath.includes(scriptDir)) {
|
2431
|
+
if (!updatePath.includes(initialConfig.codegen.outputDirectory)) {
|
2432
|
+
console.log(chalk5.blue("Contracts changed, queuing deploy\u2026"));
|
2433
|
+
lastChange$.next(Date.now());
|
2434
|
+
}
|
2435
|
+
}
|
2436
|
+
});
|
2437
|
+
let worldAddress = opts.worldAddress;
|
2438
|
+
const deploys$ = lastChange$.pipe(
|
2439
|
+
// debounce so that a large batch of file changes only triggers a deploy after it settles down, rather than the first change it sees (and then redeploying immediately after)
|
2440
|
+
debounceTime(200),
|
2441
|
+
exhaustMap(async (lastChange) => {
|
2442
|
+
if (worldAddress) {
|
2443
|
+
console.log(chalk5.blue("Rebuilding and upgrading world\u2026"));
|
2444
|
+
}
|
2445
|
+
try {
|
2446
|
+
const deploy2 = await runDeploy({
|
2447
|
+
...opts,
|
2448
|
+
configPath,
|
2449
|
+
rpc,
|
2450
|
+
rpcBatch: false,
|
2451
|
+
skipBuild: false,
|
2452
|
+
printConfig: false,
|
2453
|
+
profile: void 0,
|
2454
|
+
saveDeployment: true,
|
2455
|
+
deployerAddress: void 0,
|
2456
|
+
worldAddress,
|
2457
|
+
salt: "0x",
|
2458
|
+
kms: void 0,
|
2459
|
+
indexerUrl: void 0
|
2460
|
+
});
|
2461
|
+
worldAddress = deploy2.address;
|
2462
|
+
if (lastChange < lastChange$.value) {
|
2463
|
+
lastChange$.next(lastChange$.value);
|
2464
|
+
} else {
|
2465
|
+
console.log(chalk5.gray("\nWaiting for file changes\u2026\n"));
|
2466
|
+
}
|
2467
|
+
return deploy2;
|
2468
|
+
} catch (error4) {
|
2469
|
+
console.error(chalk5.bgRed(chalk5.whiteBright("\n Error while attempting deploy \n")));
|
2470
|
+
console.error(error4);
|
2471
|
+
console.log(chalk5.gray("\nWaiting for file changes\u2026\n"));
|
2472
|
+
}
|
2473
|
+
}),
|
2474
|
+
filter(isDefined5)
|
2475
|
+
);
|
2476
|
+
deploys$.subscribe();
|
2477
|
+
}
|
2478
|
+
};
|
2479
|
+
var dev_contracts_default = commandModule10;
|
2480
|
+
|
2481
|
+
// src/verify.ts
|
2482
|
+
import { sliceHex, zeroHash } from "viem";
|
2483
|
+
|
2484
|
+
// src/verify/verifyContract.ts
|
2485
|
+
import { forge as forge4 } from "@latticexyz/common/foundry";
|
2486
|
+
async function verifyContract(options2) {
|
2487
|
+
const args = ["verify-contract", options2.address, options2.name, "--rpc-url", options2.rpc];
|
2488
|
+
if (options2.verifier) {
|
2489
|
+
args.push("--verifier", options2.verifier);
|
2490
|
+
}
|
2491
|
+
if (options2.verifierUrl) {
|
2492
|
+
args.push("--verifier-url", options2.verifierUrl);
|
2493
|
+
}
|
2494
|
+
await forge4(args, { cwd: options2.cwd });
|
2495
|
+
}
|
2496
|
+
|
2497
|
+
// src/verify.ts
|
2498
|
+
import PQueue from "p-queue";
|
2499
|
+
import { MUDError as MUDError5 } from "@latticexyz/common/errors";
|
2500
|
+
import { getStorageAt } from "viem/actions";
|
2501
|
+
import { execa as execa3 } from "execa";
|
2502
|
+
import { getContractAddress as getContractAddress6, getDeployer } from "@latticexyz/common/internal";
|
2503
|
+
var ERC1967_IMPLEMENTATION_SLOT = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
|
2504
|
+
async function verify({
|
2505
|
+
client,
|
2506
|
+
rpc,
|
2507
|
+
systems,
|
2508
|
+
libraries,
|
2509
|
+
modules,
|
2510
|
+
worldAddress,
|
2511
|
+
deployerAddress: initialDeployerAddress,
|
2512
|
+
verifier,
|
2513
|
+
verifierUrl
|
2514
|
+
}) {
|
2515
|
+
const deployerAddress = initialDeployerAddress ?? await getDeployer(client);
|
2516
|
+
if (!deployerAddress) {
|
2517
|
+
throw new MUDError5("No deployer address provided or found.");
|
2518
|
+
}
|
2519
|
+
const implementationStorage = await getStorageAt(client, {
|
2520
|
+
address: worldAddress,
|
2521
|
+
slot: ERC1967_IMPLEMENTATION_SLOT
|
2522
|
+
});
|
2523
|
+
const usesProxy = implementationStorage && implementationStorage !== zeroHash;
|
2524
|
+
const verifyQueue = new PQueue({ concurrency: 4 });
|
2525
|
+
const libraryMap = getLibraryMap(libraries);
|
2526
|
+
systems.map(
|
2527
|
+
(system) => verifyQueue.add(() => {
|
2528
|
+
const address = getContractAddress6({
|
2529
|
+
deployerAddress,
|
2530
|
+
bytecode: system.prepareDeploy(deployerAddress, libraryMap).bytecode
|
2531
|
+
});
|
2532
|
+
return verifyContract({
|
2533
|
+
name: system.label,
|
2534
|
+
rpc,
|
2535
|
+
verifier,
|
2536
|
+
verifierUrl,
|
2537
|
+
address
|
2538
|
+
}).catch((error4) => {
|
2539
|
+
console.error(`Error verifying system contract ${system.label}:`, error4);
|
2540
|
+
});
|
2541
|
+
})
|
2542
|
+
);
|
2543
|
+
if (verifier === "sourcify") {
|
2544
|
+
await execa3("npm", ["install"], {
|
2545
|
+
cwd: "node_modules/@latticexyz/store"
|
2546
|
+
});
|
2547
|
+
await execa3("npm", ["install"], {
|
2548
|
+
cwd: "node_modules/@latticexyz/world"
|
2549
|
+
});
|
2550
|
+
await execa3("npm", ["install"], {
|
2551
|
+
cwd: "node_modules/@latticexyz/world-modules"
|
2552
|
+
});
|
2553
|
+
Object.entries(
|
2554
|
+
usesProxy ? getWorldProxyFactoryContracts(deployerAddress) : getWorldFactoryContracts(deployerAddress)
|
2555
|
+
).map(
|
2556
|
+
([name, { bytecode }]) => verifyQueue.add(
|
2557
|
+
() => verifyContract({
|
2558
|
+
cwd: "node_modules/@latticexyz/world",
|
2559
|
+
name,
|
2560
|
+
rpc,
|
2561
|
+
verifier,
|
2562
|
+
verifierUrl,
|
2563
|
+
address: getContractAddress6({
|
2564
|
+
deployerAddress,
|
2565
|
+
bytecode
|
2566
|
+
})
|
2567
|
+
}).catch((error4) => {
|
2568
|
+
console.error(`Error verifying world factory contract ${name}:`, error4);
|
2569
|
+
})
|
2570
|
+
)
|
2571
|
+
);
|
2572
|
+
modules.map(({ name, prepareDeploy }) => {
|
2573
|
+
const { address } = prepareDeploy(deployerAddress);
|
2574
|
+
return verifyQueue.add(
|
2575
|
+
() => verifyContract({
|
2576
|
+
// TODO: figure out dir from artifactPath via import.meta.resolve?
|
2577
|
+
cwd: "node_modules/@latticexyz/world-modules",
|
2578
|
+
name,
|
2579
|
+
rpc,
|
2580
|
+
verifier,
|
2581
|
+
verifierUrl,
|
2582
|
+
address
|
2583
|
+
}).catch((error4) => {
|
2584
|
+
console.error(`Error verifying module contract ${name}:`, error4);
|
2585
|
+
})
|
2586
|
+
);
|
2587
|
+
});
|
2588
|
+
if (usesProxy) {
|
2589
|
+
const implementationAddress = sliceHex(implementationStorage, -20);
|
2590
|
+
verifyQueue.add(
|
2591
|
+
() => verifyContract({
|
2592
|
+
cwd: "node_modules/@latticexyz/world",
|
2593
|
+
name: "WorldProxy",
|
2594
|
+
rpc,
|
2595
|
+
verifier,
|
2596
|
+
verifierUrl,
|
2597
|
+
address: worldAddress
|
2598
|
+
}).catch((error4) => {
|
2599
|
+
console.error(`Error verifying WorldProxy contract:`, error4);
|
2600
|
+
})
|
2601
|
+
);
|
2602
|
+
verifyQueue.add(
|
2603
|
+
() => verifyContract({
|
2604
|
+
cwd: "node_modules/@latticexyz/world",
|
2605
|
+
name: "World",
|
2606
|
+
rpc,
|
2607
|
+
verifier,
|
2608
|
+
verifierUrl,
|
2609
|
+
address: implementationAddress
|
2610
|
+
}).catch((error4) => {
|
2611
|
+
console.error(`Error verifying World contract:`, error4);
|
2612
|
+
})
|
2613
|
+
);
|
2614
|
+
} else {
|
2615
|
+
verifyQueue.add(
|
2616
|
+
() => verifyContract({
|
2617
|
+
cwd: "node_modules/@latticexyz/world",
|
2618
|
+
name: "World",
|
2619
|
+
rpc,
|
2620
|
+
verifier,
|
2621
|
+
verifierUrl,
|
2622
|
+
address: worldAddress
|
2623
|
+
}).catch((error4) => {
|
2624
|
+
console.error(`Error verifying World contract:`, error4);
|
2625
|
+
})
|
2626
|
+
);
|
2627
|
+
}
|
2628
|
+
} else {
|
2629
|
+
console.log("");
|
2630
|
+
console.log(
|
2631
|
+
`Note: MUD is currently unable to verify store, world, and world-modules contracts with ${verifier}. We are planning to expand support in a future version.`
|
2632
|
+
);
|
2633
|
+
console.log("");
|
2634
|
+
}
|
2635
|
+
}
|
2636
|
+
|
2637
|
+
// src/commands/verify.ts
|
2638
|
+
import { loadConfig as loadConfig7, resolveConfigPath as resolveConfigPath7 } from "@latticexyz/config/node";
|
2639
|
+
import { getOutDirectory as getOutDirectory2, getRpcUrl as getRpcUrl4 } from "@latticexyz/common/foundry";
|
2640
|
+
import { createWalletClient as createWalletClient2, http as http3 } from "viem";
|
2641
|
+
import chalk6 from "chalk";
|
2642
|
+
import path13 from "node:path";
|
2643
|
+
var verifyOptions = {
|
2644
|
+
deployerAddress: {
|
2645
|
+
type: "string",
|
2646
|
+
desc: "Deploy using an existing deterministic deployer (https://github.com/Arachnid/deterministic-deployment-proxy)"
|
2647
|
+
},
|
2648
|
+
worldAddress: { type: "string", required: true, desc: "Verify an existing World at the given address" },
|
2649
|
+
configPath: { type: "string", desc: "Path to the MUD config file" },
|
2650
|
+
profile: { type: "string", desc: "The foundry profile to use" },
|
2651
|
+
rpc: { type: "string", desc: "The RPC URL to use. Defaults to the RPC url from the local foundry.toml" },
|
2652
|
+
rpcBatch: {
|
2653
|
+
type: "boolean",
|
2654
|
+
desc: "Enable batch processing of RPC requests in viem client (defaults to batch size of 100 and wait of 1s)"
|
2655
|
+
},
|
2656
|
+
verifier: { type: "string", desc: "The verifier to use. Defaults to blockscout", default: "blockscout" },
|
2657
|
+
verifierUrl: {
|
2658
|
+
type: "string",
|
2659
|
+
desc: "The verification provider."
|
2660
|
+
}
|
2661
|
+
};
|
2662
|
+
var commandModule11 = {
|
2663
|
+
command: "verify",
|
2664
|
+
describe: "Verify contracts",
|
2665
|
+
builder(yargs) {
|
2666
|
+
return yargs.options(verifyOptions);
|
2667
|
+
},
|
2668
|
+
async handler(opts) {
|
2669
|
+
const profile = opts.profile;
|
2670
|
+
const configPath = await resolveConfigPath7(opts.configPath);
|
2671
|
+
const rootDir = path13.dirname(configPath);
|
2672
|
+
const config = await loadConfig7(configPath);
|
2673
|
+
const outDir = await getOutDirectory2(profile);
|
2674
|
+
const modules = await configToModules(config, outDir);
|
2675
|
+
const { systems, libraries } = await resolveConfig({
|
2676
|
+
rootDir,
|
2677
|
+
config,
|
2678
|
+
forgeOutDir: outDir
|
2679
|
+
});
|
2680
|
+
const rpc = opts.rpc ?? await getRpcUrl4(profile);
|
2681
|
+
console.log(
|
2682
|
+
chalk6.bgBlue(
|
2683
|
+
chalk6.whiteBright(`
|
2684
|
+
Verifying MUD contracts${profile ? " with profile " + profile : ""} to RPC ${rpc}
|
2685
|
+
`)
|
2686
|
+
)
|
2687
|
+
);
|
2688
|
+
const client = createWalletClient2({
|
2689
|
+
transport: http3(rpc, {
|
2690
|
+
batch: opts.rpcBatch ? {
|
2691
|
+
batchSize: 100,
|
2692
|
+
wait: 1e3
|
2693
|
+
} : void 0
|
2694
|
+
})
|
2695
|
+
});
|
2696
|
+
await verify({
|
2697
|
+
client,
|
2698
|
+
rpc,
|
2699
|
+
systems,
|
2700
|
+
libraries,
|
2701
|
+
modules,
|
2702
|
+
deployerAddress: opts.deployerAddress,
|
2703
|
+
worldAddress: opts.worldAddress,
|
2704
|
+
verifier: opts.verifier,
|
2705
|
+
verifierUrl: opts.verifierUrl
|
2706
|
+
});
|
2707
|
+
}
|
2708
|
+
};
|
2709
|
+
var verify_default = commandModule11;
|
2710
|
+
|
2711
|
+
// src/commands/pull.ts
|
2712
|
+
import { getRpcUrl as getRpcUrl5 } from "@latticexyz/common/foundry";
|
2713
|
+
import { createClient as createClient2, http as http4 } from "viem";
|
2714
|
+
import chalk7 from "chalk";
|
2715
|
+
|
2716
|
+
// src/pull/pull.ts
|
2717
|
+
import { hexToString as hexToString2, parseAbiItem, stringToHex as stringToHex4 } from "viem";
|
2718
|
+
import { getSchemaTypes as getSchemaTypes4 } from "@latticexyz/protocol-parser/internal";
|
2719
|
+
import { hexToResource as hexToResource6, resourceToHex as resourceToHex4 } from "@latticexyz/common";
|
2720
|
+
import metadataConfig2 from "@latticexyz/world-module-metadata/mud.config";
|
2721
|
+
|
2722
|
+
// src/deploy/getRecord.ts
|
2723
|
+
import {
|
2724
|
+
decodeValueArgs as decodeValueArgs2,
|
2725
|
+
getKeyTuple as getKeyTuple2,
|
2726
|
+
getSchemaTypes as getSchemaTypes3,
|
2727
|
+
getValueSchema as getValueSchema3
|
2728
|
+
} from "@latticexyz/protocol-parser/internal";
|
2729
|
+
import { readContract as readContract3 } from "viem/actions";
|
2730
|
+
import { mapObject } from "@latticexyz/common/utils";
|
2731
|
+
async function getRecord({
|
2732
|
+
client,
|
2733
|
+
worldDeploy,
|
2734
|
+
table,
|
2735
|
+
key
|
2736
|
+
}) {
|
2737
|
+
const [staticData, encodedLengths, dynamicData] = await readContract3(client, {
|
2738
|
+
blockNumber: worldDeploy.stateBlock,
|
2739
|
+
address: worldDeploy.address,
|
2740
|
+
abi: worldAbi,
|
2741
|
+
functionName: "getRecord",
|
2742
|
+
args: [table.tableId, getKeyTuple2(table, key)]
|
2743
|
+
// TODO: remove cast once https://github.com/wevm/viem/issues/2125 is resolved
|
2744
|
+
// has something to do function overloads and TS having a hard time inferring which args to use
|
2745
|
+
});
|
2746
|
+
const record = {
|
2747
|
+
...key,
|
2748
|
+
...decodeValueArgs2(getSchemaTypes3(getValueSchema3(table)), {
|
2749
|
+
staticData,
|
2750
|
+
encodedLengths,
|
2751
|
+
dynamicData
|
2752
|
+
})
|
2753
|
+
};
|
2754
|
+
return mapObject(table.schema, (value, key2) => record[key2]);
|
2755
|
+
}
|
2756
|
+
|
2757
|
+
// src/pull/pull.ts
|
2758
|
+
import path14 from "node:path";
|
2759
|
+
import fs2 from "node:fs/promises";
|
2760
|
+
import { getFunctions as getFunctions3 } from "@latticexyz/store-sync/world";
|
2761
|
+
import { abiToInterface, formatSolidity, formatTypescript } from "@latticexyz/common/codegen";
|
2762
|
+
|
2763
|
+
// src/pull/debug.ts
|
2764
|
+
var debug3 = debug.extend("pull");
|
2765
|
+
var error3 = debug.extend("pull");
|
2766
|
+
debug3.log = console.debug.bind(console);
|
2767
|
+
error3.log = console.error.bind(console);
|
2768
|
+
|
2769
|
+
// src/pull/pull.ts
|
2770
|
+
import { defineWorld } from "@latticexyz/world";
|
2771
|
+
import { findUp as findUp3 } from "find-up";
|
2772
|
+
import { isDefined as isDefined6 } from "@latticexyz/common/utils";
|
2773
|
+
var ignoredNamespaces = /* @__PURE__ */ new Set(["store", "world", "metadata"]);
|
2774
|
+
function namespaceToHex(namespace) {
|
2775
|
+
return resourceToHex4({ type: "namespace", namespace, name: "" });
|
2776
|
+
}
|
2777
|
+
var WriteFileExistsError = class extends Error {
|
2778
|
+
constructor(filename) {
|
2779
|
+
super(`Attempted to write file at "${filename}", but it already exists.`);
|
2780
|
+
this.filename = filename;
|
2781
|
+
this.name = "WriteFileExistsError";
|
2782
|
+
}
|
2783
|
+
};
|
2784
|
+
async function pull({ rootDir, client, worldAddress, replace, indexerUrl, chainId }) {
|
2785
|
+
const replaceFiles = replace ?? await findUp3(".git", { cwd: rootDir }) != null;
|
2786
|
+
const worldDeploy = await getWorldDeploy(client, worldAddress);
|
2787
|
+
const resourceIds = await getResourceIds({ client, worldDeploy, indexerUrl, chainId });
|
2788
|
+
const resources = resourceIds.map(hexToResource6).filter((resource) => !ignoredNamespaces.has(resource.namespace));
|
2789
|
+
const tables = await getTables({ client, worldDeploy, indexerUrl, chainId });
|
2790
|
+
const labels = Object.fromEntries(
|
2791
|
+
(await Promise.all(
|
2792
|
+
resourceIds.map(async (resourceId) => {
|
2793
|
+
const { value: bytesValue } = await getRecord({
|
2794
|
+
client,
|
2795
|
+
worldDeploy,
|
2796
|
+
table: metadataConfig2.tables.metadata__ResourceTag,
|
2797
|
+
key: { resource: resourceId, tag: stringToHex4("label", { size: 32 }) }
|
2798
|
+
});
|
2799
|
+
const value = hexToString2(bytesValue);
|
2800
|
+
return [resourceId, value === "" ? null : value];
|
2801
|
+
})
|
2802
|
+
)).filter(([, label]) => label != null)
|
2803
|
+
);
|
2804
|
+
labels[namespaceToHex("")] ??= "root";
|
2805
|
+
const worldFunctions = await getFunctions3({
|
2806
|
+
client,
|
2807
|
+
worldAddress: worldDeploy.address,
|
2808
|
+
fromBlock: worldDeploy.deployBlock,
|
2809
|
+
toBlock: worldDeploy.stateBlock,
|
2810
|
+
indexerUrl,
|
2811
|
+
chainId
|
2812
|
+
});
|
2813
|
+
const namespaces = resources.filter((resource) => resource.type === "namespace");
|
2814
|
+
const systems = await Promise.all(
|
2815
|
+
resources.filter((resource) => resource.type === "system").map(async ({ namespace, name, resourceId: systemId }) => {
|
2816
|
+
const namespaceId = namespaceToHex(namespace);
|
2817
|
+
const systemLabel = labels[systemId] ?? name.replace(/(S(y(s(t(e(m)?)?)?)?)?)?$/, "System");
|
2818
|
+
const [metadataAbi2, metadataWorldAbi] = await Promise.all([
|
2819
|
+
getRecord({
|
2820
|
+
client,
|
2821
|
+
worldDeploy,
|
2822
|
+
table: metadataConfig2.tables.metadata__ResourceTag,
|
2823
|
+
key: { resource: systemId, tag: stringToHex4("abi", { size: 32 }) }
|
2824
|
+
}).then((record) => hexToString2(record.value)).then((value) => value !== "" ? value.split("\n") : []),
|
2825
|
+
getRecord({
|
2826
|
+
client,
|
2827
|
+
worldDeploy,
|
2828
|
+
table: metadataConfig2.tables.metadata__ResourceTag,
|
2829
|
+
key: { resource: systemId, tag: stringToHex4("worldAbi", { size: 32 }) }
|
2830
|
+
}).then((record) => hexToString2(record.value)).then((value) => value !== "" ? value.split("\n") : [])
|
2831
|
+
]);
|
2832
|
+
const functions = worldFunctions.filter((func) => func.systemId === systemId);
|
2833
|
+
const abi = (metadataAbi2.length ? metadataAbi2 : functions.map((func) => `function ${func.systemFunctionSignature}`)).map((sig) => {
|
2834
|
+
try {
|
2835
|
+
return parseAbiItem(sig);
|
2836
|
+
} catch {
|
2837
|
+
debug3(`Skipping invalid system signature: ${sig}`);
|
2838
|
+
}
|
2839
|
+
}).filter(isDefined6);
|
2840
|
+
const worldAbi3 = (metadataWorldAbi.length ? metadataWorldAbi : functions.map((func) => `function ${func.signature}`)).map((sig) => {
|
2841
|
+
try {
|
2842
|
+
return parseAbiItem(sig);
|
2843
|
+
} catch {
|
2844
|
+
debug3(`Skipping invalid world signature: ${sig}`);
|
2845
|
+
}
|
2846
|
+
}).filter(isDefined6);
|
2847
|
+
return {
|
2848
|
+
namespaceId,
|
2849
|
+
namespaceLabel: labels[namespaceId] ?? namespace,
|
2850
|
+
label: systemLabel,
|
2851
|
+
systemId,
|
2852
|
+
namespace,
|
2853
|
+
name,
|
2854
|
+
abi,
|
2855
|
+
worldAbi: worldAbi3
|
2856
|
+
};
|
2857
|
+
})
|
2858
|
+
);
|
2859
|
+
debug3("generating config");
|
2860
|
+
const configInput = {
|
2861
|
+
namespaces: Object.fromEntries(
|
2862
|
+
namespaces.map(({ namespace, resourceId: namespaceId }) => {
|
2863
|
+
const namespaceLabel = labels[namespaceId] ?? namespace;
|
2864
|
+
return [
|
2865
|
+
namespaceLabel,
|
2866
|
+
{
|
2867
|
+
...namespaceLabel !== namespace ? { namespace } : null,
|
2868
|
+
tables: Object.fromEntries(
|
2869
|
+
tables.filter((table) => table.namespace === namespace).map((table) => {
|
2870
|
+
const tableLabel = labels[table.tableId] ?? table.name;
|
2871
|
+
return [
|
2872
|
+
tableLabel,
|
2873
|
+
{
|
2874
|
+
...tableLabel !== table.name ? { name: table.name } : null,
|
2875
|
+
...table.type !== "table" ? { type: table.type } : null,
|
2876
|
+
schema: getSchemaTypes4(table.schema),
|
2877
|
+
key: table.key,
|
2878
|
+
deploy: { disabled: true }
|
2879
|
+
}
|
2880
|
+
];
|
2881
|
+
})
|
2882
|
+
)
|
2883
|
+
}
|
2884
|
+
];
|
2885
|
+
})
|
2886
|
+
)
|
2887
|
+
};
|
2888
|
+
debug3("validating config");
|
2889
|
+
const config = defineWorld(configInput);
|
2890
|
+
debug3("writing config");
|
2891
|
+
await writeFile(
|
2892
|
+
path14.join(rootDir, "mud.config.ts"),
|
2893
|
+
await formatTypescript(`
|
2894
|
+
import { defineWorld } from "@latticexyz/world";
|
2895
|
+
|
2896
|
+
export default defineWorld(${JSON.stringify(configInput)});
|
2897
|
+
`),
|
2898
|
+
{ overwrite: replaceFiles }
|
2899
|
+
);
|
2900
|
+
const remoteDir = path14.join(config.sourceDirectory, "remote");
|
2901
|
+
if (replaceFiles) {
|
2902
|
+
await fs2.rm(remoteDir, { recursive: true, force: true });
|
2903
|
+
}
|
2904
|
+
for (const system of systems.filter((system2) => system2.abi.length)) {
|
2905
|
+
const interfaceName = `I${system.label}`;
|
2906
|
+
const interfaceFile = path14.join(remoteDir, "namespaces", system.namespaceLabel, `${interfaceName}.sol`);
|
2907
|
+
debug3("writing system interface", interfaceName, "to", interfaceFile);
|
2908
|
+
const source = abiToInterface({ name: interfaceName, systemId: system.systemId, abi: system.abi });
|
2909
|
+
await writeFile(path14.join(rootDir, interfaceFile), await formatSolidity(source), { overwrite: replaceFiles });
|
2910
|
+
}
|
2911
|
+
const worldAbi2 = systems.flatMap((system) => system.worldAbi);
|
2912
|
+
if (worldAbi2.length) {
|
2913
|
+
const interfaceName = "IWorldSystems";
|
2914
|
+
const interfaceFile = path14.join(remoteDir, `${interfaceName}.sol`);
|
2915
|
+
debug3("writing world systems interface to", interfaceFile);
|
2916
|
+
const source = abiToInterface({ name: interfaceName, abi: worldAbi2 });
|
2917
|
+
await writeFile(path14.join(rootDir, interfaceFile), await formatSolidity(source), { overwrite: replaceFiles });
|
2918
|
+
}
|
2919
|
+
return { config };
|
2920
|
+
}
|
2921
|
+
async function exists(filename) {
|
2922
|
+
return fs2.access(filename).then(
|
2923
|
+
() => true,
|
2924
|
+
() => false
|
2925
|
+
);
|
2926
|
+
}
|
2927
|
+
async function writeFile(filename, contents, opts = {}) {
|
2928
|
+
if (!opts.overwrite && await exists(filename)) {
|
2929
|
+
throw new WriteFileExistsError(filename);
|
2930
|
+
}
|
2931
|
+
await fs2.mkdir(path14.dirname(filename), { recursive: true });
|
2932
|
+
await fs2.writeFile(filename, contents);
|
2933
|
+
}
|
2934
|
+
|
2935
|
+
// src/commands/pull.ts
|
2936
|
+
import path15 from "node:path";
|
2937
|
+
import { getChainId as getChainId3 } from "viem/actions";
|
2938
|
+
var options = {
|
2939
|
+
worldAddress: { type: "string", required: true, desc: "Remote world address" },
|
2940
|
+
profile: { type: "string", desc: "The foundry profile to use" },
|
2941
|
+
rpc: { type: "string", desc: "The RPC URL to use. Defaults to the RPC url from the local foundry.toml" },
|
2942
|
+
rpcBatch: {
|
2943
|
+
type: "boolean",
|
2944
|
+
desc: "Enable batch processing of RPC requests in viem client (defaults to batch size of 100 and wait of 1s)"
|
2945
|
+
},
|
2946
|
+
replace: {
|
2947
|
+
type: "boolean",
|
2948
|
+
desc: "Replace existing files and directories with data from remote world."
|
2949
|
+
},
|
2950
|
+
indexerUrl: {
|
2951
|
+
type: "string",
|
2952
|
+
desc: "The indexer URL to pull from.",
|
2953
|
+
required: false
|
2954
|
+
}
|
2955
|
+
};
|
2956
|
+
var commandModule12 = {
|
2957
|
+
command: "pull",
|
2958
|
+
describe: "Pull mud.config.ts and interfaces from an existing world.",
|
2959
|
+
builder(yargs) {
|
2960
|
+
return yargs.options(options);
|
2961
|
+
},
|
2962
|
+
async handler(opts) {
|
2963
|
+
const profile = opts.profile;
|
2964
|
+
const rpc = opts.rpc ?? await getRpcUrl5(profile);
|
2965
|
+
const client = createClient2({
|
2966
|
+
transport: http4(rpc, {
|
2967
|
+
batch: opts.rpcBatch ? {
|
2968
|
+
batchSize: 100,
|
2969
|
+
wait: 1e3
|
2970
|
+
} : void 0
|
2971
|
+
})
|
2972
|
+
});
|
2973
|
+
const chainId = await getChainId3(client);
|
2974
|
+
const indexerUrl = opts.indexerUrl ?? defaultChains.find((chain) => chain.id === chainId)?.indexerUrl;
|
2975
|
+
console.log(chalk7.bgBlue(chalk7.whiteBright(`
|
2976
|
+
Pulling MUD config from world at ${opts.worldAddress}
|
2977
|
+
`)));
|
2978
|
+
const rootDir = process.cwd();
|
2979
|
+
try {
|
2980
|
+
const { config } = await pull({
|
2981
|
+
rootDir,
|
2982
|
+
client,
|
2983
|
+
worldAddress: opts.worldAddress,
|
2984
|
+
indexerUrl,
|
2985
|
+
chainId,
|
2986
|
+
replace: opts.replace
|
2987
|
+
});
|
2988
|
+
await build({ rootDir, config, foundryProfile: profile });
|
2989
|
+
} catch (error4) {
|
2990
|
+
if (error4 instanceof WriteFileExistsError) {
|
2991
|
+
console.log();
|
2992
|
+
console.log(chalk7.bgRed(chalk7.whiteBright(" Error ")));
|
2993
|
+
console.log(` Attempted to write file at "${path15.relative(rootDir, error4.filename)}", but it already exists.`);
|
2994
|
+
console.log();
|
2995
|
+
console.log(" To overwrite files, use `--replace` when running this command.");
|
2996
|
+
console.log();
|
2997
|
+
return;
|
2998
|
+
}
|
2999
|
+
throw error4;
|
3000
|
+
}
|
3001
|
+
}
|
3002
|
+
};
|
3003
|
+
var pull_default = commandModule12;
|
3004
|
+
|
3005
|
+
// src/commands/index.ts
|
3006
|
+
var commands = [
|
3007
|
+
build_default,
|
3008
|
+
deploy_default,
|
3009
|
+
devnode_default,
|
3010
|
+
gasReport,
|
3011
|
+
hello_default,
|
3012
|
+
tablegen_default,
|
3013
|
+
worldgen_default,
|
3014
|
+
set_version_default,
|
3015
|
+
test_default,
|
3016
|
+
trace_default,
|
3017
|
+
dev_contracts_default,
|
3018
|
+
abiTs,
|
3019
|
+
verify_default,
|
3020
|
+
pull_default
|
3021
|
+
];
|
3022
|
+
export {
|
3023
|
+
commands
|
3024
|
+
};
|
3025
|
+
//# sourceMappingURL=commands-HW6E5GIK.js.map
|