Table Libraries
Methods on StoreCore
and IStore
use raw bytes as input and output types.
This is to allow using the same methods for different types of tables types without native support for generics in Solidity, but it can be inconvenient to work with.
For this reason MUD automatically generates a library for each table that provides typed methods corresponding to the table's key and value types.
Config
Table libraries are generated by the tablegen
CLI.
The CLI config section goes into more detail on the available configuration options.
To illustrate table library functionality on this page, we'll use two example tables with the following configurations:
schema: {
entity: "address",
// Two static length fields
x: "uint32",
y: "uint32",
},
key: ["entity"],
schema: {
entity: "address",
// One dynamic length field
slots: "uint8[]",
},
key: ["entity"],
Singleton tables
When you just need to store a single value (for example, the world map or the name of an in-game currency), you use a singleton table, a table whose key schema is empty. With an empty key schema there is just one record.
schema: {
value: "uint32",
},
key: [],
Usage
Importing the library
By default table libraries are generated into a codegen/tables
directory in the contract directory.
This can be configured with the codegenDirectory
option in the config.
The library name corresponds to the table's key in the table config.
import { Position } from "./codegen/tables/Position.sol";
import { Inventory } from "./codegen/tables/Inventory.sol";
For convenience, the tablegen
CLI also generates an index.sol
file that re-exports all the generated table libraries.
import { Position, Inventory } from "./codegen/tables/index.sol";
Registering the table
Before using the table library, it must be registered once in the Store. This happens automatically as part of the deploy CLI, but if you're using the table library in a Module or a contract that's not deployed by MUD, you'll need to register it manually.
Position.register();
Inventory.register();
The table library will detect the Store
context automatically (via StoreSwitch
).
Reading data
Table libraries provide typed methods for reading data from the Store
.
Read full record
To read the entire record, use the get
method.
If the table's value schema contains more than one field, the method returns a struct with the corresponding fields.
import { Position, PositionData } from "./codegen/tables/Position.sol";
address entity = address(0x1234);
PositionData memory position = Position.get(entity);
Read individual fields
To read a single field, use the get
method with the field name as a suffix.
import { Position } from "./codegen/tables/Position.sol";
address entity = address(0x1234);
uint32 x = Position.getX(entity);
Read length of dynamic length field
To read the length of a dynamic length field, use the length
method with the field name as a suffix.
import { Inventory } from "./codegen/tables/Inventory.sol";
address entity = address(0x1234);
uint256 length = Inventory.lengthSlots(entity);
Read individual elements of dynamic length field
To read individual fields of a dynamic length field, use the getItem
method with the field name as a suffix and the index as an argument.
import { Inventory } from "./codegen/tables/Inventory.sol";
address entity = address(0x1234);
uint8 item = Inventory.getItemSlots(entity, 0);
Writing data
Table libraries provide typed methods for writing data to the Store
.
Write full record
To write the entire record, use the set
method.
There is a variant of this method that accepts a struct, and a variant that accepts the individual fields.
import { Position, PositionData } from "./codegen/tables/Position.sol";
address entity = address(0x1234);
// set with individual fields
Position.set(entity, 1, 2);
// or set with struct
Position.set(entity, PositionData(1,2));
Write individual fields
To write a single field, use the set
method with the field name as a suffix.
import { Position } from "./codegen/tables/Position.sol";
address entity = address(0x1234);
Position.setX(entity, 1);
Write individual elements of dynamic length field
To write individual fields of a dynamic length field, use the update
method with the field name as a suffix and the index as an argument.
import { Inventory } from "./codegen/tables/Inventory.sol";
address entity = address(0x1234);
Inventory.updateSlots(entity, 0, 1);
Push to dynamic length field
To push a new element to a dynamic length field, use the push
method with the field name as a suffix.
import { Inventory } from "./codegen/tables/Inventory.sol";
address entity = address(0x1234);
Inventory.pushSlots(entity, 1);
Pop from dynamic length field
To remove the last element from a dynamic length field, use the pop
method with the field name as a suffix.
import { Inventory } from "./codegen/tables/Inventory.sol";
address entity = address(0x1234);
Inventory.popSlots(entity);
Note that this method does not return the popped element.
If you need to access the popped element, use the getItem
method before calling pop
.
import { Inventory } from "./codegen/tables/Inventory.sol";
address entity = address(0x1234);
uint256 length = Inventory.lengthSlots(entity);
uint8 lastItem = Inventory.getItemSlots(entity, length - 1);
Inventory.popSlots(entity);
Advanced
StoreSwitch
Table libraries internally use the StoreSwitch
library to write data to the Store.
StoreSwitch
detects whether the library is called from within a Store
contract or from an external contract that has a reference to the Store
contract.
- If the library is called from within a
Store
contract,StoreSwitch
accesses the store directly via the internalStoreCore
library. - If the library is called from a contract that has a fixed reference to a specific external
Store
contract,StoreSwitch
accesses thisStore
via the externalIStore
interface. - If
msg.sender
is aStore
contract,StoreSwitch
accesses theStore
via the caller's address via the externalIStore
interface.
To fix a reference to a specific Store
contract (2), use the StoreSwitch.setStoreAddress
method in your contract.
import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
StoreSwitch.setStoreAddress(STORE_ADDRESS);
This can be useful to test libraries using table libraries in isolation, by setting the Store
address in the test contract to a mock Store
contract.
This happens automatically in the MudTest
setup.