From fb98e482de18ff9de9d4716dd4df72bcc5e8aa43 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 03:06:53 +0300 Subject: [PATCH 01/22] feat(cli): add enums to store config --- packages/cli/src/config/commonSchemas.ts | 6 ++++ packages/cli/src/config/parseStoreConfig.ts | 40 +++++++++++++++++++-- packages/cli/src/config/validation.ts | 38 +++++++++++++++++++- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/config/commonSchemas.ts b/packages/cli/src/config/commonSchemas.ts index 437932ed5c..15e2fa7003 100644 --- a/packages/cli/src/config/commonSchemas.ts +++ b/packages/cli/src/config/commonSchemas.ts @@ -4,6 +4,8 @@ import { validateCapitalizedName, validateDirectory, validateEthereumAddress, + validateEnum, + validateName, validateRoute, validateSingleLevelRoute, validateUncapitalizedName, @@ -13,6 +15,10 @@ import { export const ObjectName = z.string().superRefine(validateCapitalizedName); /** Uncapitalized names of values, like keys and columns */ export const ValueName = z.string().superRefine(validateUncapitalizedName); +/** Name that can start with any case */ +export const AnyCaseName = z.string().superRefine(validateName); +/** List of unique enum member names and 0 < length < 256 */ +export const UserEnum = z.array(ObjectName).superRefine(validateEnum); /** Ordinary routes */ export const OrdinaryRoute = z.string().superRefine(validateRoute); diff --git a/packages/cli/src/config/parseStoreConfig.ts b/packages/cli/src/config/parseStoreConfig.ts index 0a3a451ce0..7a04ad8391 100644 --- a/packages/cli/src/config/parseStoreConfig.ts +++ b/packages/cli/src/config/parseStoreConfig.ts @@ -1,10 +1,12 @@ import { SchemaType, getStaticByteLength } from "@latticexyz/schema-type"; -import { z } from "zod"; -import { BaseRoute, ObjectName, OrdinaryRoute, ValueName } from "./commonSchemas.js"; +import { RefinementCtx, z, ZodIssueCode } from "zod"; +import { BaseRoute, ObjectName, OrdinaryRoute, UserEnum, ValueName } from "./commonSchemas.js"; +import { getDuplicates } from "./validation.js"; const TableName = ObjectName; const KeyName = ValueName; const ColumnName = ValueName; +const UserEnumName = ObjectName; const PrimaryKey = z .nativeEnum(SchemaType) @@ -42,11 +44,19 @@ const DefaultSingleValueTable = z.nativeEnum(SchemaType).transform((schemaType) }); }); -export const StoreConfig = z.object({ +const StoreConfigUnrefined = z.object({ baseRoute: BaseRoute.default(""), storeImportPath: z.string().default("@latticexyz/store/src/"), tables: z.record(TableName, z.union([DefaultSingleValueTable, FullTable])), + userTypes: z + .object({ + path: BaseRoute.default("/types"), + enums: z.record(UserEnumName, UserEnum).default({}), + }) + .default({}), }); +// finally validate global conditions +export const StoreConfig = StoreConfigUnrefined.superRefine(validateStoreConfig); // zod doesn't preserve doc comments export interface StoreUserConfig { @@ -64,6 +74,8 @@ export interface StoreUserConfig { * - FullTableConfig object for multi-value tables (or for customizable options). */ tables: Record; + /** User-defined types that will be generated and may be used in table schemas instead of `SchemaType` */ + userTypes?: UserTypesConfig; } interface FullTableConfig { @@ -81,8 +93,30 @@ interface FullTableConfig { schema: Record; } +interface UserTypesConfig { + /** Path to the file where common types will be generated and imported from. Default is "/types" */ + path?: string; + /** Enum names mapped to lists of their member names */ + enums?: Record; +} + export type StoreConfig = z.output; export async function parseStoreConfig(config: unknown) { return StoreConfig.parse(config); } + +// Validate conditions that check multiple different config options simultaneously +function validateStoreConfig(config: z.output, ctx: RefinementCtx) { + // global names must be unique + const tableNames = Object.keys(config.tables); + const enumNames = Object.keys(config.userTypes.enums); + const allNames = [...tableNames, ...enumNames]; + const duplicateGlobalNames = getDuplicates(allNames); + if (duplicateGlobalNames.length > 0) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: `Table and enum names must be globally unique: ${duplicateGlobalNames.join(", ")}`, + }); + } +} diff --git a/packages/cli/src/config/validation.ts b/packages/cli/src/config/validation.ts index e857d5e0d0..b726c6e9e9 100644 --- a/packages/cli/src/config/validation.ts +++ b/packages/cli/src/config/validation.ts @@ -1,5 +1,5 @@ import { ethers } from "ethers"; -import { existsSync, readFileSync, statSync } from "fs"; +import { existsSync, statSync } from "fs"; import { ZodIssueCode, RefinementCtx } from "zod"; export function validateName(name: string, ctx: RefinementCtx) { @@ -33,6 +33,30 @@ export function validateUncapitalizedName(name: string, ctx: RefinementCtx) { } } +// validates only the enum array, not the names of enum members +export function validateEnum(members: string[], ctx: RefinementCtx) { + if (members.length === 0) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: `Enum must not be empty`, + }); + } + if (members.length >= 256) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: `Length of enum must be < 256`, + }); + } + + const duplicates = getDuplicates(members); + if (duplicates.length > 0) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: `Enum must not have duplicate names for: ${duplicates.join(", ")}`, + }); + } +} + function _factoryForValidateRoute(requireNonEmpty: boolean, requireSingleLevel: boolean) { return (route: string, ctx: RefinementCtx) => { if (route === "") { @@ -110,3 +134,15 @@ export function validateEthereumAddress(address: string, ctx: RefinementCtx) { }); } } + +export function getDuplicates(array: T[]) { + const checked = new Set(); + const duplicates = new Set(); + for (const element of array) { + if (checked.has(element)) { + duplicates.add(element); + } + checked.add(element); + } + return [...duplicates]; +} From 641961875a2373d82b9239fb9dc42e7c77e10232 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 03:09:41 +0300 Subject: [PATCH 02/22] chore(cli): add export for possibly useful function --- packages/cli/src/render-table/renderTablesFromConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/render-table/renderTablesFromConfig.ts b/packages/cli/src/render-table/renderTablesFromConfig.ts index 0c75c12ba9..16385d1e3c 100644 --- a/packages/cli/src/render-table/renderTablesFromConfig.ts +++ b/packages/cli/src/render-table/renderTablesFromConfig.ts @@ -85,7 +85,7 @@ export function renderTablesFromConfig(config: StoreConfig) { return renderedTables; } -function getSchemaTypeInfo(schemaType: SchemaType): RenderTableType { +export function getSchemaTypeInfo(schemaType: SchemaType): RenderTableType { const staticByteLength = getStaticByteLength(schemaType); const isDynamic = staticByteLength === 0; const typeId = SchemaTypeId[schemaType]; From 8eb8eb76c8cc7f897050c3ecaea9b1931d3ad0da Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 03:11:08 +0300 Subject: [PATCH 03/22] refactor(cli): rename render-table to render-solidity --- packages/cli/src/commands/tablegen.ts | 2 +- packages/cli/src/index.ts | 4 ++-- packages/cli/src/{render-table => render-solidity}/common.ts | 0 packages/cli/src/{render-table => render-solidity}/field.ts | 0 packages/cli/src/{render-table => render-solidity}/index.ts | 0 packages/cli/src/{render-table => render-solidity}/record.ts | 0 .../cli/src/{render-table => render-solidity}/renderTable.ts | 0 .../renderTablesFromConfig.ts | 0 packages/cli/src/{render-table => render-solidity}/types.ts | 0 9 files changed, 3 insertions(+), 3 deletions(-) rename packages/cli/src/{render-table => render-solidity}/common.ts (100%) rename packages/cli/src/{render-table => render-solidity}/field.ts (100%) rename packages/cli/src/{render-table => render-solidity}/index.ts (100%) rename packages/cli/src/{render-table => render-solidity}/record.ts (100%) rename packages/cli/src/{render-table => render-solidity}/renderTable.ts (100%) rename packages/cli/src/{render-table => render-solidity}/renderTablesFromConfig.ts (100%) rename packages/cli/src/{render-table => render-solidity}/types.ts (100%) diff --git a/packages/cli/src/commands/tablegen.ts b/packages/cli/src/commands/tablegen.ts index 0b42dbcba3..59a932dcad 100644 --- a/packages/cli/src/commands/tablegen.ts +++ b/packages/cli/src/commands/tablegen.ts @@ -2,7 +2,7 @@ import type { CommandModule } from "yargs"; import { mkdirSync, writeFileSync } from "fs"; import path from "path"; import { loadStoreConfig } from "../config/loadStoreConfig.js"; -import { renderTablesFromConfig } from "../render-table/renderTablesFromConfig.js"; +import { renderTablesFromConfig } from "../render-solidity/renderTablesFromConfig.js"; import { getSrcDirectory } from "../utils/foundry.js"; import { formatSolidity } from "../utils/format.js"; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 8d400af49a..3ae70db2ce 100755 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,8 +1,8 @@ export { parseStoreConfig } from "./config/parseStoreConfig.js"; export { loadStoreConfig } from "./config/loadStoreConfig.js"; export { loadWorldConfig, resolveWorldConfig, parseWorldConfig } from "./config/loadWorldConfig.js"; -export { renderTablesFromConfig } from "./render-table/renderTablesFromConfig.js"; -export { renderTable } from "./render-table/renderTable.js"; +export { renderTablesFromConfig } from "./render-solidity/renderTablesFromConfig.js"; +export { renderTable } from "./render-solidity/renderTable.js"; export type { StoreUserConfig, diff --git a/packages/cli/src/render-table/common.ts b/packages/cli/src/render-solidity/common.ts similarity index 100% rename from packages/cli/src/render-table/common.ts rename to packages/cli/src/render-solidity/common.ts diff --git a/packages/cli/src/render-table/field.ts b/packages/cli/src/render-solidity/field.ts similarity index 100% rename from packages/cli/src/render-table/field.ts rename to packages/cli/src/render-solidity/field.ts diff --git a/packages/cli/src/render-table/index.ts b/packages/cli/src/render-solidity/index.ts similarity index 100% rename from packages/cli/src/render-table/index.ts rename to packages/cli/src/render-solidity/index.ts diff --git a/packages/cli/src/render-table/record.ts b/packages/cli/src/render-solidity/record.ts similarity index 100% rename from packages/cli/src/render-table/record.ts rename to packages/cli/src/render-solidity/record.ts diff --git a/packages/cli/src/render-table/renderTable.ts b/packages/cli/src/render-solidity/renderTable.ts similarity index 100% rename from packages/cli/src/render-table/renderTable.ts rename to packages/cli/src/render-solidity/renderTable.ts diff --git a/packages/cli/src/render-table/renderTablesFromConfig.ts b/packages/cli/src/render-solidity/renderTablesFromConfig.ts similarity index 100% rename from packages/cli/src/render-table/renderTablesFromConfig.ts rename to packages/cli/src/render-solidity/renderTablesFromConfig.ts diff --git a/packages/cli/src/render-table/types.ts b/packages/cli/src/render-solidity/types.ts similarity index 100% rename from packages/cli/src/render-table/types.ts rename to packages/cli/src/render-solidity/types.ts From 557b75f71221967f0d12742590dd6cec68d49644 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 04:10:56 +0300 Subject: [PATCH 04/22] feat(cli): add renderTypes --- packages/cli/src/commands/tablegen.ts | 21 +++++++++++++++++-- packages/cli/src/config/parseStoreConfig.ts | 2 +- packages/cli/src/render-solidity/common.ts | 5 +++++ .../cli/src/render-solidity/renderTable.ts | 7 ++----- .../cli/src/render-solidity/renderTypes.ts | 19 +++++++++++++++++ .../render-solidity/renderTypesFromConfig.ts | 13 ++++++++++++ packages/cli/src/render-solidity/types.ts | 10 +++++++++ 7 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 packages/cli/src/render-solidity/renderTypes.ts create mode 100644 packages/cli/src/render-solidity/renderTypesFromConfig.ts diff --git a/packages/cli/src/commands/tablegen.ts b/packages/cli/src/commands/tablegen.ts index 59a932dcad..4fc74da426 100644 --- a/packages/cli/src/commands/tablegen.ts +++ b/packages/cli/src/commands/tablegen.ts @@ -5,6 +5,7 @@ import { loadStoreConfig } from "../config/loadStoreConfig.js"; import { renderTablesFromConfig } from "../render-solidity/renderTablesFromConfig.js"; import { getSrcDirectory } from "../utils/foundry.js"; import { formatSolidity } from "../utils/format.js"; +import { renderTypesFromConfig } from "../render-solidity/renderTypesFromConfig.js"; type Options = { configPath?: string; @@ -25,8 +26,10 @@ const commandModule: CommandModule = { const srcDir = await getSrcDirectory(); const config = await loadStoreConfig(configPath); - const renderedTables = renderTablesFromConfig(config); + // render tables + const renderedTables = renderTablesFromConfig(config); + // write tables to files for (const { output, tableName } of renderedTables) { const formattedOutput = await formatSolidity(output); @@ -36,7 +39,21 @@ const commandModule: CommandModule = { const outputPath = path.join(outputDirectory, `${tableName}.sol`); writeFileSync(outputPath, formattedOutput); - console.log(`Generated schema: ${outputPath}`); + console.log(`Generated table: ${outputPath}`); + } + + // render types + const renderedTypes = renderTypesFromConfig(config); + // write types to file + { + const formattedOutput = await formatSolidity(renderedTypes); + + const outputPath = path.join(srcDir, `${config.userTypes.path}.sol`); + const outputDirectory = path.dirname(outputPath); + mkdirSync(outputDirectory, { recursive: true }); + + writeFileSync(outputPath, formattedOutput); + console.log(`Generated types file: ${outputPath}`); } process.exit(0); diff --git a/packages/cli/src/config/parseStoreConfig.ts b/packages/cli/src/config/parseStoreConfig.ts index 7a04ad8391..5e173eb59e 100644 --- a/packages/cli/src/config/parseStoreConfig.ts +++ b/packages/cli/src/config/parseStoreConfig.ts @@ -50,7 +50,7 @@ const StoreConfigUnrefined = z.object({ tables: z.record(TableName, z.union([DefaultSingleValueTable, FullTable])), userTypes: z .object({ - path: BaseRoute.default("/types"), + path: OrdinaryRoute.default("/types"), enums: z.record(UserEnumName, UserEnum).default({}), }) .default({}), diff --git a/packages/cli/src/render-solidity/common.ts b/packages/cli/src/render-solidity/common.ts index 6c2733d8ed..58504c1633 100644 --- a/packages/cli/src/render-solidity/common.ts +++ b/packages/cli/src/render-solidity/common.ts @@ -1,5 +1,10 @@ import { RenderTableOptions } from "./types.js"; +export const renderedSolidityHeader = `// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/* Autogenerated file. Do not edit manually. */`; + /** * Renders a list of lines */ diff --git a/packages/cli/src/render-solidity/renderTable.ts b/packages/cli/src/render-solidity/renderTable.ts index 0236d8e37d..81a1474ef5 100644 --- a/packages/cli/src/render-solidity/renderTable.ts +++ b/packages/cli/src/render-solidity/renderTable.ts @@ -1,4 +1,4 @@ -import { renderArguments, renderCommonData, renderList } from "./common.js"; +import { renderArguments, renderCommonData, renderList, renderedSolidityHeader } from "./common.js"; import { renderFieldMethods } from "./field.js"; import { renderRecordMethods } from "./record.js"; import { RenderTableOptions } from "./types.js"; @@ -8,10 +8,7 @@ export function renderTable(options: RenderTableOptions) { const { _typedTableId, _typedKeyArgs, _primaryKeysDefinition } = renderCommonData(options); - return `// SPDX-License-Identifier: MIT -pragma solidity >=0.8.0; - -/* Autogenerated file. Do not edit manually. */ + return `${renderedSolidityHeader} import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; diff --git a/packages/cli/src/render-solidity/renderTypes.ts b/packages/cli/src/render-solidity/renderTypes.ts new file mode 100644 index 0000000000..1024f87e29 --- /dev/null +++ b/packages/cli/src/render-solidity/renderTypes.ts @@ -0,0 +1,19 @@ +import { renderArguments, renderList, renderedSolidityHeader } from "./common.js"; +import { RenderTypesOptions } from "./types.js"; + +export function renderTypes(options: RenderTypesOptions) { + const { enums } = options; + + return `${renderedSolidityHeader} + +${renderList( + enums, + ({ name, memberNames }) => ` + enum ${name} { + ${renderArguments(memberNames)} + } +` +)} + +`; +} diff --git a/packages/cli/src/render-solidity/renderTypesFromConfig.ts b/packages/cli/src/render-solidity/renderTypesFromConfig.ts new file mode 100644 index 0000000000..19d6f9923e --- /dev/null +++ b/packages/cli/src/render-solidity/renderTypesFromConfig.ts @@ -0,0 +1,13 @@ +import { StoreConfig } from "../config/parseStoreConfig.js"; +import { renderTypes } from "./renderTypes.js"; + +export function renderTypesFromConfig(config: StoreConfig) { + const enums = Object.keys(config.userTypes.enums).map((name) => ({ + name, + memberNames: config.userTypes.enums[name], + })); + + return renderTypes({ + enums, + }); +} diff --git a/packages/cli/src/render-solidity/types.ts b/packages/cli/src/render-solidity/types.ts index e054581aea..87a35a514e 100644 --- a/packages/cli/src/render-solidity/types.ts +++ b/packages/cli/src/render-solidity/types.ts @@ -49,3 +49,13 @@ export interface RenderTableField extends RenderTableType { name: string; methodNameSuffix: string; } + +export interface RenderTypesOptions { + /** List of enums to render */ + enums: RenderTypesEnum[]; +} + +export interface RenderTypesEnum { + name: string; + memberNames: string[]; +} From a9b7041150b9e78117b67fdc60e321df3bb25b1a Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 13:52:17 +0300 Subject: [PATCH 05/22] feat(cli): separate route and output directory --- packages/cli/src/commands/tablegen.ts | 11 ++++----- packages/cli/src/config/parseStoreConfig.ts | 24 +++++++++++++++---- .../render-solidity/renderTablesFromConfig.ts | 5 ++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/packages/cli/src/commands/tablegen.ts b/packages/cli/src/commands/tablegen.ts index 4fc74da426..9c8f7e3b5c 100644 --- a/packages/cli/src/commands/tablegen.ts +++ b/packages/cli/src/commands/tablegen.ts @@ -30,11 +30,10 @@ const commandModule: CommandModule = { // render tables const renderedTables = renderTablesFromConfig(config); // write tables to files - for (const { output, tableName } of renderedTables) { + for (const { directory, output, tableName } of renderedTables) { const formattedOutput = await formatSolidity(output); - const tablePath = config.tables[tableName].route; - const outputDirectory = path.join(srcDir, tablePath); + const outputDirectory = path.join(srcDir, directory); mkdirSync(outputDirectory, { recursive: true }); const outputPath = path.join(outputDirectory, `${tableName}.sol`); @@ -43,9 +42,9 @@ const commandModule: CommandModule = { } // render types - const renderedTypes = renderTypesFromConfig(config); - // write types to file - { + if (Object.keys(config.userTypes.enums).length > 0) { + const renderedTypes = renderTypesFromConfig(config); + // write types to file const formattedOutput = await formatSolidity(renderedTypes); const outputPath = path.join(srcDir, `${config.userTypes.path}.sol`); diff --git a/packages/cli/src/config/parseStoreConfig.ts b/packages/cli/src/config/parseStoreConfig.ts index 5e173eb59e..0064f78bea 100644 --- a/packages/cli/src/config/parseStoreConfig.ts +++ b/packages/cli/src/config/parseStoreConfig.ts @@ -19,7 +19,8 @@ const Schema = z const FullTable = z .object({ - route: OrdinaryRoute.default("/tables"), + directory: OrdinaryRoute.default("/tables"), + route: BaseRoute.optional(), tableIdArgument: z.boolean().default(false), storeArgument: z.boolean().default(false), primaryKeys: PrimaryKeys, @@ -33,7 +34,7 @@ const FullTable = z } else { arg.dataStruct ??= true; } - return arg as Omit & Required>; + return arg as RequiredKeys; }); const DefaultSingleValueTable = z.nativeEnum(SchemaType).transform((schemaType) => { @@ -44,10 +45,21 @@ const DefaultSingleValueTable = z.nativeEnum(SchemaType).transform((schemaType) }); }); +const TablesRecord = z.record(TableName, z.union([DefaultSingleValueTable, FullTable])).transform((tables) => { + // default route depends on tableName + for (const tableName of Object.keys(tables)) { + const table = tables[tableName]; + table.route ??= `/${tableName}`; + + tables[tableName] = table; + } + return tables as Record>; +}); + const StoreConfigUnrefined = z.object({ baseRoute: BaseRoute.default(""), storeImportPath: z.string().default("@latticexyz/store/src/"), - tables: z.record(TableName, z.union([DefaultSingleValueTable, FullTable])), + tables: TablesRecord, userTypes: z .object({ path: OrdinaryRoute.default("/types"), @@ -79,7 +91,9 @@ export interface StoreUserConfig { } interface FullTableConfig { - /** Output path for the file, and relevant for the table id. The table id will be keccak256(concat(baseRoute,route,tableName)). Default is "tables/" */ + /** Output directory path for the file. Default is "/tables" */ + directory?: string; + /** Route is used to register the table and construct its id. The table id will be keccak256(concat(baseRoute,route)). Default is "/" */ route?: string; /** Make methods accept `tableId` argument instead of it being a hardcoded constant. Default is false */ tableIdArgument?: boolean; @@ -120,3 +134,5 @@ function validateStoreConfig(config: z.output, ctx: }); } } + +type RequiredKeys, P extends string> = T & Required>; \ No newline at end of file diff --git a/packages/cli/src/render-solidity/renderTablesFromConfig.ts b/packages/cli/src/render-solidity/renderTablesFromConfig.ts index 16385d1e3c..c56f258c82 100644 --- a/packages/cli/src/render-solidity/renderTablesFromConfig.ts +++ b/packages/cli/src/render-solidity/renderTablesFromConfig.ts @@ -59,13 +59,14 @@ export function renderTablesFromConfig(config: StoreConfig) { } else { return { tableIdName: tableName + "TableId", - baseRoute: config.baseRoute + tableData.route, - subRoute: `/${tableName}`, + baseRoute: config.baseRoute, + subRoute: tableData.route, }; } })(); renderedTables.push({ + directory: tableData.directory, tableName, tableData, output: renderTable({ From aab33cfd65ee20a87e794c41916cf7ec90a3158a Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 13:53:03 +0300 Subject: [PATCH 06/22] refactor(store): use new config --- packages/store/mud.config.mts | 1 - packages/store/src/tables/Callbacks.sol | 2 +- packages/store/src/tables/Hooks.sol | 2 +- packages/store/src/tables/Mixed.sol | 2 +- packages/store/src/tables/StoreMetadata.sol | 2 +- packages/store/src/tables/Vector2.sol | 2 +- 6 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/store/mud.config.mts b/packages/store/mud.config.mts index db1015280a..16e0dc2654 100644 --- a/packages/store/mud.config.mts +++ b/packages/store/mud.config.mts @@ -3,7 +3,6 @@ import { SchemaType } from "@latticexyz/schema-type"; const config: StoreUserConfig = { storeImportPath: "../", - baseRoute: "/store_internals", tables: { Hooks: SchemaType.ADDRESS_ARRAY, diff --git a/packages/store/src/tables/Callbacks.sol b/packages/store/src/tables/Callbacks.sol index a206ad4b62..8d6117eac2 100644 --- a/packages/store/src/tables/Callbacks.sol +++ b/packages/store/src/tables/Callbacks.sol @@ -14,7 +14,7 @@ import { EncodeArray } from "../tightcoder/EncodeArray.sol"; import { Schema, SchemaLib } from "../Schema.sol"; import { PackedCounter, PackedCounterLib } from "../PackedCounter.sol"; -uint256 constant _tableId = uint256(keccak256("/store_internals/tables/Callbacks")); +uint256 constant _tableId = uint256(keccak256("/Callbacks")); uint256 constant CallbacksTableId = _tableId; library Callbacks { diff --git a/packages/store/src/tables/Hooks.sol b/packages/store/src/tables/Hooks.sol index a76b512c7c..63d6446597 100644 --- a/packages/store/src/tables/Hooks.sol +++ b/packages/store/src/tables/Hooks.sol @@ -14,7 +14,7 @@ import { EncodeArray } from "../tightcoder/EncodeArray.sol"; import { Schema, SchemaLib } from "../Schema.sol"; import { PackedCounter, PackedCounterLib } from "../PackedCounter.sol"; -uint256 constant _tableId = uint256(keccak256("/store_internals/tables/Hooks")); +uint256 constant _tableId = uint256(keccak256("/Hooks")); uint256 constant HooksTableId = _tableId; library Hooks { diff --git a/packages/store/src/tables/Mixed.sol b/packages/store/src/tables/Mixed.sol index a87c221c27..f864e64d8b 100644 --- a/packages/store/src/tables/Mixed.sol +++ b/packages/store/src/tables/Mixed.sol @@ -14,7 +14,7 @@ import { EncodeArray } from "../tightcoder/EncodeArray.sol"; import { Schema, SchemaLib } from "../Schema.sol"; import { PackedCounter, PackedCounterLib } from "../PackedCounter.sol"; -uint256 constant _tableId = uint256(keccak256("/store_internals/tables/Mixed")); +uint256 constant _tableId = uint256(keccak256("/Mixed")); uint256 constant MixedTableId = _tableId; struct MixedData { diff --git a/packages/store/src/tables/StoreMetadata.sol b/packages/store/src/tables/StoreMetadata.sol index 1611ba8bf4..59c8d6da86 100644 --- a/packages/store/src/tables/StoreMetadata.sol +++ b/packages/store/src/tables/StoreMetadata.sol @@ -14,7 +14,7 @@ import { EncodeArray } from "../tightcoder/EncodeArray.sol"; import { Schema, SchemaLib } from "../Schema.sol"; import { PackedCounter, PackedCounterLib } from "../PackedCounter.sol"; -uint256 constant _tableId = uint256(keccak256("/store_internals/tables/StoreMetadata")); +uint256 constant _tableId = uint256(keccak256("/StoreMetadata")); uint256 constant StoreMetadataTableId = _tableId; struct StoreMetadataData { diff --git a/packages/store/src/tables/Vector2.sol b/packages/store/src/tables/Vector2.sol index 30ed494b93..37df54d56b 100644 --- a/packages/store/src/tables/Vector2.sol +++ b/packages/store/src/tables/Vector2.sol @@ -14,7 +14,7 @@ import { EncodeArray } from "../tightcoder/EncodeArray.sol"; import { Schema, SchemaLib } from "../Schema.sol"; import { PackedCounter, PackedCounterLib } from "../PackedCounter.sol"; -uint256 constant _tableId = uint256(keccak256("/store_internals/tables/Vector2")); +uint256 constant _tableId = uint256(keccak256("/Vector2")); uint256 constant Vector2TableId = _tableId; struct Vector2Data { From 6bca6e9a1e239277dd6f8b89d20fb8353c8f8e28 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 13:59:42 +0300 Subject: [PATCH 07/22] refactor(world): use new config --- packages/world/mud.config.mts | 2 -- packages/world/src/tables/RouteAccess.sol | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/world/mud.config.mts b/packages/world/mud.config.mts index 1a44f319f1..4ba19e0b7c 100644 --- a/packages/world/mud.config.mts +++ b/packages/world/mud.config.mts @@ -2,8 +2,6 @@ import { StoreUserConfig } from "@latticexyz/cli"; import { SchemaType } from "@latticexyz/schema-type"; const config: StoreUserConfig = { - baseRoute: "/world_internals", - tables: { RouteAccess: { primaryKeys: { diff --git a/packages/world/src/tables/RouteAccess.sol b/packages/world/src/tables/RouteAccess.sol index 63000ad8d2..c7cacf39fd 100644 --- a/packages/world/src/tables/RouteAccess.sol +++ b/packages/world/src/tables/RouteAccess.sol @@ -14,7 +14,7 @@ import { EncodeArray } from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; import { Schema, SchemaLib } from "@latticexyz/store/src/Schema.sol"; import { PackedCounter, PackedCounterLib } from "@latticexyz/store/src/PackedCounter.sol"; -uint256 constant _tableId = uint256(keccak256("/world_internals/tables/RouteAccess")); +uint256 constant _tableId = uint256(keccak256("/RouteAccess")); uint256 constant RouteAccessTableId = _tableId; library RouteAccess { From e504b45e63b9af37126273ae44a18bfaa7dc0774 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 17:24:52 +0300 Subject: [PATCH 08/22] feat(cli): finish userType parsing --- packages/cli/src/commands/tablegen.ts | 9 ++- packages/cli/src/config/commonSchemas.ts | 6 ++ .../cli/src/config/parseStoreConfig.test-d.ts | 3 + packages/cli/src/config/parseStoreConfig.ts | 60 +++++++++++++------ .../render-solidity/renderTablesFromConfig.ts | 31 +++++++--- packages/cli/src/render-solidity/types.ts | 4 ++ packages/cli/src/render-solidity/userType.ts | 57 ++++++++++++++++++ 7 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 packages/cli/src/render-solidity/userType.ts diff --git a/packages/cli/src/commands/tablegen.ts b/packages/cli/src/commands/tablegen.ts index 9c8f7e3b5c..93910fd75b 100644 --- a/packages/cli/src/commands/tablegen.ts +++ b/packages/cli/src/commands/tablegen.ts @@ -23,17 +23,16 @@ const commandModule: CommandModule = { }, async handler({ configPath }) { - const srcDir = await getSrcDirectory(); + const srcDirectory = await getSrcDirectory(); const config = await loadStoreConfig(configPath); // render tables - const renderedTables = renderTablesFromConfig(config); + const renderedTables = renderTablesFromConfig(config, srcDirectory); // write tables to files - for (const { directory, output, tableName } of renderedTables) { + for (const { outputDirectory, output, tableName } of renderedTables) { const formattedOutput = await formatSolidity(output); - const outputDirectory = path.join(srcDir, directory); mkdirSync(outputDirectory, { recursive: true }); const outputPath = path.join(outputDirectory, `${tableName}.sol`); @@ -47,7 +46,7 @@ const commandModule: CommandModule = { // write types to file const formattedOutput = await formatSolidity(renderedTypes); - const outputPath = path.join(srcDir, `${config.userTypes.path}.sol`); + const outputPath = path.join(srcDirectory, `${config.userTypes.path}.sol`); const outputDirectory = path.dirname(outputPath); mkdirSync(outputDirectory, { recursive: true }); diff --git a/packages/cli/src/config/commonSchemas.ts b/packages/cli/src/config/commonSchemas.ts index 15e2fa7003..3957e16dd6 100644 --- a/packages/cli/src/config/commonSchemas.ts +++ b/packages/cli/src/config/commonSchemas.ts @@ -1,3 +1,4 @@ +import { getStaticByteLength, SchemaType } from "@latticexyz/schema-type"; import { z } from "zod"; import { validateBaseRoute, @@ -32,3 +33,8 @@ export const Directory = z.string().superRefine(validateDirectory); /** A valid Ethereum address */ export const EthereumAddress = z.string().superRefine(validateEthereumAddress); + +/** Static subset of SchemaType enum */ +export const StaticSchemaType = z + .nativeEnum(SchemaType) + .refine((arg) => getStaticByteLength(arg) > 0, "Primary key must not use dynamic SchemaType"); diff --git a/packages/cli/src/config/parseStoreConfig.test-d.ts b/packages/cli/src/config/parseStoreConfig.test-d.ts index 4d3a7ad9e8..96fd9a6db8 100644 --- a/packages/cli/src/config/parseStoreConfig.test-d.ts +++ b/packages/cli/src/config/parseStoreConfig.test-d.ts @@ -7,5 +7,8 @@ describe("StoreUserConfig", () => { expectTypeOf().toEqualTypeOf>(); // type equality isn't deep for optionals expectTypeOf().toEqualTypeOf["tables"][string]>(); + expectTypeOf["enums"]>[string]>().toEqualTypeOf< + NonNullable["userTypes"]>["enums"]>[string] + >(); // TODO If more nested schemas are added, provide separate tests for them }); diff --git a/packages/cli/src/config/parseStoreConfig.ts b/packages/cli/src/config/parseStoreConfig.ts index 0064f78bea..22f6a030ba 100644 --- a/packages/cli/src/config/parseStoreConfig.ts +++ b/packages/cli/src/config/parseStoreConfig.ts @@ -1,6 +1,6 @@ -import { SchemaType, getStaticByteLength } from "@latticexyz/schema-type"; +import { SchemaType } from "@latticexyz/schema-type"; import { RefinementCtx, z, ZodIssueCode } from "zod"; -import { BaseRoute, ObjectName, OrdinaryRoute, UserEnum, ValueName } from "./commonSchemas.js"; +import { BaseRoute, ObjectName, OrdinaryRoute, StaticSchemaType, UserEnum, ValueName } from "./commonSchemas.js"; import { getDuplicates } from "./validation.js"; const TableName = ObjectName; @@ -8,16 +8,18 @@ const KeyName = ValueName; const ColumnName = ValueName; const UserEnumName = ObjectName; -const PrimaryKey = z - .nativeEnum(SchemaType) - .refine((arg) => getStaticByteLength(arg) > 0, "Primary key must not use dynamic SchemaType"); +// Fields can use SchemaType or one of user defined wrapper types +const FieldData = z.union([z.nativeEnum(SchemaType), UserEnumName]); + +// Primary keys allow only static types, but allow static user defined types +const PrimaryKey = z.union([StaticSchemaType, UserEnumName]); const PrimaryKeys = z.record(KeyName, PrimaryKey).default({ key: SchemaType.BYTES32 }); const Schema = z - .record(ColumnName, z.nativeEnum(SchemaType)) + .record(ColumnName, FieldData) .refine((arg) => Object.keys(arg).length > 0, "Table schema may not be empty"); -const FullTable = z +const TableDataFull = z .object({ directory: OrdinaryRoute.default("/tables"), route: BaseRoute.optional(), @@ -37,15 +39,15 @@ const FullTable = z return arg as RequiredKeys; }); -const DefaultSingleValueTable = z.nativeEnum(SchemaType).transform((schemaType) => { - return FullTable.parse({ +const TableDataShorthand = FieldData.transform((fieldData) => { + return TableDataFull.parse({ schema: { - value: schemaType, + value: fieldData, }, }); }); -const TablesRecord = z.record(TableName, z.union([DefaultSingleValueTable, FullTable])).transform((tables) => { +const TablesRecord = z.record(TableName, z.union([TableDataShorthand, TableDataFull])).transform((tables) => { // default route depends on tableName for (const tableName of Object.keys(tables)) { const table = tables[tableName]; @@ -82,10 +84,10 @@ export interface StoreUserConfig { * The key is the table name (capitalized). * * The value: - * - SchemaType for a single-value table (aka ECS component). + * - `SchemaType | userType` for a single-value table (aka ECS component). * - FullTableConfig object for multi-value tables (or for customizable options). */ - tables: Record; + tables: Record | FullTableConfig>; /** User-defined types that will be generated and may be used in table schemas instead of `SchemaType` */ userTypes?: UserTypesConfig; } @@ -102,9 +104,9 @@ interface FullTableConfig { /** Include a data struct and methods for it. Default is false for 1-column tables; true for multi-column tables. */ dataStruct?: boolean; /** Table's primary key names mapped to their types. Default is `{ key: SchemaType.BYTES32 }` */ - primaryKeys?: Record; + primaryKeys?: Record>; /** Table's column names mapped to their types. Table name's 1st letter should be lowercase. */ - schema: Record; + schema: Record>; } interface UserTypesConfig { @@ -124,15 +126,37 @@ export async function parseStoreConfig(config: unknown) { function validateStoreConfig(config: z.output, ctx: RefinementCtx) { // global names must be unique const tableNames = Object.keys(config.tables); - const enumNames = Object.keys(config.userTypes.enums); - const allNames = [...tableNames, ...enumNames]; - const duplicateGlobalNames = getDuplicates(allNames); + const userTypeNames = Object.keys(config.userTypes.enums); + const globalNames = [...tableNames]; + const duplicateGlobalNames = getDuplicates(globalNames); if (duplicateGlobalNames.length > 0) { ctx.addIssue({ code: ZodIssueCode.custom, message: `Table and enum names must be globally unique: ${duplicateGlobalNames.join(", ")}`, }); } + // user types must exist + for (const table of Object.values(config.tables)) { + for (const primaryKeyType of Object.values(table.primaryKeys)) { + validateIfUserType(userTypeNames, primaryKeyType, ctx); + } + for (const fieldType of Object.values(table.schema)) { + validateIfUserType(userTypeNames, fieldType, ctx); + } + } +} + +function validateIfUserType( + userTypeNames: string[], + type: z.output | z.output, + ctx: RefinementCtx +) { + if (typeof type === "string" && !userTypeNames.includes(type)) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: `User type ${type} is not defined in userTypes`, + }); + } } type RequiredKeys, P extends string> = T & Required>; \ No newline at end of file diff --git a/packages/cli/src/render-solidity/renderTablesFromConfig.ts b/packages/cli/src/render-solidity/renderTablesFromConfig.ts index c56f258c82..0690694f95 100644 --- a/packages/cli/src/render-solidity/renderTablesFromConfig.ts +++ b/packages/cli/src/render-solidity/renderTablesFromConfig.ts @@ -1,3 +1,4 @@ +import path from "path"; import { renderTable } from "./renderTable.js"; import { SchemaType, SchemaTypeArrayToElement, SchemaTypeId, getStaticByteLength } from "@latticexyz/schema-type"; import { StoreConfig } from "../config/parseStoreConfig.js"; @@ -8,13 +9,15 @@ import { RenderTableStaticField, RenderTableType, } from "./types.js"; +import { resolveSchemaOrUserType } from "./userType.js"; -export function renderTablesFromConfig(config: StoreConfig) { +export function renderTablesFromConfig(config: StoreConfig, srcDirectory: string) { const storeImportPath = config.storeImportPath; const renderedTables = []; for (const tableName of Object.keys(config.tables)) { const tableData = config.tables[tableName]; + const outputDirectory = path.join(srcDirectory, tableData.directory); // struct adds methods to get/set all values at once const withStruct = tableData.dataStruct; @@ -24,26 +27,40 @@ export function renderTablesFromConfig(config: StoreConfig) { const noFieldMethodSuffix = !withRecordMethods && Object.keys(tableData.schema).length === 1; const primaryKeys = Object.keys(tableData.primaryKeys).map((name) => { - const type = tableData.primaryKeys[name]; - const typeInfo = getSchemaTypeInfo(type); + const schemaOrUserType = tableData.primaryKeys[name]; + const { schemaType, userTypeDetails } = resolveSchemaOrUserType( + schemaOrUserType, + outputDirectory, + config.userTypes + ); + + const typeInfo = getSchemaTypeInfo(schemaType); if (typeInfo.isDynamic) throw new Error("Parsing error: found dynamic primary key"); const primaryKey: RenderTablePrimaryKey = { ...typeInfo, name, isDynamic: false, + userTypeDetails, }; return primaryKey; }); const fields = Object.keys(tableData.schema).map((name) => { - const type = tableData.schema[name]; - const elementType = SchemaTypeArrayToElement[type]; + const schemaOrUserType = tableData.schema[name]; + const { schemaType, userTypeDetails } = resolveSchemaOrUserType( + schemaOrUserType, + outputDirectory, + config.userTypes + ); + + const elementType = SchemaTypeArrayToElement[schemaType]; const field: RenderTableField = { - ...getSchemaTypeInfo(type), + ...getSchemaTypeInfo(schemaType), arrayElement: elementType ? getSchemaTypeInfo(elementType) : undefined, name, methodNameSuffix: noFieldMethodSuffix ? "" : `${name[0].toUpperCase()}${name.slice(1)}`, + userTypeDetails, }; return field; }); @@ -66,7 +83,7 @@ export function renderTablesFromConfig(config: StoreConfig) { })(); renderedTables.push({ - directory: tableData.directory, + outputDirectory, tableName, tableData, output: renderTable({ diff --git a/packages/cli/src/render-solidity/types.ts b/packages/cli/src/render-solidity/types.ts index 87a35a514e..f8b8dbacf6 100644 --- a/packages/cli/src/render-solidity/types.ts +++ b/packages/cli/src/render-solidity/types.ts @@ -1,3 +1,5 @@ +import { UserTypeDetails } from "./userType.js"; + export interface RenderTableOptions { /** Name of the library to render. */ libraryName: string; @@ -34,6 +36,7 @@ export interface RenderTableType { export interface RenderTablePrimaryKey extends RenderTableType { name: string; isDynamic: false; + userTypeDetails?: UserTypeDetails; } export interface RenderTableStaticField extends RenderTableField { @@ -48,6 +51,7 @@ export interface RenderTableField extends RenderTableType { arrayElement: RenderTableType | undefined; name: string; methodNameSuffix: string; + userTypeDetails?: UserTypeDetails; } export interface RenderTypesOptions { diff --git a/packages/cli/src/render-solidity/userType.ts b/packages/cli/src/render-solidity/userType.ts new file mode 100644 index 0000000000..7905674b8d --- /dev/null +++ b/packages/cli/src/render-solidity/userType.ts @@ -0,0 +1,57 @@ +import { SchemaType } from "@latticexyz/schema-type"; +import path from "path"; +import { StoreConfig } from "../index.js"; + +export type UserTypeDetails = ReturnType; + +/** + * Resolve a SchemaType|userType into a general type info object + */ +export function resolveSchemaOrUserType( + type: SchemaType | string, + usedInDirectory: string, + userTypesConfig: StoreConfig["userTypes"] +) { + let schemaType; + let userTypeDetails; + if (typeof type === "string") { + userTypeDetails = getUserTypeDetails(type, usedInDirectory, userTypesConfig); + schemaType = userTypeDetails.schemaType; + } else { + schemaType = type; + } + + return { + schemaType, + userTypeDetails, + }; +} + +/** + * Get all the necessary rendering details for the user type + */ +export function getUserTypeDetails( + userType: string, + usedInDirectory: string, + userTypesConfig: StoreConfig["userTypes"] +) { + // Relative import path for this type. + // "./" must be added because path stripts it, + // but solidity expects it unless there's "../" ("./../" is fine) + const importPath = "./" + path.relative(usedInDirectory, userTypesConfig.path); + + if (userType in userTypesConfig.enums) { + const renderCastFrom = (arg: string) => `${userType}.unwrap(${arg})`; + const renderCastTo = (arg: string) => `${userType}.wrap(${arg})`; + + return { + importPath, + userType, + schemaType: SchemaType.UINT8, + renderCastFrom, + renderCastTo, + }; + } else { + throw new Error(`User type "${userType}" does not exist`); + } +} From d3a61370d0d4f6554becd647201afe9e11b3d889 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 20:45:21 +0300 Subject: [PATCH 09/22] feat(cli): support generalized wrapped types in rendering --- packages/cli/src/render-solidity/common.ts | 43 ++++++--- packages/cli/src/render-solidity/field.ts | 49 +++++----- packages/cli/src/render-solidity/record.ts | 11 +-- .../cli/src/render-solidity/renderTable.ts | 25 ++++-- .../render-solidity/renderTablesFromConfig.ts | 41 ++++----- packages/cli/src/render-solidity/types.ts | 19 +++- packages/cli/src/render-solidity/userType.ts | 90 ++++++++++++------- 7 files changed, 173 insertions(+), 105 deletions(-) diff --git a/packages/cli/src/render-solidity/common.ts b/packages/cli/src/render-solidity/common.ts index 58504c1633..1035965525 100644 --- a/packages/cli/src/render-solidity/common.ts +++ b/packages/cli/src/render-solidity/common.ts @@ -1,4 +1,4 @@ -import { RenderTableOptions } from "./types.js"; +import { ImportDatum, RenderTableOptions, RenderTableType } from "./types.js"; export const renderedSolidityHeader = `// SPDX-License-Identifier: MIT pragma solidity >=0.8.0; @@ -32,8 +32,8 @@ export function renderCommonData({ staticRouteData, primaryKeys }: RenderTableOp bytes32[] memory _primaryKeys = new bytes32[](${primaryKeys.length}); ${renderList( primaryKeys, - ({ name, typeId, staticByteLength }, index) => ` - _primaryKeys[${index}] = ${renderValueTypeToBytes32(name, typeId, staticByteLength)}; + (primaryKey, index) => ` + _primaryKeys[${index}] = ${renderValueTypeToBytes32(primaryKey.name, primaryKey)}; ` )} `; @@ -47,21 +47,44 @@ export function renderCommonData({ staticRouteData, primaryKeys }: RenderTableOp }; } -function renderValueTypeToBytes32(innerText: string, typeId: string, staticByteLength: number) { +/** + * Aggregates, deduplicates and renders imports for symbols per path. + * Identical symbols from different paths are NOT handled, they should be checked before rendering. + */ +export function renderImports(imports: ImportDatum[]) { + // Aggregate symbols by import path, also deduplicating them + const aggregatedImports = new Map>(); + for (const { symbol, path } of imports) { + if (!aggregatedImports.has(path)) { + aggregatedImports.set(path, new Set()); + } + aggregatedImports.get(path)?.add(symbol); + } + // Render imports + const renderedImports = []; + for (const [path, symbols] of aggregatedImports) { + const renderedSymbols = [...symbols].join(", "); + renderedImports.push(`import { ${renderedSymbols} } from "${path}";`); + } + return renderedImports.join("\n"); +} + +function renderValueTypeToBytes32(name: string, { staticByteLength, typeUnwrap, internalTypeId }: RenderTableType) { const bits = staticByteLength * 8; + const innerText = `${typeUnwrap}(${name})`; - if (typeId.match(/^uint\d{1,3}$/)) { + if (internalTypeId.match(/^uint\d{1,3}$/)) { return `bytes32(uint256(${innerText}))`; - } else if (typeId.match(/^int\d{1,3}$/)) { + } else if (internalTypeId.match(/^int\d{1,3}$/)) { return `bytes32(uint256(uint${bits}(${innerText})))`; - } else if (typeId.match(/^bytes\d{1,2}$/)) { + } else if (internalTypeId.match(/^bytes\d{1,2}$/)) { return `bytes32(${innerText})`; - } else if (typeId === "address") { + } else if (internalTypeId === "address") { return `bytes32(bytes20(${innerText}))`; - } else if (typeId === "bool") { + } else if (internalTypeId === "bool") { return `_boolToBytes32(${innerText})`; } else { - throw new Error(`Unknown value type id ${typeId}`); + throw new Error(`Unknown value type id ${internalTypeId}`); } } diff --git a/packages/cli/src/render-solidity/field.ts b/packages/cli/src/render-solidity/field.ts index f104451b30..4a3753fa50 100644 --- a/packages/cli/src/render-solidity/field.ts +++ b/packages/cli/src/render-solidity/field.ts @@ -1,5 +1,5 @@ import { renderArguments, renderCommonData } from "./common.js"; -import { RenderTableField, RenderTableOptions } from "./types.js"; +import { RenderTableField, RenderTableOptions, RenderTableType } from "./types.js"; export function renderFieldMethods(options: RenderTableOptions) { const { _typedTableId, _typedKeyArgs, _primaryKeysDefinition } = renderCommonData(options); @@ -61,7 +61,7 @@ export function renderFieldMethods(options: RenderTableOptions) { ])}) internal { ${_primaryKeysDefinition} bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, ${index}); - bytes memory _newBlob = abi.encodePacked(_blob, ${portionData.encodeFunc}(${portionData.name})); + bytes memory _newBlob = abi.encodePacked(_blob, ${portionData.encoded}); StoreSwitch.setField(_tableId, _primaryKeys, ${index}, _newBlob); } `; @@ -79,54 +79,63 @@ export function renderEncodeField(field: RenderTableField) { } else { func = "abi.encodePacked"; } - return `${func}(${field.name})`; + return `${func}(${field.typeUnwrap}(${field.name}))`; } -export function renderDecodeValueType(typeId: string, staticByteLength: number, offset: number) { +export function renderDecodeValueType(field: RenderTableType, offset: number) { + const { staticByteLength, internalTypeId } = field; + const innerSlice = `Bytes.slice${staticByteLength}(_blob, ${offset})`; const bits = staticByteLength * 8; - if (typeId.match(/^uint\d{1,3}$/) || typeId === "address") { - return `${typeId}(${innerSlice})`; - } else if (typeId.match(/^int\d{1,3}$/)) { - return `${typeId}(uint${bits}(${innerSlice}))`; - } else if (typeId.match(/^bytes\d{1,2}$/)) { - return innerSlice; - } else if (typeId === "bool") { - return `_toBool(uint8(${innerSlice}))`; + let result; + if (internalTypeId.match(/^uint\d{1,3}$/) || internalTypeId === "address") { + result = `${internalTypeId}(${innerSlice})`; + } else if (internalTypeId.match(/^int\d{1,3}$/)) { + result = `${internalTypeId}(uint${bits}(${innerSlice}))`; + } else if (internalTypeId.match(/^bytes\d{1,2}$/)) { + result = innerSlice; + } else if (internalTypeId === "bool") { + result = `_toBool(uint8(${innerSlice}))`; } else { - throw new Error(`Unknown value type id ${typeId}`); + throw new Error(`Unknown value type id ${internalTypeId}`); } + return `${field.typeWrap}(${result})`; } /** bytes/string are dynamic, but aren't really arrays */ function fieldPortionData(field: RenderTableField) { + const methodNameSuffix = ""; if (field.arrayElement) { + const name = "_element"; return { typeWithLocation: field.arrayElement.typeWithLocation, name: "_element", - encodeFunc: "abi.encodePacked", + encoded: renderEncodeField({ ...field.arrayElement, arrayElement: undefined, name, methodNameSuffix }), title: "an element", }; } else { + const name = "_slice"; return { typeWithLocation: `${field.typeId} memory`, - name: "_slice", - encodeFunc: "bytes", + name, + encoded: renderEncodeField({ ...field, name, methodNameSuffix }), title: "a slice", }; } } function renderDecodeFieldSingle(field: RenderTableField) { - const { typeId, isDynamic, arrayElement } = field; + const { isDynamic, arrayElement } = field; if (arrayElement) { // arrays - return `SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_${arrayElement.typeId}()`; + return `${field.typeWrap}( + SliceLib.getSubslice(_blob, 0, _blob.length).decodeArray_${arrayElement.internalTypeId}() + )`; } else if (isDynamic) { // bytes/string - return `${typeId}(_blob)`; + return `${field.typeWrap}(${field.internalTypeId}(_blob))`; } else { - return renderDecodeValueType(typeId, field.staticByteLength, 0); + return renderDecodeValueType(field, 0); } } diff --git a/packages/cli/src/render-solidity/record.ts b/packages/cli/src/render-solidity/record.ts index 5dc47c8104..0a3e1f9944 100644 --- a/packages/cli/src/render-solidity/record.ts +++ b/packages/cli/src/render-solidity/record.ts @@ -1,6 +1,6 @@ import { renderList, renderArguments, renderCommonData } from "./common.js"; import { renderDecodeValueType, renderEncodeField } from "./field.js"; -import { RenderTableDynamicField, RenderTableOptions, RenderTableStaticField } from "./types.js"; +import { RenderTableDynamicField, RenderTableOptions } from "./types.js"; export function renderRecordMethods(options: RenderTableOptions) { const { staticFields, dynamicFields, structName, storeArgument } = options; @@ -101,7 +101,7 @@ function renderDecodeFunction({ structName, fields, staticFields, dynamicFields ${renderList( staticFields, (field, index) => ` - ${fieldNamePrefix}${field.name} = ${renderDecodeStaticFieldPartial(field, staticOffsets[index])}; + ${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; ` )} uint256 _start; @@ -124,7 +124,7 @@ function renderDecodeFunction({ structName, fields, staticFields, dynamicFields ${renderList( staticFields, (field, index) => ` - ${fieldNamePrefix}${field.name} = ${renderDecodeStaticFieldPartial(field, staticOffsets[index])}; + ${fieldNamePrefix}${field.name} = ${renderDecodeValueType(field, staticOffsets[index])}; ` )} } @@ -152,11 +152,6 @@ function renderDecodeDynamicFieldPartial(field: RenderTableDynamicField) { } } -function renderDecodeStaticFieldPartial(field: RenderTableStaticField, start: number) { - const { typeId, staticByteLength } = field; - return renderDecodeValueType(typeId, staticByteLength, start); -} - function renderEncodedLengths(dynamicFields: RenderTableDynamicField[]) { if (dynamicFields.length > 0) { return ` diff --git a/packages/cli/src/render-solidity/renderTable.ts b/packages/cli/src/render-solidity/renderTable.ts index 81a1474ef5..3e07e57989 100644 --- a/packages/cli/src/render-solidity/renderTable.ts +++ b/packages/cli/src/render-solidity/renderTable.ts @@ -1,17 +1,19 @@ -import { renderArguments, renderCommonData, renderList, renderedSolidityHeader } from "./common.js"; +import { renderArguments, renderCommonData, renderList, renderedSolidityHeader, renderImports } from "./common.js"; import { renderFieldMethods } from "./field.js"; import { renderRecordMethods } from "./record.js"; import { RenderTableOptions } from "./types.js"; export function renderTable(options: RenderTableOptions) { - const { libraryName, structName, staticRouteData, storeImportPath, fields, withRecordMethods } = options; + const { imports, libraryName, structName, staticRouteData, storeImportPath, fields, withRecordMethods } = options; const { _typedTableId, _typedKeyArgs, _primaryKeysDefinition } = renderCommonData(options); return `${renderedSolidityHeader} +// Import schema type import { SchemaType } from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; +// Import store internals import { IStore } from "${storeImportPath}IStore.sol"; import { StoreSwitch } from "${storeImportPath}StoreSwitch.sol"; import { StoreCore } from "${storeImportPath}StoreCore.sol"; @@ -21,12 +23,21 @@ import { EncodeArray } from "${storeImportPath}tightcoder/EncodeArray.sol"; import { Schema, SchemaLib } from "${storeImportPath}Schema.sol"; import { PackedCounter, PackedCounterLib } from "${storeImportPath}PackedCounter.sol"; +${ + imports.length > 0 + ? ` + // Import user types + ${renderImports(imports)} + ` + : "" +} + ${ !staticRouteData ? "" : ` - uint256 constant _tableId = uint256(keccak256("${staticRouteData.baseRoute + staticRouteData.subRoute}")); - uint256 constant ${staticRouteData.tableIdName} = _tableId; + uint256 constant _tableId = uint256(keccak256("${staticRouteData.baseRoute + staticRouteData.subRoute}")); + uint256 constant ${staticRouteData.tableIdName} = _tableId; ` } @@ -34,9 +45,9 @@ ${ !structName ? "" : ` - struct ${structName} { - ${renderList(fields, ({ name, typeId }) => `${typeId} ${name};`)} - } + struct ${structName} { + ${renderList(fields, ({ name, typeId }) => `${typeId} ${name};`)} + } ` } diff --git a/packages/cli/src/render-solidity/renderTablesFromConfig.ts b/packages/cli/src/render-solidity/renderTablesFromConfig.ts index 0690694f95..1c3773120d 100644 --- a/packages/cli/src/render-solidity/renderTablesFromConfig.ts +++ b/packages/cli/src/render-solidity/renderTablesFromConfig.ts @@ -1,15 +1,15 @@ import path from "path"; import { renderTable } from "./renderTable.js"; -import { SchemaType, SchemaTypeArrayToElement, SchemaTypeId, getStaticByteLength } from "@latticexyz/schema-type"; +import { SchemaTypeArrayToElement } from "@latticexyz/schema-type"; import { StoreConfig } from "../config/parseStoreConfig.js"; import { + ImportDatum, RenderTableDynamicField, RenderTableField, RenderTablePrimaryKey, RenderTableStaticField, - RenderTableType, } from "./types.js"; -import { resolveSchemaOrUserType } from "./userType.js"; +import { getSchemaTypeInfo, resolveSchemaOrUserType } from "./userType.js"; export function renderTablesFromConfig(config: StoreConfig, srcDirectory: string) { const storeImportPath = config.storeImportPath; @@ -25,42 +25,45 @@ export function renderTablesFromConfig(config: StoreConfig, srcDirectory: string const withRecordMethods = withStruct || Object.keys(tableData.schema).length > 1; // field methods can be simply get/set if there's only 1 field and no record methods const noFieldMethodSuffix = !withRecordMethods && Object.keys(tableData.schema).length === 1; + // list of any symbols that need to be imported + const imports: ImportDatum[] = []; const primaryKeys = Object.keys(tableData.primaryKeys).map((name) => { const schemaOrUserType = tableData.primaryKeys[name]; - const { schemaType, userTypeDetails } = resolveSchemaOrUserType( + const { renderTableType, importDatum } = resolveSchemaOrUserType( schemaOrUserType, + srcDirectory, outputDirectory, config.userTypes ); + if (importDatum) imports.push(importDatum); - const typeInfo = getSchemaTypeInfo(schemaType); - if (typeInfo.isDynamic) throw new Error("Parsing error: found dynamic primary key"); + if (renderTableType.isDynamic) throw new Error("Parsing error: found dynamic primary key"); const primaryKey: RenderTablePrimaryKey = { - ...typeInfo, + ...renderTableType, name, isDynamic: false, - userTypeDetails, }; return primaryKey; }); const fields = Object.keys(tableData.schema).map((name) => { const schemaOrUserType = tableData.schema[name]; - const { schemaType, userTypeDetails } = resolveSchemaOrUserType( + const { renderTableType, importDatum, schemaType } = resolveSchemaOrUserType( schemaOrUserType, + srcDirectory, outputDirectory, config.userTypes ); + if (importDatum) imports.push(importDatum); const elementType = SchemaTypeArrayToElement[schemaType]; const field: RenderTableField = { - ...getSchemaTypeInfo(schemaType), - arrayElement: elementType ? getSchemaTypeInfo(elementType) : undefined, + ...renderTableType, + arrayElement: elementType !== undefined ? getSchemaTypeInfo(elementType) : undefined, name, methodNameSuffix: noFieldMethodSuffix ? "" : `${name[0].toUpperCase()}${name.slice(1)}`, - userTypeDetails, }; return field; }); @@ -87,6 +90,7 @@ export function renderTablesFromConfig(config: StoreConfig, srcDirectory: string tableName, tableData, output: renderTable({ + imports, libraryName: tableName, structName: withStruct ? tableName + "Data" : undefined, staticRouteData, @@ -102,16 +106,3 @@ export function renderTablesFromConfig(config: StoreConfig, srcDirectory: string } return renderedTables; } - -export function getSchemaTypeInfo(schemaType: SchemaType): RenderTableType { - const staticByteLength = getStaticByteLength(schemaType); - const isDynamic = staticByteLength === 0; - const typeId = SchemaTypeId[schemaType]; - return { - typeId, - typeWithLocation: isDynamic ? typeId + " memory" : typeId, - enumName: SchemaType[schemaType], - staticByteLength, - isDynamic, - }; -} diff --git a/packages/cli/src/render-solidity/types.ts b/packages/cli/src/render-solidity/types.ts index f8b8dbacf6..772790a349 100644 --- a/packages/cli/src/render-solidity/types.ts +++ b/packages/cli/src/render-solidity/types.ts @@ -1,12 +1,13 @@ -import { UserTypeDetails } from "./userType.js"; - export interface RenderTableOptions { + /** List of symbols to import, and their file paths */ + imports: ImportDatum[]; /** Name of the library to render. */ libraryName: string; /** Name of the struct to render. If undefined, struct and its methods aren't rendered. */ structName?: string; /** Data used to statically registed the table. If undefined, all methods receive `_tableId` as an argument. */ staticRouteData?: StaticRouteData; + /** Path for store package imports */ storeImportPath: string; primaryKeys: RenderTablePrimaryKey[]; fields: RenderTableField[]; @@ -18,6 +19,11 @@ export interface RenderTableOptions { storeArgument: boolean; } +export interface ImportDatum { + symbol: string; + path: string; +} + export interface StaticRouteData { /** Name of the table id constant to render. */ tableIdName: string; @@ -28,15 +34,21 @@ export interface StaticRouteData { export interface RenderTableType { typeId: string; typeWithLocation: string; + /** The name of the enum element in SchemaType to use for schema registration (e.g. "UINT256_ARRAY") */ enumName: string; staticByteLength: number; isDynamic: boolean; + /** Empty for internal types. Custom `wrap` method for user defined types. */ + typeWrap: string; + /** Empty for internal types. Custom `unwrap` method for user defined types. */ + typeUnwrap: string; + /** Same as typeId for internal types. The underlying `typeId` for user defined types. */ + internalTypeId: string; } export interface RenderTablePrimaryKey extends RenderTableType { name: string; isDynamic: false; - userTypeDetails?: UserTypeDetails; } export interface RenderTableStaticField extends RenderTableField { @@ -51,7 +63,6 @@ export interface RenderTableField extends RenderTableType { arrayElement: RenderTableType | undefined; name: string; methodNameSuffix: string; - userTypeDetails?: UserTypeDetails; } export interface RenderTypesOptions { diff --git a/packages/cli/src/render-solidity/userType.ts b/packages/cli/src/render-solidity/userType.ts index 7905674b8d..4de87ab547 100644 --- a/packages/cli/src/render-solidity/userType.ts +++ b/packages/cli/src/render-solidity/userType.ts @@ -1,55 +1,83 @@ -import { SchemaType } from "@latticexyz/schema-type"; +import { getStaticByteLength, SchemaType, SchemaTypeId } from "@latticexyz/schema-type"; import path from "path"; import { StoreConfig } from "../index.js"; +import { RenderTableType } from "./types.js"; -export type UserTypeDetails = ReturnType; +export type UserTypeInfo = ReturnType; /** - * Resolve a SchemaType|userType into a general type info object + * Resolve a SchemaType|userType into RenderTableType, required import, and internal SchemaType */ export function resolveSchemaOrUserType( - type: SchemaType | string, + schemaOrUserType: SchemaType | string, + srcDirectory: string, usedInDirectory: string, userTypesConfig: StoreConfig["userTypes"] ) { - let schemaType; - let userTypeDetails; - if (typeof type === "string") { - userTypeDetails = getUserTypeDetails(type, usedInDirectory, userTypesConfig); - schemaType = userTypeDetails.schemaType; + if (typeof schemaOrUserType === "string") { + // Relative import path for this type. + // "./" must be added because path stripts it, + // but solidity expects it unless there's "../" ("./../" is fine) + const importedFromPath = path.join(srcDirectory, userTypesConfig.path); + const importDatum = { + symbol: schemaOrUserType, + path: "./" + path.relative(usedInDirectory, importedFromPath) + ".sol", + }; + const { schemaType, renderTableType } = getUserTypeInfo(schemaOrUserType, userTypesConfig); + return { + importDatum, + renderTableType, + schemaType, + }; } else { - schemaType = type; + return { + importDatum: undefined, + renderTableType: getSchemaTypeInfo(schemaOrUserType), + schemaType: schemaOrUserType, + }; } +} +export function getSchemaTypeInfo(schemaType: SchemaType): RenderTableType { + const staticByteLength = getStaticByteLength(schemaType); + const isDynamic = staticByteLength === 0; + const typeId = SchemaTypeId[schemaType]; return { - schemaType, - userTypeDetails, + typeId, + typeWithLocation: isDynamic ? typeId + " memory" : typeId, + enumName: SchemaType[schemaType], + staticByteLength, + isDynamic, + typeWrap: "", + typeUnwrap: "", + internalTypeId: typeId, }; } -/** - * Get all the necessary rendering details for the user type - */ -export function getUserTypeDetails( +export function getUserTypeInfo( userType: string, - usedInDirectory: string, userTypesConfig: StoreConfig["userTypes"] -) { - // Relative import path for this type. - // "./" must be added because path stripts it, - // but solidity expects it unless there's "../" ("./../" is fine) - const importPath = "./" + path.relative(usedInDirectory, userTypesConfig.path); - +): { + schemaType: SchemaType; + renderTableType: RenderTableType; +} { if (userType in userTypesConfig.enums) { - const renderCastFrom = (arg: string) => `${userType}.unwrap(${arg})`; - const renderCastTo = (arg: string) => `${userType}.wrap(${arg})`; - + const schemaType = SchemaType.UINT8; + const staticByteLength = getStaticByteLength(schemaType); + const isDynamic = staticByteLength === 0; + const typeId = userType; return { - importPath, - userType, - schemaType: SchemaType.UINT8, - renderCastFrom, - renderCastTo, + schemaType, + renderTableType: { + typeId, + typeWithLocation: typeId, + enumName: SchemaType[schemaType], + staticByteLength, + isDynamic, + typeWrap: `${userType}`, + typeUnwrap: `${userType}`, + internalTypeId: `${SchemaTypeId[schemaType]}`, + }, }; } else { throw new Error(`User type "${userType}" does not exist`); From cef5a38fa05667bd3e55417ce4224804ca3526cb Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 20:57:42 +0300 Subject: [PATCH 10/22] refactor(cli): wrap tablegen logic into a standalone function --- packages/cli/src/commands/tablegen.ts | 33 ++---------------- packages/cli/src/render-solidity/tablegen.ts | 35 ++++++++++++++++++++ 2 files changed, 37 insertions(+), 31 deletions(-) create mode 100644 packages/cli/src/render-solidity/tablegen.ts diff --git a/packages/cli/src/commands/tablegen.ts b/packages/cli/src/commands/tablegen.ts index 93910fd75b..664d4f473b 100644 --- a/packages/cli/src/commands/tablegen.ts +++ b/packages/cli/src/commands/tablegen.ts @@ -1,11 +1,7 @@ import type { CommandModule } from "yargs"; -import { mkdirSync, writeFileSync } from "fs"; -import path from "path"; import { loadStoreConfig } from "../config/loadStoreConfig.js"; -import { renderTablesFromConfig } from "../render-solidity/renderTablesFromConfig.js"; import { getSrcDirectory } from "../utils/foundry.js"; -import { formatSolidity } from "../utils/format.js"; -import { renderTypesFromConfig } from "../render-solidity/renderTypesFromConfig.js"; +import { tablegen } from "../render-solidity/tablegen.js"; type Options = { configPath?: string; @@ -27,32 +23,7 @@ const commandModule: CommandModule = { const config = await loadStoreConfig(configPath); - // render tables - const renderedTables = renderTablesFromConfig(config, srcDirectory); - // write tables to files - for (const { outputDirectory, output, tableName } of renderedTables) { - const formattedOutput = await formatSolidity(output); - - mkdirSync(outputDirectory, { recursive: true }); - - const outputPath = path.join(outputDirectory, `${tableName}.sol`); - writeFileSync(outputPath, formattedOutput); - console.log(`Generated table: ${outputPath}`); - } - - // render types - if (Object.keys(config.userTypes.enums).length > 0) { - const renderedTypes = renderTypesFromConfig(config); - // write types to file - const formattedOutput = await formatSolidity(renderedTypes); - - const outputPath = path.join(srcDirectory, `${config.userTypes.path}.sol`); - const outputDirectory = path.dirname(outputPath); - mkdirSync(outputDirectory, { recursive: true }); - - writeFileSync(outputPath, formattedOutput); - console.log(`Generated types file: ${outputPath}`); - } + tablegen(config, srcDirectory); process.exit(0); }, diff --git a/packages/cli/src/render-solidity/tablegen.ts b/packages/cli/src/render-solidity/tablegen.ts new file mode 100644 index 0000000000..b4567431ad --- /dev/null +++ b/packages/cli/src/render-solidity/tablegen.ts @@ -0,0 +1,35 @@ +import { mkdirSync, writeFileSync } from "fs"; +import path from "path"; +import { StoreConfig } from "../index.js"; +import { formatSolidity } from "../utils/format.js"; +import { renderTablesFromConfig } from "./renderTablesFromConfig.js"; +import { renderTypesFromConfig } from "./renderTypesFromConfig.js"; + +export async function tablegen(config: StoreConfig, outputBaseDirectory: string) { + // render tables + const renderedTables = renderTablesFromConfig(config, outputBaseDirectory); + // write tables to files + for (const { outputDirectory, output, tableName } of renderedTables) { + const formattedOutput = await formatSolidity(output); + + mkdirSync(outputDirectory, { recursive: true }); + + const outputPath = path.join(outputDirectory, `${tableName}.sol`); + writeFileSync(outputPath, formattedOutput); + console.log(`Generated table: ${outputPath}`); + } + + // render types + if (Object.keys(config.userTypes.enums).length > 0) { + const renderedTypes = renderTypesFromConfig(config); + // write types to file + const formattedOutput = await formatSolidity(renderedTypes); + + const outputPath = path.join(outputBaseDirectory, `${config.userTypes.path}.sol`); + const outputDirectory = path.dirname(outputPath); + mkdirSync(outputDirectory, { recursive: true }); + + writeFileSync(outputPath, formattedOutput); + console.log(`Generated types file: ${outputPath}`); + } +} From 803f68b0460ce5c3e0ed67cd5f40e5e3fbcbf8e4 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 21:44:06 +0300 Subject: [PATCH 11/22] fix(cli): crosscheck local table variable name uniqueness --- packages/cli/src/config/parseStoreConfig.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/config/parseStoreConfig.ts b/packages/cli/src/config/parseStoreConfig.ts index 22f6a030ba..64392a71bc 100644 --- a/packages/cli/src/config/parseStoreConfig.ts +++ b/packages/cli/src/config/parseStoreConfig.ts @@ -124,7 +124,19 @@ export async function parseStoreConfig(config: unknown) { // Validate conditions that check multiple different config options simultaneously function validateStoreConfig(config: z.output, ctx: RefinementCtx) { - // global names must be unique + // Local table variables must be unique within the table + for (const table of Object.values(config.tables)) { + const primaryKeyNames = Object.keys(table.primaryKeys); + const fieldNames = Object.keys(table.schema); + const duplicateVariableNames = getDuplicates([...primaryKeyNames, ...fieldNames]); + if (duplicateVariableNames.length > 0) { + ctx.addIssue({ + code: ZodIssueCode.custom, + message: `Field and primary key names within one table must be unique: ${duplicateVariableNames.join(", ")}`, + }); + } + } + // Global names must be unique const tableNames = Object.keys(config.tables); const userTypeNames = Object.keys(config.userTypes.enums); const globalNames = [...tableNames]; @@ -135,7 +147,7 @@ function validateStoreConfig(config: z.output, ctx: message: `Table and enum names must be globally unique: ${duplicateGlobalNames.join(", ")}`, }); } - // user types must exist + // User types must exist for (const table of Object.values(config.tables)) { for (const primaryKeyType of Object.values(table.primaryKeys)) { validateIfUserType(userTypeNames, primaryKeyType, ctx); From 4329f29ec95461d33c082f4bed42a0eca7f26e1b Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 1 Mar 2023 23:09:40 +0300 Subject: [PATCH 12/22] test(cli): add tablegen tests --- packages/cli/.gitignore | 1 + packages/cli/foundry.toml | 7 +- packages/cli/package.json | 5 +- packages/cli/remappings.txt | 4 +- packages/cli/scripts/codegen.ts | 56 ++ packages/cli/src/utils/errors.ts | 2 +- .../cli/test-contracts-src/tables/Table1.sol | 612 ++++++++++++++++++ packages/cli/test-contracts-src/types.sol | 14 + .../cli/test-contracts-test/tablegen.t.sol | 29 + 9 files changed, 724 insertions(+), 6 deletions(-) create mode 100644 packages/cli/scripts/codegen.ts create mode 100644 packages/cli/test-contracts-src/tables/Table1.sol create mode 100644 packages/cli/test-contracts-src/types.sol create mode 100644 packages/cli/test-contracts-test/tablegen.t.sol diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index ddd64d2c51..78bace2d50 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -1,4 +1,5 @@ dist out +test-contracts-out broadcast cache \ No newline at end of file diff --git a/packages/cli/foundry.toml b/packages/cli/foundry.toml index 24985ad1d0..34af20a9cd 100644 --- a/packages/cli/foundry.toml +++ b/packages/cli/foundry.toml @@ -5,6 +5,7 @@ fuzz_runs = 256 optimizer = true optimizer_runs = 3000 verbosity = 1 -libs = ["../../node_modules", "../solecs", "../std-contracts"] -src = "src" -out = "out" +libs = ["../../node_modules", "../solecs", "../std-contracts", "../store"] +src = "test-contracts-src" +out = "test-contracts-out" +test = "test-contracts-test" diff --git a/packages/cli/package.json b/packages/cli/package.json index b4fcb1af5e..53009c305a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -16,15 +16,18 @@ }, "scripts": { "prepare": "yarn build && chmod u+x git-install.sh", + "codegen": "ts-node --esm --files ./scripts/codegen.ts", "lint": "eslint . --ext .ts", "dev": "tsup --watch", "build": "tsup", "link": "yarn link", - "test": "vitest typecheck --run && echo 'todo: add tests'", + "test": "vitest typecheck --run && yarn test:contracts", + "test:contracts": "yarn codegen && forge test", "git:install": "bash git-install.sh", "release": "npm publish --access=public" }, "devDependencies": { + "@latticexyz/store": "^1.34.0", "@types/ejs": "^3.1.1", "@types/glob": "^7.2.0", "@types/node": "^17.0.34", diff --git a/packages/cli/remappings.txt b/packages/cli/remappings.txt index 8a59ec84ae..2577208aab 100644 --- a/packages/cli/remappings.txt +++ b/packages/cli/remappings.txt @@ -2,4 +2,6 @@ ds-test/=../../node_modules/ds-test/src/ forge-std/=../../node_modules/forge-std/src/ solmate/=../../node_modules/@rari-capital/solmate/src std-contracts/=../../node_modules/@latticexyz/std-contracts/src/ -solecs/=../../node_modules/@latticexyz/solecs/src/ \ No newline at end of file +solecs/=../../node_modules/@latticexyz/solecs/src/ +@solidstate/=../../node_modules/@solidstate/ +@latticexyz/=../../node_modules/@latticexyz/ \ No newline at end of file diff --git a/packages/cli/scripts/codegen.ts b/packages/cli/scripts/codegen.ts new file mode 100644 index 0000000000..98b83c8cbf --- /dev/null +++ b/packages/cli/scripts/codegen.ts @@ -0,0 +1,56 @@ +import { SchemaType } from "@latticexyz/schema-type"; +import path from "path"; +import { parseStoreConfig, StoreUserConfig } from "../src/config/index.js"; +import { tablegen } from "../src/render-solidity/tablegen.js"; +import { logError } from "../src/utils/errors.js"; +import { getSrcDirectory } from "../src/utils/forgeConfig.js"; + +// This config is used only for tests +const config: StoreUserConfig = { + tables: { + Table1: { + primaryKeys: { + k1: SchemaType.UINT256, + k2: SchemaType.INT32, + k3: SchemaType.BYTES16, + k4: SchemaType.ADDRESS, + k5: SchemaType.BOOL, + k6: "Enum1", + k7: "Enum2", + }, + schema: { + v1: SchemaType.UINT256, + v2: SchemaType.INT32, + v3: SchemaType.BYTES16, + v4: SchemaType.ADDRESS, + v5: SchemaType.BOOL, + v6: "Enum1", + v7: "Enum2", + }, + }, + }, + + userTypes: { + enums: { + Enum1: ["E1", "E2", "E3"], + Enum2: ["E1"], + }, + }, +}; + +// Aside from avoiding `mud.config.mts` in cli package (could cause issues), +// this also tests that tablegen can work as a standalone function +const parsedConfig = await (async () => { + try { + return await parseStoreConfig(config); + } catch (error: unknown) { + logError(error); + } +})(); + +const srcDirectory = await getSrcDirectory(); +if (parsedConfig !== undefined) { + tablegen(parsedConfig, srcDirectory); +} else { + process.exit(1); +} diff --git a/packages/cli/src/utils/errors.ts b/packages/cli/src/utils/errors.ts index 90aa5fc5a2..923601a314 100644 --- a/packages/cli/src/utils/errors.ts +++ b/packages/cli/src/utils/errors.ts @@ -29,7 +29,7 @@ export function UnrecognizedSystemErrorFactory(path: string[], systemName: strin return new z.ZodError([{ code: ZodIssueCode.custom, path: path, message: `Unrecognized system: "${systemName}"` }]); } -export function logError(error: Error) { +export function logError(error: unknown) { if (error instanceof ValidationError) { console.log(chalk.redBright(error.message)); } else if (error instanceof ZodError) { diff --git a/packages/cli/test-contracts-src/tables/Table1.sol b/packages/cli/test-contracts-src/tables/Table1.sol new file mode 100644 index 0000000000..dbc8e9fff9 --- /dev/null +++ b/packages/cli/test-contracts-src/tables/Table1.sol @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/* Autogenerated file. Do not edit manually. */ + +// Import store internals +import {IStore} from "@latticexyz/store/src/IStore.sol"; +import {StoreSwitch} from "@latticexyz/store/src/StoreSwitch.sol"; +import {StoreCore} from "@latticexyz/store/src/StoreCore.sol"; +import {SchemaType} from "@latticexyz/store/src/Types.sol"; +import {Bytes} from "@latticexyz/store/src/Bytes.sol"; +import {SliceLib} from "@latticexyz/store/src/Slice.sol"; +import {EncodeArray} from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; +import {Schema, SchemaLib} from "@latticexyz/store/src/Schema.sol"; +import {PackedCounter, PackedCounterLib} from "@latticexyz/store/src/PackedCounter.sol"; + +// Import user types +import {Enum1, Enum2} from "./../types.sol"; + +uint256 constant _tableId = uint256(keccak256("/Table1")); +uint256 constant Table1TableId = _tableId; + +struct Table1Data { + uint256 v1; + int32 v2; + bytes16 v3; + address v4; + bool v5; + Enum1 v6; + Enum2 v7; +} + +library Table1 { + /** Get the table's schema */ + function getSchema() internal pure returns (Schema) { + SchemaType[] memory _schema = new SchemaType[](7); + _schema[0] = SchemaType.UINT256; + _schema[1] = SchemaType.INT32; + _schema[2] = SchemaType.BYTES16; + _schema[3] = SchemaType.ADDRESS; + _schema[4] = SchemaType.BOOL; + _schema[5] = SchemaType.UINT8; + _schema[6] = SchemaType.UINT8; + + return SchemaLib.encode(_schema); + } + + /** Register the table's schema */ + function registerSchema() internal { + StoreSwitch.registerSchema(_tableId, getSchema()); + } + + /** Get v1 */ + function getV1( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal view returns (uint256 v1) { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 0); + return (uint256(Bytes.slice32(_blob, 0))); + } + + /** Set v1 */ + function setV1( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + uint256 v1 + ) internal { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.setField(_tableId, _primaryKeys, 0, abi.encodePacked((v1))); + } + + /** Get v2 */ + function getV2( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal view returns (int32 v2) { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 1); + return (int32(uint32(Bytes.slice4(_blob, 0)))); + } + + /** Set v2 */ + function setV2( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + int32 v2 + ) internal { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.setField(_tableId, _primaryKeys, 1, abi.encodePacked((v2))); + } + + /** Get v3 */ + function getV3( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal view returns (bytes16 v3) { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 2); + return (Bytes.slice16(_blob, 0)); + } + + /** Set v3 */ + function setV3( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + bytes16 v3 + ) internal { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.setField(_tableId, _primaryKeys, 2, abi.encodePacked((v3))); + } + + /** Get v4 */ + function getV4( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal view returns (address v4) { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 3); + return (address(Bytes.slice20(_blob, 0))); + } + + /** Set v4 */ + function setV4( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + address v4 + ) internal { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.setField(_tableId, _primaryKeys, 3, abi.encodePacked((v4))); + } + + /** Get v5 */ + function getV5( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal view returns (bool v5) { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 4); + return (_toBool(uint8(Bytes.slice1(_blob, 0)))); + } + + /** Set v5 */ + function setV5( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + bool v5 + ) internal { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.setField(_tableId, _primaryKeys, 4, abi.encodePacked((v5))); + } + + /** Get v6 */ + function getV6( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal view returns (Enum1 v6) { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 5); + return Enum1(uint8(Bytes.slice1(_blob, 0))); + } + + /** Set v6 */ + function setV6( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + Enum1 v6 + ) internal { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.setField(_tableId, _primaryKeys, 5, abi.encodePacked(Enum1(v6))); + } + + /** Get v7 */ + function getV7( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal view returns (Enum2 v7) { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 6); + return Enum2(uint8(Bytes.slice1(_blob, 0))); + } + + /** Set v7 */ + function setV7( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + Enum2 v7 + ) internal { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.setField(_tableId, _primaryKeys, 6, abi.encodePacked(Enum2(v7))); + } + + /** Get the full data */ + function get( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal view returns (Table1Data memory _table) { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + bytes memory _blob = StoreSwitch.getRecord(_tableId, _primaryKeys, getSchema()); + return decode(_blob); + } + + /** Set the full data using individual values */ + function set( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + uint256 v1, + int32 v2, + bytes16 v3, + address v4, + bool v5, + Enum1 v6, + Enum2 v7 + ) internal { + bytes memory _data = abi.encodePacked(v1, v2, v3, v4, v5, v6, v7); + + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.setRecord(_tableId, _primaryKeys, _data); + } + + /** Set the full data using the data struct */ + function set( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + Table1Data memory _table + ) internal { + set(k1, k2, k3, k4, k5, k6, k7, _table.v1, _table.v2, _table.v3, _table.v4, _table.v5, _table.v6, _table.v7); + } + + /** Decode the tightly packed blob using this table's schema */ + function decode(bytes memory _blob) internal pure returns (Table1Data memory _table) { + _table.v1 = (uint256(Bytes.slice32(_blob, 0))); + + _table.v2 = (int32(uint32(Bytes.slice4(_blob, 32)))); + + _table.v3 = (Bytes.slice16(_blob, 36)); + + _table.v4 = (address(Bytes.slice20(_blob, 52))); + + _table.v5 = (_toBool(uint8(Bytes.slice1(_blob, 72)))); + + _table.v6 = Enum1(uint8(Bytes.slice1(_blob, 73))); + + _table.v7 = Enum2(uint8(Bytes.slice1(_blob, 74))); + } + + /* Delete all data for given keys */ + function deleteRecord( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal { + bytes32[] memory _primaryKeys = new bytes32[](7); + + _primaryKeys[0] = bytes32(uint256((k1))); + + _primaryKeys[1] = bytes32(uint256(uint32((k2)))); + + _primaryKeys[2] = bytes32((k3)); + + _primaryKeys[3] = bytes32(bytes20((k4))); + + _primaryKeys[4] = _boolToBytes32((k5)); + + _primaryKeys[5] = bytes32(uint256(Enum1(k6))); + + _primaryKeys[6] = bytes32(uint256(Enum2(k7))); + + StoreSwitch.deleteRecord(_tableId, _primaryKeys); + } +} + +function _toBool(uint8 value) pure returns (bool result) { + assembly { + result := value + } +} + +function _boolToBytes32(bool value) pure returns (bytes32 result) { + assembly { + result := value + } +} diff --git a/packages/cli/test-contracts-src/types.sol b/packages/cli/test-contracts-src/types.sol new file mode 100644 index 0000000000..dcde223cd8 --- /dev/null +++ b/packages/cli/test-contracts-src/types.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +/* Autogenerated file. Do not edit manually. */ + +enum Enum1 { + E1, + E2, + E3 +} + +enum Enum2 { + E1 +} diff --git a/packages/cli/test-contracts-test/tablegen.t.sol b/packages/cli/test-contracts-test/tablegen.t.sol new file mode 100644 index 0000000000..7dbca2bd9a --- /dev/null +++ b/packages/cli/test-contracts-test/tablegen.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import "forge-std/Test.sol"; +import {Table1, Table1Data} from "../test-contracts-src/tables/Table1.sol"; +import {Enum1, Enum2} from "../test-contracts-src/types.sol"; + +contract tablegenTest is Test { + function isStore() external view {} + + function testTable1SetAndGet() public { + Table1.registerSchema(); + + uint256 k1 = 1; + int32 k2 = -1; + bytes16 k3 = hex"02"; + address k4 = address(123); + bool k5 = true; + Enum1 k6 = Enum1.E3; + Enum2 k7 = Enum2.E1; + + Table1.setV1(k1, k2, k3, k4, k5, k6, k7, 4); + assertEq(Table1.getV1(k1, k2, k3, k4, k5, k6, k7), 4); + + Table1Data memory data = Table1Data(4, -5, hex"06", address(456), false, Enum1.E2, Enum2.E1); + Table1.set(k1, k2, k3, k4, k5, k6, k7, data); + assertEq(abi.encode(Table1.get(k1, k2, k3, k4, k5, k6, k7)), abi.encode(data)); + } +} From a83cc3021f4f91d81203b6d589cddce16464cf7e Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 19:45:58 +0300 Subject: [PATCH 13/22] fix(cli): add userTypeNames to globalNames Co-authored-by: alvarius <89248902+alvrs@users.noreply.github.com> --- packages/cli/src/config/parseStoreConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/config/parseStoreConfig.ts b/packages/cli/src/config/parseStoreConfig.ts index 64392a71bc..e915f7f45c 100644 --- a/packages/cli/src/config/parseStoreConfig.ts +++ b/packages/cli/src/config/parseStoreConfig.ts @@ -139,7 +139,7 @@ function validateStoreConfig(config: z.output, ctx: // Global names must be unique const tableNames = Object.keys(config.tables); const userTypeNames = Object.keys(config.userTypes.enums); - const globalNames = [...tableNames]; + const globalNames = [...tableNames, ...userTypeNames]; const duplicateGlobalNames = getDuplicates(globalNames); if (duplicateGlobalNames.length > 0) { ctx.addIssue({ From dd65bd264f47689f52711a3d0c00f3dd75cc2946 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 19:51:30 +0300 Subject: [PATCH 14/22] refactor(cli): reorganize contracts folders --- packages/cli/.gitignore | 1 - .../src}/tables/Table1.sol | 91 +++---------------- .../src}/types.sol | 0 .../test}/tablegen.t.sol | 4 +- packages/cli/foundry.toml | 6 +- 5 files changed, 16 insertions(+), 86 deletions(-) rename packages/cli/{test-contracts-src => contracts/src}/tables/Table1.sol (91%) rename packages/cli/{test-contracts-src => contracts/src}/types.sol (100%) rename packages/cli/{test-contracts-test => contracts/test}/tablegen.t.sol (84%) diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index 78bace2d50..ddd64d2c51 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -1,5 +1,4 @@ dist out -test-contracts-out broadcast cache \ No newline at end of file diff --git a/packages/cli/test-contracts-src/tables/Table1.sol b/packages/cli/contracts/src/tables/Table1.sol similarity index 91% rename from packages/cli/test-contracts-src/tables/Table1.sol rename to packages/cli/contracts/src/tables/Table1.sol index dbc8e9fff9..e3264e9bc7 100644 --- a/packages/cli/test-contracts-src/tables/Table1.sol +++ b/packages/cli/contracts/src/tables/Table1.sol @@ -3,11 +3,13 @@ pragma solidity >=0.8.0; /* Autogenerated file. Do not edit manually. */ +// Import schema type +import {SchemaType} from "@latticexyz/schema-type/src/solidity/SchemaType.sol"; + // Import store internals import {IStore} from "@latticexyz/store/src/IStore.sol"; import {StoreSwitch} from "@latticexyz/store/src/StoreSwitch.sol"; import {StoreCore} from "@latticexyz/store/src/StoreCore.sol"; -import {SchemaType} from "@latticexyz/store/src/Types.sol"; import {Bytes} from "@latticexyz/store/src/Bytes.sol"; import {SliceLib} from "@latticexyz/store/src/Slice.sol"; import {EncodeArray} from "@latticexyz/store/src/tightcoder/EncodeArray.sol"; @@ -81,16 +83,7 @@ library Table1 { } /** Set v1 */ - function setV1( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - uint256 v1 - ) internal { + function setV1(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, uint256 v1) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -141,16 +134,7 @@ library Table1 { } /** Set v2 */ - function setV2( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - int32 v2 - ) internal { + function setV2(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, int32 v2) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -201,16 +185,7 @@ library Table1 { } /** Set v3 */ - function setV3( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - bytes16 v3 - ) internal { + function setV3(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, bytes16 v3) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -261,16 +236,7 @@ library Table1 { } /** Set v4 */ - function setV4( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - address v4 - ) internal { + function setV4(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, address v4) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -321,16 +287,7 @@ library Table1 { } /** Set v5 */ - function setV5( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - bool v5 - ) internal { + function setV5(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, bool v5) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -381,16 +338,7 @@ library Table1 { } /** Set v6 */ - function setV6( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - Enum1 v6 - ) internal { + function setV6(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, Enum1 v6) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -441,16 +389,7 @@ library Table1 { } /** Set v7 */ - function setV7( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - Enum2 v7 - ) internal { + function setV7(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, Enum2 v7) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -570,15 +509,7 @@ library Table1 { } /* Delete all data for given keys */ - function deleteRecord( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal { + function deleteRecord(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); diff --git a/packages/cli/test-contracts-src/types.sol b/packages/cli/contracts/src/types.sol similarity index 100% rename from packages/cli/test-contracts-src/types.sol rename to packages/cli/contracts/src/types.sol diff --git a/packages/cli/test-contracts-test/tablegen.t.sol b/packages/cli/contracts/test/tablegen.t.sol similarity index 84% rename from packages/cli/test-contracts-test/tablegen.t.sol rename to packages/cli/contracts/test/tablegen.t.sol index 7dbca2bd9a..6862778a4e 100644 --- a/packages/cli/test-contracts-test/tablegen.t.sol +++ b/packages/cli/contracts/test/tablegen.t.sol @@ -2,8 +2,8 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; -import {Table1, Table1Data} from "../test-contracts-src/tables/Table1.sol"; -import {Enum1, Enum2} from "../test-contracts-src/types.sol"; +import {Table1, Table1Data} from "../src/tables/Table1.sol"; +import {Enum1, Enum2} from "../src/types.sol"; contract tablegenTest is Test { function isStore() external view {} diff --git a/packages/cli/foundry.toml b/packages/cli/foundry.toml index 34af20a9cd..051e95dc8a 100644 --- a/packages/cli/foundry.toml +++ b/packages/cli/foundry.toml @@ -6,6 +6,6 @@ optimizer = true optimizer_runs = 3000 verbosity = 1 libs = ["../../node_modules", "../solecs", "../std-contracts", "../store"] -src = "test-contracts-src" -out = "test-contracts-out" -test = "test-contracts-test" +src = "contracts/src" +out = "contracts/out" +test = "contracts/test" From 57f85e07f5fd5a2d38faab57e4425accb97d7d7a Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 19:56:34 +0300 Subject: [PATCH 15/22] fix(cli): fix StaticSchemaType error message --- packages/cli/src/config/commonSchemas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/config/commonSchemas.ts b/packages/cli/src/config/commonSchemas.ts index 3957e16dd6..6add143def 100644 --- a/packages/cli/src/config/commonSchemas.ts +++ b/packages/cli/src/config/commonSchemas.ts @@ -37,4 +37,4 @@ export const EthereumAddress = z.string().superRefine(validateEthereumAddress); /** Static subset of SchemaType enum */ export const StaticSchemaType = z .nativeEnum(SchemaType) - .refine((arg) => getStaticByteLength(arg) > 0, "Primary key must not use dynamic SchemaType"); + .refine((arg) => getStaticByteLength(arg) > 0, "SchemaType must be static"); From 7e5504fbc7965e393345c99a4fe23bf3cad98d02 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 19:58:30 +0300 Subject: [PATCH 16/22] chore(cli): rename RequireKeys type --- packages/cli/src/config/parseStoreConfig.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/config/parseStoreConfig.ts b/packages/cli/src/config/parseStoreConfig.ts index e915f7f45c..2b30d77381 100644 --- a/packages/cli/src/config/parseStoreConfig.ts +++ b/packages/cli/src/config/parseStoreConfig.ts @@ -36,7 +36,7 @@ const TableDataFull = z } else { arg.dataStruct ??= true; } - return arg as RequiredKeys; + return arg as RequireKeys; }); const TableDataShorthand = FieldData.transform((fieldData) => { @@ -55,7 +55,7 @@ const TablesRecord = z.record(TableName, z.union([TableDataShorthand, TableDataF tables[tableName] = table; } - return tables as Record>; + return tables as Record>; }); const StoreConfigUnrefined = z.object({ @@ -171,4 +171,4 @@ function validateIfUserType( } } -type RequiredKeys, P extends string> = T & Required>; \ No newline at end of file +type RequireKeys, P extends string> = T & Required>; \ No newline at end of file From fad2c00398504844bd6e151b3e78db80c80aedfc Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 20:00:28 +0300 Subject: [PATCH 17/22] chore(cli): better error description for dynamic primary key in renderer --- packages/cli/src/render-solidity/renderTablesFromConfig.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/render-solidity/renderTablesFromConfig.ts b/packages/cli/src/render-solidity/renderTablesFromConfig.ts index 1c3773120d..153ccdc0c6 100644 --- a/packages/cli/src/render-solidity/renderTablesFromConfig.ts +++ b/packages/cli/src/render-solidity/renderTablesFromConfig.ts @@ -38,7 +38,8 @@ export function renderTablesFromConfig(config: StoreConfig, srcDirectory: string ); if (importDatum) imports.push(importDatum); - if (renderTableType.isDynamic) throw new Error("Parsing error: found dynamic primary key"); + if (renderTableType.isDynamic) + throw new Error(`Parsing error: found dynamic primary key ${name} in table ${tableName}`); const primaryKey: RenderTablePrimaryKey = { ...renderTableType, From d80c4a5f8a46a29c3fa3f5b7c4da9cbcdb14949a Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 20:01:11 +0300 Subject: [PATCH 18/22] chore(cli): reformat Table1 --- packages/cli/contracts/src/tables/Table1.sol | 87 ++++++++++++++++++-- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/packages/cli/contracts/src/tables/Table1.sol b/packages/cli/contracts/src/tables/Table1.sol index e3264e9bc7..597a182456 100644 --- a/packages/cli/contracts/src/tables/Table1.sol +++ b/packages/cli/contracts/src/tables/Table1.sol @@ -83,7 +83,16 @@ library Table1 { } /** Set v1 */ - function setV1(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, uint256 v1) internal { + function setV1( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + uint256 v1 + ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -134,7 +143,16 @@ library Table1 { } /** Set v2 */ - function setV2(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, int32 v2) internal { + function setV2( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + int32 v2 + ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -185,7 +203,16 @@ library Table1 { } /** Set v3 */ - function setV3(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, bytes16 v3) internal { + function setV3( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + bytes16 v3 + ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -236,7 +263,16 @@ library Table1 { } /** Set v4 */ - function setV4(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, address v4) internal { + function setV4( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + address v4 + ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -287,7 +323,16 @@ library Table1 { } /** Set v5 */ - function setV5(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, bool v5) internal { + function setV5( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + bool v5 + ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -338,7 +383,16 @@ library Table1 { } /** Set v6 */ - function setV6(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, Enum1 v6) internal { + function setV6( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + Enum1 v6 + ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -389,7 +443,16 @@ library Table1 { } /** Set v7 */ - function setV7(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, Enum2 v7) internal { + function setV7( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7, + Enum2 v7 + ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); @@ -509,7 +572,15 @@ library Table1 { } /* Delete all data for given keys */ - function deleteRecord(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7) internal { + function deleteRecord( + uint256 k1, + int32 k2, + bytes16 k3, + address k4, + bool k5, + Enum1 k6, + Enum2 k7 + ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); From 2720ea38719ed1a9084e0c9dbe9b63e64bbc01ea Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 20:09:38 +0300 Subject: [PATCH 19/22] refactor(cli): render _primaryKeys more compactly --- packages/cli/contracts/src/tables/Table1.sol | 119 ------------------- packages/cli/src/render-solidity/common.ts | 4 +- 2 files changed, 1 insertion(+), 122 deletions(-) diff --git a/packages/cli/contracts/src/tables/Table1.sol b/packages/cli/contracts/src/tables/Table1.sol index 597a182456..aa0928e305 100644 --- a/packages/cli/contracts/src/tables/Table1.sol +++ b/packages/cli/contracts/src/tables/Table1.sol @@ -63,19 +63,12 @@ library Table1 { Enum2 k7 ) internal view returns (uint256 v1) { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 0); @@ -94,19 +87,12 @@ library Table1 { uint256 v1 ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.setField(_tableId, _primaryKeys, 0, abi.encodePacked((v1))); @@ -123,19 +109,12 @@ library Table1 { Enum2 k7 ) internal view returns (int32 v2) { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 1); @@ -154,19 +133,12 @@ library Table1 { int32 v2 ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.setField(_tableId, _primaryKeys, 1, abi.encodePacked((v2))); @@ -183,19 +155,12 @@ library Table1 { Enum2 k7 ) internal view returns (bytes16 v3) { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 2); @@ -214,19 +179,12 @@ library Table1 { bytes16 v3 ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.setField(_tableId, _primaryKeys, 2, abi.encodePacked((v3))); @@ -243,19 +201,12 @@ library Table1 { Enum2 k7 ) internal view returns (address v4) { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 3); @@ -274,19 +225,12 @@ library Table1 { address v4 ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.setField(_tableId, _primaryKeys, 3, abi.encodePacked((v4))); @@ -303,19 +247,12 @@ library Table1 { Enum2 k7 ) internal view returns (bool v5) { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 4); @@ -334,19 +271,12 @@ library Table1 { bool v5 ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.setField(_tableId, _primaryKeys, 4, abi.encodePacked((v5))); @@ -363,19 +293,12 @@ library Table1 { Enum2 k7 ) internal view returns (Enum1 v6) { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 5); @@ -394,19 +317,12 @@ library Table1 { Enum1 v6 ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.setField(_tableId, _primaryKeys, 5, abi.encodePacked(Enum1(v6))); @@ -423,19 +339,12 @@ library Table1 { Enum2 k7 ) internal view returns (Enum2 v7) { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); bytes memory _blob = StoreSwitch.getField(_tableId, _primaryKeys, 6); @@ -454,19 +363,12 @@ library Table1 { Enum2 v7 ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.setField(_tableId, _primaryKeys, 6, abi.encodePacked(Enum2(v7))); @@ -483,19 +385,12 @@ library Table1 { Enum2 k7 ) internal view returns (Table1Data memory _table) { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); bytes memory _blob = StoreSwitch.getRecord(_tableId, _primaryKeys, getSchema()); @@ -522,19 +417,12 @@ library Table1 { bytes memory _data = abi.encodePacked(v1, v2, v3, v4, v5, v6, v7); bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.setRecord(_tableId, _primaryKeys, _data); @@ -582,19 +470,12 @@ library Table1 { Enum2 k7 ) internal { bytes32[] memory _primaryKeys = new bytes32[](7); - _primaryKeys[0] = bytes32(uint256((k1))); - _primaryKeys[1] = bytes32(uint256(uint32((k2)))); - _primaryKeys[2] = bytes32((k3)); - _primaryKeys[3] = bytes32(bytes20((k4))); - _primaryKeys[4] = _boolToBytes32((k5)); - _primaryKeys[5] = bytes32(uint256(Enum1(k6))); - _primaryKeys[6] = bytes32(uint256(Enum2(k7))); StoreSwitch.deleteRecord(_tableId, _primaryKeys); diff --git a/packages/cli/src/render-solidity/common.ts b/packages/cli/src/render-solidity/common.ts index 1035965525..d95aec1828 100644 --- a/packages/cli/src/render-solidity/common.ts +++ b/packages/cli/src/render-solidity/common.ts @@ -32,9 +32,7 @@ export function renderCommonData({ staticRouteData, primaryKeys }: RenderTableOp bytes32[] memory _primaryKeys = new bytes32[](${primaryKeys.length}); ${renderList( primaryKeys, - (primaryKey, index) => ` - _primaryKeys[${index}] = ${renderValueTypeToBytes32(primaryKey.name, primaryKey)}; - ` + (primaryKey, index) => `_primaryKeys[${index}] = ${renderValueTypeToBytes32(primaryKey.name, primaryKey)};` )} `; From 9c30b060133bd895e577153ec27ff55f08f4ee9d Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 20:50:58 +0300 Subject: [PATCH 20/22] fix(cli): fix indirect merge conflicts --- packages/cli/contracts/src/tables/Table1.sol | 87 ++------------------ packages/cli/scripts/codegen.ts | 3 +- packages/cli/src/render-solidity/userType.ts | 15 ++++ packages/cli/src/utils/deploy-v2.ts | 8 +- 4 files changed, 31 insertions(+), 82 deletions(-) diff --git a/packages/cli/contracts/src/tables/Table1.sol b/packages/cli/contracts/src/tables/Table1.sol index aa0928e305..d5280b72d8 100644 --- a/packages/cli/contracts/src/tables/Table1.sol +++ b/packages/cli/contracts/src/tables/Table1.sol @@ -76,16 +76,7 @@ library Table1 { } /** Set v1 */ - function setV1( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - uint256 v1 - ) internal { + function setV1(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, uint256 v1) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); _primaryKeys[1] = bytes32(uint256(uint32((k2)))); @@ -122,16 +113,7 @@ library Table1 { } /** Set v2 */ - function setV2( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - int32 v2 - ) internal { + function setV2(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, int32 v2) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); _primaryKeys[1] = bytes32(uint256(uint32((k2)))); @@ -168,16 +150,7 @@ library Table1 { } /** Set v3 */ - function setV3( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - bytes16 v3 - ) internal { + function setV3(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, bytes16 v3) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); _primaryKeys[1] = bytes32(uint256(uint32((k2)))); @@ -214,16 +187,7 @@ library Table1 { } /** Set v4 */ - function setV4( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - address v4 - ) internal { + function setV4(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, address v4) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); _primaryKeys[1] = bytes32(uint256(uint32((k2)))); @@ -260,16 +224,7 @@ library Table1 { } /** Set v5 */ - function setV5( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - bool v5 - ) internal { + function setV5(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, bool v5) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); _primaryKeys[1] = bytes32(uint256(uint32((k2)))); @@ -306,16 +261,7 @@ library Table1 { } /** Set v6 */ - function setV6( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - Enum1 v6 - ) internal { + function setV6(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, Enum1 v6) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); _primaryKeys[1] = bytes32(uint256(uint32((k2)))); @@ -352,16 +298,7 @@ library Table1 { } /** Set v7 */ - function setV7( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7, - Enum2 v7 - ) internal { + function setV7(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7, Enum2 v7) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); _primaryKeys[1] = bytes32(uint256(uint32((k2)))); @@ -460,15 +397,7 @@ library Table1 { } /* Delete all data for given keys */ - function deleteRecord( - uint256 k1, - int32 k2, - bytes16 k3, - address k4, - bool k5, - Enum1 k6, - Enum2 k7 - ) internal { + function deleteRecord(uint256 k1, int32 k2, bytes16 k3, address k4, bool k5, Enum1 k6, Enum2 k7) internal { bytes32[] memory _primaryKeys = new bytes32[](7); _primaryKeys[0] = bytes32(uint256((k1))); _primaryKeys[1] = bytes32(uint256(uint32((k2)))); diff --git a/packages/cli/scripts/codegen.ts b/packages/cli/scripts/codegen.ts index 98b83c8cbf..f5163f8412 100644 --- a/packages/cli/scripts/codegen.ts +++ b/packages/cli/scripts/codegen.ts @@ -1,9 +1,8 @@ import { SchemaType } from "@latticexyz/schema-type"; -import path from "path"; import { parseStoreConfig, StoreUserConfig } from "../src/config/index.js"; import { tablegen } from "../src/render-solidity/tablegen.js"; import { logError } from "../src/utils/errors.js"; -import { getSrcDirectory } from "../src/utils/forgeConfig.js"; +import { getSrcDirectory } from "../src/utils/foundry.js"; // This config is used only for tests const config: StoreUserConfig = { diff --git a/packages/cli/src/render-solidity/userType.ts b/packages/cli/src/render-solidity/userType.ts index 4de87ab547..158976573e 100644 --- a/packages/cli/src/render-solidity/userType.ts +++ b/packages/cli/src/render-solidity/userType.ts @@ -5,6 +5,21 @@ import { RenderTableType } from "./types.js"; export type UserTypeInfo = ReturnType; +/** + * Resolve a SchemaType|userType into a SchemaType + */ +export function resolveSchemaOrUserTypeSimple( + schemaOrUserType: SchemaType | string, + userTypesConfig: StoreConfig["userTypes"] +) { + if (typeof schemaOrUserType === "string") { + const { schemaType } = getUserTypeInfo(schemaOrUserType, userTypesConfig); + return schemaType; + } else { + return schemaOrUserType; + } +} + /** * Resolve a SchemaType|userType into RenderTableType, required import, and internal SchemaType */ diff --git a/packages/cli/src/utils/deploy-v2.ts b/packages/cli/src/utils/deploy-v2.ts index 9205410dc8..6cc64e7640 100644 --- a/packages/cli/src/utils/deploy-v2.ts +++ b/packages/cli/src/utils/deploy-v2.ts @@ -9,6 +9,7 @@ import { abi as WorldABI, bytecode as WorldBytecode } from "@latticexyz/world/ab import { ArgumentsType } from "vitest"; import chalk from "chalk"; import { encodeSchema } from "@latticexyz/schema-type"; +import { resolveSchemaOrUserTypeSimple } from "../render-solidity/userType.js"; export interface DeployConfig { profile?: string; @@ -80,10 +81,15 @@ export async function deploy(mudConfig: MUDConfig, deployConfig: DeployConfig): // Register table const tableBaseRoute = toRoute(baseRoute, ...routeFragments); + + const schemaTypes = Object.values(tableConfig.schema).map((schemaOrUserType) => { + return resolveSchemaOrUserTypeSimple(schemaOrUserType, mudConfig.userTypes); + }); + await fastTxExecute(WorldContract, "registerTable", [ tableBaseRoute, lastRouteFragment, - encodeSchema(Object.values(tableConfig.schema)), + encodeSchema(schemaTypes), ]); // Register table metadata From a0049265f0942927f06765eb24323971fb95f040 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 20:58:44 +0300 Subject: [PATCH 21/22] fix(cli): use StoreView in contract test --- packages/cli/contracts/src/tables/Table1.sol | 19 +++++++++++++++++++ packages/cli/contracts/test/tablegen.t.sol | 5 ++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/packages/cli/contracts/src/tables/Table1.sol b/packages/cli/contracts/src/tables/Table1.sol index d5280b72d8..29b4caf1e4 100644 --- a/packages/cli/contracts/src/tables/Table1.sol +++ b/packages/cli/contracts/src/tables/Table1.sol @@ -47,11 +47,30 @@ library Table1 { return SchemaLib.encode(_schema); } + /** Get the table's metadata */ + function getMetadata() internal pure returns (string memory, string[] memory) { + string[] memory _fieldNames = new string[](7); + _fieldNames[0] = "v1"; + _fieldNames[1] = "v2"; + _fieldNames[2] = "v3"; + _fieldNames[3] = "v4"; + _fieldNames[4] = "v5"; + _fieldNames[5] = "v6"; + _fieldNames[6] = "v7"; + return ("Table1", _fieldNames); + } + /** Register the table's schema */ function registerSchema() internal { StoreSwitch.registerSchema(_tableId, getSchema()); } + /** Set the table's metadata */ + function setMetadata() internal { + (string memory _tableName, string[] memory _fieldNames) = getMetadata(); + StoreSwitch.setMetadata(_tableId, _tableName, _fieldNames); + } + /** Get v1 */ function getV1( uint256 k1, diff --git a/packages/cli/contracts/test/tablegen.t.sol b/packages/cli/contracts/test/tablegen.t.sol index 6862778a4e..de54cc044f 100644 --- a/packages/cli/contracts/test/tablegen.t.sol +++ b/packages/cli/contracts/test/tablegen.t.sol @@ -2,12 +2,11 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; +import {StoreView} from "@latticexyz/store/src/StoreView.sol"; import {Table1, Table1Data} from "../src/tables/Table1.sol"; import {Enum1, Enum2} from "../src/types.sol"; -contract tablegenTest is Test { - function isStore() external view {} - +contract tablegenTest is Test, StoreView { function testTable1SetAndGet() public { Table1.registerSchema(); From 3206bbd969bb28edd27368393f5c07787eed6ce7 Mon Sep 17 00:00:00 2001 From: dk1a Date: Wed, 8 Mar 2023 21:18:25 +0300 Subject: [PATCH 22/22] refactor(cli): capitalize TablegenTest --- packages/cli/contracts/test/{tablegen.t.sol => Tablegen.t.sol} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/cli/contracts/test/{tablegen.t.sol => Tablegen.t.sol} (95%) diff --git a/packages/cli/contracts/test/tablegen.t.sol b/packages/cli/contracts/test/Tablegen.t.sol similarity index 95% rename from packages/cli/contracts/test/tablegen.t.sol rename to packages/cli/contracts/test/Tablegen.t.sol index de54cc044f..5dbd54d951 100644 --- a/packages/cli/contracts/test/tablegen.t.sol +++ b/packages/cli/contracts/test/Tablegen.t.sol @@ -6,7 +6,7 @@ import {StoreView} from "@latticexyz/store/src/StoreView.sol"; import {Table1, Table1Data} from "../src/tables/Table1.sol"; import {Enum1, Enum2} from "../src/types.sol"; -contract tablegenTest is Test, StoreView { +contract TablegenTest is Test, StoreView { function testTable1SetAndGet() public { Table1.registerSchema();