@latticexyz/cli 1.41.0 → 1.41.1-alpha.41
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-GR245KYP.js → chunk-J4DJQNIC.js} +679 -133
- package/dist/chunk-O57QENJ6.js +23039 -0
- package/dist/config/index.d.ts +610 -296
- package/dist/config/index.js +49 -26
- package/dist/index.d.ts +2 -110
- package/dist/index.js +9 -50
- package/dist/mud.js +937 -57
- package/dist/utils/index.d.ts +92 -4
- package/dist/utils/index.js +2 -3
- package/package.json +10 -9
- package/src/commands/deploy-v2.ts +11 -15
- package/src/commands/index.ts +2 -0
- package/src/commands/worldgen.ts +55 -0
- package/src/config/commonSchemas.ts +11 -13
- package/src/config/dynamicResolution.ts +49 -0
- package/src/config/index.ts +15 -3
- package/src/config/loadStoreConfig.ts +1 -1
- package/src/config/parseStoreConfig.test-d.ts +31 -5
- package/src/config/parseStoreConfig.ts +218 -78
- package/src/config/validation.ts +25 -0
- package/src/config/world/index.ts +4 -0
- package/src/config/{loadWorldConfig.test-d.ts → world/loadWorldConfig.test-d.ts} +3 -3
- package/src/config/world/loadWorldConfig.ts +26 -0
- package/src/config/world/parseWorldConfig.ts +55 -0
- package/src/config/world/resolveWorldConfig.ts +80 -0
- package/src/config/world/userTypes.ts +72 -0
- package/src/index.ts +4 -6
- package/src/render-solidity/common.ts +51 -6
- package/src/render-solidity/field.ts +40 -44
- package/src/render-solidity/index.ts +5 -1
- package/src/render-solidity/record.ts +56 -73
- package/src/render-solidity/renderSystemInterface.ts +31 -0
- package/src/render-solidity/renderTable.ts +98 -70
- package/src/render-solidity/renderTypeHelpers.ts +99 -0
- package/src/render-solidity/renderTypesFromConfig.ts +2 -2
- package/src/render-solidity/renderWorld.ts +24 -0
- package/src/render-solidity/{renderTablesFromConfig.ts → tableOptions.ts} +28 -30
- package/src/render-solidity/tablegen.ts +20 -22
- package/src/render-solidity/types.ts +39 -5
- package/src/render-solidity/userType.ts +80 -48
- package/src/render-solidity/worldgen.ts +60 -0
- package/src/utils/contractToInterface.ts +130 -0
- package/src/utils/deploy-v2.ts +268 -101
- package/src/utils/formatAndWrite.ts +12 -0
- package/src/utils/getChainId.ts +10 -0
- package/src/utils/typeUtils.ts +17 -0
- package/dist/chunk-AER7UDD4.js +0 -0
- package/dist/chunk-XRS7KWBZ.js +0 -547
- package/dist/chunk-YZATC2M3.js +0 -397
- package/dist/chunk-ZYDMYSTH.js +0 -1178
- package/dist/deploy-v2-b7b3207d.d.ts +0 -92
- package/src/config/loadWorldConfig.ts +0 -178
- package/src/constants.ts +0 -1
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { parse, visit } from "@solidity-parser/parser";
|
|
2
|
+
import { TypeName, VariableDeclaration } from "@solidity-parser/parser/dist/src/ast-types.js";
|
|
3
|
+
import { RenderSystemInterfaceFunction } from "../render-solidity/types.js";
|
|
4
|
+
import { MUDError } from "./errors.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse the contract data to get the functions necessary to generate an interface,
|
|
8
|
+
* and symbols to import from the original contract.
|
|
9
|
+
* @param data contents of a file with the solidity contract
|
|
10
|
+
* @param contractName name of the contract
|
|
11
|
+
* @returns interface data
|
|
12
|
+
*/
|
|
13
|
+
export function contractToInterface(data: string, contractName: string) {
|
|
14
|
+
const ast = parse(data);
|
|
15
|
+
|
|
16
|
+
let withContract = false;
|
|
17
|
+
let symbols: string[] = [];
|
|
18
|
+
const functions: RenderSystemInterfaceFunction[] = [];
|
|
19
|
+
|
|
20
|
+
visit(ast, {
|
|
21
|
+
ContractDefinition({ name }) {
|
|
22
|
+
if (name === contractName) {
|
|
23
|
+
withContract = true;
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
FunctionDefinition(
|
|
27
|
+
{ name, visibility, parameters, returnParameters, isConstructor, isFallback, isReceiveEther },
|
|
28
|
+
parent
|
|
29
|
+
) {
|
|
30
|
+
if (parent !== undefined && parent.type === "ContractDefinition" && parent.name === contractName) {
|
|
31
|
+
try {
|
|
32
|
+
// skip constructor and fallbacks
|
|
33
|
+
if (isConstructor || isFallback || isReceiveEther) return;
|
|
34
|
+
// forbid default visibility (this check might be unnecessary, modern solidity already disallows this)
|
|
35
|
+
if (visibility === "default") throw new MUDError(`Visibility is not specified`);
|
|
36
|
+
|
|
37
|
+
if (visibility === "external" || visibility === "public") {
|
|
38
|
+
functions.push({
|
|
39
|
+
name: name === null ? "" : name,
|
|
40
|
+
parameters: parameters.map(parseParameter),
|
|
41
|
+
returnParameters: returnParameters === null ? [] : returnParameters.map(parseParameter),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
for (const { typeName } of parameters.concat(returnParameters ?? [])) {
|
|
45
|
+
symbols = symbols.concat(typeNameToExternalSymbols(typeName));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch (error: unknown) {
|
|
49
|
+
if (error instanceof MUDError) {
|
|
50
|
+
error.message = `Function "${name}" in contract "${contractName}": ${error.message}`;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (!withContract) {
|
|
59
|
+
throw new MUDError(`Contract not found: ${contractName}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
functions,
|
|
64
|
+
symbols,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parseParameter({ name, typeName, storageLocation }: VariableDeclaration): string {
|
|
69
|
+
let typedNameWithLocation = "";
|
|
70
|
+
|
|
71
|
+
const { name: flattenedTypeName, stateMutability } = flattenTypeName(typeName);
|
|
72
|
+
// type name (e.g. uint256)
|
|
73
|
+
typedNameWithLocation += flattenedTypeName;
|
|
74
|
+
// optional mutability (e.g. address payable)
|
|
75
|
+
if (stateMutability !== null) {
|
|
76
|
+
typedNameWithLocation += ` ${stateMutability}`;
|
|
77
|
+
}
|
|
78
|
+
// location, when relevant (e.g. string memory)
|
|
79
|
+
if (storageLocation !== null) {
|
|
80
|
+
typedNameWithLocation += ` ${storageLocation}`;
|
|
81
|
+
}
|
|
82
|
+
// optional variable name
|
|
83
|
+
if (name !== null) {
|
|
84
|
+
typedNameWithLocation += ` ${name}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return typedNameWithLocation;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function flattenTypeName(typeName: TypeName | null): { name: string; stateMutability: string | null } {
|
|
91
|
+
if (typeName === null) {
|
|
92
|
+
return {
|
|
93
|
+
name: "",
|
|
94
|
+
stateMutability: null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (typeName.type === "ElementaryTypeName") {
|
|
98
|
+
return {
|
|
99
|
+
name: typeName.name,
|
|
100
|
+
stateMutability: typeName.stateMutability,
|
|
101
|
+
};
|
|
102
|
+
} else if (typeName.type === "UserDefinedTypeName") {
|
|
103
|
+
return {
|
|
104
|
+
name: typeName.namePath,
|
|
105
|
+
stateMutability: null,
|
|
106
|
+
};
|
|
107
|
+
} else if (typeName.type === "ArrayTypeName") {
|
|
108
|
+
const { name, stateMutability } = flattenTypeName(typeName.baseTypeName);
|
|
109
|
+
return {
|
|
110
|
+
name: `${name}[]`,
|
|
111
|
+
stateMutability,
|
|
112
|
+
};
|
|
113
|
+
} else {
|
|
114
|
+
// TODO function types are unsupported but could be useful
|
|
115
|
+
throw new MUDError(`Invalid typeName.type ${typeName.type}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Get symbols that need to be imported for given typeName
|
|
120
|
+
function typeNameToExternalSymbols(typeName: TypeName | null): string[] {
|
|
121
|
+
if (typeName?.type === "UserDefinedTypeName") {
|
|
122
|
+
// split is needed to get a library, if types are internal to it
|
|
123
|
+
const symbol = typeName.namePath.split(".")[0];
|
|
124
|
+
return [symbol];
|
|
125
|
+
} else if (typeName?.type === "ArrayTypeName") {
|
|
126
|
+
return typeNameToExternalSymbols(typeName.baseTypeName);
|
|
127
|
+
} else {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
}
|
package/src/utils/deploy-v2.ts
CHANGED
|
@@ -1,33 +1,39 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { MUDConfig } from "../config/index.js";
|
|
3
|
+
import { MUDConfig, resolveWithContext } from "../config/index.js";
|
|
4
4
|
import { MUDError } from "./errors.js";
|
|
5
5
|
import { getOutDirectory, getScriptDirectory, cast, forge } from "./foundry.js";
|
|
6
6
|
import { BigNumber, ContractInterface, ethers } from "ethers";
|
|
7
|
-
import {
|
|
8
|
-
import { abi as WorldABI, bytecode as WorldBytecode } from "@latticexyz/world/abi/World.json";
|
|
7
|
+
import { IWorld } from "@latticexyz/world/types/ethers-contracts/IWorld.js";
|
|
9
8
|
import { ArgumentsType } from "vitest";
|
|
10
9
|
import chalk from "chalk";
|
|
11
10
|
import { encodeSchema } from "@latticexyz/schema-type";
|
|
12
|
-
import {
|
|
11
|
+
import { resolveAbiOrUserType } from "../render-solidity/userType.js";
|
|
12
|
+
import { defaultAbiCoder as abi, Fragment } from "ethers/lib/utils.js";
|
|
13
|
+
|
|
14
|
+
import WorldData from "@latticexyz/world/abi/World.json" assert { type: "json" };
|
|
15
|
+
import IWorldData from "@latticexyz/world/abi/IWorld.json" assert { type: "json" };
|
|
16
|
+
import CoreModuleData from "@latticexyz/world/abi/CoreModule.json" assert { type: "json" };
|
|
17
|
+
import RegistrationModuleData from "@latticexyz/world/abi/RegistrationModule.json" assert { type: "json" };
|
|
18
|
+
import KeysWithValueModuleData from "@latticexyz/world/abi/KeysWithValueModule.json" assert { type: "json" };
|
|
13
19
|
|
|
14
20
|
export interface DeployConfig {
|
|
15
21
|
profile?: string;
|
|
16
22
|
rpc: string;
|
|
17
23
|
privateKey: string;
|
|
18
24
|
priorityFeeMultiplier: number;
|
|
25
|
+
debug?: boolean;
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
export interface DeploymentInfo {
|
|
22
29
|
blockNumber: number;
|
|
23
30
|
worldAddress: string;
|
|
24
|
-
rpc: string;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig): Promise<DeploymentInfo> {
|
|
28
34
|
const startTime = Date.now();
|
|
29
|
-
const { worldContractName,
|
|
30
|
-
const { profile, rpc, privateKey, priorityFeeMultiplier } = deployConfig;
|
|
35
|
+
const { worldContractName, namespace, postDeployScript } = mudConfig;
|
|
36
|
+
const { profile, rpc, privateKey, priorityFeeMultiplier, debug } = deployConfig;
|
|
31
37
|
const forgeOutDirectory = await getOutDirectory(profile);
|
|
32
38
|
|
|
33
39
|
// Set up signer for deployment
|
|
@@ -44,116 +50,230 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
|
|
|
44
50
|
setInternalFeePerGas(priorityFeeMultiplier);
|
|
45
51
|
|
|
46
52
|
// Catch all to await any promises before exiting the script
|
|
47
|
-
|
|
53
|
+
let promises: Promise<unknown>[] = [];
|
|
48
54
|
|
|
49
55
|
// Get block number before deploying
|
|
50
56
|
const blockNumber = Number(await cast(["block-number", "--rpc-url", rpc], { profile }));
|
|
51
57
|
console.log("Start deployment at block", blockNumber);
|
|
52
58
|
|
|
53
|
-
// Deploy
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
// Deploy World
|
|
60
|
+
const worldPromise = {
|
|
61
|
+
World: worldContractName
|
|
62
|
+
? deployContractByName(worldContractName)
|
|
63
|
+
: deployContract(IWorldData.abi, WorldData.bytecode, "World"),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Deploy Systems
|
|
67
|
+
const systemPromises = Object.keys(mudConfig.systems).reduce<Record<string, Promise<string>>>((acc, systemName) => {
|
|
68
|
+
acc[systemName] = deployContractByName(systemName);
|
|
69
|
+
return acc;
|
|
70
|
+
}, {});
|
|
71
|
+
|
|
72
|
+
// Deploy default World modules
|
|
73
|
+
const defaultModules: Record<string, Promise<string>> = {
|
|
74
|
+
// TODO: these only need to be deployed once per chain, add a check if they exist already
|
|
75
|
+
CoreModule: deployContract(CoreModuleData.abi, CoreModuleData.bytecode, "CoreModule"),
|
|
76
|
+
RegistrationModule: deployContract(
|
|
77
|
+
RegistrationModuleData.abi,
|
|
78
|
+
RegistrationModuleData.bytecode,
|
|
79
|
+
"RegistrationModule"
|
|
80
|
+
),
|
|
81
|
+
KeysWithValueModule: deployContract(
|
|
82
|
+
KeysWithValueModuleData.abi,
|
|
83
|
+
KeysWithValueModuleData.bytecode,
|
|
84
|
+
"KeysWithValueModule"
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Deploy user Modules
|
|
89
|
+
const modulePromises = mudConfig.modules
|
|
90
|
+
.filter((module) => !defaultModules[module.name]) // Only deploy user modules here, not default modules
|
|
91
|
+
.reduce<Record<string, Promise<string>>>((acc, module) => {
|
|
92
|
+
acc[module.name] = deployContractByName(module.name);
|
|
57
93
|
return acc;
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
: deployContract(WorldABI, WorldBytecode, "World"),
|
|
63
|
-
}
|
|
64
|
-
);
|
|
94
|
+
}, defaultModules);
|
|
95
|
+
|
|
96
|
+
// Combine all contracts into one object
|
|
97
|
+
const contractPromises: Record<string, Promise<string>> = { ...worldPromise, ...systemPromises, ...modulePromises };
|
|
65
98
|
|
|
66
99
|
// Create World contract instance from deployed address
|
|
67
|
-
const WorldContract = new ethers.Contract(await contractPromises.World,
|
|
100
|
+
const WorldContract = new ethers.Contract(await contractPromises.World, IWorldData.abi, signer) as IWorld;
|
|
101
|
+
|
|
102
|
+
// Install core Modules
|
|
103
|
+
console.log(chalk.blue("Installing core World modules"));
|
|
104
|
+
await fastTxExecute(WorldContract, "installRootModule", [await modulePromises.CoreModule, "0x"]);
|
|
105
|
+
await fastTxExecute(WorldContract, "installRootModule", [await modulePromises.RegistrationModule, "0x"]);
|
|
106
|
+
console.log(chalk.green("Installed core World modules"));
|
|
68
107
|
|
|
69
|
-
// Register
|
|
70
|
-
if (
|
|
108
|
+
// Register namespace
|
|
109
|
+
if (namespace) await fastTxExecute(WorldContract, "registerNamespace", [toBytes16(namespace)]);
|
|
71
110
|
|
|
72
111
|
// Register tables
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
112
|
+
const tableIds: { [tableName: string]: Uint8Array } = {};
|
|
113
|
+
promises = [
|
|
114
|
+
...promises,
|
|
115
|
+
...Object.entries(mudConfig.tables).map(async ([tableName, { fileSelector, schema, primaryKeys }]) => {
|
|
116
|
+
console.log(chalk.blue(`Registering table ${tableName} at ${namespace}/${fileSelector}`));
|
|
76
117
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
const lastRouteFragment = toRoute(routeFragments.pop()); // Register last fragment separately as part of call to registerTable
|
|
80
|
-
await registerNestedRoute(WorldContract, baseRoute, routeFragments);
|
|
118
|
+
// Store the tableId for later use
|
|
119
|
+
tableIds[tableName] = toResourceSelector(namespace, fileSelector);
|
|
81
120
|
|
|
82
121
|
// Register table
|
|
83
|
-
const
|
|
122
|
+
const schemaTypes = Object.values(schema).map((abiOrUserType) => {
|
|
123
|
+
const { schemaType } = resolveAbiOrUserType(abiOrUserType, mudConfig);
|
|
124
|
+
return schemaType;
|
|
125
|
+
});
|
|
84
126
|
|
|
85
|
-
const
|
|
86
|
-
|
|
127
|
+
const keyTypes = Object.values(primaryKeys).map((abiOrUserType) => {
|
|
128
|
+
const { schemaType } = resolveAbiOrUserType(abiOrUserType, mudConfig);
|
|
129
|
+
return schemaType;
|
|
87
130
|
});
|
|
88
131
|
|
|
89
132
|
await fastTxExecute(WorldContract, "registerTable", [
|
|
90
|
-
|
|
91
|
-
|
|
133
|
+
toBytes16(namespace),
|
|
134
|
+
toBytes16(fileSelector),
|
|
92
135
|
encodeSchema(schemaTypes),
|
|
136
|
+
encodeSchema(keyTypes),
|
|
93
137
|
]);
|
|
94
138
|
|
|
95
139
|
// Register table metadata
|
|
96
|
-
await fastTxExecute(WorldContract, "setMetadata(
|
|
97
|
-
|
|
140
|
+
await fastTxExecute(WorldContract, "setMetadata(bytes16,bytes16,string,string[])", [
|
|
141
|
+
toBytes16(namespace),
|
|
142
|
+
toBytes16(fileSelector),
|
|
98
143
|
tableName,
|
|
99
|
-
Object.keys(
|
|
144
|
+
Object.keys(schema),
|
|
100
145
|
]);
|
|
101
146
|
|
|
102
|
-
console.log(chalk.green(
|
|
103
|
-
})
|
|
104
|
-
|
|
147
|
+
console.log(chalk.green(`Registered table ${tableName} at ${fileSelector}`));
|
|
148
|
+
}),
|
|
149
|
+
];
|
|
105
150
|
|
|
106
151
|
// Register systems (using forEach instead of for..of to avoid blocking on async calls)
|
|
107
|
-
promises
|
|
108
|
-
...
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
152
|
+
promises = [
|
|
153
|
+
...promises,
|
|
154
|
+
...Object.entries(mudConfig.systems).map(
|
|
155
|
+
async ([systemName, { fileSelector, openAccess, registerFunctionSelectors }]) => {
|
|
156
|
+
// Register system at route
|
|
157
|
+
console.log(chalk.blue(`Registering system ${systemName} at ${namespace}/${fileSelector}`));
|
|
158
|
+
await fastTxExecute(WorldContract, "registerSystem", [
|
|
159
|
+
toBytes16(namespace),
|
|
160
|
+
toBytes16(fileSelector),
|
|
161
|
+
await contractPromises[systemName],
|
|
162
|
+
openAccess,
|
|
163
|
+
]);
|
|
164
|
+
console.log(chalk.green(`Registered system ${systemName} at ${namespace}/${fileSelector}`));
|
|
165
|
+
|
|
166
|
+
// Register function selectors for the system
|
|
167
|
+
if (registerFunctionSelectors) {
|
|
168
|
+
const functionSignatures: FunctionSignature[] = await loadFunctionSignatures(systemName);
|
|
169
|
+
const isRoot = namespace === "";
|
|
170
|
+
// Using Promise.all to avoid blocking on async calls
|
|
171
|
+
await Promise.all(
|
|
172
|
+
functionSignatures.map(async ({ functionName, functionArgs }) => {
|
|
173
|
+
const functionSignature = isRoot
|
|
174
|
+
? functionName + functionArgs
|
|
175
|
+
: `${namespace}_${fileSelector}_${functionName}${functionArgs}`;
|
|
176
|
+
|
|
177
|
+
console.log(chalk.blue(`Registering function "${functionSignature}"`));
|
|
178
|
+
if (isRoot) {
|
|
179
|
+
const worldFunctionSelector = toFunctionSelector(
|
|
180
|
+
functionSignature === ""
|
|
181
|
+
? { functionName: systemName, functionArgs } // Register the system's fallback function as `<systemName>(<args>)`
|
|
182
|
+
: { functionName, functionArgs }
|
|
183
|
+
);
|
|
184
|
+
const systemFunctionSelector = toFunctionSelector({ functionName, functionArgs });
|
|
185
|
+
await fastTxExecute(WorldContract, "registerRootFunctionSelector", [
|
|
186
|
+
toBytes16(namespace),
|
|
187
|
+
toBytes16(fileSelector),
|
|
188
|
+
worldFunctionSelector,
|
|
189
|
+
systemFunctionSelector,
|
|
190
|
+
]);
|
|
191
|
+
} else {
|
|
192
|
+
await fastTxExecute(WorldContract, "registerFunctionSelector", [
|
|
193
|
+
toBytes16(namespace),
|
|
194
|
+
toBytes16(fileSelector),
|
|
195
|
+
functionName,
|
|
196
|
+
functionArgs,
|
|
197
|
+
]);
|
|
198
|
+
}
|
|
199
|
+
console.log(chalk.green(`Registered function "${functionSignature}"`));
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
];
|
|
128
206
|
|
|
129
|
-
// Wait for
|
|
130
|
-
await Promise.all(promises);
|
|
207
|
+
// Wait for resources to be registered before granting access to them
|
|
208
|
+
await Promise.all(promises); // ----------------------------------------------------------------------------------------------
|
|
209
|
+
promises = [];
|
|
131
210
|
|
|
132
211
|
// Grant access to systems
|
|
133
|
-
for (const [systemName,
|
|
134
|
-
|
|
212
|
+
for (const [systemName, { fileSelector, accessListAddresses, accessListSystems }] of Object.entries(
|
|
213
|
+
mudConfig.systems
|
|
214
|
+
)) {
|
|
215
|
+
const resourceSelector = `${namespace}/${fileSelector}`;
|
|
135
216
|
|
|
136
217
|
// Grant access to addresses
|
|
137
|
-
promises
|
|
138
|
-
...
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
218
|
+
promises = [
|
|
219
|
+
...promises,
|
|
220
|
+
...accessListAddresses.map(async (address) => {
|
|
221
|
+
console.log(chalk.blue(`Grant ${address} access to ${systemName} (${resourceSelector})`));
|
|
222
|
+
await fastTxExecute(WorldContract, "grantAccess(bytes16,bytes16,address)", [
|
|
223
|
+
toBytes16(namespace),
|
|
224
|
+
toBytes16(fileSelector),
|
|
225
|
+
address,
|
|
226
|
+
]);
|
|
227
|
+
console.log(chalk.green(`Granted ${address} access to ${systemName} (${namespace}/${fileSelector})`));
|
|
228
|
+
}),
|
|
229
|
+
];
|
|
144
230
|
|
|
145
231
|
// Grant access to other systems
|
|
146
|
-
promises
|
|
147
|
-
...
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
232
|
+
promises = [
|
|
233
|
+
...promises,
|
|
234
|
+
...accessListSystems.map(async (granteeSystem) => {
|
|
235
|
+
console.log(chalk.blue(`Grant ${granteeSystem} access to ${systemName} (${resourceSelector})`));
|
|
236
|
+
await fastTxExecute(WorldContract, "grantAccess(bytes16,bytes16,address)", [
|
|
237
|
+
toBytes16(namespace),
|
|
238
|
+
toBytes16(fileSelector),
|
|
239
|
+
await contractPromises[granteeSystem],
|
|
240
|
+
]);
|
|
241
|
+
console.log(chalk.green(`Granted ${granteeSystem} access to ${systemName} (${resourceSelector})`));
|
|
242
|
+
}),
|
|
243
|
+
];
|
|
153
244
|
}
|
|
154
245
|
|
|
155
|
-
//
|
|
156
|
-
await Promise.all(promises);
|
|
246
|
+
// Wait for access to be granted before installing modules
|
|
247
|
+
await Promise.all(promises); // ----------------------------------------------------------------------------------------------
|
|
248
|
+
promises = [];
|
|
249
|
+
|
|
250
|
+
// Install modules
|
|
251
|
+
promises = [
|
|
252
|
+
...promises,
|
|
253
|
+
...mudConfig.modules.map(async (module) => {
|
|
254
|
+
console.log(chalk.blue(`Installing${module.root ? " root " : " "}module ${module.name}`));
|
|
255
|
+
// Resolve arguments
|
|
256
|
+
const resolvedArgs = await Promise.all(
|
|
257
|
+
module.args.map((arg) => resolveWithContext(arg, { tableIds, systemAddresses: contractPromises }))
|
|
258
|
+
);
|
|
259
|
+
const values = resolvedArgs.map((arg) => arg.value);
|
|
260
|
+
const types = resolvedArgs.map((arg) => arg.type);
|
|
261
|
+
const moduleAddress = await contractPromises[module.name];
|
|
262
|
+
if (!moduleAddress) throw new Error(`Module ${module.name} not found`);
|
|
263
|
+
|
|
264
|
+
// Send transaction to install module
|
|
265
|
+
await fastTxExecute(WorldContract, module.root ? "installRootModule" : "installModule", [
|
|
266
|
+
moduleAddress,
|
|
267
|
+
abi.encode(types, values),
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
console.log(chalk.green(`Installed${module.root ? " root " : " "}module ${module.name}`));
|
|
271
|
+
}),
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
// Await all promises before executing PostDeploy script
|
|
275
|
+
await Promise.all(promises); // ----------------------------------------------------------------------------------------------
|
|
276
|
+
promises = [];
|
|
157
277
|
|
|
158
278
|
// Execute postDeploy forge script
|
|
159
279
|
const postDeployPath = path.join(await getScriptDirectory(), postDeployScript + ".s.sol");
|
|
@@ -181,7 +301,7 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
|
|
|
181
301
|
|
|
182
302
|
console.log(chalk.green("Deployment completed in", (Date.now() - startTime) / 1000, "seconds"));
|
|
183
303
|
|
|
184
|
-
return { worldAddress: await contractPromises.World, blockNumber
|
|
304
|
+
return { worldAddress: await contractPromises.World, blockNumber };
|
|
185
305
|
|
|
186
306
|
// ------------------- INTERNAL FUNCTIONS -------------------
|
|
187
307
|
// (Inlined to avoid having to pass around nonce, signer and forgeOutDir)
|
|
@@ -225,6 +345,7 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
|
|
|
225
345
|
console.log(chalk.green("Deployed", contractName, "to", address));
|
|
226
346
|
return address;
|
|
227
347
|
} catch (error: any) {
|
|
348
|
+
if (debug) console.error(error);
|
|
228
349
|
if (retryCount === 0 && error?.message.includes("transaction already imported")) {
|
|
229
350
|
// If the deployment failed because the transaction was already imported,
|
|
230
351
|
// retry with a higher priority fee
|
|
@@ -234,6 +355,8 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
|
|
|
234
355
|
throw new MUDError(
|
|
235
356
|
`Error deploying ${contractName}: invalid bytecode. Note that linking of public libraries is not supported yet, make sure none of your libraries use "external" functions.`
|
|
236
357
|
);
|
|
358
|
+
} else if (error?.message.includes("CreateContractLimit")) {
|
|
359
|
+
throw new MUDError(`Error deploying ${contractName}: CreateContractLimit exceeded.`);
|
|
237
360
|
} else throw error;
|
|
238
361
|
}
|
|
239
362
|
}
|
|
@@ -261,6 +384,18 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
|
|
|
261
384
|
// return deployedTo;
|
|
262
385
|
// }
|
|
263
386
|
|
|
387
|
+
async function loadFunctionSignatures(contractName: string): Promise<FunctionSignature[]> {
|
|
388
|
+
const { abi } = await getContractData(contractName);
|
|
389
|
+
|
|
390
|
+
return abi
|
|
391
|
+
.filter((item) => ["fallback", "function"].includes(item.type))
|
|
392
|
+
.map((item) => {
|
|
393
|
+
if (item.type === "fallback") return { functionName: "", functionArgs: "" };
|
|
394
|
+
|
|
395
|
+
return { functionName: item.name, functionArgs: `(${item.inputs.map((arg) => arg.type).join(",")})` };
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
264
399
|
/**
|
|
265
400
|
* Only await gas estimation (for speed), only execute if gas estimation succeeds (for safety)
|
|
266
401
|
*/
|
|
@@ -281,6 +416,7 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
|
|
|
281
416
|
promises.push(txPromise);
|
|
282
417
|
return txPromise;
|
|
283
418
|
} catch (error: any) {
|
|
419
|
+
if (debug) console.error(error);
|
|
284
420
|
if (retryCount === 0 && error?.message.includes("transaction already imported")) {
|
|
285
421
|
// If the deployment failed because the transaction was already imported,
|
|
286
422
|
// retry with a higher priority fee
|
|
@@ -290,30 +426,17 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
|
|
|
290
426
|
}
|
|
291
427
|
}
|
|
292
428
|
|
|
293
|
-
async function registerNestedRoute(WorldContract: World, baseRoute: string, registerRouteFragments: string[]) {
|
|
294
|
-
// Register nested routes
|
|
295
|
-
for (let i = 0; i < registerRouteFragments.length; i++) {
|
|
296
|
-
const subRoute = toRoute(registerRouteFragments[i]);
|
|
297
|
-
try {
|
|
298
|
-
await fastTxExecute(WorldContract, "registerRoute", [baseRoute, subRoute]);
|
|
299
|
-
} catch (e) {
|
|
300
|
-
// TODO: check if the gas estimation error is due to the route already being registered and ignore it
|
|
301
|
-
}
|
|
302
|
-
baseRoute += subRoute;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
429
|
/**
|
|
307
430
|
* Load the contract's abi and bytecode from the file system
|
|
308
431
|
* @param contractName: Name of the contract to load
|
|
309
432
|
*/
|
|
310
|
-
async function getContractData(contractName: string): Promise<{ bytecode: string; abi:
|
|
433
|
+
async function getContractData(contractName: string): Promise<{ bytecode: string; abi: Fragment[] }> {
|
|
311
434
|
let data: any;
|
|
312
435
|
const contractDataPath = path.join(forgeOutDirectory, contractName + ".sol", contractName + ".json");
|
|
313
436
|
try {
|
|
314
437
|
data = JSON.parse(readFileSync(contractDataPath, "utf8"));
|
|
315
438
|
} catch (error: any) {
|
|
316
|
-
throw new MUDError(`Error reading file at ${contractDataPath}
|
|
439
|
+
throw new MUDError(`Error reading file at ${contractDataPath}`);
|
|
317
440
|
}
|
|
318
441
|
|
|
319
442
|
const bytecode = data?.bytecode?.object;
|
|
@@ -334,12 +457,56 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig):
|
|
|
334
457
|
// Compute maxFeePerGas and maxPriorityFeePerGas like ethers, but allow for a multiplier to allow replacing pending transactions
|
|
335
458
|
const feeData = await provider.getFeeData();
|
|
336
459
|
if (!feeData.lastBaseFeePerGas) throw new MUDError("Can not fetch lastBaseFeePerGas from RPC");
|
|
337
|
-
|
|
460
|
+
|
|
461
|
+
// Set the priority fee to 0 for development chains with no base fee, to allow transactions from unfunded wallets
|
|
462
|
+
maxPriorityFeePerGas = feeData.lastBaseFeePerGas.eq(0) ? 0 : Math.floor(1_500_000_000 * multiplier);
|
|
338
463
|
maxFeePerGas = feeData.lastBaseFeePerGas.mul(2).add(maxPriorityFeePerGas);
|
|
339
464
|
}
|
|
340
465
|
}
|
|
341
466
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
467
|
+
// TODO: use stringToBytes16 from utils as soon as utils are usable inside cli
|
|
468
|
+
// (see https://github.com/latticexyz/mud/issues/499)
|
|
469
|
+
function toBytes16(input: string) {
|
|
470
|
+
if (input.length > 16) throw new Error("String does not fit into 16 bytes");
|
|
471
|
+
|
|
472
|
+
const result = new Uint8Array(16);
|
|
473
|
+
// Set ascii bytes
|
|
474
|
+
for (let i = 0; i < input.length; i++) {
|
|
475
|
+
result[i] = input.charCodeAt(i);
|
|
476
|
+
}
|
|
477
|
+
// Set the remaining bytes to 0
|
|
478
|
+
for (let i = input.length; i < 16; i++) {
|
|
479
|
+
result[i] = 0;
|
|
480
|
+
}
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// TODO: use TableId from utils as soon as utils are usable inside cli
|
|
485
|
+
// (see https://github.com/latticexyz/mud/issues/499)
|
|
486
|
+
function toResourceSelector(namespace: string, file: string): Uint8Array {
|
|
487
|
+
const namespaceBytes = toBytes16(namespace);
|
|
488
|
+
const fileBytes = toBytes16(file);
|
|
489
|
+
const result = new Uint8Array(32);
|
|
490
|
+
result.set(namespaceBytes);
|
|
491
|
+
result.set(fileBytes, 16);
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
interface FunctionSignature {
|
|
496
|
+
functionName: string;
|
|
497
|
+
functionArgs: string;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// TODO: move this to utils as soon as utils are usable inside cli
|
|
501
|
+
// (see https://github.com/latticexyz/mud/issues/499)
|
|
502
|
+
function toFunctionSelector({ functionName, functionArgs }: FunctionSignature): string {
|
|
503
|
+
const functionSignature = functionName + functionArgs;
|
|
504
|
+
if (functionSignature === "") return "0x";
|
|
505
|
+
return sigHash(functionSignature);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// TODO: move this to utils as soon as utils are usable inside cli
|
|
509
|
+
// (see https://github.com/latticexyz/mud/issues/499)
|
|
510
|
+
function sigHash(signature: string) {
|
|
511
|
+
return ethers.utils.hexDataSlice(ethers.utils.keccak256(ethers.utils.toUtf8Bytes(signature)), 0, 4);
|
|
345
512
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { formatSolidity } from "./format.js";
|
|
4
|
+
|
|
5
|
+
export async function formatAndWrite(output: string, fullOutputPath: string, logPrefix: string) {
|
|
6
|
+
const formattedOutput = await formatSolidity(output);
|
|
7
|
+
|
|
8
|
+
mkdirSync(dirname(fullOutputPath), { recursive: true });
|
|
9
|
+
|
|
10
|
+
writeFileSync(fullOutputPath, formattedOutput);
|
|
11
|
+
console.log(`${logPrefix}: ${fullOutputPath}`);
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
|
|
3
|
+
// TODO: Use viem's getChainId
|
|
4
|
+
export async function getChainId(rpc: string) {
|
|
5
|
+
const { result: chainId } = await ethers.utils.fetchJson(
|
|
6
|
+
rpc,
|
|
7
|
+
'{ "id": 42, "jsonrpc": "2.0", "method": "eth_chainId", "params": [ ] }'
|
|
8
|
+
);
|
|
9
|
+
return Number(chainId);
|
|
10
|
+
}
|