@latticexyz/cli 2.0.0-main-e241c056 → 2.0.0-main-29c3f508
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/index.js +0 -1
- package/dist/mud.js +13 -13
- package/dist/mud.js.map +1 -1
- package/package.json +15 -12
- package/src/commands/deploy.ts +7 -30
- package/src/commands/dev-contracts.ts +62 -137
- package/src/commands/test.ts +26 -26
- package/src/commands/trace.ts +4 -2
- package/src/debug.ts +3 -0
- package/src/deploy/assertNamespaceOwner.ts +42 -0
- package/src/deploy/common.ts +67 -0
- package/src/deploy/configToTables.ts +60 -0
- package/src/deploy/create2/README.md +9 -0
- package/src/deploy/create2/deployment.json +7 -0
- package/src/deploy/debug.ts +3 -0
- package/src/deploy/deploy.ts +84 -0
- package/src/deploy/deployWorld.ts +33 -0
- package/src/deploy/ensureContract.ts +33 -0
- package/src/deploy/ensureDeployer.ts +36 -0
- package/src/deploy/ensureFunctions.ts +62 -0
- package/src/deploy/ensureModules.ts +48 -0
- package/src/deploy/ensureSystems.ts +128 -0
- package/src/deploy/ensureTables.ts +52 -0
- package/src/deploy/ensureWorldFactory.ts +33 -0
- package/src/deploy/getFunctions.ts +58 -0
- package/src/deploy/getResourceAccess.ts +51 -0
- package/src/deploy/getResourceIds.ts +31 -0
- package/src/deploy/getSystems.ts +48 -0
- package/src/deploy/getTableValue.ts +30 -0
- package/src/deploy/getTables.ts +59 -0
- package/src/deploy/getWorldDeploy.ts +39 -0
- package/src/deploy/logsToWorldDeploy.ts +49 -0
- package/src/deploy/resolveConfig.ts +154 -0
- package/src/deploy/resourceLabel.ts +3 -0
- package/src/index.ts +1 -1
- package/src/runDeploy.ts +131 -0
- package/src/utils/modules/constants.ts +1 -2
- package/src/utils/utils/getContractData.ts +2 -5
- package/dist/chunk-K6DUKTDI.js +0 -11
- package/dist/chunk-K6DUKTDI.js.map +0 -1
- package/src/utils/deploy.ts +0 -254
- package/src/utils/deployHandler.ts +0 -93
- package/src/utils/modules/getInstallModuleCallData.ts +0 -27
- package/src/utils/modules/getUserModules.ts +0 -5
- package/src/utils/modules/types.ts +0 -14
- package/src/utils/systems/getGrantAccessCallData.ts +0 -29
- package/src/utils/systems/getRegisterFunctionSelectorsCallData.ts +0 -57
- package/src/utils/systems/getRegisterSystemCallData.ts +0 -17
- package/src/utils/systems/types.ts +0 -9
- package/src/utils/systems/utils.ts +0 -42
- package/src/utils/tables/getRegisterTableCallData.ts +0 -49
- package/src/utils/tables/getTableIds.ts +0 -18
- package/src/utils/tables/types.ts +0 -12
- package/src/utils/utils/confirmNonce.ts +0 -24
- package/src/utils/utils/deployContract.ts +0 -33
- package/src/utils/utils/fastTxExecute.ts +0 -56
- package/src/utils/utils/getChainId.ts +0 -10
- package/src/utils/utils/setInternalFeePerGas.ts +0 -49
- package/src/utils/utils/types.ts +0 -21
- package/src/utils/world.ts +0 -28
package/src/commands/deploy.ts
CHANGED
|
@@ -1,43 +1,20 @@
|
|
|
1
|
-
import type { CommandModule
|
|
1
|
+
import type { CommandModule } from "yargs";
|
|
2
2
|
import { logError } from "../utils/errors";
|
|
3
|
-
import { DeployOptions,
|
|
3
|
+
import { DeployOptions, deployOptions, runDeploy } from "../runDeploy";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
configPath: { type: "string", desc: "Path to the config file" },
|
|
7
|
-
clean: { type: "boolean", desc: "Remove the build forge artifacts and cache directories before building" },
|
|
8
|
-
printConfig: { type: "boolean", desc: "Print the resolved config" },
|
|
9
|
-
profile: { type: "string", desc: "The foundry profile to use" },
|
|
10
|
-
debug: { type: "boolean", desc: "Print debug logs, like full error messages" },
|
|
11
|
-
priorityFeeMultiplier: {
|
|
12
|
-
type: "number",
|
|
13
|
-
desc: "Multiply the estimated priority fee by the provided factor",
|
|
14
|
-
default: 1,
|
|
15
|
-
},
|
|
16
|
-
saveDeployment: { type: "boolean", desc: "Save the deployment info to a file", default: true },
|
|
17
|
-
rpc: { type: "string", desc: "The RPC URL to use. Defaults to the RPC url from the local foundry.toml" },
|
|
18
|
-
worldAddress: { type: "string", desc: "Deploy to an existing World at the given address" },
|
|
19
|
-
srcDir: { type: "string", desc: "Source directory. Defaults to foundry src directory." },
|
|
20
|
-
disableTxWait: { type: "boolean", desc: "Disable waiting for transactions to be confirmed.", default: false },
|
|
21
|
-
pollInterval: {
|
|
22
|
-
type: "number",
|
|
23
|
-
desc: "Interval in miliseconds to use to poll for transaction receipts / block inclusion",
|
|
24
|
-
default: 1000,
|
|
25
|
-
},
|
|
26
|
-
skipBuild: { type: "boolean", desc: "Skip rebuilding the contracts before deploying" },
|
|
27
|
-
} satisfies Record<keyof DeployOptions, Options>;
|
|
28
|
-
|
|
29
|
-
const commandModule: CommandModule<DeployOptions, DeployOptions> = {
|
|
5
|
+
const commandModule: CommandModule<typeof deployOptions, DeployOptions> = {
|
|
30
6
|
command: "deploy",
|
|
31
7
|
|
|
32
8
|
describe: "Deploy MUD contracts",
|
|
33
9
|
|
|
34
10
|
builder(yargs) {
|
|
35
|
-
return yargs.options(
|
|
11
|
+
return yargs.options(deployOptions);
|
|
36
12
|
},
|
|
37
13
|
|
|
38
|
-
async handler(
|
|
14
|
+
async handler(opts) {
|
|
15
|
+
// Wrap in try/catch, because yargs seems to swallow errors
|
|
39
16
|
try {
|
|
40
|
-
await
|
|
17
|
+
await runDeploy(opts);
|
|
41
18
|
} catch (error: any) {
|
|
42
19
|
logError(error);
|
|
43
20
|
process.exit(1);
|
|
@@ -1,176 +1,101 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs";
|
|
2
|
-
import {
|
|
3
|
-
anvil,
|
|
4
|
-
forge,
|
|
5
|
-
getRemappings,
|
|
6
|
-
getRpcUrl,
|
|
7
|
-
getScriptDirectory,
|
|
8
|
-
getSrcDirectory,
|
|
9
|
-
} from "@latticexyz/common/foundry";
|
|
1
|
+
import type { CommandModule, InferredOptionTypes } from "yargs";
|
|
2
|
+
import { anvil, getScriptDirectory, getSrcDirectory } from "@latticexyz/common/foundry";
|
|
10
3
|
import chalk from "chalk";
|
|
11
4
|
import chokidar from "chokidar";
|
|
12
5
|
import { loadConfig, resolveConfigPath } from "@latticexyz/config/node";
|
|
13
6
|
import { StoreConfig } from "@latticexyz/store";
|
|
14
|
-
import { tablegen } from "@latticexyz/store/codegen";
|
|
15
7
|
import path from "path";
|
|
16
|
-
import { debounce } from "throttle-debounce";
|
|
17
|
-
import { worldgenHandler } from "./worldgen";
|
|
18
8
|
import { WorldConfig } from "@latticexyz/world";
|
|
19
9
|
import { homedir } from "os";
|
|
20
10
|
import { rmSync } from "fs";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { printMUD } from "../utils/printMUD";
|
|
11
|
+
import { deployOptions, runDeploy } from "../runDeploy";
|
|
12
|
+
import { BehaviorSubject, debounceTime, exhaustMap } from "rxjs";
|
|
13
|
+
import { Address } from "viem";
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
rpc
|
|
28
|
-
configPath
|
|
15
|
+
const devOptions = {
|
|
16
|
+
rpc: deployOptions.rpc,
|
|
17
|
+
configPath: deployOptions.configPath,
|
|
29
18
|
};
|
|
30
19
|
|
|
31
|
-
const commandModule: CommandModule<
|
|
20
|
+
const commandModule: CommandModule<typeof devOptions, InferredOptionTypes<typeof devOptions>> = {
|
|
32
21
|
command: "dev-contracts",
|
|
33
22
|
|
|
34
23
|
describe: "Start a development server for MUD contracts",
|
|
35
24
|
|
|
36
25
|
builder(yargs) {
|
|
37
|
-
return yargs.options(
|
|
38
|
-
rpc: {
|
|
39
|
-
type: "string",
|
|
40
|
-
decs: "RPC endpoint of the development node. If none is provided, an anvil instance is spawned in the background on port 8545.",
|
|
41
|
-
},
|
|
42
|
-
configPath: {
|
|
43
|
-
type: "string",
|
|
44
|
-
decs: "Path to MUD config",
|
|
45
|
-
},
|
|
46
|
-
});
|
|
26
|
+
return yargs.options(devOptions);
|
|
47
27
|
},
|
|
48
28
|
|
|
49
|
-
async handler(
|
|
50
|
-
|
|
51
|
-
await
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
const configPath = args.configPath ?? (await resolveConfigPath(args.configPath));
|
|
55
|
-
const srcDirectory = await getSrcDirectory();
|
|
56
|
-
const scriptDirectory = await getScriptDirectory();
|
|
57
|
-
const remappings = await getRemappings();
|
|
29
|
+
async handler(opts) {
|
|
30
|
+
let rpc = opts.rpc;
|
|
31
|
+
const configPath = opts.configPath ?? (await resolveConfigPath(opts.configPath));
|
|
32
|
+
const srcDir = await getSrcDirectory();
|
|
33
|
+
const scriptDir = await getScriptDirectory();
|
|
58
34
|
const initialConfig = (await loadConfig(configPath)) as StoreConfig & WorldConfig;
|
|
59
35
|
|
|
60
|
-
// Initial run of all codegen steps before starting anvil
|
|
61
|
-
// (so clients can wait for everything to be ready before starting)
|
|
62
|
-
await handleConfigChange(initialConfig);
|
|
63
|
-
await handleContractsChange(initialConfig);
|
|
64
|
-
|
|
65
36
|
// Start an anvil instance in the background if no RPC url is provided
|
|
66
|
-
if (!
|
|
37
|
+
if (!opts.rpc) {
|
|
38
|
+
// Clean anvil cache as 1s block times can fill up the disk
|
|
39
|
+
// - https://github.com/foundry-rs/foundry/issues/3623
|
|
40
|
+
// - https://github.com/foundry-rs/foundry/issues/4989
|
|
41
|
+
// - https://github.com/foundry-rs/foundry/issues/3699
|
|
42
|
+
// - https://github.com/foundry-rs/foundry/issues/3512
|
|
67
43
|
console.log(chalk.gray("Cleaning devnode cache"));
|
|
68
44
|
const userHomeDir = homedir();
|
|
69
45
|
rmSync(path.join(userHomeDir, ".foundry", "anvil", "tmp"), { recursive: true, force: true });
|
|
70
46
|
|
|
71
47
|
const anvilArgs = ["--block-time", "1", "--block-base-fee-per-gas", "0"];
|
|
72
48
|
anvil(anvilArgs);
|
|
49
|
+
rpc = "http://127.0.0.1:8545";
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
const changedSinceLastHandled = {
|
|
76
|
-
config: false,
|
|
77
|
-
contracts: false,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const changeInProgress = {
|
|
81
|
-
current: false,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
52
|
// Watch for changes
|
|
85
|
-
const
|
|
86
|
-
|
|
53
|
+
const lastChange$ = new BehaviorSubject<number>(Date.now());
|
|
54
|
+
chokidar.watch([configPath, srcDir, scriptDir]).on("all", async (_, updatePath) => {
|
|
87
55
|
if (updatePath.includes(configPath)) {
|
|
88
|
-
|
|
89
|
-
// We trigger contract changes if the config changed here instead of
|
|
90
|
-
// listening to changes in the codegen directory to avoid an infinite loop
|
|
91
|
-
changedSinceLastHandled.contracts = true;
|
|
56
|
+
lastChange$.next(Date.now());
|
|
92
57
|
}
|
|
93
|
-
|
|
94
|
-
if (updatePath.includes(srcDirectory) || updatePath.includes(scriptDirectory)) {
|
|
58
|
+
if (updatePath.includes(srcDir) || updatePath.includes(scriptDir)) {
|
|
95
59
|
// Ignore changes to codegen files to avoid an infinite loop
|
|
96
|
-
if (updatePath.includes(initialConfig.codegenDirectory))
|
|
97
|
-
|
|
60
|
+
if (!updatePath.includes(initialConfig.codegenDirectory)) {
|
|
61
|
+
lastChange$.next(Date.now());
|
|
62
|
+
}
|
|
98
63
|
}
|
|
99
|
-
|
|
100
|
-
// Trigger debounced onChange
|
|
101
|
-
handleChange();
|
|
102
64
|
});
|
|
103
65
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
/** Codegen to run if config changes */
|
|
139
|
-
async function handleConfigChange(config: StoreConfig & WorldConfig) {
|
|
140
|
-
console.log(chalk.blue("mud.config.ts changed - regenerating tables and recs types"));
|
|
141
|
-
// Run tablegen to generate tables based on the config
|
|
142
|
-
const outPath = path.join(srcDirectory, config.codegenDirectory);
|
|
143
|
-
await tablegen(config, outPath, remappings);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Codegen to run if contracts changed */
|
|
147
|
-
async function handleContractsChange(config: StoreConfig & WorldConfig) {
|
|
148
|
-
console.log(chalk.blue("contracts changed - regenerating interfaces and contract types"));
|
|
149
|
-
|
|
150
|
-
// Run worldgen to generate interfaces based on the systems
|
|
151
|
-
await worldgenHandler({ config, clean: true, srcDir: srcDirectory });
|
|
152
|
-
|
|
153
|
-
// Build the contracts
|
|
154
|
-
await forge(["build", "--skip", "test", "script"]);
|
|
155
|
-
|
|
156
|
-
// Generate TS type definitions for ABIs
|
|
157
|
-
await execa("mud", ["abi-ts"], { stdio: "inherit" });
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** Run after codegen if either mud config or contracts changed */
|
|
161
|
-
async function deploy() {
|
|
162
|
-
console.log(chalk.blue("redeploying World"));
|
|
163
|
-
await deployHandler({
|
|
164
|
-
configPath,
|
|
165
|
-
skipBuild: true,
|
|
166
|
-
priorityFeeMultiplier: 1,
|
|
167
|
-
disableTxWait: true,
|
|
168
|
-
pollInterval: 1000,
|
|
169
|
-
saveDeployment: true,
|
|
170
|
-
srcDir: srcDirectory,
|
|
171
|
-
rpc,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
66
|
+
let worldAddress: Address | undefined;
|
|
67
|
+
|
|
68
|
+
const deploys$ = lastChange$.pipe(
|
|
69
|
+
// 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)
|
|
70
|
+
debounceTime(200),
|
|
71
|
+
exhaustMap(async (lastChange) => {
|
|
72
|
+
if (worldAddress) {
|
|
73
|
+
console.log(chalk.blue("Change detected, rebuilding and running deploy..."));
|
|
74
|
+
}
|
|
75
|
+
// TODO: handle errors
|
|
76
|
+
const deploy = await runDeploy({
|
|
77
|
+
configPath,
|
|
78
|
+
rpc,
|
|
79
|
+
clean: true,
|
|
80
|
+
skipBuild: false,
|
|
81
|
+
printConfig: false,
|
|
82
|
+
profile: undefined,
|
|
83
|
+
saveDeployment: true,
|
|
84
|
+
worldAddress,
|
|
85
|
+
srcDir,
|
|
86
|
+
});
|
|
87
|
+
worldAddress = deploy.address;
|
|
88
|
+
// if there were changes while we were deploying, trigger it again
|
|
89
|
+
if (lastChange < lastChange$.value) {
|
|
90
|
+
lastChange$.next(lastChange$.value);
|
|
91
|
+
} else {
|
|
92
|
+
console.log(chalk.gray("Watching for file changes..."));
|
|
93
|
+
}
|
|
94
|
+
return deploy;
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
deploys$.subscribe();
|
|
174
99
|
},
|
|
175
100
|
};
|
|
176
101
|
|
package/src/commands/test.ts
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs";
|
|
1
|
+
import type { CommandModule, InferredOptionTypes, Options } from "yargs";
|
|
2
2
|
import { anvil, forge, getRpcUrl } from "@latticexyz/common/foundry";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import {
|
|
5
|
-
import { yDeployOptions } from "./deploy";
|
|
6
|
-
import { DeployOptions, deployHandler } from "../utils/deployHandler";
|
|
4
|
+
import { deployOptions, runDeploy } from "../runDeploy";
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
const testOptions = {
|
|
7
|
+
...deployOptions,
|
|
8
|
+
port: { type: "number", description: "Port to run internal node for fork testing on", default: 4242 },
|
|
9
|
+
worldAddress: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description:
|
|
12
|
+
"Address of an existing world contract. If provided, deployment is skipped and the RPC provided in the foundry.toml is used for fork testing.",
|
|
13
|
+
},
|
|
14
|
+
forgeOptions: { type: "string", description: "Options to pass to forge test" },
|
|
15
|
+
} as const satisfies Record<string, Options>;
|
|
16
|
+
|
|
17
|
+
type TestOptions = InferredOptionTypes<typeof testOptions>;
|
|
9
18
|
|
|
10
|
-
const commandModule: CommandModule<
|
|
19
|
+
const commandModule: CommandModule<typeof testOptions, TestOptions> = {
|
|
11
20
|
command: "test",
|
|
12
21
|
|
|
13
22
|
describe: "Run tests in MUD contracts",
|
|
14
23
|
|
|
15
24
|
builder(yargs) {
|
|
16
|
-
return yargs.options(
|
|
17
|
-
...yDeployOptions,
|
|
18
|
-
port: { type: "number", description: "Port to run internal node for fork testing on", default: 4242 },
|
|
19
|
-
worldAddress: {
|
|
20
|
-
type: "string",
|
|
21
|
-
description:
|
|
22
|
-
"Address of an existing world contract. If provided, deployment is skipped and the RPC provided in the foundry.toml is used for fork testing.",
|
|
23
|
-
},
|
|
24
|
-
forgeOptions: { type: "string", description: "Options to pass to forge test" },
|
|
25
|
-
});
|
|
25
|
+
return yargs.options(testOptions);
|
|
26
26
|
},
|
|
27
27
|
|
|
28
|
-
async handler(
|
|
28
|
+
async handler(opts) {
|
|
29
29
|
// Start an internal anvil process if no world address is provided
|
|
30
|
-
if (!
|
|
31
|
-
const anvilArgs = ["--block-base-fee-per-gas", "0", "--port", String(
|
|
30
|
+
if (!opts.worldAddress) {
|
|
31
|
+
const anvilArgs = ["--block-base-fee-per-gas", "0", "--port", String(opts.port)];
|
|
32
32
|
anvil(anvilArgs);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const forkRpc =
|
|
35
|
+
const forkRpc = opts.worldAddress ? await getRpcUrl(opts.profile) : `http://127.0.0.1:${opts.port}`;
|
|
36
36
|
|
|
37
37
|
const worldAddress =
|
|
38
|
-
|
|
38
|
+
opts.worldAddress ??
|
|
39
39
|
(
|
|
40
|
-
await
|
|
41
|
-
...
|
|
40
|
+
await runDeploy({
|
|
41
|
+
...opts,
|
|
42
42
|
saveDeployment: false,
|
|
43
43
|
rpc: forkRpc,
|
|
44
44
|
})
|
|
45
|
-
).
|
|
45
|
+
).address;
|
|
46
46
|
|
|
47
47
|
console.log(chalk.blue("World address", worldAddress));
|
|
48
48
|
|
|
49
|
-
const userOptions =
|
|
49
|
+
const userOptions = opts.forgeOptions?.replaceAll("\\", "").split(" ") ?? [];
|
|
50
50
|
try {
|
|
51
51
|
await forge(["test", "--fork-url", forkRpc, ...userOptions], {
|
|
52
|
-
profile:
|
|
52
|
+
profile: opts.profile,
|
|
53
53
|
env: {
|
|
54
54
|
WORLD_ADDRESS: worldAddress,
|
|
55
55
|
},
|
package/src/commands/trace.ts
CHANGED
|
@@ -11,7 +11,8 @@ import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.j
|
|
|
11
11
|
import worldConfig from "@latticexyz/world/mud.config.js";
|
|
12
12
|
import { resourceToHex } from "@latticexyz/common";
|
|
13
13
|
import { getExistingContracts } from "../utils/getExistingContracts";
|
|
14
|
-
import {
|
|
14
|
+
import { createClient, http } from "viem";
|
|
15
|
+
import { getChainId } from "viem/actions";
|
|
15
16
|
|
|
16
17
|
// TODO account for multiple namespaces (https://github.com/latticexyz/mud/issues/994)
|
|
17
18
|
const systemsTableId = resourceToHex({
|
|
@@ -103,7 +104,8 @@ export default commandModule;
|
|
|
103
104
|
|
|
104
105
|
async function getWorldAddress(worldsFile: string, rpc: string) {
|
|
105
106
|
if (existsSync(worldsFile)) {
|
|
106
|
-
const
|
|
107
|
+
const client = createClient({ transport: http(rpc) });
|
|
108
|
+
const chainId = await getChainId(client);
|
|
107
109
|
const deploys = JSON.parse(readFileSync(worldsFile, "utf-8"));
|
|
108
110
|
|
|
109
111
|
if (!deploys[chainId]) {
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Account, Chain, Client, Hex, Transport, getAddress } from "viem";
|
|
2
|
+
import { WorldDeploy, worldTables } from "./common";
|
|
3
|
+
import { hexToResource, resourceToHex } from "@latticexyz/common";
|
|
4
|
+
import { getResourceIds } from "./getResourceIds";
|
|
5
|
+
import { getTableValue } from "./getTableValue";
|
|
6
|
+
|
|
7
|
+
export async function assertNamespaceOwner({
|
|
8
|
+
client,
|
|
9
|
+
worldDeploy,
|
|
10
|
+
resourceIds,
|
|
11
|
+
}: {
|
|
12
|
+
readonly client: Client<Transport, Chain | undefined, Account>;
|
|
13
|
+
readonly worldDeploy: WorldDeploy;
|
|
14
|
+
readonly resourceIds: readonly Hex[];
|
|
15
|
+
}): Promise<void> {
|
|
16
|
+
const desiredNamespaces = Array.from(new Set(resourceIds.map((resourceId) => hexToResource(resourceId).namespace)));
|
|
17
|
+
const existingResourceIds = await getResourceIds({ client, worldDeploy });
|
|
18
|
+
const existingNamespaces = Array.from(
|
|
19
|
+
new Set(existingResourceIds.map((resourceId) => hexToResource(resourceId).namespace))
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const namespaces = desiredNamespaces.filter((namespace) => existingNamespaces.includes(namespace));
|
|
23
|
+
const namespaceOwners = await Promise.all(
|
|
24
|
+
namespaces.map(async (namespace) => {
|
|
25
|
+
const { owner } = await getTableValue({
|
|
26
|
+
client,
|
|
27
|
+
worldDeploy,
|
|
28
|
+
table: worldTables.world_NamespaceOwner,
|
|
29
|
+
key: { namespaceId: resourceToHex({ type: "namespace", namespace, name: "" }) },
|
|
30
|
+
});
|
|
31
|
+
return [namespace, owner];
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const unauthorizedNamespaces = namespaceOwners
|
|
36
|
+
.filter(([, owner]) => getAddress(owner) !== getAddress(client.account.address))
|
|
37
|
+
.map(([namespace]) => namespace);
|
|
38
|
+
|
|
39
|
+
if (unauthorizedNamespaces.length) {
|
|
40
|
+
throw new Error(`You are attempting to deploy to namespaces you do not own: ${unauthorizedNamespaces.join(", ")}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Abi, Address, Hex, padHex } from "viem";
|
|
2
|
+
import storeConfig from "@latticexyz/store/mud.config.js";
|
|
3
|
+
import worldConfig from "@latticexyz/world/mud.config.js";
|
|
4
|
+
import IBaseWorldAbi from "@latticexyz/world/out/IBaseWorld.sol/IBaseWorld.abi.json" assert { type: "json" };
|
|
5
|
+
import { Tables, configToTables } from "./configToTables";
|
|
6
|
+
import { StoreConfig, helloStoreEvent } from "@latticexyz/store";
|
|
7
|
+
import { WorldConfig, helloWorldEvent } from "@latticexyz/world";
|
|
8
|
+
|
|
9
|
+
export const salt = padHex("0x", { size: 32 });
|
|
10
|
+
|
|
11
|
+
// TODO: add `as const` to mud config so these get more strongly typed (blocked by current config parsing not using readonly)
|
|
12
|
+
export const storeTables = configToTables(storeConfig);
|
|
13
|
+
export const worldTables = configToTables(worldConfig);
|
|
14
|
+
|
|
15
|
+
export const worldDeployEvents = [helloStoreEvent, helloWorldEvent] as const;
|
|
16
|
+
|
|
17
|
+
export const worldAbi = IBaseWorldAbi;
|
|
18
|
+
|
|
19
|
+
export type WorldDeploy = {
|
|
20
|
+
readonly address: Address;
|
|
21
|
+
readonly worldVersion: string;
|
|
22
|
+
readonly storeVersion: string;
|
|
23
|
+
/** Block number where the world was deployed */
|
|
24
|
+
readonly deployBlock: bigint;
|
|
25
|
+
/** Block number at the time of fetching world deploy. We use this block number when requesting data from the chain to align chain state with the same block during the introspection steps of the deploy. */
|
|
26
|
+
readonly stateBlock: bigint;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type WorldFunction = {
|
|
30
|
+
readonly signature: string;
|
|
31
|
+
readonly selector: Hex;
|
|
32
|
+
readonly systemId: Hex;
|
|
33
|
+
readonly systemFunctionSignature: string;
|
|
34
|
+
readonly systemFunctionSelector: Hex;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type System = {
|
|
38
|
+
readonly namespace: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly systemId: Hex;
|
|
41
|
+
readonly allowAll: boolean;
|
|
42
|
+
readonly allowedAddresses: readonly Hex[];
|
|
43
|
+
readonly address: Address;
|
|
44
|
+
readonly bytecode: Hex;
|
|
45
|
+
readonly abi: Abi;
|
|
46
|
+
readonly functions: readonly WorldFunction[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type Module = {
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly installAsRoot: boolean;
|
|
52
|
+
readonly installData: Hex; // TODO: figure out better naming for this
|
|
53
|
+
readonly address: Address;
|
|
54
|
+
readonly bytecode: Hex;
|
|
55
|
+
readonly abi: Abi;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ConfigInput = StoreConfig & WorldConfig;
|
|
59
|
+
export type Config<config extends ConfigInput> = {
|
|
60
|
+
readonly tables: Tables<config>;
|
|
61
|
+
readonly systems: readonly System[];
|
|
62
|
+
readonly modules: readonly Module[];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Ideally, this should be an append-only list. Before adding more versions here, be sure to add backwards-compatible support for old Store/World versions.
|
|
66
|
+
export const supportedStoreVersions = ["1.0.0-unaudited"];
|
|
67
|
+
export const supportedWorldVersions = ["1.0.0-unaudited"];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { resourceToHex } from "@latticexyz/common";
|
|
2
|
+
import { KeySchema, ValueSchema } from "@latticexyz/protocol-parser";
|
|
3
|
+
import { SchemaAbiType, StaticAbiType } from "@latticexyz/schema-type";
|
|
4
|
+
import { StoreConfig, resolveUserTypes } from "@latticexyz/store";
|
|
5
|
+
import { Hex } from "viem";
|
|
6
|
+
|
|
7
|
+
// TODO: we shouldn't need this file once our config parsing returns nicely formed tables
|
|
8
|
+
|
|
9
|
+
export type TableKey<
|
|
10
|
+
config extends StoreConfig = StoreConfig,
|
|
11
|
+
table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]]
|
|
12
|
+
> = `${config["namespace"]}_${table["name"]}`;
|
|
13
|
+
|
|
14
|
+
export type Table<
|
|
15
|
+
config extends StoreConfig = StoreConfig,
|
|
16
|
+
table extends config["tables"][keyof config["tables"]] = config["tables"][keyof config["tables"]]
|
|
17
|
+
> = {
|
|
18
|
+
readonly namespace: config["namespace"];
|
|
19
|
+
readonly name: table["name"];
|
|
20
|
+
readonly tableId: Hex;
|
|
21
|
+
// TODO: support enums
|
|
22
|
+
readonly keySchema: table["keySchema"] extends KeySchema<config["userTypes"]>
|
|
23
|
+
? KeySchema & {
|
|
24
|
+
readonly [k in keyof table["keySchema"]]: config["userTypes"][table["keySchema"][k]]["internalType"] extends StaticAbiType
|
|
25
|
+
? config["userTypes"][table["keySchema"][k]]["internalType"]
|
|
26
|
+
: table["keySchema"][k];
|
|
27
|
+
}
|
|
28
|
+
: KeySchema;
|
|
29
|
+
// TODO: support enums
|
|
30
|
+
readonly valueSchema: table["valueSchema"] extends ValueSchema<config["userTypes"]>
|
|
31
|
+
? {
|
|
32
|
+
readonly [k in keyof table["valueSchema"]]: config["userTypes"][table["valueSchema"][k]]["internalType"] extends SchemaAbiType
|
|
33
|
+
? config["userTypes"][table["valueSchema"][k]]["internalType"]
|
|
34
|
+
: table["valueSchema"][k];
|
|
35
|
+
}
|
|
36
|
+
: ValueSchema;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type Tables<config extends StoreConfig = StoreConfig> = {
|
|
40
|
+
readonly [k in keyof config["tables"] as TableKey<config, config["tables"][k]>]: Table<config, config["tables"][k]>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function configToTables<config extends StoreConfig>(config: config): Tables<config> {
|
|
44
|
+
return Object.fromEntries(
|
|
45
|
+
Object.entries(config.tables).map(([tableName, table]) => [
|
|
46
|
+
`${config.namespace}_${tableName}` satisfies TableKey<config, config["tables"][keyof config["tables"]]>,
|
|
47
|
+
{
|
|
48
|
+
namespace: config.namespace,
|
|
49
|
+
name: table.name,
|
|
50
|
+
tableId: resourceToHex({
|
|
51
|
+
type: table.offchainOnly ? "offchainTable" : "table",
|
|
52
|
+
namespace: config.namespace,
|
|
53
|
+
name: table.name,
|
|
54
|
+
}),
|
|
55
|
+
keySchema: resolveUserTypes(table.keySchema, config.userTypes) as any,
|
|
56
|
+
valueSchema: resolveUserTypes(table.valueSchema, config.userTypes) as any,
|
|
57
|
+
} satisfies Table<config, config["tables"][keyof config["tables"]]>,
|
|
58
|
+
])
|
|
59
|
+
) as Tables<config>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"gasPrice": 100000000000,
|
|
3
|
+
"gasLimit": 100000,
|
|
4
|
+
"signerAddress": "3fab184622dc19b6109349b94811493bf2a45362",
|
|
5
|
+
"transaction": "f8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222",
|
|
6
|
+
"address": "4e59b44847b379578588920ca78fbf26c0b4956c"
|
|
7
|
+
}
|