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
game__myFuncon theWorld. This function was registered by the owner of thegamenamespace and points to themyFuncfunction in one of theSystems in thenamespacenamespace. -
The
Worldverifies that access is permitted (for example, becausegame:Systemis publicly accessible) and if so callsmyFuncon thegame:Systemcontract with the provided parameters. -
At some point in its execution
myFuncdecides to update the data in the tablegame:Items. 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, becausegame:Systemhas access to thegamenamespace). If so, it modifies the data in thegame:Itemstable.
Once a table is created its schema is immutable. If you need to add fields to an existing table, create a new table with the same key schema and the new fields in the value schema, and retrieve from both tables to get the complete value. If you need to delete fields, just have your code ignore them.
Code samples
Reading from a table
Anybody connected to the blockchain can run the view functions that read table content, provided they know which key to use (by default MUD does not keep a list of keys written to a table onchain, to save on storage operations).
All the functions to read from a MUD store are available.
In this example we use the Counter table in the vanilla template, which is a singleton so there is no key to worry about.
// 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 { Counter } from "../src/codegen/index.sol";
contract ReadCounter is Script {
function run() external {
address worldAddress = 0xC14fBdb7808D9e2a37c1a45b635C8C3fF64a1cc1;
StoreSwitch.setStoreAddress(worldAddress);
console.log("Counter value:", Counter.get());
}
}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 { Counter } from "../src/codegen/index.sol";It is easiest if we import the definitions of the table that were generated using mud tablegen.
address worldAddress = 0xC14fBdb7808D9e2a37c1a45b635C8C3fF64a1cc1;
StoreSwitch.setStoreAddress(worldAddress);Use StoreSwitch (opens in a new tab) with the World address.
console.log("Counter value:", Counter.get());Read the information.
If this had been a table with a key, we'd need to provide the key as a parameter to <table name>.get().
Writing to table
All the functions to write to a MUD store are available.
In this example we reset Counter to zero.
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 { Counter } from "../src/codegen/index.sol";
contract ResetCounter is Script {
function run() external {
// Specify a store so that you can use tables directly
address worldAddress = 0xC14fBdb7808D9e2a37c1a45b635C8C3fF64a1cc1;
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);
Counter.set(0);
vm.stopBroadcast();
console.log("Counter value:", Counter.get());
}
}Explanation
Counter.set(0);This is how you modify a table's value. If there was a key, it would be <table name>.set(<key fields>,<value fields>).