From 8d724086f9f54e696c2c5eb048f4f50437f48807 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 20 Aug 2024 16:48:44 +0100 Subject: [PATCH 01/23] chore: bump arktype, attest --- package.json | 2 +- packages/store/package.json | 2 +- packages/world/package.json | 2 +- pnpm-lock.yaml | 94 ++++++++++++++++++------------------- pnpm-workspace.yaml | 4 +- 5 files changed, 51 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 2b1a5e4cc9..629c816786 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "package.json": "pnpm sort-package-json" }, "devDependencies": { - "@ark/attest": "0.11.0", + "@ark/attest": "catalog:", "@changesets/cli": "^2.27.7", "@types/node": "^18.15.11", "@typescript-eslint/eslint-plugin": "7.1.1", diff --git a/packages/store/package.json b/packages/store/package.json index 30e1762ce3..09a1978fc5 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -65,7 +65,7 @@ "@latticexyz/protocol-parser": "workspace:*", "@latticexyz/schema-type": "workspace:*", "abitype": "catalog:", - "arktype": "1.0.29-alpha", + "arktype": "catalog:", "viem": "catalog:" }, "devDependencies": { diff --git a/packages/world/package.json b/packages/world/package.json index 88581f348b..91cd52dc58 100644 --- a/packages/world/package.json +++ b/packages/world/package.json @@ -66,7 +66,7 @@ "@latticexyz/schema-type": "workspace:*", "@latticexyz/store": "workspace:*", "abitype": "catalog:", - "arktype": "1.0.29-alpha", + "arktype": "catalog:", "debug": "^4.3.4", "viem": "catalog:" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d5d78bf6c..c7b2574f05 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,12 +6,18 @@ settings: catalogs: default: + '@ark/attest': + specifier: 0.12.1 + version: 0.12.1 '@ark/util': - specifier: 0.1.2 - version: 0.1.2 + specifier: 0.2.2 + version: 0.2.2 abitype: specifier: 1.0.5 version: 1.0.5 + arktype: + specifier: 2.0.0-beta.6 + version: 2.0.0-beta.6 viem: specifier: 2.19.8 version: 2.19.8 @@ -21,8 +27,8 @@ importers: .: devDependencies: '@ark/attest': - specifier: 0.11.0 - version: 0.11.0(typescript@5.4.2) + specifier: 'catalog:' + version: 0.12.1(typescript@5.4.2) '@changesets/cli': specifier: ^2.27.7 version: 2.27.7 @@ -329,7 +335,7 @@ importers: dependencies: '@ark/util': specifier: 'catalog:' - version: 0.1.2 + version: 0.2.2 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -560,7 +566,7 @@ importers: dependencies: '@ark/util': specifier: 'catalog:' - version: 0.1.2 + version: 0.2.2 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -718,7 +724,7 @@ importers: dependencies: '@ark/util': specifier: 'catalog:' - version: 0.1.2 + version: 0.2.2 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -735,8 +741,8 @@ importers: specifier: 'catalog:' version: 1.0.5(typescript@5.4.2)(zod@3.23.8) arktype: - specifier: 1.0.29-alpha - version: 1.0.29-alpha + specifier: 'catalog:' + version: 2.0.0-beta.6 viem: specifier: 'catalog:' version: 2.19.8(typescript@5.4.2)(zod@3.23.8) @@ -894,7 +900,7 @@ importers: dependencies: '@ark/util': specifier: 'catalog:' - version: 0.1.2 + version: 0.2.2 '@latticexyz/block-logs-stream': specifier: workspace:* version: link:../block-logs-stream @@ -1016,7 +1022,7 @@ importers: dependencies: '@ark/util': specifier: 'catalog:' - version: 0.1.2 + version: 0.2.2 '@latticexyz/common': specifier: workspace:* version: link:../common @@ -1036,8 +1042,8 @@ importers: specifier: 'catalog:' version: 1.0.5(typescript@5.4.2)(zod@3.23.8) arktype: - specifier: 1.0.29-alpha - version: 1.0.29-alpha + specifier: 'catalog:' + version: 2.0.0-beta.6 debug: specifier: ^4.3.4 version: 4.3.4 @@ -1254,23 +1260,20 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} - '@ark/attest@0.11.0': - resolution: {integrity: sha512-KTLvnB8mjH967z9ktzlXd3q/nVLddenjR7cWJR+u2DJh9NPSSQzwoDf30ab+5VtKf0abVzRxLfqRk80T3uGD3Q==} + '@ark/attest@0.12.1': + resolution: {integrity: sha512-cx6oSOJU6WiADVV9xB6CPwTP2XeFsnS4jBEqOKOkSZoA+tvTHBlVCVzCGtF+RFNx8/HS1Suxpafcd0xRWCynIQ==} hasBin: true peerDependencies: typescript: '*' - '@ark/fs@0.2.0': - resolution: {integrity: sha512-nmrCG9XPHbz5Vg4wqxro1UMksPCZk4crxVa/XbWQuHfxFa6e9H+BNPhcehNEl5ePVK3Wzv/Y4aoy4vNqYZ4Ajg==} + '@ark/fs@0.2.2': + resolution: {integrity: sha512-t0mCeAB5agypidgxeUhMjOkql8R5wt6EWJKQRWAX7WMI7ET7icSJQDAgWbwkfNHW1dclWLajTjhwfyG00LPkZg==} - '@ark/schema@0.3.0': - resolution: {integrity: sha512-Xw6MkotVCgmmZ/OYFIXZ4DIbchNI0Okl20xrF7aZGouODZ75bED0EVyYsLDVrQ3PSKr0G4WOQbGCfM1FuN5oJw==} + '@ark/schema@0.3.3': + resolution: {integrity: sha512-SA4QggzaKHxkNB+faWmhSJbCPMHmGBCpb6dNG3VIt5VOMZhZJJSD76/tOUzQvvJNzztHkTakrTZea+iKsEDcRQ==} - '@ark/util@0.1.2': - resolution: {integrity: sha512-JhcPZhF8T8Eb9UxJqh3X8tOwZpnuW89azKlPnRhFXymhy/wBFPRC9+J+Vu+iUc/kplW3uloZb9n5R3EVVWUBgw==} - - '@ark/util@0.2.0': - resolution: {integrity: sha512-L38ALnAR1wXxYQN9pn0ir+9ybPKA0O1+hl8PKZOvT9JSbTues4kEB8lgo80DeE0X1Jw+qoHe4lxgSwaHNa3xyg==} + '@ark/util@0.2.2': + resolution: {integrity: sha512-ryZ4+f3SlReQRH9nTFLK5EeU1Pan5ZfS+ACPSk0ir5uujJouFmvOdnkVfeAJAgeOb3kKmUM9kjelv1cwH2ScZg==} '@aws-crypto/ie11-detection@3.0.0': resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} @@ -2776,8 +2779,8 @@ packages: resolution: {integrity: sha512-RnlSOPh14QbopGCApgkSx5UBgGda5MX1cHqp2fsqfiDyCwGL/m1jaeB9fzu7didVS81LQqGZZuxFBcg8YU8EVw==} hasBin: true - '@typescript/vfs@1.5.3': - resolution: {integrity: sha512-OSZ/o3wwD5VPZVdGGsXWk7sRGRtwrGnqA4zwmb33FTs7Wxmad0QTkQCbaNyqWA8hL09TCwAthdp8yjFA5G1lvw==} + '@typescript/vfs@1.6.0': + resolution: {integrity: sha512-hvJUjNVeBMp77qPINuUvYXj4FyWeeMMKZkxEATEU3hqBAQ7qdTBCUFT7Sp0Zu0faeEtFf+ldXxMEDr/bk73ISg==} peerDependencies: typescript: '*' @@ -2991,11 +2994,8 @@ packages: resolution: {integrity: sha512-XLWeRTNBJRzQkbMweLIxdtnvpE7iYUBraPwrIJX57FjL4D1RHLMJRM1AyEP6KZHgvjW7TSnxF8MpGic7YdTGOA==} engines: {node: '>=8', npm: '>=5'} - arktype@1.0.29-alpha: - resolution: {integrity: sha512-glMLgVhIQRSkR3tymiS+POAcWVJH09sfrgic0jHnyFL8BlhHAJZX2BzdImU9zYr1y9NBqy+U93ZNrRTHXsKRDw==} - - arktype@2.0.0-beta.3: - resolution: {integrity: sha512-a08Q7Mj+5hes+UvV9CdqWoXPc/qdYpCIANcg8Bhs+iYyIdKGxMpj976oevd19uRkx0qtqMwS/O4QeyvHT0/oRQ==} + arktype@2.0.0-beta.6: + resolution: {integrity: sha512-tbH5/h0z371sgrJIAhZhH2BcrErWv8uQIPVcLmknJ8ffov5/ZbMNufrQ3hG9avGKTcVnVmdQoPhl1WuKuagqXA==} array-includes@3.1.6: resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} @@ -6828,28 +6828,26 @@ snapshots: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.18 - '@ark/attest@0.11.0(typescript@5.4.2)': + '@ark/attest@0.12.1(typescript@5.4.2)': dependencies: - '@ark/fs': 0.2.0 - '@ark/util': 0.2.0 + '@ark/fs': 0.2.2 + '@ark/util': 0.2.2 '@prettier/sync': 0.5.2(prettier@3.3.3) '@typescript/analyze-trace': 0.10.1 - '@typescript/vfs': 1.5.3(typescript@5.4.2) - arktype: 2.0.0-beta.3 + '@typescript/vfs': 1.6.0(typescript@5.4.2) + arktype: 2.0.0-beta.6 prettier: 3.3.3 typescript: 5.4.2 transitivePeerDependencies: - supports-color - '@ark/fs@0.2.0': {} + '@ark/fs@0.2.2': {} - '@ark/schema@0.3.0': + '@ark/schema@0.3.3': dependencies: - '@ark/util': 0.2.0 + '@ark/util': 0.2.2 - '@ark/util@0.1.2': {} - - '@ark/util@0.2.0': {} + '@ark/util@0.2.2': {} '@aws-crypto/ie11-detection@3.0.0': dependencies: @@ -8851,7 +8849,7 @@ snapshots: treeify: 1.1.0 yargs: 16.2.0 - '@typescript/vfs@1.5.3(typescript@5.4.2)': + '@typescript/vfs@1.6.0(typescript@5.4.2)': dependencies: debug: 4.3.4 typescript: 5.4.2 @@ -9050,12 +9048,10 @@ snapshots: dependencies: iftype: 4.0.9 - arktype@1.0.29-alpha: {} - - arktype@2.0.0-beta.3: + arktype@2.0.0-beta.6: dependencies: - '@ark/schema': 0.3.0 - '@ark/util': 0.2.0 + '@ark/schema': 0.3.3 + '@ark/util': 0.2.2 array-includes@3.1.6: dependencies: @@ -13385,7 +13381,7 @@ snapshots: yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.1.1 + escalade: 3.1.2 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3b01a503f2..6777516044 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,8 @@ packages: - test/* catalog: - "@ark/util": "0.1.2" + "@ark/attest": "0.12.1" + "@ark/util": "0.2.2" "abitype": "1.0.5" + "arktype": "2.0.0-beta.6" "viem": "2.19.8" From f2eb32360780221062338f4b9bc086f203534bc8 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 20 Aug 2024 17:14:38 +0100 Subject: [PATCH 02/23] fix build error --- packages/store-sync/src/recs/createStorageAdapter.ts | 2 +- packages/store-sync/src/zustand/syncToZustand.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/store-sync/src/recs/createStorageAdapter.ts b/packages/store-sync/src/recs/createStorageAdapter.ts index 06694458bf..400f01b14d 100644 --- a/packages/store-sync/src/recs/createStorageAdapter.ts +++ b/packages/store-sync/src/recs/createStorageAdapter.ts @@ -35,7 +35,7 @@ export function createStorageAdapter({ const components = { ...tablesToComponents(world, tables), ...defineInternalComponents(world), - }; + } as CreateStorageAdapterResult["components"]; async function recsStorageAdapter({ logs }: StorageAdapterBlock): Promise { const newTables = logs.filter(isTableRegistrationLog).map(logToTable); diff --git a/packages/store-sync/src/zustand/syncToZustand.ts b/packages/store-sync/src/zustand/syncToZustand.ts index e8597935dc..a814b45c04 100644 --- a/packages/store-sync/src/zustand/syncToZustand.ts +++ b/packages/store-sync/src/zustand/syncToZustand.ts @@ -35,11 +35,11 @@ export async function syncToZustand): Promise> { - const tables: merge, extraTables>, mudTables> = { + const tables = { ...configToTables(config), ...extraTables, ...mudTables, - }; + } as unknown as merge, extraTables>, mudTables>; const useStore = store ?? createStore({ tables }); const storageAdapter = createStorageAdapter({ store: useStore }); From 59f52c70ffc5d7e17493440acd70ba1d29fe8374 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 20 Aug 2024 19:06:25 +0100 Subject: [PATCH 03/23] initial local system manifest --- .gitignore | 1 + .../src/codegen/utils/contractToInterface.ts | 9 ++- packages/common/src/utils/indent.ts | 3 + packages/common/src/utils/index.ts | 1 + packages/world/ts/node/buildSystemManifest.ts | 47 ++++++++++++ packages/world/ts/node/common.ts | 25 +++++++ .../world/ts/node/findContractArtifacts.ts | 71 +++++++++++++++++++ packages/world/ts/node/index.ts | 1 + .../world/ts/node/render-solidity/worldgen.ts | 11 ++- packages/world/ts/node/types.ts | 35 +++++++++ 10 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 packages/common/src/utils/indent.ts create mode 100644 packages/world/ts/node/buildSystemManifest.ts create mode 100644 packages/world/ts/node/common.ts create mode 100644 packages/world/ts/node/findContractArtifacts.ts create mode 100644 packages/world/ts/node/types.ts diff --git a/.gitignore b/.gitignore index 9e381979cd..91920e91c6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ test-data/world-logs-query.json .attest .tstrace +.mud diff --git a/packages/common/src/codegen/utils/contractToInterface.ts b/packages/common/src/codegen/utils/contractToInterface.ts index 2e1e83e907..88c757b249 100644 --- a/packages/common/src/codegen/utils/contractToInterface.ts +++ b/packages/common/src/codegen/utils/contractToInterface.ts @@ -27,21 +27,20 @@ interface SymbolImport { /** * Parse the contract data to get the functions necessary to generate an interface, * and symbols to import from the original contract. - * @param data contents of a file with the solidity contract + * @param source contents of a file with the solidity contract * @param contractName name of the contract * @returns interface data */ export function contractToInterface( - data: string, + source: string, contractName: string, ): { functions: ContractInterfaceFunction[]; errors: ContractInterfaceError[]; symbolImports: SymbolImport[]; } { - const ast = parse(data); - - const contractNode = findContractNode(parse(data), contractName); + const ast = parse(source); + const contractNode = findContractNode(ast, contractName); let symbolImports: SymbolImport[] = []; const functions: ContractInterfaceFunction[] = []; const errors: ContractInterfaceError[] = []; diff --git a/packages/common/src/utils/indent.ts b/packages/common/src/utils/indent.ts new file mode 100644 index 0000000000..39e691bb8a --- /dev/null +++ b/packages/common/src/utils/indent.ts @@ -0,0 +1,3 @@ +export function indent(message: string, indentation = " "): string { + return message.replaceAll(/(^|\n)/g, `$1${indentation}`); +} diff --git a/packages/common/src/utils/index.ts b/packages/common/src/utils/index.ts index 2e4b585754..699c4cd91f 100644 --- a/packages/common/src/utils/index.ts +++ b/packages/common/src/utils/index.ts @@ -6,6 +6,7 @@ export * from "./chunk"; export * from "./groupBy"; export * from "./identity"; export * from "./includes"; +export * from "./indent"; export * from "./isDefined"; export * from "./isNotNull"; export * from "./iteratorToArray"; diff --git a/packages/world/ts/node/buildSystemManifest.ts b/packages/world/ts/node/buildSystemManifest.ts new file mode 100644 index 0000000000..70d4a7cf9a --- /dev/null +++ b/packages/world/ts/node/buildSystemManifest.ts @@ -0,0 +1,47 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import { ResolvedSystem, resolveSystems } from "./resolveSystems"; +import { World } from "../config/v2"; +import { ContractArtifact } from "./common"; +import { findContractArtifacts } from "./findContractArtifacts"; +import { getOutDirectory as getForgeOutDirectory } from "@latticexyz/common/foundry"; +import path from "node:path"; +import { Hex, Abi } from "viem"; + +export type SystemManifest = { + readonly systems: readonly { + readonly systemId: Hex; + readonly abi: Abi; + }[]; +}; + +export async function buildSystemManifest(opts: { rootDir: string; config: World }): Promise { + const systems = await resolveSystems(opts); + + // TODO: expose a `cwd` option to make sure this runs relative to `rootDir` + const forgeOutDir = await getForgeOutDirectory(); + const contractArtifacts = await findContractArtifacts({ forgeOutDir }); + + function getSystemArtifact(system: ResolvedSystem): ContractArtifact { + const artifact = contractArtifacts.find((a) => a.sourcePath === system.sourcePath && a.name === system.label); + if (!artifact) { + throw new Error( + `Could not find build artifact for system \`${system.label}\` at \`${system.sourcePath}\`. Did \`forge build\` run successfully?`, + ); + } + return artifact; + } + + const manifest = { + systems: systems.map((system) => { + const artifact = getSystemArtifact(system); + return { + systemId: system.systemId, + abi: artifact.abi, + }; + }), + } satisfies SystemManifest; + + const outFile = path.join(opts.rootDir, ".mud/local/systems.json"); + await mkdir(path.dirname(outFile), { recursive: true }); + await writeFile(outFile, JSON.stringify(manifest, null, 2)); +} diff --git a/packages/world/ts/node/common.ts b/packages/world/ts/node/common.ts new file mode 100644 index 0000000000..e72111f6b9 --- /dev/null +++ b/packages/world/ts/node/common.ts @@ -0,0 +1,25 @@ +import { Abi, Hex } from "viem"; + +// https://eips.ethereum.org/EIPS/eip-170 +export const contractSizeLimit = parseInt("6000", 16); + +export type ReferenceIdentifier = { + /** + * Path to source file, e.g. `src/SomeLib.sol` + */ + sourcePath: string; + /** + * Reference name, e.g. `SomeLib` + */ + name: string; +}; + +export type PendingBytecode = readonly (Hex | ReferenceIdentifier)[]; + +export type ContractArtifact = { + readonly sourcePath: string; + readonly name: string; + // TODO: rename `createCode` or `creationBytecode` to better differentiate from deployed bytecode? + readonly bytecode: PendingBytecode; + readonly abi: Abi; +}; diff --git a/packages/world/ts/node/findContractArtifacts.ts b/packages/world/ts/node/findContractArtifacts.ts new file mode 100644 index 0000000000..8847460e74 --- /dev/null +++ b/packages/world/ts/node/findContractArtifacts.ts @@ -0,0 +1,71 @@ +import path from "node:path"; +import { readFile } from "node:fs/promises"; +import { glob } from "glob"; +import { type } from "arktype"; +import { indent, isDefined } from "@latticexyz/common/utils"; +import { Hex, size, sliceHex } from "viem"; +import { ContractArtifact, ReferenceIdentifier, contractSizeLimit } from "./common"; +import { types } from "./types"; + +export type Input = { + readonly forgeOutDir: string; +}; + +export type Output = readonly ContractArtifact[]; + +const parseArtifact = type("parse.json").to(types.Artifact); + +export async function findContractArtifacts({ forgeOutDir }: Input): Promise { + const files = (await glob("**/*.sol/*.json", { ignore: "**/*.abi.json", cwd: forgeOutDir })).sort(); + const artifactsJson = await Promise.all( + files.map(async (filename) => ({ + filename, + json: await readFile(path.join(forgeOutDir, filename), "utf8"), + })), + ); + + return artifactsJson + .map(({ filename, json }) => { + const artifact = parseArtifact(json); + if (artifact instanceof type.errors) { + // TODO: replace with debug + console.warn(`Skipping invalid artifact at \`${filename}\`:\n\n${indent(artifact.message)}\n`); + return; + } + return artifact; + }) + .filter(isDefined) + .map((artifact) => { + const sourcePath = Object.keys(artifact.metadata.settings.compilationTarget)[0]; + const name = artifact.metadata.settings.compilationTarget[sourcePath]; + const deployedBytecodeSize = size(artifact.deployedBytecode.object); + + if (deployedBytecodeSize > contractSizeLimit) { + console.warn( + // eslint-disable-next-line max-len + `\nBytecode for \`${name}\` at \`${sourcePath}\` (${deployedBytecodeSize} bytes) is over the contract size limit (${contractSizeLimit} bytes). Run \`forge build --sizes\` for more info.\n`, + ); + } else if (deployedBytecodeSize > contractSizeLimit * 0.95) { + console.warn( + // eslint-disable-next-line max-len + `\nBytecode for \`${name}\` at \`${sourcePath}\` (${deployedBytecodeSize} bytes) is almost over the contract size limit (${contractSizeLimit} bytes). Run \`forge build --sizes\` for more info.\n`, + ); + } + + const bytecode = artifact.bytecode.object; + const placeholders = Object.entries(artifact.bytecode.linkReferences ?? {}).flatMap(([sourcePath, names]) => + Object.entries(names).flatMap(([name, slices]) => slices.map((slice) => ({ sourcePath, name, ...slice }))), + ); + + const pendingBytecode: (Hex | ReferenceIdentifier)[] = []; + let offset = 0; + for (const { sourcePath, name, start, length } of placeholders) { + pendingBytecode.push(sliceHex(bytecode, offset, start)); + pendingBytecode.push({ sourcePath, name }); + offset = start + length; + } + pendingBytecode.push(sliceHex(bytecode, offset)); + + return { sourcePath, name, bytecode: pendingBytecode, abi: artifact.abi }; + }); +} diff --git a/packages/world/ts/node/index.ts b/packages/world/ts/node/index.ts index e73f48effc..73e6c46583 100644 --- a/packages/world/ts/node/index.ts +++ b/packages/world/ts/node/index.ts @@ -2,3 +2,4 @@ export * from "./render-solidity"; export * from "./findSolidityFiles"; export * from "./getSystemContracts"; export * from "./resolveSystems"; +export * from "./buildSystemManifest"; diff --git a/packages/world/ts/node/render-solidity/worldgen.ts b/packages/world/ts/node/render-solidity/worldgen.ts index 097558e681..88f5a79c4f 100644 --- a/packages/world/ts/node/render-solidity/worldgen.ts +++ b/packages/world/ts/node/render-solidity/worldgen.ts @@ -5,6 +5,10 @@ import { renderSystemInterface } from "./renderSystemInterface"; import { renderWorldInterface } from "./renderWorldInterface"; import { World as WorldConfig } from "../../config/v2/output"; import { resolveSystems } from "../resolveSystems"; +import { debug as parentDebug } from "../../debug"; +import { buildSystemManifest } from "../buildSystemManifest"; + +const debug = parentDebug.extend("worldgen"); export async function worldgen({ rootDir, @@ -49,9 +53,9 @@ export async function worldgen({ await Promise.all( systems.map(async (system) => { - const data = await fs.readFile(path.join(rootDir, system.sourcePath), "utf8"); + const source = await fs.readFile(path.join(rootDir, system.sourcePath), "utf8"); // get external functions from a contract - const { functions, errors, symbolImports } = contractToInterface(data, system.label); + const { functions, errors, symbolImports } = contractToInterface(source, system.label); const imports = symbolImports.map( ({ symbol, path: importPath }): ImportDatum => ({ symbol, @@ -85,4 +89,7 @@ export async function worldgen({ }); // write to file await formatAndWriteSolidity(output, outputPath, "Generated world interface"); + + debug("Building system manifest"); + await buildSystemManifest({ rootDir, config }); } diff --git a/packages/world/ts/node/types.ts b/packages/world/ts/node/types.ts new file mode 100644 index 0000000000..d49fee444e --- /dev/null +++ b/packages/world/ts/node/types.ts @@ -0,0 +1,35 @@ +import { scope, type } from "arktype"; +import { Hex, Abi, isHex } from "viem"; + +export const types = scope({ + Bytecode: type("string").narrow( + (input, ctx): input is Hex => isHex(input, { strict: false }) || ctx.mustBe("a hex string"), + ), + Slice: { start: "number", length: "number" }, + LinkReferences: { + // key is source filename like `src/WorldResourceId.sol` + "[string]": { + // key is library name like `WorldResourceIdLib` + "[string]": "Slice[]", + }, + }, + ArtifactBytecode: { + object: "Bytecode", + "linkReferences?": "LinkReferences", + }, + Artifact: { + bytecode: "ArtifactBytecode", + deployedBytecode: "ArtifactBytecode", + // TODO: improve narrowing with `isAbi` or import arktype type from abitype (when either are available) + abi: type("unknown[]").pipe((input) => input as Abi), + metadata: { + settings: { + compilationTarget: { + // key is source filename name like `src/WorldResourceId.sol` + // value is contract name like `WorldResourceIdLib` + "[string]": "string", + }, + }, + }, + }, +}).export(); From d5b2cbd8da2d01428d8dd460ca3a77189901692f Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 20 Aug 2024 19:41:27 +0100 Subject: [PATCH 04/23] write system abi --- packages/config/src/common.ts | 2 +- ...temManifest.ts => buildSystemsManifest.ts} | 36 +++++++++++++++---- packages/world/ts/node/index.ts | 1 - .../world/ts/node/render-solidity/worldgen.ts | 4 +-- packages/world/ts/node/resolveSystems.ts | 4 +++ 5 files changed, 37 insertions(+), 10 deletions(-) rename packages/world/ts/node/{buildSystemManifest.ts => buildSystemsManifest.ts} (52%) diff --git a/packages/config/src/common.ts b/packages/config/src/common.ts index d56e487acd..104da58979 100644 --- a/packages/config/src/common.ts +++ b/packages/config/src/common.ts @@ -24,10 +24,10 @@ export type Schema = { }; export type Table = { + readonly namespaceLabel: string; readonly label: string; readonly type: satisfy; readonly namespace: string; - readonly namespaceLabel: string; readonly name: string; readonly tableId: Hex; readonly schema: Schema; diff --git a/packages/world/ts/node/buildSystemManifest.ts b/packages/world/ts/node/buildSystemsManifest.ts similarity index 52% rename from packages/world/ts/node/buildSystemManifest.ts rename to packages/world/ts/node/buildSystemsManifest.ts index 70d4a7cf9a..0b3ff5b59a 100644 --- a/packages/world/ts/node/buildSystemManifest.ts +++ b/packages/world/ts/node/buildSystemsManifest.ts @@ -6,15 +6,27 @@ import { findContractArtifacts } from "./findContractArtifacts"; import { getOutDirectory as getForgeOutDirectory } from "@latticexyz/common/foundry"; import path from "node:path"; import { Hex, Abi } from "viem"; +import { formatAbi, formatAbiItem } from "abitype"; +import IBaseWorldAbi from "../../out/IBaseWorld.sol/IBaseWorld.abi.json"; +import SystemAbi from "../../out/System.sol/System.abi.json"; -export type SystemManifest = { +const excludedAbi = formatAbi([ + ...IBaseWorldAbi.filter((item) => item.type === "event" || item.type === "error"), + ...SystemAbi, +] as Abi); + +export type SystemsManifest = { readonly systems: readonly { + readonly namespaceLabel: string; + readonly label: string; + readonly namespace: string; + readonly name: string; readonly systemId: Hex; - readonly abi: Abi; + readonly abi: string[]; }[]; }; -export async function buildSystemManifest(opts: { rootDir: string; config: World }): Promise { +export async function buildSystemsManifest(opts: { rootDir: string; config: World }): Promise { const systems = await resolveSystems(opts); // TODO: expose a `cwd` option to make sure this runs relative to `rootDir` @@ -34,14 +46,26 @@ export async function buildSystemManifest(opts: { rootDir: string; config: World const manifest = { systems: systems.map((system) => { const artifact = getSystemArtifact(system); + const abi = artifact.abi.filter((item) => !excludedAbi.includes(formatAbiItem(item))); + const worldAbi = system.deploy.registerWorldFunctions + ? abi.map((item) => (item.type === "function" ? { ...item, name: `${system.namespace}__${item.name}` } : item)) + : []; return { + // labels + namespaceLabel: system.namespaceLabel, + label: system.label, + // resource ID + namespace: system.namespace, + name: system.name, systemId: system.systemId, - abi: artifact.abi, + // abi + abi: formatAbi(abi).sort((a, b) => a.localeCompare(b)), + worldAbi: formatAbi(worldAbi).sort((a, b) => a.localeCompare(b)), }; }), - } satisfies SystemManifest; + } satisfies SystemsManifest; const outFile = path.join(opts.rootDir, ".mud/local/systems.json"); await mkdir(path.dirname(outFile), { recursive: true }); - await writeFile(outFile, JSON.stringify(manifest, null, 2)); + await writeFile(outFile, JSON.stringify(manifest, null, 2) + "\n"); } diff --git a/packages/world/ts/node/index.ts b/packages/world/ts/node/index.ts index 73e6c46583..e73f48effc 100644 --- a/packages/world/ts/node/index.ts +++ b/packages/world/ts/node/index.ts @@ -2,4 +2,3 @@ export * from "./render-solidity"; export * from "./findSolidityFiles"; export * from "./getSystemContracts"; export * from "./resolveSystems"; -export * from "./buildSystemManifest"; diff --git a/packages/world/ts/node/render-solidity/worldgen.ts b/packages/world/ts/node/render-solidity/worldgen.ts index 88f5a79c4f..ce0e46db3d 100644 --- a/packages/world/ts/node/render-solidity/worldgen.ts +++ b/packages/world/ts/node/render-solidity/worldgen.ts @@ -6,7 +6,7 @@ import { renderWorldInterface } from "./renderWorldInterface"; import { World as WorldConfig } from "../../config/v2/output"; import { resolveSystems } from "../resolveSystems"; import { debug as parentDebug } from "../../debug"; -import { buildSystemManifest } from "../buildSystemManifest"; +import { buildSystemsManifest } from "../buildSystemsManifest"; const debug = parentDebug.extend("worldgen"); @@ -91,5 +91,5 @@ export async function worldgen({ await formatAndWriteSolidity(output, outputPath, "Generated world interface"); debug("Building system manifest"); - await buildSystemManifest({ rootDir, config }); + await buildSystemsManifest({ rootDir, config }); } diff --git a/packages/world/ts/node/resolveSystems.ts b/packages/world/ts/node/resolveSystems.ts index eada4b459f..ee815aa627 100644 --- a/packages/world/ts/node/resolveSystems.ts +++ b/packages/world/ts/node/resolveSystems.ts @@ -5,6 +5,8 @@ import { resolveNamespace } from "../config/v2/namespace"; import { resourceToLabel } from "@latticexyz/common"; export type ResolvedSystem = System & { + // TODO: move to config output + readonly namespaceLabel: string; readonly sourcePath: string; }; @@ -46,6 +48,8 @@ export async function resolveSystems({ }).systems[contract.systemLabel]; return { ...systemConfig, + // TODO: move to config output + namespaceLabel: contract.namespaceLabel, sourcePath: contract.sourcePath, }; }) From 021fcc5eb03f4d29dcdcfa0630343ae70f8ae375 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 20 Aug 2024 19:45:24 +0100 Subject: [PATCH 05/23] comment --- packages/world/ts/node/buildSystemsManifest.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/world/ts/node/buildSystemsManifest.ts b/packages/world/ts/node/buildSystemsManifest.ts index 0b3ff5b59a..02b3a8b570 100644 --- a/packages/world/ts/node/buildSystemsManifest.ts +++ b/packages/world/ts/node/buildSystemsManifest.ts @@ -45,6 +45,8 @@ export async function buildSystemsManifest(opts: { rootDir: string; config: Worl const manifest = { systems: systems.map((system) => { + // TODO: extract this from AST so we can get it before compile time and use it to generate system interfaces? + // we'll may need an extended ABI function definition to include things like visibility? but maybe not needed for interface? const artifact = getSystemArtifact(system); const abi = artifact.abi.filter((item) => !excludedAbi.includes(formatAbiItem(item))); const worldAbi = system.deploy.registerWorldFunctions From bf078a70222419b9580a6704ecbc6b1df289ad0e Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 21 Aug 2024 16:24:38 +0100 Subject: [PATCH 06/23] solidity to abi --- packages/common/package.json | 1 + .../common/src/codegen/solidityToAbi.test.ts | 108 ++++++++++++++++ packages/common/src/codegen/solidityToAbi.ts | 116 ++++++++++++++++++ .../src/codegen/utils/contractToInterface.ts | 4 +- packages/config/src/common.ts | 3 + pnpm-lock.yaml | 3 + 6 files changed, 233 insertions(+), 2 deletions(-) create mode 100644 packages/common/src/codegen/solidityToAbi.test.ts create mode 100644 packages/common/src/codegen/solidityToAbi.ts diff --git a/packages/common/package.json b/packages/common/package.json index aabbcc8cd4..5285a8d0cc 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -68,6 +68,7 @@ "dependencies": { "@latticexyz/schema-type": "workspace:*", "@solidity-parser/parser": "^0.16.0", + "abitype": "catalog:", "debug": "^4.3.4", "execa": "^7.0.0", "p-queue": "^7.4.1", diff --git a/packages/common/src/codegen/solidityToAbi.test.ts b/packages/common/src/codegen/solidityToAbi.test.ts new file mode 100644 index 0000000000..a9706d7653 --- /dev/null +++ b/packages/common/src/codegen/solidityToAbi.test.ts @@ -0,0 +1,108 @@ +import { describe, expect, it } from "vitest"; +import { formatAbi } from "abitype"; +import { solidityToAbi } from "./solidityToAbi"; + +const visibility = ["public", "external", "private", "internal"] as const; +const mutability = [null, "view", "pure", "payable", "nonpayable"] as const; + +const types = { + empty: null, + uint256: "uint256", + uint: "uint", + string: "string", + uintArray: "uint[]", + stringArray: "string[]", + keyTuples: "bytes32[][]", + fixedArray: "bool[2]", + mixedArray: "bool[2][]", + deepArray: "bool[][2][4][8][]", +} as const; + +describe("solidityToAbi", () => { + it("parses error params", () => { + const errors = Object.entries(types).map(([name, type]) => `error param_${name}(${type ? `${type} value` : ""});`); + const source = `contract MyContract {\n ${errors.join("\n ")}\n}`; + + expect(source).toMatchInlineSnapshot(` + "contract MyContract { + error param_empty(); + error param_uint256(uint256 value); + error param_uint(uint value); + error param_string(string value); + error param_uintArray(uint[] value); + error param_stringArray(string[] value); + error param_keyTuples(bytes32[][] value); + error param_fixedArray(bool[2] value); + error param_mixedArray(bool[2][] value); + error param_deepArray(bool[][2][4][8][] value); + }" + `); + + expect( + formatAbi(solidityToAbi({ sourcePath: "test/Test.sol", source, contractName: "MyContract" })), + ).toMatchInlineSnapshot( + ` + [ + "error param_empty()", + "error param_uint256(uint256 value)", + "error param_uint(uint value)", + "error param_string(string value)", + "error param_uintArray(uint[] value)", + "error param_stringArray(string[] value)", + "error param_keyTuples(bytes32[][] value)", + "error param_fixedArray(bool[2] value)", + "error param_mixedArray(bool[2][] value)", + "error param_deepArray(bool[][2][4][8][] value)", + ] + `, + ); + }); + + it("parses public/external functions", () => { + const modifiers = visibility.flatMap((vis) => mutability.map((mut) => [vis, mut] as const)); + + const functions = modifiers.map(([vis, mut]) => `function modifier_${vis}_${mut}() ${vis} ${mut ?? ""} {}`); + const source = `contract MyContract {\n ${functions.join("\n ")}\n}`; + + expect(source).toMatchInlineSnapshot(` + "contract MyContract { + function modifier_public_null() public {} + function modifier_public_view() public view {} + function modifier_public_pure() public pure {} + function modifier_public_payable() public payable {} + function modifier_public_nonpayable() public nonpayable {} + function modifier_external_null() external {} + function modifier_external_view() external view {} + function modifier_external_pure() external pure {} + function modifier_external_payable() external payable {} + function modifier_external_nonpayable() external nonpayable {} + function modifier_private_null() private {} + function modifier_private_view() private view {} + function modifier_private_pure() private pure {} + function modifier_private_payable() private payable {} + function modifier_private_nonpayable() private nonpayable {} + function modifier_internal_null() internal {} + function modifier_internal_view() internal view {} + function modifier_internal_pure() internal pure {} + function modifier_internal_payable() internal payable {} + function modifier_internal_nonpayable() internal nonpayable {} + }" + `); + + expect(formatAbi(solidityToAbi({ sourcePath: "test/Test.sol", source, contractName: "MyContract" }))) + .toMatchInlineSnapshot(` + [ + "function modifier_public_null()", + "function modifier_public_view() view", + "function modifier_public_pure() pure", + "function modifier_public_payable() payable", + "function modifier_public_nonpayable()", + "function modifier_external_null()", + "function modifier_external_view() view", + "function modifier_external_pure() pure", + "function modifier_external_payable() payable", + "function modifier_external_nonpayable()", + ] + `); + }); +}); diff --git a/packages/common/src/codegen/solidityToAbi.ts b/packages/common/src/codegen/solidityToAbi.ts new file mode 100644 index 0000000000..1ea3fc413f --- /dev/null +++ b/packages/common/src/codegen/solidityToAbi.ts @@ -0,0 +1,116 @@ +import { parse, visit } from "@solidity-parser/parser"; +// TODO: add ast types export (https://github.com/solidity-parser/parser/issues/71) +import { + SourceUnit, + ContractDefinition, + VariableDeclaration, + TypeName, +} from "@solidity-parser/parser/dist/src/ast-types"; +import { Abi, AbiFunction, AbiError, AbiParameter, AbiType } from "abitype"; +import { debug as parentDebug } from "./debug"; + +const debug = parentDebug.extend("solidityToAbi"); + +export function solidityToAbi(opts: { sourcePath: string; source: string; contractName: string }): Abi { + // TODO: enable `tolerant` and print nicer message for `ast.errors` + const ast = parse(opts.source); + + const prefix = `[${opts.sourcePath}:${opts.contractName}]`; + const contract = findContractDef(ast, opts.contractName); + if (!contract) { + debug(`${prefix} Could not find contract definition.`); + return []; + } + + // TODO: extract more types + const abi: (AbiFunction | AbiError)[] = []; + + visit(contract, { + FunctionDefinition({ + name, + visibility, + parameters, + stateMutability, + returnParameters, + isConstructor, + isFallback, + isReceiveEther, + }) { + // TODO: fill these in + if (isConstructor || isFallback || isReceiveEther) return; + + // skip invalid function definitions + if (!name) return debug(`${prefix} Skipping unnamed function.`); + if (visibility === "default") return debug(`${prefix} Skipping function "${name}" with \`default\` visibility.`); + if (stateMutability === "constant") + return debug(`${prefix} Skipping function "${name}" with \`constant\` mutability modifier.`); + + if (visibility === "external" || visibility === "public") { + abi.push({ + type: "function", + name, + stateMutability: stateMutability ?? "nonpayable", + inputs: variablesToParameters(parameters), + outputs: variablesToParameters(returnParameters ?? []), + }); + } + }, + CustomErrorDefinition({ name, parameters }) { + abi.push({ + type: "error", + name, + inputs: variablesToParameters(parameters), + }); + }, + }); + + return abi; +} + +function findContractDef(ast: SourceUnit, contractName: string): ContractDefinition | undefined { + let contract: ContractDefinition | undefined = undefined; + + visit(ast, { + ContractDefinition(node) { + if (node.name === contractName) { + contract = node; + } + }, + }); + + return contract; +} + +function variablesToParameters(variables: readonly VariableDeclaration[]): readonly AbiParameter[] { + return variables.map(({ name, typeName }, i): AbiParameter => { + if (!typeName) throw new Error(`No type definition for variable${name ? ` "${name}"` : ""} at position ${i}.`); + + const type = typeNameToAbiType(typeName); + + return { + name: name ?? "", + type, + }; + }); +} + +function typeNameToAbiType(typeName: TypeName): AbiType { + if (typeName.type === "ElementaryTypeName") { + return typeName.name as AbiType; + } + if (typeName.type === "UserDefinedTypeName") { + // TODO: resolve this to underlying abi type + return typeName.namePath as AbiType; + } + if (typeName.type === "ArrayTypeName") { + const length = ((): string => { + if (!typeName.length) return ""; + if (typeName.length.type === "NumberLiteral") return typeName.length.number; + if (typeName.length.type === "Identifier") return typeName.length.name; + throw new Error(`Unsupported array length AST type "${typeName.length.type}".`); + })(); + return `${typeNameToAbiType(typeName.baseTypeName)}[${length}]`; + } + + throw new Error(`Unsupported AST parameter type "${typeName.type}".`); +} diff --git a/packages/common/src/codegen/utils/contractToInterface.ts b/packages/common/src/codegen/utils/contractToInterface.ts index 88c757b249..a896404fac 100644 --- a/packages/common/src/codegen/utils/contractToInterface.ts +++ b/packages/common/src/codegen/utils/contractToInterface.ts @@ -106,8 +106,8 @@ export function contractToInterface( }; } -function findContractNode(ast: SourceUnit, contractName: string): ContractDefinition | undefined { - let contract = undefined; +export function findContractNode(ast: SourceUnit, contractName: string): ContractDefinition | undefined { + let contract: ContractDefinition | undefined = undefined; visit(ast, { ContractDefinition(node) { diff --git a/packages/config/src/common.ts b/packages/config/src/common.ts index 104da58979..e3f672051f 100644 --- a/packages/config/src/common.ts +++ b/packages/config/src/common.ts @@ -24,12 +24,15 @@ export type Schema = { }; export type Table = { + // labels readonly namespaceLabel: string; readonly label: string; + // resource ID readonly type: satisfy; readonly namespace: string; readonly name: string; readonly tableId: Hex; + // key/value schema readonly schema: Schema; readonly key: readonly string[]; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7b2574f05..b82988f741 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -290,6 +290,9 @@ importers: '@solidity-parser/parser': specifier: ^0.16.0 version: 0.16.0 + abitype: + specifier: 'catalog:' + version: 1.0.5(typescript@5.4.2)(zod@3.23.8) asn1.js: specifier: 5.x version: 5.4.1 From 17b20d94cd9430c09f83e3cbb59d51660942f461 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 21 Aug 2024 16:46:35 +0100 Subject: [PATCH 07/23] all the params --- .../common/src/codegen/solidityToAbi.test.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/common/src/codegen/solidityToAbi.test.ts b/packages/common/src/codegen/solidityToAbi.test.ts index a9706d7653..bf0b890113 100644 --- a/packages/common/src/codegen/solidityToAbi.test.ts +++ b/packages/common/src/codegen/solidityToAbi.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import { describe, expect, it } from "vitest"; import { formatAbi } from "abitype"; import { solidityToAbi } from "./solidityToAbi"; @@ -6,7 +7,6 @@ const visibility = ["public", "external", "private", "internal"] as const; const mutability = [null, "view", "pure", "payable", "nonpayable"] as const; const types = { - empty: null, uint256: "uint256", uint: "uint", string: "string", @@ -20,12 +20,18 @@ const types = { describe("solidityToAbi", () => { it("parses error params", () => { - const errors = Object.entries(types).map(([name, type]) => `error param_${name}(${type ? `${type} value` : ""});`); + const errors = [ + "error noParam();", + ...Object.entries(types).map(([name, type]) => `error param_${name}(${type} value);`), + `error allParams(${Object.entries(types) + .map(([name, type]) => `${type} _${name}`) + .join(", ")});`, + ]; const source = `contract MyContract {\n ${errors.join("\n ")}\n}`; expect(source).toMatchInlineSnapshot(` "contract MyContract { - error param_empty(); + error noParam(); error param_uint256(uint256 value); error param_uint(uint value); error param_string(string value); @@ -35,6 +41,7 @@ describe("solidityToAbi", () => { error param_fixedArray(bool[2] value); error param_mixedArray(bool[2][] value); error param_deepArray(bool[][2][4][8][] value); + error allParams(uint256 _uint256, uint _uint, string _string, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray); }" `); @@ -43,7 +50,7 @@ describe("solidityToAbi", () => { ).toMatchInlineSnapshot( ` [ - "error param_empty()", + "error noParam()", "error param_uint256(uint256 value)", "error param_uint(uint value)", "error param_string(string value)", @@ -53,6 +60,7 @@ describe("solidityToAbi", () => { "error param_fixedArray(bool[2] value)", "error param_mixedArray(bool[2][] value)", "error param_deepArray(bool[][2][4][8][] value)", + "error allParams(uint256 _uint256, uint _uint, string _string, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray)", ] `, ); From 0c9a68e815805dba5b3f7234f6edae2975b88827 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 21 Aug 2024 17:45:37 +0100 Subject: [PATCH 08/23] function params --- .../common/src/codegen/solidityToAbi.test.ts | 104 +++++++++++++----- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/packages/common/src/codegen/solidityToAbi.test.ts b/packages/common/src/codegen/solidityToAbi.test.ts index bf0b890113..9b99860116 100644 --- a/packages/common/src/codegen/solidityToAbi.test.ts +++ b/packages/common/src/codegen/solidityToAbi.test.ts @@ -3,13 +3,15 @@ import { describe, expect, it } from "vitest"; import { formatAbi } from "abitype"; import { solidityToAbi } from "./solidityToAbi"; -const visibility = ["public", "external", "private", "internal"] as const; -const mutability = [null, "view", "pure", "payable", "nonpayable"] as const; +const functionVisibility = ["public", "external", "private", "internal"] as const; +const functionMutability = [null, "view", "pure", "payable", "nonpayable"] as const; const types = { uint256: "uint256", uint: "uint", - string: "string", + payable: "address payable", + stringMemory: "string memory", + stringCalldata: "string calldata", uintArray: "uint[]", stringArray: "string[]", keyTuples: "bytes32[][]", @@ -22,7 +24,7 @@ describe("solidityToAbi", () => { it("parses error params", () => { const errors = [ "error noParam();", - ...Object.entries(types).map(([name, type]) => `error param_${name}(${type} value);`), + ...Object.entries(types).map(([name, type]) => `error param_${name}(${type} _${name});`), `error allParams(${Object.entries(types) .map(([name, type]) => `${type} _${name}`) .join(", ")});`, @@ -32,16 +34,18 @@ describe("solidityToAbi", () => { expect(source).toMatchInlineSnapshot(` "contract MyContract { error noParam(); - error param_uint256(uint256 value); - error param_uint(uint value); - error param_string(string value); - error param_uintArray(uint[] value); - error param_stringArray(string[] value); - error param_keyTuples(bytes32[][] value); - error param_fixedArray(bool[2] value); - error param_mixedArray(bool[2][] value); - error param_deepArray(bool[][2][4][8][] value); - error allParams(uint256 _uint256, uint _uint, string _string, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray); + error param_uint256(uint256 _uint256); + error param_uint(uint _uint); + error param_payable(address payable _payable); + error param_stringMemory(string memory _stringMemory); + error param_stringCalldata(string calldata _stringCalldata); + error param_uintArray(uint[] _uintArray); + error param_stringArray(string[] _stringArray); + error param_keyTuples(bytes32[][] _keyTuples); + error param_fixedArray(bool[2] _fixedArray); + error param_mixedArray(bool[2][] _mixedArray); + error param_deepArray(bool[][2][4][8][] _deepArray); + error allParams(uint256 _uint256, uint _uint, address payable _payable, string memory _stringMemory, string calldata _stringCalldata, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray); }" `); @@ -51,23 +55,73 @@ describe("solidityToAbi", () => { ` [ "error noParam()", - "error param_uint256(uint256 value)", - "error param_uint(uint value)", - "error param_string(string value)", - "error param_uintArray(uint[] value)", - "error param_stringArray(string[] value)", - "error param_keyTuples(bytes32[][] value)", - "error param_fixedArray(bool[2] value)", - "error param_mixedArray(bool[2][] value)", - "error param_deepArray(bool[][2][4][8][] value)", - "error allParams(uint256 _uint256, uint _uint, string _string, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray)", + "error param_uint256(uint256 _uint256)", + "error param_uint(uint _uint)", + "error param_payable(address _payable)", + "error param_stringMemory(string _stringMemory)", + "error param_stringCalldata(string _stringCalldata)", + "error param_uintArray(uint[] _uintArray)", + "error param_stringArray(string[] _stringArray)", + "error param_keyTuples(bytes32[][] _keyTuples)", + "error param_fixedArray(bool[2] _fixedArray)", + "error param_mixedArray(bool[2][] _mixedArray)", + "error param_deepArray(bool[][2][4][8][] _deepArray)", + "error allParams(uint256 _uint256, uint _uint, address _payable, string _stringMemory, string _stringCalldata, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray)", ] `, ); }); + it("parses function params", () => { + const functions = [ + "function noParams() public {}", + ...Object.entries(types).map(([name, type]) => `function param_${name}(${type} value) public {}`), + `function allParams(${Object.entries(types) + .map(([name, type]) => `${type} _${name}`) + .join(", ")}) public {}`, + ]; + const source = `contract MyContract {\n ${functions.join("\n ")}\n}`; + + expect(source).toMatchInlineSnapshot(` + "contract MyContract { + function noParams() public {} + function param_uint256(uint256 value) public {} + function param_uint(uint value) public {} + function param_payable(address payable value) public {} + function param_stringMemory(string memory value) public {} + function param_stringCalldata(string calldata value) public {} + function param_uintArray(uint[] value) public {} + function param_stringArray(string[] value) public {} + function param_keyTuples(bytes32[][] value) public {} + function param_fixedArray(bool[2] value) public {} + function param_mixedArray(bool[2][] value) public {} + function param_deepArray(bool[][2][4][8][] value) public {} + function allParams(uint256 _uint256, uint _uint, address payable _payable, string memory _stringMemory, string calldata _stringCalldata, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray) public {} + }" + `); + + expect(formatAbi(solidityToAbi({ sourcePath: "test/Test.sol", source, contractName: "MyContract" }))) + .toMatchInlineSnapshot(` + [ + "function noParams()", + "function param_uint256(uint256 value)", + "function param_uint(uint value)", + "function param_payable(address value)", + "function param_stringMemory(string value)", + "function param_stringCalldata(string value)", + "function param_uintArray(uint[] value)", + "function param_stringArray(string[] value)", + "function param_keyTuples(bytes32[][] value)", + "function param_fixedArray(bool[2] value)", + "function param_mixedArray(bool[2][] value)", + "function param_deepArray(bool[][2][4][8][] value)", + "function allParams(uint256 _uint256, uint _uint, address _payable, string _stringMemory, string _stringCalldata, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray)", + ] + `); + }); + it("parses public/external functions", () => { - const modifiers = visibility.flatMap((vis) => mutability.map((mut) => [vis, mut] as const)); + const modifiers = functionVisibility.flatMap((vis) => functionMutability.map((mut) => [vis, mut] as const)); const functions = modifiers.map(([vis, mut]) => `function modifier_${vis}_${mut}() ${vis} ${mut ?? ""} {}`); const source = `contract MyContract {\n ${functions.join("\n ")}\n}`; From b62cd372efcb8e67819627d1701cbdeb30859011 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 21 Aug 2024 17:47:23 +0100 Subject: [PATCH 09/23] leave out user types for now --- packages/common/src/codegen/solidityToAbi.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/common/src/codegen/solidityToAbi.ts b/packages/common/src/codegen/solidityToAbi.ts index 1ea3fc413f..5b8874f617 100644 --- a/packages/common/src/codegen/solidityToAbi.ts +++ b/packages/common/src/codegen/solidityToAbi.ts @@ -22,7 +22,7 @@ export function solidityToAbi(opts: { sourcePath: string; source: string; contra return []; } - // TODO: extract more types + // TODO: extract more types (we don't support em in interfaces yet so leaving em out for now) const abi: (AbiFunction | AbiError)[] = []; visit(contract, { @@ -98,10 +98,10 @@ function typeNameToAbiType(typeName: TypeName): AbiType { if (typeName.type === "ElementaryTypeName") { return typeName.name as AbiType; } - if (typeName.type === "UserDefinedTypeName") { - // TODO: resolve this to underlying abi type - return typeName.namePath as AbiType; - } + // if (typeName.type === "UserDefinedTypeName") { + // // TODO: resolve this to underlying abi type + // return typeName.namePath as AbiType; + // } if (typeName.type === "ArrayTypeName") { const length = ((): string => { if (!typeName.length) return ""; From 31ec370a5c6d32ae833d8e688c1c73fe14ed24ce Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 21 Aug 2024 18:18:30 +0100 Subject: [PATCH 10/23] move where we build manifest --- packages/cli/src/build.ts | 4 ++-- packages/world/ts/debug.ts | 5 +---- packages/world/ts/node/buildSystemsManifest.ts | 5 ++++- packages/world/ts/node/debug.ts | 3 +++ packages/world/ts/node/findContractArtifacts.ts | 5 +++-- packages/world/ts/node/index.ts | 1 + packages/world/ts/node/render-solidity/worldgen.ts | 7 ------- packages/world/ts/node/types.ts | 2 +- 8 files changed, 15 insertions(+), 17 deletions(-) create mode 100644 packages/world/ts/node/debug.ts diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts index 1413aae58c..1e9775c642 100644 --- a/packages/cli/src/build.ts +++ b/packages/cli/src/build.ts @@ -1,5 +1,5 @@ import { tablegen } from "@latticexyz/store/codegen"; -import { worldgen } from "@latticexyz/world/node"; +import { buildSystemsManifest, worldgen } from "@latticexyz/world/node"; import { World as WorldConfig } from "@latticexyz/world"; import { forge } from "@latticexyz/common/foundry"; import { execa } from "execa"; @@ -21,7 +21,7 @@ export async function build({ foundryProfile = process.env.FOUNDRY_PROFILE, }: BuildOptions): Promise { await Promise.all([tablegen({ rootDir, config }), worldgen({ rootDir, config })]); - await forge(["build"], { profile: foundryProfile }); + await buildSystemsManifest({ rootDir, config }); await execa("mud", ["abi-ts"], { stdio: "inherit" }); } diff --git a/packages/world/ts/debug.ts b/packages/world/ts/debug.ts index 7ae5bfce61..6f68f284a0 100644 --- a/packages/world/ts/debug.ts +++ b/packages/world/ts/debug.ts @@ -1,10 +1,7 @@ import createDebug from "debug"; export const debug = createDebug("mud:world"); -export const error = createDebug("mud:world"); - -// Pipe debug output to stdout instead of stderr debug.log = console.debug.bind(console); -// Pipe error output to stderr +export const error = createDebug("mud:world"); error.log = console.error.bind(console); diff --git a/packages/world/ts/node/buildSystemsManifest.ts b/packages/world/ts/node/buildSystemsManifest.ts index 02b3a8b570..9ab43366fd 100644 --- a/packages/world/ts/node/buildSystemsManifest.ts +++ b/packages/world/ts/node/buildSystemsManifest.ts @@ -9,6 +9,7 @@ import { Hex, Abi } from "viem"; import { formatAbi, formatAbiItem } from "abitype"; import IBaseWorldAbi from "../../out/IBaseWorld.sol/IBaseWorld.abi.json"; import SystemAbi from "../../out/System.sol/System.abi.json"; +import { debug } from "./debug"; const excludedAbi = formatAbi([ ...IBaseWorldAbi.filter((item) => item.type === "event" || item.type === "error"), @@ -67,7 +68,9 @@ export async function buildSystemsManifest(opts: { rootDir: string; config: Worl }), } satisfies SystemsManifest; - const outFile = path.join(opts.rootDir, ".mud/local/systems.json"); + const manifestFilename = ".mud/local/systems.json"; + const outFile = path.join(opts.rootDir, manifestFilename); await mkdir(path.dirname(outFile), { recursive: true }); await writeFile(outFile, JSON.stringify(manifest, null, 2) + "\n"); + debug("Wrote systems manifest to", manifestFilename); } diff --git a/packages/world/ts/node/debug.ts b/packages/world/ts/node/debug.ts new file mode 100644 index 0000000000..ab1807cb00 --- /dev/null +++ b/packages/world/ts/node/debug.ts @@ -0,0 +1,3 @@ +import { debug as parentDebug } from "../debug"; + +export const debug = parentDebug.extend("codegen"); diff --git a/packages/world/ts/node/findContractArtifacts.ts b/packages/world/ts/node/findContractArtifacts.ts index 8847460e74..82de40ae13 100644 --- a/packages/world/ts/node/findContractArtifacts.ts +++ b/packages/world/ts/node/findContractArtifacts.ts @@ -6,6 +6,7 @@ import { indent, isDefined } from "@latticexyz/common/utils"; import { Hex, size, sliceHex } from "viem"; import { ContractArtifact, ReferenceIdentifier, contractSizeLimit } from "./common"; import { types } from "./types"; +import { debug } from "./debug"; export type Input = { readonly forgeOutDir: string; @@ -28,13 +29,13 @@ export async function findContractArtifacts({ forgeOutDir }: Input): Promise { const artifact = parseArtifact(json); if (artifact instanceof type.errors) { - // TODO: replace with debug - console.warn(`Skipping invalid artifact at \`${filename}\`:\n\n${indent(artifact.message)}\n`); + debug(`Skipping invalid artifact at "${filename}":\n${indent(artifact.message)}`); return; } return artifact; }) .filter(isDefined) + .filter(type({ metadata: "object" }).allows) .map((artifact) => { const sourcePath = Object.keys(artifact.metadata.settings.compilationTarget)[0]; const name = artifact.metadata.settings.compilationTarget[sourcePath]; diff --git a/packages/world/ts/node/index.ts b/packages/world/ts/node/index.ts index e73f48effc..b905dd1189 100644 --- a/packages/world/ts/node/index.ts +++ b/packages/world/ts/node/index.ts @@ -2,3 +2,4 @@ export * from "./render-solidity"; export * from "./findSolidityFiles"; export * from "./getSystemContracts"; export * from "./resolveSystems"; +export * from "./buildSystemsManifest"; diff --git a/packages/world/ts/node/render-solidity/worldgen.ts b/packages/world/ts/node/render-solidity/worldgen.ts index ce0e46db3d..fcb62bf6c4 100644 --- a/packages/world/ts/node/render-solidity/worldgen.ts +++ b/packages/world/ts/node/render-solidity/worldgen.ts @@ -5,10 +5,6 @@ import { renderSystemInterface } from "./renderSystemInterface"; import { renderWorldInterface } from "./renderWorldInterface"; import { World as WorldConfig } from "../../config/v2/output"; import { resolveSystems } from "../resolveSystems"; -import { debug as parentDebug } from "../../debug"; -import { buildSystemsManifest } from "../buildSystemsManifest"; - -const debug = parentDebug.extend("worldgen"); export async function worldgen({ rootDir, @@ -89,7 +85,4 @@ export async function worldgen({ }); // write to file await formatAndWriteSolidity(output, outputPath, "Generated world interface"); - - debug("Building system manifest"); - await buildSystemsManifest({ rootDir, config }); } diff --git a/packages/world/ts/node/types.ts b/packages/world/ts/node/types.ts index d49fee444e..fd137aa51a 100644 --- a/packages/world/ts/node/types.ts +++ b/packages/world/ts/node/types.ts @@ -22,7 +22,7 @@ export const types = scope({ deployedBytecode: "ArtifactBytecode", // TODO: improve narrowing with `isAbi` or import arktype type from abitype (when either are available) abi: type("unknown[]").pipe((input) => input as Abi), - metadata: { + "metadata?": { settings: { compilationTarget: { // key is source filename name like `src/WorldResourceId.sol` From f6bd6031cc034737ce8936e0098bb5e607b29325 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 21 Aug 2024 18:51:18 +0100 Subject: [PATCH 11/23] metadata is optional, e.g. libs --- packages/world/ts/node/findContractArtifacts.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/world/ts/node/findContractArtifacts.ts b/packages/world/ts/node/findContractArtifacts.ts index 82de40ae13..a7ed010470 100644 --- a/packages/world/ts/node/findContractArtifacts.ts +++ b/packages/world/ts/node/findContractArtifacts.ts @@ -15,6 +15,13 @@ export type Input = { export type Output = readonly ContractArtifact[]; const parseArtifact = type("parse.json").to(types.Artifact); +type Artifact = typeof types.Artifact.infer; + +function hasMetadata( + artifact: Artifact, +): artifact is Artifact & { [k in "metadata"]-?: Exclude } { + return artifact.metadata !== undefined; +} export async function findContractArtifacts({ forgeOutDir }: Input): Promise { const files = (await glob("**/*.sol/*.json", { ignore: "**/*.abi.json", cwd: forgeOutDir })).sort(); @@ -35,8 +42,9 @@ export async function findContractArtifacts({ forgeOutDir }: Input): Promise { + artifact; const sourcePath = Object.keys(artifact.metadata.settings.compilationTarget)[0]; const name = artifact.metadata.settings.compilationTarget[sourcePath]; const deployedBytecodeSize = size(artifact.deployedBytecode.object); From 3d7f6b3beef43f90e60f73b12d491746f175621f Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Wed, 21 Aug 2024 19:25:25 +0100 Subject: [PATCH 12/23] move to type def --- .../world/ts/node/buildSystemsManifest.ts | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/world/ts/node/buildSystemsManifest.ts b/packages/world/ts/node/buildSystemsManifest.ts index 9ab43366fd..f91fe49944 100644 --- a/packages/world/ts/node/buildSystemsManifest.ts +++ b/packages/world/ts/node/buildSystemsManifest.ts @@ -5,27 +5,36 @@ import { ContractArtifact } from "./common"; import { findContractArtifacts } from "./findContractArtifacts"; import { getOutDirectory as getForgeOutDirectory } from "@latticexyz/common/foundry"; import path from "node:path"; -import { Hex, Abi } from "viem"; +import { Abi, Hex, isHex } from "viem"; import { formatAbi, formatAbiItem } from "abitype"; import IBaseWorldAbi from "../../out/IBaseWorld.sol/IBaseWorld.abi.json"; import SystemAbi from "../../out/System.sol/System.abi.json"; import { debug } from "./debug"; +import { type } from "arktype"; const excludedAbi = formatAbi([ ...IBaseWorldAbi.filter((item) => item.type === "event" || item.type === "error"), ...SystemAbi, ] as Abi); -export type SystemsManifest = { - readonly systems: readonly { - readonly namespaceLabel: string; - readonly label: string; - readonly namespace: string; - readonly name: string; - readonly systemId: Hex; - readonly abi: string[]; - }[]; -}; +export const SystemsManifest = type({ + systems: [ + { + // labels + namespaceLabel: "string", + label: "string", + // resource ID + namespace: "string", + name: "string", + systemId: ["string", ":", (s): s is Hex => isHex(s, { strict: false })], + // abi + abi: "string[]", + worldAbi: "string[]", + }, + "[]", + ], + createdAt: "number", +}); export async function buildSystemsManifest(opts: { rootDir: string; config: World }): Promise { const systems = await resolveSystems(opts); @@ -45,9 +54,7 @@ export async function buildSystemsManifest(opts: { rootDir: string; config: Worl } const manifest = { - systems: systems.map((system) => { - // TODO: extract this from AST so we can get it before compile time and use it to generate system interfaces? - // we'll may need an extended ABI function definition to include things like visibility? but maybe not needed for interface? + systems: systems.map((system): (typeof SystemsManifest)["infer"]["systems"][number] => { const artifact = getSystemArtifact(system); const abi = artifact.abi.filter((item) => !excludedAbi.includes(formatAbiItem(item))); const worldAbi = system.deploy.registerWorldFunctions @@ -66,7 +73,8 @@ export async function buildSystemsManifest(opts: { rootDir: string; config: Worl worldAbi: formatAbi(worldAbi).sort((a, b) => a.localeCompare(b)), }; }), - } satisfies SystemsManifest; + createdAt: Date.now(), + } satisfies typeof SystemsManifest.infer; const manifestFilename = ".mud/local/systems.json"; const outFile = path.join(opts.rootDir, manifestFilename); From 97598dd4726bb6eff6f17b0708c7c877e31ebb6a Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 14:42:19 +0100 Subject: [PATCH 13/23] better resource labels approach --- packages/cli/package.json | 1 + packages/cli/src/deploy/deploy.ts | 14 ++-- .../cli/src/deploy/ensureResourceLabels.ts | 66 ----------------- packages/cli/src/deploy/ensureResourceTags.ts | 70 +++++++++++++++++++ packages/cli/src/deploy/getRecord.ts | 45 ++++++++++++ packages/protocol-parser/package.json | 1 + .../protocol-parser/src/exports/internal.ts | 1 + .../protocol-parser/src/getKeyTuple.test.ts | 53 ++++++++++++++ packages/protocol-parser/src/getKeyTuple.ts | 19 +++++ pnpm-lock.yaml | 6 ++ 10 files changed, 206 insertions(+), 70 deletions(-) delete mode 100644 packages/cli/src/deploy/ensureResourceLabels.ts create mode 100644 packages/cli/src/deploy/ensureResourceTags.ts create mode 100644 packages/cli/src/deploy/getRecord.ts create mode 100644 packages/protocol-parser/src/getKeyTuple.test.ts create mode 100644 packages/protocol-parser/src/getKeyTuple.ts diff --git a/packages/cli/package.json b/packages/cli/package.json index 35c3475523..c012614bf3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,6 +39,7 @@ "test:ci": "pnpm run test" }, "dependencies": { + "@ark/util": "catalog:", "@aws-sdk/client-kms": "^3.556.0", "@latticexyz/abi-ts": "workspace:*", "@latticexyz/common": "workspace:*", diff --git a/packages/cli/src/deploy/deploy.ts b/packages/cli/src/deploy/deploy.ts index 3befb5f21a..6e2f4834b8 100644 --- a/packages/cli/src/deploy/deploy.ts +++ b/packages/cli/src/deploy/deploy.ts @@ -1,4 +1,4 @@ -import { Account, Address, Chain, Client, Hex, Transport } from "viem"; +import { Account, Address, Chain, Client, Hex, Transport, stringToHex } from "viem"; import { ensureDeployer } from "./ensureDeployer"; import { deployWorld } from "./deployWorld"; import { ensureTables } from "./ensureTables"; @@ -15,7 +15,7 @@ import { ensureContractsDeployed } from "./ensureContractsDeployed"; import { randomBytes } from "crypto"; import { ensureWorldFactory } from "./ensureWorldFactory"; import { Table } from "@latticexyz/config"; -import { ensureResourceLabels } from "./ensureResourceLabels"; +import { ensureResourceTags } from "./ensureResourceTags"; type DeployOptions = { client: Client; @@ -129,10 +129,16 @@ export async function deploy({ worldDeploy, modules, }); - const labelTxs = await ensureResourceLabels({ + + const labelTxs = await ensureResourceTags({ client, worldDeploy, - resources, + tags: resources.map((resource) => ({ + resourceId: resource.resourceId, + tag: "label", + value: resource.label, + })), + valueToHex: stringToHex, }); const txs = [...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs, ...labelTxs]; diff --git a/packages/cli/src/deploy/ensureResourceLabels.ts b/packages/cli/src/deploy/ensureResourceLabels.ts deleted file mode 100644 index 16ad8eb410..0000000000 --- a/packages/cli/src/deploy/ensureResourceLabels.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Hex, Client, Transport, Chain, Account, stringToHex, BaseError, hexToString } from "viem"; -import { WorldDeploy } from "./common"; -import { debug } from "./debug"; -import { hexToResource, writeContract } from "@latticexyz/common"; -import { isDefined } from "@latticexyz/common/utils"; -import metadataConfig from "@latticexyz/world-module-metadata/mud.config"; -import metadataAbi from "@latticexyz/world-module-metadata/out/IMetadataSystem.sol/IMetadataSystem.abi.json" assert { type: "json" }; -import { getTableValue } from "./getTableValue"; - -type LabeledResource = { - readonly resourceId: Hex; - readonly label: string; -}; - -export async function ensureResourceLabels({ - client, - worldDeploy, - resources, -}: { - readonly client: Client; - readonly worldDeploy: WorldDeploy; - readonly resources: readonly LabeledResource[]; -}): Promise { - const currentLabels = await Promise.all( - resources.map(async (resource) => { - const { value } = await getTableValue({ - client, - worldDeploy, - table: metadataConfig.tables.metadata__ResourceTag, - key: { resource: resource.resourceId, tag: stringToHex("label", { size: 32 }) }, - }); - return hexToString(value); - }), - ); - - const resourcesToSet = resources.filter((resource, i) => resource.label !== currentLabels[i]); - if (resourcesToSet.length === 0) { - return []; - } - - debug("setting", resources.length, "resource labels"); - return ( - await Promise.all( - resourcesToSet.map(async ({ resourceId, label }) => { - const resource = hexToResource(resourceId); - // TODO: move to resourceToDebugString - const resourceString = `${resource.type}:${resource.namespace}:${resource.name}`; - debug(`setting resource label for ${resourceString} to ${label}`); - try { - return await writeContract(client, { - chain: client.chain ?? null, - address: worldDeploy.address, - abi: metadataAbi, - // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) - functionName: "metadata__setResourceTag", - args: [resourceId, stringToHex("label", { size: 32 }), stringToHex(label)], - }); - } catch (error) { - debug( - `failed to set resource label for ${resourceString}, skipping\n ${error instanceof BaseError ? error.shortMessage : error}`, - ); - } - }), - ) - ).filter(isDefined); -} diff --git a/packages/cli/src/deploy/ensureResourceTags.ts b/packages/cli/src/deploy/ensureResourceTags.ts new file mode 100644 index 0000000000..ebb076e4d2 --- /dev/null +++ b/packages/cli/src/deploy/ensureResourceTags.ts @@ -0,0 +1,70 @@ +import { Hex, Client, Transport, Chain, Account, stringToHex, BaseError } from "viem"; +import { WorldDeploy } from "./common"; +import { debug } from "./debug"; +import { hexToResource, writeContract } from "@latticexyz/common"; +import { identity, isDefined } from "@latticexyz/common/utils"; +import metadataConfig from "@latticexyz/world-module-metadata/mud.config"; +import metadataAbi from "@latticexyz/world-module-metadata/out/IMetadataSystem.sol/IMetadataSystem.abi.json" assert { type: "json" }; +import { getRecord } from "./getRecord"; + +export type ResourceTag = { + resourceId: Hex; + tag: string; + value: value; +}; + +export async function ensureResourceTags({ + client, + worldDeploy, + tags, + valueToHex = identity, +}: { + readonly client: Client; + readonly worldDeploy: WorldDeploy; + readonly tags: readonly ResourceTag[]; +} & (value extends Hex + ? { readonly valueToHex?: (value: value) => Hex } + : { readonly valueToHex: (value: value) => Hex })): Promise { + const pendingTags = ( + await Promise.all( + tags.map(async (tag) => { + const { value } = await getRecord({ + client, + worldDeploy, + table: metadataConfig.tables.metadata__ResourceTag, + key: { resource: tag.resourceId, tag: stringToHex(tag.tag, { size: 32 }) }, + }); + if (value === valueToHex(tag.value)) return; + return tag; + }), + ) + ).filter(isDefined); + + if (pendingTags.length === 0) return []; + + debug("setting", pendingTags.length, "resource tags"); + return ( + await Promise.all( + pendingTags.map(async (tag) => { + const resource = hexToResource(tag.resourceId); + // TODO: move to resourceToDebugString + const resourceString = `${resource.type}:${resource.namespace}:${resource.name}`; + debug(`setting resource tag for ${resourceString}`, { [tag.tag]: tag.value }); + try { + return await writeContract(client, { + chain: client.chain ?? null, + address: worldDeploy.address, + abi: metadataAbi, + // TODO: replace with batchCall (https://github.com/latticexyz/mud/issues/1645) + functionName: "metadata__setResourceTag", + args: [tag.resourceId, stringToHex(tag.tag, { size: 32 }), valueToHex(tag.value)], + }); + } catch (error) { + debug( + `failed to set resource tag for ${resourceString}, skipping\n ${error instanceof BaseError ? error.shortMessage : error}`, + ); + } + }), + ) + ).filter(isDefined); +} diff --git a/packages/cli/src/deploy/getRecord.ts b/packages/cli/src/deploy/getRecord.ts new file mode 100644 index 0000000000..697ddde0c3 --- /dev/null +++ b/packages/cli/src/deploy/getRecord.ts @@ -0,0 +1,45 @@ +import { + decodeValueArgs, + getKeySchema, + getKeyTuple, + getSchemaPrimitives, + getSchemaTypes, + getValueSchema, +} from "@latticexyz/protocol-parser/internal"; +import { WorldDeploy, worldAbi } from "./common"; +import { Client, Hex } from "viem"; +import { readContract } from "viem/actions"; +import { Table } from "@latticexyz/config"; +import { mapObject } from "@latticexyz/common/utils"; +import { show } from "@ark/util"; + +export async function getRecord({ + client, + worldDeploy, + table, + key, +}: { + readonly client: Client; + readonly worldDeploy: WorldDeploy; + readonly table: table; + readonly key: getSchemaPrimitives>; +}): Promise>> { + const [staticData, encodedLengths, dynamicData] = (await readContract(client, { + blockNumber: worldDeploy.stateBlock, + address: worldDeploy.address, + abi: worldAbi, + functionName: "getRecord", + args: [table.tableId, getKeyTuple(table, key)], + // TODO: remove cast once https://github.com/wevm/viem/issues/2125 is resolved + })) as [Hex, Hex, Hex]; + const record = { + ...key, + ...decodeValueArgs(getSchemaTypes(getValueSchema(table)), { + staticData, + encodedLengths, + dynamicData, + }), + }; + // return in schema order + return mapObject(table.schema, (value, key) => record[key as never]) as never; +} diff --git a/packages/protocol-parser/package.json b/packages/protocol-parser/package.json index d477df1bd8..1c7a511e13 100644 --- a/packages/protocol-parser/package.json +++ b/packages/protocol-parser/package.json @@ -37,6 +37,7 @@ "test:ci": "pnpm run test" }, "dependencies": { + "@ark/util": "catalog:", "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", "@latticexyz/schema-type": "workspace:*", diff --git a/packages/protocol-parser/src/exports/internal.ts b/packages/protocol-parser/src/exports/internal.ts index 80e3d54bd0..88fe58411d 100644 --- a/packages/protocol-parser/src/exports/internal.ts +++ b/packages/protocol-parser/src/exports/internal.ts @@ -28,6 +28,7 @@ export * from "../valueSchemaToHex"; export * from "../getFieldIndex"; export * from "../getKeySchema"; +export * from "../getKeyTuple"; export * from "../getValueSchema"; export * from "../getSchemaTypes"; export * from "../getSchemaPrimitives"; diff --git a/packages/protocol-parser/src/getKeyTuple.test.ts b/packages/protocol-parser/src/getKeyTuple.test.ts new file mode 100644 index 0000000000..1e90e1d9b6 --- /dev/null +++ b/packages/protocol-parser/src/getKeyTuple.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from "vitest"; +import { getKeyTuple } from "./getKeyTuple"; +import { Table } from "@latticexyz/config"; + +type PartialTable = Pick; + +describe("getKeyTuple", () => { + it("can encode bool key tuple", () => { + const table = { + schema: { id: { type: "bool", internalType: "bool" } }, + key: ["id"], + } as const satisfies PartialTable; + + expect(getKeyTuple(table, { id: false })).toStrictEqual([ + "0x0000000000000000000000000000000000000000000000000000000000000000", + ]); + expect(getKeyTuple(table, { id: true })).toStrictEqual([ + "0x0000000000000000000000000000000000000000000000000000000000000001", + ]); + }); + + it("can encode complex key tuple", () => { + const table = { + schema: { + a: { type: "uint256", internalType: "uint256" }, + b: { type: "int32", internalType: "int32" }, + c: { type: "bytes16", internalType: "bytes16" }, + d: { type: "address", internalType: "address" }, + e: { type: "bool", internalType: "bool" }, + f: { type: "int8", internalType: "int8" }, + }, + key: ["a", "b", "c", "d", "e", "f"], + } as const satisfies PartialTable; + + expect( + getKeyTuple(table, { + a: 42n, + b: -42, + c: "0x12340000000000000000000000000000", + d: "0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF", + e: true, + f: 3, + }), + ).toStrictEqual([ + "0x000000000000000000000000000000000000000000000000000000000000002a", + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd6", + "0x1234000000000000000000000000000000000000000000000000000000000000", + "0x000000000000000000000000ffffffffffffffffffffffffffffffffffffffff", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000003", + ]); + }); +}); diff --git a/packages/protocol-parser/src/getKeyTuple.ts b/packages/protocol-parser/src/getKeyTuple.ts new file mode 100644 index 0000000000..17c6ab0237 --- /dev/null +++ b/packages/protocol-parser/src/getKeyTuple.ts @@ -0,0 +1,19 @@ +import { Hex, encodeAbiParameters } from "viem"; +import { getSchemaPrimitives } from "./getSchemaPrimitives"; +import { getKeySchema } from "./getKeySchema"; +import { Table } from "@latticexyz/config"; + +type PartialTable = Pick; + +export type getKeyTuple
= { + [i in keyof key]: Hex; +}; + +export function getKeyTuple( + table: table, + key: getSchemaPrimitives>, +): getKeyTuple
{ + return table.key.map((fieldName) => + encodeAbiParameters([table.schema[fieldName]], [key[fieldName as never]]), + ) as never; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b82988f741..5f9fbcfc5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -145,6 +145,9 @@ importers: packages/cli: dependencies: + '@ark/util': + specifier: 'catalog:' + version: 0.2.2 '@aws-sdk/client-kms': specifier: ^3.556.0 version: 3.556.0 @@ -542,6 +545,9 @@ importers: packages/protocol-parser: dependencies: + '@ark/util': + specifier: 'catalog:' + version: 0.2.2 '@latticexyz/common': specifier: workspace:* version: link:../common From e04791ce4d5dc8a6451a115d5758bb61d0a0e2a6 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 15:40:12 +0100 Subject: [PATCH 14/23] load manifest to register ABIs --- packages/cli/src/deploy/common.ts | 4 +++ packages/cli/src/deploy/deploy.ts | 23 ++++++------ packages/cli/src/deploy/ensureResourceTags.ts | 2 +- packages/cli/src/deploy/resolveConfig.ts | 16 +++++++-- packages/cli/src/runDeploy.ts | 8 ++--- .../world/ts/node/buildSystemsManifest.ts | 7 ++-- packages/world/ts/node/common.ts | 3 ++ packages/world/ts/node/index.ts | 1 + packages/world/ts/node/loadSystemsManifest.ts | 36 +++++++++++++++++++ 9 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 packages/world/ts/node/loadSystemsManifest.ts diff --git a/packages/cli/src/deploy/common.ts b/packages/cli/src/deploy/common.ts index 97a94dd174..b196844770 100644 --- a/packages/cli/src/deploy/common.ts +++ b/packages/cli/src/deploy/common.ts @@ -89,7 +89,11 @@ export type System = DeterministicContract & { readonly allowAll: boolean; readonly allowedAddresses: readonly Hex[]; readonly allowedSystemIds: readonly Hex[]; + // TODO: replace this with system manifest data readonly worldFunctions: readonly WorldFunction[]; + // human readable ABIs to register onchain + readonly abi: readonly string[]; + readonly worldAbi: readonly string[]; }; export type DeployedSystem = Omit< diff --git a/packages/cli/src/deploy/deploy.ts b/packages/cli/src/deploy/deploy.ts index 6e2f4834b8..e881e3d1c7 100644 --- a/packages/cli/src/deploy/deploy.ts +++ b/packages/cli/src/deploy/deploy.ts @@ -90,14 +90,10 @@ export async function deploy({ throw new Error(`Unsupported World version: ${worldDeploy.worldVersion}`); } - const resources = [ - ...tables.map((table) => ({ resourceId: table.tableId, label: table.label })), - ...systems.map((system) => ({ resourceId: system.systemId, label: system.label })), - ]; const namespaceTxs = await ensureNamespaceOwner({ client, worldDeploy, - resourceIds: resources.map(({ resourceId }) => resourceId), + resourceIds: [...tables.map(({ tableId }) => tableId), ...systems.map(({ systemId }) => systemId)], }); debug("waiting for all namespace registration transactions to confirm"); @@ -130,18 +126,21 @@ export async function deploy({ modules, }); - const labelTxs = await ensureResourceTags({ + const tableTags = tables.map(({ tableId: resourceId, label }) => ({ resourceId, tag: "label", value: label })); + const systemTags = systems.flatMap(({ systemId: resourceId, label, abi, worldAbi }) => [ + { resourceId, tag: "label", value: label }, + { resourceId, tag: "abi", value: abi.join("\n") }, + { resourceId, tag: "worldAbi", value: worldAbi.join("\n") }, + ]); + + const tagTxs = await ensureResourceTags({ client, worldDeploy, - tags: resources.map((resource) => ({ - resourceId: resource.resourceId, - tag: "label", - value: resource.label, - })), + tags: [...tableTags, ...systemTags], valueToHex: stringToHex, }); - const txs = [...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs, ...labelTxs]; + const txs = [...tableTxs, ...systemTxs, ...functionTxs, ...moduleTxs, ...tagTxs]; // wait for each tx separately/serially, because parallelizing results in RPC errors debug("waiting for all transactions to confirm"); diff --git a/packages/cli/src/deploy/ensureResourceTags.ts b/packages/cli/src/deploy/ensureResourceTags.ts index ebb076e4d2..56e83372d9 100644 --- a/packages/cli/src/deploy/ensureResourceTags.ts +++ b/packages/cli/src/deploy/ensureResourceTags.ts @@ -49,7 +49,7 @@ export async function ensureResourceTags({ const resource = hexToResource(tag.resourceId); // TODO: move to resourceToDebugString const resourceString = `${resource.type}:${resource.namespace}:${resource.name}`; - debug(`setting resource tag for ${resourceString}`, { [tag.tag]: tag.value }); + debug(`tagging ${resourceString} with ${tag.tag}: ${JSON.stringify(tag.value)}`); try { return await writeContract(client, { chain: client.chain ?? null, diff --git a/packages/cli/src/deploy/resolveConfig.ts b/packages/cli/src/deploy/resolveConfig.ts index 9209456f2f..d49b00e95e 100644 --- a/packages/cli/src/deploy/resolveConfig.ts +++ b/packages/cli/src/deploy/resolveConfig.ts @@ -1,5 +1,5 @@ import path from "path"; -import { resolveSystems } from "@latticexyz/world/node"; +import { loadSystemsManifest, resolveSystems } from "@latticexyz/world/node"; import { Library, System, WorldFunction } from "./common"; import { Hex, isHex, toFunctionSelector, toFunctionSignature } from "viem"; import { getContractData } from "../utils/getContractData"; @@ -40,12 +40,21 @@ export async function resolveConfig({ .map(toFunctionSignature); const configSystems = await resolveSystems({ rootDir, config }); + const systemsManifest = await loadSystemsManifest({ rootDir, config }); const systems = configSystems .filter((system) => !system.deploy.disabled) .map((system): System => { + const manifest = systemsManifest.systems.find(({ systemId }) => systemId === system.systemId); + if (!manifest) { + throw new Error( + `System "${system.label}" not found in systems manifest. Run \`mud build\` before trying again.`, + ); + } + const contractData = getContractData(`${system.label}.sol`, system.label, forgeOutDir); + // TODO: replace this with manifest const worldFunctions = system.deploy.registerWorldFunctions ? contractData.abi .filter((item): item is typeof item & { type: "function" } => item.type === "function") @@ -64,7 +73,7 @@ export async function resolveConfig({ }) : []; - // TODO: move to resolveSystems? + // TODO: move to resolveSystems? or system manifest? const allowedAddresses = system.accessList.filter((target): target is Hex => isHex(target)); const allowedSystemIds = system.accessList .filter((target) => !isHex(target)) @@ -80,8 +89,9 @@ export async function resolveConfig({ allowedSystemIds, prepareDeploy: createPrepareDeploy(contractData.bytecode, contractData.placeholders), deployedBytecodeSize: contractData.deployedBytecodeSize, - abi: contractData.abi, worldFunctions, + abi: manifest.abi, + worldAbi: manifest.worldAbi, }; }); diff --git a/packages/cli/src/runDeploy.ts b/packages/cli/src/runDeploy.ts index 36e63e5505..1dc7be3cb8 100644 --- a/packages/cli/src/runDeploy.ts +++ b/packages/cli/src/runDeploy.ts @@ -91,6 +91,10 @@ export async function runDeploy(opts: DeployOptions): Promise { }); const modules = await configToModules(config, outDir); + const tables = Object.values(config.namespaces) + .flatMap((namespace) => Object.values(namespace.tables)) + .filter((table) => !table.deploy.disabled); + const account = await (async () => { if (opts.kms) { const keyId = process.env.AWS_KMS_KEY_ID; @@ -129,10 +133,6 @@ export async function runDeploy(opts: DeployOptions): Promise { console.log("Deploying from", client.account.address); - const tables = Object.values(config.namespaces) - .flatMap((namespace) => Object.values(namespace.tables)) - .filter((table) => !table.deploy.disabled); - const startTime = Date.now(); const worldDeploy = await deploy({ deployerAddress: opts.deployerAddress as Hex | undefined, diff --git a/packages/world/ts/node/buildSystemsManifest.ts b/packages/world/ts/node/buildSystemsManifest.ts index f91fe49944..dce632e275 100644 --- a/packages/world/ts/node/buildSystemsManifest.ts +++ b/packages/world/ts/node/buildSystemsManifest.ts @@ -1,7 +1,7 @@ import { mkdir, writeFile } from "node:fs/promises"; import { ResolvedSystem, resolveSystems } from "./resolveSystems"; import { World } from "../config/v2"; -import { ContractArtifact } from "./common"; +import { ContractArtifact, systemsManifestFilename } from "./common"; import { findContractArtifacts } from "./findContractArtifacts"; import { getOutDirectory as getForgeOutDirectory } from "@latticexyz/common/foundry"; import path from "node:path"; @@ -76,9 +76,8 @@ export async function buildSystemsManifest(opts: { rootDir: string; config: Worl createdAt: Date.now(), } satisfies typeof SystemsManifest.infer; - const manifestFilename = ".mud/local/systems.json"; - const outFile = path.join(opts.rootDir, manifestFilename); + const outFile = path.join(opts.rootDir, systemsManifestFilename); await mkdir(path.dirname(outFile), { recursive: true }); await writeFile(outFile, JSON.stringify(manifest, null, 2) + "\n"); - debug("Wrote systems manifest to", manifestFilename); + debug("Wrote systems manifest to", systemsManifestFilename); } diff --git a/packages/world/ts/node/common.ts b/packages/world/ts/node/common.ts index e72111f6b9..2c4c615cbb 100644 --- a/packages/world/ts/node/common.ts +++ b/packages/world/ts/node/common.ts @@ -3,6 +3,9 @@ import { Abi, Hex } from "viem"; // https://eips.ethereum.org/EIPS/eip-170 export const contractSizeLimit = parseInt("6000", 16); +// relative to project root dir (`rootDir`) +export const systemsManifestFilename = ".mud/local/systems.json"; + export type ReferenceIdentifier = { /** * Path to source file, e.g. `src/SomeLib.sol` diff --git a/packages/world/ts/node/index.ts b/packages/world/ts/node/index.ts index b905dd1189..bbfa3a8013 100644 --- a/packages/world/ts/node/index.ts +++ b/packages/world/ts/node/index.ts @@ -3,3 +3,4 @@ export * from "./findSolidityFiles"; export * from "./getSystemContracts"; export * from "./resolveSystems"; export * from "./buildSystemsManifest"; +export * from "./loadSystemsManifest"; diff --git a/packages/world/ts/node/loadSystemsManifest.ts b/packages/world/ts/node/loadSystemsManifest.ts new file mode 100644 index 0000000000..a3020ab4e0 --- /dev/null +++ b/packages/world/ts/node/loadSystemsManifest.ts @@ -0,0 +1,36 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { type } from "arktype"; +import { World } from "../config/v2"; +import { systemsManifestFilename } from "./common"; +import { SystemsManifest } from "./buildSystemsManifest"; +import { indent } from "@latticexyz/common/utils"; + +const parseManifest = type("parse.json").to(SystemsManifest); + +export async function loadSystemsManifest(opts: { + rootDir: string; + // config is optional in case we want to load manifest within a dependency, like store/world packages + config?: World; +}): Promise { + const outFile = path.join(opts.rootDir, systemsManifestFilename); + try { + await fs.access(outFile, fs.constants.F_OK | fs.constants.R_OK); + } catch (error) { + throw new Error( + `Systems manifest at "${systemsManifestFilename}" not found or not readable. Run \`mud build\` before trying again.`, + ); + } + + const json = await fs.readFile(outFile, "utf8"); + const manifest = parseManifest(json); + if (manifest instanceof type.errors) { + throw new Error( + `Invalid systems manifest at "${systemsManifestFilename}". Run \`mud build\` before trying again.\n${indent(manifest.message)}`, + ); + } + + // TODO: validate that the manifest and config agree (if provided) + + return manifest; +} From 1e60c9855078399dad89275244507db7d735e2c2 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 15:48:44 +0100 Subject: [PATCH 15/23] import at runtime --- packages/world/ts/node/buildSystemsManifest.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/world/ts/node/buildSystemsManifest.ts b/packages/world/ts/node/buildSystemsManifest.ts index dce632e275..6bc40771e3 100644 --- a/packages/world/ts/node/buildSystemsManifest.ts +++ b/packages/world/ts/node/buildSystemsManifest.ts @@ -7,16 +7,9 @@ import { getOutDirectory as getForgeOutDirectory } from "@latticexyz/common/foun import path from "node:path"; import { Abi, Hex, isHex } from "viem"; import { formatAbi, formatAbiItem } from "abitype"; -import IBaseWorldAbi from "../../out/IBaseWorld.sol/IBaseWorld.abi.json"; -import SystemAbi from "../../out/System.sol/System.abi.json"; import { debug } from "./debug"; import { type } from "arktype"; -const excludedAbi = formatAbi([ - ...IBaseWorldAbi.filter((item) => item.type === "event" || item.type === "error"), - ...SystemAbi, -] as Abi); - export const SystemsManifest = type({ systems: [ { @@ -37,6 +30,14 @@ export const SystemsManifest = type({ }); export async function buildSystemsManifest(opts: { rootDir: string; config: World }): Promise { + // we have to import these at runtime because they may not yet exist at build time + const { default: IBaseWorldAbi } = await import("../../out/IBaseWorld.sol/IBaseWorld.abi.json"); + const { default: SystemAbi } = await import("../../out/System.sol/System.abi.json"); + const excludedAbi = formatAbi([ + ...IBaseWorldAbi.filter((item) => item.type === "event" || item.type === "error"), + ...SystemAbi, + ] as Abi); + const systems = await resolveSystems(opts); // TODO: expose a `cwd` option to make sure this runs relative to `rootDir` From 87d2dbc7f00fd226513261674dfb507902295efc Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 15:55:34 +0100 Subject: [PATCH 16/23] type fix --- packages/cli/src/deploy/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/deploy/common.ts b/packages/cli/src/deploy/common.ts index b196844770..f5b768c1b4 100644 --- a/packages/cli/src/deploy/common.ts +++ b/packages/cli/src/deploy/common.ts @@ -98,7 +98,7 @@ export type System = DeterministicContract & { export type DeployedSystem = Omit< System, - "label" | "abi" | "prepareDeploy" | "deployedBytecodeSize" | "allowedSystemIds" + "label" | "abi" | "worldAbi" | "prepareDeploy" | "deployedBytecodeSize" | "allowedSystemIds" > & { address: Address; }; From 1196a395c06688661fa6b2932aa23a8ee725ad82 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 16:24:51 +0100 Subject: [PATCH 17/23] ignore solidity tests/scripts --- packages/world/ts/node/findContractArtifacts.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/world/ts/node/findContractArtifacts.ts b/packages/world/ts/node/findContractArtifacts.ts index a7ed010470..5b7e910168 100644 --- a/packages/world/ts/node/findContractArtifacts.ts +++ b/packages/world/ts/node/findContractArtifacts.ts @@ -24,7 +24,12 @@ function hasMetadata( } export async function findContractArtifacts({ forgeOutDir }: Input): Promise { - const files = (await glob("**/*.sol/*.json", { ignore: "**/*.abi.json", cwd: forgeOutDir })).sort(); + const files = ( + await glob("**/*.sol/*.json", { + ignore: ["**/*.abi.json", "**/*.t.sol/*.json", "**/*.s.sol/*.json"], + cwd: forgeOutDir, + }) + ).sort(); const artifactsJson = await Promise.all( files.map(async (filename) => ({ filename, From 5da01329a6d66b6535bc4ec60514581578c24690 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Thu, 22 Aug 2024 16:35:29 +0100 Subject: [PATCH 18/23] replace parsers --- packages/world/ts/node/findContractArtifacts.ts | 2 +- packages/world/ts/node/loadSystemsManifest.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/world/ts/node/findContractArtifacts.ts b/packages/world/ts/node/findContractArtifacts.ts index 5b7e910168..85b97e1cb0 100644 --- a/packages/world/ts/node/findContractArtifacts.ts +++ b/packages/world/ts/node/findContractArtifacts.ts @@ -14,7 +14,7 @@ export type Input = { export type Output = readonly ContractArtifact[]; -const parseArtifact = type("parse.json").to(types.Artifact); +const parseArtifact = type("string").pipe.try((s) => JSON.parse(s), types.Artifact); type Artifact = typeof types.Artifact.infer; function hasMetadata( diff --git a/packages/world/ts/node/loadSystemsManifest.ts b/packages/world/ts/node/loadSystemsManifest.ts index a3020ab4e0..9147e14a7f 100644 --- a/packages/world/ts/node/loadSystemsManifest.ts +++ b/packages/world/ts/node/loadSystemsManifest.ts @@ -6,7 +6,7 @@ import { systemsManifestFilename } from "./common"; import { SystemsManifest } from "./buildSystemsManifest"; import { indent } from "@latticexyz/common/utils"; -const parseManifest = type("parse.json").to(SystemsManifest); +const parseManifest = type("string").pipe.try((s) => JSON.parse(s), SystemsManifest); export async function loadSystemsManifest(opts: { rootDir: string; From e174fdd2bf70965e58b59f76fd94d6c8d5064913 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 27 Aug 2024 09:25:18 +0100 Subject: [PATCH 19/23] rip out unused code --- packages/common/package.json | 1 - .../common/src/codegen/solidityToAbi.test.ts | 170 ------------------ packages/common/src/codegen/solidityToAbi.ts | 116 ------------ pnpm-lock.yaml | 137 +++++++------- 4 files changed, 68 insertions(+), 356 deletions(-) delete mode 100644 packages/common/src/codegen/solidityToAbi.test.ts delete mode 100644 packages/common/src/codegen/solidityToAbi.ts diff --git a/packages/common/package.json b/packages/common/package.json index 5285a8d0cc..aabbcc8cd4 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -68,7 +68,6 @@ "dependencies": { "@latticexyz/schema-type": "workspace:*", "@solidity-parser/parser": "^0.16.0", - "abitype": "catalog:", "debug": "^4.3.4", "execa": "^7.0.0", "p-queue": "^7.4.1", diff --git a/packages/common/src/codegen/solidityToAbi.test.ts b/packages/common/src/codegen/solidityToAbi.test.ts deleted file mode 100644 index 9b99860116..0000000000 --- a/packages/common/src/codegen/solidityToAbi.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* eslint-disable max-len */ -import { describe, expect, it } from "vitest"; -import { formatAbi } from "abitype"; -import { solidityToAbi } from "./solidityToAbi"; - -const functionVisibility = ["public", "external", "private", "internal"] as const; -const functionMutability = [null, "view", "pure", "payable", "nonpayable"] as const; - -const types = { - uint256: "uint256", - uint: "uint", - payable: "address payable", - stringMemory: "string memory", - stringCalldata: "string calldata", - uintArray: "uint[]", - stringArray: "string[]", - keyTuples: "bytes32[][]", - fixedArray: "bool[2]", - mixedArray: "bool[2][]", - deepArray: "bool[][2][4][8][]", -} as const; - -describe("solidityToAbi", () => { - it("parses error params", () => { - const errors = [ - "error noParam();", - ...Object.entries(types).map(([name, type]) => `error param_${name}(${type} _${name});`), - `error allParams(${Object.entries(types) - .map(([name, type]) => `${type} _${name}`) - .join(", ")});`, - ]; - const source = `contract MyContract {\n ${errors.join("\n ")}\n}`; - - expect(source).toMatchInlineSnapshot(` - "contract MyContract { - error noParam(); - error param_uint256(uint256 _uint256); - error param_uint(uint _uint); - error param_payable(address payable _payable); - error param_stringMemory(string memory _stringMemory); - error param_stringCalldata(string calldata _stringCalldata); - error param_uintArray(uint[] _uintArray); - error param_stringArray(string[] _stringArray); - error param_keyTuples(bytes32[][] _keyTuples); - error param_fixedArray(bool[2] _fixedArray); - error param_mixedArray(bool[2][] _mixedArray); - error param_deepArray(bool[][2][4][8][] _deepArray); - error allParams(uint256 _uint256, uint _uint, address payable _payable, string memory _stringMemory, string calldata _stringCalldata, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray); - }" - `); - - expect( - formatAbi(solidityToAbi({ sourcePath: "test/Test.sol", source, contractName: "MyContract" })), - ).toMatchInlineSnapshot( - ` - [ - "error noParam()", - "error param_uint256(uint256 _uint256)", - "error param_uint(uint _uint)", - "error param_payable(address _payable)", - "error param_stringMemory(string _stringMemory)", - "error param_stringCalldata(string _stringCalldata)", - "error param_uintArray(uint[] _uintArray)", - "error param_stringArray(string[] _stringArray)", - "error param_keyTuples(bytes32[][] _keyTuples)", - "error param_fixedArray(bool[2] _fixedArray)", - "error param_mixedArray(bool[2][] _mixedArray)", - "error param_deepArray(bool[][2][4][8][] _deepArray)", - "error allParams(uint256 _uint256, uint _uint, address _payable, string _stringMemory, string _stringCalldata, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray)", - ] - `, - ); - }); - - it("parses function params", () => { - const functions = [ - "function noParams() public {}", - ...Object.entries(types).map(([name, type]) => `function param_${name}(${type} value) public {}`), - `function allParams(${Object.entries(types) - .map(([name, type]) => `${type} _${name}`) - .join(", ")}) public {}`, - ]; - const source = `contract MyContract {\n ${functions.join("\n ")}\n}`; - - expect(source).toMatchInlineSnapshot(` - "contract MyContract { - function noParams() public {} - function param_uint256(uint256 value) public {} - function param_uint(uint value) public {} - function param_payable(address payable value) public {} - function param_stringMemory(string memory value) public {} - function param_stringCalldata(string calldata value) public {} - function param_uintArray(uint[] value) public {} - function param_stringArray(string[] value) public {} - function param_keyTuples(bytes32[][] value) public {} - function param_fixedArray(bool[2] value) public {} - function param_mixedArray(bool[2][] value) public {} - function param_deepArray(bool[][2][4][8][] value) public {} - function allParams(uint256 _uint256, uint _uint, address payable _payable, string memory _stringMemory, string calldata _stringCalldata, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray) public {} - }" - `); - - expect(formatAbi(solidityToAbi({ sourcePath: "test/Test.sol", source, contractName: "MyContract" }))) - .toMatchInlineSnapshot(` - [ - "function noParams()", - "function param_uint256(uint256 value)", - "function param_uint(uint value)", - "function param_payable(address value)", - "function param_stringMemory(string value)", - "function param_stringCalldata(string value)", - "function param_uintArray(uint[] value)", - "function param_stringArray(string[] value)", - "function param_keyTuples(bytes32[][] value)", - "function param_fixedArray(bool[2] value)", - "function param_mixedArray(bool[2][] value)", - "function param_deepArray(bool[][2][4][8][] value)", - "function allParams(uint256 _uint256, uint _uint, address _payable, string _stringMemory, string _stringCalldata, uint[] _uintArray, string[] _stringArray, bytes32[][] _keyTuples, bool[2] _fixedArray, bool[2][] _mixedArray, bool[][2][4][8][] _deepArray)", - ] - `); - }); - - it("parses public/external functions", () => { - const modifiers = functionVisibility.flatMap((vis) => functionMutability.map((mut) => [vis, mut] as const)); - - const functions = modifiers.map(([vis, mut]) => `function modifier_${vis}_${mut}() ${vis} ${mut ?? ""} {}`); - const source = `contract MyContract {\n ${functions.join("\n ")}\n}`; - - expect(source).toMatchInlineSnapshot(` - "contract MyContract { - function modifier_public_null() public {} - function modifier_public_view() public view {} - function modifier_public_pure() public pure {} - function modifier_public_payable() public payable {} - function modifier_public_nonpayable() public nonpayable {} - function modifier_external_null() external {} - function modifier_external_view() external view {} - function modifier_external_pure() external pure {} - function modifier_external_payable() external payable {} - function modifier_external_nonpayable() external nonpayable {} - function modifier_private_null() private {} - function modifier_private_view() private view {} - function modifier_private_pure() private pure {} - function modifier_private_payable() private payable {} - function modifier_private_nonpayable() private nonpayable {} - function modifier_internal_null() internal {} - function modifier_internal_view() internal view {} - function modifier_internal_pure() internal pure {} - function modifier_internal_payable() internal payable {} - function modifier_internal_nonpayable() internal nonpayable {} - }" - `); - - expect(formatAbi(solidityToAbi({ sourcePath: "test/Test.sol", source, contractName: "MyContract" }))) - .toMatchInlineSnapshot(` - [ - "function modifier_public_null()", - "function modifier_public_view() view", - "function modifier_public_pure() pure", - "function modifier_public_payable() payable", - "function modifier_public_nonpayable()", - "function modifier_external_null()", - "function modifier_external_view() view", - "function modifier_external_pure() pure", - "function modifier_external_payable() payable", - "function modifier_external_nonpayable()", - ] - `); - }); -}); diff --git a/packages/common/src/codegen/solidityToAbi.ts b/packages/common/src/codegen/solidityToAbi.ts deleted file mode 100644 index 5b8874f617..0000000000 --- a/packages/common/src/codegen/solidityToAbi.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { parse, visit } from "@solidity-parser/parser"; -// TODO: add ast types export (https://github.com/solidity-parser/parser/issues/71) -import { - SourceUnit, - ContractDefinition, - VariableDeclaration, - TypeName, -} from "@solidity-parser/parser/dist/src/ast-types"; -import { Abi, AbiFunction, AbiError, AbiParameter, AbiType } from "abitype"; -import { debug as parentDebug } from "./debug"; - -const debug = parentDebug.extend("solidityToAbi"); - -export function solidityToAbi(opts: { sourcePath: string; source: string; contractName: string }): Abi { - // TODO: enable `tolerant` and print nicer message for `ast.errors` - const ast = parse(opts.source); - - const prefix = `[${opts.sourcePath}:${opts.contractName}]`; - const contract = findContractDef(ast, opts.contractName); - if (!contract) { - debug(`${prefix} Could not find contract definition.`); - return []; - } - - // TODO: extract more types (we don't support em in interfaces yet so leaving em out for now) - const abi: (AbiFunction | AbiError)[] = []; - - visit(contract, { - FunctionDefinition({ - name, - visibility, - parameters, - stateMutability, - returnParameters, - isConstructor, - isFallback, - isReceiveEther, - }) { - // TODO: fill these in - if (isConstructor || isFallback || isReceiveEther) return; - - // skip invalid function definitions - if (!name) return debug(`${prefix} Skipping unnamed function.`); - if (visibility === "default") return debug(`${prefix} Skipping function "${name}" with \`default\` visibility.`); - if (stateMutability === "constant") - return debug(`${prefix} Skipping function "${name}" with \`constant\` mutability modifier.`); - - if (visibility === "external" || visibility === "public") { - abi.push({ - type: "function", - name, - stateMutability: stateMutability ?? "nonpayable", - inputs: variablesToParameters(parameters), - outputs: variablesToParameters(returnParameters ?? []), - }); - } - }, - CustomErrorDefinition({ name, parameters }) { - abi.push({ - type: "error", - name, - inputs: variablesToParameters(parameters), - }); - }, - }); - - return abi; -} - -function findContractDef(ast: SourceUnit, contractName: string): ContractDefinition | undefined { - let contract: ContractDefinition | undefined = undefined; - - visit(ast, { - ContractDefinition(node) { - if (node.name === contractName) { - contract = node; - } - }, - }); - - return contract; -} - -function variablesToParameters(variables: readonly VariableDeclaration[]): readonly AbiParameter[] { - return variables.map(({ name, typeName }, i): AbiParameter => { - if (!typeName) throw new Error(`No type definition for variable${name ? ` "${name}"` : ""} at position ${i}.`); - - const type = typeNameToAbiType(typeName); - - return { - name: name ?? "", - type, - }; - }); -} - -function typeNameToAbiType(typeName: TypeName): AbiType { - if (typeName.type === "ElementaryTypeName") { - return typeName.name as AbiType; - } - // if (typeName.type === "UserDefinedTypeName") { - // // TODO: resolve this to underlying abi type - // return typeName.namePath as AbiType; - // } - if (typeName.type === "ArrayTypeName") { - const length = ((): string => { - if (!typeName.length) return ""; - if (typeName.length.type === "NumberLiteral") return typeName.length.number; - if (typeName.length.type === "Identifier") return typeName.length.name; - throw new Error(`Unsupported array length AST type "${typeName.length.type}".`); - })(); - return `${typeNameToAbiType(typeName.baseTypeName)}[${length}]`; - } - - throw new Error(`Unsupported AST parameter type "${typeName.type}".`); -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6e69cfbf2d..948afcd295 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,7 +113,7 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/block-logs-stream: dependencies: @@ -141,7 +141,7 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/cli: dependencies: @@ -280,7 +280,7 @@ importers: version: 3.12.6 vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/common: dependencies: @@ -293,9 +293,6 @@ importers: '@solidity-parser/parser': specifier: ^0.16.0 version: 0.16.0 - abitype: - specifier: 'catalog:' - version: 1.0.5(typescript@5.4.2)(zod@3.23.8) asn1.js: specifier: 5.x version: 5.4.1 @@ -335,7 +332,7 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/config: dependencies: @@ -449,7 +446,7 @@ importers: version: 6.7.0(postcss@8.4.23)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/explorer: dependencies: @@ -640,7 +637,7 @@ importers: version: 3.12.6 vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/gas-report: dependencies: @@ -686,7 +683,7 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/protocol-parser: dependencies: @@ -714,7 +711,7 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/query: dependencies: @@ -742,7 +739,7 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/react: dependencies: @@ -773,7 +770,7 @@ importers: version: 18.2.22 '@vitejs/plugin-react': specifier: ^4.0.0 - version: 4.0.0(vite@4.3.6(@types/node@20.12.12)) + version: 4.0.0(vite@4.3.6(@types/node@20.12.12)(terser@5.31.6)) eslint-plugin-react: specifier: 7.31.11 version: 7.31.11(eslint@8.57.0) @@ -791,10 +788,10 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vite: specifier: ^4.3.6 - version: 4.3.6(@types/node@20.12.12) + version: 4.3.6(@types/node@20.12.12)(terser@5.31.6) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/recs: dependencies: @@ -822,7 +819,7 @@ importers: version: 29.5.0(@types/node@18.15.11) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.5.0)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.15.11))(typescript@5.4.2) + version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.15.11))(typescript@5.4.2) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) @@ -853,7 +850,7 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/solhint-config-mud: devDependencies: @@ -933,7 +930,7 @@ importers: version: 3.12.6 vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/store-indexer: dependencies: @@ -1048,7 +1045,7 @@ importers: version: 3.12.6 vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/store-sync: dependencies: @@ -1145,7 +1142,7 @@ importers: version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/utils: dependencies: @@ -1167,7 +1164,7 @@ importers: version: 29.5.0(@types/node@20.12.12) ts-jest: specifier: ^29.0.5 - version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.5.0)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2) + version: 29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2) tsup: specifier: ^6.7.0 version: 6.7.0(postcss@8.4.31)(typescript@5.4.2) @@ -1246,7 +1243,7 @@ importers: version: 3.12.6 vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/world-module-metadata: dependencies: @@ -1286,7 +1283,7 @@ importers: version: 3.12.6 vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) packages/world-modules: dependencies: @@ -1347,7 +1344,7 @@ importers: version: 3.12.6 vitest: specifier: 0.34.6 - version: 0.34.6(jsdom@22.1.0) + version: 0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6) test/mock-game-contracts: devDependencies: @@ -1395,7 +1392,7 @@ importers: version: 2.19.8(bufferutil@4.0.8)(typescript@5.4.2)(utf-8-validate@5.0.10)(zod@3.23.8) vite: specifier: ^4.2.1 - version: 4.3.6(@types/node@20.12.12) + version: 4.3.6(@types/node@20.12.12)(terser@5.31.6) packages: @@ -11282,7 +11279,7 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 debug: 4.3.4 lodash.debounce: 4.0.8 - resolve: 1.22.2 + resolve: 1.22.8 transitivePeerDependencies: - supports-color @@ -11491,7 +11488,7 @@ snapshots: dependencies: '@babel/core': 7.21.4 '@babel/helper-create-class-features-plugin': 7.25.0(@babel/core@7.21.4) - '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-plugin-utils': 7.24.8 transitivePeerDependencies: - supports-color @@ -11504,13 +11501,13 @@ snapshots: '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.21.4)': dependencies: '@babel/core': 7.21.4 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.4) '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.21.4)': dependencies: '@babel/core': 7.21.4 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-plugin-utils': 7.24.8 '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.4) transitivePeerDependencies: @@ -11543,7 +11540,7 @@ snapshots: '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.21.4)': dependencies: '@babel/core': 7.21.4 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-plugin-utils': 7.24.8 '@babel/plugin-syntax-export-default-from@7.24.7(@babel/core@7.21.4)': dependencies: @@ -12130,7 +12127,7 @@ snapshots: dependencies: '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.24.8 - '@babel/types': 7.21.4 + '@babel/types': 7.25.2 esutils: 2.0.3 '@babel/preset-typescript@7.24.7(@babel/core@7.21.4)': @@ -14312,7 +14309,7 @@ snapshots: '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.21.4) '@babel/plugin-transform-typescript': 7.25.2(@babel/core@7.21.4) '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.21.4) - '@babel/template': 7.20.7 + '@babel/template': 7.25.0 '@react-native/babel-plugin-codegen': 0.75.2(@babel/preset-env@7.25.3(@babel/core@7.21.4)) babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.21.4) react-refresh: 0.14.0 @@ -14322,7 +14319,7 @@ snapshots: '@react-native/codegen@0.75.2(@babel/preset-env@7.25.3(@babel/core@7.21.4))': dependencies: - '@babel/parser': 7.21.4 + '@babel/parser': 7.25.3 '@babel/preset-env': 7.25.3(@babel/core@7.21.4) glob: 7.2.3 hermes-parser: 0.22.0 @@ -14345,7 +14342,7 @@ snapshots: metro: 0.80.10(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) metro-config: 0.80.10(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) metro-core: 0.80.10 - node-fetch: 2.6.9(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.13) querystring: 0.2.1 readline: 1.3.0 transitivePeerDependencies: @@ -14366,7 +14363,7 @@ snapshots: chromium-edge-launcher: 0.2.0 connect: 3.7.0 debug: 2.6.9 - node-fetch: 2.6.9(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.13) nullthrows: 1.1.1 open: 7.4.2 selfsigned: 2.4.1 @@ -15264,13 +15261,13 @@ snapshots: - debug - utf-8-validate - '@vitejs/plugin-react@4.0.0(vite@4.3.6(@types/node@20.12.12))': + '@vitejs/plugin-react@4.0.0(vite@4.3.6(@types/node@20.12.12)(terser@5.31.6))': dependencies: '@babel/core': 7.21.4 '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.4) '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.4) react-refresh: 0.14.0 - vite: 4.3.6(@types/node@20.12.12) + vite: 4.3.6(@types/node@20.12.12)(terser@5.31.6) transitivePeerDependencies: - supports-color @@ -16113,7 +16110,7 @@ snapshots: dependencies: buffer: 5.7.1 inherits: 2.0.4 - readable-stream: 3.6.0 + readable-stream: 3.6.2 bn.js@4.12.0: {} @@ -18827,7 +18824,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.21.4 + '@babel/code-frame': 7.24.7 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -19070,7 +19067,7 @@ snapshots: jscodeshift@0.14.0(@babel/preset-env@7.25.3(@babel/core@7.21.4)): dependencies: '@babel/core': 7.21.4 - '@babel/parser': 7.21.4 + '@babel/parser': 7.25.3 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.4) '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.4) '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.4) @@ -19178,7 +19175,7 @@ snapshots: dependencies: node-addon-api: 2.0.2 node-gyp-build: 4.8.1 - readable-stream: 3.6.0 + readable-stream: 3.6.2 keygrip@1.1.0: dependencies: @@ -19602,13 +19599,13 @@ snapshots: metro-runtime@0.80.10: dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.25.0 flow-enums-runtime: 0.0.6 metro-source-map@0.80.10: dependencies: - '@babel/traverse': 7.21.4 - '@babel/types': 7.21.4 + '@babel/traverse': 7.25.3 + '@babel/types': 7.25.2 flow-enums-runtime: 0.0.6 invariant: 2.2.4 metro-symbolicate: 0.80.10 @@ -19634,9 +19631,9 @@ snapshots: metro-transform-plugins@0.80.10: dependencies: '@babel/core': 7.21.4 - '@babel/generator': 7.21.4 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.4 + '@babel/generator': 7.25.0 + '@babel/template': 7.25.0 + '@babel/traverse': 7.25.3 flow-enums-runtime: 0.0.6 nullthrows: 1.1.1 transitivePeerDependencies: @@ -19645,9 +19642,9 @@ snapshots: metro-transform-worker@0.80.10(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10): dependencies: '@babel/core': 7.21.4 - '@babel/generator': 7.21.4 - '@babel/parser': 7.21.4 - '@babel/types': 7.21.4 + '@babel/generator': 7.25.0 + '@babel/parser': 7.25.3 + '@babel/types': 7.25.2 flow-enums-runtime: 0.0.6 metro: 0.80.10(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) metro-babel-transformer: 0.80.10 @@ -19665,13 +19662,13 @@ snapshots: metro@0.80.10(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10): dependencies: - '@babel/code-frame': 7.21.4 + '@babel/code-frame': 7.24.7 '@babel/core': 7.21.4 - '@babel/generator': 7.21.4 - '@babel/parser': 7.21.4 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.4 - '@babel/types': 7.21.4 + '@babel/generator': 7.25.0 + '@babel/parser': 7.25.3 + '@babel/template': 7.25.0 + '@babel/traverse': 7.25.3 + '@babel/types': 7.25.2 accepts: 1.3.8 chalk: 4.1.2 ci-info: 2.0.0 @@ -19700,7 +19697,7 @@ snapshots: metro-transform-plugins: 0.80.10 metro-transform-worker: 0.80.10(bufferutil@4.0.8)(encoding@0.1.13)(utf-8-validate@5.0.10) mime-types: 2.1.35 - node-fetch: 2.6.9(encoding@0.1.13) + node-fetch: 2.7.0(encoding@0.1.13) nullthrows: 1.1.1 serialize-error: 2.1.0 source-map: 0.5.7 @@ -20886,7 +20883,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.25.0 regexp.prototype.flags@1.4.3: dependencies: @@ -21783,7 +21780,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.0.5(@babel/core@7.21.4)(@jest/types@29.5.0)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.15.11))(typescript@5.4.2): + ts-jest@29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@18.15.11))(typescript@5.4.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -21797,10 +21794,10 @@ snapshots: yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.21.4 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 babel-jest: 29.5.0(@babel/core@7.21.4) - ts-jest@29.0.5(@babel/core@7.21.4)(@jest/types@29.5.0)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2): + ts-jest@29.0.5(@babel/core@7.21.4)(@jest/types@29.6.3)(babel-jest@29.5.0(@babel/core@7.21.4))(jest@29.5.0(@types/node@20.12.12))(typescript@5.4.2): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 @@ -21814,7 +21811,7 @@ snapshots: yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.21.4 - '@jest/types': 29.5.0 + '@jest/types': 29.6.3 babel-jest: 29.5.0(@babel/core@7.21.4) tsconfig-paths@3.15.0: @@ -22168,14 +22165,14 @@ snapshots: - utf-8-validate - zod - vite-node@0.34.6(@types/node@18.15.11): + vite-node@0.34.6(@types/node@18.15.11)(terser@5.31.6): dependencies: cac: 6.7.14 debug: 4.3.4 mlly: 1.5.0 pathe: 1.1.2 picocolors: 1.0.0 - vite: 4.3.6(@types/node@18.15.11) + vite: 4.3.6(@types/node@18.15.11)(terser@5.31.6) transitivePeerDependencies: - '@types/node' - less @@ -22185,7 +22182,7 @@ snapshots: - supports-color - terser - vite@4.3.6(@types/node@18.15.11): + vite@4.3.6(@types/node@18.15.11)(terser@5.31.6): dependencies: esbuild: 0.17.17 postcss: 8.4.23 @@ -22193,8 +22190,9 @@ snapshots: optionalDependencies: '@types/node': 18.15.11 fsevents: 2.3.3 + terser: 5.31.6 - vite@4.3.6(@types/node@20.12.12): + vite@4.3.6(@types/node@20.12.12)(terser@5.31.6): dependencies: esbuild: 0.17.17 postcss: 8.4.23 @@ -22202,8 +22200,9 @@ snapshots: optionalDependencies: '@types/node': 20.12.12 fsevents: 2.3.3 + terser: 5.31.6 - vitest@0.34.6(jsdom@22.1.0): + vitest@0.34.6(jsdom@22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10))(terser@5.31.6): dependencies: '@types/chai': 4.3.5 '@types/chai-subset': 1.3.3 @@ -22226,8 +22225,8 @@ snapshots: strip-literal: 1.3.0 tinybench: 2.6.0 tinypool: 0.7.0 - vite: 4.3.6(@types/node@18.15.11) - vite-node: 0.34.6(@types/node@18.15.11) + vite: 4.3.6(@types/node@18.15.11)(terser@5.31.6) + vite-node: 0.34.6(@types/node@18.15.11)(terser@5.31.6) why-is-node-running: 2.2.2 optionalDependencies: jsdom: 22.1.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) From a0980a97a7c9cc4e2e714be0de0c2ae9dd51692b Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 27 Aug 2024 16:20:20 +0100 Subject: [PATCH 20/23] ignore mud artifacts --- .gitignore | 14 ++++++++++---- e2e/.gitignore | 2 -- examples/local-explorer/.gitignore | 7 ++++++- templates/phaser/.gitignore | 7 +++++-- templates/react-ecs/.gitignore | 7 +++++-- templates/react/.gitignore | 7 +++++-- templates/threejs/.gitignore | 7 +++++-- templates/vanilla/.gitignore | 7 +++++-- test/mock-game-contracts/.gitignore | 3 ++- 9 files changed, 43 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 91920e91c6..0a1e02f28a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,16 +11,22 @@ yarn.lock lerna-debug.log yarn-error.log .turbo +.attest +.tstrace + +.env* # We don't want projects created from templates to ignore their lockfiles, but we don't # want to check them in here, so we'll ignore them from the root. templates/*/pnpm-lock.yaml -.env - +# mud test data test-data/world-logs-bulk-*.json test-data/world-logs-query.json -.attest -.tstrace +# mud artifacts .mud + +# sqlite indexer data +*.db +*.db-journal diff --git a/e2e/.gitignore b/e2e/.gitignore index c7ce05d6b0..3c3629e647 100644 --- a/e2e/.gitignore +++ b/e2e/.gitignore @@ -1,3 +1 @@ node_modules -*.db -*.db-journal diff --git a/examples/local-explorer/.gitignore b/examples/local-explorer/.gitignore index 5eb2fb939a..3737e58258 100644 --- a/examples/local-explorer/.gitignore +++ b/examples/local-explorer/.gitignore @@ -1,2 +1,7 @@ -indexer.db node_modules + +# mud artifacts +.mud +# sqlite indexer data +*.db +*.db-journal diff --git a/templates/phaser/.gitignore b/templates/phaser/.gitignore index c16a9da0e3..3737e58258 100644 --- a/templates/phaser/.gitignore +++ b/templates/phaser/.gitignore @@ -1,4 +1,7 @@ node_modules -# mud sqlite indexer data -indexer.db +# mud artifacts +.mud +# sqlite indexer data +*.db +*.db-journal diff --git a/templates/react-ecs/.gitignore b/templates/react-ecs/.gitignore index c16a9da0e3..3737e58258 100644 --- a/templates/react-ecs/.gitignore +++ b/templates/react-ecs/.gitignore @@ -1,4 +1,7 @@ node_modules -# mud sqlite indexer data -indexer.db +# mud artifacts +.mud +# sqlite indexer data +*.db +*.db-journal diff --git a/templates/react/.gitignore b/templates/react/.gitignore index c16a9da0e3..3737e58258 100644 --- a/templates/react/.gitignore +++ b/templates/react/.gitignore @@ -1,4 +1,7 @@ node_modules -# mud sqlite indexer data -indexer.db +# mud artifacts +.mud +# sqlite indexer data +*.db +*.db-journal diff --git a/templates/threejs/.gitignore b/templates/threejs/.gitignore index c16a9da0e3..3737e58258 100644 --- a/templates/threejs/.gitignore +++ b/templates/threejs/.gitignore @@ -1,4 +1,7 @@ node_modules -# mud sqlite indexer data -indexer.db +# mud artifacts +.mud +# sqlite indexer data +*.db +*.db-journal diff --git a/templates/vanilla/.gitignore b/templates/vanilla/.gitignore index c16a9da0e3..3737e58258 100644 --- a/templates/vanilla/.gitignore +++ b/templates/vanilla/.gitignore @@ -1,4 +1,7 @@ node_modules -# mud sqlite indexer data -indexer.db +# mud artifacts +.mud +# sqlite indexer data +*.db +*.db-journal diff --git a/test/mock-game-contracts/.gitignore b/test/mock-game-contracts/.gitignore index da6ec0e4dc..af9b4a09ff 100644 --- a/test/mock-game-contracts/.gitignore +++ b/test/mock-game-contracts/.gitignore @@ -5,6 +5,7 @@ bindings/ artifacts/ broadcast/ -# Ignore all MUD deploy artifacts +# mud build artifacts +.mud deploys worlds.json* From 88f5141b50917ec1a0f090c9de404eef99bebdcf Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 27 Aug 2024 16:31:29 +0100 Subject: [PATCH 21/23] remove unused lib --- packages/protocol-parser/package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/protocol-parser/package.json b/packages/protocol-parser/package.json index 1c7a511e13..d477df1bd8 100644 --- a/packages/protocol-parser/package.json +++ b/packages/protocol-parser/package.json @@ -37,7 +37,6 @@ "test:ci": "pnpm run test" }, "dependencies": { - "@ark/util": "catalog:", "@latticexyz/common": "workspace:*", "@latticexyz/config": "workspace:*", "@latticexyz/schema-type": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4eff90660..983dfb60ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -687,9 +687,6 @@ importers: packages/protocol-parser: dependencies: - '@ark/util': - specifier: 'catalog:' - version: 0.2.2 '@latticexyz/common': specifier: workspace:* version: link:../common From a4b32347fa1673c9cdc8eef9bb9c1e57e96dd0b3 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 27 Aug 2024 16:33:03 +0100 Subject: [PATCH 22/23] clean up namespace labels --- packages/world/ts/node/resolveSystems.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/world/ts/node/resolveSystems.ts b/packages/world/ts/node/resolveSystems.ts index ee815aa627..152c1bb93a 100644 --- a/packages/world/ts/node/resolveSystems.ts +++ b/packages/world/ts/node/resolveSystems.ts @@ -5,8 +5,6 @@ import { resolveNamespace } from "../config/v2/namespace"; import { resourceToLabel } from "@latticexyz/common"; export type ResolvedSystem = System & { - // TODO: move to config output - readonly namespaceLabel: string; readonly sourcePath: string; }; @@ -20,13 +18,7 @@ export async function resolveSystems({ const systemContracts = await getSystemContracts({ rootDir, config }); // validate every system in config refers to an existing system contract - const configSystems = Object.values(config.namespaces).flatMap((namespace) => - Object.values(namespace.systems).map((system) => ({ - ...system, - // TODO: remove this once config outputs namespace labels of resources - namespaceLabel: namespace.label, - })), - ); + const configSystems = Object.values(config.namespaces).flatMap((namespace) => Object.values(namespace.systems)); const missingSystems = configSystems.filter( (system) => !systemContracts.some((s) => s.namespaceLabel === system.namespace && s.systemLabel === system.label), ); @@ -48,8 +40,6 @@ export async function resolveSystems({ }).systems[contract.systemLabel]; return { ...systemConfig, - // TODO: move to config output - namespaceLabel: contract.namespaceLabel, sourcePath: contract.sourcePath, }; }) From 5ebb3317af295df1f7c49f67ea82f7655a45dc77 Mon Sep 17 00:00:00 2001 From: Kevin Ingersoll Date: Tue, 27 Aug 2024 08:37:21 -0700 Subject: [PATCH 23/23] Create ten-foxes-shave.md --- .changeset/ten-foxes-shave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/ten-foxes-shave.md diff --git a/.changeset/ten-foxes-shave.md b/.changeset/ten-foxes-shave.md new file mode 100644 index 0000000000..a9261af31e --- /dev/null +++ b/.changeset/ten-foxes-shave.md @@ -0,0 +1,5 @@ +--- +"@latticexyz/cli": patch +--- + +In addition to table labels, system labels and ABIs are now registered onchain during deploy.