-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathModuleManager.sol
352 lines (305 loc) · 13 KB
/
ModuleManager.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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import { SentinelListLib, SENTINEL } from "sentinellist/SentinelList.sol";
import {
CallType, CALLTYPE_SINGLE, CALLTYPE_DELEGATECALL, CALLTYPE_STATIC
} from "../lib/ModeLib.sol";
import { AccountBase } from "./AccountBase.sol";
import "../interfaces/IERC7579Module.sol";
import "forge-std/interfaces/IERC165.sol";
import "./Receiver.sol";
/**
* @title ModuleManager
* @author zeroknots.eth | rhinestone.wtf
* @dev This contract manages Validator, Executor and Fallback modules for the MSA
* @dev it uses SentinelList to manage the linked list of modules
* NOTE: the linked list is just an example. accounts may implement this differently
*/
abstract contract ModuleManager is AccountBase, Receiver {
using SentinelListLib for SentinelListLib.SentinelList;
error InvalidModule(address module);
error NoFallbackHandler(bytes4 selector);
error CannotRemoveLastValidator();
event ValidatorUninstallFailed(address validator, bytes data);
event ExecutorUninstallFailed(address executor, bytes data);
// forgefmt: disable-next-line
// keccak256(abi.encode(uint256(keccak256("modulemanager.storage.msa")) - 1)) & ~bytes32(uint256(0xff));
bytes32 internal constant MODULEMANAGER_STORAGE_LOCATION =
0xe3a55571e8f241b58442871487cc151a8cb048bb4ad24e833467f724ec89a900;
struct FallbackHandler {
address handler;
CallType calltype;
}
/// @custom:storage-location erc7201:modulemanager.storage.msa
struct ModuleManagerStorage {
// linked list of validators. List is initialized by initializeAccount()
SentinelListLib.SentinelList $valdiators;
// linked list of executors. List is initialized by initializeAccount()
SentinelListLib.SentinelList $executors;
// single fallback handler for all fallbacks
// account vendors may implement this differently. This is just a reference implementation
mapping(bytes4 selector => FallbackHandler fallbackHandler) $fallbacks;
}
function $moduleManager() internal pure virtual returns (ModuleManagerStorage storage $ims) {
bytes32 position = MODULEMANAGER_STORAGE_LOCATION;
assembly {
$ims.slot := position
}
}
modifier onlyExecutorModule() {
SentinelListLib.SentinelList storage $executors = $moduleManager().$executors;
if (!$executors.contains(msg.sender)) revert InvalidModule(msg.sender);
_;
}
modifier onlyValidatorModule(address validator) {
SentinelListLib.SentinelList storage $valdiators = $moduleManager().$valdiators;
if (!$valdiators.contains(validator)) revert InvalidModule(validator);
_;
}
function _initModuleManager() internal virtual {
ModuleManagerStorage storage $ims = $moduleManager();
$ims.$executors.init();
$ims.$valdiators.init();
}
function isAlreadyInitialized() internal view virtual returns (bool) {
ModuleManagerStorage storage $ims = $moduleManager();
return $ims.$valdiators.alreadyInitialized();
}
/////////////////////////////////////////////////////
// Manage Validators
////////////////////////////////////////////////////
function _installValidator(address validator, bytes calldata data) internal virtual {
SentinelListLib.SentinelList storage $valdiators = $moduleManager().$valdiators;
$valdiators.push(validator);
IValidator(validator).onInstall(data);
}
function _uninstallValidator(address validator, bytes calldata data) internal {
// TODO: check if its the last validator. this might brick the account
SentinelListLib.SentinelList storage $valdiators = $moduleManager().$valdiators;
(address prev, bytes memory disableModuleData) = abi.decode(data, (address, bytes));
$valdiators.pop(prev, validator);
IValidator(validator).onUninstall(disableModuleData);
}
/*
function _tryUninstallValidators(bytes[] calldata data) internal {
SentinelListLib.SentinelList storage $valdiators = $moduleManager().$valdiators;
uint256 length = data.length;
uint256 index;
address validator = $valdiators.getNext(SENTINEL);
while (validator != SENTINEL) {
bytes memory uninstallData;
if (index < length) {
uninstallData = data[index];
}
try IValidator(validator).onUninstall(uninstallData) {} catch {
emit ValidatorUninstallFailed(validator, uninstallData);
}
validator = $valdiators.getNext(validator);
index++;
}
$valdiators.popAll();
}
*/
function _tryUninstallValidators() internal {
SentinelListLib.SentinelList storage $valdiators = $moduleManager().$valdiators;
address validator = $valdiators.getNext(SENTINEL);
while (validator != SENTINEL) {
try IValidator(validator).onUninstall("") { }
catch {
emit ValidatorUninstallFailed(validator, "");
}
validator = $valdiators.getNext(validator);
}
$valdiators.popAll();
}
function _isValidatorInstalled(address validator) internal view virtual returns (bool) {
SentinelListLib.SentinelList storage $valdiators = $moduleManager().$valdiators;
return $valdiators.contains(validator);
}
/**
* THIS IS NOT PART OF THE STANDARD
* Helper Function to access linked list
*/
function getValidatorsPaginated(
address cursor,
uint256 size
)
external
view
virtual
returns (address[] memory array, address next)
{
SentinelListLib.SentinelList storage $valdiators = $moduleManager().$valdiators;
return $valdiators.getEntriesPaginated(cursor, size);
}
/////////////////////////////////////////////////////
// Manage Executors
////////////////////////////////////////////////////
function _installExecutor(address executor, bytes calldata data) internal {
SentinelListLib.SentinelList storage $executors = $moduleManager().$executors;
$executors.push(executor);
IExecutor(executor).onInstall(data);
}
function _uninstallExecutor(address executor, bytes calldata data) internal {
SentinelListLib.SentinelList storage $executors = $moduleManager().$executors;
(address prev, bytes memory disableModuleData) = abi.decode(data, (address, bytes));
$executors.pop(prev, executor);
IExecutor(executor).onUninstall(disableModuleData);
}
/*
function _tryUninstallExecutors(bytes[] calldata data) internal {
SentinelListLib.SentinelList storage $executors = $moduleManager().$executors;
uint256 length = data.length;
uint256 index;
address executor = $executors.getNext(SENTINEL);
while (executor != SENTINEL) {
bytes memory uninstallData;
if (index < length) {
uninstallData = data[index];
}
try IExecutor(executor).onUninstall(uninstallData) {} catch {
emit ExecutorUninstallFailed(executor, uninstallData);
}
executor = $executors.getNext(executor);
index++;
}
$executors.popAll();
}
*/
function _tryUninstallExecutors() internal {
SentinelListLib.SentinelList storage $executors = $moduleManager().$executors;
address executor = $executors.getNext(SENTINEL);
while (executor != SENTINEL) {
try IExecutor(executor).onUninstall("") { }
catch {
emit ExecutorUninstallFailed(executor, "");
}
executor = $executors.getNext(executor);
}
$executors.popAll();
}
function _isExecutorInstalled(address executor) internal view virtual returns (bool) {
SentinelListLib.SentinelList storage $executors = $moduleManager().$executors;
return $executors.contains(executor);
}
/**
* THIS IS NOT PART OF THE STANDARD
* Helper Function to access linked list
*/
function getExecutorsPaginated(
address cursor,
uint256 size
)
external
view
virtual
returns (address[] memory array, address next)
{
SentinelListLib.SentinelList storage $executors = $moduleManager().$executors;
return $executors.getEntriesPaginated(cursor, size);
}
/////////////////////////////////////////////////////
// Manage Fallback
////////////////////////////////////////////////////
function _installFallbackHandler(address handler, bytes calldata params) internal virtual {
bytes4 selector = bytes4(params[0:4]);
CallType calltype = CallType.wrap(bytes1(params[4]));
bytes memory initData = params[5:];
if (_isFallbackHandlerInstalled(selector)) {
revert("Function selector already used");
}
$moduleManager().$fallbacks[selector] = FallbackHandler(handler, calltype);
IFallback(handler).onInstall(initData);
}
function _uninstallFallbackHandler(
address handler,
bytes calldata deInitData
)
internal
virtual
{
bytes4 selector = bytes4(deInitData[0:4]);
bytes memory _deInitData = deInitData[4:];
if (!_isFallbackHandlerInstalled(selector)) {
revert("Function selector not used");
}
FallbackHandler memory activeFallback = $moduleManager().$fallbacks[selector];
if (activeFallback.handler != handler) {
revert("Function selector not used by this handler");
}
$moduleManager().$fallbacks[selector] = FallbackHandler(address(0), CallType.wrap(0x00));
IFallback(handler).onUninstall(_deInitData);
}
function _isFallbackHandlerInstalled(bytes4 functionSig) internal view virtual returns (bool) {
FallbackHandler storage $fallback = $moduleManager().$fallbacks[functionSig];
return $fallback.handler != address(0);
}
function _isFallbackHandlerInstalled(
bytes4 functionSig,
address _handler
)
internal
view
virtual
returns (bool)
{
FallbackHandler storage $fallback = $moduleManager().$fallbacks[functionSig];
return $fallback.handler == _handler;
}
function getActiveFallbackHandler(bytes4 functionSig)
external
view
virtual
returns (FallbackHandler memory)
{
return $moduleManager().$fallbacks[functionSig];
}
// FALLBACK
fallback() external payable override(Receiver) receiverFallback {
FallbackHandler storage $fallbackHandler = $moduleManager().$fallbacks[msg.sig];
address handler = $fallbackHandler.handler;
CallType calltype = $fallbackHandler.calltype;
if (handler == address(0)) revert NoFallbackHandler(msg.sig);
if (calltype == CALLTYPE_STATIC) {
assembly {
function allocate(length) -> pos {
pos := mload(0x40)
mstore(0x40, add(pos, length))
}
let calldataPtr := allocate(calldatasize())
calldatacopy(calldataPtr, 0, calldatasize())
// The msg.sender address is shifted to the left by 12 bytes to remove the padding
// Then the address without padding is stored right after the calldata
let senderPtr := allocate(20)
mstore(senderPtr, shl(96, caller()))
// Add 20 bytes for the address appended add the end
let success :=
staticcall(gas(), handler, calldataPtr, add(calldatasize(), 20), 0, 0)
let returnDataPtr := allocate(returndatasize())
returndatacopy(returnDataPtr, 0, returndatasize())
if iszero(success) { revert(returnDataPtr, returndatasize()) }
return(returnDataPtr, returndatasize())
}
}
if (calltype == CALLTYPE_SINGLE) {
assembly {
function allocate(length) -> pos {
pos := mload(0x40)
mstore(0x40, add(pos, length))
}
let calldataPtr := allocate(calldatasize())
calldatacopy(calldataPtr, 0, calldatasize())
// The msg.sender address is shifted to the left by 12 bytes to remove the padding
// Then the address without padding is stored right after the calldata
let senderPtr := allocate(20)
mstore(senderPtr, shl(96, caller()))
// Add 20 bytes for the address appended add the end
let success := call(gas(), handler, 0, calldataPtr, add(calldatasize(), 20), 0, 0)
let returnDataPtr := allocate(returndatasize())
returndatacopy(returnDataPtr, 0, returndatasize())
if iszero(success) { revert(returnDataPtr, returndatasize()) }
return(returnDataPtr, returndatasize())
}
}
}
}