Tables
In autonomous worlds, any data that requires persistent storage based on contract state is implemented through tables.
Define and create tables through configuration files, and manipulate tables using automatically generated table code libraries.
Defining a Table
This is a simple configuration file that defines a table in muddoc
namespace, named Users with three fields: addr, data, and
description.
Their types are address, uint256, and string respectively, with the
addr field serving as the primary key.
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "muddoc",
tables: {
Users: {
schema: {
addr: "address",
data: "uint256",
description: "string",
},
key: ["addr"],
},
}
});
In the configuration file mud.config.ts, each key-value pair in tables
defines a table. The key of the pair determines the table’s name, while the
value is an object containing other configuration items for the table, mainly
including the definition of table fields and primary keys. All systems will
be registered in the namespace named by namespace field.
In the autonomous world defined by Mud, a table is a type of resource, and each
resource is identified by a unique ResourceId.
The ResourceId of a table is called tableId.
For example, the tableId of Users is
0x74626d7564646f63000000000000000055736572730000000000000000000000.
Here, 7462 is the hexadecimal form of tb,
online conversion tool.
6d7564646f63 represents muddoc, and 5573657273 represents Users.
The table configuration items are as follows:
schema:object, field definition of the table, key-value pairs of field names and types.Important
Each table can have up to 28 fields excluding the primary key, with a maximum of 5 reference type fields.
When defining, reference type fields must be placed at the end.
key:string[], the table’s primary key, can be an array of one or more fields. It can also be an empty array, indicating a singleton table.Important
Primary key fields must be of value types. The number of primary key fields is mainly limited by EVM stack depth. Too many primary key fields will render the table’s read/write methods unusable.
type:string(optional), type of the table.table(default, stored on-chain) oroffchainTable(table data can only be retrieved off-chain throughevent).codegen:object(optional).outputDirectory:string, default:"tables". Output directory for code generation, by default insrc/codegen/tablesunder the configuration file directory.tableIdArgument:boolean, default:false. Whether to generatetableIdparameter for read and write methods.storeArgument:boolean, default:false. Whether to generatestoreparameter for read and write methods.
Note
When the same type of table (same field and primary key definition) exists in multiple namespaces, or exists with different names in the same namespace, you can adjust
tableIdArgumentto let the automatically generated code library operate different tables based on the passedtableId.When this scenario extends to different autonomous worlds, you can further adjust
storeArgumentto introduce thestoreparameter.dataStruct:boolean, default istruewhen there is more than one field that is not part of the primary key. Whether to generate a data structure for non-primary key fields of the table, defaultstruct <TableName>Data.
deploy:object(optional)。disabled:booleandefault:false。Whether to create this table。
Supported Table Field Types
Type |
|
|---|---|
Value Types
|
uint8 ~ uint256, int8 ~ int256,address, bool, bytes1 ~ bytes32 |
Reference Types
|
Fixed-length or dynamic arrays of value types,
string, bytes |
Enums |
✅ |
User-defined Types |
✅ |
|
❌ |
|
❌ |
|
❌ |
|
❌ |
Important
It’s not that the Mud framework can’t read or write mapping,
string[], bytes[], struct type data, but rather these data
types don’t need to exist as table fields.
If we want to implement a mapping(uint256 => address) type, we can
create a table with two fields, with types uint256 and address
respectively, and set the uint256 field as the primary key.
To implement string[] or bytes[] types, we can create a table with
two fields, types uint256 and string or bytes, and set the
uint256 field as the primary key, representing the array index.
The single row in each singleton table can be viewed as a piece of data of
struct type.
Enums
We can define enums in the configuration file and use them in table fields.
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "muddoc",
enums: {
UserStatus: ["active", "inactive"],
},
tables: {
UserStates: {
schema: {
addr: "address",
status: "UserStatus",
},
key: ["addr"],
},
}
});
Each key-value pair in enums defines an enum. The key determines the
name of the enum, and the value is an array of strings containing all
enum member names.
All enums are generated and stored in src/codegen/common.sol by
CLI: mud tablegen.
User-defined Types
In the configuration file, we can import user-defined types via file paths and use these imported user-defined types in table fields.
User-defined types need to be prepared in advance. CLI: mud tablegen
automatically generates corresponding imports for the table code library based
on the import paths in the configuration file.
These user-defined types can come from either the current project or third- party libraries.
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "muddoc",
userTypes: {
MyUint256: {
type: "uint256",
filePath: "./src/utils/MyUint256s.sol",
},
ShortString: {
type: "bytes32",
filePath: "@openzeppelin/contracts/utils/ShortStrings.sol",
}
},
tables: {
UserStates: {
schema: {
addr: "address",
data: "MyUint256",
label: "ShortString",
},
key: ["addr"],
},
}
});
./src/utils/MyUint256s.sol is a relative path with respect to the
configuration file. Its content is roughly as follows:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;
type MyUint256 is uint256;
library MyUint256s {
// MyUint256 utils
}
Table Definition Shorthand
For convenience in defining tables with only one field or those not requiring
additional configuration, several shorthand methods can be used. Here, T*
represents the shorthand table definition, while the corresponding Table*
represents the equivalent complete table definition.
import { defineWorld } from "@latticexyz/world";
export default defineWorld({
namespace: "muddoc",
tables: {
T1: "address",
T2: "uint256[]",
T3: "uint8[10]",
T4: {
id: "address",
value: "uint256",
data: "string",
},
Table1: {
schema: {
id: "bytes32",
value: "address",
},
key: ["id"],
},
Table2: {
schema: {
id: "bytes32",
value: "uint256[]",
},
key: ["id"],
},
Table3: {
schema: {
id: "bytes32",
value: "uint8[10]",
},
key: ["id"],
},
Table4: {
schema: {
id: "address",
value: "uint256",
data: "string",
},
key: ["id"],
},
}
});
Table Usage
The main operations on tables include creating(registering), reading,
updating, and deleting. All operations rely on the code library generated by
CLI: mud tablegen based on the table definitions. The code library for
each table is a separate solidity library named after the table,
containing the tableId, table structure, and CRUD methods.
By simply importing the table’s code library into the contract, you can directly call the CRUD methods.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;
import { System } from "@latticexyz/world/src/System.sol";
import { Users } from "../codegen/index.sol";
contract TableOperationSystem is System {
function CRUD() public {
Users.register(); // Don't do this. It's just for demonstration purposes.
(uint256 data, string memory description) = Users.get(address(0));
Users.set(address(0), 1 /* data */, "address zero" /* description */);
Users.deleteRecord(address(0));
}
}
register(), registers the table in the autonomous world. One-time operation. Requires owning namespace ownership.Note
Tables defined in the configuration file are automatically registered during deployment, requiring no manual operation.
Note
register()is typically used in modules to register the table in the autonomous world where the module resides.get(),set(), read/write data by row. Thecodegen.dataStructconfig item in table definition affectsget()’s return type.get<Fieldname>(),set<Fieldname>(), read/write a single field.getItem<Fieldname>reads a reference type field element by index.update<Fieldname>, updates a reference type field element by index.length<Fieldname>, gets reference type field length, not for fixed arrays likeuint8[4].push<Fieldname>,pop<Fieldname>, add/remove element at end of reference type field, not for fixed-length arrays.
Internal CRUD Methods
When examining a table’s library, you’ll notice each CRUD method has a
similar counterpart with a different name. These methods start with _,
like _register(), conventionally indicating internal methods.
Here, internal methods refer to those that, compared to the methods
mentioned above, can only be used within the context of the autonomous
world’s main contract.
Note
These internal methods can be used in systems under the root namespace.
If your project uses custom namespaces, avoid these internal methods.
Don’t worry about data security; using these methods will only result in
errors or unexpected effects, without damaging project data.
CRUD Methods with tableId Parameter
In some cases, we need to distinguish tables using a tableId parameter.
In the config file, adding the codegen.tableIdArgument config item to
the required table definition introduces the tableId parameter to all
CRUD methods.
CRUD Methods with store Parameter
Sometimes, we need to specify the autonomous world of the operated table
using a store parameter. In the config file, adding the
codegen.storeArgument config item to the required table definition
generates an additional set of CRUD methods in the library with the store
parameter. These methods have the same names without the _ prefix.