Namespaces and Access Control
Access control in a World
is based on namespaces.
Every resource is part of a namespace (a blank namespace refers to the root
namespace).
Access to resources is managed by the owner of the namespace.
Resource identifiers
A ResourceId
is a 32-byte value that uniquely identifies a resource in a World
.
It is two bytes of resource type followed by 14 bytes of namespace and then 16 bytes of the name of the actual resource.
Currently, MUD supports these resource types:
- Table (
tb
) (opens in a new tab). An onchain table whose information is available both onchain (through calls toview
functions) and offchain (either through calls toview
functions, events, or an indexer). - Offchain table (
ot
) (opens in a new tab). An offchain table whose information is only available offchain (either through events, or through an indexer). - Namespace (
ns
) (opens in a new tab). A namespace is a container for tables, offchain tables, and systems, as explained here. - System (
sy
) (opens in a new tab). A system contains logic that interact with table data onchain. - Module (
md
) (opens in a new tab). A module is a deployed script that can install tables, systems, etc.
Access control can be attached to any resource.
Access control levels
There are three access levels in MUD.
"No" access
This is the level that most users of a MUD application have for all resources. Users on this level are allowed to do the following:
- Read data from tables. Any information posted or created on the blockchain is public, so this information is available anyway.
- Call functions on public
System
s.
Access
This level of access is often required for software that is part of the application. Access can be granted either on a namespace basis (access to the namespace gives access to all the resources inside it) or on an individual resource.
Entities with this level of access can do anything that "no" access allows, and also:
- Write directly to tables.
This is as opposed to users with no access, who can only modify table information by going through a public
System
(by default, aSystem
has access to its own namespace, which includes all of the namespace's tables). - Call functions on private
System
s. - In the case of access to a namespace, withdraw from the ETH balance of the namespace. To avoid giving this permission, you can give an address access to every resource in the namespace, rather than to the namespace itself.
Ownership
Namespace
is the only resource type that has an owner.
The owner is a single account (but by using multi-sig accounts ownership can be shared).
The owner is an administrative account, a superuser. When you change ownership, the new owner gets access to the namespace, and the old owner loses it. Therefore, owners can do anything an address with access can. Additionally, owners can also:
- Grant and revoke access within the namespace (either to the namespace itself or to resources inside it).
- Transfer namespace ownership.
- Register a namespace delegation control.
- Register new tables.
- Register store hooks for tables in the namespace.
- Register new systems.
- Upgrade existing systems.
- Register system hooks for systems in the namespace.
- Register function selectors for systems in the namespace.
Owners can modify which contract a World
uses for each System
.
Note: once ownership of a namespace is burned (i.e. transferred to address(0)
), no new access can be granted or revoked, ownership can't be transferred again, and System
s can't be added, removed, or upgraded (unless the System
is itself a proxy (opens in a new tab)).
Modifying access control
By default access to a namespace is granted to:
- The namespace owner
- The
System
s of that namespace
Namespace access provides access to all the resources within the namespace, so there is no need, by default, for access to the resources within the namespace.
If that is insufficient, AccessManagementSystem
(opens in a new tab) lets you grantAccess
(opens in a new tab), revokeAccess
(opens in a new tab), and transferOwnership
(opens in a new tab) of a namespace.
For example, here is a script that grants access to the Counter
table to one address and revokes it for another.
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";
import { IWorld } from "../src/codegen/world/IWorld.sol";
import { CounterTableId } from "../src/codegen/index.sol";
contract Permissions is Script {
function run() external {
address worldAddress = 0x6E9474e9c83676B9A71133FF96Db43E7AA0a4342;
// Load the private key from the `PRIVATE_KEY` environment variable (in .env)
uint256 namespaceOwnerPrivateKey = vm.envUint("PRIVATE_KEY");
// Start broadcasting transactions from the account owning the namespace
vm.startBroadcast(namespaceOwnerPrivateKey);
IWorld(worldAddress).grantAccess(CounterTableId, address(0));
IWorld(worldAddress).revokeAccess(CounterTableId, address(1));
vm.stopBroadcast();
}
}