@latticexyz/cli 2.0.0-next.11 → 2.0.0-next.12

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.
Files changed (61) hide show
  1. package/dist/index.js +0 -1
  2. package/dist/mud.js +19 -13
  3. package/dist/mud.js.map +1 -1
  4. package/package.json +16 -12
  5. package/src/commands/deploy.ts +7 -30
  6. package/src/commands/dev-contracts.ts +74 -138
  7. package/src/commands/test.ts +30 -36
  8. package/src/commands/trace.ts +7 -5
  9. package/src/debug.ts +3 -0
  10. package/src/deploy/assertNamespaceOwner.ts +42 -0
  11. package/src/deploy/common.ts +72 -0
  12. package/src/deploy/configToTables.ts +68 -0
  13. package/src/deploy/create2/README.md +9 -0
  14. package/src/deploy/create2/deployment.json +7 -0
  15. package/src/deploy/debug.ts +3 -0
  16. package/src/deploy/deploy.ts +108 -0
  17. package/src/deploy/deployWorld.ts +33 -0
  18. package/src/deploy/ensureContract.ts +49 -0
  19. package/src/deploy/ensureContractsDeployed.ts +25 -0
  20. package/src/deploy/ensureDeployer.ts +36 -0
  21. package/src/deploy/ensureFunctions.ts +86 -0
  22. package/src/deploy/ensureModules.ts +72 -0
  23. package/src/deploy/ensureSystems.ts +161 -0
  24. package/src/deploy/ensureTables.ts +65 -0
  25. package/src/deploy/ensureWorldFactory.ts +34 -0
  26. package/src/deploy/getFunctions.ts +58 -0
  27. package/src/deploy/getResourceAccess.ts +51 -0
  28. package/src/deploy/getResourceIds.ts +31 -0
  29. package/src/deploy/getSystems.ts +48 -0
  30. package/src/deploy/getTableValue.ts +30 -0
  31. package/src/deploy/getTables.ts +59 -0
  32. package/src/deploy/getWorldDeploy.ts +39 -0
  33. package/src/deploy/logsToWorldDeploy.ts +49 -0
  34. package/src/deploy/resolveConfig.ts +154 -0
  35. package/src/deploy/resourceLabel.ts +3 -0
  36. package/src/index.ts +1 -1
  37. package/src/runDeploy.ts +128 -0
  38. package/src/utils/modules/constants.ts +1 -2
  39. package/src/utils/utils/getContractData.ts +2 -5
  40. package/dist/chunk-TW3YGZ4D.js +0 -11
  41. package/dist/chunk-TW3YGZ4D.js.map +0 -1
  42. package/src/utils/deploy.ts +0 -254
  43. package/src/utils/deployHandler.ts +0 -93
  44. package/src/utils/modules/getInstallModuleCallData.ts +0 -27
  45. package/src/utils/modules/getUserModules.ts +0 -5
  46. package/src/utils/modules/types.ts +0 -14
  47. package/src/utils/systems/getGrantAccessCallData.ts +0 -29
  48. package/src/utils/systems/getRegisterFunctionSelectorsCallData.ts +0 -57
  49. package/src/utils/systems/getRegisterSystemCallData.ts +0 -17
  50. package/src/utils/systems/types.ts +0 -9
  51. package/src/utils/systems/utils.ts +0 -42
  52. package/src/utils/tables/getRegisterTableCallData.ts +0 -49
  53. package/src/utils/tables/getTableIds.ts +0 -18
  54. package/src/utils/tables/types.ts +0 -12
  55. package/src/utils/utils/confirmNonce.ts +0 -24
  56. package/src/utils/utils/deployContract.ts +0 -33
  57. package/src/utils/utils/fastTxExecute.ts +0 -56
  58. package/src/utils/utils/getChainId.ts +0 -10
  59. package/src/utils/utils/setInternalFeePerGas.ts +0 -49
  60. package/src/utils/utils/types.ts +0 -21
  61. package/src/utils/world.ts +0 -28
