From af4ae94b447eea4d361be76370991cf561d0afa7 Mon Sep 17 00:00:00 2001 From: Ori Pomerantz Date: Mon, 5 Aug 2024 06:13:19 -0500 Subject: [PATCH] docs(config): add multi-namespace support (#2993) Co-authored-by: Kevin Ingersoll --- docs/components/Params.module.css | 49 ++++++ docs/components/Params.tsx | 30 ++++ docs/pages/config.mdx | 259 ++++++++++++++++++++++-------- docs/pages/config/reference.mdx | 148 +++++++++++++++++ 4 files changed, 415 insertions(+), 71 deletions(-) create mode 100644 docs/components/Params.module.css create mode 100644 docs/components/Params.tsx create mode 100644 docs/pages/config/reference.mdx diff --git a/docs/components/Params.module.css b/docs/components/Params.module.css new file mode 100644 index 0000000000..f97a28b8f9 --- /dev/null +++ b/docs/components/Params.module.css @@ -0,0 +1,49 @@ +.list { + display: grid; + grid-template-columns: 12rem auto; +} +.list > :nth-child(n + 3) { + border-top: 1px solid rgba(255, 255, 255, 0.1); +} +.title:not(:first-child), +.list:not(:first-child):not(.title + .list) { + margin-top: 1rem; +} +.list:not(:last-child) { + margin-bottom: 1rem; +} + +.title { + font-size: 0.75rem; + text-transform: uppercase; + font-weight: bold; + text-align: center; + color: rgba(255, 255, 255, 0.5); +} + +.title, +.term, +.definition { + padding-left: 0.5rem; + padding-right: 0.5rem; +} +.term, +.definition { + padding-top: 0.375rem; + padding-bottom: 0.375rem; +} +.term { + font-family: monospace; + font-weight: bold; + background: rgba(255, 255, 255, 0.1); + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +.term:hover { + overflow: visible; + position: relative; +} +.definition { + padding-left: 0.5rem; +} diff --git a/docs/components/Params.tsx b/docs/components/Params.tsx new file mode 100644 index 0000000000..ea9b922cde --- /dev/null +++ b/docs/components/Params.tsx @@ -0,0 +1,30 @@ +import React, { ReactNode } from "react"; +import styles from "./Params.module.css"; + +type ParamsProps = { + title?: string; + children: ReactNode; +}; + +export function Params({ title, children }: ParamsProps) { + return ( + <> + {title ?
{title}
: null} +
{children}
+ + ); +} + +type ParamProps = { + name: string; + children: ReactNode; +}; + +export function Param({ name, children }: ParamProps) { + return ( + <> +
{name}
+
{children}
+ + ); +} diff --git a/docs/pages/config.mdx b/docs/pages/config.mdx index ed7d9f3876..312dc1cd9c 100644 --- a/docs/pages/config.mdx +++ b/docs/pages/config.mdx @@ -1,35 +1,21 @@ # MUD config -Certain CLI commands, such as [`mud tablegen`](/cli/tablegen) and [`mud worldgen`](/cli/worldgen) require the MUD configuration file. -This file needs to be named `mud.config.ts` and be in the same folder as your `foundry.toml` file. +The `mud.config.ts` file is where a MUD project begins. It defines the resources ([namespaces](/world/namespaces-access-control), [tables](/store/tables), [systems](/world/systems), and [modules](/world/modules)) used by your app and how MUD should codegen and deploy them. Its output is strongly typed for better type safety and developer experience in the form of hinting, inference, and autocomplete. -The config is used to define: +By default, the MUD config assumes your project is using only a [single namespace](#single-namespace). This is a great starting place for simple apps and worlds, where you're not yet thinking about extendability. -- The tables in your project in the `tables` object of your configuration. -- The [namespace](/world/namespaces-access-control) that [systems](/world/systems) and tables will be deployed in. -- The `System`s in your project. - By default, the deployer will find all Solidity matching `*System.sol` (so any file ending in `System.sol`, in any folder) and deploy them as public `System`. - If you want greater control over your systems (to change their public access or their name), you can use the `systems` object in the config. -- The [modules](/world/modules) that will be installed in the `World`. +Once you're building a more complex app or on top of an existing world, you may want to take advantage of [multiple namespaces](#multiple-namespaces). This enables more complex and composable behavior, including access control around data and functionality. Even MUD itself takes advantage of several namespaces for its core resources. -The is an example of a `World` config: +## Single namespace ```tsx import { defineWorld } from "@latticexyz/world"; export default defineWorld({ + namespace: "mud", enums: { TerrainType: ["None", "TallGrass", "Boulder"], }, - excludeSystems: ["System3", "System2"], - worldContractName: "CustomWorld", - namespace: "mud", - systems: { - IncrementSystem: { - name: "increment", - openAccess: true, - }, - }, tables: { Counter: { schema: { @@ -47,81 +33,212 @@ export default defineWorld({ key: ["id"], }, }, - deploysDirectory: "./mud-deploys", + systems: { + IncrementSystem: { + name: "increment", + openAccess: true, + }, + }, + excludeSystems: ["System3", "System2"], }); ``` -## Global configuration keys +## Multiple namespaces -The global configuration keys are all optional. +Starting with version 2.1 you can put multiple namespaces in the same config file, you create a `namespaces` record and within it a record for every namespace. +For example: -- **`namespace`**: a `string`: which namespace to deploy the resources defined in the config into. - The default value is the ROOT namespace. +### Sample `mud.config.ts` files -- **`tables`**: a record of tables. The keys in the record are table names. - The value is a record of [table properties](https://github.com/latticexyz/mud/blob/main/packages/store/ts/config/storeConfig.ts#L110-L135). +#### A namespace - - **`schema`** (record): - The keys of this record are the field names in the data, which includes both the key fields and the value fields. - By convention, these keys start with a lowercase letter. - The values are strings that contain the data types of the fields. - Note that this is the sole required field, all the others are optional. +This is an extremely simple configuration file with a single namespace that contains a single table. +The systems are defined implicitly, as all the contracts under `src/namespaces/app` that match `*System.sol`. - - **`key`** (list): - A list of the `schema` fields that are the key for that table. - If you want a singleton table, use an empty list (as in `Counter` table above). +```typescript filename="mud.config.ts" +import { defineWorld } from "@latticexyz/world"; + +export default defineWorld({ + namespaces: { + app: { + tables: { + Tasks: { + schema: { + id: "bytes32", + createdAt: "uint256", + completedAt: "uint256", + description: "string", + }, + key: ["id"], + }, + }, + }, + }, +}); +``` - - **`type`** (either `table` or `offchainTable`): [The table type](/store/tables#types-of-tables). - The default is `table`, which is a table stored onchain. - If you specify `offchainTable`, the table's data is only available offchain through events. +**Source files** -- **`systems`**: a record of system definitions. The keys in the record are file names without the `.sol` extension. For example, if your system is named `TestSystem.sol`, use `TestSystem` as the key. +- `src/namespaces/app/systems` - systems that belong to the `app` namespace. - The value is a record of system configuration properties: +Note that this is a convention, _any_ `*System.sol` under `src/namespaces/` is interpreted as a system in that namespace. - - `registerFunctionSelectors` (optional, default `true`): a `bool`. - Whether we want to automatically register the `public` functions of the `System` as - [function selectors](/world/function-selectors) - - `openAccess` (optional, default `true`): a `bool`. - If set to `false`, only the systems in the same namespace and the addresses or systems listed in the `accessList` array have access. - - `accessList` (required if openAccess is `false`): an array of `string`. Each address in the array will be granted access to this system, allowing them to call it. +**Generated files** -- **`enums`**: a record of [enumerations](https://solidity-by-example.org/enum/). - The keys are the names of the enumerations. - The values are arrays of the strings for the values the enumeration can take. +- `src/namespaces/app/codegen/tables/Tasks.sol` - the generated code for `app__Tasks` table. +- `src/namespaces/app/codegen/index.sol` - a single file that imports all the table definitions of the namespace (in this case, only `Tasks.sol`). +- `src/codegen/world/I*System.sol` - interfaces for all the systems. +- `src/codegen/world/IWorld.sol` - the `IWorld` interface that inherits from all the `I*System.sol` files. -- **`tables`**: a record of tables. The keys in the record are table names. - The value is a record of [table properties](https://github.com/latticexyz/mud/blob/main/packages/store/ts/config/storeConfig.ts#L110-L135). +#### A namespace with an explicit system definition -- **`excludeSystems`**: an array of `string`: which systems to not deploy, even if their name ends with “System”. +By default `Systems` are publicly accessible. +In this configuration, we explicitly specify `TestSystem` so we can specify that access to it is limited to authorized addresses. -- **`modules`** an array of module definitions: each module definition has a `name`, `root` (optional), and `args` key. +```typescript filename="mud.config.ts" {17-21} +import { defineWorld } from "@latticexyz/world"; - - `name`: Name of the module to install. The same module can be installed multiple times. This should be the name of the contract file without `.sol` (eg: if the file is named `DopeModule.sol`, the name of the module is `DopeModule`) +export default defineWorld({ + namespaces: { + app: { + tables: { + Tasks: { + schema: { + id: "bytes32", + createdAt: "uint256", + completedAt: "uint256", + description: "string", + }, + key: ["id"], + }, + }, + systems: { + TestSystem: { + openAccess: false, + }, + }, + }, + }, +}); +``` - - `root`: whether to create a `root` module or not. `root` modules have access to all tables and are not bound to namespace restrictions. +**Source files** - - `args`: a list of arguments to be sent to the `install` function of the module. In this array, you can use the function `resolveTableId`. This function will turn a table name from your config into its low-level ID in the World. It is useful to pass references of a table to a module. +- `src/namespaces/app/systems/RootSystem.sol` - The system specified in `mud.config.ts`. +- `src/namespaces/app/systems` - systems that belong to the `app` namespace. -- **`codegen`**: a record of code generation options. +Note that this is a convention, _any_ `*System.sol` under `src/namespaces/` is interpreted as a system in that namespace. - - **`worldInterfaceName`** a `string`: The name of the interface for the `World`. - The default value is `IWorld`. - - **`worldgenDirectory`** a `string`: The directory for system and world interfaces. - The default value is `world`. +**Generated files** + +- `src/namespaces/app/codegen/tables/Tasks.sol` - the generated code for `app__Tasks`. +- `src/namespaces/app/codegen/index.sol` - a single file that imports all the table definitions (in this case, only `Tasks.sol`). +- `src/codegen/world/I*System.sol` - interfaces for all the systems, including `IRootSystem.sol` +- `src/codegen/world/IWorld.sol` - the `IWorld` interface that inherits from all the `I*System.sol` files. + +#### Two namespaces + +In this example there are two namespaces, `app` and `config`. +Each namespace contains a single table: `app__Tasks` and `config__Configuration`. + +```typescript filename="mud.config.ts" {4-5,18} +import { defineWorld } from "@latticexyz/world"; + +export default defineWorld({ + namespaces: { + app: { + tables: { + Tasks: { + schema: { + id: "bytes32", + createdAt: "uint256", + completedAt: "uint256", + description: "string", + }, + key: ["id"], + }, + }, + }, + config: { + tables: { + Configuration: { + schema: { + deployer: "address", + tokenAddress: "address", + ipAddress: "bytes4", + url: "string", + }, + key: [], + }, + }, + }, + }, +}); +``` + +**Source files** + +- `src/namespaces/app/systems` - systems that belong to the `app` namespace. +- `src/namespaces/config/systems` - systems that belong to the `config` namespace. + +Note that this is a convention, _any_ `*System.sol` under `src/namespaces/` is interpreted as a system in that namespace. + +**Generated files** + +- `src/namespaces/app/codegen/tables/Tasks.sol` - the generated code for `app__Tasks` table. +- `src/namespaces/app/codegen/index.sol` - a single file that imports all the table definitions of the namespace (in this case, only `Tasks.sol`). +- `src/namespaces/config/systems` - systems that belong to the `config` namespace. +- `src/namespaces/config/codegen/tables/Tasks.sol` - the generated code for `config__Configuration` table. +- `src/namespaces/config/codegen/index.sol` - a single file that imports all the table definitions of the namespace (in this case, only `Configuration.sol`). +- `src/codegen/world/I*System.sol` - interfaces for all the systems. +- `src/codegen/world/IWorld.sol` - the `IWorld` interface that inherits from all the `I*System.sol` files. + +#### Enumerations + +```typescript filename="mud.config.ts" {4-7} +import { defineWorld } from "@latticexyz/world"; + +export default defineWorld({ + enums: { + Direction: ["Up", "Down", "Left", "Right"], + MapDirection: ["North", "East", "South", "West"], + }, + namespaces: { + app: { + tables: { + Heading: { + schema: { + id: "bytes32", + direction: "Direction", + name: "string", + }, + key: ["id"], + }, + MapCursor: { + schema: { + id: "bytes32", + direction: "MapDirection", + name: "string", + }, + key: ["id"], + }, + }, + }, + }, +}); +``` -- **`deploy`**: a record of deployment options. +**Source files** - - **`postDeployScript`** a `string`: Script to execute after the deployment is complete. - This script must be placed in the forge scripts directory (see `foundry.toml`) and have a `.s.sol` extension. - The default is `PostDeploy`. +- `src/namespaces/app/systems` - systems that belong to the `app` namespace. - - **`deploysDirectory`** a `string`: Which folder to put the deployment artifacts into after deployment. - The default is `./deploys`. +Note that this is a convention, _any_ `*System.sol` under `src/namespaces/` is interpreted as a system in that namespace. - - **`worldsFile`** a `string`: JSON file for the chain to `World` deploy address mapping. - The default is `./worlds.json`. +**Generated files** - - - **`upgradeableWorldImplementation`** a `bool`: Whether the `World` is to be deployed behind a proxy to [enable upgrades - of the core World implementation](/world/upgrades). The default is `false`. +- `src/namespaces/app/codegen/tables/Heading.sol` - the generated code for `app__Heading` table. +- `src/namespaces/app/codegen/tables/MapCursor.sol` - the generated code for `app__MapCursor` table. +- `src/namespaces/app/codegen/index.sol` - a single file that imports all the table definitions of the namespace (in this case, `Heading.sol` and `MapCursor.sol`). +- `src/codegn/common.sol` - the enumerations in the config file. +- `src/codegen/world/I*.sol` - interfaces for all the systems. +- `src/codegen/world/IWorld.sol` - the `IWorld` interface that inherits from all the `I*System.sol` files. diff --git a/docs/pages/config/reference.mdx b/docs/pages/config/reference.mdx new file mode 100644 index 0000000000..8c2bb986b0 --- /dev/null +++ b/docs/pages/config/reference.mdx @@ -0,0 +1,148 @@ +import { Params, Param } from "../../components/Params"; + +# MUD config reference + +The MUD config has two modes: single namespace and multiple namespaces. By default, the config will assume a single namespace unless you start using the `namespaces` config option. + +## Single namespace + + + + The namespace used for this project's resources and access control. The namespace must fit into a `bytes14`. Leaving + this blank will use the root namespace, which is [not + recommended](/guides/best-practices/system-best-practices#avoid-the-root-namespace-if-possible) for most use cases. + + + + A mapping of table labels to their table config. Unless disabled, this will codegen a corresponding table library + for better UX when reading/writing records. + + + + The `bytes14` name used in the resource ID. Defaults to the first 14 characters of the table label. + + + [Table type](/store/tables#types-of-tables), either `"table"` or `"offchainTable"`. Defaults to `"table"`. + + + An ordered mapping of field names to their schema type, `enums` label, or `userTypes` label. Static-length field types (e.g. `uint256`) must come before dynamic-length field types (e.g. `string` or `uint8[]`). + + + A tuple of field names in the `schema` to be used as the primary key of table records. Only static-length fields + are supported as keys. + + + Customize how codegen should behave when generating this table's corresponding Solidity library. + + + Output directory of codegen table library relative to top-level `codegen.outputDirectory`. Defaults to `"tables"`. + Whether or not to use a struct for table data. Defaults to `true`. + + + + + Customize how to deploy this table. + + + Disable deployment of this table. Defaults to `false`. + + + + + + Tables can also be defined using shorthands. + For multi-field tables, the value in the mapping can be just the table schema, but must include a static-length `id` field to be used as the `key`. + For single-field tables, the value in the mapping can be just the field type, which assumes a schema of `{ id: "bytes32", value: … }` where `value` is the provided field type. + + + + + A mapping of system labels to their system config. All systems must be named using a `System` suffix. Systems only need to be included here if they are deviating from system config defaults. + + + + The `bytes14` name used in the resource ID. Defaults to the first 14 characters of the system label. + + Whether or not any address can call this system. Defaults to `true`. + A list of contract addresses or system labels that can call this system, used with `openAccess: false`. + + + + + +## Multiple namespaces + + + + A mapping of namespace labels to their namespace config. Using this config option expects your project's source + directory to use a similar structure of namespace directories like `src/namespaces/[namespaceLabel]`. + + + + The `bytes14` namespace used in the resource ID. Defaults to the first 14 characters of the namespace label. + + + Tables in this namespace. Same shape as `tables` in [single namespace mode](#single-namespace). + + + Systems in this namespace. Same shape as `systems` in [single namespace mode](#single-namespace). + + + + + + +## Additional options + +The following options are available in both single- and multiple-namespace modes. + + + + A mapping of enum labels to an array of string values. Enum labels can be used as schema types within tables. + + + A mapping of user type labels to their user type config. User type labels can be used as schema types within tables. + + + Solidity-native schema type. + + Path to source file relative to project root (must start with `./`) or import path from a package (must have + proper remappings set up). + + + + + + A list of modules to install into the world during the deploy step. + + + Relative path to the module's compiled JSON artifact (usually in `out`) or an import path if using a module from an npm package. This path is resolved using [Node's module `require` API](https://nodejs.org/api/modules.html#modulerequireid). + Whether or not to install this as a root module. Defaults to `false`. + A list of arguments used to call the module's install function. + + + + A list of system labels to exclude from codegen and deploy. + Path to this project's Solidity source files, relative to the project root. Defaults to `"src"`. This should be kept in alignment with `foundry.toml`. + + Customize how codegen should behave when generating this table's corresponding Solidity library. + + + Output directory of codegen relative to `sourceDirectory`. Defaults to `"codegen"`. When using multiple namespaces, this will be prefixed with `namespaces/[namespaceLabel]`. + Filename relative to `outputDirectory` to codegen enums into. Defaults to `"common.sol"`. + Output directory of world interfaces relative to `outputDirectory`. Defaults to `"world"`. + + + + Customize how to deploy this table. + + + Directory, relative to project root, to write the deployment artifacts to. Defaults to `"deploys"`. + Script name to execute after the deployment is complete. Defaults to `"PostDeploy"`. + JSON filename, relative to project root, to write per-chain world deployment addresses. Defaults to `"worlds.json"`. + Wheter or not to deploy the world with an upgradeable proxy, allowing for the core implementation to be upgraded. Defaults to `false`, but [we recommend `true`](/guides/best-practices/deployment-settings). + + + + +