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