@@ -0,0 +1,65 @@
1
+ import { Client, Transport, Chain, Account, Hex } from "viem";
2
+ import { Table } from "./configToTables";
3
+ import { writeContract } from "@latticexyz/common";
4
+ import { WorldDeploy, worldAbi } from "./common";
5
+ import { valueSchemaToFieldLayoutHex, keySchemaToHex, valueSchemaToHex } from "@latticexyz/protocol-parser";
6
+ import { debug } from "./debug";
7
+ import { resourceLabel } from "./resourceLabel";
8
+ import { getTables } from "./getTables";
9
+ import pRetry from "p-retry";
10
+ import { wait } from "@latticexyz/common/utils";
11
+
12
+ export async function ensureTables({
13
+ client,
14
+ worldDeploy,
15
+ tables,
16
+ }: {
17
+ readonly client: Client<Transport, Chain | undefined, Account>;
18
+ readonly worldDeploy: WorldDeploy;
19
+ readonly tables: readonly Table[];
20
+ }): Promise<readonly Hex[]> {
21
+ const worldTables = await getTables({ client, worldDeploy });
22
+ const worldTableIds = worldTables.map((table) => table.tableId);
23
+
24
+ const existingTables = tables.filter((table) => worldTableIds.includes(table.tableId));
25
+ if (existingTables.length) {
26
+ debug("existing tables", existingTables.map(resourceLabel).join(", "));
27
+ }
28
+
29
+ const missingTables = tables.filter((table) => !worldTableIds.includes(table.tableId));
30
+ if (missingTables.length) {
31
+ debug("registering tables", missingTables.map(resourceLabel).join(", "));
32
+ return await Promise.all(
33
+ missingTables.map((table) =>
34
+ pRetry(
35
+ () =>
36
+ writeContract(client, {
37
+ chain: client.chain ?? null,
38
+ address: worldDeploy.address,
39
+ abi: worldAbi,
40
+ // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645)
41
+ functionName: "registerTable",
42
+ args: [
43
+ table.tableId,
44
+ valueSchemaToFieldLayoutHex(table.valueSchema),
45
+ keySchemaToHex(table.keySchema),
46
+ valueSchemaToHex(table.valueSchema),
47
+ Object.keys(table.keySchema),
48
+ Object.keys(table.valueSchema),
49
+ ],
50
+ }),
51
+ {
52
+ retries: 3,
53
+ onFailedAttempt: async (error) => {
54
+ const delay = error.attemptNumber * 500;
55
+ debug(`failed to register table ${resourceLabel(table)}, retrying in ${delay}ms...`);
56
+ await wait(delay);
57
+ },
58
+ }
59
+ )
60
+ )
61
+ );
62
+ }
63
+
64
+ return [];
65
+ }
@@ -0,0 +1,34 @@
1
+ import coreModuleBuild from "@latticexyz/world/out/CoreModule.sol/CoreModule.json" assert { type: "json" };
2
+ import worldFactoryBuild from "@latticexyz/world/out/WorldFactory.sol/WorldFactory.json" assert { type: "json" };
3
+ import { Client, Transport, Chain, Account, Hex, parseAbi, getCreate2Address, encodeDeployData } from "viem";
4
+ import { deployer } from "./ensureDeployer";
5
+ import { salt } from "./common";
6
+ import { ensureContractsDeployed } from "./ensureContractsDeployed";
7
+
8
+ export const coreModuleBytecode = encodeDeployData({
9
+ bytecode: coreModuleBuild.bytecode.object as Hex,
10
+ abi: [],
11
+ });
12
+
13
+ export const coreModule = getCreate2Address({ from: deployer, bytecode: coreModuleBytecode, salt });
14
+
15
+ export const worldFactoryBytecode = encodeDeployData({
16
+ bytecode: worldFactoryBuild.bytecode.object as Hex,
17
+ abi: parseAbi(["constructor(address)"]),
18
+ args: [coreModule],
19
+ });
20
+
21
+ export const worldFactory = getCreate2Address({ from: deployer, bytecode: worldFactoryBytecode, salt });
22
+
23
+ export async function ensureWorldFactory(
24
+ client: Client<Transport, Chain | undefined, Account>
25
+ ): Promise<readonly Hex[]> {
26
+ // WorldFactory constructor doesn't call CoreModule, only sets its address, so we can do these in parallel since the address is deterministic
27
+ return await ensureContractsDeployed({
28
+ client,
29
+ contracts: [
30
+ { bytecode: coreModuleBytecode, label: "core module" },
31
+ { bytecode: worldFactoryBytecode, label: "world factory" },
32
+ ],
33
+ });
34
+ }
@@ -0,0 +1,58 @@
1
+ import { Client, getFunctionSelector, parseAbiItem } from "viem";
2
+ import { WorldDeploy, WorldFunction, worldTables } from "./common";
3
+ import { debug } from "./debug";
4
+ import { storeSetRecordEvent } from "@latticexyz/store";
5
+ import { getLogs } from "viem/actions";
6
+ import { decodeValueArgs } from "@latticexyz/protocol-parser";
7
+ import { getTableValue } from "./getTableValue";
8
+ import { hexToResource } from "@latticexyz/common";
9
+
10
+ export async function getFunctions({
11
+ client,
12
+ worldDeploy,
13
+ }: {
14
+ readonly client: Client;
15
+ readonly worldDeploy: WorldDeploy;
16
+ }): Promise<readonly WorldFunction[]> {
17
+ // This assumes we only use `FunctionSelectors._set(...)`, which is true as of this writing.
18
+ debug("looking up function signatures for", worldDeploy.address);
19
+ const logs = await getLogs(client, {
20
+ strict: true,
21
+ fromBlock: worldDeploy.deployBlock,
22
+ toBlock: worldDeploy.stateBlock,
23
+ address: worldDeploy.address,
24
+ event: parseAbiItem(storeSetRecordEvent),
25
+ args: { tableId: worldTables.world_FunctionSignatures.tableId },
26
+ });
27
+
28
+ const signatures = logs.map((log) => {
29
+ const value = decodeValueArgs(worldTables.world_FunctionSignatures.valueSchema, log.args);
30
+ return value.functionSignature;
31
+ });
32
+ debug("found", signatures.length, "function signatures for", worldDeploy.address);
33
+
34
+ // TODO: parallelize with a bulk getRecords
35
+ const functions = await Promise.all(
36
+ signatures.map(async (signature) => {
37
+ const selector = getFunctionSelector(signature);
38
+ const { systemId, systemFunctionSelector } = await getTableValue({
39
+ client,
40
+ worldDeploy,
41
+ table: worldTables.world_FunctionSelectors,
42
+ key: { functionSelector: selector },
43
+ });
44
+ const { namespace, name } = hexToResource(systemId);
45
+ // TODO: find away around undoing contract logic (https://github.com/latticexyz/mud/issues/1708)
46
+ const systemFunctionSignature = namespace === "" ? signature : signature.replace(`${namespace}_${name}_`, "");
47
+ return {
48
+ signature,
49
+ selector,
50
+ systemId,
51
+ systemFunctionSignature,
52
+ systemFunctionSelector,
53
+ };
54
+ })
55
+ );
56
+
57
+ return functions;
58
+ }
@@ -0,0 +1,51 @@
1
+ import { Client, parseAbiItem, Hex, Address, getAddress } from "viem";
2
+ import { WorldDeploy, worldTables } from "./common";
3
+ import { debug } from "./debug";
4
+ import { storeSpliceStaticDataEvent } from "@latticexyz/store";
5
+ import { getLogs } from "viem/actions";
6
+ import { decodeKey } from "@latticexyz/protocol-parser";
7
+ import { getTableValue } from "./getTableValue";
8
+
9
+ export async function getResourceAccess({
10
+ client,
11
+ worldDeploy,
12
+ }: {
13
+ readonly client: Client;
14
+ readonly worldDeploy: WorldDeploy;
15
+ }): Promise<readonly { readonly resourceId: Hex; readonly address: Address }[]> {
16
+ // This assumes we only use `ResourceAccess._set(...)`, which is true as of this writing.
17
+ // TODO: PR to viem's getLogs to accept topics array so we can filter on all store events and quickly recreate this table's current state
18
+
19
+ debug("looking up resource access for", worldDeploy.address);
20
+
21
+ const logs = await getLogs(client, {
22
+ strict: true,
23
+ fromBlock: worldDeploy.deployBlock,
24
+ toBlock: worldDeploy.stateBlock,
25
+ address: worldDeploy.address,
26
+ // our usage of `ResourceAccess._set(...)` emits a splice instead of set record
27
+ // TODO: https://github.com/latticexyz/mud/issues/479
28
+ event: parseAbiItem(storeSpliceStaticDataEvent),
29
+ args: { tableId: worldTables.world_ResourceAccess.tableId },
30
+ });
31
+
32
+ const keys = logs.map((log) => decodeKey(worldTables.world_ResourceAccess.keySchema, log.args.keyTuple));
33
+
34
+ const access = (
35
+ await Promise.all(
36
+ keys.map(
37
+ async (key) =>
38
+ [key, await getTableValue({ client, worldDeploy, table: worldTables.world_ResourceAccess, key })] as const
39
+ )
40
+ )
41
+ )
42
+ .filter(([, value]) => value.access)
43
+ .map(([key]) => ({
44
+ resourceId: key.resourceId,
45
+ address: getAddress(key.caller),
46
+ }));
47
+
48
+ debug("found", access.length, "resource<>address access pairs");
49
+
50
+ return access;
51
+ }
@@ -0,0 +1,31 @@
1
+ import { Client, parseAbiItem, Hex } from "viem";
2
+ import { getLogs } from "viem/actions";
3
+ import { storeSpliceStaticDataEvent } from "@latticexyz/store";
4
+ import { WorldDeploy, storeTables } from "./common";
5
+ import { debug } from "./debug";
6
+
7
+ export async function getResourceIds({
8
+ client,
9
+ worldDeploy,
10
+ }: {
11
+ readonly client: Client;
12
+ readonly worldDeploy: WorldDeploy;
13
+ }): Promise<readonly Hex[]> {
14
+ // This assumes we only use `ResourceIds._setExists(true)`, which is true as of this writing.
15
+ // TODO: PR to viem's getLogs to accept topics array so we can filter on all store events and quickly recreate this table's current state
16
+
17
+ debug("looking up resource IDs for", worldDeploy.address);
18
+ const logs = await getLogs(client, {
19
+ strict: true,
20
+ address: worldDeploy.address,
21
+ fromBlock: worldDeploy.deployBlock,
22
+ toBlock: worldDeploy.stateBlock,
23
+ event: parseAbiItem(storeSpliceStaticDataEvent),
24
+ args: { tableId: storeTables.store_ResourceIds.tableId },
25
+ });
26
+
27
+ const resourceIds = logs.map((log) => log.args.keyTuple[0]);
28
+ debug("found", resourceIds.length, "resource IDs for", worldDeploy.address);
29
+
30
+ return resourceIds;
31
+ }
@@ -0,0 +1,48 @@
1
+ import { System, WorldDeploy, worldTables } from "./common";
2
+ import { Client } from "viem";
3
+ import { getResourceIds } from "./getResourceIds";
4
+ import { hexToResource } from "@latticexyz/common";
5
+ import { getTableValue } from "./getTableValue";
6
+ import { debug } from "./debug";
7
+ import { resourceLabel } from "./resourceLabel";
8
+ import { getFunctions } from "./getFunctions";
9
+ import { getResourceAccess } from "./getResourceAccess";
10
+
11
+ export async function getSystems({
12
+ client,
13
+ worldDeploy,
14
+ }: {
15
+ readonly client: Client;
16
+ readonly worldDeploy: WorldDeploy;
17
+ }): Promise<readonly Omit<System, "abi" | "bytecode">[]> {
18
+ const [resourceIds, functions, resourceAccess] = await Promise.all([
19
+ getResourceIds({ client, worldDeploy }),
20
+ getFunctions({ client, worldDeploy }),
21
+ getResourceAccess({ client, worldDeploy }),
22
+ ]);
23
+ const systems = resourceIds.map(hexToResource).filter((resource) => resource.type === "system");
24
+
25
+ debug("looking up systems", systems.map(resourceLabel).join(", "));
26
+ return await Promise.all(
27
+ systems.map(async (system) => {
28
+ const { system: address, publicAccess } = await getTableValue({
29
+ client,
30
+ worldDeploy,
31
+ table: worldTables.world_Systems,
32
+ key: { systemId: system.resourceId },
33
+ });
34
+ const systemFunctions = functions.filter((func) => func.systemId === system.resourceId);
35
+ return {
36
+ address,
37
+ namespace: system.namespace,
38
+ name: system.name,
39
+ systemId: system.resourceId,
40
+ allowAll: publicAccess,
41
+ allowedAddresses: resourceAccess
42
+ .filter(({ resourceId }) => resourceId === system.resourceId)
43
+ .map(({ address }) => address),
44
+ functions: systemFunctions,
45
+ };
46
+ })
47
+ );
48
+ }
@@ -0,0 +1,30 @@
1
+ import { SchemaToPrimitives, decodeValueArgs, encodeKey } from "@latticexyz/protocol-parser";
2
+ import { WorldDeploy, worldAbi } from "./common";
3
+ import { Client } from "viem";
4
+ import { readContract } from "viem/actions";
5
+ import { Table } from "./configToTables";
6
+
7
+ export async function getTableValue<table extends Table>({
8
+ client,
9
+ worldDeploy,
10
+ table,
11
+ key,
12
+ }: {
13
+ readonly client: Client;
14
+ readonly worldDeploy: WorldDeploy;
15
+ readonly table: table;
16
+ readonly key: SchemaToPrimitives<table["keySchema"]>;
17
+ }): Promise<SchemaToPrimitives<table["valueSchema"]>> {
18
+ const [staticData, encodedLengths, dynamicData] = await readContract(client, {
19
+ blockNumber: worldDeploy.stateBlock,
20
+ address: worldDeploy.address,
21
+ abi: worldAbi,
22
+ functionName: "getRecord",
23
+ args: [table.tableId, encodeKey(table.keySchema, key)],
24
+ });
25
+ return decodeValueArgs(table.valueSchema, {
26
+ staticData,
27
+ encodedLengths,
28
+ dynamicData,
29
+ });
30
+ }
@@ -0,0 +1,59 @@
1
+ import { Client, parseAbiItem, decodeAbiParameters, parseAbiParameters } from "viem";
2
+ import { Table } from "./configToTables";
3
+ import { hexToResource } from "@latticexyz/common";
4
+ import { WorldDeploy, storeTables } from "./common";
5
+ import { debug } from "./debug";
6
+ import { storeSetRecordEvent } from "@latticexyz/store";
7
+ import { getLogs } from "viem/actions";
8
+ import { KeySchema, ValueSchema, decodeKey, decodeValueArgs, hexToSchema } from "@latticexyz/protocol-parser";
9
+
10
+ export async function getTables({
11
+ client,
12
+ worldDeploy,
13
+ }: {
14
+ readonly client: Client;
15
+ readonly worldDeploy: WorldDeploy;
16
+ }): Promise<readonly Table[]> {
17
+ // This assumes we only use `Tables._set(...)`, which is true as of this writing.
18
+ // TODO: PR to viem's getLogs to accept topics array so we can filter on all store events and quickly recreate this table's current state
19
+ // TODO: consider moving this to a batched getRecord for Tables table
20
+
21
+ debug("looking up tables for", worldDeploy.address);
22
+ const logs = await getLogs(client, {
23
+ strict: true,
24
+ // this may fail for certain RPC providers with block range limits
25
+ // if so, could potentially use our fetchLogs helper (which does pagination)
26
+ fromBlock: worldDeploy.deployBlock,
27
+ toBlock: worldDeploy.stateBlock,
28
+ address: worldDeploy.address,
29
+ event: parseAbiItem(storeSetRecordEvent),
30
+ args: { tableId: storeTables.store_Tables.tableId },
31
+ });
32
+
33
+ // TODO: combine with store-sync logToTable and export from somewhere
34
+ const tables = logs.map((log) => {
35
+ const { tableId } = decodeKey(storeTables.store_Tables.keySchema, log.args.keyTuple);
36
+ const { namespace, name } = hexToResource(tableId);
37
+ const value = decodeValueArgs(storeTables.store_Tables.valueSchema, log.args);
38
+
39
+ // TODO: migrate to better helper
40
+ const keySchemaFields = hexToSchema(value.keySchema);
41
+ const valueSchemaFields = hexToSchema(value.valueSchema);
42
+ const keyNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedKeyNames)[0];
43
+ const fieldNames = decodeAbiParameters(parseAbiParameters("string[]"), value.abiEncodedFieldNames)[0];
44
+
45
+ const valueAbiTypes = [...valueSchemaFields.staticFields, ...valueSchemaFields.dynamicFields];
46
+
47
+ const keySchema = Object.fromEntries(
48
+ keySchemaFields.staticFields.map((abiType, i) => [keyNames[i], abiType])
49
+ ) as KeySchema;
50
+ const valueSchema = Object.fromEntries(valueAbiTypes.map((abiType, i) => [fieldNames[i], abiType])) as ValueSchema;
51
+
52
+ return { namespace, name, tableId, keySchema, valueSchema } as const;
53
+ });
54
+ // TODO: filter/detect duplicates?
55
+
56
+ debug("found", tables.length, "tables for", worldDeploy.address);
57
+
58
+ return tables;
59
+ }
@@ -0,0 +1,39 @@
1
+ import { Client, Address, getAddress, parseAbi } from "viem";
2
+ import { getBlockNumber, getLogs } from "viem/actions";
3
+ import { WorldDeploy, worldDeployEvents } from "./common";
4
+ import { debug } from "./debug";
5
+ import { logsToWorldDeploy } from "./logsToWorldDeploy";
6
+
7
+ const deploys = new Map<Address, WorldDeploy>();
8
+
9
+ export async function getWorldDeploy(client: Client, worldAddress: Address): Promise<WorldDeploy> {
10
+ const address = getAddress(worldAddress);
11
+
12
+ let deploy = deploys.get(address);
13
+ if (deploy != null) {
14
+ return deploy;
15
+ }
16
+
17
+ debug("looking up world deploy for", address);
18
+
19
+ const stateBlock = await getBlockNumber(client);
20
+ const logs = await getLogs(client, {
21
+ strict: true,
22
+ address,
23
+ events: parseAbi(worldDeployEvents),
24
+ // this may fail for certain RPC providers with block range limits
25
+ // if so, could potentially use our fetchLogs helper (which does pagination)
26
+ fromBlock: "earliest",
27
+ toBlock: stateBlock,
28
+ });
29
+
30
+ deploy = {
31
+ ...logsToWorldDeploy(logs),
32
+ stateBlock,
33
+ };
34
+ deploys.set(address, deploy);
35
+
36
+ debug("found world deploy for", address, "at block", deploy.deployBlock);
37
+
38
+ return deploy;
39
+ }
@@ -0,0 +1,49 @@
1
+ import { AbiEventSignatureNotFoundError, Log, decodeEventLog, hexToString, parseAbi, trim } from "viem";
2
+ import { WorldDeploy, worldDeployEvents } from "./common";
3
+ import { isDefined } from "@latticexyz/common/utils";
4
+
5
+ export function logsToWorldDeploy(logs: readonly Log<bigint, number, false>[]): Omit<WorldDeploy, "stateBlock"> {
6
+ const deployLogs = logs
7
+ .map((log) => {
8
+ try {
9
+ return {
10
+ ...log,
11
+ ...decodeEventLog({
12
+ strict: true,
13
+ abi: parseAbi(worldDeployEvents),
14
+ topics: log.topics,
15
+ data: log.data,
16
+ }),
17
+ };
18
+ } catch (error: unknown) {
19
+ if (error instanceof AbiEventSignatureNotFoundError) {
20
+ return;
21
+ }
22
+ throw error;
23
+ }
24
+ })
25
+ .filter(isDefined);
26
+
27
+ // TODO: should this test for/validate that only one of each of these events is present? and that the address/block number don't change between each?
28
+ const { address, deployBlock, worldVersion, storeVersion } = deployLogs.reduce<Partial<WorldDeploy>>(
29
+ (deploy, log) => ({
30
+ ...deploy,
31
+ address: log.address,
32
+ deployBlock: log.blockNumber,
33
+ ...(log.eventName === "HelloWorld"
34
+ ? { worldVersion: hexToString(trim(log.args.worldVersion, { dir: "right" })) }
35
+ : null),
36
+ ...(log.eventName === "HelloStore"
37
+ ? { storeVersion: hexToString(trim(log.args.storeVersion, { dir: "right" })) }
38
+ : null),
39
+ }),
40
+ {}
41
+ );
42
+
43
+ if (address == null) throw new Error("could not find world address");
44
+ if (deployBlock == null) throw new Error("could not find world deploy block number");
45
+ if (worldVersion == null) throw new Error("could not find world version");
46
+ if (storeVersion == null) throw new Error("could not find store version");
47
+
48
+ return { address, deployBlock, worldVersion, storeVersion };
49
+ }
@@ -0,0 +1,154 @@
1
+ import { resolveWorldConfig } from "@latticexyz/world";
2
+ import { Config, ConfigInput, WorldFunction, salt } from "./common";
3
+ import { resourceToHex, hexToResource } from "@latticexyz/common";
4
+ import { resolveWithContext } from "@latticexyz/config";
5
+ import { encodeField } from "@latticexyz/protocol-parser";
6
+ import { SchemaAbiType, SchemaAbiTypeToPrimitiveType } from "@latticexyz/schema-type";
7
+ import {
8
+ getFunctionSelector,
9
+ Hex,
10
+ getCreate2Address,
11
+ getAddress,
12
+ hexToBytes,
13
+ Abi,
14
+ bytesToHex,
15
+ getFunctionSignature,
16
+ } from "viem";
17
+ import { getExistingContracts } from "../utils/getExistingContracts";
18
+ import { defaultModuleContracts } from "../utils/modules/constants";
19
+ import { getContractData } from "../utils/utils/getContractData";
20
+ import { configToTables } from "./configToTables";
21
+ import { deployer } from "./ensureDeployer";
22
+ import { resourceLabel } from "./resourceLabel";
23
+
24
+ // TODO: this should be replaced by https://github.com/latticexyz/mud/issues/1668
25
+
26
+ export function resolveConfig<config extends ConfigInput>({
27
+ config,
28
+ forgeSourceDir,
29
+ forgeOutDir,
30
+ }: {
31
+ config: config;
32
+ forgeSourceDir: string;
33
+ forgeOutDir: string;
34
+ }): Config<config> {
35
+ const tables = configToTables(config);
36
+
37
+ // TODO: should the config parser/loader help with resolving systems?
38
+ const contractNames = getExistingContracts(forgeSourceDir).map(({ basename }) => basename);
39
+ const resolvedConfig = resolveWorldConfig(config, contractNames);
40
+ const baseSystemContractData = getContractData("System", forgeOutDir);
41
+ const baseSystemFunctions = baseSystemContractData.abi
42
+ .filter((item): item is typeof item & { type: "function" } => item.type === "function")
43
+ .map(getFunctionSignature);
44
+
45
+ const systems = Object.entries(resolvedConfig.systems).map(([systemName, system]) => {
46
+ const namespace = config.namespace;
47
+ const name = system.name;
48
+ const systemId = resourceToHex({ type: "system", namespace, name });
49
+ const contractData = getContractData(systemName, forgeOutDir);
50
+
51
+ const systemFunctions = contractData.abi
52
+ .filter((item): item is typeof item & { type: "function" } => item.type === "function")
53
+ .map(getFunctionSignature)
54
+ .filter((sig) => !baseSystemFunctions.includes(sig))
55
+ .map((sig): WorldFunction => {
56
+ // TODO: figure out how to not duplicate contract behavior (https://github.com/latticexyz/mud/issues/1708)
57
+ const worldSignature = namespace === "" ? sig : `${namespace}_${name}_${sig}`;
58
+ return {
59
+ signature: worldSignature,
60
+ selector: getFunctionSelector(worldSignature),
61
+ systemId,
62
+ systemFunctionSignature: sig,
63
+ systemFunctionSelector: getFunctionSelector(sig),
64
+ };
65
+ });
66
+
67
+ return {
68
+ namespace,
69
+ name,
70
+ systemId,
71
+ allowAll: system.openAccess,
72
+ allowedAddresses: system.accessListAddresses as Hex[],
73
+ allowedSystemIds: system.accessListSystems.map((name) =>
74
+ resourceToHex({ type: "system", namespace, name: resolvedConfig.systems[name].name })
75
+ ),
76
+ address: getCreate2Address({ from: deployer, bytecode: contractData.bytecode, salt }),
77
+ bytecode: contractData.bytecode,
78
+ abi: contractData.abi,
79
+ functions: systemFunctions,
80
+ };
81
+ });
82
+
83
+ // resolve allowedSystemIds
84
+ // TODO: resolve this at deploy time so we can allow for arbitrary system IDs registered in the world as the source-of-truth rather than config
85
+ const systemsWithAccess = systems.map(({ allowedAddresses, allowedSystemIds, ...system }) => {
86
+ const allowedSystemAddresses = allowedSystemIds.map((systemId) => {
87
+ const targetSystem = systems.find((s) => s.systemId === systemId);
88
+ if (!targetSystem) {
89
+ throw new Error(
90
+ `System ${resourceLabel(system)} wanted access to ${resourceLabel(
91
+ hexToResource(systemId)
92
+ )}, but it wasn't found in the config.`
93
+ );
94
+ }
95
+ return targetSystem.address;
96
+ });
97
+ return {
98
+ ...system,
99
+ allowedAddresses: Array.from(
100
+ new Set([...allowedAddresses, ...allowedSystemAddresses].map((addr) => getAddress(addr)))
101
+ ),
102
+ };
103
+ });
104
+
105
+ // ugh (https://github.com/latticexyz/mud/issues/1668)
106
+ const resolveContext = {
107
+ tableIds: Object.fromEntries(
108
+ Object.entries(config.tables).map(([tableName, table]) => [
109
+ tableName,
110
+ hexToBytes(
111
+ resourceToHex({
112
+ type: table.offchainOnly ? "offchainTable" : "table",
113
+ namespace: config.namespace,
114
+ name: table.name,
115
+ })
116
+ ),
117
+ ])
118
+ ),
119
+ };
120
+
121
+ const defaultModules = defaultModuleContracts.map((mod) => ({
122
+ name: mod.name,
123
+ bytecode: (typeof mod.bytecode === "string" ? mod.bytecode : mod.bytecode.object) as Hex,
124
+ abi: mod.abi as Abi,
125
+ }));
126
+
127
+ const modules = config.modules.map((mod) => {
128
+ const contractData =
129
+ defaultModules.find((defaultMod) => defaultMod.name === mod.name) ?? getContractData(mod.name, forgeOutDir);
130
+ const installArgs = mod.args
131
+ .map((arg) => resolveWithContext(arg, resolveContext))
132
+ .map((arg) => {
133
+ const value = arg.value instanceof Uint8Array ? bytesToHex(arg.value) : arg.value;
134
+ return encodeField(arg.type as SchemaAbiType, value as SchemaAbiTypeToPrimitiveType<SchemaAbiType>);
135
+ });
136
+ if (installArgs.length > 1) {
137
+ throw new Error(`${mod.name} module should only have 0-1 args, but had ${installArgs.length} args.`);
138
+ }
139
+ return {
140
+ name: mod.name,
141
+ installAsRoot: mod.root,
142
+ installData: installArgs.length === 0 ? "0x" : installArgs[0],
143
+ address: getCreate2Address({ from: deployer, bytecode: contractData.bytecode, salt }),
144
+ bytecode: contractData.bytecode,
145
+ abi: contractData.abi,
146
+ };
147
+ });
148
+
149
+ return {
150
+ tables,
151
+ systems: systemsWithAccess,
152
+ modules,
153
+ };
154
+ }
@@ -0,0 +1,3 @@
1
+ export function resourceLabel({ namespace, name }: { readonly namespace: string; readonly name: string }): string {
2
+ return `${namespace}:${name}`;
3
+ }
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export * from "./utils/deployHandler";
1
+ // nothing to export