-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathModuleManagerInternals.sol
More file actions
323 lines (270 loc) · 12.8 KB
/
ModuleManagerInternals.sol
File metadata and controls
323 lines (270 loc) · 12.8 KB
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
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.20;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {collectReturnData} from "../helpers/CollectReturnData.sol";
import {MAX_VALIDATION_ASSOC_HOOKS} from "../helpers/Constants.sol";
import {ExecutionManifest, ManifestExecutionHook} from "../interfaces/IExecutionModule.sol";
import {
HookConfig,
IModularAccount,
ModuleEntity,
ValidationConfig,
ValidationFlags
} from "../interfaces/IModularAccount.sol";
import {IModule} from "../interfaces/IModule.sol";
import {HookConfigLib} from "../libraries/HookConfigLib.sol";
import {KnownSelectorsLib} from "../libraries/KnownSelectorsLib.sol";
import {ModuleEntityLib} from "../libraries/ModuleEntityLib.sol";
import {ValidationConfigLib} from "../libraries/ValidationConfigLib.sol";
import {
AccountStorage,
ExecutionStorage,
ValidationStorage,
getAccountStorage,
toModuleEntity,
toSetValue
} from "./AccountStorage.sol";
abstract contract ModuleManagerInternals is IModularAccount {
using EnumerableSet for EnumerableSet.Bytes32Set;
using ModuleEntityLib for ModuleEntity;
using ValidationConfigLib for ValidationConfig;
using HookConfigLib for HookConfig;
error ArrayLengthMismatch();
error Erc4337FunctionNotAllowed(bytes4 selector);
error ExecutionFunctionAlreadySet(bytes4 selector);
error IModuleFunctionNotAllowed(bytes4 selector);
error InterfaceNotSupported(address module);
error NativeFunctionNotAllowed(bytes4 selector);
error NullModule();
error ExecutionHookAlreadySet(HookConfig hookConfig);
error ModuleInstallCallbackFailed(address module, bytes revertReason);
error ModuleNotInstalled(address module);
error PreValidationHookLimitExceeded();
error ValidationAlreadySet(bytes4 selector, ModuleEntity validationFunction);
// Storage update operations
function _setExecutionFunction(
bytes4 selector,
bool skipRuntimeValidation,
bool allowGlobalValidation,
address module
) internal {
ExecutionStorage storage _executionStorage = getAccountStorage().executionStorage[selector];
if (_executionStorage.module != address(0)) {
revert ExecutionFunctionAlreadySet(selector);
}
// Make sure incoming execution function does not collide with any native functions (data are stored on the
// account implementation contract)
if (KnownSelectorsLib.isNativeFunction(selector)) {
revert NativeFunctionNotAllowed(selector);
}
// Make sure incoming execution function is not a function in IModule
if (KnownSelectorsLib.isIModuleFunction(selector)) {
revert IModuleFunctionNotAllowed(selector);
}
// Also make sure it doesn't collide with functions defined by ERC-4337
// and called by the entry point. This prevents a malicious module from
// sneaking in a function with the same selector as e.g.
// `validatePaymasterUserOp` and turning the account into their own
// personal paymaster.
if (KnownSelectorsLib.isErc4337Function(selector)) {
revert Erc4337FunctionNotAllowed(selector);
}
_executionStorage.module = module;
_executionStorage.skipRuntimeValidation = skipRuntimeValidation;
_executionStorage.allowGlobalValidation = allowGlobalValidation;
}
function _removeExecutionFunction(bytes4 selector) internal {
ExecutionStorage storage _executionStorage = getAccountStorage().executionStorage[selector];
_executionStorage.module = address(0);
_executionStorage.skipRuntimeValidation = false;
_executionStorage.allowGlobalValidation = false;
}
function _removeValidationFunction(ModuleEntity validationFunction) internal {
ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[validationFunction];
_validationStorage.validationFlags = ValidationFlags.wrap(0);
}
function _addExecHooks(EnumerableSet.Bytes32Set storage hooks, HookConfig hookConfig) internal {
if (!hooks.add(toSetValue(hookConfig))) {
revert ExecutionHookAlreadySet(hookConfig);
}
}
function _removeExecHooks(EnumerableSet.Bytes32Set storage hooks, HookConfig hookConfig) internal {
hooks.remove(toSetValue(hookConfig));
}
function _installExecution(
address module,
ExecutionManifest calldata manifest,
bytes calldata moduleInstallData
) internal {
AccountStorage storage _storage = getAccountStorage();
if (module == address(0)) {
revert NullModule();
}
// Update components according to the manifest.
uint256 length = manifest.executionFunctions.length;
for (uint256 i = 0; i < length; ++i) {
bytes4 selector = manifest.executionFunctions[i].executionSelector;
bool skipRuntimeValidation = manifest.executionFunctions[i].skipRuntimeValidation;
bool allowGlobalValidation = manifest.executionFunctions[i].allowGlobalValidation;
_setExecutionFunction(selector, skipRuntimeValidation, allowGlobalValidation, module);
}
length = manifest.executionHooks.length;
for (uint256 i = 0; i < length; ++i) {
ManifestExecutionHook memory mh = manifest.executionHooks[i];
EnumerableSet.Bytes32Set storage execHooks =
_storage.executionStorage[mh.executionSelector].executionHooks;
HookConfig hookConfig = HookConfigLib.packExecHook({
_module: module,
_entityId: mh.entityId,
_hasPre: mh.isPreHook,
_hasPost: mh.isPostHook
});
_addExecHooks(execHooks, hookConfig);
}
length = manifest.interfaceIds.length;
for (uint256 i = 0; i < length; ++i) {
_storage.supportedIfaces[manifest.interfaceIds[i]] += 1;
}
_onInstall(module, moduleInstallData);
emit ExecutionInstalled(module, manifest);
}
function _uninstallExecution(address module, ExecutionManifest calldata manifest, bytes calldata uninstallData)
internal
{
AccountStorage storage _storage = getAccountStorage();
// Remove components according to the manifest, in reverse order (by component type) of their installation.
uint256 length = manifest.executionHooks.length;
for (uint256 i = 0; i < length; ++i) {
ManifestExecutionHook memory mh = manifest.executionHooks[i];
EnumerableSet.Bytes32Set storage execHooks =
_storage.executionStorage[mh.executionSelector].executionHooks;
HookConfig hookConfig = HookConfigLib.packExecHook({
_module: module,
_entityId: mh.entityId,
_hasPre: mh.isPreHook,
_hasPost: mh.isPostHook
});
_removeExecHooks(execHooks, hookConfig);
}
length = manifest.executionFunctions.length;
for (uint256 i = 0; i < length; ++i) {
bytes4 selector = manifest.executionFunctions[i].executionSelector;
_removeExecutionFunction(selector);
}
length = manifest.interfaceIds.length;
for (uint256 i = 0; i < length; ++i) {
_storage.supportedIfaces[manifest.interfaceIds[i]] -= 1;
}
// Clear the module storage for the account.
bool onUninstallSuccess = _onUninstall(module, uninstallData);
emit ExecutionUninstalled(module, onUninstallSuccess, manifest);
}
function _onInstall(address module, bytes calldata data) internal {
if (data.length > 0) {
// solhint-disable-next-line no-empty-blocks
try IModule(module).onInstall(data) {}
catch {
bytes memory revertReason = collectReturnData();
revert ModuleInstallCallbackFailed(module, revertReason);
}
}
}
function _onUninstall(address module, bytes calldata data) internal returns (bool onUninstallSuccess) {
onUninstallSuccess = true;
if (data.length > 0) {
// Clear the module storage for the account.
// solhint-disable-next-line no-empty-blocks
try IModule(module).onUninstall(data) {}
catch {
onUninstallSuccess = false;
}
}
}
function _installValidation(
ValidationConfig validationConfig,
bytes4[] calldata selectors,
bytes calldata installData,
bytes[] calldata hooks
) internal {
ValidationStorage storage _validationStorage =
getAccountStorage().validationStorage[validationConfig.moduleEntity()];
(ModuleEntity moduleEntity, ValidationFlags validationFlags) = validationConfig.unpack();
for (uint256 i = 0; i < hooks.length; ++i) {
HookConfig hookConfig = HookConfig.wrap(bytes25(hooks[i][:25]));
bytes calldata hookData = hooks[i][25:];
if (hookConfig.isValidationHook()) {
_validationStorage.validationHooks.push(hookConfig);
// Avoid collision between reserved index and actual indices
if (_validationStorage.validationHooks.length > MAX_VALIDATION_ASSOC_HOOKS) {
revert PreValidationHookLimitExceeded();
}
_onInstall(hookConfig.module(), hookData);
continue;
}
// Hook is an execution hook
_addExecHooks(_validationStorage.executionHooks, hookConfig);
_onInstall(hookConfig.module(), hookData);
}
for (uint256 i = 0; i < selectors.length; ++i) {
bytes4 selector = selectors[i];
if (!_validationStorage.selectors.add(toSetValue(selector))) {
revert ValidationAlreadySet(selector, moduleEntity);
}
}
_validationStorage.validationFlags = validationFlags;
_onInstall(validationConfig.module(), installData);
emit ValidationInstalled(validationConfig.module(), validationConfig.entityId());
}
function _uninstallValidation(
ModuleEntity validationFunction,
bytes calldata uninstallData,
bytes[] calldata hookUninstallDatas
) internal {
ValidationStorage storage _validationStorage = getAccountStorage().validationStorage[validationFunction];
bool onUninstallSuccess = true;
_removeValidationFunction(validationFunction);
// Send `onUninstall` to hooks
if (hookUninstallDatas.length > 0) {
// If any uninstall data is provided, assert it is of the correct length.
if (
hookUninstallDatas.length
!= _validationStorage.validationHooks.length + _validationStorage.executionHooks.length()
) {
revert ArrayLengthMismatch();
}
// Hook uninstall data is provided in the order of pre validation hooks, then execution hooks.
uint256 hookIndex = 0;
for (uint256 i = 0; i < _validationStorage.validationHooks.length; ++i) {
bytes calldata hookData = hookUninstallDatas[hookIndex];
(address hookModule,) =
ModuleEntityLib.unpack(_validationStorage.validationHooks[i].moduleEntity());
onUninstallSuccess = onUninstallSuccess && _onUninstall(hookModule, hookData);
hookIndex++;
}
for (uint256 i = 0; i < _validationStorage.executionHooks.length(); ++i) {
bytes calldata hookData = hookUninstallDatas[hookIndex];
(address hookModule,) =
ModuleEntityLib.unpack(toModuleEntity(_validationStorage.executionHooks.at(i)));
onUninstallSuccess = onUninstallSuccess && _onUninstall(hookModule, hookData);
hookIndex++;
}
}
// Clear all stored hooks
delete _validationStorage.validationHooks;
EnumerableSet.Bytes32Set storage executionHooks = _validationStorage.executionHooks;
uint256 executionHookLen = executionHooks.length();
for (uint256 i = 0; i < executionHookLen; ++i) {
bytes32 executionHook = executionHooks.at(0);
executionHooks.remove(executionHook);
}
// Clear selectors
uint256 selectorLen = _validationStorage.selectors.length();
for (uint256 i = 0; i < selectorLen; ++i) {
bytes32 selectorSetValue = _validationStorage.selectors.at(0);
_validationStorage.selectors.remove(selectorSetValue);
}
(address module, uint32 entityId) = ModuleEntityLib.unpack(validationFunction);
onUninstallSuccess = onUninstallSuccess && _onUninstall(module, uninstallData);
emit ValidationUninstalled(module, entityId, onUninstallSuccess);
}
}