/
WorldRegistrationSystem.sol
224 lines (186 loc) · 10 KB
/
WorldRegistrationSystem.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import { Hook, HookLib } from "@latticexyz/store/src/Hook.sol";
import { System } from "../../../System.sol";
import { WorldContextConsumer, WORLD_CONTEXT_CONSUMER_INTERFACE_ID } from "../../../WorldContext.sol";
import { ResourceSelector } from "../../../ResourceSelector.sol";
import { Resource } from "../../../Types.sol";
import { SystemCall } from "../../../SystemCall.sol";
import { ROOT_NAMESPACE, ROOT_NAME, UNLIMITED_DELEGATION } from "../../../constants.sol";
import { AccessControl } from "../../../AccessControl.sol";
import { requireInterface } from "../../../requireInterface.sol";
import { NamespaceOwner } from "../../../tables/NamespaceOwner.sol";
import { ResourceAccess } from "../../../tables/ResourceAccess.sol";
import { Delegations } from "../../../tables/Delegations.sol";
import { ISystemHook, SYSTEM_HOOK_INTERFACE_ID } from "../../../interfaces/ISystemHook.sol";
import { IWorldErrors } from "../../../interfaces/IWorldErrors.sol";
import { IDelegationControl, DELEGATION_CONTROL_INTERFACE_ID } from "../../../interfaces/IDelegationControl.sol";
import { ResourceType } from "../tables/ResourceType.sol";
import { SystemHooks, SystemHooksTableId } from "../tables/SystemHooks.sol";
import { SystemRegistry } from "../tables/SystemRegistry.sol";
import { Systems } from "../tables/Systems.sol";
import { FunctionSelectors } from "../tables/FunctionSelectors.sol";
/**
* Functions related to registering resources other than tables in the World.
* Registering tables is implemented in StoreRegistrationSystem.sol
*/
contract WorldRegistrationSystem is System, IWorldErrors {
using ResourceSelector for bytes32;
/**
* Register a new namespace
*/
function registerNamespace(bytes16 namespace) public virtual {
bytes32 resourceSelector = ResourceSelector.from(namespace);
// Require namespace to not exist yet
if (ResourceType._get(namespace) != Resource.NONE) revert ResourceExists(resourceSelector.toString());
// Register namespace resource
ResourceType._set(namespace, Resource.NAMESPACE);
// Register caller as the namespace owner
NamespaceOwner._set(namespace, _msgSender());
// Give caller access to the new namespace
ResourceAccess._set(resourceSelector, _msgSender(), true);
}
/**
* Register a hook for the system at the given resource selector
*/
function registerSystemHook(
bytes32 resourceSelector,
ISystemHook hookAddress,
uint8 enabledHooksBitmap
) public virtual {
// Require the provided address to implement the ISystemHook interface
requireInterface(address(hookAddress), SYSTEM_HOOK_INTERFACE_ID);
// Require caller to own the namespace
AccessControl.requireOwner(resourceSelector, _msgSender());
// Register the hook
SystemHooks.push(resourceSelector, Hook.unwrap(HookLib.encode(address(hookAddress), enabledHooksBitmap)));
}
/**
* Unregister the given hook for the system at the given resource selector
*/
function unregisterSystemHook(bytes32 resourceSelector, ISystemHook hookAddress) public virtual {
// Require caller to own the namespace
AccessControl.requireOwner(resourceSelector, _msgSender());
// Remove the hook from the list of hooks for this resourceSelector in the system hooks table
HookLib.filterListByAddress(SystemHooksTableId, resourceSelector, address(hookAddress));
}
/**
* Register the given system in the given namespace.
* If the namespace doesn't exist yet, it is registered.
* The system is granted access to its namespace, so it can write to any table in the same namespace.
* If publicAccess is true, no access control check is performed for calling the system.
*
* Note: this function doesn't check whether a system already exists at the given selector,
* making it possible to upgrade systems.
*/
function registerSystem(bytes32 resourceSelector, WorldContextConsumer system, bool publicAccess) public virtual {
// Require the provided address to implement the WorldContextConsumer interface
requireInterface(address(system), WORLD_CONTEXT_CONSUMER_INTERFACE_ID);
// Require the name to not be the namespace's root name
if (resourceSelector.getName() == ROOT_NAME) revert InvalidSelector(resourceSelector.toString());
// Require this system to not be registered at a different resource selector yet
bytes32 existingResourceSelector = SystemRegistry._get(address(system));
if (existingResourceSelector != 0 && existingResourceSelector != resourceSelector) {
revert SystemExists(address(system));
}
// If the namespace doesn't exist yet, register it
// otherwise require caller to own the namespace
bytes16 namespace = resourceSelector.getNamespace();
if (ResourceType._get(namespace) == Resource.NONE) registerNamespace(namespace);
else AccessControl.requireOwner(namespace, _msgSender());
// Require no resource other than a system to exist at this selector yet
Resource resourceType = ResourceType._get(resourceSelector);
if (resourceType != Resource.NONE && resourceType != Resource.SYSTEM) {
revert ResourceExists(resourceSelector.toString());
}
// Check if a system already exists at this resource selector
address existingSystem = Systems._getSystem(resourceSelector);
// If there is an existing system with this resource selector, remove it
if (existingSystem != address(0)) {
// Remove the existing system from the system registry
SystemRegistry._deleteRecord(existingSystem);
// Remove the existing system's access to its namespace
ResourceAccess._deleteRecord(namespace, existingSystem);
} else {
// Otherwise, this is a new system, so register its resource type
ResourceType._set(resourceSelector, Resource.SYSTEM);
}
// Systems = mapping from resourceSelector to system address and publicAccess
Systems._set(resourceSelector, address(system), publicAccess);
// SystemRegistry = mapping from system address to resourceSelector
SystemRegistry._set(address(system), resourceSelector);
// Grant the system access to its namespace
ResourceAccess._set(namespace, address(system), true);
}
/**
* Register a World function selector for the given namespace, name and system function.
* TODO: instead of mapping to a resource, the function selector could map direcly to a system function,
* which would save one sload per call, but add some complexity to upgrading systems. TBD.
* (see https://github.com/latticexyz/mud/issues/444)
* TODO: replace separate systemFunctionName and systemFunctionArguments with a signature argument
*/
function registerFunctionSelector(
bytes32 resourceSelector,
string memory systemFunctionName,
string memory systemFunctionArguments
) public returns (bytes4 worldFunctionSelector) {
// Require the caller to own the namespace
AccessControl.requireOwner(resourceSelector, _msgSender());
// Compute global function selector
string memory namespaceString = ResourceSelector.toTrimmedString(resourceSelector.getNamespace());
string memory nameString = ResourceSelector.toTrimmedString(resourceSelector.getName());
worldFunctionSelector = bytes4(
keccak256(abi.encodePacked(namespaceString, "_", nameString, "_", systemFunctionName, systemFunctionArguments))
);
// Require the function selector to be globally unique
bytes32 existingResourceSelector = FunctionSelectors._getResourceSelector(worldFunctionSelector);
if (existingResourceSelector != 0) revert FunctionSelectorExists(worldFunctionSelector);
// Register the function selector
bytes memory systemFunctionSignature = abi.encodePacked(systemFunctionName, systemFunctionArguments);
bytes4 systemFunctionSelector = systemFunctionSignature.length == 0
? bytes4(0) // Save gas by storing 0x0 for empty function signatures (= fallback function)
: bytes4(keccak256(systemFunctionSignature));
FunctionSelectors._set(worldFunctionSelector, resourceSelector, systemFunctionSelector);
}
/**
* Register a root World function selector (without namespace / name prefix).
* Requires the caller to own the root namespace.
* TODO: instead of mapping to a resource, the function selector could map direcly to a system function,
* which would save one sload per call, but add some complexity to upgrading systems. TBD.
* (see https://github.com/latticexyz/mud/issues/444)
*/
function registerRootFunctionSelector(
bytes32 resourceSelector,
bytes4 worldFunctionSelector,
bytes4 systemFunctionSelector
) public returns (bytes4) {
// Require the caller to own the root namespace
AccessControl.requireOwner(ROOT_NAMESPACE, _msgSender());
// Require the function selector to be globally unique
bytes32 existingResourceSelector = FunctionSelectors._getResourceSelector(worldFunctionSelector);
if (existingResourceSelector != 0) revert FunctionSelectorExists(worldFunctionSelector);
// Register the function selector
FunctionSelectors._set(worldFunctionSelector, resourceSelector, systemFunctionSelector);
return worldFunctionSelector;
}
/**
* Register a delegation from the caller to the given delegatee.
*/
function registerDelegation(address delegatee, bytes32 delegationControlId, bytes memory initCallData) public {
// Store the delegation control contract address
Delegations.set({ delegator: _msgSender(), delegatee: delegatee, delegationControlId: delegationControlId });
// If the delegation is not unlimited...
if (delegationControlId != UNLIMITED_DELEGATION && initCallData.length > 0) {
// Require the delegationControl contract to implement the IDelegationControl interface
(address delegationControl, ) = Systems._get(delegationControlId);
requireInterface(delegationControl, DELEGATION_CONTROL_INTERFACE_ID);
// Call the delegation control contract's init function
SystemCall.call({
caller: _msgSender(),
resourceSelector: delegationControlId,
callData: initCallData,
value: 0
});
}
}
}