Tables
World uses the store tables, but adds access control.
For onchain tables, the data is stored by the World contract, which is also a StoreData (opens in a new tab).
When a System reads or writes storage via table libraries, the request goes into StoreSwitch (opens in a new tab). This library decides which approach to use:
-
If the
Systemis in the root namespace, then it was called withdelegatecall(opens in a new tab). This means it inherits theWorldstorage and can write directly to storage usingStoreCore(opens in a new tab). These calls bypass access control. -
If the
Systemis in any other namespace, then it was called withcall(opens in a new tab) and has to call back into theWorldusingIStore(opens in a new tab). These calls go through access control. They are only permitted if theSystemhas access to the table in question. By default aSystemhas access to its own namespace and therefore to all the tables inside it. Additional access can be granted by the namespace owner.
-
An account calls a function called
namespace__functionvia theWorld. This function was registered by the owner of thenamespacenamespace and points to thefunctionfunction in one of theSystems in thenamespacenamespace. -
The
Worldverifies that access is permitted (for example, becausenamespace:systemis publicly accessible) and if so callsfunctionon thenamespace:systemcontract with the provided parameters. -
At some point in its execution
functiondecides to update the data in the tablenamespace:table. As with all other tables, this table is stored in theWorld's storage. To modify it,functioncalls a function on theWorldcontract. -
The
Worldverifies that access is permitted (by default it would be, becausenamespace:systemhas access to thenamespacenamespace). If so, it modifies the data in thenamespace:tabletable.
Code samples
Reading from a table
Anybody connected to the blockchain can run the view functions that read table content.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.21;
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
// Read and manipulate the Systems table
import { Systems, SystemsTableId } from "@latticexyz/world/src/codegen/index.sol";
// The key is a ResourceId
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol";
contract ReadTableInformation is Script {
function run() external {
// Load the world address and specify it as the Store address
address worldAddress = vm.envAddress("WORLD_ADDRESS");
StoreSwitch.setStoreAddress(worldAddress);
// Table metainformation (field names)
string[] memory keyNames = Systems.getKeyNames();
string[] memory valueNames = Systems.getFieldNames();
console.log("Key fields:");
for (uint i = 0; i < keyNames.length; i++) {
console.log("\t", i, keyNames[i]);
}
console.log("Value fields:");
for (uint i = 0; i < valueNames.length; i++) {
console.log("\t", i, valueNames[i]);
}
// Read information about the :AccessManagement System
ResourceId accessManagementSystem = WorldResourceIdLib.encode(
RESOURCE_SYSTEM, // System
"", // Root namespace
"AccessManagement" // Called AccessManagement
);
(address systemAddress, bool publicAccess) = Systems.get(accessManagementSystem);
console.log("The address for the :AccessManagement System:", systemAddress);
console.log("Public access:", publicAccess);
}
}Explanation
import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";We need the StoreSwitch library (opens in a new tab) library to specify the address of the World with the data.
// Read and manipulate the Systems table
import { Systems, SystemsTableId } from "@latticexyz/world/src/codegen/index.sol";It is easiest if we import the definitions of the table that were generated using mud tablegen.
// The key is a ResourceId
import { ResourceId } from "@latticexyz/store/src/ResourceId.sol";
import { RESOURCE_SYSTEM } from "@latticexyz/world/src/worldResourceTypes.sol";
import { WorldResourceIdLib } from "@latticexyz/world/src/WorldResourceId.sol";The key of the store:Tables table is the resource ID for the various tables.
To read the information of a specific table later we need to create the appropriate resource ID.
contract ReadTableInformation is Script {
function run() external {
// Load the world address and specify it as the Store address
address worldAddress = vm.envAddress("WORLD_ADDRESS");
StoreSwitch.setStoreAddress(worldAddress);StoreSwitch.setStoreAddress (opens in a new tab) is the function we call to specify the World's address.
// Table metainformation (field names)
string[] memory keyNames = Systems.getKeyNames();
string[] memory valueNames = Systems.getFieldNames();These functions give us the names of the key fields and value field.
// Read information about the :AccessManagement System
ResourceId accessManagementSystem = WorldResourceIdLib.encode(
RESOURCE_SYSTEM, // System
"", // Root namespace
"AccessManagement" // Called AccessManagement
);Here we create the resource ID for the table whose information we want.
(address systemAddress, bool publicAccess) = Systems.get(accessManagementSystem);And actually read the information.
Writing to table
This code is taken from the React template (opens in a new tab). Note that only authorized addresses are allowed to write directly to a table.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.24;
import { Script } from "forge-std/Script.sol";
import { console } from "forge-std/console.sol";
import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";
import { IWorld } from "../src/codegen/world/IWorld.sol";
import { Tasks, TasksData } from "../src/codegen/index.sol";
contract PostDeploy is Script {
function run(address worldAddress) external {
// Specify a store so that you can use tables directly in PostDeploy
StoreSwitch.setStoreAddress(worldAddress);
// Load the private key from the `PRIVATE_KEY` environment variable (in .env)
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
// Start broadcasting transactions from the deployer account
vm.startBroadcast(deployerPrivateKey);
// We can set table records directly
Tasks.set("1", TasksData({ description: "Walk the dog", createdAt: block.timestamp, completedAt: 0 }));
// Or we can call our own systems
IWorld(worldAddress).addTask("Take out the trash");
bytes32 key = IWorld(worldAddress).addTask("Do the dishes");
IWorld(worldAddress).completeTask(key);
vm.stopBroadcast();
}
}Explanation
import { StoreSwitch } from "@latticexyz/store/src/StoreSwitch.sol";We need the StoreSwitch library (opens in a new tab) library to specify the address of the World with the data.
import { Tasks, TasksData } from "../src/codegen/index.sol";It is easiest if we import the definitions of the table that were generated using mud tablegen.
contract PostDeploy is Script {
function run(address worldAddress) external {
// Specify a store so that you can use tables directly in PostDeploy
StoreSwitch.setStoreAddress(worldAddress);StoreSwitch.setStoreAddress (opens in a new tab) is the function we call to specify the World's address.
// Start broadcasting transactions from the deployer account
vm.startBroadcast(deployerPrivateKey);.set changes the state of the blockchain, so it requires an address.
This address is necessary for two reasons:
- To spend ETH for gas.
- To check permissions.
// We can set table records directly
Tasks.set("1", TasksData({ description: "Walk the dog", createdAt: block.timestamp, completedAt: 0 }));Create a new TasksData and set that value with the key "1" (in hex the key is 0x310...0).
vm.stopBroadcast();
}
}Stop broadcasting transactions as the authorized address.